@botonic/plugin-flow-builder 0.58.0-alpha.1 → 0.58.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/cjs/action/contents-result.d.ts +5 -0
- package/lib/cjs/action/contents-result.js +3 -0
- package/lib/cjs/action/contents-result.js.map +1 -0
- package/lib/cjs/action/first-interaction.js +1 -1
- package/lib/cjs/action/first-interaction.js.map +1 -1
- package/lib/cjs/action/get-contents.d.ts +3 -2
- package/lib/cjs/action/get-contents.js +13 -6
- package/lib/cjs/action/get-contents.js.map +1 -1
- package/lib/cjs/action/index.d.ts +2 -0
- package/lib/cjs/action/index.js +16 -6
- package/lib/cjs/action/index.js.map +1 -1
- package/lib/cjs/action/payload.d.ts +2 -2
- package/lib/cjs/action/payload.js +12 -28
- package/lib/cjs/action/payload.js.map +1 -1
- package/lib/cjs/action/push-flow-resolver.d.ts +19 -0
- package/lib/cjs/action/push-flow-resolver.js +52 -0
- package/lib/cjs/action/push-flow-resolver.js.map +1 -0
- package/lib/cjs/api.d.ts +1 -1
- package/lib/cjs/api.js +7 -2
- package/lib/cjs/api.js.map +1 -1
- package/lib/cjs/content-fields/flow-do-nothing.js +1 -1
- package/lib/cjs/content-fields/flow-do-nothing.js.map +1 -1
- package/lib/cjs/content-fields/flow-update-variables.js +5 -9
- package/lib/cjs/content-fields/flow-update-variables.js.map +1 -1
- package/lib/esm/action/contents-result.d.ts +5 -0
- package/lib/esm/action/contents-result.js +3 -0
- package/lib/esm/action/contents-result.js.map +1 -0
- package/lib/esm/action/first-interaction.js +1 -1
- package/lib/esm/action/first-interaction.js.map +1 -1
- package/lib/esm/action/get-contents.d.ts +3 -2
- package/lib/esm/action/get-contents.js +13 -6
- package/lib/esm/action/get-contents.js.map +1 -1
- package/lib/esm/action/index.d.ts +2 -0
- package/lib/esm/action/index.js +16 -6
- package/lib/esm/action/index.js.map +1 -1
- package/lib/esm/action/payload.d.ts +2 -2
- package/lib/esm/action/payload.js +12 -28
- package/lib/esm/action/payload.js.map +1 -1
- package/lib/esm/action/push-flow-resolver.d.ts +19 -0
- package/lib/esm/action/push-flow-resolver.js +52 -0
- package/lib/esm/action/push-flow-resolver.js.map +1 -0
- package/lib/esm/api.d.ts +1 -1
- package/lib/esm/api.js +7 -2
- package/lib/esm/api.js.map +1 -1
- package/lib/esm/content-fields/flow-do-nothing.js +1 -1
- package/lib/esm/content-fields/flow-do-nothing.js.map +1 -1
- package/lib/esm/content-fields/flow-update-variables.js +5 -9
- package/lib/esm/content-fields/flow-update-variables.js.map +1 -1
- package/package.json +2 -2
- package/src/action/contents-result.ts +6 -0
- package/src/action/first-interaction.ts +7 -6
- package/src/action/get-contents.ts +17 -8
- package/src/action/index.tsx +40 -6
- package/src/action/payload.ts +16 -46
- package/src/action/push-flow-resolver.ts +91 -0
- package/src/api.ts +13 -3
- package/src/content-fields/flow-do-nothing.tsx +1 -1
- package/src/content-fields/flow-update-variables.tsx +10 -8
package/src/action/index.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type BotContext, isDev, isWebchat } from '@botonic/core'
|
|
2
2
|
import {
|
|
3
|
+
CampaignMessageAttributesProvider,
|
|
3
4
|
Multichannel,
|
|
4
5
|
RequestContext,
|
|
5
6
|
WebchatSettings,
|
|
@@ -21,6 +22,7 @@ import { getContents } from './get-contents'
|
|
|
21
22
|
|
|
22
23
|
export type FlowBuilderActionProps = {
|
|
23
24
|
contents: FlowContent[]
|
|
25
|
+
campaignFirstMessageId?: string
|
|
24
26
|
webchatSettingsParams?: WebchatSettingsProps
|
|
25
27
|
}
|
|
26
28
|
|
|
@@ -46,14 +48,20 @@ export class FlowBuilderAction extends React.Component<FlowBuilderActionProps> {
|
|
|
46
48
|
botContext: BotContext,
|
|
47
49
|
contentID?: string
|
|
48
50
|
): Promise<FlowBuilderActionProps> {
|
|
49
|
-
const contents = await getContents(
|
|
51
|
+
const { contents, campaignFirstMessageId } = await getContents(
|
|
52
|
+
botContext,
|
|
53
|
+
contentID
|
|
54
|
+
)
|
|
50
55
|
|
|
51
56
|
const contentsToRender = await FlowBuilderAction.prepareContentsToRender(
|
|
52
57
|
botContext,
|
|
53
58
|
contents
|
|
54
59
|
)
|
|
55
60
|
|
|
56
|
-
return {
|
|
61
|
+
return {
|
|
62
|
+
contents: contentsToRender,
|
|
63
|
+
campaignFirstMessageId,
|
|
64
|
+
}
|
|
57
65
|
}
|
|
58
66
|
|
|
59
67
|
static async prepareContentsToRender(
|
|
@@ -137,7 +145,7 @@ export class FlowBuilderAction extends React.Component<FlowBuilderActionProps> {
|
|
|
137
145
|
}
|
|
138
146
|
|
|
139
147
|
render(): JSX.Element | JSX.Element[] {
|
|
140
|
-
const { contents } = this.props
|
|
148
|
+
const { contents, campaignFirstMessageId } = this.props
|
|
141
149
|
const botContext = this.context as BotContext
|
|
142
150
|
const { shouldSendWebchatSettings, webchatSettingsParams } =
|
|
143
151
|
this.getWebchatSettingsParams(botContext)
|
|
@@ -147,15 +155,37 @@ export class FlowBuilderAction extends React.Component<FlowBuilderActionProps> {
|
|
|
147
155
|
{shouldSendWebchatSettings && (
|
|
148
156
|
<WebchatSettings {...webchatSettingsParams} />
|
|
149
157
|
)}
|
|
150
|
-
{
|
|
158
|
+
{FlowBuilderAction.renderContents(
|
|
159
|
+
contents,
|
|
160
|
+
botContext,
|
|
161
|
+
campaignFirstMessageId
|
|
162
|
+
)}
|
|
151
163
|
</>
|
|
152
164
|
)
|
|
153
165
|
}
|
|
166
|
+
|
|
167
|
+
protected static renderContents(
|
|
168
|
+
contents: FlowContent[],
|
|
169
|
+
botContext: BotContext,
|
|
170
|
+
campaignFirstMessageId?: string
|
|
171
|
+
): JSX.Element | JSX.Element[] {
|
|
172
|
+
const rendered = contents.map(content => content.toBotonic(botContext))
|
|
173
|
+
|
|
174
|
+
if (!campaignFirstMessageId) {
|
|
175
|
+
return rendered
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return (
|
|
179
|
+
<CampaignMessageAttributesProvider campaignId={campaignFirstMessageId}>
|
|
180
|
+
{rendered}
|
|
181
|
+
</CampaignMessageAttributesProvider>
|
|
182
|
+
)
|
|
183
|
+
}
|
|
154
184
|
}
|
|
155
185
|
|
|
156
186
|
export class FlowBuilderMultichannelAction extends FlowBuilderAction {
|
|
157
187
|
render(): JSX.Element | JSX.Element[] {
|
|
158
|
-
const { contents } = this.props
|
|
188
|
+
const { contents, campaignFirstMessageId } = this.props
|
|
159
189
|
const botContext = this.context as BotContext
|
|
160
190
|
const { shouldSendWebchatSettings, webchatSettingsParams } =
|
|
161
191
|
this.getWebchatSettingsParams(botContext)
|
|
@@ -165,7 +195,11 @@ export class FlowBuilderMultichannelAction extends FlowBuilderAction {
|
|
|
165
195
|
{shouldSendWebchatSettings && (
|
|
166
196
|
<WebchatSettings {...webchatSettingsParams} />
|
|
167
197
|
)}
|
|
168
|
-
{
|
|
198
|
+
{FlowBuilderAction.renderContents(
|
|
199
|
+
contents,
|
|
200
|
+
botContext,
|
|
201
|
+
campaignFirstMessageId
|
|
202
|
+
)}
|
|
169
203
|
</Multichannel>
|
|
170
204
|
)
|
|
171
205
|
}
|
package/src/action/payload.ts
CHANGED
|
@@ -1,25 +1,22 @@
|
|
|
1
1
|
import { EventAction, type EventFeedback, storeCaseRating } from '@botonic/core'
|
|
2
2
|
import { v7 as uuid } from 'uuid'
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
AGENT_RATING_PAYLOAD,
|
|
6
|
-
PUSH_FLOW_PAYLOAD,
|
|
7
|
-
SEPARATOR,
|
|
8
|
-
} from '../constants'
|
|
9
|
-
import type { FlowContent } from '../content-fields'
|
|
3
|
+
import { AGENT_RATING_PAYLOAD, PUSH_FLOW_PAYLOAD } from '../constants'
|
|
10
4
|
import type { HtNodeWithContent } from '../content-fields/hubtype-fields'
|
|
11
5
|
import { trackEvent } from '../tracking'
|
|
6
|
+
import type { FlowBuilderContentsResult } from './contents-result'
|
|
12
7
|
import type { FlowBuilderContext } from './context'
|
|
8
|
+
import { PushFlowResolver } from './push-flow-resolver'
|
|
13
9
|
|
|
14
10
|
export async function getContentsByPayload(
|
|
15
11
|
context: FlowBuilderContext
|
|
16
|
-
): Promise<
|
|
12
|
+
): Promise<FlowBuilderContentsResult> {
|
|
17
13
|
const { cmsApi, flowBuilderPlugin, request, contentID } = context
|
|
18
14
|
if (request.input.payload?.startsWith(AGENT_RATING_PAYLOAD)) {
|
|
19
15
|
return await resolveRatingPayload(context)
|
|
20
16
|
}
|
|
21
17
|
if (request.input.payload?.startsWith(PUSH_FLOW_PAYLOAD)) {
|
|
22
|
-
|
|
18
|
+
const pushFlow = new PushFlowResolver(context)
|
|
19
|
+
return await pushFlow.getContents()
|
|
23
20
|
}
|
|
24
21
|
|
|
25
22
|
const id = contentID
|
|
@@ -28,19 +25,21 @@ export async function getContentsByPayload(
|
|
|
28
25
|
const targetNode = id ? cmsApi.getNodeById<HtNodeWithContent>(id) : undefined
|
|
29
26
|
|
|
30
27
|
if (targetNode) {
|
|
31
|
-
return
|
|
28
|
+
return {
|
|
29
|
+
contents: await flowBuilderPlugin.getContentsByNode(targetNode),
|
|
30
|
+
}
|
|
32
31
|
}
|
|
33
32
|
|
|
34
|
-
return []
|
|
33
|
+
return { contents: [] }
|
|
35
34
|
}
|
|
36
35
|
|
|
37
36
|
async function resolveRatingPayload(
|
|
38
37
|
context: FlowBuilderContext
|
|
39
|
-
): Promise<
|
|
38
|
+
): Promise<FlowBuilderContentsResult> {
|
|
40
39
|
const { cmsApi, flowBuilderPlugin, request } = context
|
|
41
40
|
|
|
42
41
|
if (!request.input.payload) {
|
|
43
|
-
return []
|
|
42
|
+
return { contents: [] }
|
|
44
43
|
}
|
|
45
44
|
|
|
46
45
|
const { target, text, value, possibleOptions, possibleValues } =
|
|
@@ -66,39 +65,10 @@ async function resolveRatingPayload(
|
|
|
66
65
|
: undefined
|
|
67
66
|
|
|
68
67
|
if (targetNode) {
|
|
69
|
-
return
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
return []
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
async function resolvePushFlowPayload(
|
|
76
|
-
context: FlowBuilderContext
|
|
77
|
-
): Promise<FlowContent[]> {
|
|
78
|
-
const { cmsApi, flowBuilderPlugin, request } = context
|
|
79
|
-
|
|
80
|
-
if (!request.input.payload) {
|
|
81
|
-
return []
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const parts = request.input.payload.split(SEPARATOR)
|
|
85
|
-
const pushFlowId = parts.length > 1 ? parts[1] : ''
|
|
86
|
-
|
|
87
|
-
if (!pushFlowId) {
|
|
88
|
-
return []
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
try {
|
|
92
|
-
const pushFlowNode =
|
|
93
|
-
cmsApi.getNodeByCampaignId<HtNodeWithContent>(pushFlowId)
|
|
94
|
-
|
|
95
|
-
if (!pushFlowNode) {
|
|
96
|
-
return []
|
|
68
|
+
return {
|
|
69
|
+
contents: await flowBuilderPlugin.getContentsByNode(targetNode),
|
|
97
70
|
}
|
|
98
|
-
|
|
99
|
-
return await flowBuilderPlugin.getContentsByNode(pushFlowNode)
|
|
100
|
-
} catch {
|
|
101
|
-
// Campaign not found - return empty to trigger fallback
|
|
102
|
-
return []
|
|
103
71
|
}
|
|
72
|
+
|
|
73
|
+
return { contents: [] }
|
|
104
74
|
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type BotContext,
|
|
3
|
+
BotonicInputContextType,
|
|
4
|
+
getInputContextOfType,
|
|
5
|
+
} from '@botonic/core'
|
|
6
|
+
import type { FlowBuilderApi } from '../api'
|
|
7
|
+
import type { HtNodeWithContent } from '../content-fields/hubtype-fields'
|
|
8
|
+
import type BotonicPluginFlowBuilder from '../index'
|
|
9
|
+
import type { FlowBuilderContentsResult } from './contents-result'
|
|
10
|
+
import type { FlowBuilderContext } from './context'
|
|
11
|
+
|
|
12
|
+
export enum PushFlowErrorCode {
|
|
13
|
+
NoCampaignContext = 'NoCampaignContext',
|
|
14
|
+
NoCampaignStartNode = 'NoCampaignStartNode',
|
|
15
|
+
NoCampaignFollowUpNode = 'NoCampaignFollowUpNode',
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export class PushFlowError extends Error {
|
|
19
|
+
readonly code: PushFlowErrorCode
|
|
20
|
+
|
|
21
|
+
constructor(code: PushFlowErrorCode, reason: string) {
|
|
22
|
+
super(`push-flow failed: ${reason}`)
|
|
23
|
+
this.code = code
|
|
24
|
+
this.name = 'PushFlowError'
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class PushFlowResolver {
|
|
29
|
+
private botContext: BotContext
|
|
30
|
+
private cmsApi: FlowBuilderApi
|
|
31
|
+
private flowBuilderPlugin: BotonicPluginFlowBuilder
|
|
32
|
+
|
|
33
|
+
constructor(context: FlowBuilderContext) {
|
|
34
|
+
this.botContext = context.request
|
|
35
|
+
this.cmsApi = context.cmsApi
|
|
36
|
+
this.flowBuilderPlugin = context.flowBuilderPlugin
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async getContents(): Promise<FlowBuilderContentsResult> {
|
|
40
|
+
const campaignContext = getInputContextOfType(
|
|
41
|
+
this.botContext.input.context,
|
|
42
|
+
BotonicInputContextType.PushFlow
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
if (!campaignContext) {
|
|
46
|
+
throw new PushFlowError(
|
|
47
|
+
PushFlowErrorCode.NoCampaignContext,
|
|
48
|
+
'No campaign context found in input context'
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const {
|
|
53
|
+
campaign_id: campaignId,
|
|
54
|
+
campaign_flow_id: campaignFlowId,
|
|
55
|
+
campaign_follow_up_id: campaignFollowUpId,
|
|
56
|
+
} = campaignContext
|
|
57
|
+
|
|
58
|
+
const campaignStartNode = this.getCampaignStartNode(campaignFlowId)
|
|
59
|
+
const followUpNode = campaignFollowUpId
|
|
60
|
+
? this.cmsApi.getNodeById<HtNodeWithContent>(campaignFollowUpId)
|
|
61
|
+
: undefined
|
|
62
|
+
|
|
63
|
+
if (campaignFollowUpId && !followUpNode) {
|
|
64
|
+
throw new PushFlowError(
|
|
65
|
+
PushFlowErrorCode.NoCampaignFollowUpNode,
|
|
66
|
+
'No campaign follow up node found'
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
contents: await this.flowBuilderPlugin.getContentsByNode(
|
|
72
|
+
followUpNode ?? campaignStartNode
|
|
73
|
+
),
|
|
74
|
+
campaignFirstMessageId: campaignId,
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private getCampaignStartNode(campaignId: string): HtNodeWithContent {
|
|
79
|
+
const campaignStartNode =
|
|
80
|
+
this.cmsApi.getNodeByCampaignId<HtNodeWithContent>(campaignId)
|
|
81
|
+
|
|
82
|
+
if (!campaignStartNode) {
|
|
83
|
+
throw new PushFlowError(
|
|
84
|
+
PushFlowErrorCode.NoCampaignStartNode,
|
|
85
|
+
'No campaign start node found'
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return campaignStartNode
|
|
90
|
+
}
|
|
91
|
+
}
|
package/src/api.ts
CHANGED
|
@@ -82,12 +82,22 @@ export class FlowBuilderApi {
|
|
|
82
82
|
return this.getNodeById<HtNodeWithContent>(subFlow.start_node_id)
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
getNodeByCampaignId<T extends HtNodeComponent>(id: string): T {
|
|
85
|
+
getNodeByCampaignId<T extends HtNodeComponent>(id: string): T | undefined {
|
|
86
86
|
const campaign = this.flow.campaigns.find(campaign => campaign.id === id)
|
|
87
87
|
if (!campaign) {
|
|
88
|
-
|
|
88
|
+
return undefined
|
|
89
89
|
}
|
|
90
|
-
|
|
90
|
+
|
|
91
|
+
const node = this.flow.nodes.find(
|
|
92
|
+
flowNode => flowNode.id === campaign.start_node_id
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
if (!node) {
|
|
96
|
+
console.error(`Node with id: '${campaign.start_node_id}' not found`)
|
|
97
|
+
return undefined
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return node as T
|
|
91
101
|
}
|
|
92
102
|
|
|
93
103
|
getNodeById<T extends HtNodeComponent>(id: string): T {
|
|
@@ -8,7 +8,7 @@ export class FlowDoNothing extends ContentFieldsBase {
|
|
|
8
8
|
static fromHubtypeCMS(cmsDoNothing: HtDoNothingNode): FlowDoNothing {
|
|
9
9
|
const newDoNothing = new FlowDoNothing(cmsDoNothing.id)
|
|
10
10
|
newDoNothing.code = cmsDoNothing.code
|
|
11
|
-
newDoNothing.followUp =
|
|
11
|
+
newDoNothing.followUp = undefined
|
|
12
12
|
|
|
13
13
|
return newDoNothing
|
|
14
14
|
}
|
|
@@ -41,8 +41,12 @@ export class FlowUpdateVariables extends ContentFieldsBase {
|
|
|
41
41
|
value: string | number | boolean | undefined
|
|
42
42
|
}[] = []
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
const updateVariable = (
|
|
45
|
+
keyPath: string,
|
|
46
|
+
value: string | number | boolean | undefined
|
|
47
|
+
) => {
|
|
45
48
|
const fieldName = this.getContactInfoFieldName(keyPath)
|
|
49
|
+
|
|
46
50
|
if (fieldName) {
|
|
47
51
|
this.updateSessionContactInfoField(botContext, fieldName, value)
|
|
48
52
|
contactInfoUpdates.push({ fieldName, value })
|
|
@@ -51,14 +55,12 @@ export class FlowUpdateVariables extends ContentFieldsBase {
|
|
|
51
55
|
}
|
|
52
56
|
}
|
|
53
57
|
|
|
58
|
+
for (const [keyPath, value] of Object.entries(this.setVariables)) {
|
|
59
|
+
updateVariable(keyPath, value)
|
|
60
|
+
}
|
|
61
|
+
|
|
54
62
|
for (const keyPath of this.unsetVariables) {
|
|
55
|
-
|
|
56
|
-
if (fieldName) {
|
|
57
|
-
this.updateSessionContactInfoField(botContext, fieldName, undefined)
|
|
58
|
-
contactInfoUpdates.push({ fieldName, value: undefined })
|
|
59
|
-
} else {
|
|
60
|
-
this.setSessionValueFromKeyPath(botContext, keyPath, undefined)
|
|
61
|
-
}
|
|
63
|
+
updateVariable(keyPath, undefined)
|
|
62
64
|
}
|
|
63
65
|
|
|
64
66
|
if (contactInfoUpdates.length === 0) {
|