Skip to content

Commit

Permalink
feat: mobile replay onboarding (#21122)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
pauldambra and github-actions[bot] authored Mar 26, 2024
1 parent 4d75323 commit a54ae88
Show file tree
Hide file tree
Showing 11 changed files with 126 additions and 23 deletions.
1 change: 1 addition & 0 deletions frontend/src/lib/constants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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]

Expand Down
17 changes: 14 additions & 3 deletions frontend/src/scenes/onboarding/Onboarding.tsx
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -139,18 +144,24 @@ const SessionReplayOnboarding = (): JSX.Element => {
})
}

const sdkInstructionMap = SessionReplaySDKInstructions
if (hasAndroidOnBoarding) {
sdkInstructionMap[SDKKey.ANDROID] = AndroidInstructions
}

return (
<OnboardingWrapper>
<SDKs
usersAction="recording sessions"
sdkInstructionMap={SessionReplaySDKInstructions}
sdkInstructionMap={sdkInstructionMap}
subtitle="Choose the framework your frontend is built on, or use our all-purpose JavaScript library. If you already have the snippet installed, you can skip this step!"
stepKey={OnboardingStepKey.INSTALL}
/>
<OnboardingProductConfiguration stepKey={OnboardingStepKey.PRODUCT_CONFIGURATION} options={configOptions} />
</OnboardingWrapper>
)
}

const FeatureFlagsOnboarding = (): JSX.Element => {
return (
<OnboardingWrapper>
Expand Down
18 changes: 12 additions & 6 deletions frontend/src/scenes/onboarding/onboardingLogic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -316,9 +316,9 @@ export const onboardingLogic = kea<onboardingLogicType>([
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: () => {
Expand All @@ -327,9 +327,12 @@ export const onboardingLogic = kea<onboardingLogicType>([
)
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: () => {
Expand All @@ -338,9 +341,12 @@ export const onboardingLogic = kea<onboardingLogicType>([
)
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) {
Expand Down
1 change: 1 addition & 0 deletions frontend/src/scenes/onboarding/sdks/SDKs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ export function SDKs({
{sdks?.map((sdk) => (
<React.Fragment key={`sdk-${sdk.key}`}>
<LemonButton
data-attr={`onboarding-sdk-${sdk.key}`}
active={selectedSDK?.key === sdk.key}
onClick={selectedSDK?.key !== sdk.key ? () => setSelectedSDK(sdk) : undefined}
fullWidth
Expand Down
27 changes: 27 additions & 0 deletions frontend/src/scenes/onboarding/sdks/product-analytics/android.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,44 @@
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'

function AndroidCaptureSnippet(): JSX.Element {
return <CodeSnippet language={Language.Kotlin}>{`PostHog.capture(event = "test-event")`}</CodeSnippet>
}

function AdvertiseAndroidReplay(): JSX.Element {
return (
<div>
<h3 className="mt-8">
Session Replay for Android <LemonTag type="highlight">NEW</LemonTag>
</h3>
<div>
Session replay is now in beta for Android.{' '}
<Link to={urls.onboarding('session_replay', OnboardingStepKey.INSTALL, SDKKey.ANDROID)}>
Learn how to set it up
</Link>
</div>
</div>
)
}

export function ProductAnalyticsAndroidInstructions(): JSX.Element {
return (
<>
<SDKInstallAndroidInstructions />
<h3>Send an Event</h3>
<AndroidCaptureSnippet />
<FlaggedFeature flag={FEATURE_FLAGS.SESSION_REPLAY_MOBILE_ONBOARDING} match={true}>
<AdvertiseAndroidReplay />
</FlaggedFeature>
</>
)
}
Original file line number Diff line number Diff line change
@@ -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 (
<CodeSnippet language={Language.Kotlin}>
Expand All @@ -13,7 +19,7 @@ function AndroidInstallSnippet(): JSX.Element {
)
}

function AndroidSetupSnippet(): JSX.Element {
function AndroidSetupSnippet({ includeReplay }: AndroidSetupProps): JSX.Element {
const { currentTeam } = useValues(teamLogic)

return (
Expand All @@ -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)
Expand All @@ -41,13 +59,24 @@ function AndroidSetupSnippet(): JSX.Element {
)
}

export function SDKInstallAndroidInstructions(): JSX.Element {
export function SDKInstallAndroidInstructions(props: AndroidSetupProps): JSX.Element {
return (
<>
{props.includeReplay ? (
<LemonBanner type="info">
🚧 NOTE: <Link to="https://posthog.com/docs/session-replay/mobile">Mobile recording</Link> 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{' '}
<Link to="https://us.posthog.com/#panel=support%3Afeedback%3Asession_replay%3Alow">
in-app support panel
</Link>{' '}
or one of our other <Link to="https://posthog.com/docs/support-options">support options</Link>.
</LemonBanner>
) : null}
<h3>Install</h3>
<AndroidInstallSnippet />
<h3>Configure</h3>
<AndroidSetupSnippet />
<AndroidSetupSnippet {...props} />
</>
)
}
29 changes: 20 additions & 9 deletions frontend/src/scenes/onboarding/sdks/sdksLogic.tsx
Original file line number Diff line number Diff line change
@@ -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'

Expand All @@ -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
Expand Down Expand Up @@ -118,14 +119,16 @@ export const sdksLogic = kea<sdksLogicType>([
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)
Expand Down Expand Up @@ -188,4 +191,12 @@ export const sdksLogic = kea<sdksLogicType>([
afterMount(({ actions }) => {
actions.loadSnippetEvents()
}),
urlToAction(({ actions }) => ({
'/onboarding/:productKey': (_productKey, { sdk }) => {
const matchedSDK = allSDKs.find((s) => s.key === sdk)
if (matchedSDK) {
actions.setSelectedSDK(matchedSDK)
}
},
})),
])
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
11 changes: 11 additions & 0 deletions frontend/src/scenes/onboarding/sdks/session-replay/android.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { SDKInstallAndroidInstructions } from '../sdk-install-instructions'
import { SessionReplayFinalSteps } from '../shared-snippets'

export function AndroidInstructions(): JSX.Element {
return (
<>
<SDKInstallAndroidInstructions includeReplay={true} />
<SessionReplayFinalSteps />
</>
)
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './android'
export * from './html-snippet'
export * from './js-web'
export * from './next-js'
Expand Down
7 changes: 5 additions & 2 deletions frontend/src/scenes/urls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
PipelineTab,
ProductKey,
ReplayTabs,
SDKKey,
} from '~/types'

import { OnboardingStepKey } from './onboarding/onboardingLogic'
Expand Down Expand Up @@ -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(',')}` : ''}`,
Expand Down

0 comments on commit a54ae88

Please sign in to comment.