diff --git a/products/workflows/frontend/Channels/EmailDomain/EmailDomain.stories.tsx b/products/workflows/frontend/Channels/EmailDomain/EmailDomain.stories.tsx index 6de6c373e57b..549327b4ef85 100644 --- a/products/workflows/frontend/Channels/EmailDomain/EmailDomain.stories.tsx +++ b/products/workflows/frontend/Channels/EmailDomain/EmailDomain.stories.tsx @@ -238,6 +238,17 @@ export const ReadyWithSuggestions: Story = { }, } +export const VerifyingWithSuggestions: Story = { + args: { + host: 'cloudflare', + seed: (backend) => { + backend.publishRecords(DOMAIN, halfPublished) + backend.suggestWorkflows(SUGGESTED_WORKFLOWS) + }, + aiApproved: true, + }, +} + export const Unavailable: Story = { render: () => } export const AgentHandoff: Story = { diff --git a/products/workflows/frontend/Channels/EmailDomain/EmailDomainScene.test.tsx b/products/workflows/frontend/Channels/EmailDomain/EmailDomainScene.test.tsx index c284c6ab385f..6b9b9f1fab6c 100644 --- a/products/workflows/frontend/Channels/EmailDomain/EmailDomainScene.test.tsx +++ b/products/workflows/frontend/Channels/EmailDomain/EmailDomainScene.test.tsx @@ -156,6 +156,12 @@ describe('EmailDomainScene', () => { expectProgress('8 of 8 found') expect(screen.getByText('Turn on your workflows now')).toBeInTheDocument() expect(element('email-domain-verifying-go-to-workflows')).toBeInTheDocument() + expect(element('email-domain-verifying-create-welcome-workflow')).toBeInTheDocument() + expect(element('email-domain-verifying-create-blank-workflow')).toBeInTheDocument() + expect(screen.queryByText('Proves you own the domain')).not.toBeInTheDocument() + click('email-domain-dns-records') + await flush() + expect(screen.getByText('Proves you own the domain')).toBeInTheDocument() backend.verifyDomain(DOMAIN) await flush(POLL_MS) @@ -224,7 +230,7 @@ describe('EmailDomainScene', () => { expectStep(/You can send from/) }) - it('replaces the template with workflows suggested from the data, loaded while DNS was checked', async () => { + it('replaces the template with workflows suggested from the data, from the wait for DNS to the last step', async () => { const capture = jest.spyOn(posthog, 'capture') const shownEvents = (): unknown[][] => capture.mock.calls.filter(([event]) => event === 'workflows data suggestions shown') @@ -237,21 +243,33 @@ describe('EmailDomainScene', () => { ]) await open(urls.workflowsEmailDomain(SENDER_ID)) expectStep('Checking your settings…') - expect(screen.queryByText('Welcome new teammates')).not.toBeInTheDocument() - expect(shownEvents()).toHaveLength(0) + expect(screen.getByText('Welcome new teammates')).toBeInTheDocument() + expect(document.querySelector('[data-attr="email-domain-verifying-create-welcome-workflow"]')).toBeNull() + expect(element('email-domain-verifying-skip-suggestions')).toBeInTheDocument() + expect(element('email-domain-verifying-suggested-workflow-choices')).toContainElement( + element('workflows-data-suggestion-ai') + ) + expect(shownEvents()).toHaveLength(1) backend.verifyDomain(DOMAIN) await flush(POLL_MS) expectStep(/You can send from/) expect(screen.getByText('Welcome new teammates')).toBeInTheDocument() expect(element('workflows-data-suggestion-ai')).toHaveTextContent('Create workflow') + expect(element('email-domain-suggested-workflow-choices')).toContainElement( + element('workflows-data-suggestion-ai') + ) expect(document.querySelector('[data-attr="email-domain-create-welcome-workflow"]')).toBeNull() expect(document.querySelector('[data-attr="email-domain-create-blank-workflow"]')).toBeNull() expect(backend.requestsTo('data_suggestions_generate')).toHaveLength(1) expect(shownEvents()).toEqual([ [ 'workflows data suggestions shown', - expect.objectContaining({ surface: 'email_domain_wizard', stages: ['onboarding'] }), + expect.objectContaining({ surface: 'email_domain_wizard', placement: 'verifying' }), + ], + [ + 'workflows data suggestions shown', + expect.objectContaining({ surface: 'email_domain_wizard', placement: 'ready' }), ], ]) @@ -409,6 +427,38 @@ describe('EmailDomainScene', () => { } ) + it('opens the DNS records by itself once automatic checks stop with a setting missing', async () => { + seedSender() + backend.publishRecords(DOMAIN, (record) => record.kind !== 'verification') + await open(urls.workflowsEmailDomain(SENDER_ID)) + expect(screen.queryByText('Proves you own the domain')).not.toBeInTheDocument() + + await flush(31 * 60_000) + expect(screen.getByText('One setting is still missing')).toBeInTheDocument() + expect(screen.getByText('Proves you own the domain')).toBeInTheDocument() + }) + + it.each<[string, (fake: FakeEmailDomainBackend) => void]>([ + ['verifying', (fake) => fake.publishRecords(DOMAIN)], + ['ready', (fake) => fake.verifyDomain(DOMAIN)], + ])('reports a suggestion clicked on the %s step with its placement', async (placement, reachStep) => { + const capture = jest.spyOn(posthog, 'capture') + turnOnSuggestions(true) + seedSender() + reachStep(backend) + backend.suggestWorkflows([SUGGESTED_WORKFLOW]) + await open(urls.workflowsEmailDomain(SENDER_ID)) + + click('workflows-data-suggestion-ai') + + expect(capture.mock.calls.filter(([event]) => event === 'workflows data suggestion clicked')).toEqual([ + [ + 'workflows data suggestion clicked', + expect.objectContaining({ surface: 'email_domain_wizard', placement }), + ], + ]) + }) + it('falls back to the generic manual setup when host detection fails', async () => { seedSender() backend.fail('domain_connect_check', { status: 500, body: { detail: 'DNS lookup timed out' } }) diff --git a/products/workflows/frontend/Channels/EmailDomain/EmailDomainSetup.tsx b/products/workflows/frontend/Channels/EmailDomain/EmailDomainSetup.tsx index 37adae338f46..60b50bf84276 100644 --- a/products/workflows/frontend/Channels/EmailDomain/EmailDomainSetup.tsx +++ b/products/workflows/frontend/Channels/EmailDomain/EmailDomainSetup.tsx @@ -6,7 +6,7 @@ import { LemonSkeleton } from '@posthog/lemon-ui' import { urls } from 'scenes/urls' import { AgentHandoffModal } from './components/AgentHandoffModal' -import { SendOnceVerifiedCard } from './components/SendOnceVerifiedCard' +import { SendOnceVerifiedChoices } from './components/SendOnceVerifiedChoices' import { StepIndicator } from './components/StepIndicator' import { emailDomainLogic } from './emailDomainLogic' import { emailDomainStatusLogic } from './emailDomainStatusLogic' @@ -43,7 +43,7 @@ function CurrentStep(): JSX.Element { return ( <> - {status?.status !== 'failed' && } + {status?.status !== 'failed' && } ) case 'ready': diff --git a/products/workflows/frontend/Channels/EmailDomain/components/DnsRecordsSummary.tsx b/products/workflows/frontend/Channels/EmailDomain/components/DnsRecordsSummary.tsx new file mode 100644 index 000000000000..f1fab72ff54b --- /dev/null +++ b/products/workflows/frontend/Channels/EmailDomain/components/DnsRecordsSummary.tsx @@ -0,0 +1,56 @@ +import { useValues } from 'kea' + +import { IconCheck, IconWarning } from '@posthog/icons' +import { LemonCollapse, Spinner } from '@posthog/lemon-ui' + +import type { EmailDomainStatusRecordApi } from 'products/integrations/frontend/generated/api.schemas' + +import { emailDomainAutoConfigureLogic } from '../emailDomainAutoConfigureLogic' +import { emailDomainStatusLogic } from '../emailDomainStatusLogic' +import { DnsRecordRows } from './DnsRecordRows' + +interface DnsRecordsSummaryProps { + /** The records to highlight as missing. */ + missing: EmailDomainStatusRecordApi[] + /** Opens the records, because the person has to fix some of them. */ + needsAttention: boolean +} + +function SummaryIcon({ needsAttention }: { needsAttention: boolean }): JSX.Element { + const { allRecordsFound } = useValues(emailDomainStatusLogic) + if (allRecordsFound) { + return + } + return needsAttention ? : +} + +/** The DNS records behind one summary line, which a person expands to copy a record. */ +export function DnsRecordsSummary({ missing, needsAttention }: DnsRecordsSummaryProps): JSX.Element { + const { records, foundCount, allRecordsFound } = useValues(emailDomainStatusLogic) + const { hostName } = useValues(emailDomainAutoConfigureLogic) + const host = hostName ?? 'your DNS host' + return ( + + + + {allRecordsFound + ? `${records.length} settings in place at ${host}` + : `${foundCount} of ${records.length} settings in place at ${host}`} + + + ), + content: , + }, + ]} + /> + ) +} diff --git a/products/workflows/frontend/Channels/EmailDomain/components/FirstWorkflowChoices.tsx b/products/workflows/frontend/Channels/EmailDomain/components/FirstWorkflowChoices.tsx index 2abf419d9979..1e751af0bb47 100644 --- a/products/workflows/frontend/Channels/EmailDomain/components/FirstWorkflowChoices.tsx +++ b/products/workflows/frontend/Channels/EmailDomain/components/FirstWorkflowChoices.tsx @@ -7,6 +7,7 @@ import { LemonButton, LemonTag, Tooltip } from '@posthog/lemon-ui' import { SuggestionCard } from '../../../setupGuide/suggestions/suggestionCards' import { WorkflowDataSuggestions } from '../../../setupGuide/suggestions/WorkflowDataSuggestions' import { + WorkflowDataSuggestionsPlacement, WorkflowDataSuggestionsSurface, workflowDataSuggestionsLogic, } from '../../../setupGuide/suggestions/workflowDataSuggestionsLogic' @@ -17,27 +18,45 @@ import { CreateWelcomeWorkflowAction } from './CreateWelcomeWorkflowAction' const SURFACE: WorkflowDataSuggestionsSurface = 'email_domain_wizard' -/** The ways to start a first workflow from the domain wizard: the best suggestion once loaded, else the welcome template and a blank workflow. */ -export function FirstWorkflowChoices(): JSX.Element { +// pinned: data-attr prefixes, autocapture dashboards read them +const DATA_ATTR_PREFIX: Record = { + ready: 'email-domain', + verifying: 'email-domain-verifying', +} + +/** + * The ways to start a first workflow from the domain wizard: the best suggestion once loaded, else the welcome + * template and a blank workflow. Each placement, the wait for DNS or the last step, reports its own data-attrs. + */ +export function FirstWorkflowChoices({ + placement = 'ready', +}: { + placement?: WorkflowDataSuggestionsPlacement +}): JSX.Element { const { offersDataSuggestions } = useValues(emailDomainLogic) // The suggestions logic loads on mount, so it only mounts when the organization allows AI processing. - return offersDataSuggestions ? : + return offersDataSuggestions ? ( + + ) : ( + + ) } -function ChoicesWithSuggestions(): JSX.Element { +function ChoicesWithSuggestions({ placement }: { placement: WorkflowDataSuggestionsPlacement }): JSX.Element { const { cards, generating } = useValues(workflowDataSuggestionsLogic({ surface: SURFACE })) + const prefix = DATA_ATTR_PREFIX[placement] const [skipped, setSkipped] = useState(false) if (skipped) { return ( - + setSkipped(false)} - data-attr="email-domain-back-to-suggestions" + data-attr={`${prefix}-back-to-suggestions`} > Back to the suggestions @@ -47,15 +66,24 @@ function ChoicesWithSuggestions(): JSX.Element { // Until suggestions load, and when none come back, the welcome template stays the first choice. if (generating || cards.length === 0) { return ( - + ) } - return setSkipped(true)} /> + return setSkipped(true)} /> } -function SuggestedChoices({ cards, onSkip }: { cards: SuggestionCard[]; onSkip: () => void }): JSX.Element { +function SuggestedChoices({ + placement, + cards, + onSkip, +}: { + placement: WorkflowDataSuggestionsPlacement + cards: SuggestionCard[] + onSkip: () => void +}): JSX.Element { + const prefix = DATA_ATTR_PREFIX[placement] const logic = workflowDataSuggestionsLogic({ surface: SURFACE }) const { buildingKey } = useValues(logic) const { reportCardsShown } = useActions(logic) @@ -64,55 +92,66 @@ function SuggestedChoices({ cards, onSkip }: { cards: SuggestionCard[]; onSkip: // Only the cards on screen count as shown, so expanding reports the rest. useEffect(() => { - reportCardsShown((expanded ? cards : [best]).map((card) => card.suggestionId)) - }, [cards, best, expanded, reportCardsShown]) + reportCardsShown( + (expanded ? cards : [best]).map((card) => card.suggestionId), + placement + ) + }, [cards, best, expanded, placement, reportCardsShown]) return ( -
- - - Suggested from your project's events - - - Beta - - - - - {expanded && - others.map((card) => ( - - ))} -
- {others.length > 0 && !expanded && ( +
+
+ + + Suggested from your project's events + + + Beta + + + + + {expanded && + others.map((card) => ( + + ))} +
+ {others.length > 0 && !expanded && ( + setExpanded(true)} + data-attr={`${prefix}-show-more-suggestions`} + > + Show {others.length} more {others.length === 1 ? 'suggestion' : 'suggestions'} + + )} setExpanded(true)} - data-attr="email-domain-show-more-suggestions" + onClick={onSkip} + disabledReason={buildingKey ? 'Wait until the suggested workflow is built' : undefined} + data-attr={`${prefix}-skip-suggestions`} > - Show {others.length} more {others.length === 1 ? 'suggestion' : 'suggestions'} + Start without a suggestion - )} - - Start without a suggestion - +
) } -function TemplateChoices({ children }: { children?: ReactNode }): JSX.Element { +function TemplateChoices({ prefix, children }: { prefix: string; children?: ReactNode }): JSX.Element { return ( -
- - +
+ + {children}
) diff --git a/products/workflows/frontend/Channels/EmailDomain/components/SendOnceVerifiedCard.tsx b/products/workflows/frontend/Channels/EmailDomain/components/SendOnceVerifiedChoices.tsx similarity index 81% rename from products/workflows/frontend/Channels/EmailDomain/components/SendOnceVerifiedCard.tsx rename to products/workflows/frontend/Channels/EmailDomain/components/SendOnceVerifiedChoices.tsx index d1ad758868a2..f91617f5b720 100644 --- a/products/workflows/frontend/Channels/EmailDomain/components/SendOnceVerifiedCard.tsx +++ b/products/workflows/frontend/Channels/EmailDomain/components/SendOnceVerifiedChoices.tsx @@ -1,16 +1,17 @@ import { useValues } from 'kea' -import { LemonCard, Link } from '@posthog/lemon-ui' +import { Link } from '@posthog/lemon-ui' import { urls } from 'scenes/urls' import { emailDomainSenderLogic } from '../emailDomainSenderLogic' import { HedgehogMailbox } from '../hoggies' +import { FirstWorkflowChoices } from './FirstWorkflowChoices' -export function SendOnceVerifiedCard(): JSX.Element { +export function SendOnceVerifiedChoices(): JSX.Element { const { domain } = useValues(emailDomainSenderLogic) return ( - +
@@ -22,11 +23,12 @@ export function SendOnceVerifiedCard(): JSX.Element {

+
Go to workflows
- +
) } diff --git a/products/workflows/frontend/Channels/EmailDomain/steps/ReadyStep.tsx b/products/workflows/frontend/Channels/EmailDomain/steps/ReadyStep.tsx index c6204811cd20..8cc3e1dabb08 100644 --- a/products/workflows/frontend/Channels/EmailDomain/steps/ReadyStep.tsx +++ b/products/workflows/frontend/Channels/EmailDomain/steps/ReadyStep.tsx @@ -1,10 +1,7 @@ import { useValues } from 'kea' import { ReactNode } from 'react' -import { IconCheck, IconWarning } from '@posthog/icons' -import { LemonCollapse } from '@posthog/lemon-ui' - -import { DnsRecordRows } from '../components/DnsRecordRows' +import { DnsRecordsSummary } from '../components/DnsRecordsSummary' import { NextWorkflowCard } from '../components/NextWorkflowCard' import { SenderCard } from '../components/SenderCard' import { StepHeading } from '../components/StepHeading' @@ -37,34 +34,9 @@ export function ReadyStep({ next = }: { next?: ReactNode }) />
- - {allRecordsFound ? ( - - ) : ( - - )} - - {allRecordsFound - ? `${records.length} settings in place at ${host}` - : `${foundCount} of ${records.length} settings in place at ${host}`} - - - ), - content: ( - record.status === 'pending')} - showStatus - /> - ), - }, - ]} + record.status === 'pending')} + needsAttention={!allRecordsFound} /> {next}
diff --git a/products/workflows/frontend/Channels/EmailDomain/steps/VerifyingStep.tsx b/products/workflows/frontend/Channels/EmailDomain/steps/VerifyingStep.tsx index a9d83c9ef967..3477ef45dcd1 100644 --- a/products/workflows/frontend/Channels/EmailDomain/steps/VerifyingStep.tsx +++ b/products/workflows/frontend/Channels/EmailDomain/steps/VerifyingStep.tsx @@ -8,7 +8,8 @@ import { LemonProgress } from 'lib/lemon-ui/LemonProgress' import { AgentSetupButton } from '../components/AgentSetupButton' import { BigAction } from '../components/BigAction' import { DnsHostLink } from '../components/DnsHostLink' -import { DnsRecordRows, RECORD_KIND_LABEL } from '../components/DnsRecordRows' +import { RECORD_KIND_LABEL } from '../components/DnsRecordRows' +import { DnsRecordsSummary } from '../components/DnsRecordsSummary' import { StepHeading } from '../components/StepHeading' import { VerificationStepsStrip } from '../components/VerificationStepsStrip' import { emailDomainAutoConfigureLogic } from '../emailDomainAutoConfigureLogic' @@ -163,7 +164,7 @@ export function VerifyingStep(): JSX.Element { )} - +
) } diff --git a/products/workflows/frontend/setupGuide/suggestions/WorkflowSuggestionCard.tsx b/products/workflows/frontend/setupGuide/suggestions/WorkflowSuggestionCard.tsx index c52a1ebfc47b..dbf2579c0d15 100644 --- a/products/workflows/frontend/setupGuide/suggestions/WorkflowSuggestionCard.tsx +++ b/products/workflows/frontend/setupGuide/suggestions/WorkflowSuggestionCard.tsx @@ -17,7 +17,11 @@ import { IconSlack, IconTwilio } from 'lib/lemon-ui/icons' import { humanFriendlyNumber } from 'lib/utils/numbers' import type { SuggestionCard, SuggestionStepKind } from './suggestionCards' -import { WorkflowDataSuggestionsSurface, workflowDataSuggestionsLogic } from './workflowDataSuggestionsLogic' +import { + WorkflowDataSuggestionsPlacement, + WorkflowDataSuggestionsSurface, + workflowDataSuggestionsLogic, +} from './workflowDataSuggestionsLogic' const STEP_DISPLAY: Record = { trigger: { label: 'Trigger', icon: }, @@ -39,10 +43,12 @@ const MAX_STEP_CHIPS = 6 export function WorkflowSuggestionCard({ card, surface, + placement, primary = false, }: { card: SuggestionCard surface: WorkflowDataSuggestionsSurface + placement?: WorkflowDataSuggestionsPlacement primary?: boolean }): JSX.Element { const logic = workflowDataSuggestionsLogic({ surface }) @@ -86,7 +92,7 @@ export function WorkflowSuggestionCard({ size="small" loading={building} disabledReason={buildingKey && !building ? 'Another workflow is being built' : undefined} - onClick={() => startSuggestion(card)} + onClick={() => startSuggestion(card, placement)} // pinned: data-attr - autocapture dashboards read it data-attr="workflows-data-suggestion-ai" > diff --git a/products/workflows/frontend/setupGuide/suggestions/workflowDataSuggestionsLogic.test.ts b/products/workflows/frontend/setupGuide/suggestions/workflowDataSuggestionsLogic.test.ts index 47ac1a0e7723..2e0151edd241 100644 --- a/products/workflows/frontend/setupGuide/suggestions/workflowDataSuggestionsLogic.test.ts +++ b/products/workflows/frontend/setupGuide/suggestions/workflowDataSuggestionsLogic.test.ts @@ -87,45 +87,54 @@ describe('workflowDataSuggestionsLogic', () => { }) it.each([ - ['empty_state', false, false], - ['onboarding_wizard', true, false], - ['empty_state', false, true], - ] as const)('opens the saved draft on the %s, wizard %s, premade %s', async (surface, finishesWizard, premade) => { - const capture = jest.spyOn(posthog, 'capture') - jest.mocked(workflowDataSuggestionsBuild).mockResolvedValue({ - status: 'ready', - workflow: { name: 'Welcome new users' }, - uses_saved_template: false, - workflow_id: premade ? 'workflow-1' : null, - missing_inputs: [], - }) - jest.mocked(hogFlowsCreate).mockResolvedValue({ id: 'workflow-1' } as any) - if (finishesWizard) { - workflowsOnboardingWizardLogic.mount() - } - const logic = workflowDataSuggestionsLogic({ surface }) - logic.mount() + ['empty_state', false, false, undefined], + ['onboarding_wizard', true, false, undefined], + ['empty_state', false, true, undefined], + ['email_domain_wizard', false, false, 'verifying'], + ] as const)( + 'opens the saved draft on the %s, wizard %s, premade %s, placement %s', + async (surface, finishesWizard, premade, placement) => { + const capture = jest.spyOn(posthog, 'capture') + jest.mocked(workflowDataSuggestionsBuild).mockResolvedValue({ + status: 'ready', + workflow: { name: 'Welcome new users' }, + uses_saved_template: false, + workflow_id: premade ? 'workflow-1' : null, + missing_inputs: [], + }) + jest.mocked(hogFlowsCreate).mockResolvedValue({ id: 'workflow-1' } as any) + if (finishesWizard) { + workflowsOnboardingWizardLogic.mount() + } + const logic = workflowDataSuggestionsLogic({ surface }) + logic.mount() - await logic.asyncActions.startSuggestion({ ...card, premade }) - - expect(jest.mocked(workflowDataSuggestionsBuild).mock.calls.map(([, body]) => body)).toEqual([ - premade - ? { suggestion_id: 'suggestion-1', save_draft: true } - : { suggestion_id: 'suggestion-1', retry: true }, - ]) - expect(jest.mocked(hogFlowsCreate).mock.calls.map(([, body]) => body)).toEqual( - premade ? [] : [expect.objectContaining({ name: 'Welcome new users', status: 'draft' })] - ) - expect(router.values.location.pathname).toContain('workflow-1') - expect(loadEntrySource('workflow-1')).toBe('data_suggestion') - expect(logic.values.buildingKey).toBeNull() - const wizardFinished = capture.mock.calls.filter(([event]) => event === 'workflows onboarding wizard finished') - expect(wizardFinished).toEqual( - finishesWizard - ? [['workflows onboarding wizard finished', { path: 'messaging', template_id: 'data_suggestion' }]] - : [] - ) - }) + await logic.asyncActions.startSuggestion({ ...card, premade }, placement) + + expect(jest.mocked(workflowDataSuggestionsBuild).mock.calls.map(([, body]) => body)).toEqual([ + premade + ? { suggestion_id: 'suggestion-1', save_draft: true } + : { suggestion_id: 'suggestion-1', retry: true }, + ]) + expect(jest.mocked(hogFlowsCreate).mock.calls.map(([, body]) => body)).toEqual( + premade ? [] : [expect.objectContaining({ name: 'Welcome new users', status: 'draft' })] + ) + expect(router.values.location.pathname).toContain('workflow-1') + expect(loadEntrySource('workflow-1')).toBe('data_suggestion') + expect(logic.values.buildingKey).toBeNull() + const clicked = capture.mock.calls.find(([event]) => event === 'workflows data suggestion clicked') + expect(clicked?.[1]).toMatchObject({ surface }) + expect(clicked?.[1]?.placement).toEqual(placement) + const wizardFinished = capture.mock.calls.filter( + ([event]) => event === 'workflows onboarding wizard finished' + ) + expect(wizardFinished).toEqual( + finishesWizard + ? [['workflows onboarding wizard finished', { path: 'messaging', template_id: 'data_suggestion' }]] + : [] + ) + } + ) it.each([ ['saves the draft once the background build is ready', false, 1], diff --git a/products/workflows/frontend/setupGuide/suggestions/workflowDataSuggestionsLogic.ts b/products/workflows/frontend/setupGuide/suggestions/workflowDataSuggestionsLogic.ts index afa634afbdf6..b9b559292cdd 100644 --- a/products/workflows/frontend/setupGuide/suggestions/workflowDataSuggestionsLogic.ts +++ b/products/workflows/frontend/setupGuide/suggestions/workflowDataSuggestionsLogic.ts @@ -43,6 +43,8 @@ const BUILD_FAILED_MESSAGE = "We couldn't build this workflow. Try again, or sta // pinned: analytics property values for the `workflows data suggestion*` events - renaming breaks dashboards export type WorkflowDataSuggestionsSurface = 'empty_state' | 'onboarding_wizard' | 'email_domain_wizard' +// pinned: analytics property values. Where the cards show, on a surface that shows them in more than one step. +export type WorkflowDataSuggestionsPlacement = 'verifying' | 'ready' export interface WorkflowDataSuggestionsLogicProps { surface: WorkflowDataSuggestionsSurface @@ -94,14 +96,22 @@ export interface workflowDataSuggestionsLogicActions { aiResult: DataSuggestionsResponseApi | null payload?: any } - reportCardsShown: (shownSuggestionIds?: string[]) => { + reportCardsShown: ( + shownSuggestionIds?: string[], + placement?: WorkflowDataSuggestionsPlacement + ) => { shownSuggestionIds: string[] | undefined + placement: WorkflowDataSuggestionsPlacement | undefined } setBuildingKey: (key: string | null) => { key: string | null } - startSuggestion: (card: SuggestionCard) => { + startSuggestion: ( + card: SuggestionCard, + placement?: WorkflowDataSuggestionsPlacement + ) => { card: SuggestionCard + placement: WorkflowDataSuggestionsPlacement | undefined } } @@ -143,8 +153,11 @@ export const workflowDataSuggestionsLogic = kea ({ shownSuggestionIds }), - startSuggestion: (card: SuggestionCard) => ({ card }), + reportCardsShown: (shownSuggestionIds?: string[], placement?: WorkflowDataSuggestionsPlacement) => ({ + shownSuggestionIds, + placement, + }), + startSuggestion: (card: SuggestionCard, placement?: WorkflowDataSuggestionsPlacement) => ({ card, placement }), setBuildingKey: (key: string | null) => ({ key }), }), reducers({ @@ -225,7 +238,7 @@ export const workflowDataSuggestionsLogic = kea { + reportCardsShown: ({ shownSuggestionIds, placement }) => { const aiResult = values.aiResult const shown = aiResult?.status === 'ready' @@ -237,6 +250,7 @@ export const workflowDataSuggestionsLogic = kea suggestion.premade) ? 'premade' : 'ai', trigger_events: shown.map((suggestion) => suggestion.trigger_event), stages: shown.map((suggestion) => suggestion.stage), @@ -248,13 +262,14 @@ export const workflowDataSuggestionsLogic = kea handleResult(aiResult), - startSuggestion: async ({ card }, breakpoint) => { + startSuggestion: async ({ card, placement }, breakpoint) => { if (values.buildingKey) { return } // pinned: analytics event name - renaming breaks dashboards posthog.capture('workflows data suggestion clicked', { surface: props.surface, + ...(placement ? { placement } : {}), source: card.premade ? 'premade' : 'ai', trigger_event: card.triggerEvent, weekly_count: card.weeklyCount,