fix(gbdialog): Trying to save context.

This commit is contained in:
Rodrigo Rodriguez (pragmatismo.io) 2018-12-01 14:38:08 -02:00
parent f0a0cd36be
commit ce04290fcd
10 changed files with 128 additions and 174 deletions

2
.gitignore vendored
View file

@ -13,3 +13,5 @@
/packages/default.gbui/node_modules
/tmp
/work
/packages/default.gbdialog/bot.js
/packages/default.gbdialog/bot.ts

18
package-lock.json generated
View file

@ -3299,9 +3299,9 @@
}
},
"@types/node": {
"version": "9.6.36",
"resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.36.tgz",
"integrity": "sha512-Fbw+AdRLL01vv7Rk7bYaNPecqmKoinJHGbpKnDpbUZmUj/0vj3nLqPQ4CNBzr3q2zso6Cq/4jHoCAdH78fvJrw=="
"version": "9.6.40",
"resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.40.tgz",
"integrity": "sha512-M3HHoXXndsho/sTbQML2BJr7/uwNhMg8P0D4lb+UsM65JQZx268faiz9hKpY4FpocWqpwlLwa8vevw8hLtKjOw=="
},
"async": {
"version": "1.5.2",
@ -3548,9 +3548,9 @@
}
},
"botlib": {
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/botlib/-/botlib-0.1.6.tgz",
"integrity": "sha512-NG/F7Yxhx/duehDzjI78mYMonZ03d+Gx+WtRmqj7TimGcc4xK1y4m7s+n9jt0XR+GYhj0jcA5uKA2LqDRtq22A==",
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/botlib/-/botlib-0.1.7.tgz",
"integrity": "sha512-vp8htUT/AL+pYXdiy9s13HFLbygCUorELw1dg1FEqHsfXQOoTlUvr52rNEeKikHvNYaXEEHqhv2F4pLRvEHIYw==",
"requires": {
"async": "2.6.1",
"botbuilder": "4.1.3",
@ -3607,9 +3607,9 @@
}
},
"@types/node": {
"version": "9.6.36",
"resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.36.tgz",
"integrity": "sha512-Fbw+AdRLL01vv7Rk7bYaNPecqmKoinJHGbpKnDpbUZmUj/0vj3nLqPQ4CNBzr3q2zso6Cq/4jHoCAdH78fvJrw=="
"version": "9.6.40",
"resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.40.tgz",
"integrity": "sha512-M3HHoXXndsho/sTbQML2BJr7/uwNhMg8P0D4lb+UsM65JQZx268faiz9hKpY4FpocWqpwlLwa8vevw8hLtKjOw=="
},
"botbuilder": {
"version": "4.1.3",

View file

@ -66,7 +66,7 @@
"botbuilder-choices": "^4.0.0-preview1.2",
"botbuilder-dialogs": "^4.1.5",
"botbuilder-prompts": "^4.0.0-preview1.2",
"botlib": "0.1.6",
"botlib": "^0.1.7",
"chai": "4.2.0",
"child_process": "^1.0.2",
"chokidar": "2.0.4",

View file

@ -33,7 +33,9 @@
'use strict';
import { TurnContext } from 'botbuilder';
import { WaterfallStepContext } from 'botbuilder-dialogs';
import { GBMinInstance } from 'botlib';
const WaitUntil = require('wait-until');
/**
* @fileoverview General Bots server core.
@ -42,19 +44,21 @@ import { GBMinInstance } from 'botlib';
export class DialogClass {
public min: GBMinInstance;
public context: TurnContext;
public step: WaterfallStepContext;
constructor(min: GBMinInstance) {
this.min = min;
}
public hear(text: string) {
// TODO: await this.context.beginDialog('textPrompt', text);
}
public talk(text: string) {
this.context.sendActivity(text);
public async hear(cb) {
const id = Math.floor(Math.random() * 1000000000000);
this.min.cbMap[id] = cb;
await this.step.beginDialog('/feedback', { id: id });
}
public async talk(text: string) {
return await this.context.sendActivity(text);
}
/**
* Generic function to call any REST API.
@ -67,7 +71,5 @@ export class DialogClass {
/**
* Generic function to call any REST API.
*/
public post(url: string, data) {
}
public post(url: string, data) {}
}

View file

@ -184,7 +184,6 @@ export class GBCoreService implements IGBCoreService {
alter: alter,
force: force
});
} else {
const msg = `Database synchronization is disabled.`;
logger.info(msg);
@ -239,9 +238,15 @@ export class GBCoreService implements IGBCoreService {
}
public async ensureProxy(port): Promise<string> {
const ngrok = require('ngrok');
return await ngrok.connect({ port: port });
try {
const ngrok = require('ngrok');
return await ngrok.connect({ port: port });
} catch (error) {
// There are false positive from ngrok regarding to no memory, but it's just
// lack of connection.
logger.verbose(error);
throw new Error('Error connecting to remote ngrok server, please check network connection.');
}
}
public async saveInstance(fullInstance: any) {
@ -313,7 +318,6 @@ export class GBCoreService implements IGBCoreService {
}
public loadSysPackages(core: GBCoreService) {
// NOTE: if there is any code before this line a semicolon
// will be necessary before this line.
// Loads all system packages.
@ -344,19 +348,19 @@ export class GBCoreService implements IGBCoreService {
public async createBootInstance(core: GBCoreService, azureDeployer: AzureDeployerService, proxyAddress: string) {
let instance: IGBInstance;
logger.info(`Deploying cognitive infrastructure (on the cloud / on premises)...`);
try {
instance = await azureDeployer.deployFarm(proxyAddress);
} catch (error) {
logger.warn(
`In case of error, please cleanup any infrastructure objects
logger.info(`Deploying cognitive infrastructure (on the cloud / on premises)...`);
try {
instance = await azureDeployer.deployFarm(proxyAddress);
} catch (error) {
logger.warn(
`In case of error, please cleanup any infrastructure objects
created during this procedure and .env before running again.`
);
throw error;
}
core.writeEnv(instance);
logger.info(`File .env written, starting General Bots...`);
GBConfigService.init();
);
throw error;
}
core.writeEnv(instance);
logger.info(`File .env written, starting General Bots...`);
GBConfigService.init();
return instance;
}

View file

@ -43,21 +43,9 @@ const logger = require('../../../src/logger');
const request = require('request-promise-native');
const AuthenticationContext = require('adal-node').AuthenticationContext;
import {
AutoSaveStateMiddleware,
BotFrameworkAdapter,
ConversationState,
MemoryStorage,
UserState
} from 'botbuilder';
import { AutoSaveStateMiddleware, BotFrameworkAdapter, ConversationState, MemoryStorage, UserState } from 'botbuilder';
import {
GBMinInstance,
IGBAdminService,
IGBConversationalService,
IGBCoreService,
IGBPackage
} from 'botlib';
import { GBMinInstance, IGBAdminService, IGBConversationalService, IGBCoreService, IGBPackage } from 'botlib';
import { GBAnalyticsPackage } from '../../analytics.gblib';
import { GBCorePackage } from '../../core.gbapp';
import { GBCustomerSatisfactionPackage } from '../../customer-satisfaction.gbapp';
@ -117,10 +105,7 @@ export class GBMinService {
// Serves default UI on root address '/'.
const uiPackage = 'default.gbui';
server.use(
'/',
express.static(UrlJoin(GBDeployer.deployFolder, uiPackage, 'build'))
);
server.use('/', express.static(UrlJoin(GBDeployer.deployFolder, uiPackage, 'build')));
Promise.all(
instances.map(async instance => {
@ -136,7 +121,7 @@ export class GBMinService {
// Returns the instance object to clients requesting bot info.
let botId = req.params.botId;
if (botId === '[default]'){
if (botId === '[default]') {
botId = bootInstance.botId;
}
@ -171,15 +156,12 @@ export class GBMinService {
// Build bot adapter.
const { min, adapter, conversationState } = await this.buildBotAdapter(
instance
);
const { min, adapter, conversationState } = await this.buildBotAdapter(instance);
// Install default VBA module.
deployer.deployPackageFromLocalPath(min, 'packages/default.gbdialog');
// Call the loadBot context.activity for all packages.
this.invokeLoadBot(appPackages, min, server);
@ -188,32 +170,17 @@ export class GBMinService {
const url = `/api/messages/${instance.botId}`;
server.post(url, async (req, res) => {
return this.receiver(
adapter,
req,
res,
conversationState,
min,
instance,
appPackages
);
return await this.receiver(adapter, req, res, conversationState, min, instance, appPackages);
});
logger.info(
`GeneralBots(${instance.engineName}) listening on: ${url}.`
);
logger.info(`GeneralBots(${instance.engineName}) listening on: ${url}.`);
// Serves individual URL for each bot user interface.
const uiUrl = `/${instance.botId}`;
server.use(
uiUrl,
express.static(UrlJoin(GBDeployer.deployFolder, uiPackage, 'build'))
);
server.use(uiUrl, express.static(UrlJoin(GBDeployer.deployFolder, uiPackage, 'build')));
logger.info(`Bot UI ${uiPackage} accessible at: ${uiUrl}.`);
const state = `${instance.instanceId}${Math.floor(
Math.random() * 1000000000
)}`;
const state = `${instance.instanceId}${Math.floor(Math.random() * 1000000000)}`;
// Clients get redirected here in order to create an OAuth authorize url and redirect them to AAD.
// There they will authenticate and give their consent to allow this app access to
@ -226,9 +193,7 @@ export class GBMinService {
);
authorizationUrl = `${authorizationUrl}?response_type=code&client_id=${
min.instance.authenticatorClientId
}&redirect_uri=${min.instance.botEndpoint}/${
min.instance.botId
}/token`;
}&redirect_uri=${min.instance.botEndpoint}/${min.instance.botId}/token`;
res.redirect(authorizationUrl);
});
@ -238,23 +203,16 @@ export class GBMinService {
// access token that can be used to access the user owned resource.
server.get(`/${min.instance.botId}/token`, async (req, res) => {
const state = await min.adminService.getValue(
min.instance.instanceId,
'AntiCSRFAttackState'
);
const state = await min.adminService.getValue(min.instance.instanceId, 'AntiCSRFAttackState');
if (req.query.state !== state) {
const msg =
'WARNING: state field was not provided as anti-CSRF token';
const msg = 'WARNING: state field was not provided as anti-CSRF token';
logger.error(msg);
throw new Error(msg);
}
const authenticationContext = new AuthenticationContext(
UrlJoin(
min.instance.authenticatorAuthorityHostUrl,
min.instance.authenticatorTenant
)
UrlJoin(min.instance.authenticatorAuthorityHostUrl, min.instance.authenticatorTenant)
);
const resource = 'https://graph.microsoft.com';
@ -271,47 +229,16 @@ export class GBMinService {
logger.error(msg);
res.send(msg);
} else {
await this.adminService.setValue(
instance.instanceId,
'refreshToken',
token.refreshToken
);
await this.adminService.setValue(
instance.instanceId,
'accessToken',
token.accessToken
);
await this.adminService.setValue(
instance.instanceId,
'expiresOn',
token.expiresOn.toString()
);
await this.adminService.setValue(
instance.instanceId,
'AntiCSRFAttackState',
null
);
await this.adminService.setValue(instance.instanceId, 'refreshToken', token.refreshToken);
await this.adminService.setValue(instance.instanceId, 'accessToken', token.accessToken);
await this.adminService.setValue(instance.instanceId, 'expiresOn', token.expiresOn.toString());
await this.adminService.setValue(instance.instanceId, 'AntiCSRFAttackState', null);
res.redirect(min.instance.botEndpoint);
}
}
);
});
// Setups handlers.
// send: function (context.activity, next) {
// logger.info(
// `[SND]: ChannelID: ${context.activity.address.channelId}, ConversationID: ${context.activity.address.conversation},
// Type: ${context.activity.type} `)
// this.core.createMessage(
// this.min.conversation,
// this.min.conversation.startedBy,
// context.activity.source,
// (data, err) => {
// logger.info(context.activity.source)
// }
// )
// next()
})
);
}
@ -388,8 +315,10 @@ export class GBMinService {
min.conversationalService = this.conversationalService;
min.adminService = this.adminService;
min.instance = await this.core.loadInstance(min.botId);
min.userProfile = conversationState.createProperty('userProfile');
const dialogState = conversationState.createProperty('dialogState');
min.dialogs = new DialogSet(dialogState);
min.dialogs.add(new TextPrompt('textPrompt'));
@ -417,18 +346,18 @@ export class GBMinService {
p.channel.received(req, res);
});
}
}, this);
}, this);
appPackages.forEach(e => {
e.sysPackages = sysPackages;
e.loadBot(min);
}, this);
}, this);
}
/**
* Bot Service hook method.
*/
private receiver(
private async receiver(
adapter: BotFrameworkAdapter,
req: any,
res: any,
@ -437,8 +366,9 @@ export class GBMinService {
instance: any,
appPackages: any[]
) {
return adapter.processActivity(req, res, async context => {
const state = conversationState.get(context);
return await adapter.processActivity(req, res, async context => {
// Get loaded user state
const state = await conversationState.get(context);
const step = await min.dialogs.createContext(context, state);
step.context.activity.locale = 'en-US'; // TODO: Make dynamic.
@ -454,18 +384,16 @@ export class GBMinService {
});
user.loaded = true;
user.subjects = [];
user.cb = null;
await min.userProfile.set(step.context, user);
}
logger.info(
`User>: ${context.activity.text} (${context.activity.type}, ${
context.activity.name
}, ${context.activity.channelId}, {context.activity.value})`
`User>: ${context.activity.text} (${context.activity.type}, ${context.activity.name}, ${
context.activity.channelId
}, {context.activity.value})`
);
if (
context.activity.type === 'conversationUpdate' &&
context.activity.membersAdded.length > 0
) {
if (context.activity.type === 'conversationUpdate' && context.activity.membersAdded.length > 0) {
const member = context.activity.membersAdded[0];
if (member.name === 'GeneralBots') {
logger.info(`Bot added to conversation, starting chat...`);
@ -480,12 +408,15 @@ export class GBMinService {
}
// Processes messages.
} else if (context.activity.type === 'message') {
// Checks for /admin request.
if (context.activity.text === 'vba') {
min.sandbox.context = context;
min.sandbox.step = step;
min.sandbox['bot'].bind(min.sandbox);
min.sandbox['bot']();
await min.sandbox['bot']();
} else if (context.activity.text === 'admin') {
await step.beginDialog('/admin');
@ -497,7 +428,9 @@ export class GBMinService {
// Otherwise, continue to the active dialog in the stack.
} else {
if (step.activeDialog) {
const user = await min.userProfile.get(context, {});
if (step.activeDialog || user.dialog) {
await step.continueDialog();
} else {
await step.beginDialog('/answer', {
@ -508,7 +441,6 @@ export class GBMinService {
// Processes events.
} else if (context.activity.type === 'event') {
// Empties dialog stack before going to the target.
await step.endAll();
@ -539,13 +471,12 @@ export class GBMinService {
await step.continueDialog();
}
}
await conversationState.saveChanges(context, true);
} catch (error) {
const msg = `ERROR: ${error.message} ${error.stack ? error.stack : ''}`;
logger.error(msg);
await step.context.sendActivity(
Messages[step.context.activity.locale].very_sorry_about_error
);
await step.context.sendActivity(Messages[step.context.activity.locale].very_sorry_about_error);
await step.beginDialog('/ask', { isReturning: true });
}
});

View file

@ -76,13 +76,15 @@ export class GBVMService implements IGBCoreService {
// Convert TS into JS.
const tsfile = `bot.ts`;
const tsc = new TSCompiler();
tsc.compile([UrlJoin(path, tsfile)]);
// TODO: tsc.compile([UrlJoin(path, tsfile)]);
// Run JS into the GB context.
const jsfile = `bot.js`;
localPath = UrlJoin(path, jsfile);
if (fs.existsSync(localPath)) {
let code: string = fs.readFileSync(localPath, 'utf8');
code = code.replace(/^.*exports.*$/gm, '');
code = code.replace(/this\./gm, 'await this.');
code = code.replace(/function/gm, 'async function');
const sandbox: DialogClass = new DialogClass(min);
const context = vm.createContext(sandbox);
vm.runInContext(code, context);

View file

@ -78,35 +78,48 @@ export class FeedbackDialog extends IGBDialog {
])
);
min.dialogs.add(new WaterfallDialog('/feedback', [
async step => {
const locale = step.context.activity.locale;
if (step.result.fromMenu) {
min.dialogs.add(
new WaterfallDialog('/feedback', [
async step => {
const locale = step.context.activity.locale;
await step.context.sendActivity(Messages[locale].about_suggestions);
step.activeDialog.state.cbId = step.options['id'];
return await step.prompt('textPrompt', Messages[locale].what_about_service);
},
async step => {
console.log(step.result);
// min.sandbox.context = step.context;
// min.sandbox.step = step;
let cbId = step.activeDialog.state.cbId;
let cb = min.cbMap[cbId];
cb.bind({ step: step, context: step.context });
await cb();
// const locale = step.context.activity.locale;
// const rate = await AzureText.getSentiment(
// min.instance.textAnalyticsKey,
// min.instance.textAnalyticsEndpoint,
// min.conversationalService.getCurrentLanguage(step),
// step.result
// );
// if (rate > 0.5) {
// await step.context.sendActivity(Messages[locale].glad_you_liked);
// } else {
// await step.context.sendActivity(Messages[locale].we_will_improve);
// // TODO: Record.
// }
// await step.replaceDialog('/ask', { isReturning: true });
return await step.next();
}
await step.prompt('textPrompt', Messages[locale].what_about_service);
return await step.next();
},
async step => {
const locale = step.context.activity.locale;
const rate = await AzureText.getSentiment(
min.instance.textAnalyticsKey,
min.instance.textAnalyticsEndpoint,
min.conversationalService.getCurrentLanguage(step),
step.result
);
if (rate > 0.5) {
await step.context.sendActivity(Messages[locale].glad_you_liked);
} else {
await step.context.sendActivity(Messages[locale].we_will_improve);
// TODO: Record.
}
await step.replaceDialog('/ask', { isReturning: true });
return await step.next();
}
]));
])
);
}
}

View file

@ -35,5 +35,4 @@ this.talk ("Please, what's your e-mail address?")
let email = this.hear()
this.talk("Thanks, sending e-mail to: " + email);
this.sendEmail(email, "Message from VBA Bot", "Yes, I can send e-mails.");
%>

View file

@ -93,6 +93,7 @@ export class GBServer {
// Ensures cloud / on-premises infrastructure is setup.
logger.info(`Establishing a development local proxy (ngrok)...`);
const proxyAddress: string = await core.ensureProxy(port);
logger.info(`Deploying packages...`);