Skip to content

Commit

Permalink
Full stack/refactor internal session vars - adapt backend code to bot…
Browse files Browse the repository at this point in the history
…State data model (#1966)

* chore(dev-template): remove warnings in Actions and sort imports

* chore(dev-template): project changes regarding botState data model

* chore(core): split data models into User, Session and BotState

* chore(api): changes regarding botState in botonic/api

* chore(core): core-bot and router logic to work with botState

* chore(core): adapt tests to work with botState model, disabling handoff tests for 1.0

* Full stack/refactor internal session vars - adapt frontend code to botState data model (#1967)

* chore(react): changes in webchat regarding move to botState model

* refactor(core/api/dev-template): remove initial locale and minor fixes

* refactor(react): remove locale from initial session and make use of merge func
  • Loading branch information
vanbasten17 authored Oct 29, 2021
1 parent 3b5fb3b commit cce00af
Show file tree
Hide file tree
Showing 48 changed files with 752 additions and 517 deletions.
20 changes: 10 additions & 10 deletions packages/botonic-api/src/rest/routes/events.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { BotonicEvent, MessageEventAck } from '@botonic/core'
import { BotonicEvent, MessageEventAck, PROVIDER } from '@botonic/core'
import { dataProviderFactory } from '@botonic/core/lib/esm/data-provider'
import { Router } from 'express'
import jwt from 'express-jwt'
Expand Down Expand Up @@ -76,25 +76,25 @@ export default function eventsRouter(args: any): Router {
const { userId } = req.user
const { message, sender } = req.body
let user = await dp.getUser(userId)
user = await dp.updateUser({
...user,
session: JSON.stringify({ user: sender }),
})
// TODO: Next iterations: We should receive an event with userId and eventId from frontend
const updatedUser = { ...user, ...sender }
user = await dp.updateUser(updatedUser)
// TODO: Only update ack for webchat
// TODO: Specific logic for webchat, move to webchat-events?
const webchatMsgId = message.id
await handlers.run('sender', {
events: [
{
action: 'update_message_info',
message: { id: webchatMsgId, ack: MessageEventAck.RECEIVED },
id: webchatMsgId,
ack: MessageEventAck.RECEIVED,
},
],
websocketId: user.websocketId,
})
await handlers.run('botExecutor', {
input: message,
session: JSON.parse(user.session),
lastRoutePath: user.route,
input: { ...message, userId }, // To identify user executing the input
session: user.session,
botState: user.botState,
websocketId: user.websocketId,
})
} catch (e) {
Expand Down
59 changes: 39 additions & 20 deletions packages/botonic-core/src/core-bot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
BotonicEvent,
BotRequest,
BotResponse,
BotState,
Locales,
MessageEventAck,
MessageEventFrom,
Expand Down Expand Up @@ -81,26 +82,30 @@ export class CoreBot {
)
}

getString(id: string, session: Session): string {
// @ts-ignore
return getString(this.locales, session.__locale, id)
getString(id: string, botState: BotState): string {
if (!botState.locale) {
console.error('Locale is not defined')
return ''
}
return getString(this.locales, botState.locale, id)
}

setLocale(locale: string, session: Session): void {
session.__locale = locale
setLocale(locale: string, botState: BotState): void {
botState.locale = locale
}

async input({
input,
session,
lastRoutePath,
botState,
dataProvider,
}: BotRequest): Promise<BotResponse> {
session = session || {}
if (!session.__locale) session.__locale = 'en'
if (!botState.locale) botState.locale = 'en'
// @ts-ignore
const userId = input.userId

const parsedUserEvent = this.botonicOutputParser.inputToBotonicEvent(input)

const parsedUserEvent = this.botonicOutputParser.parseFromUserInput(input)
const userId = session.user.id
if (dataProvider) {
// TODO: Next iterations. Review cycle of commited events to DB when messages change their ACK
// @ts-ignore
Expand All @@ -120,7 +125,7 @@ export class CoreBot {
'pre',
input,
session,
lastRoutePath,
botState,
undefined,
undefined,
dataProvider
Expand All @@ -133,7 +138,7 @@ export class CoreBot {
...(await getComputedRoutes(this.routes, {
input,
session,
lastRoutePath,
botState,
})),
...this.defaultRoutes,
],
Expand All @@ -144,18 +149,19 @@ export class CoreBot {
const output = (this.router as Router).processInput(
input,
session,
lastRoutePath
botState
)

const request = {
getString: stringId => this.getString(stringId, session),
setLocale: locale => this.setLocale(locale, session),
getString: stringId => this.getString(stringId, botState),
setLocale: locale => this.setLocale(locale, botState),
session: session || {},
params: output.params || {},
input: input,
plugins: this.plugins,
defaultTyping: this.defaultTyping,
defaultDelay: this.defaultDelay,
lastRoutePath,
botState,
dataProvider,
}

Expand All @@ -171,14 +177,15 @@ export class CoreBot {
// console.error(e)
}

lastRoutePath = output.lastRoutePath
botState.lastRoutePath = output.botState.lastRoutePath

if (this.plugins) {
await runPlugins(
this.plugins,
'post',
input,
session,
lastRoutePath,
botState,
response,
messageEvents,
dataProvider
Expand All @@ -200,13 +207,25 @@ export class CoreBot {
}
}

session.is_first_interaction = false
botState.isFirstInteraction = false

if (dataProvider) {
const user = await dataProvider.getUser(userId)
if (!user) {
// throw error
} else {
const updatedUser = { ...user, session, botState }
// @ts-ignore
await dataProvider.updateUser(updatedUser)
}
}
// TODO: return also updatedUser?
return {
input,
response,
messageEvents,
session,
lastRoutePath,
botState,
dataProvider,
}
}
Expand Down
8 changes: 7 additions & 1 deletion packages/botonic-core/src/data-provider/dynamodb-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,15 @@ export function getUserEntity(table: Table): Entity<any> {
},
userId: [SORT_KEY_NAME, 0],
websocketId: 'string',
name: 'string',
userName: 'string',
channel: 'string',
idFromChannel: 'string',
isOnline: 'boolean',
route: 'string',
session: 'string',
session: 'map',
botState: 'map',
details: 'map',
},
table,
})
Expand Down
24 changes: 13 additions & 11 deletions packages/botonic-core/src/handoff.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import axios from 'axios'

import { PATH_PAYLOAD_IDENTIFIER } from './constants'
import { Session } from './models'
import { BotState, Session } from './models'

const HUBTYPE_API_URL = 'https://api.hubtype.com'

Expand Down Expand Up @@ -57,7 +57,7 @@ export async function getOpenQueues(
}

export class HandOffBuilder {
_session: SessionWithBotonicAction
_botState: any
_queue: string
_onFinish: string
_email: string
Expand All @@ -66,8 +66,8 @@ export class HandOffBuilder {
_caseInfo: string
_shadowing: boolean

constructor(session: SessionWithBotonicAction) {
this._session = session
constructor(botState: any) {
this._botState = botState
}

withQueue(queueNameOrId: string): this {
Expand Down Expand Up @@ -112,7 +112,7 @@ export class HandOffBuilder {

async handOff(): Promise<void> {
return _humanHandOff(
this._session,
this._botState,
this._queue,
this._onFinish,
this._email,
Expand Down Expand Up @@ -154,7 +154,7 @@ interface HubtypeHandoffParams {
on_finish?: string
}
async function _humanHandOff(
session: SessionWithBotonicAction,
botState: any,
queueNameOrId = '',
onFinish: string,
agentEmail = '',
Expand Down Expand Up @@ -185,7 +185,8 @@ async function _humanHandOff(
if (onFinish) {
params.on_finish = onFinish
}
session._botonic_action = `create_case:${JSON.stringify(params)}`
botState.botonicAction = `create_case:${JSON.stringify(params)}`
botState.isHandoff = true
}

export async function storeCaseRating(
Expand Down Expand Up @@ -256,14 +257,15 @@ export async function getAgentVacationRanges(
}

export function cancelHandoff(
session: SessionWithBotonicAction,
botState: BotState,
typification: string | null = null
): void {
let action = 'discard_case'
if (typification) action = `${action}:${JSON.stringify({ typification })}`
session._botonic_action = action
botState.botonicAction = action
botState.isHandoff = false // TODO: Review handoff functionalities
}

export function deleteUser(session: SessionWithBotonicAction): void {
session._botonic_action = `delete_user`
export function deleteUser(botState: BotState): void {
botState.botonicAction = `delete_user`
}
10 changes: 5 additions & 5 deletions packages/botonic-core/src/hubtype-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import axios, { AxiosResponse } from 'axios'
import Pusher, { AuthOptions, Channel } from 'pusher-js'
import Channels from 'pusher-js/types/src/core/channels/channels'

import { Input, SessionUser } from './models'
import { Input } from './models'
import { getWebpackEnvVar } from './utils'

interface UnsentInput {
Expand All @@ -21,7 +21,7 @@ interface ServerConfig {
}
interface HubtypeServiceArgs {
appId: string
user: SessionUser
user: any
lastMessageId: string
lastMessageUpdateDate: string
onEvent: any
Expand Down Expand Up @@ -50,7 +50,7 @@ const PONG_TIMEOUT = 5 * 1000 // https://pusher.com/docs/channels/using_channels
*/
export class HubtypeService {
appId: string
user: SessionUser
user: any
lastMessageId: string
lastMessageUpdateDate: string
onEvent: any
Expand Down Expand Up @@ -185,7 +185,7 @@ export class HubtypeService {
}

handleConnectionChange(online: boolean): void {
this.onPusherEvent({ action: 'connectionChange', online })
this.onPusherEvent({ action: 'connection_change', online })
}

onPusherEvent(event: any): void {
Expand Down Expand Up @@ -213,7 +213,7 @@ export class HubtypeService {
/**
* @return {Promise<void>}
*/
async postMessage(user: SessionUser, message: any): Promise<void> {
async postMessage(user: any, message: any): Promise<void> {
try {
// @ts-ignore
await this.init(user)
Expand Down
12 changes: 12 additions & 0 deletions packages/botonic-core/src/models/bot-state.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { RoutePath } from './legacy-types'

export interface BotState {
botId: string
isFirstInteraction: boolean
isHandoff: boolean
isShadowing: boolean
lastRoutePath: RoutePath
locale?: string
retries: number
botonicAction?: any
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,6 @@ export interface BotonicMessageEvent extends BaseEvent {
type: MessageEventTypes
typing: number
delay: number
// idFromChannel?:string references to msgId
// also channel
}
2 changes: 2 additions & 0 deletions packages/botonic-core/src/models/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export * from './bot-state'
export * from './events'
export * from './legacy-types'
export * from './session'
export * from './user'
Loading

0 comments on commit cce00af

Please sign in to comment.