From 39b357d5695b0a79a1306e83ab0aab76252f0e21 Mon Sep 17 00:00:00 2001 From: Raquel Smith Date: Tue, 5 Dec 2023 12:32:11 -0800 Subject: [PATCH] feat(onboarding): add product config screen (#18720) * 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 --------- Co-authored-by: Paul D'Ambra --- frontend/src/lib/constants.tsx | 29 ++++++++ frontend/src/scenes/onboarding/Onboarding.tsx | 55 +++++++++++++++ .../OnboardingProductConfiguration.tsx | 68 +++++++++++++++++++ .../src/scenes/onboarding/OnboardingStep.tsx | 7 +- .../src/scenes/onboarding/onboardingLogic.tsx | 1 + .../onboardingProductConfigurationLogic.ts | 58 ++++++++++++++++ .../project/SessionRecordingSettings.tsx | 29 +------- frontend/src/scenes/teamLogic.tsx | 4 +- 8 files changed, 222 insertions(+), 29 deletions(-) create mode 100644 frontend/src/scenes/onboarding/OnboardingProductConfiguration.tsx create mode 100644 frontend/src/scenes/onboarding/onboardingProductConfigurationLogic.ts diff --git a/frontend/src/lib/constants.tsx b/frontend/src/lib/constants.tsx index 63bdf73a8d8857..af8f77a6e67183 100644 --- a/frontend/src/lib/constants.tsx +++ b/frontend/src/lib/constants.tsx @@ -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. */ @@ -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 = [ + { + 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, + }, +] diff --git a/frontend/src/scenes/onboarding/Onboarding.tsx b/frontend/src/scenes/onboarding/Onboarding.tsx index f7e01007b81cf7..e2d3bcb0829c17 100644 --- a/frontend/src/scenes/onboarding/Onboarding.tsx +++ b/frontend/src/scenes/onboarding/Onboarding.tsx @@ -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' @@ -65,6 +70,8 @@ const OnboardingWrapper = ({ children }: { children: React.ReactNode }): JSX.Ele } const ProductAnalyticsOnboarding = (): JSX.Element => { + const { currentTeam } = useValues(teamLogic) + return ( { teamPropertyToVerify="ingested_event" stepKey={OnboardingStepKey.VERIFY} /> + ) } 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 ( { 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} /> + ) } diff --git a/frontend/src/scenes/onboarding/OnboardingProductConfiguration.tsx b/frontend/src/scenes/onboarding/OnboardingProductConfiguration.tsx new file mode 100644 index 00000000000000..b44d0cfc801d31 --- /dev/null +++ b/frontend/src/scenes/onboarding/OnboardingProductConfiguration.tsx @@ -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 ? ( + + {configOptions?.map((option: ProductConfigOption) => ( +
+ {option.type == 'toggle' ? ( + <> + { + 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} + /> +

{option.description}

+ + ) : ( + <> + +
+

{option.description}

+ { + setConfigOptions( + configOptions.map((o) => + o.teamProperty === option.teamProperty ? { ...o, value: v } : o + ) + ) + }} + options={option.selectOptions || []} + value={option.value} + /> +
+ + )} +
+ ))} +
+ ) : null +} diff --git a/frontend/src/scenes/onboarding/OnboardingStep.tsx b/frontend/src/scenes/onboarding/OnboardingStep.tsx index fde8a4dfff9493..2617df2f59132a 100644 --- a/frontend/src/scenes/onboarding/OnboardingStep.tsx +++ b/frontend/src/scenes/onboarding/OnboardingStep.tsx @@ -14,6 +14,7 @@ export const OnboardingStep = ({ children, showSkip = false, onSkip, + continueAction, continueOverride, backActionOverride, }: { @@ -23,6 +24,7 @@ export const OnboardingStep = ({ children: React.ReactNode showSkip?: boolean onSkip?: () => void + continueAction?: () => void continueOverride?: JSX.Element backActionOverride?: () => void }): JSX.Element => { @@ -77,7 +79,10 @@ export const OnboardingStep = ({ ) : ( (!hasNextStep ? completeOnboarding() : goToNextStep())} + onClick={() => { + continueAction && continueAction() + !hasNextStep ? completeOnboarding() : goToNextStep() + }} sideIcon={hasNextStep ? : null} > {!hasNextStep ? 'Finish' : 'Continue'} diff --git a/frontend/src/scenes/onboarding/onboardingLogic.tsx b/frontend/src/scenes/onboarding/onboardingLogic.tsx index f79b06d1a9011d..a8cece773e6169 100644 --- a/frontend/src/scenes/onboarding/onboardingLogic.tsx +++ b/frontend/src/scenes/onboarding/onboardingLogic.tsx @@ -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 diff --git a/frontend/src/scenes/onboarding/onboardingProductConfigurationLogic.ts b/frontend/src/scenes/onboarding/onboardingProductConfigurationLogic.ts new file mode 100644 index 00000000000000..09ac6605530838 --- /dev/null +++ b/frontend/src/scenes/onboarding/onboardingProductConfigurationLogic.ts @@ -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 +} + +export type ProductConfigOption = ProductConfigurationToggle | ProductConfigurationSelect + +export const onboardingProductConfigurationLogic = kea([ + 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) + }, + })), +]) diff --git a/frontend/src/scenes/settings/project/SessionRecordingSettings.tsx b/frontend/src/scenes/settings/project/SessionRecordingSettings.tsx index 44ff966db0c658..dbff8a288fa84b 100644 --- a/frontend/src/scenes/settings/project/SessionRecordingSettings.tsx +++ b/frontend/src/scenes/settings/project/SessionRecordingSettings.tsx @@ -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' @@ -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} /> diff --git a/frontend/src/scenes/teamLogic.tsx b/frontend/src/scenes/teamLogic.tsx index 0e7c63f67931a9..87a87e7f7cbca4 100644 --- a/frontend/src/scenes/teamLogic.tsx +++ b/frontend/src/scenes/teamLogic.tsx @@ -104,7 +104,9 @@ export const teamLogic = kea([ eventUsageLogic.findMounted()?.actions?.reportTeamSettingChange(property, payload[property]) }) - lemonToast.success(message) + if (!window.location.pathname.match(/\/(onboarding|products)/)) { + lemonToast.success(message) + } return patchedTeam },