From 22f4250831d871b7f1382b224210a8c68fb2a314 Mon Sep 17 00:00:00 2001 From: Rodrigo Rodriguez Date: Mon, 27 Jan 2020 16:19:09 -0300 Subject: [PATCH] fix(analytics.gblib): Fixes in database storage. --- .vscode/tasks.json | 11 +++ boot.js | 8 +- packages/analytics.gblib/index.ts | 3 + packages/analytics.gblib/models/index.ts | 89 +++++++++---------- .../services/AnalyticsService.ts | 37 ++++++-- packages/core.gbapp/services/GBCoreService.ts | 2 +- packages/core.gbapp/services/GBMinService.ts | 53 ++++++----- .../dialogs/QualityDialog.ts | 9 ++ packages/default.gbui/package-lock.json | 52 ++++++----- packages/default.gbui/package.json | 3 +- packages/default.gbui/src/GBUIApp.js | 35 ++++---- .../security.gblib/services/SecService.ts | 3 +- 12 files changed, 177 insertions(+), 128 deletions(-) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index e402cbb0..2011b151 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -14,6 +14,17 @@ "kind": "build", "isDefault": true } + }, + { + "type": "typescript", + "tsconfig": "tsconfig.json", + "problemMatcher": [ + "$tsc" + ], + "group": { + "kind": "build", + "isDefault": true + } } ] } \ No newline at end of file diff --git a/boot.js b/boot.js index 1802247f..8992cd25 100644 --- a/boot.js +++ b/boot.js @@ -6,15 +6,9 @@ const { exec } = require('child_process'); // Displays version of Node JS being used at runtime and others attributes. -console.log(`[GB Runtime] version = ${process.version}`); -console.log(`[GB Runtime] env = ${process.env}`); +console.log(`[GB Runtime] NodeJS = ${process.version}`); console.log(`[GB Runtime] platform = ${process.platform}`); -console.log(`[GB Runtime] release = ${process.release}`); console.log(`[GB Runtime] argv = ${process.argv}`); -console.log(`[GB Runtime] env.USER = ${process.env.USER}`); -console.log(`[GB Runtime] env.PATH = ${process.env.PATH.split(':').join('\n')}`); -console.log(`[GB Runtime] env.PWD = ${process.env.PWD}`); -console.log(`[GB Runtime] env.HOME = ${process.env.HOME}`); console.log(`[GB Runtime] debugPort = ${process.debugPort}`); var now = () => { diff --git a/packages/analytics.gblib/index.ts b/packages/analytics.gblib/index.ts index 6e123cb4..b7a62a1e 100644 --- a/packages/analytics.gblib/index.ts +++ b/packages/analytics.gblib/index.ts @@ -38,6 +38,7 @@ import { GBDialogStep, GBLog, GBMinInstance, IGBCoreService, IGBPackage } from 'botlib'; import { Sequelize } from 'sequelize-typescript'; +import { GuaribasConversation, GuaribasConversationMessage } from './models'; /** * .gblib Package handler. @@ -49,6 +50,8 @@ export class GBAnalyticsPackage implements IGBPackage { } public loadPackage(core: IGBCoreService, sequelize: Sequelize): void { GBLog.verbose(`loadPackage called.`); + core.sequelize.addModels([GuaribasConversation, GuaribasConversationMessage]); + } public unloadPackage(core: IGBCoreService): void { GBLog.verbose(`unloadPackage called.`); diff --git a/packages/analytics.gblib/models/index.ts b/packages/analytics.gblib/models/index.ts index 6d0fc6e5..19537b89 100644 --- a/packages/analytics.gblib/models/index.ts +++ b/packages/analytics.gblib/models/index.ts @@ -58,6 +58,50 @@ import { GuaribasChannel, GuaribasInstance } from '../../core.gbapp/models/GBMod import { GuaribasSubject } from '../../kb.gbapp/models'; import { GuaribasUser } from '../../security.gblib/models'; + +/** + * A conversation that groups many messages. + */ +@Table +export class GuaribasConversation extends Model { + + @PrimaryKey + @AutoIncrement + @Column + public conversationId: number; + + @ForeignKey(() => GuaribasSubject) + @Column + public startSubjectId: number; + + @BelongsTo(() => GuaribasSubject) + public startSubject: GuaribasSubject; + + @ForeignKey(() => GuaribasChannel) + @Column + public channelId: string; + + @Column public rateDate: Date; + + @Column(DataType.FLOAT) + @Column + public rate: number; + + @Column + @CreatedAt + public createdAt: Date; + + @Column public text: string; + + @ForeignKey(() => GuaribasUser) + @Column + public startedByUserId: number; + + @BelongsTo(() => GuaribasUser) + public startedBy: GuaribasUser; +} + + /** * A single message in a conversation. */ @@ -104,48 +148,3 @@ export class GuaribasConversationMessage extends Model GuaribasUser) public user: GuaribasUser; } - -/** - * A conversation that groups many messages. - */ -@Table -export class GuaribasConversation extends Model { - - @PrimaryKey - @AutoIncrement - @Column - public conversationId: number; - - @ForeignKey(() => GuaribasSubject) - @Column - public startSubjectId: number; - - @BelongsTo(() => GuaribasSubject) - public startSubject: GuaribasSubject; - - @ForeignKey(() => GuaribasChannel) - @Column - public channelId: string; - - @Column public rateDate: Date; - - @Column(DataType.FLOAT) - @Column - public rate: number; - - @Column - @CreatedAt - public createdAt: Date; - - @Column public text: string; - - @HasMany(() => GuaribasConversationMessage) - public conversationMessage: GuaribasConversationMessage[]; - - @ForeignKey(() => GuaribasUser) - @Column - public startedByUserId: number; - - @BelongsTo(() => GuaribasUser) - public startedBy: GuaribasUser; -} diff --git a/packages/analytics.gblib/services/AnalyticsService.ts b/packages/analytics.gblib/services/AnalyticsService.ts index b91062ff..e651ecb5 100644 --- a/packages/analytics.gblib/services/AnalyticsService.ts +++ b/packages/analytics.gblib/services/AnalyticsService.ts @@ -45,22 +45,41 @@ export class AnalyticsService { public async createConversation( user: GuaribasUser ): Promise { - const conversation = new GuaribasConversation(); - conversation.startedBy = user; - conversation.startedByUserId = user.userId; + const conversation = new GuaribasConversation(); + conversation.startedBy = user; + conversation.startedByUserId = user.userId; - return await conversation.save(); + return await conversation.save(); } + public async updateConversationRate( + instanceId: number, + conversationId: number, + rate: number + ): Promise { + const options = { where: {} }; + // TODO: Filter by instanceId: instanceId + options.where = { conversationId: conversationId }; + const item = await GuaribasConversation.findOne(options); + item.rate = rate; + item.rateDate = new Date(); + return item.save(); + } + + public async createMessage( + instanceId: number, conversation: GuaribasConversation, user: GuaribasUser, content: string ): Promise { - const message = GuaribasConversationMessage.build(); - message.conversation = conversation; - message.user = user; - message.content = content; - return await message.save(); + + const message = GuaribasConversationMessage.build(); + message.content = content; + message.instanceId = instanceId; + message.userId = user.userId; + message.conversationId = conversation.conversationId; + + return await message.save(); } } diff --git a/packages/core.gbapp/services/GBCoreService.ts b/packages/core.gbapp/services/GBCoreService.ts index 27c34ea5..d67cb8aa 100644 --- a/packages/core.gbapp/services/GBCoreService.ts +++ b/packages/core.gbapp/services/GBCoreService.ts @@ -382,11 +382,11 @@ STORAGE_SYNC=true const sysPackages: IGBPackage[] = []; [ GBAdminPackage, - GBAnalyticsPackage, GBCorePackage, GBSecurityPackage, GBKBPackage, GBCustomerSatisfactionPackage, + GBAnalyticsPackage, GBWhatsappPackage, GBAzureDeployerPackage, GBSharePointPackage, diff --git a/packages/core.gbapp/services/GBMinService.ts b/packages/core.gbapp/services/GBMinService.ts index ab2627f9..d455e254 100644 --- a/packages/core.gbapp/services/GBMinService.ts +++ b/packages/core.gbapp/services/GBMinService.ts @@ -58,19 +58,12 @@ import { import { MicrosoftAppCredentials } from 'botframework-connector'; import { GBServer } from '../../../src/app'; -import { GBAnalyticsPackage } from '../../analytics.gblib'; -import { GBCorePackage } from '../../core.gbapp'; -import { GBCustomerSatisfactionPackage } from '../../customer-satisfaction.gbapp'; -import { GBKBPackage } from '../../kb.gbapp'; import { AskDialogArgs } from '../../kb.gbapp/dialogs/AskDialog'; -import { GBSecurityPackage } from '../../security.gblib'; -import { GBWhatsappPackage } from '../../whatsapp.gblib'; import { Messages } from '../strings'; -import { GBAdminPackage } from './../../admin.gbapp/index'; import { GBConfigService } from './GBConfigService'; import { GBDeployer } from './GBDeployer'; import { SecService } from '../../security.gblib/services/SecService'; -import { isBreakOrContinueStatement } from 'typescript'; +import { AnalyticsService } from '../../analytics.gblib/services/AnalyticsService'; /** * Minimal service layer for a bot. @@ -124,7 +117,7 @@ export class GBMinService { if (process.env.DISABLE_WEB !== 'true') { GBServer.globals.server.get('/instances/:botId', (req, res) => { (async () => { - await this.handleGetInstanceFroClient(req, res); + await this.handleGetInstanceForClient(req, res); })(); }); } @@ -189,7 +182,7 @@ export class GBMinService { public async mountBot(instance: IGBInstance) { // Build bot adapter. - const { min, adapter, conversationState } = await this.buildBotAdapter(instance, GBServer.globals.publicAddress, GBServer.globals.sysPackages); + const { min, adapter, conversationState } = await this.buildBotAdapter(instance, GBServer.globals.sysPackages); if (GBServer.globals.minInstances.length === 0) { GBServer.globals.minBoot = min; @@ -201,7 +194,7 @@ export class GBMinService { // this.deployer.deployPackage(min, 'packages/default.gbdialog'); // Call the loadBot context.activity for all packages. - this.invokeLoadBot(GBServer.globals.appPackages, GBServer.globals.sysPackages, min, GBServer.globals.server); + this.invokeLoadBot(GBServer.globals.appPackages, GBServer.globals.sysPackages, min); // Serves individual URL for each bot conversational interface... const url = `/api/messages/${instance.botId}`; @@ -280,7 +273,7 @@ export class GBMinService { /** * Returns the instance object to clients requesting bot info. */ - private async handleGetInstanceFroClient(req: any, res: any) { + private async handleGetInstanceForClient(req: any, res: any) { let botId = req.params.botId; if (botId === '[default]' || botId === undefined) { botId = GBConfigService.get('BOT_ID'); @@ -365,7 +358,7 @@ export class GBMinService { } } - private async buildBotAdapter(instance: any, proxyAddress: string, sysPackages: IGBPackage[]) { + private async buildBotAdapter(instance: any, sysPackages: IGBPackage[]) { const adapter = new BotFrameworkAdapter({ appId: instance.marketplaceId, appPassword: instance.marketplacePassword @@ -404,11 +397,9 @@ export class GBMinService { return { min, adapter, conversationState }; } - private invokeLoadBot(appPackages: IGBPackage[], sysPackages: IGBPackage[], min: GBMinInstance, server: any) { - let index = 0; + private invokeLoadBot(appPackages: IGBPackage[], sysPackages: IGBPackage[], min: GBMinInstance) { sysPackages.forEach(e => { e.loadBot(min); - index++; }, this); appPackages.forEach(p => { @@ -456,15 +447,24 @@ export class GBMinService { user.loaded = true; user.subjects = []; user.cb = undefined; - await min.userProfile.set(step.context, user); + if (context.activity.membersAdded !== undefined) { let sec = new SecService(); const member = context.activity.membersAdded[0]; - await sec.ensureUser(instance.instanceId, member.id, + const persistedUser = await sec.ensureUser(instance.instanceId, member.id, min.botId, member.id, "", "web", member.name, member.id); + + const analytics = new AnalyticsService(); + + user.systemUser = persistedUser; + user.conversation = await analytics.createConversation(persistedUser); + } + + await min.userProfile.set(step.context, user); + } GBLog.info( @@ -534,7 +534,17 @@ export class GBMinService { } private async processMessageActivity(context, min: GBMinInstance, step: GBDialogStep) { - // Direct script invoking by itent name. + + // Adds message to the analytics layer. + + const analytics = new AnalyticsService(); + const user = await min.userProfile.get(context, {}); + analytics.createMessage(min.instance.instanceId, + user.conversation, user.systemUser, + context.activity.text); + + // Checks for global exit kewywords cancelling any active dialogs. + const globalQuit = (locale, utterance) => { return utterance.match(Messages[locale].global_quit); } @@ -543,10 +553,10 @@ export class GBMinService { const simpleLocale = context.activity.locale.substring(0, 2); const hasBadWord = wash.check(simpleLocale, context.activity.text); - + if (hasBadWord) { await step.beginDialog('/pleaseNoBadWords'); - }else if (isVMCall) { + } else if (isVMCall) { await GBMinService.callVM(context.activity.text, min, step); } else if (context.activity.text.charAt(0) === '/') { await step.beginDialog(context.activity.text); @@ -562,7 +572,6 @@ export class GBMinService { await step.beginDialog('/menu', JSON.parse(context.activity.text)); // Otherwise, continue to the active dialog in the stack. } else { - const user = await min.userProfile.get(context, {}); if (step.activeDialog !== undefined) { await step.continueDialog(); } else { diff --git a/packages/customer-satisfaction.gbapp/dialogs/QualityDialog.ts b/packages/customer-satisfaction.gbapp/dialogs/QualityDialog.ts index b9c4ce5e..696395a5 100644 --- a/packages/customer-satisfaction.gbapp/dialogs/QualityDialog.ts +++ b/packages/customer-satisfaction.gbapp/dialogs/QualityDialog.ts @@ -42,6 +42,7 @@ import { BotAdapter } from 'botbuilder'; import { WaterfallDialog } from 'botbuilder-dialogs'; import { CSService } from '../services/CSService'; import { Messages } from '../strings'; +import { AnalyticsService } from '../../analytics.gblib/services/AnalyticsService'; /** * Dialog for collecting quality of answer. @@ -78,6 +79,14 @@ export class QualityDialog extends IGBDialog { user.lastQuestion, user.lastQuestionId ); + + // Updates values to perform Bot Analytics. + + const analytics = new AnalyticsService(); + analytics.updateConversationRate(min.instance.instanceId, user.conversation, score); + + // Goes to the ask loop. + await step.replaceDialog('/ask', { isReturning: true }); } diff --git a/packages/default.gbui/package-lock.json b/packages/default.gbui/package-lock.json index 1e4556f0..9ad7cc42 100644 --- a/packages/default.gbui/package-lock.json +++ b/packages/default.gbui/package-lock.json @@ -2818,6 +2818,15 @@ "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=" }, + "browser-id": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/browser-id/-/browser-id-1.1.0.tgz", + "integrity": "sha512-MuvthhJ8pDjIYpJzHwYOrlFtTPhEOF3wk9AVvy19pLQwmS/OdGWptuhzjkgLnDJS3WgV2m7bLDvlCwufg1jWBA==", + "requires": { + "uuid": "^3.3.3", + "versioned-storage": "^1.1.0" + } + }, "browser-process-hrtime": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-0.1.3.tgz", @@ -6058,6 +6067,11 @@ "locate-path": "^3.0.0" } }, + "fingerprintjs2": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fingerprintjs2/-/fingerprintjs2-2.1.0.tgz", + "integrity": "sha512-H1k/ESTD2rJ3liupyqWBPjZC+LKfCGixQzz/NDN4dkgbmG1bVFyMOh7luKSkVDoyfhgvRm62pviNMPI+eJTZcQ==" + }, "flat-cache": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", @@ -7639,8 +7653,7 @@ }, "ansi-regex": { "version": "2.1.1", - "bundled": true, - "optional": true + "bundled": true }, "aproba": { "version": "1.2.0", @@ -7677,8 +7690,7 @@ }, "code-point-at": { "version": "1.1.0", - "bundled": true, - "optional": true + "bundled": true }, "concat-map": { "version": "0.0.1", @@ -7687,8 +7699,7 @@ }, "console-control-strings": { "version": "1.1.0", - "bundled": true, - "optional": true + "bundled": true }, "core-util-is": { "version": "1.0.2", @@ -7791,8 +7802,7 @@ }, "inherits": { "version": "2.0.4", - "bundled": true, - "optional": true + "bundled": true }, "ini": { "version": "1.3.5", @@ -7802,7 +7812,6 @@ "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -7822,13 +7831,11 @@ }, "minimist": { "version": "0.0.8", - "bundled": true, - "optional": true + "bundled": true }, "minipass": { "version": "2.9.0", "bundled": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -7845,7 +7852,6 @@ "mkdirp": { "version": "0.5.1", "bundled": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -7926,8 +7932,7 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true, - "optional": true + "bundled": true }, "object-assign": { "version": "4.1.1", @@ -7937,7 +7942,6 @@ "once": { "version": "1.4.0", "bundled": true, - "optional": true, "requires": { "wrappy": "1" } @@ -8013,8 +8017,7 @@ }, "safe-buffer": { "version": "5.1.2", - "bundled": true, - "optional": true + "bundled": true }, "safer-buffer": { "version": "2.1.2", @@ -8044,7 +8047,6 @@ "string-width": { "version": "1.0.2", "bundled": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -8062,7 +8064,6 @@ "strip-ansi": { "version": "3.0.1", "bundled": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -8101,13 +8102,11 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true, - "optional": true + "bundled": true }, "yallist": { "version": "3.1.1", - "bundled": true, - "optional": true + "bundled": true } } } @@ -14283,6 +14282,11 @@ "extsprintf": "^1.2.0" } }, + "versioned-storage": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/versioned-storage/-/versioned-storage-1.1.0.tgz", + "integrity": "sha512-oS+unMiWJjaVAFCkNJvmHiu6LDb8KrzT8YmfrQpAmn8/yKnW4Roq3lcVMxQofT9Oidqg8oWChbGYl4FGQJyPPg==" + }, "vfile": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/vfile/-/vfile-3.0.1.tgz", diff --git a/packages/default.gbui/package.json b/packages/default.gbui/package.json index 1ee7b7f8..e978c493 100644 --- a/packages/default.gbui/package.json +++ b/packages/default.gbui/package.json @@ -1,6 +1,6 @@ { "name": "default.gbui", - "version": "0.0.12", + "version": "0.0.13", "private": false, "repository": "https://github.com/pragmatismo-io/BotServer", "description": "Default web interface for General Bots open-core", @@ -12,6 +12,7 @@ "botframework-webchat": "^4.7.1", "deep-extend": "0.6.0", "fetch": "1.1.0", + "fingerprintjs2": "^2.1.0", "msal": "^1.2.0", "powerbi-client": "2.10.2", "react": "^16.12.0", diff --git a/packages/default.gbui/src/GBUIApp.js b/packages/default.gbui/src/GBUIApp.js index 87d993f5..d9777908 100644 --- a/packages/default.gbui/src/GBUIApp.js +++ b/packages/default.gbui/src/GBUIApp.js @@ -41,8 +41,8 @@ import GBCss from './components/GBCss.js'; import { DirectLine } from 'botframework-directlinejs'; import { ConnectionStatus } from 'botframework-directlinejs'; import ReactWebChat from 'botframework-webchat'; -// import GBPowerBIPlayer from './players/GBPowerBIPlayer.js'; import { UserAgentApplication } from 'msal'; +import Fingerprint2 from 'fingerprintjs2'; class GBUIApp extends React.Component { constructor() { @@ -90,7 +90,8 @@ class GBUIApp extends React.Component { } getUser() { - return { id: 'webUser@gb', name: 'You' }; + + return { id: 'web@gb', name: 'You' }; } postEvent(name, value) { @@ -145,7 +146,7 @@ class GBUIApp extends React.Component { let userAgentApplication = new UserAgentApplication( this.state.instanceClient.authenticatorClientId, authority, - function(errorDesc, token, error, tokenType) { + function (errorDesc, token, error, tokenType) { if (error) { console.log(error); } @@ -157,10 +158,10 @@ class GBUIApp extends React.Component { var user = userAgentApplication.getUser(); if (user) { userAgentApplication.acquireTokenSilent(graphScopes).then( - function(accessToken) { + function (accessToken) { _this_.sendToken(accessToken); }, - function(error) { + function (error) { console.log(error); } ); @@ -264,16 +265,16 @@ class GBUIApp extends React.Component { /> ); break; - /* case 'pbi': - playerComponent = ( - { - this.player = player; - }} - /> - ); - break; */ + /* case 'pbi': + playerComponent = ( + { + this.player = player; + }} + /> + ); + break; */ case 'login': playerComponent = ( {playerComponent}
- {chat} + {chat}
- ); + ); } } diff --git a/packages/security.gblib/services/SecService.ts b/packages/security.gblib/services/SecService.ts index bc138765..39839357 100644 --- a/packages/security.gblib/services/SecService.ts +++ b/packages/security.gblib/services/SecService.ts @@ -65,8 +65,7 @@ export class SecService extends GBService { user.email = userName; user.phone = phone; user.defaultChannel = channelName; - user.save(); - return user; + return await user.save(); } /**