Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
92 commits
Select commit Hold shift + click to select a range
a1749fb
add Mixpanel, switch to ENV VARs, and split up debug logging
lisensee Sep 13, 2015
2eaa726
update README
lisensee Sep 13, 2015
e358956
save performance if env vars aren't set
lisensee Sep 13, 2015
4ad749b
first attempt at Localytics
lisensee Sep 14, 2015
32af4b4
enable Localytics logging
lisensee Sep 14, 2015
c8110e7
new line fix
lisensee Sep 14, 2015
c8e4259
pad with extra brakes
lisensee Sep 14, 2015
821c90d
Try another way to handle blog newline
lisensee Sep 14, 2015
2782658
newline in logs
lisensee Sep 14, 2015
0dbf4db
quote characters
lisensee Sep 14, 2015
4f49f7a
debug logging parity
lisensee Sep 14, 2015
7c26ade
remove double query string stringify
lisensee Sep 14, 2015
1713ebd
try grouping before string
lisensee Sep 14, 2015
31bfc8f
move constants to qs and test logging
lisensee Sep 14, 2015
341d93f
no data passing test
lisensee Sep 14, 2015
b6562c3
JSON String the QS String test
lisensee Sep 14, 2015
035f921
comment all the Localytics stuff out
lisensee Sep 14, 2015
be0fba6
Localytics: switch to JSON stringify and then escape
lisensee Sep 14, 2015
7d4a98b
Localytics: no need for "data" in the JSON.
lisensee Sep 14, 2015
7e0598a
Localytics: switch escape to encodeURIComponent
lisensee Sep 14, 2015
c2f7d20
Localytics: playing with the newline issue
lisensee Sep 14, 2015
48f6d2d
Localytics: didn't need to manually encode.
lisensee Sep 14, 2015
a398979
Mongo: Initial setup
lisensee Sep 14, 2015
d4d7067
object goof
lisensee Sep 14, 2015
6c45526
double quotes are your friend
lisensee Sep 14, 2015
69cb88a
Mongo: remember to user set env vars
lisensee Sep 14, 2015
3fdc910
better debugging, lighten logging if analytics option not enabled
lisensee Sep 14, 2015
4e4d8f2
quiet non-error console log
lisensee Sep 14, 2015
e28e534
Mixpanel: Engage Set and Add must be separate
lisensee Sep 14, 2015
55a8380
separate out mongo and analytics to lighten memory
lisensee Sep 14, 2015
0812fce
env var environment
lisensee Sep 14, 2015
8a1a656
maybe extended values
lisensee Sep 14, 2015
6dddab7
nope, that wasn't it
lisensee Sep 14, 2015
5a58ba2
why not try - prod vs dev
lisensee Sep 14, 2015
6387322
ugh, if statements
lisensee Sep 14, 2015
f709a20
support for verbose post response, set separate string for URLs
lisensee Sep 14, 2015
722a5a3
switch env_var debug to string
lisensee Sep 14, 2015
74be406
redesign logging
lisensee Sep 14, 2015
3557f4b
oops
lisensee Sep 14, 2015
7106e87
wrong identifier
lisensee Sep 14, 2015
9ff24fa
non-identifiable hmmm
lisensee Sep 14, 2015
2a332f7
simplify GA to find bug
lisensee Sep 14, 2015
f8a679f
need to use JSLint more often
lisensee Sep 14, 2015
2ca1d05
deal with true/false on db and analytics options later
lisensee Sep 14, 2015
636a2b0
stringify errors
lisensee Sep 14, 2015
8b1c153
deal with debug tracking paths later
lisensee Sep 15, 2015
b5a2806
include Logentries service
lisensee Sep 16, 2015
91c9cbb
Logentries: locate le_node
lisensee Sep 16, 2015
26f0973
Logentries: underscore not dash
lisensee Sep 16, 2015
b8a92aa
env_var keeps on catching me
lisensee Sep 16, 2015
4fb737d
Logentries: bump to 1.1.x
lisensee Sep 16, 2015
8a1f7ea
-Localytics -MixpanelEngage closed conn
lisensee Jan 30, 2018
5dd26d0
lisensee Jul 8, 2018
88c9d7f
lisensee Jul 8, 2018
90ec3c6
lisensee Jul 8, 2018
8f26eb1
lisensee Jul 8, 2018
e5e6bc6
lisensee Jul 8, 2018
f2241c0
lisensee Jul 8, 2018
e6c9855
lisensee Jul 8, 2018
a05edcf
lisensee Jul 8, 2018
99d025b
lisensee Jul 8, 2018
6952ed4
SegmentIO
lisensee Jul 9, 2018
db0a2de
lisensee Jul 9, 2018
aef1b95
lisensee Jul 9, 2018
24b43ba
Log fix & tight code
lisensee Jul 10, 2018
47c52f8
Ping Date Time
lisensee Jan 22, 2019
b370c95
D/T & math security update
lisensee Jan 22, 2019
836ecc5
remove datetime and cleanup datetime formatting
lisensee Jan 22, 2019
b2a7ba5
fix Buffer
lisensee Apr 27, 2020
ea67d74
mongodb clusters
lisensee Apr 28, 2020
5a0a017
Upgrade mongo version
lisensee Apr 28, 2020
743c8f8
new mongo connection
lisensee Apr 28, 2020
a09b27a
Revert "new mongo connection"
lisensee Apr 28, 2020
e1f7500
put back old mongo stuff
lisensee Apr 28, 2020
d753998
upgrade mongo connector
lisensee May 1, 2020
388645c
mongodb2.2+
lisensee May 1, 2020
72f38a3
Merge branch 'mongodb3'
lisensee May 1, 2020
f21af8d
switch back to old db setup
lisensee May 1, 2020
9433481
back to mongo cluster
lisensee May 1, 2020
cb9e7ee
Mongo Security Fix
lisensee Sep 15, 2020
6a725b2
Rollback
lisensee Sep 15, 2020
0785dd7
Merge remote-tracking branch 'origin/master'
lisensee Sep 15, 2020
8df7657
Fix le_node error levels
sahava Oct 27, 2020
b11e09c
Update mongodb and refactor code to use latest client.
sahava Oct 27, 2020
c2548aa
Update mongodb to use .insertOne()
sahava Oct 27, 2020
f90d9be
Fix missing db reference
sahava Oct 27, 2020
ddaf120
Switch db.close() to client.close()
sahava Oct 27, 2020
ebc8926
Update mathjs version
lisensee Jul 27, 2021
4164eac
Upgrade le_node version
lisensee Jul 27, 2021
2f43234
Patch lodash
lisensee Jul 27, 2021
6141e15
Rollback lodash
lisensee Jul 27, 2021
3153065
Drop LogEntries & Env Mongo
lisensee Nov 8, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
# Slackalytics

Slackalytics is a textual analysis bot built in Node.js that allows for deeper custome analytics by sending message strings to Google Analysis via Slacks realtime API and Google Analytics Measurement Protocol.

More Readme info is coming soon

In the meantime checkout the post for this on my blog: http://nicomiceli.com/slackalytics/
Slackalytics is a textual analysis bot built in Node.js that allows for deeper customer analytics by sending message strings to analytics solutions via Slacks realtime outbound webhook API and respective analytics solution data collection services.
264 changes: 221 additions & 43 deletions app.js
Original file line number Diff line number Diff line change
@@ -1,93 +1,271 @@
//Set up Env Vars
var env_var = {
environment: process.env.NODE_ENV || 'development', // supports: production and development
log_level: process.env.LOGLEVEL, // supports: debug, error, warn, info, off
write_mongo: process.env.MONGO_ENABLED, // true / false
analytics_track: process.env.ANALYTICS_ENABLED, // true / false
mongo_user: process.env.MONGO_USER_PROD,
mongo_password: process.env.MONGO_PASSWORD_PROD,
mongo_server: process.env.MONGO_SERVER_PROD,
mongo_port: process.env.MONGO_PORT_PROD,
mongo_db: process.env.MONGO_DB_PROD,
mongo_cluster_db: process.env.MONGO_CLUSTER_DB,
mongo_shard_1: process.env.MONGO_SHARD_ONE,
mongo_shard_1_port: process.env.MONGO_SHARD_ONE_PORT,
mongo_shard_2: process.env.MONGO_SHARD_TWO,
mongo_shard_2_port: process.env.MONGO_SHARD_TWO_PORT,
mongo_shard_3: process.env.MONGO_SHARD_THREE,
mongo_shard_3_port: process.env.MONGO_SHARD_THREE_PORT,
mongo_shard_query: process.env.MONGO_QUERY,
mongo_cluster_server: process.env.MONGO_CLUSTER_SERVER,
ga_key: process.env.GOOGLE_ANALYTICS_PROD,
mixpanel_key: process.env.MIXPANEL_PROD,
segmentio_key: process.env.SEGMENTIO_PROD
};

//Set up Reqs
var express = require('express');
var bodyParser = require('body-parser');
var request = require('request');
var qs = require('querystring');

var math = require('mathjs');
var moment = require('moment');
var uuid = require('node-uuid');
var mongodb = require('mongodb');
var Analytics = require('analytics-node');

//Server Details
var app = express();
var port = process.env.PORT || 3000;

//SegmentIO
var analytics = new Analytics(env_var.segmentio_key);

//Logger
var logger = exports;
logger.debugLevel = env_var.log_level || 'warn';
logger.log = function(level, message) {
var levels = ['debug', 'error', 'warn', 'info', 'off'];
if (levels.indexOf(level) >= levels.indexOf(logger.debugLevel) ) {
if (typeof message !== 'string') {
message = JSON.stringify(message);
}
console.log(level+': '+message);
}
};

//Set Body Parser
app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.json());
app.use(bodyParser.json({ type: 'application/vnd.api+json' }));

//Simple Base64 handler
var base64 = exports;
base64.encode = function (unencoded) {
return new Buffer.from(unencoded || '').toString('base64');
};
base64.decode = function (encoded) {
return new Buffer.from(encoded || '', 'base64').toString('utf8');
};

//Routes
app.get('/', function(req, res){
res.send('here');
});

app.post('/collect', function(req, res){
app.get('/ping', function(req, res){
res.send('I\'m alive!' + " Ping Time: " + new Date().toISOString().replace(/T/, ' ').replace(/\..+/, ''));
});

app.post('/collect', function(req, res){

var channel = {
id: req.body.channel_id,
name: req.body.channel_name
};

var user = {
id: req.body.user_id
id: req.body.user_id,
name: req.body.user_name
};

var teamDomain = req.body.team_domain;

var msgTime = math.round(req.body.timestamp, 0); //in epoch by day

var msgSeconds = req.body.timestamp.replace(/./g,''); //in epoch by millisecond

var msgText = req.body.text;

function searchM(regex){
var searchStr = msgText.match(regex);
if(searchStr != null){
if(searchStr !== null){
return searchStr.length;
}
return 0;
};
}

function searchS(regex){
var searchStr = msgText.split(regex);
if(searchStr != undefined){
if(searchStr !== undefined){
return searchStr.length;
}
return 0;
};
}

var wordCount = searchS(/\s+\b/);
var emojiCount = searchM(/:[a-z_0-9]*:/g);
var emojiCount = searchM(/:[a-z_0-9_-]*:/g);
var exclaCount = searchM(/!/g);
var questionMark = searchM(/\?/g);
var elipseCount = searchM(/\.\.\./g);


//Structure Data
var data = {
v: 1,
tid: "UA-XXXXXXXX-1",
cid: user.id,
ds: "slack", //data source
cs: "slack", // campaign source
cd1: user.id,
cd2: channel.name,
cd3: msgText,
cm1: wordCount,
cm2: emojiCount,
cm3: exclaCount,
// cm4: letterCount,
cm5: elipseCount,
cm6: questionMark, //need to set up in GA
t: "event",
ec: "slack: "+ channel.name + "|" + channel.id,
ea: "post by " + user.id,
el: msgText,
ev: 1
};
console.log(JSON.stringify(data));
console.log(req.body);
//Make Post Request
request.post("https://www.google-analytics.com/collect?" + qs.stringify(data),
function(error, resp, body){
console.log(error);
})
var elipsisCount = searchM(/\.\.\./g);
var alertCount = searchM(/<!/g);
var urlCount = searchM(/<http/g);


if (env_var.write_mongo) {
// var url = "mongodb://"+env_var.mongo_user+":"+env_var.mongo_password+"@"+env_var.mongo_shard_1+":"+env_var.mongo_shard_1_port+","+env_var.mongo_shard_2+":"+env_var.mongo_shard_2_port+","+env_var.mongo_shard_3+":"+env_var.mongo_shard_3_port+"/"+env_var.mongo_cluster_db+"?"+env_var.mongo_shard_query;
// var url = "mongodb://"+env_var.mongo_user+":"+env_var.mongo_password+"@"+env_var.mongo_server+":"+env_var.mongo_port+"/"+env_var.mongo_db;
var url = "mongodb+srv://"+env_var.mongo_user+":"+encodeURIComponent(env_var.mongo_password)+"@"+env_var.mongo_cluster_server+"/"+env_var.mongo_cluster_db+"?retryWrites=true&w=majority";
var collection_name = "posts";

mongodb.MongoClient.connect(url, function(err, client) {
if (err) {logger.log('error', 'Mongo Error: Unable to connect to the server. Error: ' + JSON.stringify(err));}
else {
logger.log('debug','Mongo: Connection established to '+url);
var db = client.db(env_var.mongo_cluster_db);
var collection = db.collection(collection_name);

// Insert post contents
collection.insertOne(req.body, function (err, result) {
if (err) {logger.log('error', 'Mongo Error: '+JSON.stringify(err));}
else {logger.log('debug', 'Mongo: '+result.length+' inserted documents into the '+collection_name+' collection. The documents inserted with "_id" are: '+JSON.stringify(result)); }
//Close connection
client.close(function (err) {
if (err) {logger.log('error','Mongo Error: '+JSON.stringify(err));}
});
});
}
});
}

if (env_var.analytics_track) {
// GOOGLE ANALYTICS COLLECT AND POST
if (env_var.ga_key) {
var GAdata = {
v: 1,
tid: env_var.ga_key,
cid: user.id,
uid: user.name,
ds: "slack",
ua: "Slack 1.2.0",
cd1: user.name + " (" + user.id + ")",
cd2: channel.name + " (" + channel.id + ")",
cd3: msgText,
/*cm1: wordCount,
cm2: emojiCount,
cm3: exclaCount,
cm4: letterCount,
cm5: elipsisCount,
cm6: questionMark,*/
dh: teamDomain+".slack.com",
dp: "/"+channel.name,
dt: "Slack Channel: "+channel.name,
t: "event",
ec: "slack: "+ channel.name + " | " + channel.id,
ea: "message posted",
el: "posted by: "+ user.name + " (" + user.id + ")",
an: "Slackalytics",
av: "1.2.0",
ni: 1,
ev: 1
};

var google_url = {track: "https://www.google-analytics.com/collect?"};

logger.log('debug', "Google Analytics Data: "+JSON.stringify(GAdata));
logger.log('debug', "Google Analytics Tracking Post Output: "+google_url.track + qs.stringify(GAdata));

// Post Data
request.post(google_url.track + qs.stringify(GAdata), function(error, resp, body) {
if(error) {logger.log('error', 'Google Analytics Error: '+JSON.stringify(error));}
else {logger.log('debug', 'Google Analytics Tracking Response Debug: '+JSON.stringify(resp));}
});
} else {logger.log('error',"Google Analytics account ID not defined as environment variable");}

// MIXPANEL COLLECT AND POST
if (env_var.mixpanel_key) {
var mixTrack = {
event: "message posted",
properties: {
$os: "Slack",
$browser: "Slack",
$current_url: "https://"+teamDomain+".slack.com/" + channel.name,
mp_lib: "slack_nodeJS",
$lib_version: "Slack 1.2.0",
distinct_id: user.id,
/*num_words: wordCount,
num_emoji: emojiCount,
num_exclamation: exclaCount,
num_ellipsis: elipsisCount,
num_questions: questionMark,*/
message: msgText,
channel: channel.name + " (" + channel.id + ")",
user: user.name + " (" + user.id + ")",
time: msgTime,
seconds: msgSeconds,
token: env_var.mixpanel_key
}
};

var mixpanel_url = {track: "https://api.mixpanel.com/track/?ip=0&data="};

logger.log('debug', "Mixpanel Tracking Data: "+JSON.stringify(mixTrack));
logger.log('debug', "Mixpanel Tracking Post Output: "+mixpanel_url.track + encodeURIComponent(base64.encode(JSON.stringify(mixTrack))));

// Post Data
request.post(mixpanel_url.track + encodeURIComponent(base64.encode(JSON.stringify(mixTrack))), function(error, resp, body) {
if(error) {logger.log('error','Mixpanel Error: '+JSON.stringify(error));}
else {logger.log('debug', 'Mixpanel Tracking Response Debug: '+JSON.stringify(resp));}
});
} else {logger.log('error', "Mixpanel account ID not defined as environment variable");}

// SEGMENTIO COLLECT AND POST
if (env_var.segmentio_key) {
analytics.identify({
userId: user.id,
traits: {
username: user.name
}
});

analytics.track({
userId: user.id,
event: 'message posted',
properties: {
message: msgText,
channel: channel.name + " (" + channel.id + ")",
user: user.name + " (" + user.id + ")",
time: msgTime,
seconds: msgSeconds,
os: "Slack",
browser: "Slack",
current_url: "https://"+teamDomain+".slack.com/" + channel.name,
lib_version: "Slack 1.2.0"
}
});

analytics.screen({
userId: user.id,
name: channel.name + " (" + channel.id + ")"
});
} else { logger.log("error", "SegmentIO token not defined as environment variable");}
}

// DEBUG LOGGING
logger.log('info', "Raw Slack Webhook Post: "+JSON.stringify(req.body));

res.send("OK")
});

//Start Server
app.listen(port, function () {
console.log('Listening on port ' + port);
});
app.listen(port, function () {logger.log('info', 'Listening on port ' + port);});
22 changes: 16 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
{
"name": "SlackGAIntegration",
"version": "1.0.0",
"description": "Integration with GA to track Slack usage",
"name": "SlackAlytics",
"version": "1.5.4",
"description": "Integration with analytics tools to track Slack usage",
"repository": {
"type": "git",
"url": "git+https://github.com/lisensee/slackalytics"
},
"license": "MIT",
"main": "app.js",
"dependencies": {
"express": "^4.12.3",
"analytics-node": "^3.0.0",
"body-parser": "^1.12.2",
"request": "^2.54.0",
"querystring": "^0.2.0"
"express": "^4.12.3",
"mathjs": "~>7.5.1",
"moment": "^2.4.0",
"mongodb": "^3.6.2",
"node-uuid": "^1.4.0",
"querystring": "^0.2.0",
"request": "^2.54.0"
},
"scripts": {
"start": "node app.js"
Expand Down