From 33ca6d5bc1ff514ca978d3b244424b1e0fa91773 Mon Sep 17 00:00:00 2001 From: morsh Date: Sun, 14 May 2017 18:55:42 +0300 Subject: [PATCH] ts working --- events.js => dist/events.js | 0 dist/example.js | 8 + dist/index.js | 392 +++++++++++++++++++++++++++++++++++ main.js => dist/main.js | 19 +- package.json | 4 +- events.ts => src/events.ts | 0 example.js => src/example.js | 0 index.js => src/index.js | 0 main.ts => src/main.ts | 17 +- tsconfig.json | 8 +- 10 files changed, 430 insertions(+), 18 deletions(-) rename events.js => dist/events.js (100%) create mode 100644 dist/example.js create mode 100644 dist/index.js rename main.js => dist/main.js (96%) rename events.ts => src/events.ts (100%) rename example.js => src/example.js (100%) rename index.js => src/index.js (100%) rename main.ts => src/main.ts (96%) diff --git a/events.js b/dist/events.js similarity index 100% rename from events.js rename to dist/events.js diff --git a/dist/example.js b/dist/example.js new file mode 100644 index 0000000..4694080 --- /dev/null +++ b/dist/example.js @@ -0,0 +1,8 @@ +var monitor = require('./'); +var bot = null; // botbuilder object +monitor.monitor(bot, { transactions: [ + { + intent: 'alarm.set', + test: /^(Creating alarm named)/i + } + ] }); diff --git a/dist/index.js b/dist/index.js new file mode 100644 index 0000000..faea459 --- /dev/null +++ b/dist/index.js @@ -0,0 +1,392 @@ +var util = require('util'); +var _ = require('lodash'); +var request = require('request'); +var builder = require('botbuilder'); +var appInsights = require("applicationinsights"); +var client = null; +var _console = {}; +var _methods = { + "debug": 0, + "info": 1, + "log": 2, + "warn": 3, + "error": 4 +}; +var _sentimentMinWords = 3; +var _sentimentUrl = 'https://westus.api.cognitive.microsoft.com/text/analytics/v2.0/sentiment'; +var _sentimentId = 'bot-analytics'; +var _sentimentKey = null; +var Events = { + ReceiveMessage: { + name: 'message.received', + format: { + text: 'message.text', + type: 'message.type', + timestamp: 'message.timestamp', + conversationId: 'message.address.conversation.id' + } + }, + SendMessage: { + name: 'message.send', + format: { + text: 'message.text', + type: 'message.type', + timestamp: '(new Date()).toISOString()', + conversationId: 'message.address.conversation.id' + } + }, + ConversionStarted: { + name: 'message.convert.start', + format: { + name: 'comversion name', + timestamp: 'message.timestamp', + channel: 'address.channelId - facebook/slack/webchat/etc...', + conversationId: 'conversation.id', + userId: 'user.id', + userName: 'user.name' + } + }, + ConversionEnded: { + name: 'message.convert.end', + format: { + name: 'comversion name - similar to start', + successful: 'true/false', + count: 'default is 1, but can log more than 1', + timestamp: 'message.timestamp', + channel: 'address.channelId', + conversationId: 'conversation.id', + callstack_length: 'callstack.length - how many dialogs/steps are in the stack', + userId: 'user.id', + userName: 'user.name' + } + }, + Intents: { + name: 'message.intent.dialog', + format: { + intent: 'intent name / id / string', + state: 'current session state', + channel: 'address.channelId', + conversationId: 'conversation.id', + callstack_length: 'callstack.length', + userId: 'user.id', + userName: 'user.name' + } + }, + Sentiment: { + name: 'message.sentiment', + format: { + text: 'message.text', + score: 'sentiment score', + timestamp: 'message.timestamp', + channel: 'address.channelId', + conversationId: 'conversation.id', + userId: 'user.id', + userName: 'user.name' + } + } +}; +var formatArgs = (args) => { + return util.format.apply(util.format, Array.prototype.slice.call(args)); +}; +var setup = () => { + Object.keys(_methods).forEach(method => { + console[method] = (function () { + var orig = console.log; + return function () { + try { + var msg = formatArgs(arguments); + client.trackTrace(msg, _methods[method]); + var tmp = process.stdout; + process.stdout = process.stderr; + orig.apply(console, arguments); + } + finally { + process.stdout = tmp; + } + }; + })(); + }); +}; +/** + * Monitor requests made to the bot framework + * @param {UniversalBot} bot + * @param {ConversionConfig} conversionConfig + */ +var monitor = (bot, options) => { + options = options || {}; + if ((!options.instrumentationKey) && + (!process.env.APPINSIGHTS_INSTRUMENTATIONKEY)) { + throw new Error('App Insights instrumentation key was not provided in options or the environment variable APPINSIGHTS_INSTRUMENTATIONKEY'); + } + appInsights.setup(options.instrumentationKey || process.env.APPINSIGHTS_INSTRUMENTATIONKEY).start(); + client = appInsights.getClient(options.instrumentationKey || process.env.APPINSIGHTS_INSTRUMENTATIONKEY); + if (!options.sentimentKey && !process.env.CG_SENTIMENT_KEY) { + console.warn('No sentiment key was provided - text sentiments will not be collected'); + } + else { + _sentimentKey = options.sentimentKey || process.env.CG_SENTIMENT_KEY; + } + var transactions = options.transactions || []; + setup(); + if (bot) { + // Adding middleware to intercept all received messages + bot.use({ + botbuilder: function (session, next) { + try { + var message = session.message; + var address = message.address || {}; + var conversation = address.conversation || {}; + var user = address.user || {}; + var item = { + text: message.text, + type: message.type, + timestamp: message.timestamp, + conversationId: conversation.id, + channel: address.channelId, + userId: user.id, + userName: user.name + }; + client.trackEvent(Events.ReceiveMessage.name, item); + } + catch (e) { + } + finally { + next(); + } + }, + send: function (message, next) { + try { + var b = bot; + client.trackEvent(Events.SendMessage.name, { + text: message.text, + type: message.type, + timestamp: (new Date()).toISOString(), + conversationId: message.address && message.address.conversation && message.address.conversation.id + }); + } + catch (e) { + } + finally { + next(); + } + } + }); + } + // Monitoring new dialog calls like session.beginDialog + // When beginning a new dialog, the framework uses pushDialog to change context + // to a new dialog + // Todo: Check alternative as + builder.Session.prototype.pushDialog = (function () { + var orig = builder.Session.prototype.pushDialog; + return function (args) { + var _session = this; + var _message = _session.message || {}; + var _address = _message.address || {}; + var _conversation = _address.conversation || {}; + var _user = _address.user || {}; + var _callstack = _session.sessionState.callstack; + var item = { + intent: args && args.id, + state: args && args.state && JSON.stringify(args.state), + channel: _address.channelId, + conversationId: _conversation.id, + callstack_length: _callstack.length.toString(), + userId: _user.id, + userName: _user.name + }; + _.take(_callstack, 3).forEach((stackItem, idx) => { + item[`callstack_${idx}_id`] = stackItem.id; + item[`callstack_${idx}_state`] = JSON.stringify(stackItem.state); + }); + client.trackEvent(Events.Intents.name, item); + orig.apply(_session, [args]); + }; + })(); + // Capture message session before send + builder.Session.prototype.prepareMessage = (function () { + var orig = builder.Session.prototype.prepareMessage; + return function (msg) { + var _session = this; + var res = orig.apply(_session, [msg]); + if (_session.dialogData['transaction.started']) { + var transactionEnded = false; + var success = false; + var conversation = _.find(transactions, { intent: _session.dialogData['transaction.id'] }); + if (conversation.intent != _session.dialogData['BotBuilder.Data.Intent']) { + transactionEnded = true; + } + else { + var test = conversation.test; + var success = typeof test == 'string' ? test == msg.text : test.test(msg.text); + if (success) { + transactionEnded = true; + } + } + if (transactionEnded) { + endConverting(_session, null, success); + delete _session.dialogData['transaction.started']; + delete _session.dialogData['transaction.id']; + } + } + return res; + }; + })(); + // Collect intents collected from LUIS after entities were resolved + builder.IntentDialog.prototype.recognize = (function () { + var _recognize = builder.IntentDialog.prototype.recognize; + return function (context, cb) { + var _dialog = this; + _recognize.apply(_dialog, [context, (err, result) => { + var entities = []; + if (result && result.entities) { + result.entities.forEach(value => { + entities.push({ + type: value.type, + entity: value.entity + }); + }); + } + var message = context.message; + var address = message.address || {}; + var conversation = address.conversation || {}; + var user = address.user || {}; + var item = { + text: message.text, + timestamp: message.timestamp, + intent: result && result.intent, + channel: address.channelId, + score: result && result.score, + entities: entities, + withError: !err, + error: err, + conversationId: conversation.id, + userId: user.id, + userName: user.name + }; + client.trackEvent("message.intent.received", item); + transactions.forEach(cc => { + if (cc.intent == item.intent) { + startConverting(context, null); + context.dialogData['transaction.started'] = true; + context.dialogData['transaction.id'] = cc.intent; + } + }); + collectSentiment(context, message.text); + // Todo: on "set alarm" utterence, failiure + return cb(err, result); + }]); + }; + })(); +}; +var collectSentiment = (session, text) => { + text = text || ''; + if (!_sentimentKey) + return; + if (text.match(/\S+/g).length < _sentimentMinWords) + return; + var _message = session.message || {}; + var _address = _message.address || {}; + var _conversation = _address.conversation || {}; + var _user = _address.user || {}; + request({ + url: _sentimentUrl, + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Ocp-Apim-Subscription-Key': _sentimentKey + }, + json: true, + body: { + "documents": [ + { + "language": "en", + "id": _sentimentId, + "text": text + } + ] + } + }, (error, response, body) => { + if (error) { + return client.trackException(error); + } + try { + var score = _.find(body.documents, { id: _sentimentId }).score; + if (isNaN(score)) { + throw new Error('Could not collect sentiment'); + } + var item = { + text: text, + score: score, + timestamp: _message.timestamp, + channel: _address.channelId, + conversationId: _conversation.id, + userId: _user.id, + userName: _user.name + }; + client.trackEvent(Events.Sentiment.name, item); + } + catch (error) { + return client.trackException(error); + } + }); +}; +var startConverting = (session, name) => { + name = name || 'default'; + var _message = session.message || {}; + var _address = _message.address || {}; + var _conversation = _address.conversation || {}; + var _user = _address.user || {}; + var item = { + name, + timestamp: _message.timestamp, + channel: _address.channelId, + conversationId: _conversation.id, + userId: _user.id, + userName: _user.name + }; + client.trackEvent(Events.ConversionStarted.name, item); +}; +var endConverting = (session, name, successful, count) => { + name = name || 'default'; + count = isNaN(count) && 1 || count; + successful = successful !== false; + var _message = session.message || {}; + var _address = _message.address || {}; + var _conversation = _address.conversation || {}; + var _user = _address.user || {}; + var _callstack = session.sessionState.callstack; + var item = { + name, + successful: successful.toString(), + count: count.toString(), + timestamp: _message.timestamp, + channel: _address.channelId, + conversationId: _conversation.id, + callstack_length: _callstack.length.toString(), + userId: _user.id, + userName: _user.name + }; + client.trackEvent(Events.ConversionEnded.name, item); +}; +var measure = (session, name, count) => { + name = name || 'default'; + count = count || 1; + var _message = session.message || {}; + var _address = _message.address || {}; + var _conversation = _address.conversation || {}; + var _user = _address.user || {}; + var _callstack = session.sessionState.callstack; + var item = { + timestamp: _message.timestamp, + channel: _address.channelId, + conversationId: _conversation.id, + callstack_length: _callstack.length.toString(), + userId: _user.id, + userName: _user.name + }; + client.trackEvent('custom-' + name, item, { count }); +}; +module.exports = { + monitor, + measure +}; diff --git a/main.js b/dist/main.js similarity index 96% rename from main.js rename to dist/main.js index a2adb68..217e639 100644 --- a/main.js +++ b/dist/main.js @@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); const util = require("util"); const _ = require("lodash"); const builder = require("botbuilder"); -const request_1 = require("request"); +const request = require("request"); const ApplicationInsights = require("applicationinsights"); const events_1 = require("./events"); class BotFrameworkInstrumentation { @@ -71,7 +71,7 @@ class BotFrameworkInstrumentation { var _address = _message.address || {}; var _conversation = _address.conversation || {}; var _user = _address.user || {}; - request_1.default({ + request({ url: this.settings.sentiments.url, method: 'POST', headers: { @@ -115,13 +115,17 @@ class BotFrameworkInstrumentation { }); } monitor(bot) { - ApplicationInsights.setup(this.settings.instrumentationKey).start(); + ApplicationInsights.setup(this.settings.instrumentationKey) + .setAutoCollectConsole(true) + .setAutoCollectExceptions(true) + .setAutoCollectRequests(true) + .start(); this.appInsightsClient = ApplicationInsights.getClient(this.settings.instrumentationKey); - this.setupConsoleCollection(); + //this.setupConsoleCollection(); // Adding middleware to intercept all user messages if (bot) { bot.use({ - botbuilder: function (session, next) { + botbuilder: (session, next) => { try { let message = session.message; let address = message.address || {}; @@ -224,6 +228,7 @@ class BotFrameworkInstrumentation { // } // })(); // Collect intents collected from LUIS after entities were resolved + let self = this; builder.IntentDialog.prototype.recognize = (() => { let _recognize = builder.IntentDialog.prototype.recognize; return function (context, cb) { @@ -255,7 +260,7 @@ class BotFrameworkInstrumentation { userId: user.id, userName: user.name }; - this.appInsightsClient.trackEvent(events_1.default.Intent.name, item); + self.appInsightsClient.trackEvent(events_1.default.Intent.name, item); // transactions.forEach(cc => { // if (cc.intent == item.intent) { // startConverting(context, null); @@ -263,7 +268,7 @@ class BotFrameworkInstrumentation { // context.dialogData['transaction.id'] = cc.intent; // } // }); - this.collectSentiment(context, message.text); + self.collectSentiment(context, message.text); // Todo: on "set alarm" utterence, failiure return cb(err, result); }]); diff --git a/package.json b/package.json index ecd5b33..1084a07 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { "name": "botbuilder-instrumentation", - "version": "1.0.6", + "version": "1.0.7", "description": "", - "main": "main.js", + "main": "dist/main.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, diff --git a/events.ts b/src/events.ts similarity index 100% rename from events.ts rename to src/events.ts diff --git a/example.js b/src/example.js similarity index 100% rename from example.js rename to src/example.js diff --git a/index.js b/src/index.js similarity index 100% rename from index.js rename to src/index.js diff --git a/main.ts b/src/main.ts similarity index 96% rename from main.ts rename to src/main.ts index 4c559c1..d00863f 100644 --- a/main.ts +++ b/src/main.ts @@ -1,7 +1,7 @@ import * as util from 'util'; import * as _ from 'lodash'; import * as builder from 'botbuilder'; -import request from 'request'; +import * as request from 'request'; import ApplicationInsights = require("applicationinsights"); import Events from './events'; @@ -151,15 +151,19 @@ export class BotFrameworkInstrumentation { monitor (bot: builder.UniversalBot) { - ApplicationInsights.setup(this.settings.instrumentationKey).start(); + ApplicationInsights.setup(this.settings.instrumentationKey) + .setAutoCollectConsole(true) + .setAutoCollectExceptions(true) + .setAutoCollectRequests(true) + .start(); this.appInsightsClient = ApplicationInsights.getClient(this.settings.instrumentationKey); - this.setupConsoleCollection(); + //this.setupConsoleCollection(); // Adding middleware to intercept all user messages if (bot) { bot.use({ - botbuilder: function (session, next) { + botbuilder: (session, next) => { try { let message: any = session.message; @@ -278,6 +282,7 @@ export class BotFrameworkInstrumentation { // Collect intents collected from LUIS after entities were resolved + let self = this; builder.IntentDialog.prototype.recognize = (() => { let _recognize = builder.IntentDialog.prototype.recognize; return function(context, cb) { @@ -314,7 +319,7 @@ export class BotFrameworkInstrumentation { userName: user.name }; - this.appInsightsClient.trackEvent(Events.Intent.name, item); + self.appInsightsClient.trackEvent(Events.Intent.name, item); // transactions.forEach(cc => { // if (cc.intent == item.intent) { @@ -324,7 +329,7 @@ export class BotFrameworkInstrumentation { // } // }); - this.collectSentiment(context, message.text); + self.collectSentiment(context, message.text); // Todo: on "set alarm" utterence, failiure return cb(err, result); diff --git a/tsconfig.json b/tsconfig.json index 108a050..fc5c7cf 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,15 +3,17 @@ "module": "commonjs", "target": "es6", "allowJs": true, + "outDir": "dist", + "rootDir": "src", "moduleResolution": "node", "forceConsistentCasingInFileNames": true, "noImplicitReturns": true, "noImplicitThis": false, - "noImplicitAny": false, - "checkJs": true + "noImplicitAny": false }, "exclude": [ - "node_modules" + "node_modules", + "dist" ], "types": [ "node_modules/@types"