Skip to content

Commit

Permalink
feat: filter recordings by platform (mobile / web) (#23055)
Browse files Browse the repository at this point in the history
  • Loading branch information
daibhin authored Jun 19, 2024
1 parent be51489 commit 30c5073
Show file tree
Hide file tree
Showing 16 changed files with 228 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,9 @@ export function OperatorValueSelect({
if (tentativeValidationError) {
setValidationError(tentativeValidationError)
return
} else {
setValidationError(null)
}
setValidationError(null)

setCurrentOperator(newOperator)
if (isOperatorFlag(newOperator)) {
onChange(newOperator, newOperator)
Expand Down Expand Up @@ -151,9 +151,9 @@ export function OperatorValueSelect({
if (tentativeValidationError) {
setValidationError(tentativeValidationError)
return
} else {
setValidationError(null)
}
setValidationError(null)

onChange(currentOperator || PropertyOperator.Exact, newValue)
}}
// open automatically only if new filter
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/lib/components/PropertyFilters/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,8 @@ export function propertyFilterTypeToPropertyDefinitionType(
? PropertyDefinitionType.Group
: filterType === PropertyFilterType.Session
? PropertyDefinitionType.Session
: filterType === PropertyFilterType.Recording
? PropertyDefinitionType.Session
: PropertyDefinitionType.Event
}

Expand Down
5 changes: 5 additions & 0 deletions frontend/src/lib/taxonomy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1076,6 +1076,11 @@ export const CORE_FILTER_DEFINITIONS_BY_GROUP = {
},
},
replay: {
snapshot_source: {
label: 'Platform',
description: 'Platform the session was recorded on',
examples: ['web', 'mobile'],
},
console_log_level: {
label: 'Log level',
description: 'Level of console logs captured',
Expand Down
5 changes: 4 additions & 1 deletion frontend/src/models/propertyDefinitionsModel.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,10 @@ describe('the property definitions model', () => {
.toFinishAllListeners()
.toNotHaveDispatchedActions(['updatePropertyDefinitions'])
.toMatchValues({
propertyDefinitionStorage: { 'event/$session_duration': partial({ name: '$session_duration' }) },
propertyDefinitionStorage: {
'event/$session_duration': partial({ name: '$session_duration' }),
'session/snapshot_source': partial({ name: 'snapshot_source' }),
},
})
})
})
Expand Down
34 changes: 23 additions & 11 deletions frontend/src/models/propertyDefinitionsModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,26 @@ const localProperties: PropertyDefinitionStorage = {
is_seen_on_filtered_events: false,
property_type: PropertyType.Duration,
},
'session/snapshot_source': {
id: 'snapshot_source',
name: 'snapshot_source',
description: 'Platform session occurred on',
is_numerical: false,
is_seen_on_filtered_events: false,
property_type: PropertyType.Selector,
},
}

const localOptions: Record<string, PropValue[]> = {
'session/snapshot_source': [
{ id: 0, name: 'web' },
{ id: 1, name: 'mobile' },
],
'session/console_log_level': [
{ id: 0, name: 'info' },
{ id: 1, name: 'warn' },
{ id: 2, name: 'error' },
],
}

export type FormatPropertyValueForDisplayFunction = (
Expand Down Expand Up @@ -323,17 +343,9 @@ export const propertyDefinitionsModel = kea<propertyDefinitionsModelType>([
if (!propertyKey || values.currentTeamId === null) {
return
}
if (propertyKey === 'console_log_level') {
actions.setOptions(
propertyKey,
[
// id is not used so can be arbitrarily chosen
{ id: 0, name: 'info' },
{ id: 1, name: 'warn' },
{ id: 2, name: 'error' },
],
false
)

if (localOptions[getPropertyKey(type, propertyKey)]) {
actions.setOptions(propertyKey, localOptions[getPropertyKey(type, propertyKey)], false)
return
}

Expand Down
4 changes: 4 additions & 0 deletions frontend/src/queries/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -7090,6 +7090,10 @@
{
"const": "console_log_query",
"type": "string"
},
{
"const": "snapshot_source",
"type": "string"
}
]
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,34 +20,41 @@ export function ReplayTaxonomicFilters({ onChange }: ReplayTaxonomicFiltersProps
filterGroup: { values: filters },
} = useValues(universalFiltersLogic)

const hasConsoleLogLevelFilter = filters.find(
(f) => f.type === PropertyFilterType.Recording && f.key === 'console_log_level'
)
const hasConsoleLogQueryFilter = filters.find(
(f) => f.type === PropertyFilterType.Recording && f.key === 'console_log_query'
)
const hasFilter = (key: string): boolean => {
return !!filters.find((f) => f.type === PropertyFilterType.Recording && f.key === key)
}

const sessionProperties = [
{
label: 'Platform',
key: 'snapshot_source',
},
{
label: 'Console log level',
key: 'console_log_level',
},
{
label: 'Console log text',
key: 'console_log_query',
},
]

return (
<div className="grid grid-cols-2 gap-4 px-1 pt-1.5 pb-2.5">
<section>
<h5 className="mx-2 my-1">Session properties</h5>
<ul className="space-y-px">
<LemonButton
size="small"
fullWidth
onClick={() => onChange('console_log_level', {})}
disabledReason={hasConsoleLogLevelFilter ? 'Log level filter already added' : undefined}
>
Console log level
</LemonButton>
<LemonButton
size="small"
fullWidth
onClick={() => onChange('console_log_query', {})}
disabledReason={hasConsoleLogQueryFilter ? 'Log text filter already added' : undefined}
>
Console log text
</LemonButton>
{sessionProperties.map(({ key, label }) => (
<LemonButton
key={key}
size="small"
fullWidth
onClick={() => onChange(key, {})}
disabledReason={hasFilter(key) ? `${label} filter already added` : undefined}
>
{label}
</LemonButton>
))}
</ul>
</section>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,7 @@ describe('sessionRecordingsPlaylistLogic', () => {
value: 600,
operator: PropertyOperator.LessThan,
},
snapshot_source: null,
operand: FilterLogicalOperator.And,
},
})
Expand Down Expand Up @@ -437,6 +438,7 @@ describe('sessionRecordingsPlaylistLogic', () => {
events: [],
properties: [],
operand: FilterLogicalOperator.And,
snapshot_source: null,
},
})
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ export const DEFAULT_RECORDING_FILTERS: RecordingFilters = {
date_from: '-3d',
date_to: null,
console_logs: [],
snapshot_source: null,
console_search_query: '',
operand: FilterLogicalOperator.And,
}
Expand Down Expand Up @@ -131,6 +132,7 @@ function convertUniversalFiltersToLegacyFilters(universalFilters: RecordingUnive
const events: FilterType['events'] = []
const actions: FilterType['actions'] = []
let console_logs: FilterableLogLevel[] = []
let snapshot_source: AnyPropertyFilter | null = null
let console_search_query = ''

filters.forEach((f) => {
Expand All @@ -144,6 +146,11 @@ function convertUniversalFiltersToLegacyFilters(universalFilters: RecordingUnive
console_logs = f.value as FilterableLogLevel[]
} else if (f.key === 'console_log_query') {
console_search_query = (f.value || '') as string
} else if (f.key === 'snapshot_source') {
const value = f.value as string[] | null
if (value) {
snapshot_source = f
}
}
} else {
properties.push(f)
Expand All @@ -162,6 +169,7 @@ function convertUniversalFiltersToLegacyFilters(universalFilters: RecordingUnive
duration_type_filter: durationFilter.key,
console_search_query,
console_logs,
snapshot_source,
operand: nestedFilters.type,
}
}
Expand Down Expand Up @@ -499,6 +507,7 @@ export const sessionRecordingsPlaylistLogic = kea<sessionRecordingsPlaylistLogic
{
loadSessionRecordingsFailure: () => true,
loadSessionRecordingSuccess: () => false,
setUniversalFilters: () => false,
setAdvancedFilters: () => false,
setSimpleFilters: () => false,
loadNext: () => false,
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -950,7 +950,7 @@ export type ActionStepProperties =

export interface RecordingPropertyFilter extends BasePropertyFilter {
type: PropertyFilterType.Recording
key: DurationType | 'console_log_level' | 'console_log_query'
key: DurationType | 'console_log_level' | 'console_log_query' | 'snapshot_source'
operator: PropertyOperator
}

Expand All @@ -962,6 +962,7 @@ export interface RecordingDurationFilter extends RecordingPropertyFilter {
export type DurationType = 'duration' | 'active_seconds' | 'inactive_seconds'

export type FilterableLogLevel = 'info' | 'warn' | 'error'

export interface RecordingFilters {
/**
* live mode is front end only, sets date_from and date_to to the last hour
Expand All @@ -975,6 +976,7 @@ export interface RecordingFilters {
session_recording_duration?: RecordingDurationFilter
duration_type_filter?: DurationType
console_search_query?: string
snapshot_source?: AnyPropertyFilter | null
console_logs?: FilterableLogLevel[]
filter_test_accounts?: boolean
operand?: FilterLogicalOperator
Expand Down
1 change: 1 addition & 0 deletions posthog/hogql/database/schema/session_replay_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ def join_with_console_logs_log_entries_table(
"size": IntegerDatabaseField(name="size"),
"event_count": IntegerDatabaseField(name="event_count"),
"message_count": IntegerDatabaseField(name="message_count"),
"snapshot_source": StringDatabaseField(name="snapshot_source"),
"events": LazyJoin(
from_field=["session_id"],
join_table=EventsTable(),
Expand Down
18 changes: 18 additions & 0 deletions posthog/hogql/database/test/__snapshots__/test_database.ambr
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,15 @@
"table": null,
"type": "integer"
},
"snapshot_source": {
"chain": null,
"fields": null,
"hogql_value": "snapshot_source",
"name": "snapshot_source",
"schema_valid": true,
"table": null,
"type": "string"
},
"events": {
"chain": null,
"fields": [
Expand Down Expand Up @@ -1979,6 +1988,15 @@
"table": null,
"type": "integer"
},
"snapshot_source": {
"chain": null,
"fields": null,
"hogql_value": "snapshot_source",
"name": "snapshot_source",
"schema_valid": true,
"table": null,
"type": "string"
},
"events": {
"chain": null,
"fields": [
Expand Down
8 changes: 8 additions & 0 deletions posthog/models/filters/mixins/session_recordings.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,14 @@ def recording_duration_filter(self) -> Optional[Property]:
return Property(**filter_data)
return None

@cached_property
def snapshot_source_filter(self) -> Optional[Property]:
snapshot_source_data_str = self._data.get("snapshot_source", None)
if isinstance(snapshot_source_data_str, str):
filter_data = json.loads(snapshot_source_data_str)
return Property(**filter_data)
return None

@cached_property
def session_ids(self) -> Optional[list[str]]:
# Can be ['a', 'b'] or "['a', 'b']" or "a,b"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ class SessionRecordingListFromFilters:
sum(s.keypress_count),
sum(s.mouse_activity_count),
sum(s.active_milliseconds)/1000 as active_seconds,
duration-active_seconds as inactive_seconds,
(duration - active_seconds) as inactive_seconds,
sum(s.console_log_count) as console_log_count,
sum(s.console_warn_count) as console_warn_count,
sum(s.console_error_count) as console_error_count
Expand Down Expand Up @@ -273,6 +273,20 @@ def _having_predicates(self) -> ast.And | Constant:
),
)

if self._filter.snapshot_source_filter:
op = (
ast.CompareOperationOp.In
if self._filter.snapshot_source_filter.operator == "exact"
else ast.CompareOperationOp.NotIn
)
exprs.append(
ast.CompareOperation(
op=op,
left=ast.Call(name="argMinMerge", args=[ast.Field(chain=["s", "snapshot_source"])]),
right=ast.Constant(value=self._filter.snapshot_source_filter.value),
),
)

return ast.And(exprs=exprs) if exprs else ast.Constant(value=True)

def _strip_person_and_event_properties(self, property_group: PropertyGroup) -> PropertyGroup | None:
Expand Down
Loading

0 comments on commit 30c5073

Please sign in to comment.