diff --git a/packages/botonic-plugin-flow-builder/package-lock.json b/packages/botonic-plugin-flow-builder/package-lock.json index e648657eb9..e71068295f 100644 --- a/packages/botonic-plugin-flow-builder/package-lock.json +++ b/packages/botonic-plugin-flow-builder/package-lock.json @@ -1,6 +1,6 @@ { "name": "@botonic/plugin-flow-builder", - "version": "0.22.3", + "version": "0.22.4-alpha.3", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -61,6 +61,11 @@ "@babel/types": "^7.22.5" } }, + "@babel/helper-plugin-utils": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", + "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==" + }, "@babel/helper-split-export-declaration": { "version": "7.22.5", "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.5.tgz", @@ -94,6 +99,14 @@ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.5.tgz", "integrity": "sha512-DFZMC9LJUG9PLOclRC32G63UXwzqS2koQC8dkx+PLdmt1xSePYpbT/NbsrJy8Q/muXz7o/h/d4A7Fuyixm559Q==" }, + "@babel/plugin-syntax-jsx": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz", + "integrity": "sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==", + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, "@babel/runtime": { "version": "7.22.5", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.5.tgz", @@ -157,6 +170,17 @@ "ulid": "^2.3.0" } }, + "@botonic/plugin-hubtype-analytics": { + "version": "0.22.0-alpha.5", + "resolved": "https://registry.npmjs.org/@botonic/plugin-hubtype-analytics/-/plugin-hubtype-analytics-0.22.0-alpha.5.tgz", + "integrity": "sha512-NURxDVBo6IYIMkfKX1TyNZbSrHcgV+w5ZYh6QN89POUnUE1vcf3QP92RHedXlrTstSzwGI5eG+NMahflWjOUZA==", + "requires": { + "@babel/runtime": "^7.21.0", + "@botonic/core": "^0.22.0", + "@types/axios": "^0.14.0", + "axios": "^1.4.0" + } + }, "@botonic/react": { "version": "0.22.2", "resolved": "https://registry.npmjs.org/@botonic/react/-/react-0.22.2.tgz", @@ -184,6 +208,13 @@ "unescape": "^1.0.1", "use-async-effect": "^2.2.7", "uuid": "^8.3.2" + }, + "dependencies": { + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + } } }, "@emotion/is-prop-valid": { @@ -257,6 +288,14 @@ "resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.4.0.tgz", "integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==" }, + "@types/axios": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@types/axios/-/axios-0.14.0.tgz", + "integrity": "sha512-KqQnQbdYE54D7oa/UmYVMZKq7CO4l8DEENzOKc4aBRwxCXSlJXGz83flFx5L7AWrOQnmuN3kVsRdt+GZPPjiVQ==", + "requires": { + "axios": "*" + } + }, "@types/base16": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@types/base16/-/base16-1.0.2.tgz", @@ -328,9 +367,9 @@ "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==" }, "aws-sdk": { - "version": "2.1403.0", - "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1403.0.tgz", - "integrity": "sha512-qRhiE2iqfXhTEsGJ0unNY7mz5Y7YA4ljEh/1lU/HaUI8hPCQtmZjU3P2/w2YcHFaSzJsu9J9svzjkALfHpetoA==", + "version": "2.1405.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1405.0.tgz", + "integrity": "sha512-NVVZpRmr+KoBq5xFbB+ivCMDPGx8g1XOZVcswXotZZZIQVdDdHixrkZDqOrZ/p1hJ0eylGc7VQ8mkR7DVryXlQ==", "requires": { "buffer": "4.9.2", "events": "1.1.1", @@ -342,13 +381,6 @@ "util": "^0.12.4", "uuid": "8.0.0", "xml2js": "0.5.0" - }, - "dependencies": { - "uuid": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.0.0.tgz", - "integrity": "sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw==" - } } }, "axios": { @@ -362,22 +394,17 @@ } }, "babel-plugin-styled-components": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.1.3.tgz", - "integrity": "sha512-jBioLwBVHpOMU4NsueH/ADcHrjS0Y/WTpt2eGVmmuSFNEv2DF3XhcMncuZlbbjxQ4vzxg+yEr6E6TNjrIQbsJQ==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.1.4.tgz", + "integrity": "sha512-Xgp9g+A/cG47sUyRwwYxGM4bR/jDRg5N6it/8+HxCnbT5XNKSKDT9xm4oag/osgqjC2It/vH0yXsomOG6k558g==", "requires": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-module-imports": "^7.21.4", - "babel-plugin-syntax-jsx": "^6.18.0", + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-module-imports": "^7.22.5", + "@babel/plugin-syntax-jsx": "^7.22.5", "lodash": "^4.17.21", "picomatch": "^2.3.1" } }, - "babel-plugin-syntax-jsx": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", - "integrity": "sha512-qrPaCSo9c8RHNRHIotaufGbuOBN8rtdC4QrrFFc43vyWCCz7Kl7GL1PGaXtMGQZUXrkCjNEgxDfmAuAabr/rlw==" - }, "base16": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/base16/-/base16-1.0.0.tgz", @@ -694,9 +721,9 @@ } }, "html-entities": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.6.tgz", - "integrity": "sha512-9o0+dcpIw2/HxkNuYKxSJUF/MMRZQECK4GnF+oQOmJ83yCVHTWgCH5aOXxK5bozNRmM8wtgryjHD3uloPBDEGw==" + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.4.0.tgz", + "integrity": "sha512-igBTJcNNNhvZFRtm8uA6xMY6xYleeDwn3PeBCkDz7tHttv4F2hsDI2aPgNERWzvRcNYHNT3ymRaQzllmXj4YsQ==" }, "ieee754": { "version": "1.1.13", @@ -1262,9 +1289,9 @@ } }, "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.0.0.tgz", + "integrity": "sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw==" }, "value-equal": { "version": "1.0.1", diff --git a/packages/botonic-plugin-flow-builder/package.json b/packages/botonic-plugin-flow-builder/package.json index eec5ffc436..ae4efb8588 100644 --- a/packages/botonic-plugin-flow-builder/package.json +++ b/packages/botonic-plugin-flow-builder/package.json @@ -1,6 +1,6 @@ { "name": "@botonic/plugin-flow-builder", - "version": "0.22.3", + "version": "0.22.4-alpha.3", "main": "./lib/cjs/index.js", "module": "./lib/esm/index.js", "description": "Use Flow Builder to show your contents", @@ -48,6 +48,7 @@ "dependencies": { "@babel/runtime": "^7.21.0", "@botonic/react": "^0.22.2", + "@botonic/plugin-hubtype-analytics": "0.22.0-alpha.5", "axios": "^1.3.6" } } diff --git a/packages/botonic-plugin-flow-builder/src/action.tsx b/packages/botonic-plugin-flow-builder/src/action.tsx deleted file mode 100644 index 50473b6e33..0000000000 --- a/packages/botonic-plugin-flow-builder/src/action.tsx +++ /dev/null @@ -1,103 +0,0 @@ -import { ActionRequest, Multichannel, RequestContext } from '@botonic/react' -import React from 'react' - -import { FlowBuilderApi } from './api' -import { FlowContent, FlowHandoff } from './content-fields' -import { HtNodeWithContent } from './content-fields/hubtype-fields' -import { getFlowBuilderPlugin } from './helpers' - -type FlowBuilderActionProps = { - contents: FlowContent[] -} - -export class FlowBuilderAction extends React.Component { - static contextType = RequestContext - - static async botonicInit(request: ActionRequest): Promise { - const flowBuilderPlugin = getFlowBuilderPlugin(request.plugins) - const locale = flowBuilderPlugin.getLocale(request.session) - - const targetNode = getTargetNode(flowBuilderPlugin.cmsApi, locale, request) - - const contents = await flowBuilderPlugin.getContentsByNode( - targetNode, - locale - ) - - if (flowBuilderPlugin.trackEvent) { - // TODO: track all targets nodes? - await flowBuilderPlugin.trackEvent(request, contents[0].code) - } - - const renderContents = contents.filter(async content => { - if (content instanceof FlowHandoff) { - await content.doHandoff(request) - return false - } - return true - }) - - return { contents: renderContents } - } - - render(): JSX.Element | JSX.Element[] { - const { contents } = this.props - return contents.map(content => content.toBotonic(content.id)) - } -} - -export class FlowBuilderMultichannelAction extends FlowBuilderAction { - render(): JSX.Element | JSX.Element[] { - const { contents } = this.props - return ( - - {contents.map(content => content.toBotonic(content.id))} - - ) - } -} - -function getTargetNode( - cmsApi: FlowBuilderApi, - locale: string, - request: ActionRequest -) { - const contentId = request.input.payload - let targetNode: HtNodeWithContent | undefined - if (!contentId) { - targetNode = getNodeByUserInput(cmsApi, locale, request) - } else { - targetNode = cmsApi.getNodeById(contentId) as HtNodeWithContent - } - if (targetNode) { - return targetNode - } - return getFallbackNode(cmsApi, request) -} - -function getNodeByUserInput( - cmsApi: FlowBuilderApi, - locale: string, - request: ActionRequest -): HtNodeWithContent | undefined { - if (request.session.is_first_interaction) { - return cmsApi.getStartNode() - } - - if (request.input.data) { - const intentNode = cmsApi.getNodeByIntent(request.input, locale) - if (intentNode) return intentNode - const keywordNode = cmsApi.getNodeByKeyword(request.input.data, locale) - return keywordNode - } - - return undefined -} - -function getFallbackNode(cmsApi: FlowBuilderApi, request: ActionRequest) { - const isFirstFallbackOption = - request.session.user.extra_data.isFirstFallbackOption || true - const fallbackNode = cmsApi.getFallbackNode(isFirstFallbackOption) - request.session.user.extra_data.isFirstFallbackOption = !isFirstFallbackOption - return fallbackNode -} diff --git a/packages/botonic-plugin-flow-builder/src/action/index.tsx b/packages/botonic-plugin-flow-builder/src/action/index.tsx new file mode 100644 index 0000000000..bef410efdf --- /dev/null +++ b/packages/botonic-plugin-flow-builder/src/action/index.tsx @@ -0,0 +1,103 @@ +import { + EventBotFaq, + EventFallback, + EventName, +} from '@botonic/plugin-hubtype-analytics/lib/cjs/types' +import { ActionRequest, Multichannel, RequestContext } from '@botonic/react' +import React from 'react' + +import { FlowBuilderApi } from '../api' +import { FlowContent, FlowHandoff } from '../content-fields' +import { HtNodeWithContent } from '../content-fields/hubtype-fields' +import { getFlowBuilderPlugin } from '../helpers' +import { trackEvent } from './tracking' +import { getNodeByUserInput } from './user-input' + +type FlowBuilderActionProps = { + contents: FlowContent[] +} + +export class FlowBuilderAction extends React.Component { + static contextType = RequestContext + + static async botonicInit( + request: ActionRequest + ): Promise { + const flowBuilderPlugin = getFlowBuilderPlugin(request.plugins) + const locale = flowBuilderPlugin.getLocale(request.session) + + const targetNode = await getTargetNode( + flowBuilderPlugin.cmsApi, + locale, + request + ) + + const contents = await flowBuilderPlugin.getContentsByNode( + targetNode, + locale + ) + + const handoffContent = contents.find( + content => content instanceof FlowHandoff + ) as FlowHandoff + if (handoffContent) await handoffContent.doHandoff(request) + + const renderContents = contents.filter(content => + content instanceof FlowHandoff ? false : true + ) + + return { contents: renderContents } + } + + render(): JSX.Element | JSX.Element[] { + const { contents } = this.props + return contents.map(content => content.toBotonic(content.id)) + } +} + +export class FlowBuilderMultichannelAction extends FlowBuilderAction { + render(): JSX.Element | JSX.Element[] { + const { contents } = this.props + return ( + + {contents.map(content => content.toBotonic(content.id))} + + ) + } +} + +async function getTargetNode( + cmsApi: FlowBuilderApi, + locale: string, + request: ActionRequest +) { + const contentId = request.input.payload + const targetNode = !contentId + ? await getNodeByUserInput(cmsApi, locale, request) + : (cmsApi.getNodeById(contentId) as HtNodeWithContent) + + if (targetNode) { + const event: EventBotFaq = { + event_type: EventName.botFaq, + event_data: { + faq_name: targetNode.code, + }, + } + await trackEvent(request, event) + return targetNode + } + return await getFallbackNode(cmsApi, request) +} + +async function getFallbackNode(cmsApi: FlowBuilderApi, request: ActionRequest) { + const isFirstFallbackOption = + request.session.user.extra_data.isFirstFallbackOption ?? true + const fallbackNode = cmsApi.getFallbackNode(isFirstFallbackOption) + request.session.user.extra_data.isFirstFallbackOption = !isFirstFallbackOption + + const event: EventFallback = { + event_type: EventName.fallback, + } + await trackEvent(request, event) + return fallbackNode +} diff --git a/packages/botonic-plugin-flow-builder/src/action/intent.ts b/packages/botonic-plugin-flow-builder/src/action/intent.ts new file mode 100644 index 0000000000..31c4fe3452 --- /dev/null +++ b/packages/botonic-plugin-flow-builder/src/action/intent.ts @@ -0,0 +1,48 @@ +import { + EventBotAiModel, + EventName, +} from '@botonic/plugin-hubtype-analytics/lib/cjs/types' +import { ActionRequest } from '@botonic/react' + +import { FlowBuilderApi } from '../api' +import { + HtIntentNode, + HtNodeWithContent, +} from '../content-fields/hubtype-fields' +import { trackEvent } from './tracking' + +export async function getNodeByIntent( + cmsApi: FlowBuilderApi, + locale: string, + request: ActionRequest +) { + const intentNode = cmsApi.getIntentNode(request.input, locale) + const eventBotAiModel: EventBotAiModel = { + event_type: EventName.botAiModel, + event_data: { + intent: request.input.intent as string, + confidence: request.input.confidence as number, + confidence_successful: true, + }, + } + if (isIntentValid(intentNode, request, cmsApi) && intentNode?.target?.id) { + await trackEvent(request, eventBotAiModel) + return cmsApi.getNodeById(intentNode.target.id) + } else { + eventBotAiModel.event_data.confidence_successful = false + await trackEvent(request, eventBotAiModel) + return undefined + } +} + +function isIntentValid( + intentNode: HtIntentNode | undefined, + request: ActionRequest, + cmsApi: FlowBuilderApi +) { + return ( + intentNode && + request.input.confidence && + cmsApi.hasMetConfidenceThreshold(intentNode, request.input.confidence) + ) +} diff --git a/packages/botonic-plugin-flow-builder/src/action/keyword.ts b/packages/botonic-plugin-flow-builder/src/action/keyword.ts new file mode 100644 index 0000000000..967e42b397 --- /dev/null +++ b/packages/botonic-plugin-flow-builder/src/action/keyword.ts @@ -0,0 +1,29 @@ +import { + EventBotKeywordModel, + EventName, +} from '@botonic/plugin-hubtype-analytics/lib/cjs/types' +import { ActionRequest } from '@botonic/react' + +import { FlowBuilderApi } from '../api' +import { HtNodeWithContent } from '../content-fields/hubtype-fields' +import { trackEvent } from './tracking' + +export async function getNodeByKeyword( + cmsApi: FlowBuilderApi, + locale: string, + request: ActionRequest, + userInput: string +): Promise { + const keywordNode = cmsApi.getNodeByKeyword(userInput, locale) + if (!keywordNode) { + return undefined + } + const eventBotKeywordModel: EventBotKeywordModel = { + event_type: EventName.botKeywordsModel, + event_data: { + confidence_successful: true, + }, + } + await trackEvent(request, eventBotKeywordModel) + return keywordNode +} diff --git a/packages/botonic-plugin-flow-builder/src/action/tracking.ts b/packages/botonic-plugin-flow-builder/src/action/tracking.ts new file mode 100644 index 0000000000..1f049616c2 --- /dev/null +++ b/packages/botonic-plugin-flow-builder/src/action/tracking.ts @@ -0,0 +1,12 @@ +import { HtEventProps } from '@botonic/plugin-hubtype-analytics/lib/cjs/types' +import { ActionRequest } from '@botonic/react' + +import { getFlowBuilderPlugin } from '../helpers' + +export async function trackEvent(request: ActionRequest, event: HtEventProps) { + const flowBuilderPlugin = getFlowBuilderPlugin(request.plugins) + if (flowBuilderPlugin.trackEvent) { + await flowBuilderPlugin.trackEvent(request, event.event_type, event) + } + return +} diff --git a/packages/botonic-plugin-flow-builder/src/action/user-input.ts b/packages/botonic-plugin-flow-builder/src/action/user-input.ts new file mode 100644 index 0000000000..c2ae62a4e9 --- /dev/null +++ b/packages/botonic-plugin-flow-builder/src/action/user-input.ts @@ -0,0 +1,40 @@ +import { + EventBotStart, + EventName, +} from '@botonic/plugin-hubtype-analytics/lib/cjs/types' +import { ActionRequest } from '@botonic/react' + +import { FlowBuilderApi } from '../api' +import { HtNodeWithContent } from '../content-fields/hubtype-fields' +import { getNodeByIntent } from './intent' +import { getNodeByKeyword } from './keyword' +import { trackEvent } from './tracking' + +export async function getNodeByUserInput( + cmsApi: FlowBuilderApi, + locale: string, + request: ActionRequest +): Promise { + if (request.session.is_first_interaction) { + const startNode = cmsApi.getStartNode() + const event: EventBotStart = { + event_type: EventName.botStart, + } + await trackEvent(request, event) + return startNode + } + + if (request.input.data) { + const nodeByIntent = await getNodeByIntent(cmsApi, locale, request) + if (nodeByIntent) return nodeByIntent + + const keywordNode = await getNodeByKeyword( + cmsApi, + locale, + request, + request.input.data + ) + if (keywordNode) return keywordNode + } + return undefined +} diff --git a/packages/botonic-plugin-flow-builder/src/api.ts b/packages/botonic-plugin-flow-builder/src/api.ts index 5767641e37..68ae59c199 100644 --- a/packages/botonic-plugin-flow-builder/src/api.ts +++ b/packages/botonic-plugin-flow-builder/src/api.ts @@ -78,24 +78,16 @@ export class FlowBuilderApi { : this.getNodeById(fallbackSecondMessage.id) } - getNodeByIntent(input: Input, locale: string): HtNodeWithContent | undefined { + getIntentNode(input: Input, locale: string): HtIntentNode | undefined { try { - const intents = this.flow.nodes.filter( + const intentsNodes = this.flow.nodes.filter( node => node.type === HtNodeWithContentType.INTENT ) as HtIntentNode[] const inputIntent = input.intent - const inputConfidence = input.confidence if (inputIntent) { - const matchedIntentNode = intents.find( + return intentsNodes.find( node => - inputIntent && - this.hasIntent(node, inputIntent, locale) && - inputConfidence && - this.hasMetConfidenceThreshold(node, inputConfidence) - ) - return ( - matchedIntentNode?.target && - this.getNodeById(matchedIntentNode?.target.id) + inputIntent && this.nodeContainsIntent(node, inputIntent, locale) ) } } catch (error) { @@ -105,7 +97,7 @@ export class FlowBuilderApi { return undefined } - private hasIntent( + private nodeContainsIntent( node: HtIntentNode, intent: string, locale: string @@ -115,7 +107,7 @@ export class FlowBuilderApi { ) } - private hasMetConfidenceThreshold( + hasMetConfidenceThreshold( node: HtIntentNode, predictedConfidence: number ): boolean { diff --git a/packages/botonic-plugin-flow-builder/src/content-fields/flow-handoff.tsx b/packages/botonic-plugin-flow-builder/src/content-fields/flow-handoff.tsx index 3bdd0b8a26..4afd7cb3b0 100644 --- a/packages/botonic-plugin-flow-builder/src/content-fields/flow-handoff.tsx +++ b/packages/botonic-plugin-flow-builder/src/content-fields/flow-handoff.tsx @@ -1,8 +1,14 @@ import { HandOffBuilder } from '@botonic/core' +import { + EventHandoffSuccess, + EventName, +} from '@botonic/plugin-hubtype-analytics/lib/cjs/types' import { ActionRequest } from '@botonic/react' import React from 'react' +import { trackEvent } from '../action/tracking' import { FlowBuilderApi } from '../api' +import { getQueueAvailability } from '../functions/conditional-queue-status' import { ContentFieldsBase } from './content-fields-base' import { HtCarouselNode, @@ -69,7 +75,7 @@ export class FlowHandoff extends ContentFieldsBase { return (actionPayload as HtPayloadNode).content.payload } - async doHandoff(request: ActionRequest) { + async doHandoff(request: ActionRequest): Promise { // @ts-ignore const handOffBuilder = new HandOffBuilder(request.session) handOffBuilder.withAutoAssignOnWaiting(this.handoffAutoAssign) @@ -77,6 +83,18 @@ export class FlowHandoff extends ContentFieldsBase { handOffBuilder.withOnFinishPayload(this.onFinishPayload) } if (this.queue) { + const availabilityData = await getQueueAvailability(this.queue.id) + const event: EventHandoffSuccess = { + event_type: EventName.handoffSuccess, + event_data: { + queue_open: availabilityData.open, + available_agents: availabilityData.available_agents > 0, + threshold_reached: + availabilityData.availability_threshold_waiting_cases > 0, + }, + } + await trackEvent(request, event) + handOffBuilder.withQueue(this.queue.id) await handOffBuilder.handOff() } diff --git a/packages/botonic-plugin-flow-builder/src/functions/conditional-provider.ts b/packages/botonic-plugin-flow-builder/src/functions/conditional-provider.ts index 0479facb60..3ad4330d1f 100644 --- a/packages/botonic-plugin-flow-builder/src/functions/conditional-provider.ts +++ b/packages/botonic-plugin-flow-builder/src/functions/conditional-provider.ts @@ -1,4 +1,14 @@ -export function conditionalProvider({ request, results }): string { +import { ActionRequest } from '@botonic/react' + +interface ConditionalProviderArgs { + request: ActionRequest + results: string[] +} + +export function conditionalProvider({ + request, + results, +}: ConditionalProviderArgs): string { const provider = request.session.user.provider if (results.includes(provider)) return provider return 'default' diff --git a/packages/botonic-plugin-flow-builder/src/functions/conditional-queue-status.ts b/packages/botonic-plugin-flow-builder/src/functions/conditional-queue-status.ts index 28fb39cce8..1a03c8c806 100644 --- a/packages/botonic-plugin-flow-builder/src/functions/conditional-queue-status.ts +++ b/packages/botonic-plugin-flow-builder/src/functions/conditional-queue-status.ts @@ -18,12 +18,27 @@ type ConditionalQueueStatusArgs = { } export async function conditionalQueueStatus({ - request, queue_id, - queue_name, }: ConditionalQueueStatusArgs): Promise { + const data = await getQueueAvailability(queue_id) + const isAvailable = data.available + return isAvailable ? 'open' : 'closed' +} + +interface AvailabilityData { + available: boolean + waiting_cases: number + availability_threshold_waiting_cases: number + open: boolean + name: string + available_agents: number +} + +export async function getQueueAvailability( + queueId: string +): Promise { const response = await axios.get( - `${_HUBTYPE_API_URL_}/v1/queues/${queue_id}/availability/`, + `${_HUBTYPE_API_URL_}/v1/queues/${queueId}/availability/`, // TODO: Make it configurable in the future { params: { @@ -33,17 +48,5 @@ export async function conditionalQueueStatus({ }, } ) - const isAvailable = response.data.available - - const flowBuilderPlugin = getFlowBuilderPlugin(request.plugins) - if (flowBuilderPlugin.trackEvent) { - const eventName = `QUEUE_${isAvailable ? 'OPEN' : 'CLOSED'}` - const args = { - queue_id, - queue_name, - } - await flowBuilderPlugin.trackEvent(request, eventName, args) - } - - return isAvailable ? 'open' : 'closed' + return response.data } diff --git a/packages/botonic-plugin-flow-builder/src/functions/index.ts b/packages/botonic-plugin-flow-builder/src/functions/index.ts index 890f79c728..b34b0824e2 100644 --- a/packages/botonic-plugin-flow-builder/src/functions/index.ts +++ b/packages/botonic-plugin-flow-builder/src/functions/index.ts @@ -3,7 +3,6 @@ import { conditionalQueueStatus } from './conditional-queue-status' export const DEFAULT_FUNCTIONS = { // TODO: Rename api action name - // 'conditional-queue-status': conditionalQueueStatus, 'check-queue-status': conditionalQueueStatus, 'get-channel-type': conditionalProvider, } diff --git a/packages/botonic-plugin-flow-builder/src/helpers.ts b/packages/botonic-plugin-flow-builder/src/helpers.ts index b5e06e7f39..fe96f1d667 100644 --- a/packages/botonic-plugin-flow-builder/src/helpers.ts +++ b/packages/botonic-plugin-flow-builder/src/helpers.ts @@ -1,10 +1,5 @@ import { Plugin } from '@botonic/core' -import { - HtHandoffNode, - HtNodeWithContent, - HtNodeWithContentType, -} from './content-fields/hubtype-fields' import BotonicPluginFlowBuilder from './index' const FLOW_BUILDER_PLUGIN_NAME = 'BotonicPluginFlowBuilder' diff --git a/packages/botonic-plugin-flow-builder/src/index.ts b/packages/botonic-plugin-flow-builder/src/index.ts index f7edc2c99a..5c41574cb1 100644 --- a/packages/botonic-plugin-flow-builder/src/index.ts +++ b/packages/botonic-plugin-flow-builder/src/index.ts @@ -131,7 +131,7 @@ export default class BotonicPluginFlowBuilder implements Plugin { const args = Object.assign( { request: this.currentRequest, - results: [functionNode.content.result_mapping.map(r => r.result)], + results: functionNode.content.result_mapping.map(r => r.result), }, ...nameValues ) diff --git a/packages/botonic-plugin-flow-builder/src/types.ts b/packages/botonic-plugin-flow-builder/src/types.ts index 6cb87a7bae..38b3030299 100644 --- a/packages/botonic-plugin-flow-builder/src/types.ts +++ b/packages/botonic-plugin-flow-builder/src/types.ts @@ -1,7 +1,6 @@ import { Session } from '@botonic/core' import { ActionRequest } from '@botonic/react' -import { FlowBuilderApi } from './api' import { HtFlowBuilderData } from './content-fields/hubtype-fields' export interface BotonicPluginFlowBuilderOptions {