* Error handling improved and logging enriched as well.

* Setting DATABASE_ is now STORAGE_.
This commit is contained in:
Rodrigo Rodriguez 2018-09-24 15:27:26 -03:00
parent c2d0ef3f2e
commit b922a5b413
12 changed files with 145 additions and 77 deletions

View file

@ -65,8 +65,8 @@ Notes:
### Configure the server to deploy specific directory ### Configure the server to deploy specific directory
1. Create/Edit the .env file and add the ADDITIONAL_DEPLOY_PATH key pointing to the .gbai local parent folder of .gbapp, .gbot, .gbtheme, .gbkb package directories. 1. Create/Edit the .env file and add the ADDITIONAL_DEPLOY_PATH key pointing to the .gbai local parent folder of .gbapp, .gbot, .gbtheme, .gbkb package directories.
2. Specify DATABASE_SYNC to TRUE so database sync is run when the server is run. 2. Specify STORAGE_SYNC to TRUE so database sync is run when the server is run.
3. In case of Microsoft SQL Server add the following keys: DATABASE_HOST, DATABASE_NAME, DATABASE_USERNAME, DATABASE_PASSWORD, DATABASE_DIALECT to `mssql`. 3. In case of Microsoft SQL Server add the following keys: STORAGE_HOST, STORAGE_NAME, STORAGE_USERNAME, STORAGE_PASSWORD, STORAGE_DIALECT to `mssql`.
Note: Note:

View file

@ -1,5 +1,8 @@
# Release History # Release History
## Version 0.1.4
## Version 0.1.3 ## Version 0.1.3
* FIX: Admin now is internationalized. * FIX: Admin now is internationalized.
@ -24,8 +27,8 @@
## Version 0.0.30 ## Version 0.0.30
- FIX: Packages updated. - FIX: Packages updated.
- NEW: DATABASE_SYNC_ALTER environment parameter. - NEW: STORAGE_SYNC_ALTER environment parameter.
- NEW: DATABASE_SYNC_FORCE environment parameter. - NEW: STORAGE_SYNC_FORCE environment parameter.
- NEW: Define constraint names in MSSQL. - NEW: Define constraint names in MSSQL.
## Version 0.0.29 ## Version 0.0.29
@ -44,7 +47,7 @@
- FIX: Packages updated. - FIX: Packages updated.
- NEW: If a bot package's name begins with '.', then it is ignored. - NEW: If a bot package's name begins with '.', then it is ignored.
- NEW: Created DATABASE_LOGGING environment parameter. - NEW: Created STORAGE_LOGGING environment parameter.
## Version 0.0.25 ## Version 0.0.25

View file

@ -48,7 +48,6 @@ import { Messages } from "../strings";
* Dialogs for administration tasks. * Dialogs for administration tasks.
*/ */
export class AdminDialog extends IGBDialog { export class AdminDialog extends IGBDialog {
static async undeployPackageCommand(text: any, min: GBMinInstance) { static async undeployPackageCommand(text: any, min: GBMinInstance) {
let packageName = text.split(" ")[1]; let packageName = text.split(" ")[1];
let importer = new GBImporter(min.core); let importer = new GBImporter(min.core);
@ -59,9 +58,7 @@ export class AdminDialog extends IGBDialog {
); );
} }
static async deployPackageCommand(text: string, static async deployPackageCommand(text: string, deployer: GBDeployer) {
deployer: GBDeployer
) {
let packageName = text.split(" ")[1]; let packageName = text.split(" ")[1];
let additionalPath = GBConfigService.get("ADDITIONAL_DEPLOY_PATH"); let additionalPath = GBConfigService.get("ADDITIONAL_DEPLOY_PATH");
await deployer.deployPackageFromLocalPath( await deployer.deployPackageFromLocalPath(
@ -92,10 +89,7 @@ export class AdminDialog extends IGBDialog {
password === GBConfigService.get("ADMIN_PASS") && password === GBConfigService.get("ADMIN_PASS") &&
GBAdminService.StrongRegex.test(password) GBAdminService.StrongRegex.test(password)
) { ) {
await dc.context.sendActivity(Messages[locale].welcome);
await dc.context.sendActivity(
Messages[locale].welcome
);
await dc.prompt("textPrompt", Messages[locale].which_task); await dc.prompt("textPrompt", Messages[locale].which_task);
} else { } else {
await dc.prompt("textPrompt", Messages[locale].wrong_password); await dc.prompt("textPrompt", Messages[locale].wrong_password);
@ -105,11 +99,12 @@ export class AdminDialog extends IGBDialog {
async (dc, value) => { async (dc, value) => {
const locale = dc.context.activity.locale; const locale = dc.context.activity.locale;
var text = value; var text = value;
const user = min.userState.get(dc.context);
let cmdName = text.split(" ")[0]; let cmdName = text.split(" ")[0];
dc.context.sendActivity(Messages[locale].working(cmdName))
dc.context.sendActivity(Messages[locale].working(cmdName));
let unknownCommand = false;
if (text === "quit") { if (text === "quit") {
user.authenticated = false;
await dc.replace("/"); await dc.replace("/");
} else if (cmdName === "deployPackage") { } else if (cmdName === "deployPackage") {
await AdminDialog.deployPackageCommand(text, deployer); await AdminDialog.deployPackageCommand(text, deployer);
@ -124,12 +119,19 @@ export class AdminDialog extends IGBDialog {
await dc.replace("/admin", { firstRun: false }); await dc.replace("/admin", { firstRun: false });
} else if (cmdName === "setupSecurity") { } else if (cmdName === "setupSecurity") {
await AdminDialog.setupSecurity(min, dc); await AdminDialog.setupSecurity(min, dc);
} else {
unknownCommand = true;
} }
else{
if (unknownCommand) {
await dc.context.sendActivity(Messages[locale].unknown_command); await dc.context.sendActivity(Messages[locale].unknown_command);
dc.endAll() } else {
await dc.replace("/answer", { query: text }); await dc.context.sendActivity(
Messages[locale].finshed_working(cmdName)
);
} }
await dc.endAll();
await dc.replace("/answer", { query: text });
} }
]); ]);
} }
@ -152,8 +154,6 @@ export class AdminDialog extends IGBDialog {
min.instance.botId min.instance.botId
}/token&state=${state}&response_mode=query`; }/token&state=${state}&response_mode=query`;
await dc.context.sendActivity( await dc.context.sendActivity(Messages[locale].consent(url));
Messages[locale].consent(url)
);
} }
} }

View file

@ -3,7 +3,8 @@ export const Messages = {
authenticate: "Please, authenticate:", authenticate: "Please, authenticate:",
welcome: "Welcome to Pragmatismo.io GeneralBots Administration.", welcome: "Welcome to Pragmatismo.io GeneralBots Administration.",
which_task: "Which task do you wanna run now?", which_task: "Which task do you wanna run now?",
working:(command)=> `I'm working on ${command}`, working:(command)=> `I'm working on ${command}...`,
finshed_working:"Done.",
unknown_command: text => unknown_command: text =>
`Well, but ${text} is not a administrative General Bots command, I will try to search for it.`, `Well, but ${text} is not a administrative General Bots command, I will try to search for it.`,
hi: text => `Hello, ${text}.`, hi: text => `Hello, ${text}.`,

View file

@ -58,11 +58,11 @@ export class GBConfigService {
if (!value) { if (!value) {
switch (key) { switch (key) {
case "DATABASE_DIALECT": case "STORAGE_DIALECT":
value = "sqlite" value = "sqlite"
break break
case "DATABASE_STORAGE": case "STORAGE_STORAGE":
value = "./guaribas.sqlite" value = "./guaribas.sqlite"
break break
@ -70,17 +70,17 @@ export class GBConfigService {
value = undefined value = undefined
break break
case "DATABASE_SYNC": case "STORAGE_SYNC":
case "DATABASE_SYNC_ALTER": case "STORAGE_SYNC_ALTER":
case "DATABASE_SYNC_FORCE": case "STORAGE_SYNC_FORCE":
value = "false" value = "false"
break break
case "DATABASE_LOGGING": case "STORAGE_LOGGING":
value = "false" value = "false"
break break
case "DATABASE_ENCRYPT": case "STORAGE_ENCRYPT":
value = "true" value = "true"
break break

View file

@ -42,6 +42,7 @@ import { LuisRecognizer } from "botbuilder-ai";
import { MessageFactory } from "botbuilder"; import { MessageFactory } from "botbuilder";
import { Messages } from "../strings"; import { Messages } from "../strings";
import { AzureText } from "pragmatismo-io-framework"; import { AzureText } from "pragmatismo-io-framework";
import { any } from "bluebird";
const Nexmo = require("nexmo"); const Nexmo = require("nexmo");
export interface LanguagePickerSettings { export interface LanguagePickerSettings {
@ -103,31 +104,44 @@ export class GBConversationalService implements IGBConversationalService {
subscriptionKey: min.instance.nlpSubscriptionKey, subscriptionKey: min.instance.nlpSubscriptionKey,
serviceEndpoint: min.instance.nlpServerUrl serviceEndpoint: min.instance.nlpServerUrl
}); });
let res = await model.recognize(dc.context);
let nlp: any;
try {
nlp = await model.recognize(dc.context);
} catch (error) {
let msg = `Error calling NLP server, check if you have a published model and assigned keys on the service. Error: ${
error.statusCode ? error.statusCode : ""
} ${error.message}`;
return Promise.reject(new Error(msg));
}
// Resolves intents returned from LUIS. // Resolves intents returned from LUIS.
let topIntent = LuisRecognizer.topIntent(res); let topIntent = LuisRecognizer.topIntent(nlp);
if (topIntent) { if (topIntent) {
var intent = topIntent; var intent = topIntent;
var entity = var entity =
res.entities && res.entities.length > 0 nlp.entities && nlp.entities.length > 0
? res.entities[0].entity.toUpperCase() ? nlp.entities[0].entity.toUpperCase()
: null; : null;
if (intent === "None") {
return Promise.resolve(false);
}
logger.info("NLP called:" + intent + ", " + entity); logger.info("NLP called:" + intent + ", " + entity);
try { try {
await dc.replace("/" + intent, res.entities); await dc.replace("/" + intent, nlp.entities);
return Promise.resolve(true);
} catch (error) { } catch (error) {
let msg = `Error running NLP (${intent}): ${error}`; let msg = `Error finding dialog associated to NLP event: ${intent}: ${
logger.info(msg); error.message
return Promise.reject(msg); }`;
return Promise.reject(new Error(msg));
} }
return Promise.resolve(true);
} else {
return Promise.resolve(false);
} }
return Promise.resolve(false);
} }
async checkLanguage(dc, min, text) { async checkLanguage(dc, min, text) {

View file

@ -77,7 +77,7 @@ export class GBCoreService implements IGBCoreService {
* Constructor retrieves default values. * Constructor retrieves default values.
*/ */
constructor() { constructor() {
this.dialect = GBConfigService.get("DATABASE_DIALECT") this.dialect = GBConfigService.get("STORAGE_DIALECT")
this.adminService = new GBAdminService(this) this.adminService = new GBAdminService(this)
} }
@ -94,22 +94,22 @@ export class GBCoreService implements IGBCoreService {
let storage: string | undefined let storage: string | undefined
if (this.dialect === "mssql") { if (this.dialect === "mssql") {
host = GBConfigService.get("DATABASE_HOST") host = GBConfigService.get("STORAGE_HOST")
database = GBConfigService.get("DATABASE_NAME") database = GBConfigService.get("STORAGE_NAME")
username = GBConfigService.get("DATABASE_USERNAME") username = GBConfigService.get("STORAGE_USERNAME")
password = GBConfigService.get("DATABASE_PASSWORD") password = GBConfigService.get("STORAGE_PASSWORD")
} else if (this.dialect === "sqlite") { } else if (this.dialect === "sqlite") {
storage = GBConfigService.get("DATABASE_STORAGE") storage = GBConfigService.get("STORAGE_STORAGE")
} }
let logging = let logging =
GBConfigService.get("DATABASE_LOGGING") === "true" GBConfigService.get("STORAGE_LOGGING") === "true"
? (str: string) => { ? (str: string) => {
logger.info(str) logger.info(str)
} }
: false : false
let encrypt = GBConfigService.get("DATABASE_ENCRYPT") === "true" let encrypt = GBConfigService.get("STORAGE_ENCRYPT") === "true"
this.sequelize = new Sequelize({ this.sequelize = new Sequelize({
host: host, host: host,
@ -247,9 +247,9 @@ export class GBCoreService implements IGBCoreService {
} }
async syncDatabaseStructure() { async syncDatabaseStructure() {
if (GBConfigService.get("DATABASE_SYNC") === "true") { if (GBConfigService.get("STORAGE_SYNC") === "true") {
const alter = GBConfigService.get("DATABASE_SYNC_ALTER") === "true" const alter = GBConfigService.get("STORAGE_SYNC_ALTER") === "true"
const force = GBConfigService.get("DATABASE_SYNC_FORCE") === "true" const force = GBConfigService.get("STORAGE_SYNC_FORCE") === "true"
logger.info("Syncing database...") logger.info("Syncing database...")
return this.sequelize.sync({ return this.sequelize.sync({
alter: alter, alter: alter,
@ -258,7 +258,6 @@ export class GBCoreService implements IGBCoreService {
} else { } else {
let msg = "Database synchronization is disabled."; let msg = "Database synchronization is disabled.";
logger.info(msg) logger.info(msg)
return Promise.reject(msg)
} }
} }

View file

@ -152,7 +152,11 @@ export class GBDeployer {
.done(async result => { .done(async result => {
logger.info(`App Package deployment done.`); logger.info(`App Package deployment done.`);
await core.syncDatabaseStructure(); try{
await core.syncDatabaseStructure();
}catch(e){
throw e;
}
/** Deploys all .gbot files first. */ /** Deploys all .gbot files first. */
@ -212,7 +216,7 @@ export class GBDeployer {
}) })
.done(function(result) { .done(function(result) {
if (botPackages.length === 0) { if (botPackages.length === 0) {
logger.info( logger.warn(
"The server is running with no bot instances, at least one .gbot file must be deployed." "The server is running with no bot instances, at least one .gbot file must be deployed."
); );
} else { } else {

View file

@ -62,6 +62,8 @@ import {
IGBCoreService, IGBCoreService,
IGBConversationalService IGBConversationalService
} from "botlib"; } from "botlib";
import { GuaribasInstance } from "../models/GBModel";
import { Messages } from "../strings";
/** Minimal service layer for a bot. */ /** Minimal service layer for a bot. */
@ -103,7 +105,8 @@ export class GBMinService {
async buildMin( async buildMin(
server: any, server: any,
appPackages: Array<IGBPackage> appPackages: Array<IGBPackage>,
instances:GuaribasInstance[]
): Promise<GBMinInstance> { ): Promise<GBMinInstance> {
// Serves default UI on root address '/'. // Serves default UI on root address '/'.
@ -112,10 +115,7 @@ export class GBMinService {
"/", "/",
express.static(UrlJoin(GBDeployer.deployFolder, uiPackage, "build")) express.static(UrlJoin(GBDeployer.deployFolder, uiPackage, "build"))
); );
// Loads all bot instances from storage and starting loading them.
let instances = await this.core.loadInstances();
Promise.all( Promise.all(
instances.map(async instance => { instances.map(async instance => {
// Gets the authorization key for each instance from Bot Service. // Gets the authorization key for each instance from Bot Service.
@ -363,11 +363,15 @@ export class GBMinService {
instance: any, instance: any,
appPackages: any[] appPackages: any[]
) { ) {
return adapter.processActivity(req, res, async context => { return adapter.processActivity(req, res, async context => {
const state = conversationState.get(context);
const dc = min.dialogs.createContext(context, state);
dc.context.activity.locale = "en-US"; // TODO: Make dynamic.
try { try {
const state = conversationState.get(context);
const dc = min.dialogs.createContext(context, state);
dc.context.activity.locale = "en-US";
const user = min.userState.get(dc.context); const user = min.userState.get(dc.context);
if (!user.loaded) { if (!user.loaded) {
@ -459,10 +463,14 @@ export class GBMinService {
} }
} }
} catch (error) { } catch (error) {
let msg = `Error in main activity: ${error.message} ${ let msg = `ERROR: ${error.message} ${
error.stack ? error.stack : "" error.stack ? error.stack : ""
}`; }`;
logger.error(msg); logger.error(msg);
await dc.context.sendActivity(Messages[dc.context.activity.locale].very_sorry_about_error)
await dc.begin("/ask", { isReturning: true });
} }
}); });
} }
@ -487,10 +495,9 @@ export class GBMinService {
return Promise.resolve(JSON.parse(json)); return Promise.resolve(JSON.parse(json));
} catch (error) { } catch (error) {
let msg = `Error calling Direct Line client, verify Bot endpoint on the cloud. Error is: ${error}.`; let msg = `Error calling Direct Line client, verify Bot endpoint on the cloud. Error is: ${error}.`;
logger.error(msg); return Promise.reject(new Error(msg));
return Promise.reject(msg);
} }
} }
/** /**
* Gets a Speech to Text / Text to Speech token from the provider. * Gets a Speech to Text / Text to Speech token from the provider.
@ -513,8 +520,7 @@ export class GBMinService {
return await request(options); return await request(options);
} catch (error) { } catch (error) {
let msg = `Error calling Speech to Text client. Error is: ${error}.`; let msg = `Error calling Speech to Text client. Error is: ${error}.`;
logger.error(msg); return Promise.reject(new Error(msg));
return Promise.reject(msg);
} }
} }
} }

View file

@ -4,13 +4,16 @@ export const Messages = {
good_morning: "good morning", good_morning: "good morning",
good_evening: "good evening", good_evening: "good evening",
good_night: "good night", good_night: "good night",
hi: (msg ) => `Hello, ${msg}.` hi: (msg ) => `Hello, ${msg}.`,
very_sorry_about_error: `I'm sorry to inform that there was an error which was recorded to be solved.`
}, },
"pt-BR": { "pt-BR": {
show_video: "Vou te mostrar um vídeo. Por favor, aguarde...", show_video: "Vou te mostrar um vídeo. Por favor, aguarde...",
good_morning: "bom dia", good_morning: "bom dia",
good_evening: "boa tarde", good_evening: "boa tarde",
good_night: "boa noite", good_night: "boa noite",
hi: (msg ) => `Oi, ${msg}.` hi: (msg ) => `Oi, ${msg}.`,
very_sorry_about_error: `Lamento, ocorreu um erro que já foi registrado para ser tratado.`
} }
}; };

View file

@ -148,7 +148,7 @@ export class KBService {
} }
// TODO: Filter by instance. what = `${what}&$filter=instanceId eq ${instanceId}` // TODO: Filter by instance. what = `${what}&$filter=instanceId eq ${instanceId}`
try { try {
if (instance.searchKey && GBConfigService.get("DATABASE_DIALECT") == "mssql") { if (instance.searchKey && GBConfigService.get("STORAGE_DIALECT") == "mssql") {
let service = new AzureSearch( let service = new AzureSearch(
instance.searchKey, instance.searchKey,
instance.searchHost, instance.searchHost,
@ -179,7 +179,7 @@ export class KBService {
} }
} }
catch (reason) { catch (reason) {
return Promise.reject(reason) return Promise.reject(new Error(reason));
} }
} }

View file

@ -55,6 +55,7 @@ import { GBAdminPackage } from "../deploy/admin.gbapp/index";
import { GBCustomerSatisfactionPackage } from "../deploy/customer-satisfaction.gbapp"; import { GBCustomerSatisfactionPackage } from "../deploy/customer-satisfaction.gbapp";
import { IGBPackage } from "botlib"; import { IGBPackage } from "botlib";
import { GBAdminService } from "../deploy/admin.gbapp/services/GBAdminService"; import { GBAdminService } from "../deploy/admin.gbapp/services/GBAdminService";
import { GuaribasInstance } from "deploy/core.gbapp/models/GBModel";
let appPackages = new Array<IGBPackage>(); let appPackages = new Array<IGBPackage>();
@ -62,7 +63,10 @@ let appPackages = new Array<IGBPackage>();
* General Bots open-core entry point. * General Bots open-core entry point.
*/ */
export class GBServer { export class GBServer {
/** Program entry-point. */ /**
* Program entry-point.
*/
static run() { static run() {
// Creates a basic HTTP server that will serve several URL, one for each // Creates a basic HTTP server that will serve several URL, one for each
// bot instance. This allows the same server to attend multiple Bot on // bot instance. This allows the same server to attend multiple Bot on
@ -108,6 +112,8 @@ export class GBServer {
); );
} }
// Creates the minimal service shared across all .gbapps.
let minService = new GBMinService( let minService = new GBMinService(
core, core,
conversationalService, conversationalService,
@ -132,16 +138,48 @@ export class GBServer {
p.loadPackage(core, core.sequelize); p.loadPackage(core, core.sequelize);
}); });
// Loads all bot instances from object storage, if it's formatted.
logger.info(`All instances are being now loaded...`);
let instances: GuaribasInstance[];
try {
instances = await core.loadInstances();
} catch (error) {
// Check if storage is empty and needs formatting.
let isInvalidObject =
error.parent.number == 208 || error.parent.errno == 1; // MSSQL or SQLITE.
if (
isInvalidObject &&
GBConfigService.get("STORAGE_SYNC") !== "true"
) {
throw `Operating storage is out of sync or there is a storage connection error. Try setting STORAGE_SYNC to true in .env file. Error: ${
error.message
}.`;
}
}
// Deploy packages and format object store according to .gbapp storage models.
logger.info(`Deploying packages.`); logger.info(`Deploying packages.`);
await deployer.deployPackages(core, server, appPackages); await deployer.deployPackages(core, server, appPackages);
// If instances is undefined here it's because storage has been formatted.
// Load all instances from .gbot found on deploy package directory.
if (!instances) {
instances = await core.loadInstances();
}
// Setup server dynamic (per bot instance) resources and listeners.
logger.info(`Building minimal instances.`); logger.info(`Building minimal instances.`);
await minService.buildMin(server, appPackages); await minService.buildMin(server, appPackages, instances);
logger.info(`All instances are now loaded and available.`);
logger.info(`The Bot Server is in RUNNING mode...`); logger.info(`The Bot Server is in RUNNING mode...`);
return core; return core;
} catch (err) { } catch (err) {
logger.info(`STOP: ${err} ${err.stack ? err.stack : ""}`); logger.info(`STOP: ${err} ${err.stack ? err.stack : ""}`);
process.exit(1);
} }
})(); })();
}); });