Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion frontend/src/lib/integrations/integrationsLogic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -254,12 +254,25 @@ describe('integrationsLogic', () => {

ensureStatus = 200
await expectLogic(logic, () => logic.actions.ensureSandboxEmailSender())
.toDispatchActions(['provisionSandboxEmailSenderSuccess', 'loadIntegrationsSuccess'])
.toDispatchActions(['loadIntegrationsSuccess', 'provisionSandboxEmailSenderSuccess'])
.toMatchValues({ sandboxEmailSender: expect.objectContaining({ id: sandboxSender.id }) })

expect(ensureCalls).toBe(2)
})

it('reports provisioning failure when the refreshed sender list cannot load', async () => {
useMocks({
get: {
'/api/projects/:team_id/integrations/': () => [500, { detail: 'Sender list unavailable' }],
},
})
enableFlag()
await expectLogic(logic, () => logic.actions.ensureSandboxEmailSender()).toFinishAllListeners()

expect(logic.values.sandboxEmailSenderProvisionResult).toBe('failed')
expect(ensureCalls).toBe(1)
})

it('provisions once the flag arrives after a surface asked for the sandbox sender', async () => {
await expectLogic(logic, () => {
logic.actions.ensureSandboxEmailSender()
Expand Down
32 changes: 27 additions & 5 deletions frontend/src/lib/integrations/integrationsLogic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1105,7 +1105,7 @@ export const integrationsLogic = kea<integrationsLogicType>([
},
],
}),
loaders(({ actions, values, cache }) => ({
loaders(({ actions, asyncActions, values, cache }) => ({
integrations: [
null as IntegrationType[] | null,
{
Expand Down Expand Up @@ -1233,14 +1233,36 @@ export const integrationsLogic = kea<integrationsLogicType>([
{
provisionSandboxEmailSender: async (): Promise<SandboxEmailSenderProvisionResult | null> => {
const projectId = values.currentProjectId
const result = await requestSandboxEmailSender(String(projectId))
if (!cache.sandboxProvisionRequest || cache.sandboxProvisionRequest.projectId !== projectId) {
const promise = requestSandboxEmailSender(String(projectId))
.then(async (result): Promise<Awaited<ReturnType<typeof requestSandboxEmailSender>>> => {
if (values.currentProjectId === projectId && result.result === 'provisioned') {
await asyncActions.loadIntegrations()
if (
values.currentProjectId === projectId &&
!values.integrations?.some(isSandboxEmailSender)
) {
return {
result: 'failed',
error: 'Could not load the sandbox sender after setup. Try again.',
}
}
}
return result
})
.finally(() => {
if (cache.sandboxProvisionRequest?.promise === promise) {
cache.sandboxProvisionRequest = undefined
}
})
cache.sandboxProvisionRequest = { projectId, promise }
}
const result: Awaited<ReturnType<typeof requestSandboxEmailSender>> =
await cache.sandboxProvisionRequest.promise
if (values.currentProjectId !== projectId) {
return values.sandboxEmailSenderProvisionResult
}
actions.setSandboxEmailSenderProvisionError(result.error)
if (result.result === 'provisioned') {
actions.loadIntegrations()
}
return result.result
},
},
Expand Down
2 changes: 1 addition & 1 deletion products/workflows/frontend/Channels/defaultEmailSender.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { isSandboxEmailSender } from 'lib/integrations/utils'

import { IntegrationType } from '~/types'

export type DefaultEmailSenderReason = 'project_default' | 'only_verified_sender'
export type DefaultEmailSenderReason = 'project_default' | 'only_verified_sender' | 'sandbox_sender'

export interface DefaultEmailSender {
integrationId: number
Expand Down
28 changes: 10 additions & 18 deletions products/workflows/frontend/Workflows/WorkflowActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export function WorkflowActions(props: WorkflowSceneLogicProps): JSX.Element {
discardDisabledReason,
showDraftActions,
statusToggleDisabledReason,
saveBlockedReason,
} = useValues(workflowLogic)
const { saveWorkflowPartial, submitWorkflow, discardChanges, publishDraft, discardDraft } =
useActions(workflowLogic)
Expand All @@ -44,6 +45,13 @@ export function WorkflowActions(props: WorkflowSceneLogicProps): JSX.Element {
const isCreatedFromTemplate = props.id === 'new' && !!templateId
const isCreatedFromLink = props.id === 'new' && !!parseWorkflowTriggerPrefill(searchParams[TRIGGER_PREFILL_PARAM])
const isManualWorkflow = ['manual', 'batch'].includes(workflow?.trigger?.type || '')
const saveDisabledReason = saveBlockedReason
? saveBlockedReason
: workflowHasErrors
? 'Some fields still need work'
: isCreatedFromTemplate || isCreatedFromLink || hasUnsavedChanges
? undefined
: 'No changes to save'
const todayRailNav = useFeatureFlag('TODAY_RAIL_NAV')
const [displayStatus, setDisplayStatus] = useState(workflow?.status)
const [isTransitioning, setIsTransitioning] = useState(false)
Expand Down Expand Up @@ -135,15 +143,7 @@ export function WorkflowActions(props: WorkflowSceneLogicProps): JSX.Element {
editTemplateId={editTemplateId}
isWorkflowSubmitting={isWorkflowSubmitting}
draftActionPending={draftActionPending}
saveDisabledReason={
workflowHasErrors
? 'Some fields still need work'
: isCreatedFromTemplate || isCreatedFromLink
? undefined
: hasUnsavedChanges
? undefined
: 'No changes to save'
}
saveDisabledReason={saveDisabledReason}
publishDisabledReason={publishDisabledReason}
discardDisabledReason={discardDisabledReason}
saveLabel={
Expand Down Expand Up @@ -199,15 +199,7 @@ export function WorkflowActions(props: WorkflowSceneLogicProps): JSX.Element {
form="workflow"
onClick={submitWorkflow}
loading={isWorkflowSubmitting}
disabledReason={
workflowHasErrors
? 'Some fields still need work'
: isCreatedFromTemplate || isCreatedFromLink
? undefined
: hasUnsavedChanges
? undefined
: 'No changes to save'
}
disabledReason={saveDisabledReason}
>
{props.id === 'new'
? 'Create as draft'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { expectLogic } from 'kea-test-utils'
import posthog from 'posthog-js'

import { lemonToast } from '@posthog/lemon-ui'

import { loadAppMetricsTotals } from 'lib/components/AppMetrics/appMetricsLogic'

import { useMocks } from '~/mocks/jest'
Expand Down Expand Up @@ -216,7 +218,7 @@ describe('tileCanvasLogic', () => {
logic.mount()
})

it.each([
const sources = [
{
source: 'palette',
add: (): void => {
Expand All @@ -237,14 +239,30 @@ describe('tileCanvasLogic', () => {
source: 'quick add',
add: (): void => logic.actions.addStepAt(delayStep, logic.values.workflow.edges[0]),
},
])('counts a step added from the $source', ({ source, add }) => {
]

it.each(sources)('counts a step added from the $source', ({ source, add }) => {
const capture = jest.spyOn(posthog, 'capture')

add()

expect(capture).toHaveBeenCalledWith('workflows tile canvas step added', { source, step_type: 'delay' })
})

it.each(sources)('asks to wait instead of adding from the $source while an email is added', ({ add }) => {
hogFlowEditorLogic().actions.setEmailInsertionPending(true)
const capture = jest.spyOn(posthog, 'capture')
const toast = jest.spyOn(lemonToast, 'info').mockImplementation()

add()

expect(capture).not.toHaveBeenCalledWith('workflows tile canvas step added', expect.anything())
expect(toast).toHaveBeenCalledWith('An email step is still being added. Try again in a moment.')
expect(logic.values.nodeToBeAdded).toBeNull()
capture.mockRestore()
toast.mockRestore()
})

it.each(['startMovingNode', 'startCopyingNode'] as const)(
'drops a pending %s when someone starts adding a step',
async (startPending) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import { subscriptions } from 'kea-subscriptions'
import posthog from 'posthog-js'
import type { DragEvent } from 'react'

import { lemonToast } from '@posthog/lemon-ui'

import { loadAppMetricsTotals } from 'lib/components/AppMetrics/appMetricsLogic'
import { FeatureFlagsSet, featureFlagLogic } from 'lib/logic/featureFlagLogic'
import { objectsEqual } from 'lib/utils/objects'
Expand All @@ -25,7 +27,7 @@ import { teamLogic } from 'scenes/teamLogic'
import { sceneLayoutLogic } from '~/layout/scenes/sceneLayoutLogic'
import { HogFunctionTemplateType, TeamPublicType, TeamType } from '~/types'

import { WorkflowLogicProps, workflowLogic } from '../../../workflowLogic'
import { EMAIL_INSERTION_PENDING_REASON, WorkflowLogicProps, workflowLogic } from '../../../workflowLogic'
import { getEdgeId } from '../../edgeId'
import {
actionMetricsFromTotals,
Expand Down Expand Up @@ -155,6 +157,7 @@ function isTypedOnCanvas(event: KeyboardEvent): boolean {
export interface tileCanvasLogicValues {
featureFlags: FeatureFlagsSet // featureFlagLogic
dropzoneNodes: DropzoneNode[] // hogFlowEditorLogic
emailInsertionPending: boolean // hogFlowEditorLogic
isCopyingNode: boolean // hogFlowEditorLogic
isMovingNode: boolean // hogFlowEditorLogic
mode: HogFlowEditorMode // hogFlowEditorLogic
Expand Down Expand Up @@ -446,6 +449,7 @@ export const tileCanvasLogic = kea<tileCanvasLogicType>([
'selectedNodeCanBeDeleted',
'selectedNodeCanBeCopiedOrMoved',
'mode',
'emailInsertionPending',
],
featureFlagLogic,
['featureFlags'],
Expand Down Expand Up @@ -722,6 +726,12 @@ export const tileCanvasLogic = kea<tileCanvasLogicType>([
actions.copyNodeToHighlightedDropzone()
}
}
const emailInsertionBlocks = (): boolean => {
if (values.emailInsertionPending) {
lemonToast.info(EMAIL_INSERTION_PENDING_REASON)
}
return values.emailInsertionPending
}
const placeNewStep = (edge: HogFlowEdge, step: CreateActionType | HogFlowActionNode): void => {
actions.onDrop(undefined, edge)
captureStepAdded(values.placingSource, 'data' in step ? step.data.type : step.type)
Expand Down Expand Up @@ -781,16 +791,25 @@ export const tileCanvasLogic = kea<tileCanvasLogicType>([
},
revealStep: ({ actionId }) => revealContainingPaths(actionId),
startPlacing: ({ step }) => {
if (emailInsertionBlocks()) {
return
}
dropPendingMoveOrCopy()
actions.setNodeToBeAdded(step)
},
addStepAt: ({ step, edge }) => {
if (emailInsertionBlocks()) {
return
}
dropPendingMoveOrCopy()
actions.setNodeToBeAdded(step)
actions.onDrop(undefined, edge)
captureStepAdded('quick add', step.type)
},
placeAt: ({ edge }) => {
if (emailInsertionBlocks()) {
return
}
if (values.isMovingNode || values.isCopyingNode) {
placeExistingStep(edge)
} else if (values.nodeToBeAdded) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ describe('hogFlowEditorLogic', () => {
},
} as HogFlowAction,
},
])('duplicates $case below itself', ({ step }) => {
])('duplicates $case below itself', async ({ step }) => {
teamLogic.actions.loadCurrentTeamSuccess({
...MOCK_DEFAULT_TEAM,
workflows_config: {
Expand All @@ -303,7 +303,7 @@ describe('hogFlowEditorLogic', () => {
edges: [edge(TRIGGER_NODE_ID, step.id, 'continue'), edge(step.id, EXIT_NODE_ID, 'continue')],
})

logic.actions.duplicateNodeBelow(step.id)
await expectLogic(logic, () => logic.actions.duplicateNodeBelow(step.id)).toFinishAllListeners()

const duplicatedAction = logic.values.workflow.actions.find(
(action) => action.id !== step.id && action.type === step.type
Expand Down
Loading
Loading