From a54ae8868fa437c4b53e4d55c8982da8e0aaf3ec Mon Sep 17 00:00:00 2001 From: Paul D'Ambra Date: Tue, 26 Mar 2024 17:01:03 +0000 Subject: [PATCH] feat: mobile replay onboarding (#21122) * feat: mobile replay in onboarding * wrap replay in a flag too * Update frontend/src/scenes/onboarding/sdks/product-analytics/android.tsx * Update UI snapshots for `chromium` (2) * Update UI snapshots for `chromium` (2) * change text * Update UI snapshots for `chromium` (1) * Update UI snapshots for `chromium` (1) --------- Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- frontend/src/lib/constants.tsx | 1 + frontend/src/scenes/onboarding/Onboarding.tsx | 17 +++++++-- .../src/scenes/onboarding/onboardingLogic.tsx | 18 ++++++---- frontend/src/scenes/onboarding/sdks/SDKs.tsx | 1 + .../sdks/product-analytics/android.tsx | 27 ++++++++++++++ .../sdks/sdk-install-instructions/android.tsx | 35 +++++++++++++++++-- .../src/scenes/onboarding/sdks/sdksLogic.tsx | 29 ++++++++++----- .../SessionReplaySDKInstructions.tsx | 2 ++ .../sdks/session-replay/android.tsx | 11 ++++++ .../onboarding/sdks/session-replay/index.tsx | 1 + frontend/src/scenes/urls.ts | 7 ++-- 11 files changed, 126 insertions(+), 23 deletions(-) create mode 100644 frontend/src/scenes/onboarding/sdks/session-replay/android.tsx diff --git a/frontend/src/lib/constants.tsx b/frontend/src/lib/constants.tsx index 292fcb5d957b7..b4ef15a8bea8f 100644 --- a/frontend/src/lib/constants.tsx +++ b/frontend/src/lib/constants.tsx @@ -212,6 +212,7 @@ export const FEATURE_FLAGS = { AUDIT_LOGS_ACCESS: 'audit-logs-access', // owner: #team-growth SUBSCRIBE_FROM_PAYGATE: 'subscribe-from-paygate', // owner: #team-growth REVERSE_PROXY_ONBOARDING: 'reverse-proxy-onboarding', // owner: @zlwaterfield + SESSION_REPLAY_MOBILE_ONBOARDING: 'session-replay-mobile-onboarding', // owner: #team-replay } as const export type FeatureFlagKey = (typeof FEATURE_FLAGS)[keyof typeof FEATURE_FLAGS] diff --git a/frontend/src/scenes/onboarding/Onboarding.tsx b/frontend/src/scenes/onboarding/Onboarding.tsx index 52efdd5d47f0a..adcd6d3476b4d 100644 --- a/frontend/src/scenes/onboarding/Onboarding.tsx +++ b/frontend/src/scenes/onboarding/Onboarding.tsx @@ -1,11 +1,13 @@ import { useActions, useValues } from 'kea' -import { SESSION_REPLAY_MINIMUM_DURATION_OPTIONS } from 'lib/constants' +import { FEATURE_FLAGS, SESSION_REPLAY_MINIMUM_DURATION_OPTIONS } from 'lib/constants' +import { featureFlagLogic } from 'lib/logic/featureFlagLogic' import { useEffect, useState } from 'react' +import { AndroidInstructions } from 'scenes/onboarding/sdks/session-replay' import { SceneExport } from 'scenes/sceneTypes' import { teamLogic } from 'scenes/teamLogic' import { userLogic } from 'scenes/userLogic' -import { AvailableFeature, ProductKey } from '~/types' +import { AvailableFeature, ProductKey, SDKKey } from '~/types' import { OnboardingBillingStep } from './OnboardingBillingStep' import { OnboardingInviteTeammates } from './OnboardingInviteTeammates' @@ -108,6 +110,9 @@ const SessionReplayOnboarding = (): JSX.Element => { const { hasAvailableFeature } = useValues(userLogic) const { currentTeam } = useValues(teamLogic) + const { featureFlags } = useValues(featureFlagLogic) + const hasAndroidOnBoarding = !!featureFlags[FEATURE_FLAGS.SESSION_REPLAY_MOBILE_ONBOARDING] + const configOptions: ProductConfigOption[] = [ { type: 'toggle', @@ -139,11 +144,16 @@ const SessionReplayOnboarding = (): JSX.Element => { }) } + const sdkInstructionMap = SessionReplaySDKInstructions + if (hasAndroidOnBoarding) { + sdkInstructionMap[SDKKey.ANDROID] = AndroidInstructions + } + return ( @@ -151,6 +161,7 @@ const SessionReplayOnboarding = (): JSX.Element => { ) } + const FeatureFlagsOnboarding = (): JSX.Element => { return ( diff --git a/frontend/src/scenes/onboarding/onboardingLogic.tsx b/frontend/src/scenes/onboarding/onboardingLogic.tsx index 4c8b35542109e..b53b14afc6b62 100644 --- a/frontend/src/scenes/onboarding/onboardingLogic.tsx +++ b/frontend/src/scenes/onboarding/onboardingLogic.tsx @@ -316,9 +316,9 @@ export const onboardingLogic = kea([ actionToUrl(({ values }) => ({ setStepKey: ({ stepKey }) => { if (stepKey) { - return [`/onboarding/${values.productKey}`, { step: stepKey }] + return [`/onboarding/${values.productKey}`, { ...router.values.searchParams, step: stepKey }] } else { - return [`/onboarding/${values.productKey}`] + return [`/onboarding/${values.productKey}`, router.values.searchParams] } }, goToNextStep: () => { @@ -327,9 +327,12 @@ export const onboardingLogic = kea([ ) const nextStep = values.allOnboardingSteps[currentStepIndex + 1] if (nextStep) { - return [`/onboarding/${values.productKey}`, { step: nextStep.props.stepKey }] + return [ + `/onboarding/${values.productKey}`, + { ...router.values.searchParams, step: nextStep.props.stepKey }, + ] } else { - return [`/onboarding/${values.productKey}`] + return [`/onboarding/${values.productKey}`, router.values.searchParams] } }, goToPreviousStep: () => { @@ -338,9 +341,12 @@ export const onboardingLogic = kea([ ) const previousStep = values.allOnboardingSteps[currentStepIndex - 1] if (previousStep) { - return [`/onboarding/${values.productKey}`, { step: previousStep.props.stepKey }] + return [ + `/onboarding/${values.productKey}`, + { ...router.values.searchParams, step: previousStep.props.stepKey }, + ] } else { - return [`/onboarding/${values.productKey}`] + return [`/onboarding/${values.productKey}`, router.values.searchParams] } }, updateCurrentTeamSuccess(val) { diff --git a/frontend/src/scenes/onboarding/sdks/SDKs.tsx b/frontend/src/scenes/onboarding/sdks/SDKs.tsx index 33555a1f17ca9..610caee92ee4f 100644 --- a/frontend/src/scenes/onboarding/sdks/SDKs.tsx +++ b/frontend/src/scenes/onboarding/sdks/SDKs.tsx @@ -118,6 +118,7 @@ export function SDKs({ {sdks?.map((sdk) => ( setSelectedSDK(sdk) : undefined} fullWidth diff --git a/frontend/src/scenes/onboarding/sdks/product-analytics/android.tsx b/frontend/src/scenes/onboarding/sdks/product-analytics/android.tsx index 365f8685a8a74..b6a1fb3c9520f 100644 --- a/frontend/src/scenes/onboarding/sdks/product-analytics/android.tsx +++ b/frontend/src/scenes/onboarding/sdks/product-analytics/android.tsx @@ -1,4 +1,12 @@ import { CodeSnippet, Language } from 'lib/components/CodeSnippet' +import { FlaggedFeature } from 'lib/components/FlaggedFeature' +import { FEATURE_FLAGS } from 'lib/constants' +import { LemonTag } from 'lib/lemon-ui/LemonTag' +import { Link } from 'lib/lemon-ui/Link' +import { OnboardingStepKey } from 'scenes/onboarding/onboardingLogic' +import { urls } from 'scenes/urls' + +import { SDKKey } from '~/types' import { SDKInstallAndroidInstructions } from '../sdk-install-instructions' @@ -6,12 +14,31 @@ function AndroidCaptureSnippet(): JSX.Element { return {`PostHog.capture(event = "test-event")`} } +function AdvertiseAndroidReplay(): JSX.Element { + return ( +
+

+ Session Replay for Android NEW +

+
+ Session replay is now in beta for Android.{' '} + + Learn how to set it up + +
+
+ ) +} + export function ProductAnalyticsAndroidInstructions(): JSX.Element { return ( <>

Send an Event

+ + + ) } diff --git a/frontend/src/scenes/onboarding/sdks/sdk-install-instructions/android.tsx b/frontend/src/scenes/onboarding/sdks/sdk-install-instructions/android.tsx index ff740be34f4fd..103a87f183508 100644 --- a/frontend/src/scenes/onboarding/sdks/sdk-install-instructions/android.tsx +++ b/frontend/src/scenes/onboarding/sdks/sdk-install-instructions/android.tsx @@ -1,8 +1,14 @@ +import { Link } from '@posthog/lemon-ui' import { useValues } from 'kea' import { CodeSnippet, Language } from 'lib/components/CodeSnippet' +import { LemonBanner } from 'lib/lemon-ui/LemonBanner' import { apiHostOrigin } from 'lib/utils/apiHost' import { teamLogic } from 'scenes/teamLogic' +export interface AndroidSetupProps { + includeReplay?: boolean +} + function AndroidInstallSnippet(): JSX.Element { return ( @@ -13,7 +19,7 @@ function AndroidInstallSnippet(): JSX.Element { ) } -function AndroidSetupSnippet(): JSX.Element { +function AndroidSetupSnippet({ includeReplay }: AndroidSetupProps): JSX.Element { const { currentTeam } = useValues(teamLogic) return ( @@ -33,6 +39,18 @@ function AndroidSetupSnippet(): JSX.Element { apiKey = POSTHOG_API_KEY, host = POSTHOG_HOST ) + ${ + includeReplay + ? ` + // check https://posthog.com/docs/session-replay/mobile#installation + // for more config and to learn about how we capture sessions on mobile + // and what to expect + config.sessionReplay = true + // choose whether to mask images or text + config.sessionReplayConfig.maskAllImages = false + config.sessionReplayConfig.maskAllTextInputs = true` + : '' + } // Setup PostHog with the given Context and Config PostHogAndroid.setup(this, config) @@ -41,13 +59,24 @@ function AndroidSetupSnippet(): JSX.Element { ) } -export function SDKInstallAndroidInstructions(): JSX.Element { +export function SDKInstallAndroidInstructions(props: AndroidSetupProps): JSX.Element { return ( <> + {props.includeReplay ? ( + + 🚧 NOTE: Mobile recording is + currently in beta. We are keen to gather as much feedback as possible so if you try this out please + let us know. You can send feedback via the{' '} + + in-app support panel + {' '} + or one of our other support options. + + ) : null}

Install

Configure

- + ) } diff --git a/frontend/src/scenes/onboarding/sdks/sdksLogic.tsx b/frontend/src/scenes/onboarding/sdks/sdksLogic.tsx index df4a13d8adaf1..a46984ee8f897 100644 --- a/frontend/src/scenes/onboarding/sdks/sdksLogic.tsx +++ b/frontend/src/scenes/onboarding/sdks/sdksLogic.tsx @@ -1,5 +1,6 @@ import { actions, afterMount, connect, events, kea, listeners, path, reducers, selectors } from 'kea' import { loaders } from 'kea-loaders' +import { urlToAction } from 'kea-router' import api from 'lib/api' import { LemonSelectOptions } from 'lib/lemon-ui/LemonSelect/LemonSelect' @@ -11,7 +12,7 @@ import { onboardingLogic } from '../onboardingLogic' import { allSDKs } from './allSDKs' import type { sdksLogicType } from './sdksLogicType' -/* +/* To add SDK instructions for your product: 1. If needed, add a new ProductKey enum value in ~/types.ts 2. Create a folder in this directory for your product @@ -118,14 +119,16 @@ export const sdksLogic = kea([ loadSnippetEvents: async () => { const query: HogQLQuery = { kind: NodeKind.HogQLQuery, - query: hogql`SELECT properties.$lib_version AS lib_version, max(timestamp) AS latest_timestamp, count(lib_version) as count - FROM events - WHERE timestamp >= now() - INTERVAL 3 DAY - AND timestamp <= now() - AND properties.$lib = 'web' - GROUP BY lib_version - ORDER BY latest_timestamp DESC - limit 10`, + query: hogql`SELECT properties.$lib_version AS lib_version, + max(timestamp) AS latest_timestamp, + count(lib_version) as count + FROM events + WHERE timestamp >= now() - INTERVAL 3 DAY + AND timestamp <= now() + AND properties.$lib = 'web' + GROUP BY lib_version + ORDER BY latest_timestamp DESC + limit 10`, } const res = await api.query(query) @@ -188,4 +191,12 @@ export const sdksLogic = kea([ afterMount(({ actions }) => { actions.loadSnippetEvents() }), + urlToAction(({ actions }) => ({ + '/onboarding/:productKey': (_productKey, { sdk }) => { + const matchedSDK = allSDKs.find((s) => s.key === sdk) + if (matchedSDK) { + actions.setSelectedSDK(matchedSDK) + } + }, + })), ]) diff --git a/frontend/src/scenes/onboarding/sdks/session-replay/SessionReplaySDKInstructions.tsx b/frontend/src/scenes/onboarding/sdks/session-replay/SessionReplaySDKInstructions.tsx index 7e43a06b7faba..16db14dbd1d85 100644 --- a/frontend/src/scenes/onboarding/sdks/session-replay/SessionReplaySDKInstructions.tsx +++ b/frontend/src/scenes/onboarding/sdks/session-replay/SessionReplaySDKInstructions.tsx @@ -7,4 +7,6 @@ export const SessionReplaySDKInstructions: SDKInstructionsMap = { [SDKKey.HTML_SNIPPET]: HTMLSnippetInstructions, [SDKKey.NEXT_JS]: NextJSInstructions, [SDKKey.REACT]: ReactInstructions, + // added by feature flag in Onboarding.tsx until released + //[SDKKey.ANDROID]: AndroidInstructions, } diff --git a/frontend/src/scenes/onboarding/sdks/session-replay/android.tsx b/frontend/src/scenes/onboarding/sdks/session-replay/android.tsx new file mode 100644 index 0000000000000..4afb1dc91ce60 --- /dev/null +++ b/frontend/src/scenes/onboarding/sdks/session-replay/android.tsx @@ -0,0 +1,11 @@ +import { SDKInstallAndroidInstructions } from '../sdk-install-instructions' +import { SessionReplayFinalSteps } from '../shared-snippets' + +export function AndroidInstructions(): JSX.Element { + return ( + <> + + + + ) +} diff --git a/frontend/src/scenes/onboarding/sdks/session-replay/index.tsx b/frontend/src/scenes/onboarding/sdks/session-replay/index.tsx index bee13a5ce58bb..1ef01349747b4 100644 --- a/frontend/src/scenes/onboarding/sdks/session-replay/index.tsx +++ b/frontend/src/scenes/onboarding/sdks/session-replay/index.tsx @@ -1,3 +1,4 @@ +export * from './android' export * from './html-snippet' export * from './js-web' export * from './next-js' diff --git a/frontend/src/scenes/urls.ts b/frontend/src/scenes/urls.ts index 943ebbaa80bb2..13262c0eb3656 100644 --- a/frontend/src/scenes/urls.ts +++ b/frontend/src/scenes/urls.ts @@ -16,6 +16,7 @@ import { PipelineTab, ProductKey, ReplayTabs, + SDKKey, } from '~/types' import { OnboardingStepKey } from './onboarding/onboardingLogic' @@ -175,8 +176,10 @@ export const urls = { `/verify_email${userUuid ? `/${userUuid}` : ''}${token ? `/${token}` : ''}`, inviteSignup: (id: string): string => `/signup/${id}`, products: (): string => '/products', - onboarding: (productKey: string, stepKey?: OnboardingStepKey): string => - `/onboarding/${productKey}${stepKey ? '?step=' + stepKey : ''}`, + onboarding: (productKey: string, stepKey?: OnboardingStepKey, sdk?: SDKKey): string => + `/onboarding/${productKey}${stepKey ? '?step=' + stepKey : ''}${ + sdk && stepKey ? '&sdk=' + sdk : sdk ? '?sdk=' + sdk : '' + }`, // Cloud only organizationBilling: (products?: ProductKey[]): string => `/organization/billing${products && products.length ? `?products=${products.join(',')}` : ''}`,