Skip to content

Commit f43071d

Browse files
authored
chore(shared,ui): Add telemetry event for flow steps (#8951)
1 parent 3327226 commit f43071d

5 files changed

Lines changed: 84 additions & 2 deletions

File tree

‎.changeset/gold-ends-throw.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@clerk/shared': patch
3+
'@clerk/ui': patch
4+
---
5+
6+
Add a generic `FLOW_STEP_MOUNTED` telemetry event (`eventFlowStepMounted`) for measuring multi-step flow funnels, and wire it into the self-serve SSO flow
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import type { TelemetryEventRaw } from '../../types';
2+
3+
const EVENT_FLOW_STEP_MOUNTED = 'FLOW_STEP_MOUNTED';
4+
const EVENT_SAMPLING_RATE = 1;
5+
6+
type EventFlowStepMounted = {
7+
/** The flow the step belongs to, e.g. `configureSSO` (mirrors `Flow.Root`'s `flow`). */
8+
flow: string;
9+
/** The step/part that mounted, e.g. `verify-domain` */
10+
step: string;
11+
/** Free-form, flow-specific metadata supplied by the caller (e.g. `timestamp`, `connectionStatus`). */
12+
metadata: TelemetryEventRaw['payload'];
13+
} & TelemetryEventRaw['payload'];
14+
15+
/**
16+
* Fires an event from a part of a multi-step flow.
17+
*
18+
* @param flow - The flow identifier (matches `Flow.Root`'s `flow`).
19+
* @param step - The step/part that mounted.
20+
* @param metadata - Flow-specific metadata sent under `payload.metadata`.
21+
* @param eventSamplingRate - Override the default full-capture sampling rate.
22+
* @example
23+
* telemetry.record(eventFlowStepMounted('configureSSO', 'verify-domain', { timestamp: new Date().toISOString(), connectionStatus: 'unconfigured' }));
24+
*/
25+
export function eventFlowStepMounted(
26+
flow: string,
27+
step: string,
28+
metadata: TelemetryEventRaw['payload'] = {},
29+
eventSamplingRate: number = EVENT_SAMPLING_RATE,
30+
): TelemetryEventRaw<EventFlowStepMounted> {
31+
return {
32+
event: EVENT_FLOW_STEP_MOUNTED,
33+
eventSamplingRate,
34+
payload: {
35+
flow,
36+
step,
37+
metadata,
38+
},
39+
};
40+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export * from './component-mounted';
2+
export * from './flow-step';
23
export * from './method-called';
34
export * from './framework-metadata';
45
export * from './theme-usage';

‎packages/ui/src/components/ConfigureSSO/steps/ActivateStep.tsx‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import { useClerk, useOrganization } from '@clerk/shared/react';
2+
import { eventFlowStepMounted } from '@clerk/shared/telemetry';
3+
14
import { Button, Col, descriptors, Flex, Flow, Heading, Icon, localizationKeys, Text } from '@/customizables';
25
import { useCardState } from '@/elements/contexts';
36
import { ChevronRight, DuotoneShieldCheck } from '@/icons';
@@ -15,6 +18,8 @@ export const ActivateStep = (): JSX.Element => {
1518
onExit,
1619
} = useConfigureSSO();
1720
const card = useCardState();
21+
const clerk = useClerk();
22+
const { organization } = useOrganization();
1823

1924
// The activate step is only reachable with a configured connection, so the
2025
// domains are set; join multiples for the subtitle copy.
@@ -31,6 +36,14 @@ export const ActivateStep = (): JSX.Element => {
3136

3237
try {
3338
await setConnectionActive(enterpriseConnection.id, true);
39+
clerk.telemetry?.record(
40+
eventFlowStepMounted('configureSSO', 'activate', {
41+
timestamp: new Date().toISOString(),
42+
connectionStatus: 'active',
43+
connectionId: enterpriseConnection.id,
44+
organizationId: organization?.id ?? null,
45+
}),
46+
);
3447
onExit?.();
3548
} catch (err) {
3649
handleError(err as Error, [], card.setError);

‎packages/ui/src/components/ConfigureSSO/steps/OrganizationDomainsStep.tsx‎

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import { useUser } from '@clerk/shared/react';
1+
import { useClerk, useOrganization, useUser } from '@clerk/shared/react';
2+
import { eventFlowStepMounted } from '@clerk/shared/telemetry';
23
import type { OrganizationDomainResource } from '@clerk/shared/types';
34
import type React from 'react';
4-
import { useState } from 'react';
5+
import { useEffect, useRef, useState } from 'react';
56

67
import {
78
Badge,
@@ -40,14 +41,35 @@ export const OrganizationDomainsStep = (): JSX.Element => {
4041
const {
4142
enterpriseConnection,
4243
organizationDomains,
44+
organizationEnterpriseConnection,
4345
contentRef,
4446
organizationDomainMutations: { createDomain, revalidate },
4547
enterpriseConnectionMutations: { updateConnection },
4648
} = useConfigureSSO();
4749
const { goPrev, goNext, isFirstStep, isLastStep } = useWizard();
4850
const card = useCardState();
51+
const clerk = useClerk();
52+
const { organization } = useOrganization();
4953
const [domainToRemove, setDomainToRemove] = useState<OrganizationDomainResource | null>(null);
5054

55+
const hasRecordedTelemetryEvent = useRef(false);
56+
useEffect(() => {
57+
if (hasRecordedTelemetryEvent.current) {
58+
return;
59+
}
60+
61+
hasRecordedTelemetryEvent.current = true;
62+
clerk.telemetry?.record(
63+
eventFlowStepMounted('configureSSO', 'verify-domain', {
64+
timestamp: new Date().toISOString(),
65+
connectionStatus: organizationEnterpriseConnection.status,
66+
connectionId: enterpriseConnection?.id ?? null,
67+
organizationId: organization?.id ?? null,
68+
}),
69+
);
70+
// eslint-disable-next-line react-hooks/exhaustive-deps
71+
}, []);
72+
5173
const handleCreateDomain = async (domain: string) => {
5274
card.setError(undefined);
5375

0 commit comments

Comments
 (0)