* 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
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.
3. In case of Microsoft SQL Server add the following keys: DATABASE_HOST, DATABASE_NAME, DATABASE_USERNAME, DATABASE_PASSWORD, DATABASE_DIALECT to `mssql`.
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: STORAGE_HOST, STORAGE_NAME, STORAGE_USERNAME, STORAGE_PASSWORD, STORAGE_DIALECT to `mssql`.
Note:

View file

@ -1,5 +1,8 @@
# Release History
## Version 0.1.4
## Version 0.1.3
* FIX: Admin now is internationalized.
@ -24,8 +27,8 @@
## Version 0.0.30
- FIX: Packages updated.
- NEW: DATABASE_SYNC_ALTER environment parameter.
- NEW: DATABASE_SYNC_FORCE environment parameter.
- NEW: STORAGE_SYNC_ALTER environment parameter.
- NEW: STORAGE_SYNC_FORCE environment parameter.
- NEW: Define constraint names in MSSQL.
## Version 0.0.29
@ -44,7 +47,7 @@
- FIX: Packages updated.
- 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

View file

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

View file

@ -3,7 +3,8 @@ export const Messages = {
authenticate: "Please, authenticate:",
welcome: "Welcome to Pragmatismo.io GeneralBots Administration.",
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 =>
`Well, but ${text} is not a administrative General Bots command, I will try to search for it.`,
hi: text => `Hello, ${text}.`,

View file

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

View file

@ -42,6 +42,7 @@ import { LuisRecognizer } from "botbuilder-ai";
import { MessageFactory } from "botbuilder";
import { Messages } from "../strings";
import { AzureText } from "pragmatismo-io-framework";
import { any } from "bluebird";
const Nexmo = require("nexmo");
export interface LanguagePickerSettings {
@ -103,32 +104,45 @@ export class GBConversationalService implements IGBConversationalService {
subscriptionKey: min.instance.nlpSubscriptionKey,
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.
let topIntent = LuisRecognizer.topIntent(res);
let topIntent = LuisRecognizer.topIntent(nlp);
if (topIntent) {
var intent = topIntent;
var entity =
res.entities && res.entities.length > 0
? res.entities[0].entity.toUpperCase()
nlp.entities && nlp.entities.length > 0
? nlp.entities[0].entity.toUpperCase()
: null;
if (intent === "None") {
return Promise.resolve(false);
}
logger.info("NLP called:" + intent + ", " + entity);
try {
await dc.replace("/" + intent, res.entities);
} catch (error) {
let msg = `Error running NLP (${intent}): ${error}`;
logger.info(msg);
return Promise.reject(msg);
}
await dc.replace("/" + intent, nlp.entities);
return Promise.resolve(true);
} else {
return Promise.resolve(false);
} catch (error) {
let msg = `Error finding dialog associated to NLP event: ${intent}: ${
error.message
}`;
return Promise.reject(new Error(msg));
}
}
return Promise.resolve(false);
}
async checkLanguage(dc, min, text) {
let locale = await AzureText.getLocale(

View file

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

View file

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

View file

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

View file

@ -4,13 +4,16 @@ export const Messages = {
good_morning: "good morning",
good_evening: "good evening",
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": {
show_video: "Vou te mostrar um vídeo. Por favor, aguarde...",
good_morning: "bom dia",
good_evening: "boa tarde",
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}`
try {
if (instance.searchKey && GBConfigService.get("DATABASE_DIALECT") == "mssql") {
if (instance.searchKey && GBConfigService.get("STORAGE_DIALECT") == "mssql") {
let service = new AzureSearch(
instance.searchKey,
instance.searchHost,
@ -179,7 +179,7 @@ export class KBService {
}
}
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 { IGBPackage } from "botlib";
import { GBAdminService } from "../deploy/admin.gbapp/services/GBAdminService";
import { GuaribasInstance } from "deploy/core.gbapp/models/GBModel";
let appPackages = new Array<IGBPackage>();
@ -62,7 +63,10 @@ let appPackages = new Array<IGBPackage>();
* General Bots open-core entry point.
*/
export class GBServer {
/** Program entry-point. */
/**
* Program entry-point.
*/
static run() {
// 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
@ -108,6 +112,8 @@ export class GBServer {
);
}
// Creates the minimal service shared across all .gbapps.
let minService = new GBMinService(
core,
conversationalService,
@ -132,16 +138,48 @@ export class GBServer {
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.`);
await deployer.deployPackages(core, server, appPackages);
logger.info(`Building minimal instances.`);
await minService.buildMin(server, appPackages);
logger.info(`All instances are now loaded and available.`);
// 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.`);
await minService.buildMin(server, appPackages, instances);
logger.info(`The Bot Server is in RUNNING mode...`);
return core;
} catch (err) {
logger.info(`STOP: ${err} ${err.stack ? err.stack : ""}`);
process.exit(1);
}
})();
});