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
12 changes: 8 additions & 4 deletions src/components/dashboard/AppOnboardingFlow.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<script setup lang="ts">
import type { Database } from '~/types/supabase.types'
import type {
OnboardingActionEvent,
OnboardingDetailsEvent,
OnboardingDetailsEventProperties,
OnboardingIntent,
Expand Down Expand Up @@ -980,9 +981,11 @@ async function seedDemoData() {
}
}

async function copyText(text: string) {
async function copyText(text: string, event?: OnboardingActionEvent) {
try {
await navigator.clipboard.writeText(text)
if (event)
progressTracker?.trackActionEvent(event)
toast.success(t('copied-to-clipboard'))
}
catch (error) {
Expand All @@ -1005,10 +1008,11 @@ async function copyCliCommand() {
if (!apiKey.value)
return

await copyText(cliCommand.value)
await copyText(cliCommand.value, 'onboarding_cli_init_command_copied')
}

async function copyAiInstructions() {
progressTracker?.trackActionEvent('onboarding_ai_instructions_copy_clicked')
try {
await loadApiKey()
}
Expand All @@ -1025,13 +1029,13 @@ async function copyAiInstructions() {
{
text: t('app-onboarding-ai-help-copy-without-key'),
role: 'secondary',
handler: () => copyText(createAiHelpPrompt(redactedCliCommand.value)),
handler: () => copyText(createAiHelpPrompt(redactedCliCommand.value), 'onboarding_ai_instructions_copied_without_api_key'),
},
{
text: t('app-onboarding-ai-help-copy-with-key'),
role: 'primary',
disabled: !apiKey.value,
handler: () => copyText(createAiHelpPrompt(cliCommand.value)),
handler: () => copyText(createAiHelpPrompt(cliCommand.value), 'onboarding_ai_instructions_copied_with_api_key'),
},
],
})
Expand Down
15 changes: 15 additions & 0 deletions src/utils/onboardingProgressAnalytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ export const ONBOARDING_ANALYTICS_VERSION = 2
export type OnboardingAnalyticsFlow = 'pre_org' | 'existing_org'
export type OnboardingAnalyticsStep = 'intent' | 'details' | 'organization' | 'choice' | 'install' | 'setup'
export type OnboardingIntent = 'ota' | 'builder' | 'both' | 'exploring'
export type OnboardingActionEvent
= | 'onboarding_ai_instructions_copy_clicked'
| 'onboarding_ai_instructions_copied_with_api_key'
| 'onboarding_ai_instructions_copied_without_api_key'
| 'onboarding_cli_init_command_copied'
export type OnboardingDetailsEvent
= | 'onboarding_app_id_entered'
| 'onboarding_app_id_help_opened'
Expand Down Expand Up @@ -172,8 +177,18 @@ export function createOnboardingProgressTracker(options: CreateOnboardingProgres
safelyCapture(name, { ...properties, ...details })
}

function trackActionEvent(name: OnboardingActionEvent) {
if (!activeStep)
return

const properties = sharedProperties(activeStep)
if (properties)
safelyCapture(name, properties)
}

return {
completeStep,
trackActionEvent,
trackDetailsEvent,
viewStep,
}
Expand Down
14 changes: 14 additions & 0 deletions tests/app-onboarding-progress-integration.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,20 @@ describe('app onboarding progress analytics integration', () => {
expect(onboardingSource).toContain("pushEvent('onboarding_intent_selected', config.supaHost, {")
})

it.concurrent('tracks AI instruction and CLI copy actions', () => {
const copyActions = sourceBetween('async function copyText(', 'function goToInstallStep()')
const clipboardWriteIndex = copyActions.indexOf('await navigator.clipboard.writeText(text)')
const actionTrackIndex = copyActions.indexOf('progressTracker?.trackActionEvent(event)')
const catchIndex = copyActions.indexOf('catch (error)')
expect(copyActions).toContain("trackActionEvent('onboarding_ai_instructions_copy_clicked')")
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
expect(copyActions).toContain("'onboarding_ai_instructions_copied_with_api_key'")
expect(copyActions).toContain("'onboarding_ai_instructions_copied_without_api_key'")
expect(copyActions).toContain("'onboarding_cli_init_command_copied'")
expect(clipboardWriteIndex).toBeGreaterThanOrEqual(0)
expect(actionTrackIndex).toBeGreaterThan(clipboardWriteIndex)
expect(actionTrackIndex).toBeLessThan(catchIndex)
})
Comment thread
coderabbitai[bot] marked this conversation as resolved.

it.concurrent('keeps the unload warning scoped to unfinished pre-org onboarding', () => {
expect(onboardingSource).toContain('useBeforeUnloadWarning(Boolean(props.preOrg))')
const creation = sourceBetween('async function createOrganizationAndApp()', 'async function createAppRecord(')
Expand Down
26 changes: 26 additions & 0 deletions tests/onboarding-progress-analytics.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,32 @@ describe('onboarding progress analytics', () => {
)
})

it.concurrent('associates copy actions with the active onboarding step', () => {
const capture = vi.fn()
const tracker = createOnboardingProgressTracker({
capture,
flow: 'existing_org',
resumed: true,
steps: ['details', 'choice', 'install'],
supaHost: 'https://supabase.capgo.test',
})

tracker.viewStep('install')
tracker.trackActionEvent('onboarding_cli_init_command_copied')

expect(capture).toHaveBeenLastCalledWith(
'onboarding_cli_init_command_copied',
'https://supabase.capgo.test',
expect.objectContaining({
flow: 'existing_org',
onboarding_attempt_id: expect.any(String),
onboarding_version: 2,
resumed: true,
step: 'install',
}),
)
})

it.concurrent('deduplicates completion for one visit and resets timing after back navigation', () => {
let now = 10
const capture = vi.fn()
Expand Down
Loading