From 14023e8b90bda56c91c170db4c0346da0bb1bf96 Mon Sep 17 00:00:00 2001 From: "Reichenbach, Michael" <755327+Silthus@users.noreply.github.com> Date: Tue, 27 Oct 2026 10:39:24 +0000 Subject: [PATCH 1/4] feat(workflows): give an inserted email step the sandbox sender The workflow editor sets up the sandbox sender when it opens, so a new email step can take it on insertion like the own default sender. Co-Authored-By: Claude Opus 5.5 --- .../frontend/Workflows/firstRunSender.ts | 57 +++++++++++---- .../Workflows/hogflows/hogFlowEditorLogic.tsx | 48 +++++------- .../workflowLogic.firstRunSender.test.ts | 73 +++++++++++++++++++ 3 files changed, 134 insertions(+), 44 deletions(-) diff --git a/products/workflows/frontend/Workflows/firstRunSender.ts b/products/workflows/frontend/Workflows/firstRunSender.ts index 30f028ad56e2..4fbf69f9c477 100644 --- a/products/workflows/frontend/Workflows/firstRunSender.ts +++ b/products/workflows/frontend/Workflows/firstRunSender.ts @@ -75,18 +75,50 @@ async function provisionSandboxSender(): Promise { return sandbox.id } -async function resolveFirstRunSender(allowSandbox: boolean): Promise { +function teamDefaultSenderId(): number | null | undefined { const team = teamLogic.findMounted()?.values.currentTeam - const integrations = await loadAllIntegrations() - const ownSender = resolveDefaultEmailSender( - integrations, - team && 'workflows_config' in team ? team.workflows_config?.default_email_integration_id : null - ) - if (ownSender || !allowSandbox || !sandboxSenderEnabled() || verifiedOwnEmailSenders(integrations).length > 0) { - return ownSender - } + return team && 'workflows_config' in team ? team.workflows_config?.default_email_integration_id : null +} + +function sandboxApplies(integrations: IntegrationType[], sandboxAllowed: boolean): boolean { + return sandboxAllowed && sandboxSenderEnabled() && verifiedOwnEmailSenders(integrations).length === 0 +} + +function chooseSender(integrations: IntegrationType[], sandboxAllowed: boolean): DefaultEmailSender | null { const sandbox = integrations.find(isSandboxEmailSender) - return { integrationId: sandbox?.id ?? (await provisionSandboxSender()), reason: 'sandbox_sender' } + return sandbox && sandboxApplies(integrations, sandboxAllowed) + ? { integrationId: sandbox.id, reason: 'sandbox_sender' } + : resolveDefaultEmailSender(integrations, teamDefaultSenderId()) +} + +async function resolveFirstRunSender(allowSandbox: boolean): Promise { + const unmountIntegrations = integrationsLogic.mount() + try { + const integrations = await loadAllIntegrations() + if (!sandboxApplies(integrations, allowSandbox) || integrations.some(isSandboxEmailSender)) { + return chooseSender(integrations, allowSandbox) + } + return { integrationId: await provisionSandboxSender(), reason: 'sandbox_sender' } + } finally { + unmountIntegrations() + } +} + +export async function prepareSandboxSender(): Promise { + if (sandboxSenderEnabled() && !integrationsLogic.findMounted()?.values.sandboxEmailSender) { + await resolveFirstRunSender(true) + } +} + +export function withInsertedStepSender( + action: HogFlowAction, + workflow: HogFlow, + integrations: IntegrationType[] | null +): { action: HogFlowAction; sender: DefaultEmailSender | null } { + const sender = takesOwnSender(action) + ? chooseSender(integrations ?? [], takesSandboxSender(action, workflow)) + : null + return { action: sender ? withSender(action, sender.integrationId) : action, sender } } export async function withFirstRunSender( @@ -95,10 +127,7 @@ export async function withFirstRunSender( if (!workflow.actions.some(takesOwnSender)) { return { workflow, sender: null } } - const unmountIntegrations = integrationsLogic.mount() - const sender = await resolveFirstRunSender( - workflow.actions.some((action) => takesSandboxSender(action, workflow)) - ).finally(unmountIntegrations) + const sender = await resolveFirstRunSender(workflow.actions.some((action) => takesSandboxSender(action, workflow))) if (!sender) { return { workflow, sender: null } } diff --git a/products/workflows/frontend/Workflows/hogflows/hogFlowEditorLogic.tsx b/products/workflows/frontend/Workflows/hogflows/hogFlowEditorLogic.tsx index c66aa6e1c3bc..275d19396b51 100644 --- a/products/workflows/frontend/Workflows/hogflows/hogFlowEditorLogic.tsx +++ b/products/workflows/frontend/Workflows/hogflows/hogFlowEditorLogic.tsx @@ -41,9 +41,9 @@ import type { TeamType, UserBasicType, } from '../../../../../frontend/src/types' -import { resolveDefaultEmailSender } from '../../Channels/defaultEmailSender' import { optOutCategoriesLogic } from '../../OptOuts/optOutCategoriesLogic' import type { MessageCategory } from '../../OptOuts/optOutCategoriesLogic' +import { prepareSandboxSender, withInsertedStepSender } from '../firstRunSender' import { EXIT_NODE_ID, TRIGGER_NODE_ID, WorkflowLogicProps, workflowLogic } from '../workflowLogic' import { getEdgeId } from './edgeId' import { getFormattedNodes } from './react_flow_utils/autolayout' @@ -2192,6 +2192,9 @@ export interface hogFlowEditorLogicActions { onNodesDelete: (deleted: HogFlowActionNode[]) => { deleted: HogFlowActionNode[] } + prepareFirstRunSender: () => { + value: true + } resetFlowFromHogFlow: (hogFlow: HogFlow) => { hogFlow: HogFlow } @@ -2320,6 +2323,7 @@ export const hogFlowEditorLogic = kea([ onEdgesChange: (edges: EdgeChange[]) => ({ edges }), onNodesChange: (nodes: NodeChange[]) => ({ nodes }), onNodesDelete: (deleted: HogFlowActionNode[]) => ({ deleted }), + prepareFirstRunSender: true, setNodes: (nodes: HogFlowActionNode[]) => ({ nodes }), setDropzoneNodes: (dropzoneNodes: DropzoneNode[]) => ({ dropzoneNodes, @@ -2724,6 +2728,9 @@ export const hogFlowEditorLogic = kea([ actions.setNodesRaw(reconcileById(values.nodes, formattedNodes, (node) => node.id)) }, + prepareFirstRunSender: async () => { + await prepareSandboxSender().catch(() => undefined) + }, onNodesDelete: ({ deleted }) => { if (deleted.some((node) => node.id === values.selectedNodeId)) { actions.setSelectedNodeId(null) @@ -2914,35 +2921,7 @@ export const hogFlowEditorLogic = kea([ } } - if (!isHogFlowActionNode && 'template_id' in config && config.template_id === 'template-email') { - const emailInput = config.inputs?.email - const team = values.currentTeam - const sender = resolveDefaultEmailSender( - values.integrations, - team && 'workflows_config' in team - ? team.workflows_config?.default_email_integration_id - : null - ) - if (sender && emailInput?.value && !emailInput.value.from?.integrationId) { - config = { - ...config, - inputs: { - ...config.inputs, - email: { - ...emailInput, - value: { - ...emailInput.value, - from: { ...emailInput.value.from, integrationId: sender.integrationId }, - }, - }, - }, - } - // pinned: analytics event name - posthog.capture('email sender preselected', { surface: 'workflow', reason: sender.reason }) - } - } - - const newAction = { + const builtAction = { id: isHogFlowActionNode ? (values.nodeToBeAdded as HogFlowActionNode).id : `action_${partialNewAction.type}_${uuid()}`, @@ -2960,6 +2939,14 @@ export const hogFlowEditorLogic = kea([ : {}), } as HogFlowAction + const { action: newAction, sender } = isHogFlowActionNode + ? { action: builtAction, sender: null } + : withInsertedStepSender(builtAction, values.workflow, values.integrations) + if (sender) { + // pinned: analytics event name + posthog.capture('email sender preselected', { surface: 'workflow', reason: sender.reason }) + } + const step = getHogFlowStep(newAction, values.hogFunctionTemplatesById) const branchEdges = isHogFlowActionNode @@ -3287,6 +3274,7 @@ export const hogFlowEditorLogic = kea([ actions.setEditorLayout('advanced') } actions.resetFlowFromHogFlow(values.workflow) + actions.prepareFirstRunSender() const handleKeyDown = (e: KeyboardEvent): void => { if (e.key === 'Escape') { diff --git a/products/workflows/frontend/Workflows/workflowLogic.firstRunSender.test.ts b/products/workflows/frontend/Workflows/workflowLogic.firstRunSender.test.ts index a8845901b144..84f13299ad65 100644 --- a/products/workflows/frontend/Workflows/workflowLogic.firstRunSender.test.ts +++ b/products/workflows/frontend/Workflows/workflowLogic.firstRunSender.test.ts @@ -10,6 +10,7 @@ import { initKeaTests } from '~/test/init' import { IntegrationType } from '~/types' import { draftMessage } from '../MessageAudience/messageDrafts' +import { hogFlowEditorLogic } from './hogflows/hogFlowEditorLogic' import { HogFlowAction } from './hogflows/types' import { NEW_WORKFLOW, workflowLogic } from './workflowLogic' @@ -294,4 +295,76 @@ describe('workflow first-run sender', () => { expect(ensureCalls).toBe(2) logic.unmount() }) + it.each<{ + name: string + step: HogFlowAction + senders: Partial[] + flag: boolean + originProduct?: 'broadcasts' + expectedFrom: Record + expectedEnsureCalls: number + }>([ + { + name: 'an inserted email step gets the sandbox sender set up when the editor opened', + step: emailAction('function_email'), + senders: [], + flag: true, + expectedFrom: { integrationId: SANDBOX.id }, + expectedEnsureCalls: 1, + }, + { + name: 'an inserted email step gets the verified own sender over the sandbox sender', + step: emailAction('function_email'), + senders: [OWN_VERIFIED, SANDBOX], + flag: true, + expectedFrom: { integrationId: OWN_VERIFIED.id }, + expectedEnsureCalls: 0, + }, + { + name: 'an inserted broadcast email step never gets the sandbox sender', + step: emailAction('function_email'), + senders: [SANDBOX], + flag: true, + originProduct: 'broadcasts', + expectedFrom: {}, + expectedEnsureCalls: 0, + }, + { + name: 'an inserted generic email function never gets the sandbox sender', + step: emailAction('function'), + senders: [SANDBOX], + flag: true, + expectedFrom: {}, + expectedEnsureCalls: 0, + }, + { + name: 'flag off sets nothing up and leaves an inserted email step empty', + step: emailAction('function_email'), + senders: [], + flag: false, + expectedFrom: {}, + expectedEnsureCalls: 0, + }, + ])('$name', async ({ step, senders, flag, originProduct, expectedFrom, expectedEnsureCalls }) => { + integrations = senders + setSandboxFlag(flag) + const workflow = workflowLogic({ id: 'new' }) + workflow.mount() + await expectLogic(workflow).toDispatchActions(['loadWorkflowSuccess']) + if (originProduct) { + workflow.actions.setWorkflowInfo({ origin_product: originProduct }) + } + const editor = hogFlowEditorLogic({ id: 'new' }) + editor.mount() + await expectLogic(editor).toFinishAllListeners() + + editor.actions.setNodeToBeAdded({ ...step, id: undefined } as any) + editor.actions.onDrop(undefined, NEW_WORKFLOW.edges[0]) + + const inserted = workflow.values.workflow.actions.find((action) => action.type === step.type) + expect((inserted?.config as any)?.inputs?.email?.value?.from).toEqual(expectedFrom) + expect(ensureCalls).toBe(expectedEnsureCalls) + editor.unmount() + workflow.unmount() + }) }) From d7ad5539dc23bb6662a6263c7c9c294990ca140b Mon Sep 17 00:00:00 2001 From: "Reichenbach, Michael" <755327+Silthus@users.noreply.github.com> Date: Tue, 27 Oct 2026 10:54:53 +0000 Subject: [PATCH 2/4] fix(workflows): set up the sandbox sender when flags load after the editor opens Co-Authored-By: Claude Opus 5.5 --- .../Workflows/hogflows/hogFlowEditorLogic.tsx | 12 +++ .../workflowLogic.firstRunSender.test.ts | 77 ++++++++++++++----- 2 files changed, 69 insertions(+), 20 deletions(-) diff --git a/products/workflows/frontend/Workflows/hogflows/hogFlowEditorLogic.tsx b/products/workflows/frontend/Workflows/hogflows/hogFlowEditorLogic.tsx index 275d19396b51..322f30cfa31f 100644 --- a/products/workflows/frontend/Workflows/hogflows/hogFlowEditorLogic.tsx +++ b/products/workflows/frontend/Workflows/hogflows/hogFlowEditorLogic.tsx @@ -2074,6 +2074,13 @@ export interface hogFlowEditorLogicActions { } payload?: any } // workflowLogic + setFeatureFlags: ( + flags: string[], + variants: Record + ) => { + flags: string[] + variants: Record + } // featureFlagLogic setWorkflowAction: ( actionId: string, action: HogFlowAction @@ -2317,6 +2324,8 @@ export const hogFlowEditorLogic = kea([ ['setWorkflowInfo', 'setWorkflowAction', 'setWorkflowActionEdges', 'loadWorkflowSuccess'], optOutCategoriesLogic(), ['loadCategories'], + featureFlagLogic, + ['setFeatureFlags'], ], })), actions({ @@ -2731,6 +2740,9 @@ export const hogFlowEditorLogic = kea([ prepareFirstRunSender: async () => { await prepareSandboxSender().catch(() => undefined) }, + setFeatureFlags: () => { + actions.prepareFirstRunSender() + }, onNodesDelete: ({ deleted }) => { if (deleted.some((node) => node.id === values.selectedNodeId)) { actions.setSelectedNodeId(null) diff --git a/products/workflows/frontend/Workflows/workflowLogic.firstRunSender.test.ts b/products/workflows/frontend/Workflows/workflowLogic.firstRunSender.test.ts index 84f13299ad65..429981a25699 100644 --- a/products/workflows/frontend/Workflows/workflowLogic.firstRunSender.test.ts +++ b/products/workflows/frontend/Workflows/workflowLogic.firstRunSender.test.ts @@ -301,6 +301,8 @@ describe('workflow first-run sender', () => { senders: Partial[] flag: boolean originProduct?: 'broadcasts' + flagArrivesAfterOpen?: boolean + setupFails?: boolean expectedFrom: Record expectedEnsureCalls: number }>([ @@ -337,6 +339,24 @@ describe('workflow first-run sender', () => { expectedFrom: {}, expectedEnsureCalls: 0, }, + { + name: 'an inserted email step gets the sandbox sender when the flag arrives after the editor opened', + step: emailAction('function_email'), + senders: [], + flag: true, + flagArrivesAfterOpen: true, + expectedFrom: { integrationId: SANDBOX.id }, + expectedEnsureCalls: 1, + }, + { + name: 'an inserted email step stays empty when the sandbox sender cannot be set up', + step: emailAction('function_email'), + senders: [], + flag: true, + setupFails: true, + expectedFrom: {}, + expectedEnsureCalls: 1, + }, { name: 'flag off sets nothing up and leaves an inserted email step empty', step: emailAction('function_email'), @@ -345,26 +365,43 @@ describe('workflow first-run sender', () => { expectedFrom: {}, expectedEnsureCalls: 0, }, - ])('$name', async ({ step, senders, flag, originProduct, expectedFrom, expectedEnsureCalls }) => { - integrations = senders - setSandboxFlag(flag) - const workflow = workflowLogic({ id: 'new' }) - workflow.mount() - await expectLogic(workflow).toDispatchActions(['loadWorkflowSuccess']) - if (originProduct) { - workflow.actions.setWorkflowInfo({ origin_product: originProduct }) - } - const editor = hogFlowEditorLogic({ id: 'new' }) - editor.mount() - await expectLogic(editor).toFinishAllListeners() + ])( + '$name', + async ({ + step, + senders, + flag, + originProduct, + flagArrivesAfterOpen, + setupFails, + expectedFrom, + expectedEnsureCalls, + }) => { + integrations = senders + ensureFails = setupFails ? 1 : 0 + setSandboxFlag(flagArrivesAfterOpen ? false : flag) + const workflow = workflowLogic({ id: 'new' }) + workflow.mount() + await expectLogic(workflow).toDispatchActions(['loadWorkflowSuccess']) + if (originProduct) { + workflow.actions.setWorkflowInfo({ origin_product: originProduct }) + } + const editor = hogFlowEditorLogic({ id: 'new' }) + editor.mount() + await expectLogic(editor).toFinishAllListeners() + if (flagArrivesAfterOpen) { + setSandboxFlag(flag) + await expectLogic(editor).toFinishAllListeners() + } - editor.actions.setNodeToBeAdded({ ...step, id: undefined } as any) - editor.actions.onDrop(undefined, NEW_WORKFLOW.edges[0]) + editor.actions.setNodeToBeAdded({ ...step, id: undefined } as any) + editor.actions.onDrop(undefined, NEW_WORKFLOW.edges[0]) - const inserted = workflow.values.workflow.actions.find((action) => action.type === step.type) - expect((inserted?.config as any)?.inputs?.email?.value?.from).toEqual(expectedFrom) - expect(ensureCalls).toBe(expectedEnsureCalls) - editor.unmount() - workflow.unmount() - }) + const inserted = workflow.values.workflow.actions.find((action) => action.type === step.type) + expect((inserted?.config as any)?.inputs?.email?.value?.from).toEqual(expectedFrom) + expect(ensureCalls).toBe(expectedEnsureCalls) + editor.unmount() + workflow.unmount() + } + ) }) From 1bdbd8f2b3329efc6a449036ef3a3c8ec22c0a71 Mon Sep 17 00:00:00 2001 From: "Reichenbach, Michael" <755327+Silthus@users.noreply.github.com> Date: Wed, 28 Oct 2026 10:41:57 +0000 Subject: [PATCH 3/4] fix(workflows): give a step inserted during sandbox sender setup that sender An email step inserted while the editor still creates the sandbox sender found no sender and kept an empty one. The editor now checks the step again once the setup finishes. Co-Authored-By: Claude Opus 5.5 --- .../Workflows/hogflows/hogFlowEditorLogic.tsx | 28 ++++++++++++- .../workflowLogic.firstRunSender.test.ts | 42 +++++++++++++++++++ 2 files changed, 68 insertions(+), 2 deletions(-) diff --git a/products/workflows/frontend/Workflows/hogflows/hogFlowEditorLogic.tsx b/products/workflows/frontend/Workflows/hogflows/hogFlowEditorLogic.tsx index 322f30cfa31f..fb74097f63ef 100644 --- a/products/workflows/frontend/Workflows/hogflows/hogFlowEditorLogic.tsx +++ b/products/workflows/frontend/Workflows/hogflows/hogFlowEditorLogic.tsx @@ -2202,6 +2202,9 @@ export interface hogFlowEditorLogicActions { prepareFirstRunSender: () => { value: true } + assignInsertedStepSender: (actionId: string) => { + actionId: string + } resetFlowFromHogFlow: (hogFlow: HogFlow) => { hogFlow: HogFlow } @@ -2333,6 +2336,7 @@ export const hogFlowEditorLogic = kea([ onNodesChange: (nodes: NodeChange[]) => ({ nodes }), onNodesDelete: (deleted: HogFlowActionNode[]) => ({ deleted }), prepareFirstRunSender: true, + assignInsertedStepSender: (actionId: string) => ({ actionId }), setNodes: (nodes: HogFlowActionNode[]) => ({ nodes }), setDropzoneNodes: (dropzoneNodes: DropzoneNode[]) => ({ dropzoneNodes, @@ -2602,7 +2606,7 @@ export const hogFlowEditorLogic = kea([ }, ], })), - listeners(({ values, actions }) => { + listeners(({ values, actions, cache }) => { let animationTimeout: ReturnType | null = null return { onEdgesChange: ({ edges }) => { @@ -2738,7 +2742,24 @@ export const hogFlowEditorLogic = kea([ }, prepareFirstRunSender: async () => { - await prepareSandboxSender().catch(() => undefined) + cache.senderPreparation = prepareSandboxSender().catch(() => undefined) + await cache.senderPreparation + }, + assignInsertedStepSender: async ({ actionId }) => { + await cache.senderPreparation + if (cache.disposables.isDisposed) { + return + } + const step = values.workflow.actions.find((action) => action.id === actionId) + if (!step) { + return + } + const { action, sender } = withInsertedStepSender(step, values.workflow, values.integrations) + if (sender) { + actions.setWorkflowAction(actionId, action) + // pinned: analytics event name + posthog.capture('email sender preselected', { surface: 'workflow', reason: sender.reason }) + } }, setFeatureFlags: () => { actions.prepareFirstRunSender() @@ -3058,6 +3079,9 @@ export const hogFlowEditorLogic = kea([ actions.setWorkflowInfo({ actions: newActions, edges: newEdges, variables: updatedVariables }) actions.setNodeToBeAdded(null) actions.setSelectedNodeId(newAction.id) + if (!sender && !isHogFlowActionNode) { + actions.assignInsertedStepSender(newAction.id) + } } // We can clear the dropzones now actions.hideDropzones() diff --git a/products/workflows/frontend/Workflows/workflowLogic.firstRunSender.test.ts b/products/workflows/frontend/Workflows/workflowLogic.firstRunSender.test.ts index 429981a25699..af9f723c4b25 100644 --- a/products/workflows/frontend/Workflows/workflowLogic.firstRunSender.test.ts +++ b/products/workflows/frontend/Workflows/workflowLogic.firstRunSender.test.ts @@ -404,4 +404,46 @@ describe('workflow first-run sender', () => { workflow.unmount() } ) + + it('gives an email step inserted during the sandbox sender setup that sender once the setup finishes', async () => { + let finishSetup = (): void => undefined + const setupFinished = new Promise((resolve) => { + finishSetup = resolve + }) + useMocks({ + post: { + '/api/projects/:team_id/integrations/email_sandbox_sender/': async () => { + await setupFinished + ensureCalls++ + integrations = [...integrations, SANDBOX] + return [200, SANDBOX] + }, + }, + }) + const capture = jest.spyOn(posthog, 'capture').mockImplementation() + const workflow = workflowLogic({ id: 'new' }) + workflow.mount() + await expectLogic(workflow).toDispatchActions(['loadWorkflowSuccess']) + const editor = hogFlowEditorLogic({ id: 'new' }) + editor.mount() + + editor.actions.setNodeToBeAdded({ ...emailAction('function_email'), id: undefined } as any) + editor.actions.onDrop(undefined, NEW_WORKFLOW.edges[0]) + const insertedFrom = (): unknown => + (workflow.values.workflow.actions.find((action) => action.type === 'function_email')?.config as any)?.inputs + ?.email?.value?.from + expect(insertedFrom()).toEqual({}) + + finishSetup() + await expectLogic(editor).toFinishAllListeners() + + expect(insertedFrom()).toEqual({ integrationId: SANDBOX.id }) + expect(ensureCalls).toBe(1) + expect(capture.mock.calls.filter(([event]) => event === 'email sender preselected')).toEqual([ + ['email sender preselected', { surface: 'workflow', reason: 'sandbox_sender' }], + ]) + capture.mockRestore() + editor.unmount() + workflow.unmount() + }) }) From 1805e42fca9bd40998516a66aea3f3bd006a3166 Mon Sep 17 00:00:00 2001 From: "Reichenbach, Michael" <755327+Silthus@users.noreply.github.com> Date: Wed, 28 Oct 2026 10:52:37 +0000 Subject: [PATCH 4/4] fix(workflows): share the running sandbox sender setup in the editor A flag refresh during the setup started a second one, which cancelled the first one's sender list load. A step inserted meanwhile then kept an empty sender. Later calls now wait on the running setup. Co-Authored-By: Claude Opus 5.5 --- .../Workflows/hogflows/hogFlowEditorLogic.tsx | 7 +- .../workflowLogic.firstRunSender.test.ts | 83 ++++++++++--------- 2 files changed, 52 insertions(+), 38 deletions(-) diff --git a/products/workflows/frontend/Workflows/hogflows/hogFlowEditorLogic.tsx b/products/workflows/frontend/Workflows/hogflows/hogFlowEditorLogic.tsx index fb74097f63ef..68e4f0569681 100644 --- a/products/workflows/frontend/Workflows/hogflows/hogFlowEditorLogic.tsx +++ b/products/workflows/frontend/Workflows/hogflows/hogFlowEditorLogic.tsx @@ -2742,7 +2742,12 @@ export const hogFlowEditorLogic = kea([ }, prepareFirstRunSender: async () => { - cache.senderPreparation = prepareSandboxSender().catch(() => undefined) + // A second setup while one runs would cancel its sender list load, so later calls share the running one. + cache.senderPreparation ??= prepareSandboxSender() + .catch(() => undefined) + .finally(() => { + cache.senderPreparation = null + }) await cache.senderPreparation }, assignInsertedStepSender: async ({ actionId }) => { diff --git a/products/workflows/frontend/Workflows/workflowLogic.firstRunSender.test.ts b/products/workflows/frontend/Workflows/workflowLogic.firstRunSender.test.ts index af9f723c4b25..eb8135daf25c 100644 --- a/products/workflows/frontend/Workflows/workflowLogic.firstRunSender.test.ts +++ b/products/workflows/frontend/Workflows/workflowLogic.firstRunSender.test.ts @@ -405,45 +405,54 @@ describe('workflow first-run sender', () => { } ) - it('gives an email step inserted during the sandbox sender setup that sender once the setup finishes', async () => { - let finishSetup = (): void => undefined - const setupFinished = new Promise((resolve) => { - finishSetup = resolve - }) - useMocks({ - post: { - '/api/projects/:team_id/integrations/email_sandbox_sender/': async () => { - await setupFinished - ensureCalls++ - integrations = [...integrations, SANDBOX] - return [200, SANDBOX] + it.each([ + ['', false], + [' and the flags refresh meanwhile', true], + ])( + 'gives an email step inserted during the sandbox sender setup that sender once it finishes%s', + async (_case, flagsRefresh) => { + let finishSetup = (): void => undefined + const setupFinished = new Promise((resolve) => { + finishSetup = resolve + }) + useMocks({ + post: { + '/api/projects/:team_id/integrations/email_sandbox_sender/': async () => { + await setupFinished + ensureCalls++ + integrations = [...integrations, SANDBOX] + return [200, SANDBOX] + }, }, - }, - }) - const capture = jest.spyOn(posthog, 'capture').mockImplementation() - const workflow = workflowLogic({ id: 'new' }) - workflow.mount() - await expectLogic(workflow).toDispatchActions(['loadWorkflowSuccess']) - const editor = hogFlowEditorLogic({ id: 'new' }) - editor.mount() + }) + const capture = jest.spyOn(posthog, 'capture').mockImplementation() + const workflow = workflowLogic({ id: 'new' }) + workflow.mount() + await expectLogic(workflow).toDispatchActions(['loadWorkflowSuccess']) + const editor = hogFlowEditorLogic({ id: 'new' }) + editor.mount() - editor.actions.setNodeToBeAdded({ ...emailAction('function_email'), id: undefined } as any) - editor.actions.onDrop(undefined, NEW_WORKFLOW.edges[0]) - const insertedFrom = (): unknown => - (workflow.values.workflow.actions.find((action) => action.type === 'function_email')?.config as any)?.inputs - ?.email?.value?.from - expect(insertedFrom()).toEqual({}) + editor.actions.setNodeToBeAdded({ ...emailAction('function_email'), id: undefined } as any) + editor.actions.onDrop(undefined, NEW_WORKFLOW.edges[0]) + const insertedFrom = (): unknown => + (workflow.values.workflow.actions.find((action) => action.type === 'function_email')?.config as any) + ?.inputs?.email?.value?.from + expect(insertedFrom()).toEqual({}) + if (flagsRefresh) { + setSandboxFlag(true) + } - finishSetup() - await expectLogic(editor).toFinishAllListeners() + finishSetup() + await expectLogic(editor).toFinishAllListeners() - expect(insertedFrom()).toEqual({ integrationId: SANDBOX.id }) - expect(ensureCalls).toBe(1) - expect(capture.mock.calls.filter(([event]) => event === 'email sender preselected')).toEqual([ - ['email sender preselected', { surface: 'workflow', reason: 'sandbox_sender' }], - ]) - capture.mockRestore() - editor.unmount() - workflow.unmount() - }) + expect(insertedFrom()).toEqual({ integrationId: SANDBOX.id }) + expect(ensureCalls).toBe(1) + expect(capture.mock.calls.filter(([event]) => event === 'email sender preselected')).toEqual([ + ['email sender preselected', { surface: 'workflow', reason: 'sandbox_sender' }], + ]) + capture.mockRestore() + editor.unmount() + workflow.unmount() + } + ) })