Skip to content

Commit

Permalink
feat(onboarding): add product config screen (#18720)
Browse files Browse the repository at this point in the history
* stub out config screen

* saave the configuration

* fix

* add existing replay controls to config

* dont show any toasts in onboarding

* add minimum duration option

* move dropdown

* flag the control

* Update frontend/src/scenes/onboarding/Onboarding.tsx

Co-authored-by: Paul D'Ambra <[email protected]>

---------

Co-authored-by: Paul D'Ambra <[email protected]>
  • Loading branch information
2 people authored and Twixes committed Dec 6, 2023
1 parent 15d9265 commit 39b357d
Show file tree
Hide file tree
Showing 8 changed files with 222 additions and 29 deletions.
29 changes: 29 additions & 0 deletions frontend/src/lib/constants.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { LemonSelectOptions } from '@posthog/lemon-ui'

import { AvailableFeature, ChartDisplayType, LicensePlan, Region, SSOProvider } from '../types'

/** Display types which don't allow grouping by unit of time. Sync with backend NON_TIME_SERIES_DISPLAY_TYPES. */
Expand Down Expand Up @@ -264,3 +266,30 @@ export const EVENT_DEFINITIONS_PER_PAGE = 50
export const PROPERTY_DEFINITIONS_PER_EVENT = 5
export const EVENT_PROPERTY_DEFINITIONS_PER_PAGE = 50
export const LOGS_PORTION_LIMIT = 50

export const SESSION_REPLAY_MINIMUM_DURATION_OPTIONS: LemonSelectOptions<number | null> = [
{
label: 'no minimum',
value: null,
},
{
label: '1',
value: 1000,
},
{
label: '2',
value: 2000,
},
{
label: '5',
value: 5000,
},
{
label: '10',
value: 10000,
},
{
label: '15',
value: 15000,
},
]
55 changes: 55 additions & 0 deletions frontend/src/scenes/onboarding/Onboarding.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import { useActions, useValues } from 'kea'
import { FEATURE_FLAGS, SESSION_REPLAY_MINIMUM_DURATION_OPTIONS } from 'lib/constants'
import { featureFlagLogic } from 'lib/logic/featureFlagLogic'
import { useEffect, useState } from 'react'
import { SceneExport } from 'scenes/sceneTypes'
import { teamLogic } from 'scenes/teamLogic'

import { ProductKey } from '~/types'

import { OnboardingBillingStep } from './OnboardingBillingStep'
import { onboardingLogic, OnboardingStepKey } from './onboardingLogic'
import { OnboardingOtherProductsStep } from './OnboardingOtherProductsStep'
import { OnboardingProductConfiguration } from './OnboardingProductConfiguration'
import { ProductConfigOption } from './onboardingProductConfigurationLogic'
import { OnboardingVerificationStep } from './OnboardingVerificationStep'
import { FeatureFlagsSDKInstructions } from './sdks/feature-flags/FeatureFlagsSDKInstructions'
import { ProductAnalyticsSDKInstructions } from './sdks/product-analytics/ProductAnalyticsSDKInstructions'
Expand Down Expand Up @@ -65,6 +70,8 @@ const OnboardingWrapper = ({ children }: { children: React.ReactNode }): JSX.Ele
}

const ProductAnalyticsOnboarding = (): JSX.Element => {
const { currentTeam } = useValues(teamLogic)

return (
<OnboardingWrapper>
<SDKs
Expand All @@ -77,10 +84,57 @@ const ProductAnalyticsOnboarding = (): JSX.Element => {
teamPropertyToVerify="ingested_event"
stepKey={OnboardingStepKey.VERIFY}
/>
<OnboardingProductConfiguration
stepKey={OnboardingStepKey.PRODUCT_CONFIGURATION}
options={[
{
title: 'Autocapture frontend interactions',
description: `If you use our JavaScript or React Native libraries, we'll automagically
capture frontend interactions like pageviews, clicks, and more. Fine-tune what you
capture directly in your code snippet.`,
teamProperty: 'autocapture_opt_out',
value: !currentTeam?.autocapture_opt_out,
type: 'toggle',
inverseToggle: true,
},
]}
/>
</OnboardingWrapper>
)
}
const SessionReplayOnboarding = (): JSX.Element => {
const { featureFlags } = useValues(featureFlagLogic)
const configOptions: ProductConfigOption[] = [
{
type: 'toggle',
title: 'Capture console logs',
description: `Capture console logs as a part of user session recordings.
Use the console logs alongside recordings to debug any issues with your app.`,
teamProperty: 'capture_console_log_opt_in',
value: true,
},
{
type: 'toggle',
title: 'Capture network performance',
description: `Capture performance and network information alongside recordings. Use the
network requests and timings in the recording player to help you debug issues with your app.`,
teamProperty: 'capture_performance_opt_in',
value: true,
},
]

if (featureFlags[FEATURE_FLAGS.SESSION_RECORDING_SAMPLING] === true) {
configOptions.push({
type: 'select',
title: 'Minimum session duration (seconds)',
description: `Only record sessions that are longer than the specified duration.
Start with it low and increase it later if you're getting too many short sessions.`,
teamProperty: 'session_recording_minimum_duration_milliseconds',
value: null,
selectOptions: SESSION_REPLAY_MINIMUM_DURATION_OPTIONS,
})
}

return (
<OnboardingWrapper>
<SDKs
Expand All @@ -89,6 +143,7 @@ const SessionReplayOnboarding = (): JSX.Element => {
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.SDKS}
/>
<OnboardingProductConfiguration stepKey={OnboardingStepKey.PRODUCT_CONFIGURATION} options={configOptions} />
</OnboardingWrapper>
)
}
Expand Down
68 changes: 68 additions & 0 deletions frontend/src/scenes/onboarding/OnboardingProductConfiguration.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { LemonSelect, LemonSwitch } from '@posthog/lemon-ui'
import { useActions, useValues } from 'kea'
import { useEffect } from 'react'

import { OnboardingStepKey } from './onboardingLogic'
import { onboardingProductConfigurationLogic, ProductConfigOption } from './onboardingProductConfigurationLogic'
import { OnboardingStep } from './OnboardingStep'

export const OnboardingProductConfiguration = ({
stepKey = OnboardingStepKey.PRODUCT_CONFIGURATION,
options,
}: {
stepKey?: OnboardingStepKey
options: ProductConfigOption[]
}): JSX.Element | null => {
const { configOptions } = useValues(onboardingProductConfigurationLogic)
const { setConfigOptions, saveConfiguration } = useActions(onboardingProductConfigurationLogic)
useEffect(() => {
setConfigOptions(options)
}, [])

return configOptions ? (
<OnboardingStep title={`Set up your configuration`} stepKey={stepKey} continueAction={saveConfiguration}>
{configOptions?.map((option: ProductConfigOption) => (
<div className="my-8" key={option.teamProperty}>
{option.type == 'toggle' ? (
<>
<LemonSwitch
data-attr="opt-in-session-recording-switch"
onChange={(checked) => {
setConfigOptions(
configOptions.map((o) =>
o.teamProperty === option.teamProperty ? { ...o, value: checked } : o
)
)
}}
label={option.title}
fullWidth={true}
labelClassName={'text-base font-semibold'}
checked={option.value || false}
/>
<p className="prompt-text ml-0">{option.description}</p>
</>
) : (
<>
<label className="text-base font-semibold">{option.title}</label>
<div className="flex justify-between items-center mb-1 gap-x-4">
<p className="prompt-text m-0">{option.description}</p>
<LemonSelect
dropdownMatchSelectWidth={false}
onChange={(v) => {
setConfigOptions(
configOptions.map((o) =>
o.teamProperty === option.teamProperty ? { ...o, value: v } : o
)
)
}}
options={option.selectOptions || []}
value={option.value}
/>
</div>
</>
)}
</div>
))}
</OnboardingStep>
) : null
}
7 changes: 6 additions & 1 deletion frontend/src/scenes/onboarding/OnboardingStep.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export const OnboardingStep = ({
children,
showSkip = false,
onSkip,
continueAction,
continueOverride,
backActionOverride,
}: {
Expand All @@ -23,6 +24,7 @@ export const OnboardingStep = ({
children: React.ReactNode
showSkip?: boolean
onSkip?: () => void
continueAction?: () => void
continueOverride?: JSX.Element
backActionOverride?: () => void
}): JSX.Element => {
Expand Down Expand Up @@ -77,7 +79,10 @@ export const OnboardingStep = ({
) : (
<LemonButton
type="primary"
onClick={() => (!hasNextStep ? completeOnboarding() : goToNextStep())}
onClick={() => {
continueAction && continueAction()
!hasNextStep ? completeOnboarding() : goToNextStep()
}}
sideIcon={hasNextStep ? <IconArrowRight /> : null}
>
{!hasNextStep ? 'Finish' : 'Continue'}
Expand Down
1 change: 1 addition & 0 deletions frontend/src/scenes/onboarding/onboardingLogic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export enum OnboardingStepKey {
BILLING = 'billing',
OTHER_PRODUCTS = 'other_products',
VERIFY = 'verify',
PRODUCT_CONFIGURATION = 'configure',
}

// These types have to be set like this, so that kea typegen is happy
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { LemonSelectOptions } from '@posthog/lemon-ui'
import { actions, connect, kea, listeners, path, reducers } from 'kea'
import { teamLogic } from 'scenes/teamLogic'

import type { onboardingProductConfigurationLogicType } from './onboardingProductConfigurationLogicType'

export interface ProductConfigOptionBase {
title: string
description: string
teamProperty: string
}

export interface ProductConfigurationToggle extends ProductConfigOptionBase {
type: 'toggle'
/** Sets the initial value. Use a team setting to reflect current state, or a static value to set a default. */
value: boolean
/** If true, the value is inverted when saving, used for 'opt_out' type settings */
inverseToggle?: boolean
}

export interface ProductConfigurationSelect extends ProductConfigOptionBase {
type: 'select'
/** Sets the initial value. Use a team setting to reflect current state, or a static value to set a default. */
value: string | number | null
selectOptions: LemonSelectOptions<any>
}

export type ProductConfigOption = ProductConfigurationToggle | ProductConfigurationSelect

export const onboardingProductConfigurationLogic = kea<onboardingProductConfigurationLogicType>([
path(() => ['scenes', 'onboarding', 'onboardingProductConfigurationLogic']),
connect({
actions: [teamLogic, ['updateCurrentTeam']],
}),
actions({
setConfigOptions: (configOptions: ProductConfigOption[]) => ({ configOptions }),
saveConfiguration: true,
}),
reducers(() => ({
configOptions: [
[],
{
setConfigOptions: (_, { configOptions }) => configOptions,
},
],
})),
listeners(({ values, actions }) => ({
saveConfiguration: async () => {
const updateConfig = {}
values.configOptions.forEach((configOption) => {
updateConfig[configOption.teamProperty] = configOption.inverseToggle
? !configOption.value
: configOption.value
})
actions.updateCurrentTeam(updateConfig)
},
})),
])
29 changes: 2 additions & 27 deletions frontend/src/scenes/settings/project/SessionRecordingSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { AuthorizedUrlList } from 'lib/components/AuthorizedUrlList/AuthorizedUr
import { AuthorizedUrlListType } from 'lib/components/AuthorizedUrlList/authorizedUrlListLogic'
import { FlaggedFeature } from 'lib/components/FlaggedFeature'
import { FlagSelector } from 'lib/components/FlagSelector'
import { FEATURE_FLAGS } from 'lib/constants'
import { FEATURE_FLAGS, SESSION_REPLAY_MINIMUM_DURATION_OPTIONS } from 'lib/constants'
import { IconCancel } from 'lib/lemon-ui/icons'
import { LemonLabel } from 'lib/lemon-ui/LemonLabel/LemonLabel'
import { teamLogic } from 'scenes/teamLogic'
Expand Down Expand Up @@ -305,32 +305,7 @@ export function ReplayCostControl(): JSX.Element {
onChange={(v) => {
updateCurrentTeam({ session_recording_minimum_duration_milliseconds: v })
}}
options={[
{
label: 'no minimum',
value: null,
},
{
label: '1',
value: 1000,
},
{
label: '2',
value: 2000,
},
{
label: '5',
value: 5000,
},
{
label: '10',
value: 10000,
},
{
label: '15',
value: 15000,
},
]}
options={SESSION_REPLAY_MINIMUM_DURATION_OPTIONS}
value={currentTeam?.session_recording_minimum_duration_milliseconds}
/>
</div>
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/scenes/teamLogic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,9 @@ export const teamLogic = kea<teamLogicType>([
eventUsageLogic.findMounted()?.actions?.reportTeamSettingChange(property, payload[property])
})

lemonToast.success(message)
if (!window.location.pathname.match(/\/(onboarding|products)/)) {
lemonToast.success(message)
}

return patchedTeam
},
Expand Down

0 comments on commit 39b357d

Please sign in to comment.