BotFlow Caso de estudio

Objetivos

  • Permitir el crecimiento del sistema de forma orgánica, sin tener que modificar código existente cuando se añaden nuevas características.
    • Añadir nuevas plataformas de chat.
    • Implementar nuevos handlers.

Divsión lógica del sistema

  • index.ts y HandlerResolver
  • DI Container
  • PlatformStrategy
  • Handler
  • Clases de apoyo

DI Container

containerMerge.bind(TYPES.PLATFORM_FACTORY).toFactory((context: any) => {
    return (platform, params) => {
      const instance: PlatformStrategy = context.container.get(`${platform.toUpperCase()}_${typePlatform}_PLATFORM`)
      instance.platform = platform
      instance.params = params
      return instance
    }
  });
containerMerge.bind(TYPES.HANDLER_FACTORY).toFactory((context: any) => {
    return (className, environment) => {
      const instance: Handler = context.container.resolve(className)
      instance.environment = environment
      instance.i18n = context.container.get(TYPES.I18n)
      instance.platformFactory = context.container.get(TYPES.PLATFORM_FACTORY)
      instance.dailybBotApi = context.container.get(DailyBotApi)
      return instance
    }
  });

HandlerResolver

  1. Verifica si hay un intent activo en el contexto del usuario.
  2. Si no hay intent activo le pide al platform strategy que le devuelva el intent
  3. Verifica si hay un handler específico para ese intent en la plataforma activa.
  4. Si no hay busca un handler general.
  5. Le pide handler que procese la petición.

¿Cómo determinar el handler?

// src/configs/intents.ts Global handlers
import { DailyHandler } from './../handlers/conversations/DailyHandler'
import { ReportTaskHandler } from './../handlers/tasks/ReportTaskHandler'
import HelpHandler from "../handlers/commands/HelpHandler";
import HelloHandler from "../handlers/commands/HelloHandler";
import ReportHandler from "../handlers/commands/ReportHandler";

export default {
  help: HelpHandler,
  hello: HelloHandler,
  report: ReportHandler,
  followups_team_report: ReportTaskHandler,
  daily: DailyHandler,
}

¿Cómo determinar el handler?

// src/bridges/slack/intents.ts Slack handlers
import { HelloSlackHandler } from "./handlers/HelloSlackHandler";
import { ReportSlackHandler } from "./handlers/ReportSlackHandler";

export default {
  hello: HelloSlackHandler,
  report: ReportSlackHandler,
}
const platformConfig = await import(`./bridges/${platformName}/intents`)
const config = { ...globalConfig, ...platformConfig.default }
const handlerClass = config[intent]

PlatformStrategy

export abstract class PlatformStrategy {
  async getUserContext(intent = null)
  getUserId(): string
  set organization(value)
  get organization()
  set params(params)
  get params()
  get platform()
  set platform(value)
  get channel() 
  set channel(channel: string) 
  mentionUsers(users: any[])
  abstract mentionOneUser(users: any): string
  abstract send(message: any): Promise<any>
}
export interface PlatformStrategyInterface {
  getUserContext(intent?: string): Promise<ConcreteUserContext>
}

export interface EventPlatformStrategyInterface extends PlatformStrategyInterface {
  isPrivateMessage(): boolean
  getIntent(params: any): string
  getUserMentions(): string[]
  getUserResponse(): string
  getUserId(): string
  getOrganizationExternalId(): string
}

SlackStrategy

@injectable()
export default class SlackStrategy extends PlatformStrategy {
  constructor(
    protected userContext: UserContext,
  ) {
    super(userContext)
  }
  mentionOneUser(user: any) {
    return `<@${user.user_external_id}>`
  }
  async send(message: any) {
    let params: any = {
      token: this.organization.access_token,
      channel: this.channel,
      icon_url: settings.DAILYBOT_ASSETS_URL + '/images/dailybot-isologo.png'
    }
    if (typeof message === 'string') {
      params.text = message
    } else {
      params = { ...params, ...message }
    }
    return rp.post(`${settings.SLACK_API_URL}/chat.postMessage`, {
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded'
      },
      body: qs.encode(params)
    })
  }
}

SlackEventStrategy

export class SlackEventStrategy extends SlackStrategy implements EventPlatformStrategyInterface {
  constructor(
    protected userContext: UserContext,
  ) {
    super(userContext)
  }
  getUserId(): string {
    return this.params.event.user
  }
  getOrganizationExternalId(): string {
    return this.params.team_id
  }
  get channel() {
    return this.params.event.channel
  }
  getIntent(params: any) {
    const intentParts = params.event.text.trim().split(' ')
    if (!intentParts.length) {
      return
    }
    let intent = intentParts[0]
    if (intentParts[0].startsWith('<@') && intentParts.length >= 2) {
      intent = intentParts[1]
    }
    return intent.toLowerCase()
  }
  getUserResponse() {
    return this.params.event.text.trim()
  }
  getUserMentions() {
    const parts = this.getUserResponse().split(' ')
    return parts
      // TODO: It's needed filter the DailyBot's id.
      .filter(item => item.startsWith('<@'))
      .map(item => item.replace('<@', '').replace('>', ''))
  }
  isPrivateMessage() {
    return this.params.event.channel_type === 'im'
  }
}

Handler

export abstract class Handler {
  private _dailybBotApi: DailyBotApi
  protected _environment: any
  protected _i18n: any
  protected _platformFactory: any
  set dailybBotApi(dailybBotApi)
  get i18n()
  set i18n(value)
  get platformFactory()
  set platformFactory(value)
  getApiParameters({ platformName, platform  }) 
  protected async loadEnvironment(context)
  get environment()
  set environment(value)
  getOrganization()
  getCurrentUser()
  getLanguage()
  protected trans(text, params = {}, count = null)
  protected createPlatform({ platformName, params })
  async handle(context: any)
  abstract doHandle(context: any): Promise<any> | string
}

Handler

export default class HelloHandler extends Handler {
  async doHandle({ platform }) {
    const currentUser = this.getCurrentUser()
    const user = platform.isPrivateMessage() ?
      currentUser.full_name :
      platform.mentionOneUser(currentUser)
    return this.trans('hello', { user })
  }
}

BotFlow Caso de estudio

By product@dailybot.com

BotFlow Caso de estudio

  • 1,170