Skip to content

Commit

Permalink
Merge pull request #1155 from botpress/rl_talk_route
Browse files Browse the repository at this point in the history
feat(core): add converse api
  • Loading branch information
rndlaine committed Nov 23, 2018
2 parents 50fedce + cc356c7 commit de098dd
Show file tree
Hide file tree
Showing 26 changed files with 953 additions and 679 deletions.
3 changes: 2 additions & 1 deletion bench/package.json
Expand Up @@ -7,7 +7,8 @@
"license": "AGPL-3.0",
"private": true,
"scripts": {
"start": "artillery run ./stress_messages.yml"
"web": "artillery run ./stress_channel_web.yml",
"api": "artillery run ./stress_channel_api.yml"
},
"dependencies": {
"artillery": "^1.6.0-25"
Expand Down
22 changes: 22 additions & 0 deletions bench/stress_channel_api.yml
@@ -0,0 +1,22 @@
config:
target: 'http://localhost:3000'
phases:
- duration: 10 # lasts for 'X' seconds
arrivalRate: 1 # 'X' new users per second
payload:
path: 'users.csv'
fields: ['prefix', 'uuid']
order: 'sequence'
processor: './functions.js'
scenarios:
- name: Each user sends 'X' messages to channel-api
flow:
- log: Testing channel-api
- function: 'setupTestId'
- loop:
- post:
url: '/api/v1/bots/welcome-bot/converse/{{ prefix }}{{ testId }}_{{ uuid }}'
json:
type: 'text'
text: 'U{{ testId }}_{{ uuid }}|M{{ $loopCount }}'
count: 50
3 changes: 2 additions & 1 deletion bench/stress_messages.yml → bench/stress_channel_web.yml
Expand Up @@ -9,8 +9,9 @@ config:
order: 'sequence'
processor: './functions.js'
scenarios:
- name: Each user sends 'X' messages
- name: Each user sends 'X' messages to channel-web
flow:
- log: Testing channel-web
- function: 'setupTestId'
- loop:
- post:
Expand Down
8 changes: 2 additions & 6 deletions build/gulp.ui.js
Expand Up @@ -63,7 +63,7 @@ const watchAdmin = cb => {
})
}

const watch = cb => {
const watchStudio = cb => {
exec('yarn && yarn watch', { cwd: 'src/bp/ui-studio' }, (err, stdout, stderr) => {
if (err) {
console.error(stderr)
Expand All @@ -73,12 +73,8 @@ const watch = cb => {
})
}

const watchStudio = () => {
return gulp.series([cleanStudioAssets, createStudioSymlink, watch])
}

const watchAll = () => {
return gulp.parallel([watchStudio(), watchAdmin])
return gulp.parallel([watchStudio, watchAdmin])
}

module.exports = {
Expand Down
2 changes: 1 addition & 1 deletion gulpfile.js
Expand Up @@ -34,7 +34,7 @@ gulp.task(

gulp.task('watch', gulp.parallel([core.watch, ui.watchAll()]))
gulp.task('watch:core', core.watch)
gulp.task('watch:studio', ui.watchStudio())
gulp.task('watch:studio', ui.watchStudio)
gulp.task('watch:admin', ui.watchAdmin)

gulp.task('clean:node', cb => rimraf('**/node_modules/**', cb))
Expand Down
2 changes: 1 addition & 1 deletion src/bp/core/api.ts
Expand Up @@ -15,7 +15,7 @@ import { SessionRepository, UserRepository } from './repositories'
import { Event, RealTimePayload } from './sdk/impl'
import HTTPServer from './server'
import { GhostService } from './services'
import { CMSService } from './services/cms/cms-service'
import { CMSService } from './services/cms'
import { DialogEngine } from './services/dialog/engine'
import { SessionIdFactory } from './services/dialog/session/id-factory'
import { ScopedGhostService } from './services/ghost/service'
Expand Down
2 changes: 1 addition & 1 deletion src/bp/core/bot-loader.ts
Expand Up @@ -8,7 +8,7 @@ import { ConfigProvider } from './config/config-loader'
import Database from './database'
import { ModuleLoader } from './module-loader'
import { GhostService } from './services'
import { CMSService } from './services/cms/cms-service'
import { CMSService } from './services/cms'
import { Hooks, HookService } from './services/hook/hook-service'
import { TYPES } from './types'

Expand Down
5 changes: 3 additions & 2 deletions src/bp/core/botpress.ts
Expand Up @@ -18,7 +18,8 @@ import { LoggerPersister, LoggerProvider } from './logger'
import { ModuleLoader } from './module-loader'
import HTTPServer from './server'
import { GhostService } from './services'
import { CMSService } from './services/cms/cms-service'
import { CMSService } from './services/cms'
import { converseApiEvents } from './services/converse'
import { DecisionEngine } from './services/dialog/decision-engine'
import { DialogEngine, ProcessingError } from './services/dialog/engine'
import { DialogJanitor } from './services/dialog/janitor'
Expand Down Expand Up @@ -158,9 +159,9 @@ export class Botpress {

this.eventEngine.onAfterIncomingMiddleware = async (event: sdk.IO.IncomingEvent) => {
await this.hookService.executeHook(new Hooks.AfterIncomingMiddleware(this.api, event))

const sessionId = SessionIdFactory.createIdFromEvent(event)
await this.decisionEngine.processEvent(sessionId, event)
await converseApiEvents.emitAsync(`done.${event.target}`, event)
}

this.dataRetentionService.initialize()
Expand Down
141 changes: 141 additions & 0 deletions src/bp/core/routers/bots/content.ts
@@ -0,0 +1,141 @@
import { ContentElement } from 'botpress/sdk'
import { AdminService } from 'core/services/admin/service'
import AuthService, { TOKEN_AUDIENCE } from 'core/services/auth/auth-service'
import { DefaultSearchParams } from 'core/services/cms'
import { CMSService } from 'core/services/cms'
import { RequestHandler, Router } from 'express'

import { CustomRouter } from '..'
import { checkTokenHeader, needPermissions } from '../util'

export class ContentRouter implements CustomRouter {
public readonly router: Router

private _checkTokenHeader: RequestHandler
private _needPermissions: (operation: string, resource: string) => RequestHandler

constructor(private adminService: AdminService, private authService: AuthService, private cms: CMSService) {
this._needPermissions = needPermissions(this.adminService)
this._checkTokenHeader = checkTokenHeader(this.authService, TOKEN_AUDIENCE)

this.router = Router({ mergeParams: true })
this.setupRoutes()
}

setupRoutes() {
this.router.get(
'/types',
this._checkTokenHeader,
this._needPermissions('read', 'bot.content'),
async (req, res) => {
const botId = req.params.botId
const types = await this.cms.getAllContentTypes(botId)

const response = await Promise.map(types, async type => {
const count = await this.cms.countContentElementsForContentType(botId, type.id)
return {
id: type.id,
count,
title: type.title,
schema: {
json: type.jsonSchema,
ui: type.uiSchema,
title: type.title,
renderer: type.id
}
}
})

res.send(response)
}
)

this.router.get(
'/elements/count',
this._checkTokenHeader,
this._needPermissions('read', 'bot.content'),
async (req, res) => {
const botId = req.params.botId
const count = await this.cms.countContentElements(botId)
res.send({ count })
}
)

this.router.get(
'/:contentType?/elements',
this._checkTokenHeader,
this._needPermissions('read', 'bot.content'),
async (req, res) => {
const { botId, contentType } = req.params
const query = req.query || {}

const elements = await this.cms.listContentElements(botId, contentType, {
...DefaultSearchParams,
count: Number(query.count) || DefaultSearchParams.count,
from: Number(query.from) || DefaultSearchParams.from,
searchTerm: query.searchTerm || DefaultSearchParams.searchTerm,
ids: (query.ids && query.ids.split(',')) || DefaultSearchParams.ids
})

const augmentedElements = await Promise.map(elements, this._augmentElement)
res.send(augmentedElements)
}
)

this.router.get(
'/:contentType?/elements/count',
this._checkTokenHeader,
this._needPermissions('read', 'bot.content'),
async (req, res) => {
const { botId, contentType } = req.params
const count = await this.cms.countContentElementsForContentType(botId, contentType)
res.send({ count })
}
)

this.router.get(
'/elements/:elementId',
this._checkTokenHeader,
this._needPermissions('read', 'bot.content'),
async (req, res) => {
const { botId, elementId } = req.params
const element = await this.cms.getContentElement(botId, elementId)
res.send(await this._augmentElement(element))
}
)

this.router.post(
'/:contentType/elements/:elementId?',
this._checkTokenHeader,
this._needPermissions('write', 'bot.content'),
async (req, res) => {
const { botId, contentType, elementId } = req.params
const element = await this.cms.createOrUpdateContentElement(botId, contentType, req.body.formData, elementId)
res.send(element)
}
)

this.router.post(
'/elements/bulk_delete',
this._checkTokenHeader,
this._needPermissions('write', 'bot.content'),
async (req, res) => {
await this.cms.deleteContentElements(req.params.botId, req.body)
res.sendStatus(200)
}
)
}

private _augmentElement = async (element: ContentElement) => {
const contentType = await this.cms.getContentType(element.contentType)
return {
...element,
schema: {
json: contentType.jsonSchema,
ui: contentType.uiSchema,
title: contentType.title,
renderer: contentType.id
}
}
}
}
39 changes: 39 additions & 0 deletions src/bp/core/routers/bots/converse.ts
@@ -0,0 +1,39 @@
import { ConverseService } from 'core/services/converse'
import { Router } from 'express'
import _ from 'lodash'

import { CustomRouter } from '..'

export class ConverseRouter implements CustomRouter {
public readonly router: Router

constructor(private converseService: ConverseService) {
this.router = Router({ mergeParams: true })
this.setupRoutes()
}

setupRoutes() {
this.router.post('/:userId', async (req, res) => {
const { userId, botId } = req.params
const rawOutput = await this.converseService.sendMessage(botId, userId, req.body).catch(err => {
return res.status(408).json({ error: err })
})

const formatedOutput = this.prepareResponse(rawOutput, req.query.include)
return res.json(formatedOutput)
})
}

private prepareResponse(output, params: string) {
const split = (params && params.toLowerCase().split(',')) || []

if (!split.includes('nlu')) {
delete output.nlu
}
if (!split.includes('state')) {
delete output.state
}

return output
}
}

0 comments on commit de098dd

Please sign in to comment.