Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: configure session recording sample rate #18068

Merged
merged 42 commits into from
Oct 24, 2023
Merged
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
3acdaf1
feat: configure session recording sample rate
pauldambra Oct 18, 2023
b70ec1a
fix mock
pauldambra Oct 18, 2023
24084c1
Update query snapshots
github-actions[bot] Oct 18, 2023
db33638
Update query snapshots
github-actions[bot] Oct 18, 2023
bc0142b
Update query snapshots
github-actions[bot] Oct 18, 2023
a3f171a
fix
pauldambra Oct 18, 2023
1a080c7
fix
pauldambra Oct 18, 2023
76d07f6
Update query snapshots
github-actions[bot] Oct 18, 2023
93a0e6c
fix
pauldambra Oct 18, 2023
1440392
fix
pauldambra Oct 18, 2023
9116ff0
Update query snapshots
github-actions[bot] Oct 18, 2023
3d02cd3
Update query snapshots
github-actions[bot] Oct 18, 2023
8c0bda7
Update query snapshots
github-actions[bot] Oct 19, 2023
5d2ea58
Update query snapshots
github-actions[bot] Oct 19, 2023
379ecda
Update query snapshots
github-actions[bot] Oct 19, 2023
10e0c02
Fix
pauldambra Oct 19, 2023
89725ca
fix
pauldambra Oct 19, 2023
6174109
Merge branch 'master' into feat/configure-session-recording-sample-rate
pauldambra Oct 19, 2023
0f30d39
fix
pauldambra Oct 19, 2023
3845825
add minimum duration setting
pauldambra Oct 20, 2023
87ce2e2
dropdown instead of text box
pauldambra Oct 20, 2023
ca76d1c
start adding linked flag
pauldambra Oct 21, 2023
c0833ee
fix
pauldambra Oct 21, 2023
a083c3b
fix
pauldambra Oct 21, 2023
a8013d2
Merge branch 'master' into feat/configure-session-recording-sample-rate
pauldambra Oct 21, 2023
a561d6b
Update query snapshots
github-actions[bot] Oct 21, 2023
0b77b0c
Update query snapshots
github-actions[bot] Oct 21, 2023
42f2d8f
Update query snapshots
github-actions[bot] Oct 21, 2023
faa375a
Update query snapshots
github-actions[bot] Oct 21, 2023
9ddc393
only return flag key in decide
pauldambra Oct 21, 2023
1e9ee03
doh
pauldambra Oct 21, 2023
82cde4d
fix
pauldambra Oct 21, 2023
d19aa8e
Fix
pauldambra Oct 21, 2023
605d058
don't have a restricted list of sample rates
pauldambra Oct 21, 2023
337c11a
fix
pauldambra Oct 21, 2023
3a884ae
fix
pauldambra Oct 22, 2023
3b20ab9
Update query snapshots
github-actions[bot] Oct 22, 2023
933b537
all the options
pauldambra Oct 23, 2023
e5a2714
review changes
pauldambra Oct 23, 2023
545ebae
Fix
pauldambra Oct 24, 2023
5221b16
Merge branch 'master' into feat/configure-session-recording-sample-rate
pauldambra Oct 24, 2023
71ba595
fix
pauldambra Oct 24, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions frontend/src/lib/api.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ export const MOCK_DEFAULT_TEAM: TeamType = {
},
autocapture_opt_out: true,
session_recording_opt_in: true,
session_recording_sample_rate: '1.0',
session_recording_minimum_duration_milliseconds: null,
session_recording_linked_flag: null,
capture_console_log_opt_in: true,
capture_performance_opt_in: true,
autocapture_exceptions_opt_in: false,
Expand Down
51 changes: 51 additions & 0 deletions frontend/src/lib/components/FlagSelector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { useState } from 'react'
import { useValues } from 'kea'
import { featureFlagLogic } from 'scenes/feature-flags/featureFlagLogic'
import { TaxonomicFilterGroupType, TaxonomicFilterLogicProps } from 'lib/components/TaxonomicFilter/types'
import { Popover } from 'lib/lemon-ui/Popover'
import { TaxonomicFilter } from 'lib/components/TaxonomicFilter/TaxonomicFilter'
import { LemonButton } from 'lib/lemon-ui/LemonButton'

interface FlagSelectorProps {
value: number | undefined
onChange: (id: number, key: string) => void
readOnly?: boolean
}

export function FlagSelector({ value, onChange, readOnly }: FlagSelectorProps): JSX.Element {
const [visible, setVisible] = useState(false)

const { featureFlag } = useValues(featureFlagLogic({ id: value || 'link' }))

const taxonomicFilterLogicProps: TaxonomicFilterLogicProps = {
groupType: TaxonomicFilterGroupType.FeatureFlags,
value: value,
onChange: (_, __, item) => {
'id' in item && item.id && onChange(item.id, item.key)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@liyiy @neilkakkar I've moved the flag selector into components since I wanted to use it in replay config. And exposed the flag key as well as the id on change... That ok?

setVisible(false)
},
taxonomicGroupTypes: [TaxonomicFilterGroupType.FeatureFlags],
optionsFromProp: undefined,
popoverEnabled: true,
selectFirstItem: true,
taxonomicFilterLogicKey: 'flag-selectorz',
}

return (
<Popover
overlay={<TaxonomicFilter {...taxonomicFilterLogicProps} />}
visible={visible}
placement="right-start"
fallbackPlacements={['left-end', 'bottom']}
onClickOutside={() => setVisible(false)}
>
{readOnly ? (
<div>{featureFlag.key}</div>
) : (
<LemonButton type="secondary" onClick={() => setVisible(!visible)}>
{featureFlag.key ? featureFlag.key : 'Select flag'}
</LemonButton>
)}
</Popover>
)
}
1 change: 1 addition & 0 deletions frontend/src/lib/constants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ export const FEATURE_FLAGS = {
SURVEYS_PAYGATES: 'surveys-paygates',
CONSOLE_RECORDING_SEARCH: 'console-recording-search', // owner: #team-monitoring
PERSONS_HOGQL_QUERY: 'persons-hogql-query', // owner: @mariusandra
SESSION_RECORDING_SAMPLING: 'session-recording-sampling', // owner: #team-monitoring
} as const
export type FeatureFlagKey = (typeof FEATURE_FLAGS)[keyof typeof FEATURE_FLAGS]

Expand Down
53 changes: 3 additions & 50 deletions frontend/src/scenes/early-access-features/EarlyAccessFeature.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { LemonButton, LemonDivider, LemonInput, LemonSkeleton, LemonTag, LemonTextArea } from '@posthog/lemon-ui'
import { useActions, useValues, BindLogic } from 'kea'
import { BindLogic, useActions, useValues } from 'kea'
import { PageHeader } from 'lib/components/PageHeader'
import { Field, PureField } from 'lib/forms/Field'
import { SceneExport } from 'scenes/sceneTypes'
Expand All @@ -16,13 +16,9 @@ import {
import { urls } from 'scenes/urls'
import { IconClose, IconFlag, IconHelpOutline } from 'lib/lemon-ui/icons'
import { router } from 'kea-router'
import { useState } from 'react'
import { Popover } from 'lib/lemon-ui/Popover'
import { TaxonomicFilter } from 'lib/components/TaxonomicFilter/TaxonomicFilter'
import { TaxonomicFilterLogicProps } from 'lib/components/TaxonomicFilter/types'
import { TaxonomicFilterGroupType } from 'lib/components/TaxonomicFilter/types'
import { featureFlagLogic } from 'scenes/feature-flags/featureFlagLogic'
import { PersonsLogicProps, personsLogic } from 'scenes/persons/personsLogic'
import { personsLogic, PersonsLogicProps } from 'scenes/persons/personsLogic'
import clsx from 'clsx'
import { InstructionsModal } from './InstructionsModal'
import { PersonsTable } from 'scenes/persons/PersonsTable'
Expand All @@ -31,6 +27,7 @@ import { PersonsSearch } from 'scenes/persons/PersonsSearch'
import { LemonDialog } from 'lib/lemon-ui/LemonDialog'
import { LemonTabs } from 'lib/lemon-ui/LemonTabs'
import { NotFound } from 'lib/components/NotFound'
import { FlagSelector } from 'lib/components/FlagSelector'

export const scene: SceneExport = {
component: EarlyAccessFeature,
Expand Down Expand Up @@ -295,50 +292,6 @@ export function EarlyAccessFeature({ id }: { id?: string } = {}): JSX.Element {
)
}

interface FlagSelectorProps {
value: number | undefined
onChange: (value: any) => void
readOnly?: boolean
}

export function FlagSelector({ value, onChange, readOnly }: FlagSelectorProps): JSX.Element {
const [visible, setVisible] = useState(false)

const { featureFlag } = useValues(featureFlagLogic({ id: value || 'link' }))

const taxonomicFilterLogicProps: TaxonomicFilterLogicProps = {
groupType: TaxonomicFilterGroupType.FeatureFlags,
value,
onChange: (_, __, item) => {
'id' in item && item.id && onChange(item.id)
setVisible(false)
},
taxonomicGroupTypes: [TaxonomicFilterGroupType.FeatureFlags],
optionsFromProp: undefined,
popoverEnabled: true,
selectFirstItem: true,
taxonomicFilterLogicKey: 'flag-selectorz',
}

return (
<Popover
overlay={<TaxonomicFilter {...taxonomicFilterLogicProps} />}
visible={visible}
placement="right-start"
fallbackPlacements={['bottom']}
onClickOutside={() => setVisible(false)}
>
{readOnly ? (
<div>{featureFlag.key}</div>
) : (
<LemonButton type="secondary" onClick={() => setVisible(!visible)}>
{featureFlag.key ? featureFlag.key : 'Select flag'}
</LemonButton>
)}
</Popover>
)
}

interface PersonListProps {
earlyAccessFeature: EarlyAccessFeatureType
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,10 @@ exports[`verifiedDomainsLogic values has proper defaults 1`] = `
"recording_domains": [
"https://recordings.posthog.com/",
],
"session_recording_linked_flag": null,
"session_recording_minimum_duration_milliseconds": null,
"session_recording_opt_in": true,
"session_recording_sample_rate": "1.0",
"slack_incoming_webhook": "",
"test_account_filters": [
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import { useActions, useValues } from 'kea'
import { teamLogic } from 'scenes/teamLogic'
import { LemonSwitch, Link } from '@posthog/lemon-ui'
import { LemonButton, LemonSelect, LemonSwitch, Link } from '@posthog/lemon-ui'
import { urls } from 'scenes/urls'
import { AuthorizedUrlList } from 'lib/components/AuthorizedUrlList/AuthorizedUrlList'
import { AuthorizedUrlListType } from 'lib/components/AuthorizedUrlList/authorizedUrlListLogic'
import { LemonDialog } from 'lib/lemon-ui/LemonDialog'
import { LemonLabel } from 'lib/lemon-ui/LemonLabel/LemonLabel'
import { FlaggedFeature } from 'lib/components/FlaggedFeature'
import { FEATURE_FLAGS } from 'lib/constants'
import { SampleRate } from '~/types'
import { IconCancel } from 'lib/lemon-ui/icons'
import { FlagSelector } from 'lib/components/FlagSelector'

export type SessionRecordingSettingsProps = {
inModal?: boolean
Expand Down Expand Up @@ -102,6 +107,113 @@ export function SessionRecordingSettings({ inModal = false }: SessionRecordingSe
</p>
<AuthorizedUrlList type={AuthorizedUrlListType.RECORDING_DOMAINS} />
</div>
<FlaggedFeature flag={FEATURE_FLAGS.SESSION_RECORDING_SAMPLING}>
pauldambra marked this conversation as resolved.
Show resolved Hide resolved
<>
<div className={'flex flex-row justify-between'}>
<LemonLabel className="text-base">Sampling</LemonLabel>
<LemonSelect
onChange={(v) => {
updateCurrentTeam({ session_recording_sample_rate: v as SampleRate })
}}
options={[
{
label: '100%',
value: '1.00',
},
{
label: '95%',
value: '0.95',
},
{
label: '90%',
value: '0.90',
},
{
label: '80%',
value: '0.80',
},
{
label: '50%',
value: '0.50',
},
]}
pauldambra marked this conversation as resolved.
Show resolved Hide resolved
value={
(typeof currentTeam?.session_recording_sample_rate === 'string'
? currentTeam?.session_recording_sample_rate
: '1.00') as SampleRate
}
/>
</div>
<p>
Use this setting to restrict the percentage of sessions that will be recorded. This is useful if
you want to reduce the amount of data you collect. 100% means all sessions will be collected.
50% means roughly half of sessions will be collected.
</p>
<div className={'flex flex-row justify-between'}>
<LemonLabel className="text-base">Minimum session duration (seconds)</LemonLabel>
<LemonSelect
dropdownMatchSelectWidth={false}
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,
},
]}
value={currentTeam?.session_recording_minimum_duration_milliseconds}
/>
</div>
<p>
Setting a minimum session duration will ensure that only sessions that last longer than that
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should warn that this does mean some useful data may not be collected. Debugging a user issue that starts with a loading screen before redirecting to another domain. With this setting set to say, 10 seconds, the initial redirect screen would not have been recorded as it is only buffered in memory.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could also be expanded in documentation instead of the modal

value are collected. This helps you avoid collecting sessions that are too short to be useful.
</p>
<div className={'flex flex-row justify-between'}>
<LemonLabel className="text-base">Enable recordings using feature flag</LemonLabel>
{currentTeam?.session_recording_linked_flag && (
<LemonButton
className="ml-2"
icon={<IconCancel />}
size="small"
status="stealth"
onClick={() => updateCurrentTeam({ session_recording_linked_flag: null })}
aria-label="close"
/>
)}
<FlagSelector
value={currentTeam?.session_recording_linked_flag?.id ?? undefined}
onChange={(id, key) => {
updateCurrentTeam({ session_recording_linked_flag: { id, key } })
}}
/>
pauldambra marked this conversation as resolved.
Show resolved Hide resolved
</div>
<p>
Linking a flag means that recordings will only be collected for users who have the flag enabled.
Only supports release toggles (boolean flags).
</p>
</>
</FlaggedFeature>
</div>
)
}
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/scenes/surveys/Survey.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ import { LemonButton, LemonDivider, Link } from '@posthog/lemon-ui'
import { router } from 'kea-router'
import { urls } from 'scenes/urls'
import { Survey, SurveyUrlMatchType } from '~/types'
import { FlagSelector } from 'scenes/early-access-features/EarlyAccessFeature'
import { SurveyView } from './SurveyView'
import { featureFlagLogic } from 'scenes/feature-flags/featureFlagLogic'
import { NewSurvey, SurveyUrlMatchTypeLabels } from './constants'
import { FeatureFlagReleaseConditions } from 'scenes/feature-flags/FeatureFlagReleaseConditions'
import SurveyEdit from './SurveyEdit'
import { NotFound } from 'lib/components/NotFound'
import { FlagSelector } from 'lib/components/FlagSelector'

export const scene: SceneExport = {
component: SurveyComponent,
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/scenes/surveys/SurveyEdit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import {
SurveyUrlMatchType,
AvailableFeature,
} from '~/types'
import { FlagSelector } from 'scenes/early-access-features/EarlyAccessFeature'
import { IconCancel, IconDelete, IconLock, IconPlus, IconPlusMini } from 'lib/lemon-ui/icons'
import {
BaseAppearance,
Expand All @@ -48,6 +47,7 @@ import { featureFlagLogic as enabledFeaturesLogic } from 'lib/logic/featureFlagL
import { SurveyFormAppearance } from './SurveyFormAppearance'
import { PayGateMini } from 'lib/components/PayGateMini/PayGateMini'
import { surveysLogic } from './surveysLogic'
import { FlagSelector } from 'lib/components/FlagSelector'

function PresentationTypeCard({
title,
Expand Down
5 changes: 4 additions & 1 deletion frontend/src/scenes/teamLogic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,14 @@ export const teamLogic = kea<teamLogicType>([
return null
}
},
updateCurrentTeam: async (payload: Partial<TeamType>) => {
updateCurrentTeam: async (payload: Partial<TeamType>, breakpoint) => {
if (!values.currentTeam) {
throw new Error('Current team has not been loaded yet, so it cannot be updated!')
}

const patchedTeam = (await api.update(`api/projects/${values.currentTeam.id}`, payload)) as TeamType
breakpoint()

actions.loadUser()

/* Notify user the update was successful */
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,8 @@ export interface CorrelationConfigType {
excluded_event_names?: string[]
}

export type SampleRate = '0.25' | '0.5' | '0.75' | '1.0' | undefined

export interface TeamType extends TeamBasicType {
created_at: string
updated_at: string
Expand All @@ -346,6 +348,9 @@ export interface TeamType extends TeamBasicType {
session_recording_opt_in: boolean
capture_console_log_opt_in: boolean
capture_performance_opt_in: boolean
session_recording_sample_rate: SampleRate
session_recording_minimum_duration_milliseconds: number | null
session_recording_linked_flag: Pick<FeatureFlagBasicType, 'id' | 'key'> | null
autocapture_exceptions_opt_in: boolean
surveys_opt_in?: boolean
autocapture_exceptions_errors_to_ignore: string[]
Expand Down
2 changes: 1 addition & 1 deletion latest_migrations.manifest
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ contenttypes: 0002_remove_content_type_name
ee: 0015_add_verified_properties
otp_static: 0002_throttling
otp_totp: 0002_auto_20190420_0723
posthog: 0355_add_batch_export_backfill_model
posthog: 0356_add_replay_cost_control
sessions: 0001_initial
social_django: 0010_uid_db_index
two_factor: 0007_auto_20201201_1019
5 changes: 4 additions & 1 deletion posthog/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,10 @@ class TeamAdmin(admin.ModelAdmin):
"session_recording_opt_in",
"capture_console_log_opt_in",
"capture_performance_opt_in",
"data_attributes",
"session_recording_sample_rate",
"session_recording_minimum_duration_milliseconds",
"session_recording_linked_flag",
"updateCurrentTeam({ capture_console_log_opt_in: checked })" "data_attributes",
"session_recording_version",
"access_control",
"inject_web_apps",
Expand Down
Loading