diff --git a/frontend/__snapshots__/exporter-exporter--funnel-top-to-bottom-breakdown-insight--dark.png b/frontend/__snapshots__/exporter-exporter--funnel-top-to-bottom-breakdown-insight--dark.png index badd58a5a74b6..723c5895b17fc 100644 Binary files a/frontend/__snapshots__/exporter-exporter--funnel-top-to-bottom-breakdown-insight--dark.png and b/frontend/__snapshots__/exporter-exporter--funnel-top-to-bottom-breakdown-insight--dark.png differ diff --git a/frontend/__snapshots__/exporter-exporter--funnel-top-to-bottom-breakdown-insight--light.png b/frontend/__snapshots__/exporter-exporter--funnel-top-to-bottom-breakdown-insight--light.png index 3d908501e3be5..559ec7fd6866b 100644 Binary files a/frontend/__snapshots__/exporter-exporter--funnel-top-to-bottom-breakdown-insight--light.png and b/frontend/__snapshots__/exporter-exporter--funnel-top-to-bottom-breakdown-insight--light.png differ diff --git a/frontend/__snapshots__/replay-player-failure--recent-recordings-404--dark.png b/frontend/__snapshots__/replay-player-failure--recent-recordings-404--dark.png index 7c36f82f3c19e..c6f7ea6c355c1 100644 Binary files a/frontend/__snapshots__/replay-player-failure--recent-recordings-404--dark.png and b/frontend/__snapshots__/replay-player-failure--recent-recordings-404--dark.png differ diff --git a/frontend/__snapshots__/replay-player-failure--recent-recordings-404--light.png b/frontend/__snapshots__/replay-player-failure--recent-recordings-404--light.png index e07d3b8776663..4506520e43910 100644 Binary files a/frontend/__snapshots__/replay-player-failure--recent-recordings-404--light.png and b/frontend/__snapshots__/replay-player-failure--recent-recordings-404--light.png differ diff --git a/frontend/__snapshots__/replay-player-success--recent-recordings--dark.png b/frontend/__snapshots__/replay-player-success--recent-recordings--dark.png index 01a57fdc939a3..af464e4ed5ca9 100644 Binary files a/frontend/__snapshots__/replay-player-success--recent-recordings--dark.png and b/frontend/__snapshots__/replay-player-success--recent-recordings--dark.png differ diff --git a/frontend/__snapshots__/replay-player-success--recent-recordings--light.png b/frontend/__snapshots__/replay-player-success--recent-recordings--light.png index 33a4cd371aba0..50cad86623416 100644 Binary files a/frontend/__snapshots__/replay-player-success--recent-recordings--light.png and b/frontend/__snapshots__/replay-player-success--recent-recordings--light.png differ diff --git a/frontend/__snapshots__/replay-player-success--second-recording-in-list--dark.png b/frontend/__snapshots__/replay-player-success--second-recording-in-list--dark.png index 5f2d993d05efb..2bf8d71403341 100644 Binary files a/frontend/__snapshots__/replay-player-success--second-recording-in-list--dark.png and b/frontend/__snapshots__/replay-player-success--second-recording-in-list--dark.png differ diff --git a/frontend/__snapshots__/replay-player-success--second-recording-in-list--light.png b/frontend/__snapshots__/replay-player-success--second-recording-in-list--light.png index 693947ce7669d..7b64cab21e28d 100644 Binary files a/frontend/__snapshots__/replay-player-success--second-recording-in-list--light.png and b/frontend/__snapshots__/replay-player-success--second-recording-in-list--light.png differ diff --git a/frontend/__snapshots__/scenes-app-dashboards--insight-legend--dark.png b/frontend/__snapshots__/scenes-app-dashboards--insight-legend--dark.png index d7d9398cd00cc..d1217fba75dfb 100644 Binary files a/frontend/__snapshots__/scenes-app-dashboards--insight-legend--dark.png and b/frontend/__snapshots__/scenes-app-dashboards--insight-legend--dark.png differ diff --git a/frontend/__snapshots__/scenes-app-dashboards--insight-legend-legacy--dark.png b/frontend/__snapshots__/scenes-app-dashboards--insight-legend-legacy--dark.png index 60a8efe898a62..9d811913930ca 100644 Binary files a/frontend/__snapshots__/scenes-app-dashboards--insight-legend-legacy--dark.png and b/frontend/__snapshots__/scenes-app-dashboards--insight-legend-legacy--dark.png differ diff --git a/frontend/__snapshots__/scenes-app-dashboards--insight-legend-legacy--light.png b/frontend/__snapshots__/scenes-app-dashboards--insight-legend-legacy--light.png index 242a806185f1f..c067fa7a5cd00 100644 Binary files a/frontend/__snapshots__/scenes-app-dashboards--insight-legend-legacy--light.png and b/frontend/__snapshots__/scenes-app-dashboards--insight-legend-legacy--light.png differ diff --git a/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom-breakdown--dark--webkit.png b/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom-breakdown--dark--webkit.png index 784f39f6eb8de..a1b8b4db32e91 100644 Binary files a/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom-breakdown--dark--webkit.png and b/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom-breakdown--dark--webkit.png differ diff --git a/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom-breakdown--dark.png b/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom-breakdown--dark.png index 55e3c8157b614..804a66a0a880f 100644 Binary files a/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom-breakdown--dark.png and b/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom-breakdown--dark.png differ diff --git a/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom-breakdown--light--webkit.png b/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom-breakdown--light--webkit.png index 87a3743cf6524..48d9cecc3902b 100644 Binary files a/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom-breakdown--light--webkit.png and b/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom-breakdown--light--webkit.png differ diff --git a/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom-breakdown--light.png b/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom-breakdown--light.png index e708c340d5366..756c64885336f 100644 Binary files a/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom-breakdown--light.png and b/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom-breakdown--light.png differ diff --git a/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom-breakdown-edit--dark--webkit.png b/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom-breakdown-edit--dark--webkit.png index 2678587de5429..3d4ce71a129a4 100644 Binary files a/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom-breakdown-edit--dark--webkit.png and b/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom-breakdown-edit--dark--webkit.png differ diff --git a/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom-breakdown-edit--dark.png b/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom-breakdown-edit--dark.png index 3ff13d640c61a..4d2ff3308afa8 100644 Binary files a/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom-breakdown-edit--dark.png and b/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom-breakdown-edit--dark.png differ diff --git a/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom-breakdown-edit--light--webkit.png b/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom-breakdown-edit--light--webkit.png index 86d01b2d6c77f..37ae1a7b3ca1f 100644 Binary files a/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom-breakdown-edit--light--webkit.png and b/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom-breakdown-edit--light--webkit.png differ diff --git a/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom-breakdown-edit--light.png b/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom-breakdown-edit--light.png index a8b763cfcb7c0..2ea9fc2018537 100644 Binary files a/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom-breakdown-edit--light.png and b/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom-breakdown-edit--light.png differ diff --git a/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom-edit--light.png b/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom-edit--light.png index 01df2b71a3a80..4ca21748c10e4 100644 Binary files a/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom-edit--light.png and b/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom-edit--light.png differ diff --git a/frontend/__snapshots__/scenes-app-notebooks--recordings-playlist--light.png b/frontend/__snapshots__/scenes-app-notebooks--recordings-playlist--light.png index 10c2fe114e7d6..4e344bfa7e24f 100644 Binary files a/frontend/__snapshots__/scenes-app-notebooks--recordings-playlist--light.png and b/frontend/__snapshots__/scenes-app-notebooks--recordings-playlist--light.png differ diff --git a/frontend/src/lib/components/Playlist/Playlist.scss b/frontend/src/lib/components/Playlist/Playlist.scss index 5f6ab75a144b0..250c3acf9997b 100644 --- a/frontend/src/lib/components/Playlist/Playlist.scss +++ b/frontend/src/lib/components/Playlist/Playlist.scss @@ -51,8 +51,8 @@ .SessionRecordingPlaylistHeightWrapper { // NOTE: Somewhat random way to offset the various headers and tabs above the playlist - height: calc(100vh - 15rem); - min-height: 41rem; + height: calc(100vh - 14rem); + min-height: 25rem; } .SessionRecordingPreview { diff --git a/frontend/src/lib/constants.tsx b/frontend/src/lib/constants.tsx index f403db126644c..0fdbccfcb1435 100644 --- a/frontend/src/lib/constants.tsx +++ b/frontend/src/lib/constants.tsx @@ -186,7 +186,6 @@ export const FEATURE_FLAGS = { REPLAY_SIMILAR_RECORDINGS: 'session-replay-similar-recordings', // owner: #team-replay SAVED_NOT_PINNED: 'saved-not-pinned', // owner: #team-replay NEW_EXPERIMENTS_UI: 'new-experiments-ui', // owner: @jurajmajerik #team-feature-success - SESSION_REPLAY_FILTER_ORDERING: 'session-replay-filter-ordering', // owner: #team-replay REPLAY_ERROR_CLUSTERING: 'session-replay-error-clustering', // owner: #team-replay AUDIT_LOGS_ACCESS: 'audit-logs-access', // owner: #team-growth SUBSCRIBE_FROM_PAYGATE: 'subscribe-from-paygate', // owner: #team-growth @@ -202,7 +201,6 @@ export const FEATURE_FLAGS = { HOG: 'hog', // owner: @mariusandra HOG_FUNCTIONS: 'hog-functions', // owner: #team-cdp PERSONLESS_EVENTS_NOT_SUPPORTED: 'personless-events-not-supported', // owner: @raquelmsmith - SESSION_REPLAY_UNIVERSAL_FILTERS: 'session-replay-universal-filters', // owner: #team-replay ALERTS: 'alerts', // owner: github.com/nikitaevg ERROR_TRACKING: 'error-tracking', // owner: #team-replay SETTINGS_BOUNCE_RATE_PAGE_VIEW_MODE: 'settings-bounce-rate-page-view-mode', // owner: @robbie-c diff --git a/frontend/src/queries/examples.ts b/frontend/src/queries/examples.ts index 5728a1e46fe38..7cbad0eccc4f3 100644 --- a/frontend/src/queries/examples.ts +++ b/frontend/src/queries/examples.ts @@ -287,6 +287,19 @@ limit 100`, }, } +const HogQLForDataWarehouse: HogQLQuery = { + kind: NodeKind.HogQLQuery, + query: `select toDate(timestamp) as timestamp, event as event + from events +limit 100`, + explain: true, +} + +const DataWarehouse: DataVisualizationNode = { + kind: NodeKind.DataVisualizationNode, + source: HogQLForDataWarehouse, +} + const HogQLTable: DataTableNode = { kind: NodeKind.DataTableNode, full: true, @@ -344,6 +357,7 @@ export const examples: Record = { DataVisualization, Hog, Hoggonacci, + DataWarehouse, } export const stringifiedExamples: Record = Object.fromEntries( diff --git a/frontend/src/queries/nodes/DataVisualization/DataVisualization.tsx b/frontend/src/queries/nodes/DataVisualization/DataVisualization.tsx index 757ffb244e9ba..7c1fcc94e28da 100644 --- a/frontend/src/queries/nodes/DataVisualization/DataVisualization.tsx +++ b/frontend/src/queries/nodes/DataVisualization/DataVisualization.tsx @@ -1,11 +1,13 @@ import { LemonDivider } from '@posthog/lemon-ui' import { BindLogic, useValues } from 'kea' +import { router } from 'kea-router' import { AnimationType } from 'lib/animations/animations' import { Animation } from 'lib/components/Animation/Animation' import { useCallback, useState } from 'react' import { DatabaseTableTreeWithItems } from 'scenes/data-warehouse/external/DataWarehouseTables' import { insightLogic } from 'scenes/insights/insightLogic' import { HogQLBoldNumber } from 'scenes/insights/views/BoldNumber/BoldNumber' +import { urls } from 'scenes/urls' import { insightVizDataCollectionId, insightVizDataNodeKey } from '~/queries/nodes/InsightViz/InsightViz' import { AnyResponseType, DataVisualizationNode, HogQLQuery, NodeKind } from '~/queries/schema' @@ -123,19 +125,6 @@ function InternalDataTableVisualization(props: DataTableVisualizationProps): JSX {!readOnly && showEditingUI && ( <> - {sourceFeatures.has(QueryFeature.dateRangePicker) && ( -
- { - if (query.kind === NodeKind.HogQLQuery) { - setQuerySource(query) - } - }} - /> -
- )} )} {!readOnly && showResultControls && ( @@ -147,6 +136,20 @@ function InternalDataTableVisualization(props: DataTableVisualizationProps): JSX
+ {sourceFeatures.has(QueryFeature.dateRangePicker) && + !router.values.location.pathname.includes(urls.dataWarehouse()) && ( // decouple this component from insights tab and datawarehouse scene +
+ { + if (query.kind === NodeKind.HogQLQuery) { + setQuerySource(query) + } + }} + /> +
+ )}
diff --git a/frontend/src/queries/nodes/HogQLQuery/HogQLQueryEditor.tsx b/frontend/src/queries/nodes/HogQLQuery/HogQLQueryEditor.tsx index 9b0cc060d17ee..99ec8572c6552 100644 --- a/frontend/src/queries/nodes/HogQLQuery/HogQLQueryEditor.tsx +++ b/frontend/src/queries/nodes/HogQLQuery/HogQLQueryEditor.tsx @@ -223,29 +223,17 @@ export function HogQLQueryEditor(props: HogQLQueryEditorProps): JSX.Element { : '' } data-attr="hogql-query-editor-save-as-view" - > - Save as view - - )} - } - type="secondary" - size="small" - dropdown={{ - overlay: ( + tooltip={
Save a query as a view that can be referenced in another query. This is useful for modeling data and organizing large queries into readable chunks.{' '} More Info{' '}
- ), - placement: 'right-start', - fallbackPlacements: ['left-start'], - actionable: true, - closeParentPopoverOnClickInside: true, - }} - /> + } + > + Save as view + + )} )} diff --git a/frontend/src/queries/schema.json b/frontend/src/queries/schema.json index 22351b2189557..20afa26306c9d 100644 --- a/frontend/src/queries/schema.json +++ b/frontend/src/queries/schema.json @@ -423,6 +423,9 @@ "normalize_url": { "type": "boolean" }, + "property": { + "type": "string" + }, "type": { "anyOf": [ { @@ -432,12 +435,9 @@ "type": "null" } ] - }, - "value": { - "type": "string" } }, - "required": ["value"], + "required": ["property"], "type": "object" }, "BreakdownAttributionType": { diff --git a/frontend/src/queries/schema.ts b/frontend/src/queries/schema.ts index 2b7e27639f914..924bc2bc0ee7e 100644 --- a/frontend/src/queries/schema.ts +++ b/frontend/src/queries/schema.ts @@ -1604,7 +1604,7 @@ export type MultipleBreakdownType = Extract @@ -57,14 +56,14 @@ export function DataWarehouseExternalScene(): JSX.Element { buttons={ <> {currentTab === DataWarehouseTab.Explore && ( - + saveAs(true)} + loading={insightSaving} + > + Save as insight + )} ([ }, ], }), - listeners(({ actions, values, cache }) => ({ - deleteSelfManagedTable: async ({ tableId }) => { - await api.dataWarehouseTables.delete(tableId) - actions.loadDatabase() - }, - loadSourcesSuccess: () => { + urlToAction(({ cache, actions }) => ({ + '/data-warehouse/managed-sources': () => { clearTimeout(cache.refreshTimeout) cache.refreshTimeout = setTimeout(() => { actions.loadSources(null) }, REFRESH_INTERVAL) }, - loadSourcesFailure: () => { - clearTimeout(cache.refreshTimeout) - - cache.refreshTimeout = setTimeout(() => { - actions.loadSources(null) - }, REFRESH_INTERVAL) + '*': () => { + if (cache.refreshTimeout && router.values.location.pathname !== '/data-warehouse/managed-sources') { + clearTimeout(cache.refreshTimeout) + } + }, + })), + listeners(({ actions, values, cache }) => ({ + deleteSelfManagedTable: async ({ tableId }) => { + await api.dataWarehouseTables.delete(tableId) + actions.loadDatabase() }, deleteSource: async ({ source }) => { await api.externalDataSources.delete(source.id) @@ -214,4 +215,7 @@ export const dataWarehouseSettingsLogic = kea([ afterMount(({ actions }) => { actions.loadSources(null) }), + beforeUnmount(({ cache }) => { + clearTimeout(cache.refreshTimeout) + }), ]) diff --git a/frontend/src/scenes/data-warehouse/settings/dataWarehouseSourcesTableSyncMethodModalLogic.ts b/frontend/src/scenes/data-warehouse/settings/dataWarehouseSourcesTableSyncMethodModalLogic.ts index d76f22a3583ad..d21813aa509b4 100644 --- a/frontend/src/scenes/data-warehouse/settings/dataWarehouseSourcesTableSyncMethodModalLogic.ts +++ b/frontend/src/scenes/data-warehouse/settings/dataWarehouseSourcesTableSyncMethodModalLogic.ts @@ -16,7 +16,10 @@ export const dataWarehouseSourcesTableSyncMethodModalLogic = kea props.schema.id), connect(() => ({ - actions: [dataWarehouseSettingsLogic, ['updateSchema', 'updateSchemaSuccess', 'updateSchemaFailure']], + actions: [ + dataWarehouseSettingsLogic, + ['updateSchema', 'updateSchemaSuccess', 'updateSchemaFailure', 'loadSources'], + ], })), actions({ openSyncMethodModal: (schema: ExternalDataSourceSchema) => ({ schema }), @@ -59,6 +62,7 @@ export const dataWarehouseSourcesTableSyncMethodModalLogic = kea ({ updateSchemaSuccess: () => { + actions.loadSources(null) actions.resetSchemaIncrementalFields() actions.closeSyncMethodModal() }, diff --git a/frontend/src/scenes/funnels/funnelDataLogic.ts b/frontend/src/scenes/funnels/funnelDataLogic.ts index 3c50d171db9dc..0a48c272e929b 100644 --- a/frontend/src/scenes/funnels/funnelDataLogic.ts +++ b/frontend/src/scenes/funnels/funnelDataLogic.ts @@ -179,7 +179,7 @@ export const funnelDataLogic = kea([ if (!isTimeToConvertFunnel && Array.isArray(results)) { if (isBreakdownFunnelResults(results)) { const breakdownProperty = breakdownFilter?.breakdowns - ? breakdownFilter?.breakdowns.map((b) => b.value).join('::') + ? breakdownFilter?.breakdowns.map((b) => b.property).join('::') : breakdownFilter?.breakdown ?? undefined return aggregateBreakdownResult(results, breakdownProperty).sort((a, b) => a.order - b.order) } diff --git a/frontend/src/scenes/groups/Group.tsx b/frontend/src/scenes/groups/Group.tsx index 278132349979c..1cf23337da773 100644 --- a/frontend/src/scenes/groups/Group.tsx +++ b/frontend/src/scenes/groups/Group.tsx @@ -5,6 +5,7 @@ import { NotFound } from 'lib/components/NotFound' import { PageHeader } from 'lib/components/PageHeader' import { PropertiesTable } from 'lib/components/PropertiesTable' import { TZLabel } from 'lib/components/TZLabel' +import { isEventFilter } from 'lib/components/UniversalFilters/utils' import { LemonBanner } from 'lib/lemon-ui/LemonBanner' import { LemonTabs } from 'lib/lemon-ui/LemonTabs' import { lemonToast } from 'lib/lemon-ui/LemonToast' @@ -17,6 +18,7 @@ import { NotebookSelectButton } from 'scenes/notebooks/NotebookSelectButton/Note import { RelatedFeatureFlags } from 'scenes/persons/RelatedFeatureFlags' import { SceneExport } from 'scenes/sceneTypes' import { SessionRecordingsPlaylist } from 'scenes/session-recordings/playlist/SessionRecordingsPlaylist' +import { filtersFromUniversalFilterGroups } from 'scenes/session-recordings/utils' import { teamLogic } from 'scenes/teamLogic' import { urls } from 'scenes/urls' @@ -154,7 +156,7 @@ export function Group(): JSX.Element { { - const stillHasGroupFilter = legacyFilters.events?.some((event) => { - return event.properties.some( + onFiltersChange={(filters) => { + const eventFilters = + filtersFromUniversalFilterGroups(filters).filter(isEventFilter) + + const stillHasGroupFilter = eventFilters?.some((event) => { + return event.properties?.some( (prop: Record) => prop.key === `$group_${groupTypeIndex} = '${groupKey}'` ) diff --git a/frontend/src/scenes/insights/filters/BreakdownFilter/TaxonomicBreakdownFilter.tsx b/frontend/src/scenes/insights/filters/BreakdownFilter/TaxonomicBreakdownFilter.tsx index 20233ac3f7423..f488bfef49aae 100644 --- a/frontend/src/scenes/insights/filters/BreakdownFilter/TaxonomicBreakdownFilter.tsx +++ b/frontend/src/scenes/insights/filters/BreakdownFilter/TaxonomicBreakdownFilter.tsx @@ -47,8 +47,8 @@ export function TaxonomicBreakdownFilter({ const tags = breakdownArray.map((breakdown) => typeof breakdown === 'object' ? ( diff --git a/frontend/src/scenes/insights/filters/BreakdownFilter/breakdownTagLogic.ts b/frontend/src/scenes/insights/filters/BreakdownFilter/breakdownTagLogic.ts index 60855f5fdd6fe..e091fc941b7e2 100644 --- a/frontend/src/scenes/insights/filters/BreakdownFilter/breakdownTagLogic.ts +++ b/frontend/src/scenes/insights/filters/BreakdownFilter/breakdownTagLogic.ts @@ -97,7 +97,7 @@ export const breakdownTagLogic = kea([ (s) => [s.breakdownFilter, s.breakdown, s.breakdownType], ({ breakdowns }, breakdown, breakdownType) => breakdowns?.find( - (savedBreakdown) => savedBreakdown.value === breakdown && savedBreakdown.type === breakdownType + (savedBreakdown) => savedBreakdown.property === breakdown && savedBreakdown.type === breakdownType ), ], histogramBinsUsed: [ @@ -147,7 +147,7 @@ export const breakdownTagLogic = kea([ ? filterToTaxonomicFilterType( multipleBreakdown?.type, multipleBreakdown?.group_type_index, - multipleBreakdown?.value + multipleBreakdown?.property ) : breakdownFilterToTaxonomicFilterType(breakdownFilter) diff --git a/frontend/src/scenes/insights/filters/BreakdownFilter/taxonomicBreakdownFilterLogic.test.ts b/frontend/src/scenes/insights/filters/BreakdownFilter/taxonomicBreakdownFilterLogic.test.ts index fb75bb20da880..1064db47dd981 100644 --- a/frontend/src/scenes/insights/filters/BreakdownFilter/taxonomicBreakdownFilterLogic.test.ts +++ b/frontend/src/scenes/insights/filters/BreakdownFilter/taxonomicBreakdownFilterLogic.test.ts @@ -237,7 +237,7 @@ describe('taxonomicBreakdownFilterLogic', () => { breakdownFilter: { breakdowns: [ { - value: 'prop1', + property: 'prop1', type: 'event', }, ], @@ -256,11 +256,11 @@ describe('taxonomicBreakdownFilterLogic', () => { breakdownFilter: { breakdowns: [ { - value: 'prop1', + property: 'prop1', type: 'event', }, { - value: 'prop2', + property: 'prop2', type: 'event', }, ], @@ -282,15 +282,15 @@ describe('taxonomicBreakdownFilterLogic', () => { breakdownFilter: { breakdowns: [ { - value: 'prop1', + property: 'prop1', type: 'event', }, { - value: 'prop2', + property: 'prop2', type: 'event', }, { - value: 'prop3', + property: 'prop3', type: 'event', }, ], @@ -397,7 +397,7 @@ describe('taxonomicBreakdownFilterLogic', () => { breakdown_type: undefined, breakdowns: [ { - value: 'c', + property: 'c', type: 'event', }, ], @@ -412,7 +412,7 @@ describe('taxonomicBreakdownFilterLogic', () => { breakdownFilter: { breakdowns: [ { - value: 'c', + property: 'c', type: 'event', }, ], @@ -455,7 +455,7 @@ describe('taxonomicBreakdownFilterLogic', () => { breakdown_type: undefined, breakdowns: [ { - value: 'height', + property: 'height', type: 'person', }, ], @@ -486,7 +486,7 @@ describe('taxonomicBreakdownFilterLogic', () => { breakdowns: [ { type: 'group', - value: '$lib_version', + property: '$lib_version', group_type_index: 0, }, ], @@ -501,7 +501,7 @@ describe('taxonomicBreakdownFilterLogic', () => { breakdownFilter: { breakdowns: [ { - value: 'c', + property: 'c', type: 'event', }, ], @@ -533,7 +533,7 @@ describe('taxonomicBreakdownFilterLogic', () => { breakdowns: [ { type: 'event', - value: 'a', + property: 'a', }, ], breakdown_group_type_index: undefined, @@ -547,11 +547,11 @@ describe('taxonomicBreakdownFilterLogic', () => { breakdownFilter: { breakdowns: [ { - value: 'c', + property: 'c', type: 'event', }, { - value: 'duplicate', + property: 'duplicate', type: 'event', }, ], @@ -562,14 +562,13 @@ describe('taxonomicBreakdownFilterLogic', () => { }) mockFeatureFlag(logic) logic.mount() - const changedBreakdown = 'c' const group: TaxonomicFilterGroup = taxonomicGroupFor(TaxonomicFilterGroupType.EventProperties, undefined) await expectLogic(logic, () => { logic.actions.replaceBreakdown( { type: 'event', - value: changedBreakdown, + value: 'c', }, { group: group, @@ -613,7 +612,7 @@ describe('taxonomicBreakdownFilterLogic', () => { breakdowns: [ { type: 'event', - value: 'prop2', + property: 'prop2', }, ], }) @@ -665,7 +664,7 @@ describe('taxonomicBreakdownFilterLogic', () => { breakdowns: [ { type: 'event', - value: 'prop', + property: 'prop', }, ], }, @@ -736,14 +735,14 @@ describe('taxonomicBreakdownFilterLogic', () => { breakdown_normalize_url: undefined, breakdowns: [ { - value: 'prop', + property: 'prop', type: 'event', normalize_url: true, group_type_index: 0, histogram_bin_count: 10, }, { - value: 'c', + property: 'c', type: 'event', }, ], @@ -893,7 +892,7 @@ describe('taxonomicBreakdownFilterLogic', () => { breakdowns: [ { type: 'person', - value: 'new_prop', + property: 'new_prop', }, ], }) @@ -927,7 +926,7 @@ describe('taxonomicBreakdownFilterLogic', () => { breakdowns: [ { type: 'group', - value: '$lib_version', + property: '$lib_version', group_type_index: 0, }, ], @@ -1002,7 +1001,7 @@ describe('taxonomicBreakdownFilterLogic', () => { breakdown_group_type_index: undefined, breakdowns: [ { - value: 'c', + property: 'c', type: 'person', }, ], diff --git a/frontend/src/scenes/insights/filters/BreakdownFilter/taxonomicBreakdownFilterLogic.ts b/frontend/src/scenes/insights/filters/BreakdownFilter/taxonomicBreakdownFilterLogic.ts index 18ed86670a3b1..291968d794bf9 100644 --- a/frontend/src/scenes/insights/filters/BreakdownFilter/taxonomicBreakdownFilterLogic.ts +++ b/frontend/src/scenes/insights/filters/BreakdownFilter/taxonomicBreakdownFilterLogic.ts @@ -236,8 +236,8 @@ export const taxonomicBreakdownFilterLogic = kea !(savedBreakdown.value === breakdown && savedBreakdown.type === breakdownType) + (savedBreakdown) => + !(savedBreakdown.property === breakdown && savedBreakdown.type === breakdownType) ) props.updateBreakdownFilter({ @@ -381,7 +382,7 @@ export const taxonomicBreakdownFilterLogic = kea { if ( - savedBreakdown.value === previousBreakdown.value && + savedBreakdown.property === previousBreakdown.value && savedBreakdown.type === previousBreakdown.type ) { return { ...savedBreakdown, - value: breakdownValue, + property: breakdownValue, type: breakdownType, group_type_index: newBreakdown.group.groupTypeIndex, histogram_bin_count: isHistogramable @@ -505,7 +506,7 @@ function updateNestedBreakdown( lookupType: string ): Breakdown[] | undefined { return breakdowns?.map((savedBreakdown) => - savedBreakdown.value === lookupValue && savedBreakdown.type === lookupType + savedBreakdown.property === lookupValue && savedBreakdown.type === lookupType ? { ...savedBreakdown, ...breakdownUpdate, @@ -520,7 +521,7 @@ function checkBreakdownExists( lookupType: string ): boolean { return !!breakdowns?.find( - (savedBreakdown) => savedBreakdown.value === lookupValue && savedBreakdown.type === lookupType + (savedBreakdown) => savedBreakdown.property === lookupValue && savedBreakdown.type === lookupType ) } diff --git a/frontend/src/scenes/insights/insightDataLogic.tsx b/frontend/src/scenes/insights/insightDataLogic.tsx index 1e8eaf4153423..668798a9fe49c 100644 --- a/frontend/src/scenes/insights/insightDataLogic.tsx +++ b/frontend/src/scenes/insights/insightDataLogic.tsx @@ -80,8 +80,8 @@ export const insightDataLogic = kea([ actions({ setQuery: (query: Node | null) => ({ query }), - saveAs: true, - saveAsNamingSuccess: (name: string) => ({ name }), + saveAs: (redirectToViewMode?: boolean) => ({ redirectToViewMode }), + saveAsNamingSuccess: (name: string, redirectToViewMode?: boolean) => ({ name, redirectToViewMode }), saveInsight: (redirectToViewMode = true) => ({ redirectToViewMode }), toggleQueryEditorPanel: true, cancelChanges: true, @@ -237,11 +237,14 @@ export const insightDataLogic = kea([ actions.insightLogicSaveInsight(redirectToViewMode) }, - saveAs: async () => { + saveAs: async ({ redirectToViewMode }) => { LemonDialog.openForm({ title: 'Save as new insight', initialValues: { - insightName: `${values.queryBasedInsight.name || values.queryBasedInsight.derived_name} (copy)`, + insightName: + values.queryBasedInsight.name || values.queryBasedInsight.derived_name + ? `${values.queryBasedInsight.name || values.queryBasedInsight.derived_name} (copy)` + : '', }, content: ( @@ -251,10 +254,10 @@ export const insightDataLogic = kea([ errors: { insightName: (name) => (!name ? 'You must enter a name' : undefined), }, - onSubmit: async ({ insightName }) => actions.saveAsNamingSuccess(insightName), + onSubmit: async ({ insightName }) => actions.saveAsNamingSuccess(insightName, redirectToViewMode), }) }, - saveAsNamingSuccess: ({ name }) => { + saveAsNamingSuccess: ({ name, redirectToViewMode }) => { let filters = values.legacyInsight.filters if (isInsightVizNode(values.query)) { const querySource = values.query.source @@ -277,7 +280,7 @@ export const insightDataLogic = kea([ { overrideFilter: true, fromPersistentApi: false } ) - actions.insightLogicSaveAsNamingSuccess(name) + actions.insightLogicSaveAsNamingSuccess(name, redirectToViewMode) }, cancelChanges: () => { const savedFilters = values.savedInsight.filters diff --git a/frontend/src/scenes/insights/insightLogic.ts b/frontend/src/scenes/insights/insightLogic.ts index 0aede61c99371..88480775ad8b2 100644 --- a/frontend/src/scenes/insights/insightLogic.ts +++ b/frontend/src/scenes/insights/insightLogic.ts @@ -93,7 +93,7 @@ export const insightLogic = kea([ insight, options, }), - saveAsNamingSuccess: (name: string) => ({ name }), + saveAsNamingSuccess: (name: string, redirectToViewMode?: boolean) => ({ name, redirectToViewMode }), cancelChanges: true, saveInsight: (redirectToViewMode = true) => ({ redirectToViewMode }), saveInsightSuccess: true, @@ -441,7 +441,7 @@ export const insightLogic = kea([ router.actions.push(urls.insightEdit(savedInsight.short_id)) } }, - saveAsNamingSuccess: async ({ name }) => { + saveAsNamingSuccess: async ({ name, redirectToViewMode }) => { const { filters, query } = getInsightFilterOrQueryForPersistance( values.queryBasedInsight, values.queryBasedInsightSaving @@ -454,12 +454,17 @@ export const insightLogic = kea([ }) lemonToast.info( `You're now working on a copy of ${ - values.queryBasedInsight.name || values.queryBasedInsight.derived_name + values.queryBasedInsight.name || values.queryBasedInsight.derived_name || name }` ) actions.setInsight(insight, { fromPersistentApi: true, overrideFilter: true }) savedInsightsLogic.findMounted()?.actions.loadInsights() // Load insights afresh - router.actions.push(urls.insightEdit(insight.short_id)) + + if (redirectToViewMode) { + router.actions.push(urls.insightView(insight.short_id)) + } else { + router.actions.push(urls.insightEdit(insight.short_id)) + } }, cancelChanges: () => { actions.setFilters(values.savedInsight.filters || {}) diff --git a/frontend/src/scenes/insights/insightSceneLogic.tsx b/frontend/src/scenes/insights/insightSceneLogic.tsx index 1b06e53060bef..75e99677715a4 100644 --- a/frontend/src/scenes/insights/insightSceneLogic.tsx +++ b/frontend/src/scenes/insights/insightSceneLogic.tsx @@ -191,7 +191,7 @@ export const insightSceneLogic = kea([ urlToAction(({ actions, values }) => ({ '/data-warehouse/*': (_, __, { q }) => { actions.setSceneState(String('new') as InsightShortId, ItemMode.Edit, undefined) - values.insightDataLogicRef?.logic.actions.setQuery(examples.DataVisualization) + values.insightDataLogicRef?.logic.actions.setQuery(examples.DataWarehouse) values.insightLogicRef?.logic.actions.setInsight( { ...createEmptyInsight('new', false), diff --git a/frontend/src/scenes/insights/summarizeInsight.test.ts b/frontend/src/scenes/insights/summarizeInsight.test.ts index f903c83774968..33f9dd1d31471 100644 --- a/frontend/src/scenes/insights/summarizeInsight.test.ts +++ b/frontend/src/scenes/insights/summarizeInsight.test.ts @@ -594,15 +594,15 @@ describe('summarizing insights', () => { breakdowns: [ { type: 'event', - value: '$browser', + property: '$browser', }, { type: 'person', - value: 'custom_prop', + property: 'custom_prop', }, { type: 'session', - value: '$session_duration', + property: '$session_duration', }, ], }, diff --git a/frontend/src/scenes/insights/summarizeInsight.ts b/frontend/src/scenes/insights/summarizeInsight.ts index aefdb19a4048c..f44b3f07e7ed6 100644 --- a/frontend/src/scenes/insights/summarizeInsight.ts +++ b/frontend/src/scenes/insights/summarizeInsight.ts @@ -63,7 +63,7 @@ function summarizeMultipleBreakdown( if (breakdowns && breakdowns.length > 0) { return (breakdowns as Breakdown[]) .map((breakdown) => - summarizeSinglularBreakdown(breakdown.value, breakdown.type, breakdown.group_type_index, context) + summarizeSinglularBreakdown(breakdown.property, breakdown.type, breakdown.group_type_index, context) ) .filter((label): label is string => !!label) .join(', ') diff --git a/frontend/src/scenes/insights/utils.test.ts b/frontend/src/scenes/insights/utils.test.ts index 7d5a6435bb668..bf6b56a205b56 100644 --- a/frontend/src/scenes/insights/utils.test.ts +++ b/frontend/src/scenes/insights/utils.test.ts @@ -340,11 +340,11 @@ describe('formatBreakdownLabel()', () => { const breakdownFilter: BreakdownFilter = { breakdowns: [ { - value: 'demographic', + property: 'demographic', type: 'event', }, { - value: '$browser', + property: '$browser', type: 'event', }, ], @@ -362,11 +362,11 @@ describe('formatBreakdownLabel()', () => { const breakdownFilter: BreakdownFilter = { breakdowns: [ { - value: 'demographic', + property: 'demographic', type: 'event', }, { - value: '$browser', + property: '$browser', type: 'event', }, ], @@ -390,7 +390,7 @@ describe('formatBreakdownLabel()', () => { const breakdownFilter2: BreakdownFilter = { breakdowns: [ { - value: '$session_duration', + property: '$session_duration', type: 'session', }, ], @@ -410,7 +410,7 @@ describe('formatBreakdownLabel()', () => { const breakdownFilter2: BreakdownFilter = { breakdowns: [ { - value: '$session_duration', + property: '$session_duration', type: 'session', }, ], diff --git a/frontend/src/scenes/insights/utils.tsx b/frontend/src/scenes/insights/utils.tsx index 418821d2329f5..b0bb7446125ed 100644 --- a/frontend/src/scenes/insights/utils.tsx +++ b/frontend/src/scenes/insights/utils.tsx @@ -242,7 +242,7 @@ function formatNumericBreakdownLabel( return ( formatPropertyValueForDisplay( - nestedBreakdown?.value ?? breakdownFilter?.breakdown, + nestedBreakdown?.property ?? breakdownFilter?.breakdown, breakdown_value, propertyFilterTypeToPropertyDefinitionType(nestedBreakdown?.type ?? breakdownFilter?.breakdown_type) )?.toString() ?? 'None' diff --git a/frontend/src/scenes/insights/utils/cleanFilters.test.ts b/frontend/src/scenes/insights/utils/cleanFilters.test.ts index 4f40cb338f15f..ea0cb6156f1f6 100644 --- a/frontend/src/scenes/insights/utils/cleanFilters.test.ts +++ b/frontend/src/scenes/insights/utils/cleanFilters.test.ts @@ -64,7 +64,15 @@ describe('cleanFilters', () => { breakdown_type: 'event', } as TrendsFilterType) - expect(cleanedFilters).toHaveProperty('breakdown_normalize_url', true) + expect(cleanedFilters).toMatchObject({ + breakdowns: [ + { + property: '$current_url', + type: 'event', + normalize_url: true, + }, + ], + }) }) it('defaults to normalizing URL for breakdown by $current_url', () => { @@ -82,7 +90,15 @@ describe('cleanFilters', () => { breakdown_type: 'event', } as TrendsFilterType) - expect(cleanedFilters).toHaveProperty('breakdown_normalize_url', true) + expect(cleanedFilters).toMatchObject({ + breakdowns: [ + { + property: '$pathname', + type: 'event', + normalize_url: true, + }, + ], + }) }) it('defaults to normalizing URL for breakdown by $pathname', () => { @@ -172,39 +188,29 @@ describe('cleanFilters', () => { it('keeps multiple breakdowns', () => { const cleanedFilters = cleanFilters({ - breakdowns: [{ value: 'any', type: 'event' }], + breakdowns: [{ property: 'any', type: 'event' }], insight: InsightType.TRENDS, } as TrendsFilterType) - expect(cleanedFilters).toHaveProperty('breakdowns', [{ value: 'any', type: 'event' }]) + expect(cleanedFilters).toHaveProperty('breakdowns', [{ property: 'any', type: 'event' }]) }) it('keeps normalize_url for multiple breakdowns', () => { const cleanedFilters = cleanFilters({ - breakdowns: [{ value: '$current_url', type: 'event', normalize_url: true }], + breakdowns: [{ property: '$current_url', type: 'event', normalize_url: true }], insight: InsightType.TRENDS, } as TrendsFilterType) expect(cleanedFilters).toHaveProperty('breakdowns', [ - { value: '$current_url', type: 'event', normalize_url: true }, + { property: '$current_url', type: 'event', normalize_url: true }, ]) cleanedFilters.breakdowns![0].normalize_url = false expect(cleanedFilters).toHaveProperty('breakdowns', [ - { value: '$current_url', type: 'event', normalize_url: false }, + { property: '$current_url', type: 'event', normalize_url: false }, ]) }) - it('restores legacy multiple breakdowns', () => { - const cleanedFilters = cleanFilters({ - breakdowns: [{ property: 'any', type: 'event' }], - insight: InsightType.TRENDS, - } as TrendsFilterType) - - expect(cleanedFilters).toHaveProperty('breakdown', 'any') - expect(cleanedFilters).toHaveProperty('breakdown_type', 'event') - }) - it('restores a breakdown type for legacy multiple breakdowns', () => { const cleanedFilters = cleanFilters({ breakdowns: [{ property: 'any' }], @@ -212,20 +218,19 @@ describe('cleanFilters', () => { insight: InsightType.TRENDS, } as TrendsFilterType) - expect(cleanedFilters).toHaveProperty('breakdown', 'any') - expect(cleanedFilters).toHaveProperty('breakdown_type', 'event') - expect(cleanedFilters).toHaveProperty('breakdowns', undefined) + expect(cleanedFilters).toHaveProperty('breakdowns', [{ property: 'any', type: 'event' }]) + expect(cleanedFilters.breakdown_type).toBeUndefined() }) it('cleans a breakdown when multiple breakdowns are used', () => { const cleanedFilters = cleanFilters({ - breakdowns: [{ value: 'any', type: 'event' }], + breakdowns: [{ property: 'any', type: 'event' }], breakdown_type: 'event', breakdown: 'test', insight: InsightType.TRENDS, } as TrendsFilterType) - expect(cleanedFilters).toHaveProperty('breakdowns', [{ value: 'any', type: 'event' }]) + expect(cleanedFilters).toHaveProperty('breakdowns', [{ property: 'any', type: 'event' }]) expect(cleanedFilters).toHaveProperty('breakdown_type', undefined) expect(cleanedFilters).toHaveProperty('breakdown', undefined) }) @@ -279,7 +284,7 @@ describe('cleanFilters', () => { expect(cleanedFilters).toHaveProperty('breakdown_group_type_index', undefined) }) - it('keeps the first multi property filter for trends', () => { + it('keeps a multi property breakdown for trends', () => { const cleanedFilters = cleanFilters({ breakdowns: [ { property: 'one thing', type: 'event' }, @@ -289,10 +294,11 @@ describe('cleanFilters', () => { insight: InsightType.TRENDS, }) - expect(cleanedFilters).toHaveProperty('breakdowns', undefined) - expect(cleanedFilters).toHaveProperty('breakdown', 'one thing') - expect(cleanedFilters).toHaveProperty('breakdown_type', 'event') - expect(cleanedFilters).toHaveProperty('breakdown_group_type_index', undefined) + expect(cleanedFilters).toHaveProperty('breakdowns', [ + { property: 'one thing', type: 'event' }, + { property: 'two thing', type: 'event' }, + ]) + expect(cleanedFilters.breakdown_type).toBeUndefined() }) it('reads "smoothing_intervals" and "interval" from URL when viewing and corrects bad pairings', () => { diff --git a/frontend/src/scenes/insights/utils/cleanFilters.ts b/frontend/src/scenes/insights/utils/cleanFilters.ts index 8f6dea7261a6a..1fc283cf996b2 100644 --- a/frontend/src/scenes/insights/utils/cleanFilters.ts +++ b/frontend/src/scenes/insights/utils/cleanFilters.ts @@ -157,29 +157,20 @@ const cleanBreakdownParams = (cleanedParams: Partial, filters: Parti if (canMultiPropertyBreakdown && filters.breakdowns && filters.breakdowns.length > 0) { cleanedParams['breakdowns'] = filters.breakdowns } else if (isTrends && filters.breakdowns && filters.breakdowns.length > 0) { - // Clean up a legacy breakdown - if (filters.breakdowns[0].property) { - cleanedParams['breakdown'] = filters.breakdowns[0].property - cleanedParams['breakdown_type'] = filters.breakdowns[0].type || filters.breakdown_type - cleanedParams['breakdown_normalize_url'] = cleanBreakdownNormalizeURL( - cleanedParams['breakdown'] as string, - filters.breakdown_normalize_url - ) - } else { - cleanedParams['breakdown_type'] = undefined - cleanedParams['breakdowns'] = filters.breakdowns - .map((b) => ({ - value: b.value, - type: b.type, - histogram_bin_count: b.histogram_bin_count, - group_type_index: b.group_type_index, - normalize_url: - b.normalize_url && b.value - ? cleanBreakdownNormalizeURL(b.value, filters.breakdown_normalize_url) - : b.normalize_url, - })) - .filter((b) => !!b.value) - } + cleanedParams['breakdown_type'] = undefined + cleanedParams['breakdowns'] = filters.breakdowns.map((b) => ({ + property: b.property, + type: b.type || filters.breakdown_type || 'event', + histogram_bin_count: b.histogram_bin_count, + group_type_index: b.group_type_index, + normalize_url: + typeof b.property === 'string' + ? cleanBreakdownNormalizeURL( + b.property, + typeof b.normalize_url === 'boolean' ? b.normalize_url : filters.breakdown_normalize_url + ) + : undefined, + })) } else if (filters.breakdown) { cleanedParams['breakdown'] = filters.breakdown cleanedParams['breakdown_normalize_url'] = cleanBreakdownNormalizeURL( diff --git a/frontend/src/scenes/insights/utils/compareFilters.test.ts b/frontend/src/scenes/insights/utils/compareFilters.test.ts index 9a9a7d933c632..ff8d3b0fe8c43 100644 --- a/frontend/src/scenes/insights/utils/compareFilters.test.ts +++ b/frontend/src/scenes/insights/utils/compareFilters.test.ts @@ -129,7 +129,7 @@ describe('compareFilters', () => { insight: InsightType.TRENDS, breakdowns: [ { - value: '$browser', + property: '$browser', type: 'event', }, ], @@ -143,11 +143,11 @@ describe('compareFilters', () => { insight: InsightType.TRENDS, breakdowns: [ { - value: '$browser', + property: '$browser', type: 'event', }, { - value: '$prop', + property: '$prop', type: 'event', }, ], @@ -156,7 +156,7 @@ describe('compareFilters', () => { insight: InsightType.TRENDS, breakdowns: [ { - value: '$browser', + property: '$browser', type: 'event', }, ], @@ -170,11 +170,11 @@ describe('compareFilters', () => { insight: InsightType.TRENDS, breakdowns: [ { - value: '$browser', + property: '$browser', type: 'event', }, { - value: '$prop', + property: '$prop', type: 'event', }, ], @@ -183,11 +183,11 @@ describe('compareFilters', () => { insight: InsightType.TRENDS, breakdowns: [ { - value: '$browser', + property: '$browser', type: 'event', }, { - value: '$prop', + property: '$prop', type: 'event', }, ], @@ -201,13 +201,13 @@ describe('compareFilters', () => { insight: InsightType.TRENDS, breakdowns: [ { - value: '$browser', + property: '$browser', type: 'group', group_type_index: 1, histogram_bin_count: 10, }, { - value: '$prop', + property: '$pathname', type: 'group', normalize_url: true, }, @@ -217,13 +217,13 @@ describe('compareFilters', () => { insight: InsightType.TRENDS, breakdowns: [ { - value: '$browser', + property: '$browser', type: 'group', group_type_index: 1, histogram_bin_count: 10, }, { - value: '$prop', + property: '$pathname', type: 'group', normalize_url: false, }, @@ -236,13 +236,13 @@ describe('compareFilters', () => { insight: InsightType.TRENDS, breakdowns: [ { - value: '$browser', + property: '$browser', type: 'group', group_type_index: 0, histogram_bin_count: 10, }, { - value: '$prop', + property: '$pathname', type: 'group', normalize_url: true, }, diff --git a/frontend/src/scenes/insights/views/InsightsTable/InsightsTable.tsx b/frontend/src/scenes/insights/views/InsightsTable/InsightsTable.tsx index a4f4fb23d7284..0917ebf802747 100644 --- a/frontend/src/scenes/insights/views/InsightsTable/InsightsTable.tsx +++ b/frontend/src/scenes/insights/views/InsightsTable/InsightsTable.tsx @@ -180,7 +180,7 @@ export function InsightsTable({ ) columns.push({ - title: {breakdown.value?.toString()}, + title: {breakdown.property?.toString()}, render: (_, item) => ( ), - key: `breakdown-${breakdown.value?.toString() || index}`, + key: `breakdown-${breakdown.property?.toString() || index}`, sorter: (a, b) => { const leftValue = Array.isArray(a.breakdown_value) ? a.breakdown_value[index] : a.breakdown_value const rightValue = Array.isArray(b.breakdown_value) ? b.breakdown_value[index] : b.breakdown_value diff --git a/frontend/src/scenes/notebooks/Nodes/NotebookNodePlaylist.tsx b/frontend/src/scenes/notebooks/Nodes/NotebookNodePlaylist.tsx index 555ac064846b3..b97ddaca04e92 100644 --- a/frontend/src/scenes/notebooks/Nodes/NotebookNodePlaylist.tsx +++ b/frontend/src/scenes/notebooks/Nodes/NotebookNodePlaylist.tsx @@ -1,10 +1,8 @@ import { createPostHogWidgetNode } from 'scenes/notebooks/Nodes/NodeWrapper' -import { FilterType, NotebookNodeType, RecordingFilters, RecordingUniversalFilters, ReplayTabs } from '~/types' +import { FilterType, NotebookNodeType, RecordingUniversalFilters, ReplayTabs } from '~/types' import { - DEFAULT_SIMPLE_RECORDING_FILTERS, SessionRecordingPlaylistLogicProps, convertLegacyFiltersToUniversalFilters, - getDefaultFilters, sessionRecordingsPlaylistLogic, } from 'scenes/session-recordings/playlist/sessionRecordingsPlaylistLogic' import { BuiltLogic, useActions, useValues } from 'kea' @@ -12,33 +10,27 @@ import { useEffect, useMemo } from 'react' import { urls } from 'scenes/urls' import { notebookNodeLogic } from './notebookNodeLogic' import { JSONContent, NotebookNodeProps, NotebookNodeAttributeProperties } from '../Notebook/utils' -import { SessionRecordingsFilters } from 'scenes/session-recordings/filters/SessionRecordingsFilters' import { ErrorBoundary } from '@sentry/react' import { SessionRecordingsPlaylist } from 'scenes/session-recordings/playlist/SessionRecordingsPlaylist' import { sessionRecordingPlayerLogic } from 'scenes/session-recordings/player/sessionRecordingPlayerLogic' import { IconComment } from 'lib/lemon-ui/icons' import { sessionRecordingPlayerLogicType } from 'scenes/session-recordings/player/sessionRecordingPlayerLogicType' -import { useFeatureFlag } from 'lib/hooks/useFeatureFlag' import { RecordingsUniversalFilters } from 'scenes/session-recordings/filters/RecordingsUniversalFilters' const Component = ({ attributes, updateAttributes, }: NotebookNodeProps): JSX.Element => { - const { filters, simpleFilters, pinned, nodeId, universalFilters } = attributes + const { pinned, nodeId, universalFilters } = attributes const playerKey = `notebook-${nodeId}` const recordingPlaylistLogicProps: SessionRecordingPlaylistLogicProps = useMemo( () => ({ logicKey: playerKey, - advancedFilters: filters, - simpleFilters, - universalFilters, + filters: universalFilters, updateSearchParams: false, autoPlay: false, - onFiltersChange: (newFilters, legacyFilters) => { - updateAttributes({ universalFilters: newFilters, filters: legacyFilters }) - }, + onFiltersChange: (newFilters) => updateAttributes({ universalFilters: newFilters }), pinnedRecordings: pinned, onPinnedChange(recording, isPinned) { updateAttributes({ @@ -48,7 +40,7 @@ const Component = ({ }) }, }), - [playerKey, filters, pinned] + [playerKey, universalFilters, pinned] ) const { setActions, insertAfter, insertReplayCommentByTimestamp, setMessageListeners, scrollIntoView } = @@ -120,30 +112,15 @@ export const Settings = ({ attributes, updateAttributes, }: NotebookNodeAttributeProperties): JSX.Element => { - const { filters, simpleFilters, universalFilters } = attributes - const defaultFilters = getDefaultFilters() - const hasUniversalFiltering = useFeatureFlag('SESSION_REPLAY_UNIVERSAL_FILTERS') + const { universalFilters: filters } = attributes - const setUniversalFilters = (filters: Partial): void => { - updateAttributes({ universalFilters: { ...universalFilters, ...filters } }) + const setFilters = (newFilters: Partial): void => { + updateAttributes({ universalFilters: { ...filters, ...newFilters } }) } return ( - {hasUniversalFiltering ? ( - - ) : ( - updateAttributes({ filters })} - setSimpleFilters={(simpleFilters) => updateAttributes({ simpleFilters })} - showPropertyFilters - onReset={() => - updateAttributes({ filters: defaultFilters, simpleFilters: DEFAULT_SIMPLE_RECORDING_FILTERS }) - } - /> - )} + ) } @@ -151,9 +128,6 @@ export const Settings = ({ export type NotebookNodePlaylistAttributes = { universalFilters: RecordingUniversalFilters pinned?: string[] - // TODO: these filters are now deprecated and will be removed once we rollout universal filters to everyone - filters: RecordingFilters - simpleFilters?: RecordingFilters } export const NotebookNodePlaylist = createPostHogWidgetNode({ @@ -163,17 +137,11 @@ export const NotebookNodePlaylist = createPostHogWidgetNode { // TODO: Fix parsing of attrs - return urls.replay(undefined, attrs.filters) + return urls.replay(undefined, attrs.universalFilters) }, resizeable: true, expandable: false, attributes: { - filters: { - default: undefined, - }, - simpleFilters: { - default: {}, - }, universalFilters: { default: undefined, }, diff --git a/frontend/src/scenes/notebooks/Notebook/migrations/migrate.ts b/frontend/src/scenes/notebooks/Notebook/migrations/migrate.ts index b48491ffdbb91..b90653f251135 100644 --- a/frontend/src/scenes/notebooks/Notebook/migrations/migrate.ts +++ b/frontend/src/scenes/notebooks/Notebook/migrations/migrate.ts @@ -33,7 +33,7 @@ import { TrendsFilter, TrendsFilterLegacy, } from '~/queries/schema' -import { FunnelExclusionLegacy, NotebookNodeType, NotebookType } from '~/types' +import { FunnelExclusionLegacy, NotebookNodeType, NotebookType, RecordingFilters } from '~/types' // NOTE: Increment this number when you add a new content migration // It will bust the cache on the localContent in the notebookLogic @@ -61,7 +61,11 @@ function convertPlaylistFiltersToUniversalFilters(content: JSONContent[]): JSONC return node } - const { simpleFilters, filters, universalFilters } = node.attrs as NotebookNodePlaylistAttributes + // Legacy attrs on Notebook playlist nodes + const simpleFilters = node.attrs?.simpleFilters as RecordingFilters + const filters = node.attrs?.filters as RecordingFilters + + const { universalFilters } = node.attrs as NotebookNodePlaylistAttributes if (universalFilters) { return node diff --git a/frontend/src/scenes/pipeline/Transformations.tsx b/frontend/src/scenes/pipeline/Transformations.tsx index a748d9b3a61a3..48f7fecb1d8d5 100644 --- a/frontend/src/scenes/pipeline/Transformations.tsx +++ b/frontend/src/scenes/pipeline/Transformations.tsx @@ -2,7 +2,7 @@ import { DndContext, DragEndEvent } from '@dnd-kit/core' import { restrictToParentElement, restrictToVerticalAxis } from '@dnd-kit/modifiers' import { arrayMove, SortableContext, useSortable, verticalListSortingStrategy } from '@dnd-kit/sortable' import { CSS } from '@dnd-kit/utilities' -import { LemonBadge, LemonButton, LemonModal, LemonTable, LemonTableColumn } from '@posthog/lemon-ui' +import { LemonBadge, LemonButton, LemonModal, LemonTable, LemonTableColumn, Link } from '@posthog/lemon-ui' import { useActions, useValues } from 'kea' import { PageHeader } from 'lib/components/PageHeader' import { ProductIntroduction } from 'lib/components/ProductIntroduction/ProductIntroduction' @@ -10,9 +10,11 @@ import { More } from 'lib/lemon-ui/LemonButton/More' import { LemonMenuOverlay } from 'lib/lemon-ui/LemonMenu/LemonMenu' import { statusColumn, updatedAtColumn } from 'lib/lemon-ui/LemonTable/columnUtils' import { PluginImage } from 'scenes/plugins/plugin/PluginImage' +import { urls } from 'scenes/urls' -import { PipelineStage, ProductKey } from '~/types' +import { PipelineNodeTab, PipelineStage, ProductKey } from '~/types' +import { AppMetricSparkLine } from './AppMetricSparkLine' import { NewButton } from './NewButton' import { pipelineAccessLogic } from './pipelineAccessLogic' import { pipelineTransformationsLogic } from './transformationsLogic' @@ -90,6 +92,22 @@ export function TransformationsTable({ inOverview = false }: { inOverview?: bool }, appColumn() as LemonTableColumn, nameColumn() as LemonTableColumn, + { + title: 'Weekly volume', + render: function RenderSuccessRate(_, transformation) { + return ( + + + + ) + }, + }, updatedAtColumn() as LemonTableColumn, statusColumn() as LemonTableColumn, { diff --git a/frontend/src/scenes/pipeline/pipelinePluginConfigurationLogic.tsx b/frontend/src/scenes/pipeline/pipelinePluginConfigurationLogic.tsx index b8fded3120c81..9e7d981624e25 100644 --- a/frontend/src/scenes/pipeline/pipelinePluginConfigurationLogic.tsx +++ b/frontend/src/scenes/pipeline/pipelinePluginConfigurationLogic.tsx @@ -148,7 +148,8 @@ export const pipelinePluginConfigurationLogic = kea = { [urls.dataWarehouse()]: Scene.DataWarehouse, [urls.dataWarehouseView(':id')]: Scene.DataWarehouse, [urls.dataWarehouseTable()]: Scene.DataWarehouseTable, - [urls.dataWarehouseSettings(':tab')]: Scene.DataWarehouseSettings, + [urls.dataWarehouseSettings(':tab')]: Scene.DataWarehouse, [urls.dataWarehouseRedirect(':kind')]: Scene.DataWarehouseRedirect, [urls.dataWarehouseSourceSettings(':id', ':tab')]: Scene.dataWarehouseSourceSettings, [urls.featureFlags()]: Scene.FeatureFlags, diff --git a/frontend/src/scenes/session-recordings/SessionRecordings.tsx b/frontend/src/scenes/session-recordings/SessionRecordings.tsx index 890a81d1cb059..b82171522363b 100644 --- a/frontend/src/scenes/session-recordings/SessionRecordings.tsx +++ b/frontend/src/scenes/session-recordings/SessionRecordings.tsx @@ -7,17 +7,13 @@ import { PageHeader } from 'lib/components/PageHeader' import { upgradeModalLogic } from 'lib/components/UpgradeModal/upgradeModalLogic' import { VersionCheckerBanner } from 'lib/components/VersionChecker/VersionCheckerBanner' import { useAsyncHandler } from 'lib/hooks/useAsyncHandler' -import { useFeatureFlag } from 'lib/hooks/useFeatureFlag' import { LemonBanner } from 'lib/lemon-ui/LemonBanner' import { LemonTabs } from 'lib/lemon-ui/LemonTabs' import { Spinner } from 'lib/lemon-ui/Spinner/Spinner' import { eventUsageLogic } from 'lib/utils/eventUsageLogic' import { NotebookSelectButton } from 'scenes/notebooks/NotebookSelectButton/NotebookSelectButton' import { SceneExport } from 'scenes/sceneTypes' -import { - convertUniversalFiltersToLegacyFilters, - sessionRecordingsPlaylistLogic, -} from 'scenes/session-recordings/playlist/sessionRecordingsPlaylistLogic' +import { sessionRecordingsPlaylistLogic } from 'scenes/session-recordings/playlist/sessionRecordingsPlaylistLogic' import { teamLogic } from 'scenes/teamLogic' import { urls } from 'scenes/urls' @@ -39,15 +35,13 @@ function Header(): JSX.Element { const { currentTeam } = useValues(teamLogic) const recordingsDisabled = currentTeam && !currentTeam?.session_recording_opt_in const { reportRecordingPlaylistCreated } = useActions(eventUsageLogic) - const hasUniversalFiltering = useFeatureFlag('SESSION_REPLAY_UNIVERSAL_FILTERS') const { openSettingsPanel } = useActions(sidePanelSettingsLogic) // NB this relies on `updateSearchParams` being the only prop needed to pick the correct "Recent" tab list logic const { filters, totalFiltersCount } = useValues(sessionRecordingsPlaylistLogic({ updateSearchParams: true })) const saveFiltersPlaylistHandler = useAsyncHandler(async () => { - const existingFilters = hasUniversalFiltering ? filters : convertUniversalFiltersToLegacyFilters(filters) - await createPlaylist({ filters: existingFilters }, true) + await createPlaylist({ filters }, true) reportRecordingPlaylistCreated('filters') }) diff --git a/frontend/src/scenes/session-recordings/detail/SessionRecordingScene.scss b/frontend/src/scenes/session-recordings/detail/SessionRecordingScene.scss index a5928a38658ea..0973ee37587bb 100644 --- a/frontend/src/scenes/session-recordings/detail/SessionRecordingScene.scss +++ b/frontend/src/scenes/session-recordings/detail/SessionRecordingScene.scss @@ -1,6 +1,6 @@ .SessionRecordingScene { .SessionRecordingPlayer { - height: calc(100vh - 12rem); - min-height: 30rem; + height: calc(100vh - 6rem); + min-height: 25rem; } } diff --git a/frontend/src/scenes/session-recordings/filters/AdvancedSessionRecordingsFilters.tsx b/frontend/src/scenes/session-recordings/filters/AdvancedSessionRecordingsFilters.tsx deleted file mode 100644 index 2dc0a86c7fc63..0000000000000 --- a/frontend/src/scenes/session-recordings/filters/AdvancedSessionRecordingsFilters.tsx +++ /dev/null @@ -1,224 +0,0 @@ -import { LemonButtonWithDropdown, LemonCheckbox, LemonInput } from '@posthog/lemon-ui' -import { useValues } from 'kea' -import { DateFilter } from 'lib/components/DateFilter/DateFilter' -import { PropertyFilters } from 'lib/components/PropertyFilters/PropertyFilters' -import { TaxonomicFilterGroupType } from 'lib/components/TaxonomicFilter/types' -import { TestAccountFilterSwitch } from 'lib/components/TestAccountFiltersSwitch' -import { LemonLabel } from 'lib/lemon-ui/LemonLabel/LemonLabel' -import { ActionFilter } from 'scenes/insights/filters/ActionFilter/ActionFilter' -import { MathAvailability } from 'scenes/insights/filters/ActionFilter/ActionFilterRow/ActionFilterRow' -import { defaultRecordingDurationFilter } from 'scenes/session-recordings/playlist/sessionRecordingsPlaylistLogic' - -import { groupsModel } from '~/models/groupsModel' -import { EntityTypes, FilterableLogLevel, RecordingFilters } from '~/types' - -import { DurationFilter } from './DurationFilter' - -function DateAndDurationFilters({ - filters, - setFilters, -}: { - filters: RecordingFilters - setFilters: (filters: RecordingFilters) => void -}): JSX.Element { - return ( -
- Time and duration -
- { - setFilters({ - date_from: changedDateFrom, - date_to: changedDateTo, - }) - }} - dateOptions={[ - { key: 'Custom', values: [] }, - { key: 'Last 24 hours', values: ['-24h'] }, - { key: 'Last 3 days', values: ['-3d'] }, - { key: 'Last 7 days', values: ['-7d'] }, - { key: 'Last 30 days', values: ['-30d'] }, - { key: 'All time', values: ['-90d'] }, - ]} - dropdownPlacement="bottom-start" - /> - { - setFilters({ - session_recording_duration: newRecordingDurationFilter, - duration_type_filter: newDurationType, - }) - }} - recordingDurationFilter={filters.session_recording_duration || defaultRecordingDurationFilter} - durationTypeFilter={filters.duration_type_filter || 'duration'} - pageKey="session-recordings" - /> -
-
- ) -} - -export const AdvancedSessionRecordingsFilters = ({ - filters, - setFilters, - showPropertyFilters, -}: { - filters: RecordingFilters - setFilters: (filters: RecordingFilters) => void - showPropertyFilters?: boolean -}): JSX.Element => { - const { groupsTaxonomicTypes } = useValues(groupsModel) - - const allowedPropertyTaxonomyTypes = [ - TaxonomicFilterGroupType.EventProperties, - TaxonomicFilterGroupType.EventFeatureFlags, - TaxonomicFilterGroupType.Elements, - TaxonomicFilterGroupType.HogQLExpression, - ...groupsTaxonomicTypes, - ] - - allowedPropertyTaxonomyTypes.push(TaxonomicFilterGroupType.SessionProperties) - - const addFilterTaxonomyTypes = [TaxonomicFilterGroupType.PersonProperties, TaxonomicFilterGroupType.Cohorts] - addFilterTaxonomyTypes.push(TaxonomicFilterGroupType.SessionProperties) - - return ( -
- - Events and actions - - - { - setFilters({ - events: payload.events || [], - actions: payload.actions || [], - }) - }} - typeKey="session-recordings" - mathAvailability={MathAvailability.None} - hideRename - hideDuplicate - showNestedArrow={false} - actionsTaxonomicGroupTypes={[TaxonomicFilterGroupType.Actions, TaxonomicFilterGroupType.Events]} - propertiesTaxonomicGroupTypes={allowedPropertyTaxonomyTypes} - propertyFiltersPopover - addFilterDefaultOptions={{ - id: '$pageview', - name: '$pageview', - type: EntityTypes.EVENTS, - }} - buttonProps={{ type: 'secondary', size: 'small' }} - /> - - - Properties - - - setFilters({ filter_test_accounts: val })} - fullWidth - /> - - {showPropertyFilters && ( - { - setFilters({ properties }) - }} - /> - )} - - - - -
- ) -} - -function ConsoleFilters({ - filters, - setFilters, -}: { - filters: RecordingFilters - setFilters: (filters: RecordingFilters) => void -}): JSX.Element { - function updateLevelChoice(checked: boolean, level: FilterableLogLevel): void { - const newChoice = filters.console_logs?.filter((c) => c !== level) || [] - if (checked) { - setFilters({ - console_logs: [...newChoice, level], - }) - } else { - setFilters({ - console_logs: newChoice, - }) - } - } - - return ( - <> - Console logs -
- { - setFilters({ - console_search_query: s, - }) - }} - /> -
- - { - updateLevelChoice(checked, 'info') - }} - label="info" - /> - updateLevelChoice(checked, 'warn')} - label="warn" - /> - updateLevelChoice(checked, 'error')} - label="error" - /> - , - ], - actionable: true, - }} - > - {filters.console_logs?.map((x) => `console.${x}`).join(' or ') || ( - Console types to filter for... - )} - - - ) -} diff --git a/frontend/src/scenes/session-recordings/filters/SessionRecordingsFilters.tsx b/frontend/src/scenes/session-recordings/filters/SessionRecordingsFilters.tsx deleted file mode 100644 index 2af4cfe621c33..0000000000000 --- a/frontend/src/scenes/session-recordings/filters/SessionRecordingsFilters.tsx +++ /dev/null @@ -1,106 +0,0 @@ -import { LemonButton, LemonCollapse } from '@posthog/lemon-ui' -import equal from 'fast-deep-equal' -import { useActions, useValues } from 'kea' -import { useFeatureFlag } from 'lib/hooks/useFeatureFlag' -import { LemonLabel } from 'lib/lemon-ui/LemonLabel/LemonLabel' -import { useMemo } from 'react' - -import { RecordingFilters } from '~/types' - -import { playerSettingsLogic } from '../player/playerSettingsLogic' -import { getDefaultFilters } from '../playlist/sessionRecordingsPlaylistLogic' -import { AdvancedSessionRecordingsFilters } from './AdvancedSessionRecordingsFilters' -import { OrderingFilters } from './OrderingFilters' -import { SimpleSessionRecordingsFilters } from './SimpleSessionRecordingsFilters' - -interface SessionRecordingsFiltersProps { - advancedFilters: RecordingFilters - simpleFilters: RecordingFilters - setAdvancedFilters: (filters: RecordingFilters) => void - setSimpleFilters: (filters: RecordingFilters) => void - showPropertyFilters?: boolean - hideSimpleFilters?: boolean - onReset?: () => void -} - -export function SessionRecordingsFilters({ - advancedFilters, - simpleFilters, - setAdvancedFilters, - setSimpleFilters, - showPropertyFilters, - hideSimpleFilters, - onReset, -}: SessionRecordingsFiltersProps): JSX.Element { - const { prefersAdvancedFilters } = useValues(playerSettingsLogic) - const { setPrefersAdvancedFilters } = useActions(playerSettingsLogic) - - const initiallyOpen = useMemo(() => { - // advanced always open if not showing simple filters, saves computation - if (hideSimpleFilters) { - return true - } - const defaultFilters = getDefaultFilters() - return prefersAdvancedFilters || !equal(advancedFilters, defaultFilters) - }, []) - const hasFilterOrdering = useFeatureFlag('SESSION_REPLAY_FILTER_ORDERING') - - const AdvancedFilters = ( - - ) - - const panels = [ - !hideSimpleFilters && { - key: 'advanced-filters', - header: 'Advanced filters', - className: 'p-0', - content: AdvancedFilters, - }, - hasFilterOrdering && { - key: 'ordering', - header: 'Ordering', - content: , - }, - ].filter(Boolean) - - return ( -
-
-
- Find sessions: - - {onReset && ( - - - Reset - - - )} -
- - {!hideSimpleFilters && ( - - )} -
- - {hideSimpleFilters && AdvancedFilters} - - {panels.length > 0 && ( - { - setPrefersAdvancedFilters(activeKeys.includes('advanced-filters')) - }} - /> - )} -
- ) -} diff --git a/frontend/src/scenes/session-recordings/filters/SimpleSessionRecordingsFilters.tsx b/frontend/src/scenes/session-recordings/filters/SimpleSessionRecordingsFilters.tsx deleted file mode 100644 index f03ed52112f44..0000000000000 --- a/frontend/src/scenes/session-recordings/filters/SimpleSessionRecordingsFilters.tsx +++ /dev/null @@ -1,218 +0,0 @@ -import { IconGear, IconTrash } from '@posthog/icons' -import { LemonButton, LemonMenu, Popover } from '@posthog/lemon-ui' -import { useActions, useValues } from 'kea' -import { PropertyFilters } from 'lib/components/PropertyFilters/PropertyFilters' -import { PropertyKeyInfo } from 'lib/components/PropertyKeyInfo' -import { TaxonomicFilter } from 'lib/components/TaxonomicFilter/TaxonomicFilter' -import { TaxonomicFilterGroupType } from 'lib/components/TaxonomicFilter/types' -import { useMemo, useRef, useState } from 'react' - -import { EntityTypes, EventPropertyFilter, PropertyFilterType, PropertyOperator, RecordingFilters } from '~/types' - -import { playerSettingsLogic } from '../player/playerSettingsLogic' - -export const SimpleSessionRecordingsFilters = ({ - filters, - setFilters, -}: { - filters: RecordingFilters - setFilters: (filters: RecordingFilters) => void -}): JSX.Element => { - const { quickFilterProperties } = useValues(playerSettingsLogic) - const { setQuickFilterProperties } = useActions(playerSettingsLogic) - const [showPropertySelector, setShowPropertySelector] = useState(false) - const buttonRef = useRef(null) - - const pageviewEvent = filters.events?.find((event) => event.id === '$pageview') - - const personProperties = filters.properties || [] - const eventProperties = pageviewEvent?.properties || [] - - const onClickPersonProperty = (key: string): void => { - setFilters({ - ...filters, - properties: [ - ...personProperties, - { type: PropertyFilterType.Person, key: key, value: null, operator: PropertyOperator.Exact }, - ], - }) - } - - const onClickCurrentUrl = (): void => { - const events = filters.events || [] - setFilters({ - ...filters, - events: [ - ...events, - { - id: '$pageview', - name: '$pageview', - type: EntityTypes.EVENTS, - properties: [ - { - type: PropertyFilterType.Event, - key: '$current_url', - value: null, - operator: PropertyOperator.Exact, - }, - ], - }, - ], - }) - } - - const defaultItems = useMemo(() => { - const eventKeys = eventProperties.map((p: EventPropertyFilter) => p.key) - - return [ - !eventKeys.includes('$current_url') && { - label: , - key: '$current_url', - onClick: onClickCurrentUrl, - }, - ].filter(Boolean) - }, [eventProperties]) - - const personPropertyItems = useMemo(() => { - const personKeys = personProperties.map((p) => p.key) - - return quickFilterProperties - .map((property) => { - return ( - !personKeys.includes(property) && { - label: , - key: property, - onClick: () => onClickPersonProperty(property), - } - ) - }) - .filter(Boolean) - }, [quickFilterProperties, personProperties]) - - return ( -
-
- setFilters({ properties })} - allowNew={false} - openOnInsert - /> - { - setFilters({ - ...filters, - events: - properties.length > 0 - ? [ - { - id: '$pageview', - name: '$pageview', - type: EntityTypes.EVENTS, - properties: properties, - }, - ] - : [], - }) - }} - allowNew={false} - openOnInsert - /> - setShowPropertySelector(false)} - overlay={ - { - setQuickFilterProperties(value) - }} - /> - } - referenceElement={buttonRef.current} - placement="right-start" - > - 0 && { items: defaultItems }, - personPropertyItems.length > 0 && { - title: 'Person properties', - items: personPropertyItems, - }, - ]} - onVisibilityChange={() => setShowPropertySelector(false)} - > - , - tooltip: 'Edit properties', - onClick: () => setShowPropertySelector(true), - }} - > - Choose quick filter - - - -
-
- ) -} - -const Configuration = ({ - properties, - onChange, -}: { - properties: string[] - onChange: (properties: string[]) => void -}): JSX.Element => { - const [showPropertySelector, setShowPropertySelector] = useState(false) - - return ( -
- {properties.map((property) => ( -
- - - - } - onClick={() => { - const newProperties = properties.filter((p) => p != property) - onChange(newProperties) - }} - /> -
- ))} - setShowPropertySelector(false)} - placement="right-start" - overlay={ - { - properties.push(value as string) - onChange([...properties]) - setShowPropertySelector(false) - }} - taxonomicGroupTypes={[TaxonomicFilterGroupType.PersonProperties]} - excludedProperties={{ [TaxonomicFilterGroupType.PersonProperties]: properties }} - /> - } - > - setShowPropertySelector(!showPropertySelector)} fullWidth> - Add person properties - - -
- ) -} diff --git a/frontend/src/scenes/session-recordings/player/playerSettingsLogic.ts b/frontend/src/scenes/session-recordings/player/playerSettingsLogic.ts index 89d3d41e7d0cb..7de31840ebca2 100644 --- a/frontend/src/scenes/session-recordings/player/playerSettingsLogic.ts +++ b/frontend/src/scenes/session-recordings/player/playerSettingsLogic.ts @@ -199,7 +199,6 @@ export const playerSettingsLogic = kea([ setSearchQuery: (search: string) => ({ search }), setDurationTypeToShow: (type: DurationType) => ({ type }), setShowFilters: (showFilters: boolean) => ({ showFilters }), - setPrefersAdvancedFilters: (prefersAdvancedFilters: boolean) => ({ prefersAdvancedFilters }), setQuickFilterProperties: (properties: string[]) => ({ properties }), setTimestampFormat: (format: TimestampFormat) => ({ format }), setPreferredInspectorStacking: (stacking: InspectorStacking) => ({ stacking }), @@ -232,15 +231,6 @@ export const playerSettingsLogic = kea([ setPlaybackViewMode: (_, { mode }) => mode, }, ], - prefersAdvancedFilters: [ - true, - { - persist: true, - }, - { - setPrefersAdvancedFilters: (_, { prefersAdvancedFilters }) => prefersAdvancedFilters, - }, - ], quickFilterProperties: [ [...(values.currentTeam?.person_display_name_properties || [])] as string[], { diff --git a/frontend/src/scenes/session-recordings/playlist/SessionRecordingsPlaylist.tsx b/frontend/src/scenes/session-recordings/playlist/SessionRecordingsPlaylist.tsx index 8dee5b119caf9..b0b9d18313f3b 100644 --- a/frontend/src/scenes/session-recordings/playlist/SessionRecordingsPlaylist.tsx +++ b/frontend/src/scenes/session-recordings/playlist/SessionRecordingsPlaylist.tsx @@ -1,11 +1,10 @@ -import { IconFilter, IconGear } from '@posthog/icons' +import { IconGear } from '@posthog/icons' import { LemonButton, Link, Spinner } from '@posthog/lemon-ui' import { BindLogic, useActions, useValues } from 'kea' import { EmptyMessage } from 'lib/components/EmptyMessage/EmptyMessage' import { Playlist, PlaylistSection } from 'lib/components/Playlist/Playlist' import { PropertyKeyInfo } from 'lib/components/PropertyKeyInfo' import { FEATURE_FLAGS } from 'lib/constants' -import { IconWithCount } from 'lib/lemon-ui/icons' import { LemonBanner } from 'lib/lemon-ui/LemonBanner' import { featureFlagLogic } from 'lib/logic/featureFlagLogic' import { useNotebookNode } from 'scenes/notebooks/Nodes/NotebookNodeContext' @@ -14,7 +13,6 @@ import { urls } from 'scenes/urls' import { ReplayTabs, SessionRecordingType } from '~/types' import { RecordingsUniversalFilters } from '../filters/RecordingsUniversalFilters' -import { SessionRecordingsFilters } from '../filters/SessionRecordingsFilters' import { SessionRecordingPlayer } from '../player/SessionRecordingPlayer' import { SessionRecordingPreview } from './SessionRecordingPreview' import { @@ -34,27 +32,14 @@ export function SessionRecordingsPlaylist(props: SessionRecordingPlaylistLogicPr const { filters, pinnedRecordings, - totalFiltersCount, - useUniversalFiltering, matchingEventsMatchType, sessionRecordingsResponseLoading, otherRecordings, sessionSummaryLoading, - advancedFilters, - simpleFilters, activeSessionRecordingId, hasNext, - universalFilters, } = useValues(logic) - const { - maybeLoadSessionRecordings, - summarizeSession, - setSelectedRecordingId, - setAdvancedFilters, - setSimpleFilters, - resetFilters, - setUniversalFilters, - } = useActions(logic) + const { maybeLoadSessionRecordings, summarizeSession, setSelectedRecordingId, setFilters } = useActions(logic) const { featureFlags } = useValues(featureFlagLogic) const isTestingSaved = featureFlags[FEATURE_FLAGS.SAVED_NOT_PINNED] === 'test' @@ -70,30 +55,6 @@ export function SessionRecordingsPlaylist(props: SessionRecordingPlaylistLogicPr summarizeSession(recording.id) } - if (!useUniversalFiltering) { - headerActions.push({ - key: 'filters', - tooltip: 'Filter recordings', - content: ( - - ), - icon: ( - - - - ), - children: 'Filter', - }) - } - headerActions.push({ key: 'settings', tooltip: 'Playlist settings', @@ -146,13 +107,9 @@ export function SessionRecordingsPlaylist(props: SessionRecordingPlaylistLogicPr return (
- {useUniversalFiltering && !notebookNode ? ( - - ) : null} + {!notebookNode && ( + + )} { const { filters, sessionRecordingsAPIErrored, unusableEventsInFilter } = useValues(sessionRecordingsPlaylistLogic) - const { setAdvancedFilters } = useActions(sessionRecordingsPlaylistLogic) + const { setFilters } = useActions(sessionRecordingsPlaylistLogic) return (
@@ -226,11 +183,7 @@ const ListEmptyState = (): JSX.Element => { { - setAdvancedFilters({ - date_from: '-30d', - }) - }} + onClick={() => setFilters({ date_from: '-30d' })} > Search over the last 30 days diff --git a/frontend/src/scenes/session-recordings/playlist/SessionRecordingsPlaylistScene.tsx b/frontend/src/scenes/session-recordings/playlist/SessionRecordingsPlaylistScene.tsx index 02cfdeadecc84..64a6c62143bad 100644 --- a/frontend/src/scenes/session-recordings/playlist/SessionRecordingsPlaylistScene.tsx +++ b/frontend/src/scenes/session-recordings/playlist/SessionRecordingsPlaylistScene.tsx @@ -4,7 +4,6 @@ import { EditableField } from 'lib/components/EditableField/EditableField' import { NotFound } from 'lib/components/NotFound' import { PageHeader } from 'lib/components/PageHeader' import { UserActivityIndicator } from 'lib/components/UserActivityIndicator/UserActivityIndicator' -import { useFeatureFlag } from 'lib/hooks/useFeatureFlag' import { More } from 'lib/lemon-ui/LemonButton/More' import { LemonSkeleton } from 'lib/lemon-ui/LemonSkeleton' import { SceneExport } from 'scenes/sceneTypes' @@ -31,7 +30,6 @@ export function SessionRecordingsPlaylistScene(): JSX.Element { const { showFilters } = useValues(playerSettingsLogic) const { setShowFilters } = useActions(playerSettingsLogic) - const hasUniversalFiltering = useFeatureFlag('SESSION_REPLAY_UNIVERSAL_FILTERS') if (!playlist && playlistLoading) { return ( @@ -136,16 +134,13 @@ export function SessionRecordingsPlaylistScene(): JSX.Element { {playlist.short_id && pinnedRecordings !== null ? (
- setFilters(hasUniversalFiltering ? universalFilters : legacyFilters) - } + onFiltersChange={setFilters} onPinnedChange={onPinnedChange} pinnedRecordings={pinnedRecordings ?? []} /> diff --git a/frontend/src/scenes/session-recordings/playlist/sessionRecordingsPlaylistLogic.test.ts b/frontend/src/scenes/session-recordings/playlist/sessionRecordingsPlaylistLogic.test.ts index 3bd970487e15b..d0104691cfa88 100644 --- a/frontend/src/scenes/session-recordings/playlist/sessionRecordingsPlaylistLogic.test.ts +++ b/frontend/src/scenes/session-recordings/playlist/sessionRecordingsPlaylistLogic.test.ts @@ -3,16 +3,13 @@ import { expectLogic } from 'kea-test-utils' import { useMocks } from '~/mocks/jest' import { initKeaTests } from '~/test/init' -import { FilterLogicalOperator, PropertyFilterType, PropertyOperator, RecordingFilters } from '~/types' +import { FilterLogicalOperator, PropertyFilterType, PropertyOperator } from '~/types' import { sessionRecordingDataLogic } from '../player/sessionRecordingDataLogic' import { convertLegacyFiltersToUniversalFilters, convertUniversalFiltersToLegacyFilters, DEFAULT_RECORDING_FILTERS, - DEFAULT_RECORDING_UNIVERSAL_FILTERS, - DEFAULT_SIMPLE_RECORDING_FILTERS, - defaultRecordingDurationFilter, sessionRecordingsPlaylistLogic, } from './sessionRecordingsPlaylistLogic' @@ -201,120 +198,140 @@ describe('sessionRecordingsPlaylistLogic', () => { describe('entityFilters', () => { it('starts with default values', () => { expectLogic(logic).toMatchValues({ - advancedFilters: DEFAULT_RECORDING_FILTERS, - simpleFilters: DEFAULT_SIMPLE_RECORDING_FILTERS, - universalFilters: DEFAULT_RECORDING_UNIVERSAL_FILTERS, + filters: DEFAULT_RECORDING_FILTERS, }) }) - it('is set by setAdvancedFilters and loads filtered results and sets the url', async () => { + it('is set by setFilters and loads filtered results and sets the url', async () => { await expectLogic(logic, () => { - logic.actions.setAdvancedFilters({ - events: [{ id: '$autocapture', type: 'events', order: 0, name: '$autocapture' }], + logic.actions.setFilters({ + filter_group: { + type: FilterLogicalOperator.And, + values: [ + { + type: FilterLogicalOperator.And, + values: [{ id: '$autocapture', type: 'events', order: 0, name: '$autocapture' }], + }, + ], + }, }) }) - .toDispatchActions(['setAdvancedFilters', 'loadSessionRecordings', 'loadSessionRecordingsSuccess']) + .toDispatchActions(['setFilters', 'loadSessionRecordings', 'loadSessionRecordingsSuccess']) .toMatchValues({ sessionRecordings: ['List of recordings filtered by events'], }) - expect(router.values.searchParams.advancedFilters).toHaveProperty('events', [ - { id: '$autocapture', type: 'events', order: 0, name: '$autocapture' }, - ]) + expect(router.values.searchParams.filters).toHaveProperty('filter_group', { + type: FilterLogicalOperator.And, + values: [ + { + type: FilterLogicalOperator.And, + values: [{ id: '$autocapture', type: 'events', order: 0, name: '$autocapture' }], + }, + ], + }) }) it('reads filters from the logic props', async () => { logic = sessionRecordingsPlaylistLogic({ key: 'tests-with-props', - advancedFilters: { - events: [{ id: '$autocapture', type: 'events', order: 0, name: '$autocapture' }], - }, - simpleFilters: { - properties: [ - { - key: '$geoip_country_name', - value: ['Australia'], - operator: PropertyOperator.Exact, - type: PropertyFilterType.Person, - }, - ], + filters: { + duration: [], + filter_group: { + type: FilterLogicalOperator.And, + values: [ + { + type: FilterLogicalOperator.And, + values: [ + { id: '$autocapture', type: 'events', order: 0, name: '$autocapture' }, + { + key: '$geoip_country_name', + value: ['Australia'], + operator: PropertyOperator.Exact, + type: PropertyFilterType.Person, + }, + ], + }, + ], + }, }, }) logic.mount() await expectLogic(logic).toMatchValues({ - advancedFilters: { - events: [{ id: '$autocapture', type: 'events', order: 0, name: '$autocapture' }], - }, - simpleFilters: { - properties: [ - { key: '$geoip_country_name', value: ['Australia'], operator: 'exact', type: 'person' }, - ], + filters: { + duration: [], + filter_group: { + type: FilterLogicalOperator.And, + values: [ + { + type: FilterLogicalOperator.And, + values: [ + { id: '$autocapture', type: 'events', order: 0, name: '$autocapture' }, + { + key: '$geoip_country_name', + value: ['Australia'], + operator: PropertyOperator.Exact, + type: PropertyFilterType.Person, + }, + ], + }, + ], + }, }, }) }) }) describe('date range', () => { - it('is set by setAdvancedFilters and fetches results from server and sets the url', async () => { + it('is set by setFilters and fetches results from server and sets the url', async () => { await expectLogic(logic, () => { - logic.actions.setAdvancedFilters({ + logic.actions.setFilters({ date_from: '2021-10-05', date_to: '2021-10-20', }) }) .toMatchValues({ - legacyFilters: expect.objectContaining({ - date_from: '2021-10-05', - date_to: '2021-10-20', - }), filters: expect.objectContaining({ date_from: '2021-10-05', date_to: '2021-10-20', }), }) - .toDispatchActions(['setAdvancedFilters', 'loadSessionRecordingsSuccess']) + .toDispatchActions(['setFilters', 'loadSessionRecordingsSuccess']) .toMatchValues({ sessionRecordings: ['Recordings filtered by date'] }) - expect(router.values.searchParams.advancedFilters).toHaveProperty('date_from', '2021-10-05') expect(router.values.searchParams.filters).toHaveProperty('date_from', '2021-10-05') - expect(router.values.searchParams.advancedFilters).toHaveProperty('date_to', '2021-10-20') expect(router.values.searchParams.filters).toHaveProperty('date_to', '2021-10-20') }) }) describe('duration filter', () => { - it('is set by setAdvancedFilters and fetches results from server and sets the url', async () => { + it('is set by setFilters and fetches results from server and sets the url', async () => { await expectLogic(logic, () => { - logic.actions.setAdvancedFilters({ - session_recording_duration: { - type: PropertyFilterType.Recording, - key: 'duration', - value: 600, - operator: PropertyOperator.LessThan, - }, - }) - }) - .toMatchValues({ - legacyFilters: expect.objectContaining({ - session_recording_duration: { + logic.actions.setFilters({ + duration: [ + { type: PropertyFilterType.Recording, key: 'duration', value: 600, operator: PropertyOperator.LessThan, }, - }), + ], + }) + }) + .toMatchValues({ filters: expect.objectContaining({ - duration: [{ key: 'duration', operator: 'lt', type: 'recording', value: 600 }], + duration: [ + { + key: 'duration', + operator: PropertyOperator.LessThan, + type: PropertyFilterType.Recording, + value: 600, + }, + ], }), }) - .toDispatchActions(['setAdvancedFilters', 'loadSessionRecordingsSuccess']) + .toDispatchActions(['setFilters', 'loadSessionRecordingsSuccess']) .toMatchValues({ sessionRecordings: ['Recordings filtered by duration'] }) - expect(router.values.searchParams.advancedFilters).toHaveProperty('session_recording_duration', { - type: PropertyFilterType.Recording, - key: 'duration', - value: 600, - operator: PropertyOperator.LessThan, - }) expect(router.values.searchParams.filters).toHaveProperty('duration', [ { type: PropertyFilterType.Recording, @@ -381,13 +398,21 @@ describe('sessionRecordingsPlaylistLogic', () => { }) }) - it('is set by setAdvancedFilters and loads filtered results', async () => { + it('is set by setFilters and loads filtered results', async () => { await expectLogic(logic, () => { - logic.actions.setAdvancedFilters({ - events: [{ id: '$autocapture', type: 'events', order: 0, name: '$autocapture' }], + logic.actions.setFilters({ + filter_group: { + type: FilterLogicalOperator.And, + values: [ + { + type: FilterLogicalOperator.And, + values: [{ id: '$autocapture', type: 'events', order: 0, name: '$autocapture' }], + }, + ], + }, }) }) - .toDispatchActions(['setAdvancedFilters', 'loadSessionRecordings', 'loadSessionRecordingsSuccess']) + .toDispatchActions(['setFilters', 'loadSessionRecordings', 'loadSessionRecordingsSuccess']) .toMatchValues({ sessionRecordings: ['List of recordings filtered by events'], }) @@ -396,43 +421,29 @@ describe('sessionRecordingsPlaylistLogic', () => { it('reads filters from the URL', async () => { router.actions.push('/replay', { - advancedFilters: { - actions: [{ id: '1', type: 'actions', order: 0, name: 'View Recording' }], - events: [{ id: '$autocapture', type: 'events', order: 0, name: '$autocapture' }], + filters: { date_from: '2021-10-01', date_to: '2021-10-10', - offset: 50, - session_recording_duration: { - type: PropertyFilterType.Recording, - key: 'duration', - value: 600, - operator: PropertyOperator.LessThan, + duration: [{ key: 'duration', operator: 'lt', type: 'recording', value: 600 }], + filter_group: { + type: FilterLogicalOperator.And, + values: [ + { + type: FilterLogicalOperator.And, + values: [ + { id: '$autocapture', name: '$autocapture', order: 0, type: 'events' }, + { id: '1', name: 'View Recording', order: 0, type: 'actions' }, + ], + }, + ], }, - operand: FilterLogicalOperator.And, + filter_test_accounts: false, }, }) await expectLogic(logic) - .toDispatchActions(['setAdvancedFilters']) + .toDispatchActions(['setFilters']) .toMatchValues({ - legacyFilters: { - events: [{ id: '$autocapture', type: 'events', order: 0, name: '$autocapture' }], - actions: [{ id: '1', type: 'actions', order: 0, name: 'View Recording' }], - date_from: '2021-10-01', - date_to: '2021-10-10', - offset: 50, - console_logs: [], - console_search_query: '', - properties: [], - session_recording_duration: { - type: PropertyFilterType.Recording, - key: 'duration', - value: 600, - operator: PropertyOperator.LessThan, - }, - snapshot_source: null, - operand: FilterLogicalOperator.And, - }, filters: { date_from: '2021-10-01', date_to: '2021-10-10', @@ -456,38 +467,31 @@ describe('sessionRecordingsPlaylistLogic', () => { it('reads filters from the URL and defaults the duration filter', async () => { router.actions.push('/replay', { - advancedFilters: { - actions: [{ id: '1', type: 'actions', order: 0, name: 'View Recording' }], + filters: { + filter_group: { + type: FilterLogicalOperator.And, + values: [ + { + type: FilterLogicalOperator.And, + values: [{ id: '1', type: 'actions', order: 0, name: 'View Recording' }], + }, + ], + }, }, }) await expectLogic(logic) - .toDispatchActions(['setAdvancedFilters']) + .toDispatchActions(['setFilters']) .toMatchValues({ - advancedFilters: expect.objectContaining({ - actions: [{ id: '1', type: 'actions', order: 0, name: 'View Recording' }], - }), - legacyFilters: { - actions: [{ id: '1', type: 'actions', order: 0, name: 'View Recording' }], - session_recording_duration: defaultRecordingDurationFilter, - console_logs: [], - console_search_query: '', - date_from: '-3d', - date_to: null, - events: [], - properties: [], - operand: FilterLogicalOperator.And, - snapshot_source: null, - }, filters: { date_from: '-3d', date_to: null, duration: [{ key: 'duration', operator: 'gt', type: 'recording', value: 1 }], filter_group: { - type: 'AND', + type: FilterLogicalOperator.And, values: [ { - type: 'AND', + type: FilterLogicalOperator.And, values: [{ id: '1', name: 'View Recording', order: 0, type: 'actions' }], }, ], @@ -505,10 +509,18 @@ describe('sessionRecordingsPlaylistLogic', () => { }) await expectLogic(logic) - .toDispatchActions(['setAdvancedFilters']) + .toDispatchActions(['setFilters']) .toMatchValues({ - advancedFilters: expect.objectContaining({ - events: [{ id: '$autocapture', type: 'events', order: 0, name: '$autocapture' }], + filters: expect.objectContaining({ + filter_group: { + type: FilterLogicalOperator.And, + values: [ + { + type: FilterLogicalOperator.And, + values: [{ id: '$autocapture', type: 'events', order: 0, name: '$autocapture' }], + }, + ], + }, }), }) }) @@ -528,19 +540,26 @@ describe('sessionRecordingsPlaylistLogic', () => { }) await expectLogic(logic) - .toDispatchActions(['setSimpleFilters']) + .toDispatchActions(['setFilters']) .toMatchValues({ - simpleFilters: { - events: [], - properties: [ - { - key: '$geoip_country_name', - value: ['Australia'], - operator: PropertyOperator.Exact, - type: PropertyFilterType.Person, - }, - ], - }, + filters: expect.objectContaining({ + filter_group: { + type: FilterLogicalOperator.And, + values: [ + { + type: FilterLogicalOperator.And, + values: [ + { + key: '$geoip_country_name', + value: ['Australia'], + operator: PropertyOperator.Exact, + type: PropertyFilterType.Person, + }, + ], + }, + ], + }, + }), }) }) }) @@ -584,17 +603,47 @@ describe('sessionRecordingsPlaylistLogic', () => { it('counts console log filters', async () => { await expectLogic(logic, () => { - logic.actions.setAdvancedFilters({ - console_logs: ['warn', 'error'], - } satisfies Partial) + logic.actions.setFilters({ + filter_group: { + type: FilterLogicalOperator.And, + values: [ + { + type: FilterLogicalOperator.And, + values: [ + { + type: PropertyFilterType.Recording, + key: 'console_log_level', + operator: PropertyOperator.IContains, + value: ['warn', 'error'], + }, + ], + }, + ], + }, + }) }).toMatchValues({ totalFiltersCount: 1 }) }) it('counts console log search query', async () => { await expectLogic(logic, () => { - logic.actions.setAdvancedFilters({ - console_search_query: 'this is a test', - } satisfies Partial) + logic.actions.setFilters({ + filter_group: { + type: FilterLogicalOperator.And, + values: [ + { + type: FilterLogicalOperator.And, + values: [ + { + type: PropertyFilterType.Recording, + key: 'console_log_query', + operator: PropertyOperator.Exact, + value: 'this is a test', + }, + ], + }, + ], + }, + }) }).toMatchValues({ totalFiltersCount: 1 }) }) }) @@ -611,9 +660,24 @@ describe('sessionRecordingsPlaylistLogic', () => { it('resets console log filters', async () => { await expectLogic(logic, () => { - logic.actions.setAdvancedFilters({ - console_logs: ['warn', 'error'], - } satisfies Partial) + logic.actions.setFilters({ + filter_group: { + type: FilterLogicalOperator.And, + values: [ + { + type: FilterLogicalOperator.And, + values: [ + { + type: PropertyFilterType.Recording, + key: 'console_log_level', + operator: PropertyOperator.IContains, + value: ['warn', 'error'], + }, + ], + }, + ], + }, + }) logic.actions.resetFilters() }).toMatchValues({ totalFiltersCount: 0 }) }) @@ -646,7 +710,7 @@ describe('sessionRecordingsPlaylistLogic', () => { describe('convertUniversalFiltersToLegacyFilters', () => { it('expands the visited_page filter to a pageview with $current_url property', () => { const result = convertUniversalFiltersToLegacyFilters({ - ...DEFAULT_RECORDING_UNIVERSAL_FILTERS, + ...DEFAULT_RECORDING_FILTERS, filter_group: { type: FilterLogicalOperator.And, values: [ diff --git a/frontend/src/scenes/session-recordings/playlist/sessionRecordingsPlaylistLogic.ts b/frontend/src/scenes/session-recordings/playlist/sessionRecordingsPlaylistLogic.ts index 476638ee412a9..83105f0c7f8ee 100644 --- a/frontend/src/scenes/session-recordings/playlist/sessionRecordingsPlaylistLogic.ts +++ b/frontend/src/scenes/session-recordings/playlist/sessionRecordingsPlaylistLogic.ts @@ -7,9 +7,8 @@ import api from 'lib/api' import { isAnyPropertyfilter } from 'lib/components/PropertyFilters/utils' import { DEFAULT_UNIVERSAL_GROUP_FILTER } from 'lib/components/UniversalFilters/universalFiltersLogic' import { isActionFilter, isEventFilter, isRecordingPropertyFilter } from 'lib/components/UniversalFilters/utils' -import { FEATURE_FLAGS } from 'lib/constants' import { featureFlagLogic } from 'lib/logic/featureFlagLogic' -import { objectClean, objectsEqual } from 'lib/utils' +import { objectClean } from 'lib/utils' import { eventUsageLogic } from 'lib/utils/eventUsageLogic' import posthog from 'posthog-js' @@ -78,25 +77,7 @@ export const defaultRecordingDurationFilter: RecordingDurationFilter = { operator: PropertyOperator.GreaterThan, } -export const DEFAULT_SIMPLE_RECORDING_FILTERS: SimpleFiltersType = { - events: [], - properties: [], -} - -export const DEFAULT_RECORDING_FILTERS: RecordingFilters = { - session_recording_duration: defaultRecordingDurationFilter, - properties: [], - events: [], - actions: [], - date_from: '-3d', - date_to: null, - console_logs: [], - snapshot_source: null, - console_search_query: '', - operand: FilterLogicalOperator.And, -} - -export const DEFAULT_RECORDING_UNIVERSAL_FILTERS: RecordingUniversalFilters = { +export const DEFAULT_RECORDING_FILTERS: RecordingUniversalFilters = { filter_test_accounts: false, date_from: '-3d', date_to: null, @@ -104,12 +85,12 @@ export const DEFAULT_RECORDING_UNIVERSAL_FILTERS: RecordingUniversalFilters = { duration: [defaultRecordingDurationFilter], } -const DEFAULT_PERSON_RECORDING_FILTERS: RecordingFilters = { +const DEFAULT_PERSON_RECORDING_FILTERS: RecordingUniversalFilters = { ...DEFAULT_RECORDING_FILTERS, date_from: '-30d', } -export const getDefaultFilters = (personUUID?: PersonUUID): RecordingFilters => { +export const getDefaultFilters = (personUUID?: PersonUUID): RecordingUniversalFilters => { return personUUID ? DEFAULT_PERSON_RECORDING_FILTERS : DEFAULT_RECORDING_FILTERS } @@ -221,15 +202,15 @@ export function convertLegacyFiltersToUniversalFilters( : [] return { - date_from: filters.date_from || DEFAULT_RECORDING_UNIVERSAL_FILTERS['date_from'], - date_to: filters.date_to || DEFAULT_RECORDING_UNIVERSAL_FILTERS['date_to'], + date_from: filters.date_from || DEFAULT_RECORDING_FILTERS['date_from'], + date_to: filters.date_to || DEFAULT_RECORDING_FILTERS['date_to'], filter_test_accounts: filters.filter_test_accounts == undefined - ? DEFAULT_RECORDING_UNIVERSAL_FILTERS['filter_test_accounts'] + ? DEFAULT_RECORDING_FILTERS['filter_test_accounts'] : filters.filter_test_accounts, duration: filters.session_recording_duration ? [{ ...filters.session_recording_duration, key: filters.duration_type_filter || 'duration' }] - : DEFAULT_RECORDING_UNIVERSAL_FILTERS['duration'], + : DEFAULT_RECORDING_FILTERS['duration'], filter_group: { type: FilterLogicalOperator.And, values: [ @@ -255,11 +236,8 @@ export interface SessionRecordingPlaylistLogicProps { personUUID?: PersonUUID updateSearchParams?: boolean autoPlay?: boolean - hideSimpleFilters?: boolean - universalFilters?: RecordingUniversalFilters - advancedFilters?: RecordingFilters - simpleFilters?: RecordingFilters - onFiltersChange?: (filters: RecordingUniversalFilters, legacyFilters: RecordingFilters) => void + filters?: RecordingUniversalFilters + onFiltersChange?: (filters: RecordingUniversalFilters) => void pinnedRecordings?: (SessionRecordingType | string)[] onPinnedChange?: (recording: SessionRecordingType, pinned: boolean) => void } @@ -293,9 +271,7 @@ export const sessionRecordingsPlaylistLogic = kea) => ({ filters }), - setAdvancedFilters: (filters: Partial) => ({ filters }), - setSimpleFilters: (filters: SimpleFiltersType) => ({ filters }), + setFilters: (filters: Partial) => ({ filters }), setShowFilters: (showFilters: boolean) => ({ showFilters }), setShowSettings: (showSettings: boolean) => ({ showSettings }), setOrderBy: (orderBy: SessionOrderingType) => ({ orderBy }), @@ -313,13 +289,6 @@ export const sessionRecordingsPlaylistLogic = kea ({ show }), }), propsChanged(({ actions, props }, oldProps) => { - if (!objectsEqual(props.advancedFilters, oldProps.advancedFilters)) { - actions.setAdvancedFilters(props.advancedFilters || {}) - } - if (!objectsEqual(props.simpleFilters, oldProps.simpleFilters)) { - actions.setSimpleFilters(props.simpleFilters || {}) - } - // If the defined list changes, we need to call the loader to either load the new items or change the list if (props.pinnedRecordings !== oldProps.pinnedRecordings) { actions.loadPinnedRecordings() @@ -471,20 +440,10 @@ export const sessionRecordingsPlaylistLogic = kea ({ - ...state, - ...filters, - }), - resetFilters: () => DEFAULT_SIMPLE_RECORDING_FILTERS, - }, - ], - advancedFilters: [ - props.advancedFilters ?? getDefaultFilters(props.personUUID), + filters: [ + props.filters ?? getDefaultFilters(props.personUUID), { - setAdvancedFilters: (state, { filters }) => { + setFilters: (state, { filters }) => { return { ...state, ...filters, @@ -493,18 +452,6 @@ export const sessionRecordingsPlaylistLogic = kea getDefaultFilters(props.personUUID), }, ], - universalFilters: [ - props.universalFilters ?? DEFAULT_RECORDING_UNIVERSAL_FILTERS, - { - setUniversalFilters: (state, { filters }) => { - return { - ...state, - ...filters, - } - }, - resetFilters: () => DEFAULT_RECORDING_UNIVERSAL_FILTERS, - }, - ], showFilters: [ true, { @@ -584,9 +531,8 @@ export const sessionRecordingsPlaylistLogic = kea true, loadSessionRecordingSuccess: () => false, - setUniversalFilters: () => false, + setFilters: () => false, setAdvancedFilters: () => false, - setSimpleFilters: () => false, loadNext: () => false, loadPrev: () => false, }, @@ -597,21 +543,9 @@ export const sessionRecordingsPlaylistLogic = kea { - actions.loadSessionRecordings() - props.onFiltersChange?.(values.filters, values.legacyFilters) - capturePartialFilters(filters) - actions.loadEventsHaveSessionId() - }, - setAdvancedFilters: ({ filters }) => { + setFilters: ({ filters }) => { actions.loadSessionRecordings() - props.onFiltersChange?.(values.filters, values.legacyFilters) - capturePartialFilters(filters) - actions.loadEventsHaveSessionId() - }, - setUniversalFilters: ({ filters }) => { - actions.loadSessionRecordings() - props.onFiltersChange?.(values.filters, values.legacyFilters) + props.onFiltersChange?.(values.filters) capturePartialFilters(filters) actions.loadEventsHaveSessionId() }, @@ -622,7 +556,7 @@ export const sessionRecordingsPlaylistLogic = kea { actions.loadSessionRecordings() - props.onFiltersChange?.(values.filters, values.legacyFilters) + props.onFiltersChange?.(values.filters) }, maybeLoadSessionRecordings: ({ direction }) => { @@ -648,29 +582,8 @@ export const sessionRecordingsPlaylistLogic = kea [s.featureFlags], - (featureFlags) => !!featureFlags[FEATURE_FLAGS.SESSION_REPLAY_UNIVERSAL_FILTERS], - ], - logicProps: [() => [(_, props) => props], (props): SessionRecordingPlaylistLogicProps => props], - filters: [ - (s) => [s.simpleFilters, s.advancedFilters, s.universalFilters, s.featureFlags], - (simpleFilters, advancedFilters, universalFilters, featureFlags): RecordingUniversalFilters => { - if (featureFlags[FEATURE_FLAGS.SESSION_REPLAY_UNIVERSAL_FILTERS]) { - return universalFilters - } - - return convertLegacyFiltersToUniversalFilters(simpleFilters, advancedFilters) - }, - ], - legacyFilters: [ - (s) => [s.simpleFilters, s.advancedFilters], - (simpleFilters, advancedFilters): RecordingFilters => - combineRecordingFilters(simpleFilters, advancedFilters), - ], - matchingEventsMatchType: [ (s) => [s.filters], (filters): MatchingEventsMatchType => { @@ -754,7 +667,7 @@ export const sessionRecordingsPlaylistLogic = kea buildURL(false), - setUniversalFilters: () => buildURL(true), - setAdvancedFilters: () => buildURL(true), - setSimpleFilters: () => buildURL(true), + setFilters: () => buildURL(true), resetFilters: () => buildURL(true), } }), @@ -847,23 +756,17 @@ export const sessionRecordingsPlaylistLogic = kea +export type HogFunctionConfigurationType = Omit< + HogFunctionType, + 'created_at' | 'created_by' | 'updated_at' | 'status' | 'hog' +> & { + hog?: HogFunctionType['hog'] // In the config it can be empty if using a template +} export type HogFunctionTemplateType = Pick< HogFunctionType, diff --git a/package.json b/package.json index 1e34b62df4af5..6fe9d5d70e712 100644 --- a/package.json +++ b/package.json @@ -147,7 +147,7 @@ "pmtiles": "^2.11.0", "postcss": "^8.4.31", "postcss-preset-env": "^9.3.0", - "posthog-js": "1.148.2", + "posthog-js": "1.149.0", "posthog-js-lite": "3.0.0", "prettier": "^2.8.8", "prop-types": "^15.7.2", diff --git a/plugin-server/package.json b/plugin-server/package.json index cea586fc0d38d..28c9bb1a1351e 100644 --- a/plugin-server/package.json +++ b/plugin-server/package.json @@ -73,7 +73,7 @@ "lru-cache": "^6.0.0", "luxon": "^3.4.4", "node-fetch": "^2.6.1", - "node-rdkafka": "^3.1.0", + "node-rdkafka": "^2.17.0", "node-schedule": "^2.1.0", "pg": "^8.6.0", "pino": "^8.6.0", @@ -137,7 +137,7 @@ }, "pnpm": { "patchedDependencies": { - "node-rdkafka@3.1.0": "patches/node-rdkafka@3.1.0.patch" + "node-rdkafka@2.17.0": "patches/node-rdkafka@2.17.0.patch" } } } diff --git a/plugin-server/patches/node-rdkafka@3.1.0.patch b/plugin-server/patches/node-rdkafka@2.17.0.patch similarity index 91% rename from plugin-server/patches/node-rdkafka@3.1.0.patch rename to plugin-server/patches/node-rdkafka@2.17.0.patch index 36c94f66aef38..a83bf425bd5e3 100644 --- a/plugin-server/patches/node-rdkafka@3.1.0.patch +++ b/plugin-server/patches/node-rdkafka@2.17.0.patch @@ -1,3 +1,35 @@ +diff --git a/Oops.rej b/Oops.rej +new file mode 100644 +index 0000000000000000000000000000000000000000..328fc546fcb400745783b3562f1cb1cb055e1804 +--- /dev/null ++++ b/Oops.rej +@@ -0,0 +1,26 @@ ++@@ -1,25 +0,0 @@ ++-# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created ++-# For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages ++- ++-name: Publish node-rdkafka ++- ++-on: ++- release: ++- types: [created] ++- ++-jobs: ++- publish-npm: ++- runs-on: ubuntu-latest ++- steps: ++- - uses: actions/checkout@v3 ++- with: ++- submodules: recursive ++- - uses: actions/setup-node@v3 ++- with: ++- node-version: 18 ++- registry-url: https://registry.npmjs.org/ ++- cache: "npm" ++- - run: npm ci ++- - run: npm publish ++- env: ++- NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} diff --git a/docker-compose.yml b/docker-compose.yml index abe29df25c7312382074b3e15289cb862a340247..8a12f135b4f96e5a0dd25e7c21adb2b3b0e644fa 100644 --- a/docker-compose.yml @@ -519,10 +551,10 @@ index a167483f1e0ea15c4edcb368e36640b4349574e8..38fcfd7464afb7df682b7b5f1fdb228b it('should happen gracefully', function(cb) { diff --git a/index.d.ts b/index.d.ts -index 43bedfc97223ee938c6d0f2c5b2c4ecec7dfa800..e10a6d7258613b8ff78b21e9da3d2370f042c5cf 100644 +index d7ce7e61e985ce46ceae2c10329d6448cc487dca..2c7b9a3d40b0547209c2cffe1f4e62d9573ab617 100644 --- a/index.d.ts +++ b/index.d.ts -@@ -227,6 +227,12 @@ export class KafkaConsumer extends Client { +@@ -223,6 +223,12 @@ export class KafkaConsumer extends Client { consume(cb: (err: LibrdKafkaError, messages: Message[]) => void): void; consume(): void; @@ -749,10 +781,10 @@ index a6aadbd64609e5d5ae1a80205aac7ce3a49d9345..f817aa976c83b74670c7464099679eb3 topic_result="$?" if [ "$topic_result" == "1" ]; then diff --git a/src/kafka-consumer.cc b/src/kafka-consumer.cc -index 0f5e32eda8968a26733d7c76cbdce71bfe7d7085..eec338eb06e5502960f12e826b1a48bdc146361f 100644 +index 019b0cb6478756120efe9a5f6f1bb4182b4af4ea..3895407788ae31ae38d7707eb63528ebac6e3b24 100644 --- a/src/kafka-consumer.cc +++ b/src/kafka-consumer.cc -@@ -197,6 +197,32 @@ Baton KafkaConsumer::Assign(std::vector partitions) { +@@ -179,6 +179,32 @@ Baton KafkaConsumer::Assign(std::vector partitions) { return Baton(errcode); } @@ -785,7 +817,7 @@ index 0f5e32eda8968a26733d7c76cbdce71bfe7d7085..eec338eb06e5502960f12e826b1a48bd Baton KafkaConsumer::Unassign() { if (!IsClosing() && !IsConnected()) { return Baton(RdKafka::ERR__STATE); -@@ -213,12 +239,46 @@ Baton KafkaConsumer::Unassign() { +@@ -195,12 +221,46 @@ Baton KafkaConsumer::Unassign() { // Destroy the old list of partitions since we are no longer using it RdKafka::TopicPartition::destroy(m_partitions); @@ -832,7 +864,7 @@ index 0f5e32eda8968a26733d7c76cbdce71bfe7d7085..eec338eb06e5502960f12e826b1a48bd Baton KafkaConsumer::Commit(std::vector toppars) { if (!IsConnected()) { return Baton(RdKafka::ERR__STATE); -@@ -487,6 +547,12 @@ Baton KafkaConsumer::RefreshAssignments() { +@@ -469,6 +529,12 @@ Baton KafkaConsumer::RefreshAssignments() { } } @@ -845,7 +877,7 @@ index 0f5e32eda8968a26733d7c76cbdce71bfe7d7085..eec338eb06e5502960f12e826b1a48bd std::string KafkaConsumer::Name() { if (!IsConnected()) { return std::string(""); -@@ -546,8 +612,11 @@ void KafkaConsumer::Init(v8::Local exports) { +@@ -527,8 +593,11 @@ void KafkaConsumer::Init(v8::Local exports) { Nan::SetPrototypeMethod(tpl, "committed", NodeCommitted); Nan::SetPrototypeMethod(tpl, "position", NodePosition); Nan::SetPrototypeMethod(tpl, "assign", NodeAssign); @@ -857,7 +889,7 @@ index 0f5e32eda8968a26733d7c76cbdce71bfe7d7085..eec338eb06e5502960f12e826b1a48bd Nan::SetPrototypeMethod(tpl, "commit", NodeCommit); Nan::SetPrototypeMethod(tpl, "commitSync", NodeCommitSync); -@@ -778,6 +847,64 @@ NAN_METHOD(KafkaConsumer::NodeAssign) { +@@ -759,6 +828,64 @@ NAN_METHOD(KafkaConsumer::NodeAssign) { info.GetReturnValue().Set(Nan::True()); } @@ -922,7 +954,7 @@ index 0f5e32eda8968a26733d7c76cbdce71bfe7d7085..eec338eb06e5502960f12e826b1a48bd NAN_METHOD(KafkaConsumer::NodeUnassign) { Nan::HandleScope scope; -@@ -798,6 +925,71 @@ NAN_METHOD(KafkaConsumer::NodeUnassign) { +@@ -779,6 +906,71 @@ NAN_METHOD(KafkaConsumer::NodeUnassign) { info.GetReturnValue().Set(Nan::True()); } @@ -1021,7 +1053,7 @@ index c91590ecc5d47c1d7a2a93c3e46b4b4657525df0..43e016db4ec47121051cb282f718a2b3 static NAN_METHOD(NodeUnsubscribe); static NAN_METHOD(NodeCommit); diff --git a/test/consumer.spec.js b/test/consumer.spec.js -index 4fc3bd53208bdaafa3df2df1e5bd9184387b8859..2d32c854d13f99531b9b6a36765c65fa42dd8d62 100644 +index 40b52ee4e1c718890f43b91adfb543319d5cc342..5e1a5655be0d2598163478aaaae936213c3bf27c 100644 --- a/test/consumer.spec.js +++ b/test/consumer.spec.js @@ -77,7 +77,7 @@ module.exports = { @@ -1046,4 +1078,50 @@ index 0f4de520ed6b8a06dfe355e0bb9091273def98a5..ada72a7e621ea5433f194ab3d22eef32 + var t = require('assert'); - var client; \ No newline at end of file + var client; +diff --git a/deps/librdkafka/src/rdkafka_partition.h b/deps/librdkafka/src/rdkafka_partition.h +index f9dd686423..aef704b95f 100644 +--- a/deps/librdkafka/src/rdkafka_partition.h ++++ b/deps/librdkafka/src/rdkafka_partition.h +@@ -68,24 +68,35 @@ struct rd_kafka_toppar_err { + * last msg sequence */ + }; + +- ++/** ++ * @brief Fetchpos comparator, only offset is compared. ++ */ ++static RD_UNUSED RD_INLINE int ++rd_kafka_fetch_pos_cmp_offset(const rd_kafka_fetch_pos_t *a, ++ const rd_kafka_fetch_pos_t *b) { ++ if (a->offset < b->offset) ++ return -1; ++ else if (a->offset > b->offset) ++ return 1; ++ else ++ return 0; ++} + + /** + * @brief Fetchpos comparator, leader epoch has precedence. ++ * iff both values are not null. + */ + static RD_UNUSED RD_INLINE int + rd_kafka_fetch_pos_cmp(const rd_kafka_fetch_pos_t *a, + const rd_kafka_fetch_pos_t *b) { ++ if (a->leader_epoch == -1 || b->leader_epoch == -1) ++ return rd_kafka_fetch_pos_cmp_offset(a, b); + if (a->leader_epoch < b->leader_epoch) + return -1; + else if (a->leader_epoch > b->leader_epoch) + return 1; +- else if (a->offset < b->offset) +- return -1; +- else if (a->offset > b->offset) +- return 1; + else +- return 0; ++ return rd_kafka_fetch_pos_cmp_offset(a, b); + } + + diff --git a/plugin-server/pnpm-lock.yaml b/plugin-server/pnpm-lock.yaml index 3e91a8bd187aa..ea69c6b1b7c6a 100644 --- a/plugin-server/pnpm-lock.yaml +++ b/plugin-server/pnpm-lock.yaml @@ -1,13 +1,13 @@ -lockfileVersion: '6.1' +lockfileVersion: '6.0' settings: autoInstallPeers: true excludeLinksFromLockfile: false patchedDependencies: - node-rdkafka@3.1.0: - hash: 2blh4g6m4osumqx2eqyegyjb7q - path: patches/node-rdkafka@3.1.0.patch + node-rdkafka@2.17.0: + hash: bugorwxdhlhl2utknko2c5ibqm + path: patches/node-rdkafka@2.17.0.patch dependencies: '@aws-sdk/client-s3': @@ -113,8 +113,8 @@ dependencies: specifier: ^2.6.1 version: 2.6.9 node-rdkafka: - specifier: ^3.1.0 - version: 3.1.0(patch_hash=2blh4g6m4osumqx2eqyegyjb7q) + specifier: ^2.17.0 + version: 2.17.0(patch_hash=bugorwxdhlhl2utknko2c5ibqm) node-schedule: specifier: ^2.1.0 version: 2.1.1 @@ -8318,10 +8318,6 @@ packages: resolution: {integrity: sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==} dev: false - /nan@2.20.0: - resolution: {integrity: sha512-bk3gXBZDGILuuo/6sKtr0DQmSThYHLtNCdSdXk9YkxD/jK6X2vmCyyXBBxyqZ4XcnzTyYEAThfX3DCEnLf6igw==} - dev: false - /nanoassert@1.1.0: resolution: {integrity: sha512-C40jQ3NzfkP53NsO8kEOFd79p4b9kDXQMwgiY1z8ZwrDZgUyom0AHwGegF4Dm99L+YoYhuaB0ceerUcXmqr1rQ==} dev: true @@ -8435,13 +8431,13 @@ packages: resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} dev: true - /node-rdkafka@3.1.0(patch_hash=2blh4g6m4osumqx2eqyegyjb7q): - resolution: {integrity: sha512-H0FeV6cgkFX/NHKPyWsUHoC4l645/2vfKVdTyvKmwX+nafHuZzT6xVWl2kJQBNVJcRVgtQA1Loz6iXWhq03RKw==} - engines: {node: '>=16'} + /node-rdkafka@2.17.0(patch_hash=bugorwxdhlhl2utknko2c5ibqm): + resolution: {integrity: sha512-vFABzRcE5FaH0WqfqJRxDoqeG6P8UEB3M4qFQ7SkwMgQueMMO78+fm8MYfl5hLW3bBYfBekK2BXIIr0lDQtSEQ==} + engines: {node: '>=6.0.0'} requiresBuild: true dependencies: bindings: 1.5.0 - nan: 2.20.0 + nan: 2.17.0 dev: false patched: true diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8489d56f6dc97..13add50e919ca 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -263,8 +263,8 @@ dependencies: specifier: ^9.3.0 version: 9.3.0(postcss@8.4.31) posthog-js: - specifier: 1.148.2 - version: 1.148.2 + specifier: 1.149.0 + version: 1.149.0 posthog-js-lite: specifier: 3.0.0 version: 3.0.0 @@ -17721,8 +17721,8 @@ packages: resolution: {integrity: sha512-dyajjnfzZD1tht4N7p7iwf7nBnR1MjVaVu+MKr+7gBgA39bn28wizCIJZztZPtHy4PY0YwtSGgwfBCuG/hnHgA==} dev: false - /posthog-js@1.148.2: - resolution: {integrity: sha512-YQt8D+RS1a56ykLPLsqSq73vLYLIFQwlvY40ncgOM/uhxAV4FmbnYv85ZtwnB2SAZD/gChWXI/fprYQA9lhc1w==} + /posthog-js@1.149.0: + resolution: {integrity: sha512-uIknyqxv5uDAToPaYVBzGqWwTiuga56cHs+3OeiXKZgjkm97yWh9VA5/gRD/3LEq3iszxHEOU4I5pVIaUrMNtg==} dependencies: fflate: 0.4.8 preact: 10.22.1 diff --git a/posthog/hogql_queries/insights/trends/breakdown.py b/posthog/hogql_queries/insights/trends/breakdown.py index a4975ae1e4a9d..24d281d6b2bf2 100644 --- a/posthog/hogql_queries/insights/trends/breakdown.py +++ b/posthog/hogql_queries/insights/trends/breakdown.py @@ -120,7 +120,7 @@ def column_exprs(self) -> list[ast.Alias]: breakdowns.append( self._get_breakdown_col_expr( self._get_multiple_breakdown_alias_name(idx + 1), - value=breakdown.value, + value=breakdown.property, breakdown_type=breakdown.type, normalize_url=breakdown.normalize_url, histogram_bin_count=breakdown.histogram_bin_count, @@ -233,7 +233,7 @@ def get_actors_query_where_filter(self, lookup_values: str | int | list[int | st cast(list[BreakdownSchema], self._breakdown_filter.breakdowns), lookup_values ): actors_filter = self._get_actors_query_where_expr( - breakdown_value=breakdown.value, + breakdown_value=breakdown.property, breakdown_type=breakdown.type, lookup_value=str( lookup_value diff --git a/posthog/hogql_queries/insights/trends/test/test_trends.py b/posthog/hogql_queries/insights/trends/test/test_trends.py index 7cb7980cf68b3..7f7977f406bfb 100644 --- a/posthog/hogql_queries/insights/trends/test/test_trends.py +++ b/posthog/hogql_queries/insights/trends/test/test_trends.py @@ -534,7 +534,7 @@ def test_no_props_string(self): team=self.team, data={ "date_from": "-14d", - "breakdowns": [{"value": "$some_property"}], + "breakdowns": [{"property": "$some_property"}], "events": [ { "id": "sign up", @@ -582,7 +582,7 @@ def test_no_props_numeric(self): team=self.team, data={ "date_from": "-14d", - "breakdowns": [{"value": "$some_property"}], + "breakdowns": [{"property": "$some_property"}], "events": [ { "id": "sign up", @@ -630,7 +630,7 @@ def test_no_props_boolean(self): team=self.team, data={ "date_from": "-14d", - "breakdowns": [{"value": "$some_property"}], + "breakdowns": [{"property": "$some_property"}], "events": [ { "id": "sign up", @@ -1406,8 +1406,8 @@ def test_trends_multiple_breakdowns_single_aggregate(self): data={ "display": TRENDS_TABLE, "breakdowns": [ - {"value": "$browser"}, - {"value": "$variant"}, + {"property": "$browser"}, + {"property": "$variant"}, ], "events": [{"id": "sign up"}], }, @@ -1544,7 +1544,7 @@ def test_trends_breakdown_single_aggregate_with_zero_person_ids(self): team=self.team, data={ "display": TRENDS_TABLE, - "breakdowns": [{"value": "$browser"}], + "breakdowns": [{"property": "$browser"}], "events": [{"id": "sign up"}], }, ), @@ -1611,7 +1611,7 @@ def test_trends_breakdown_single_aggregate_math(self): filters: list[dict[str, Any]] = [ {"breakdown": "$some_property"}, - {"breakdowns": [{"value": "$some_property"}]}, + {"breakdowns": [{"property": "$some_property"}]}, ] for breakdown_filter in filters: with freeze_time("2020-01-04T13:00:01Z"): @@ -1821,7 +1821,7 @@ def test_trends_breakdown_with_session_property_single_aggregate_math_and_breakd data={ "display": TRENDS_TABLE, "interval": "week", - "breakdowns": [{"value": "$some_property"}], + "breakdowns": [{"property": "$some_property"}], "events": [ { "id": "sign up", @@ -1850,7 +1850,7 @@ def test_trends_breakdown_with_session_property_single_aggregate_math_and_breakd data={ "display": TRENDS_TABLE, "interval": "day", - "breakdowns": [{"value": "$some_property"}], + "breakdowns": [{"property": "$some_property"}], "events": [ { "id": "sign up", @@ -2002,7 +2002,7 @@ def test_trends_person_breakdown_with_session_property_single_aggregate_math_and data={ "display": TRENDS_TABLE, "interval": "week", - "breakdowns": [{"type": "person", "value": "$some_prop"}], + "breakdowns": [{"type": "person", "property": "$some_prop"}], "events": [ { "id": "sign up", @@ -2110,7 +2110,7 @@ def test_trends_breakdown_with_math_func(self): data={ "display": TRENDS_TABLE, "interval": "day", - "breakdowns": [{"value": "$some_property"}], + "breakdowns": [{"property": "$some_property"}], "events": [ { "id": "sign up", @@ -3392,7 +3392,7 @@ def test_trends_with_session_property_total_volume_math_with_breakdowns(self): breakdown_filter: dict[str, Any] = ( {"breakdown": "$some_property"} if breakdown_type == "single" - else {"breakdowns": [{"value": "$some_property"}]} + else {"breakdowns": [{"property": "$some_property"}]} ) with freeze_time("2020-01-04T13:00:01Z"): @@ -4554,7 +4554,7 @@ def test_breakdown_by_person_property(self): "breakdowns": [ { "type": "person", - "value": "name", + "property": "name", } ] } @@ -4673,7 +4673,7 @@ def test_breakdown_by_person_property_for_person_on_events(self): "date_from": "-14d", "breakdowns": [ { - "value": "name", + "property": "name", "type": "person", } ], @@ -4779,7 +4779,7 @@ def test_breakdown_by_person_property_for_person_on_events_with_zero_person_ids( "date_from": "-14d", "breakdowns": [ { - "value": "name", + "property": "name", "type": "person", } ], @@ -4934,7 +4934,7 @@ def test_breakdown_by_person_property_pie(self): team=self.team, data={ "date_from": "-14d", - "breakdowns": [{"type": "person", "value": "name"}], + "breakdowns": [{"type": "person", "property": "name"}], "display": "ActionsPie", "events": [ { @@ -5006,7 +5006,7 @@ def test_breakdown_by_person_property_pie_with_event_dau_filter(self): "breakdowns": [ { "type": "person", - "value": "name", + "property": "name", } ], } @@ -5121,7 +5121,7 @@ def test_breakdown_filter_by_precalculated_cohort(self): "breakdowns": [ { "type": "person", - "value": "name", + "property": "name", }, ], }, @@ -5233,7 +5233,7 @@ def test_trends_aggregate_by_distinct_id(self): data={ "interval": "day", "events": [{"id": "sign up", "math": "dau"}], - "breakdowns": [{"type": "person", "value": "$some_prop"}], + "breakdowns": [{"type": "person", "property": "$some_prop"}], }, ), self.team, @@ -5318,7 +5318,7 @@ def test_breakdown_filtering_limit(self): team=self.team, data={ "date_from": "-14d", - "breakdowns": [{"value": "$some_property"}], + "breakdowns": [{"property": "$some_property"}], "events": [ { "id": "sign up", @@ -5396,7 +5396,7 @@ def test_breakdown_with_person_property_filter(self): team=self.team, data={ **action_filter, - "breakdowns": [{"value": "order"}], + "breakdowns": [{"property": "order"}], }, ), self.team, @@ -5406,7 +5406,7 @@ def test_breakdown_with_person_property_filter(self): team=self.team, data={ **event_filter, - "breakdowns": [{"value": "order"}], + "breakdowns": [{"property": "order"}], }, ), self.team, @@ -5462,7 +5462,7 @@ def test_breakdown_filtering(self): team=self.team, data={ **filter, - "breakdowns": [{"value": "$some_property"}], + "breakdowns": [{"property": "$some_property"}], }, ), self.team, @@ -5561,7 +5561,7 @@ def test_multiple_breakdowns_label_formatting(self): team=self.team, data={ **filter, - "breakdowns": [{"value": "$browser"}, {"value": "$variant"}], + "breakdowns": [{"property": "$browser"}, {"property": "$variant"}], }, ), self.team, @@ -5582,7 +5582,7 @@ def test_multiple_breakdowns_label_formatting(self): team=self.team, data={ **filter, - "breakdowns": [{"value": "$browser"}, {"value": "$variant"}], + "breakdowns": [{"property": "$browser"}, {"property": "$variant"}], "breakdown_limit": 1, }, ), @@ -5627,7 +5627,7 @@ def test_breakdown_filtering_persons(self): filters: list[dict[str, Any]] = [ {"breakdown": "email", "breakdown_type": "person"}, - {"breakdowns": [{"type": "person", "value": "email"}]}, + {"breakdowns": [{"type": "person", "property": "email"}]}, ] for breakdown_filter in filters: response = self._run( @@ -5697,7 +5697,7 @@ def test_breakdown_filtering_persons_with_action_props(self): filters: list[dict[str, Any]] = [ {"breakdown": "email", "breakdown_type": "person"}, - {"breakdowns": [{"value": "email", "type": "person"}]}, + {"breakdowns": [{"property": "email", "type": "person"}]}, ] for breakdown_filter in filters: response = self._run( @@ -5764,7 +5764,7 @@ def test_breakdown_filtering_with_properties(self): }, ) - filters: list[dict[str, Any]] = [{"breakdown": "$current_url"}, {"breakdowns": [{"value": "$current_url"}]}] + filters: list[dict[str, Any]] = [{"breakdown": "$current_url"}, {"breakdowns": [{"property": "$current_url"}]}] for breakdown_filter in filters: with freeze_time("2020-01-05T13:01:01Z"): response = self._run( @@ -5851,7 +5851,7 @@ def test_breakdown_filtering_with_properties_in_new_format(self): filters: list[dict[str, Any]] = [ {"breakdown": "$current_url"}, - {"breakdowns": [{"value": "$current_url"}]}, + {"breakdowns": [{"property": "$current_url"}]}, ] for breakdown_filter in filters: with freeze_time("2020-01-05T13:01:01Z"): @@ -5952,7 +5952,7 @@ def test_mau_with_breakdown_filtering_and_prop_filter(self): filters: list[dict[str, Any]] = [ {"breakdown": "$some_prop", "breakdown_type": "person"}, - {"breakdowns": [{"value": "$some_prop", "type": "person"}]}, + {"breakdowns": [{"property": "$some_prop", "type": "person"}]}, ] for breakdown_filter in filters: with freeze_time("2020-01-04T13:01:01Z"): @@ -5997,7 +5997,7 @@ def test_dau_with_breakdown_filtering(self): filters: list[dict[str, Any]] = [ {"breakdown": "$some_property"}, - {"breakdowns": [{"value": "$some_property"}]}, + {"breakdowns": [{"property": "$some_property"}]}, ] for breakdown_filter in filters: with freeze_time("2020-01-04T13:01:01Z"): @@ -6047,7 +6047,7 @@ def test_dau_with_breakdown_filtering_with_sampling(self): filters: list[dict[str, Any]] = [ {"breakdown": "$some_property"}, - {"breakdowns": [{"value": "$some_property"}]}, + {"breakdowns": [{"property": "$some_property"}]}, ] for breakdown_filter in filters: with freeze_time("2020-01-04T13:01:01Z"): @@ -6099,7 +6099,7 @@ def test_dau_with_breakdown_filtering_with_prop_filter(self): filters: list[dict[str, Any]] = [ {"breakdown": "$some_property"}, - {"breakdowns": [{"value": "$some_property"}]}, + {"breakdowns": [{"property": "$some_property"}]}, ] for breakdown_filter in filters: with freeze_time("2020-01-04T13:01:01Z"): @@ -6151,7 +6151,7 @@ def test_against_clashing_entity_and_property_filter_naming(self): filters: list[dict[str, Any]] = [ {"breakdown": "$some_prop", "breakdown_type": "person"}, - {"breakdowns": [{"value": "$some_prop", "type": "person"}]}, + {"breakdowns": [{"property": "$some_prop", "type": "person"}]}, ] for breakdown_filter in filters: with freeze_time("2020-01-04T13:01:01Z"): diff --git a/posthog/hogql_queries/insights/trends/test/test_trends_persons.py b/posthog/hogql_queries/insights/trends/test/test_trends_persons.py index 7a235302337f0..ae1ba00404291 100644 --- a/posthog/hogql_queries/insights/trends/test/test_trends_persons.py +++ b/posthog/hogql_queries/insights/trends/test/test_trends_persons.py @@ -401,11 +401,7 @@ def test_trends_multiple_breakdowns_others_persons(self): series=[EventsNode(event="$pageview")], dateRange=InsightDateRange(date_from="-7d"), breakdownFilter=BreakdownFilter( - breakdowns=[ - Breakdown( - value="$browser", - ) - ], + breakdowns=[Breakdown(property="$browser")], breakdown_limit=1, ), ) @@ -431,7 +427,7 @@ def test_trends_filter_by_other(self): series=[EventsNode(event="$pageview")], dateRange=InsightDateRange(date_from="-7d"), breakdownFilter=BreakdownFilter( - breakdowns=[Breakdown(value="some_property", type=MultipleBreakdownType.EVENT)], + breakdowns=[Breakdown(property="some_property", type=MultipleBreakdownType.EVENT)], breakdown_limit=1, ), ) @@ -444,8 +440,8 @@ def test_trends_filter_by_other(self): dateRange=InsightDateRange(date_from="-7d"), breakdownFilter=BreakdownFilter( breakdowns=[ - Breakdown(value="some_property", type=MultipleBreakdownType.EVENT), - Breakdown(value="$browser", type=MultipleBreakdownType.EVENT), + Breakdown(property="some_property", type=MultipleBreakdownType.EVENT), + Breakdown(property="$browser", type=MultipleBreakdownType.EVENT), ], breakdown_limit=1, ), @@ -769,7 +765,7 @@ def test_trends_event_multiple_breakdowns_persons(self): source_query = TrendsQuery( series=[EventsNode(event="$pageview")], dateRange=InsightDateRange(date_from="-7d"), - breakdownFilter=BreakdownFilter(breakdowns=[Breakdown(value="$browser")]), + breakdownFilter=BreakdownFilter(breakdowns=[Breakdown(property="$browser")]), ) result = self._get_actors(trends_query=source_query, day="2023-05-01", breakdown=["Safari"]) @@ -790,7 +786,7 @@ def test_trends_person_multiple_breakdown_persons(self): series=[EventsNode(event="$pageview")], dateRange=InsightDateRange(date_from="-7d"), breakdownFilter=BreakdownFilter( - breakdowns=[Breakdown(value="$geoip_country_code", type=BreakdownType.PERSON)] + breakdowns=[Breakdown(property="$geoip_country_code", type=BreakdownType.PERSON)] ), ) @@ -812,11 +808,7 @@ def test_trends_multiple_breakdown_null_persons(self): series=[EventsNode(event="$pageview")], dateRange=InsightDateRange(date_from="-7d"), breakdownFilter=BreakdownFilter( - breakdowns=[ - Breakdown( - value="$browser", - ) - ], + breakdowns=[Breakdown(property="$browser")], breakdown_limit=1, ), ) @@ -839,7 +831,7 @@ def test_trends_multiple_breakdowns_hogql_persons(self): series=[EventsNode(event="$pageview")], dateRange=InsightDateRange(date_from="-7d"), breakdownFilter=BreakdownFilter( - breakdowns=[Breakdown(value="properties.some_property", type=BreakdownType.HOGQL)], + breakdowns=[Breakdown(property="properties.some_property", type=BreakdownType.HOGQL)], breakdown_limit=1, ), ) @@ -894,7 +886,7 @@ def test_trends_multiple_breakdowns_filter_by_range(self): source_query = TrendsQuery( series=[EventsNode(event="$pageview")], dateRange=InsightDateRange(date_from="-7d"), - breakdownFilter=BreakdownFilter(breakdowns=[Breakdown(value="some_property", histogram_bin_count=4)]), + breakdownFilter=BreakdownFilter(breakdowns=[Breakdown(property="some_property", histogram_bin_count=4)]), ) # should not include 20 @@ -962,11 +954,7 @@ def test_trends_breakdown_by_boolean(self): series=[EventsNode(event="$pageview")], dateRange=InsightDateRange(date_from="-7d"), breakdownFilter=BreakdownFilter( - breakdowns=[ - Breakdown( - value="bool", - ) - ], + breakdowns=[Breakdown(property="bool")], breakdown_limit=1, ), ) diff --git a/posthog/hogql_queries/insights/trends/test/test_trends_query_runner.py b/posthog/hogql_queries/insights/trends/test/test_trends_query_runner.py index 192e8bcd4fb0a..f0deb2b73dab1 100644 --- a/posthog/hogql_queries/insights/trends/test/test_trends_query_runner.py +++ b/posthog/hogql_queries/insights/trends/test/test_trends_query_runner.py @@ -1782,7 +1782,7 @@ def test_multiple_breakdowns_values_limit(self): IntervalType.DAY, [EventsNode(event="$pageview")], TrendsFilter(display=ChartDisplayType.ACTIONS_LINE_GRAPH), - BreakdownFilter(breakdowns=[Breakdown(value="breakdown_value", type=MultipleBreakdownType.EVENT)]), + BreakdownFilter(breakdowns=[Breakdown(property="breakdown_value", type=MultipleBreakdownType.EVENT)]), ) self.assertEqual(len(response.results), 26) @@ -1794,7 +1794,7 @@ def test_multiple_breakdowns_values_limit(self): [EventsNode(event="$pageview")], TrendsFilter(display=ChartDisplayType.ACTIONS_LINE_GRAPH), BreakdownFilter( - breakdowns=[Breakdown(value="breakdown_value", type=MultipleBreakdownType.EVENT)], breakdown_limit=10 + breakdowns=[Breakdown(property="breakdown_value", type=MultipleBreakdownType.EVENT)], breakdown_limit=10 ), ) self.assertEqual(len(response.results), 11) @@ -1807,7 +1807,7 @@ def test_multiple_breakdowns_values_limit(self): [EventsNode(event="$pageview")], TrendsFilter(display=ChartDisplayType.ACTIONS_LINE_GRAPH), BreakdownFilter( - breakdowns=[Breakdown(value="breakdown_value", type=MultipleBreakdownType.EVENT)], + breakdowns=[Breakdown(property="breakdown_value", type=MultipleBreakdownType.EVENT)], breakdown_limit=10, breakdown_hide_other_aggregation=True, ), @@ -1820,7 +1820,7 @@ def test_multiple_breakdowns_values_limit(self): IntervalType.DAY, [EventsNode(event="$pageview")], TrendsFilter(display=ChartDisplayType.ACTIONS_LINE_GRAPH), - BreakdownFilter(breakdowns=[Breakdown(value="breakdown_value", type=MultipleBreakdownType.EVENT)]), + BreakdownFilter(breakdowns=[Breakdown(property="breakdown_value", type=MultipleBreakdownType.EVENT)]), limit_context=LimitContext.EXPORT, ) self.assertEqual(len(response.results), 30) @@ -1834,7 +1834,7 @@ def test_multiple_breakdowns_values_limit(self): [EventsNode(event="$pageview")], TrendsFilter(display=ChartDisplayType.ACTIONS_TABLE), BreakdownFilter( - breakdowns=[Breakdown(value="breakdown_value", type=MultipleBreakdownType.EVENT)], breakdown_limit=10 + breakdowns=[Breakdown(property="breakdown_value", type=MultipleBreakdownType.EVENT)], breakdown_limit=10 ), ) self.assertEqual(len(response.results), 11) @@ -1847,7 +1847,7 @@ def test_multiple_breakdowns_values_limit(self): [EventsNode(event="$pageview")], TrendsFilter(display=ChartDisplayType.ACTIONS_TABLE), BreakdownFilter( - breakdowns=[Breakdown(value="breakdown_value", type=MultipleBreakdownType.EVENT)], + breakdowns=[Breakdown(property="breakdown_value", type=MultipleBreakdownType.EVENT)], breakdown_limit=10, breakdown_hide_other_aggregation=True, ), @@ -2356,7 +2356,7 @@ def test_to_actors_query_options_breakdowns(self): IntervalType.DAY, [EventsNode(event="$pageview")], None, - BreakdownFilter(breakdowns=[Breakdown(type=BreakdownType.EVENT, value="$browser")], breakdown_limit=3), + BreakdownFilter(breakdowns=[Breakdown(type=BreakdownType.EVENT, property="$browser")], breakdown_limit=3), ) response = runner.to_actors_query_options() @@ -2398,7 +2398,7 @@ def test_to_actors_query_options_breakdowns_boolean(self): IntervalType.DAY, [EventsNode(event="$pageview")], None, - BreakdownFilter(breakdowns=[Breakdown(type=BreakdownType.EVENT, value="bool_field")]), + BreakdownFilter(breakdowns=[Breakdown(type=BreakdownType.EVENT, property="bool_field")]), ) response = runner.to_actors_query_options() @@ -2445,15 +2445,7 @@ def test_to_actors_query_options_breakdowns_histogram(self): IntervalType.DAY, [EventsNode(event="$pageview")], None, - BreakdownFilter( - breakdowns=[ - Breakdown( - type=BreakdownType.EVENT, - value="prop", - histogram_bin_count=4, - ) - ] - ), + BreakdownFilter(breakdowns=[Breakdown(type=BreakdownType.EVENT, property="prop", histogram_bin_count=4)]), ) response = runner.to_actors_query_options() @@ -2533,7 +2525,7 @@ def test_to_actors_query_options_breakdowns_hogql(self): IntervalType.DAY, [EventsNode(event="$pageview")], None, - BreakdownFilter(breakdowns=[Breakdown(type=BreakdownType.HOGQL, value="properties.$browser")]), + BreakdownFilter(breakdowns=[Breakdown(type=BreakdownType.HOGQL, property="properties.$browser")]), ) response = runner.to_actors_query_options() @@ -2577,7 +2569,7 @@ def test_to_actors_query_options_bar_value(self): IntervalType.DAY, [EventsNode(event="$pageview")], TrendsFilter(display=ChartDisplayType.ACTIONS_BAR_VALUE), - BreakdownFilter(breakdowns=[Breakdown(type=BreakdownType.EVENT, value="$browser")]), + BreakdownFilter(breakdowns=[Breakdown(type=BreakdownType.EVENT, property="$browser")]), ) response = runner.to_actors_query_options() @@ -2604,9 +2596,9 @@ def test_to_actors_query_options_multiple_breakdowns(self): TrendsFilter(display=ChartDisplayType.ACTIONS_BAR_VALUE), BreakdownFilter( breakdowns=[ - Breakdown(type=BreakdownType.EVENT, value="$browser"), - Breakdown(type=BreakdownType.EVENT, value="prop", histogram_bin_count=2), - Breakdown(type=BreakdownType.EVENT, value="bool_field"), + Breakdown(type=BreakdownType.EVENT, property="$browser"), + Breakdown(type=BreakdownType.EVENT, property="prop", histogram_bin_count=2), + Breakdown(type=BreakdownType.EVENT, property="bool_field"), ] ), ) @@ -2731,7 +2723,7 @@ def test_trends_multiple_event_breakdowns(self): [EventsNode(event="$pageview")], None, BreakdownFilter( - breakdowns=[Breakdown(type="event", value="$browser"), Breakdown(type="event", value="prop")] + breakdowns=[Breakdown(type="event", property="$browser"), Breakdown(type="event", property="prop")] ), ) @@ -2758,9 +2750,9 @@ def test_trends_multiple_event_breakdowns(self): None, BreakdownFilter( breakdowns=[ - Breakdown(type="event", value="$browser"), - Breakdown(type="event", value="prop"), - Breakdown(type="event", value="bool_field"), + Breakdown(type="event", property="$browser"), + Breakdown(type="event", property="prop"), + Breakdown(type="event", property="bool_field"), ] ), ) @@ -2789,10 +2781,10 @@ def test_trends_multiple_breakdowns_have_max_limit(self): with pytest.raises(ValidationError, match=".*at most 3.*"): BreakdownFilter( breakdowns=[ - Breakdown(type="event", value="$browser"), - Breakdown(type="event", value="prop"), - Breakdown(type="event", value="bool_field"), - Breakdown(type="event", value="bool_field"), + Breakdown(type="event", property="$browser"), + Breakdown(type="event", property="prop"), + Breakdown(type="event", property="bool_field"), + Breakdown(type="event", property="bool_field"), ] ) @@ -2808,7 +2800,7 @@ def test_trends_event_and_person_breakdowns(self): [EventsNode(event="$pageview")], None, BreakdownFilter( - breakdowns=[Breakdown(type="event", value="$browser"), Breakdown(type="person", value="name")] + breakdowns=[Breakdown(type="event", property="$browser"), Breakdown(type="person", property="name")] ), ) @@ -2840,8 +2832,8 @@ def test_trends_event_person_group_breakdowns(self): None, BreakdownFilter( breakdowns=[ - Breakdown(type="event", value="$browser"), - Breakdown(type="group", group_type_index=0, value="industry"), + Breakdown(type="event", property="$browser"), + Breakdown(type="group", group_type_index=0, property="industry"), ] ), ) @@ -2879,8 +2871,8 @@ def test_trends_event_with_two_group_breakdowns(self): None, BreakdownFilter( breakdowns=[ - Breakdown(type="group", group_type_index=1, value="employee_count"), - Breakdown(type="group", group_type_index=0, value="industry"), + Breakdown(type="group", group_type_index=1, property="employee_count"), + Breakdown(type="group", group_type_index=0, property="industry"), ] ), ) @@ -2918,9 +2910,9 @@ def test_trends_event_with_three_group_breakdowns(self): None, BreakdownFilter( breakdowns=[ - Breakdown(type="group", group_type_index=0, value="industry"), - Breakdown(type="group", group_type_index=0, value="name"), - Breakdown(type="group", group_type_index=1, value="employee_count"), + Breakdown(type="group", group_type_index=0, property="industry"), + Breakdown(type="group", group_type_index=0, property="name"), + Breakdown(type="group", group_type_index=1, property="employee_count"), ] ), ) @@ -3019,7 +3011,7 @@ def test_trends_event_multiple_breakdowns_normalizes_url(self): None, BreakdownFilter( breakdowns=[ - Breakdown(value="$url", normalize_url=True), + Breakdown(property="$url", normalize_url=True), ] ), ) @@ -3041,7 +3033,7 @@ def test_trends_event_multiple_breakdowns_normalizes_url(self): None, BreakdownFilter( breakdowns=[ - Breakdown(value="$url", normalize_url=normalize_url), + Breakdown(property="$url", normalize_url=normalize_url), ] ), ) @@ -3138,7 +3130,7 @@ def test_trends_event_multiple_numeric_breakdowns(self): None, BreakdownFilter( breakdowns=[ - Breakdown(value="$bin"), + Breakdown(property="$bin"), ], ), ) @@ -3246,7 +3238,7 @@ def test_trends_event_multiple_numeric_breakdowns_into_bins(self): None, BreakdownFilter( breakdowns=[ - Breakdown(value="$bin", histogram_bin_count=5), + Breakdown(property="$bin", histogram_bin_count=5), ], ), ) @@ -3271,7 +3263,7 @@ def test_trends_event_multiple_numeric_breakdowns_into_bins(self): None, BreakdownFilter( breakdowns=[ - Breakdown(value="$bin", histogram_bin_count=5), + Breakdown(property="$bin", histogram_bin_count=5), ], breakdown_limit=2, ), @@ -3367,7 +3359,7 @@ def test_trends_event_histogram_breakdowns_return_equal_result(self): None, BreakdownFilter( breakdowns=[ - Breakdown(value="$bin", histogram_bin_count=5), + Breakdown(property="$bin", histogram_bin_count=5), ], ), ) @@ -3485,7 +3477,7 @@ def test_trends_event_breakdowns_handle_null(self): IntervalType.DAY, [EventsNode(event="$pageview")], None, - BreakdownFilter(breakdowns=[Breakdown(value="$bin", histogram_bin_count=10)]), + BreakdownFilter(breakdowns=[Breakdown(property="$bin", histogram_bin_count=10)]), ) breakdown_labels = [result["breakdown_value"] for result in response.results] @@ -3502,8 +3494,8 @@ def test_trends_event_breakdowns_handle_null(self): None, BreakdownFilter( breakdowns=[ - Breakdown(value="$bin", histogram_bin_count=10), - Breakdown(value="$second_bin", histogram_bin_count=10), + Breakdown(property="$bin", histogram_bin_count=10), + Breakdown(property="$second_bin", histogram_bin_count=10), ] ), ) @@ -3525,7 +3517,7 @@ def test_trends_event_breakdowns_handle_null(self): None, BreakdownFilter( breakdowns=[ - Breakdown(value="$second_bin", histogram_bin_count=10), + Breakdown(property="$second_bin", histogram_bin_count=10), ] ), ) @@ -3541,9 +3533,9 @@ def test_trends_event_breakdowns_can_combine_bool_sting_and_numeric_in_any_order flush_persons_and_events() breakdowns = [ - Breakdown(value="prop", histogram_bin_count=2), - Breakdown(value="$browser"), - Breakdown(value="bool_field"), + Breakdown(property="prop", histogram_bin_count=2), + Breakdown(property="$browser"), + Breakdown(property="bool_field"), ] for breakdown_filter in itertools.combinations(breakdowns, 3): response = self._run_trends_query( @@ -3578,8 +3570,8 @@ def test_trends_event_breakdowns_handle_none_histogram_bin_count(self): None, BreakdownFilter( breakdowns=[ - Breakdown(value="prop", histogram_bin_count=2), - Breakdown(value="$browser", histogram_bin_count=None), + Breakdown(property="prop", histogram_bin_count=2), + Breakdown(property="$browser", histogram_bin_count=None), ] ), ) @@ -3616,7 +3608,7 @@ def test_trends_event_math_session_duration_with_breakdowns(self): [EventsNode(event="$pageview", math=PropertyMathType.MEDIAN, math_property="$session_duration")], None, BreakdownFilter( - breakdowns=[Breakdown(value="$session_duration", type=MultipleBreakdownType.SESSION)], + breakdowns=[Breakdown(property="$session_duration", type=MultipleBreakdownType.SESSION)], ), ) @@ -3651,7 +3643,7 @@ def test_trends_event_math_session_duration_with_breakdowns_and_histogram_bins(s None, BreakdownFilter( breakdowns=[ - Breakdown(value="$session_duration", type=MultipleBreakdownType.SESSION, histogram_bin_count=4) + Breakdown(property="$session_duration", type=MultipleBreakdownType.SESSION, histogram_bin_count=4) ], ), ) @@ -3684,7 +3676,7 @@ def test_trends_event_math_wau_with_breakdowns(self): [EventsNode(event="$pageview", math=BaseMathType.WEEKLY_ACTIVE)], None, BreakdownFilter( - breakdowns=[Breakdown(value="$session_duration", type="session", histogram_bin_count=4)], + breakdowns=[Breakdown(property="$session_duration", type="session", histogram_bin_count=4)], ), ) @@ -3716,7 +3708,7 @@ def test_trends_event_math_mau_with_breakdowns(self): [EventsNode(event="$pageview", math=BaseMathType.MONTHLY_ACTIVE)], None, BreakdownFilter( - breakdowns=[Breakdown(value="$browser", type="event")], + breakdowns=[Breakdown(property="$browser", type="event")], ), ) @@ -3740,7 +3732,7 @@ def test_trends_multiple_breakdowns_hogql(self): IntervalType.DAY, [EventsNode(event="$pageview")], None, - BreakdownFilter(breakdowns=[Breakdown(value="properties.$browser", type=MultipleBreakdownType.HOGQL)]), + BreakdownFilter(breakdowns=[Breakdown(property="properties.$browser", type=MultipleBreakdownType.HOGQL)]), ) breakdown_labels = [result["breakdown_value"] for result in response.results] @@ -3767,8 +3759,8 @@ def test_trends_multiple_breakdowns_hogql_and_numeric_prop(self): None, BreakdownFilter( breakdowns=[ - Breakdown(value="properties.$browser", type=MultipleBreakdownType.HOGQL), - Breakdown(value="prop", histogram_bin_count=2), + Breakdown(property="properties.$browser", type=MultipleBreakdownType.HOGQL), + Breakdown(property="prop", histogram_bin_count=2), ] ), ) @@ -3799,14 +3791,14 @@ def test_trends_event_multiple_breakdowns_combined_types(self): flush_persons_and_events() breakdowns = [ - Breakdown(value="prop", histogram_bin_count=2, type=MultipleBreakdownType.EVENT), - Breakdown(value="$browser", type=MultipleBreakdownType.EVENT), - Breakdown(value="bool_field", type=MultipleBreakdownType.EVENT), - Breakdown(value="properties.$browser", type=MultipleBreakdownType.HOGQL), - Breakdown(value="name", type=MultipleBreakdownType.PERSON), - Breakdown(value="$session_duration", type=MultipleBreakdownType.SESSION), - Breakdown(type="group", group_type_index=1, value="employee_count"), - Breakdown(type="group", group_type_index=0, value="industry"), + Breakdown(property="prop", histogram_bin_count=2, type=MultipleBreakdownType.EVENT), + Breakdown(property="$browser", type=MultipleBreakdownType.EVENT), + Breakdown(property="bool_field", type=MultipleBreakdownType.EVENT), + Breakdown(property="properties.$browser", type=MultipleBreakdownType.HOGQL), + Breakdown(property="name", type=MultipleBreakdownType.PERSON), + Breakdown(property="$session_duration", type=MultipleBreakdownType.SESSION), + Breakdown(type="group", group_type_index=1, property="employee_count"), + Breakdown(type="group", group_type_index=0, property="industry"), ] for breakdown_filter in itertools.permutations(breakdowns, 3): @@ -3832,7 +3824,7 @@ def test_trends_multiple_breakdowns_multiple_hogql(self): IntervalType.DAY, [EventsNode(event="$pageview"), EventsNode(event="$pageleave")], None, - BreakdownFilter(breakdowns=[Breakdown(type=MultipleBreakdownType.HOGQL, value="properties.$browser")]), + BreakdownFilter(breakdowns=[Breakdown(type=MultipleBreakdownType.HOGQL, property="properties.$browser")]), ) breakdown_labels = [result["breakdown_value"] for result in response.results] @@ -3877,9 +3869,9 @@ def test_to_insight_query_applies_multiple_breakdowns(self): TrendsFilter(display=ChartDisplayType.ACTIONS_BAR_VALUE), BreakdownFilter( breakdowns=[ - Breakdown(type=BreakdownType.EVENT, value="$browser"), - Breakdown(type=BreakdownType.EVENT, value="prop", histogram_bin_count=2), - Breakdown(type=BreakdownType.EVENT, value="bool_field"), + Breakdown(type=BreakdownType.EVENT, property="$browser"), + Breakdown(type=BreakdownType.EVENT, property="prop", histogram_bin_count=2), + Breakdown(type=BreakdownType.EVENT, property="bool_field"), ] ), ) @@ -3949,15 +3941,7 @@ def test_to_actors_query_options_orders_options_with_histogram_breakdowns(self): IntervalType.DAY, [EventsNode(event="$pageview")], None, - BreakdownFilter( - breakdowns=[ - Breakdown( - type=BreakdownType.EVENT, - value="prop", - histogram_bin_count=4, - ) - ] - ), + BreakdownFilter(breakdowns=[Breakdown(type=BreakdownType.EVENT, property="prop", histogram_bin_count=4)]), ) response = runner.to_actors_query_options() @@ -3997,7 +3981,7 @@ def test_to_insight_query_applies_breakdown_limit(self): IntervalType.DAY, [EventsNode(event="$pageview")], TrendsFilter(display=ChartDisplayType.ACTIONS_BAR_VALUE), - BreakdownFilter(breakdowns=[Breakdown(value="$browser")], breakdown_limit=2), + BreakdownFilter(breakdowns=[Breakdown(property="$browser")], breakdown_limit=2), ) breakdown_labels = [result["breakdown_value"] for result in response.results] @@ -4042,7 +4026,7 @@ def test_trends_table_uses_breakdown_bins(self): [EventsNode(event="$pageview")], TrendsFilter(display=display), BreakdownFilter( - breakdowns=[Breakdown(value="prop", type=MultipleBreakdownType.EVENT, histogram_bin_count=2)], + breakdowns=[Breakdown(property="prop", type=MultipleBreakdownType.EVENT, histogram_bin_count=2)], breakdown_limit=10, breakdown_hide_other_aggregation=True, ), diff --git a/posthog/hogql_queries/insights/trends/trends_query_runner.py b/posthog/hogql_queries/insights/trends/trends_query_runner.py index 5ebac61537caf..201cd6fe4a140 100644 --- a/posthog/hogql_queries/insights/trends/trends_query_runner.py +++ b/posthog/hogql_queries/insights/trends/trends_query_runner.py @@ -272,7 +272,7 @@ def to_actors_query_options(self) -> InsightActorsQueryOptionsResponse: MultipleBreakdownOptions( values=self._get_breakdown_items( values, - breakdown_filter.value, + breakdown_filter.property, breakdown_filter.type, histogram_breakdown=isinstance(breakdown_filter.histogram_bin_count, int), group_type_index=breakdown_filter.group_type_index, @@ -987,7 +987,7 @@ def _format_breakdown_label(self, breakdown_value: Any): if self.query.breakdownFilter is not None and self.query.breakdownFilter.breakdowns is not None: labels = [] for breakdown, label in zip(self.query.breakdownFilter.breakdowns, breakdown_value): - if self._is_breakdown_field_boolean(breakdown.value, breakdown.type, breakdown.group_type_index): + if self._is_breakdown_field_boolean(breakdown.property, breakdown.type, breakdown.group_type_index): labels.append(self._convert_boolean(label)) else: labels.append(label) diff --git a/posthog/hogql_queries/legacy_compatibility/filter_to_query.py b/posthog/hogql_queries/legacy_compatibility/filter_to_query.py index 3ecbcb926e8f5..6bff858aaf130 100644 --- a/posthog/hogql_queries/legacy_compatibility/filter_to_query.py +++ b/posthog/hogql_queries/legacy_compatibility/filter_to_query.py @@ -321,23 +321,34 @@ def _breakdown_filter(_filter: dict): if breakdownFilter["breakdown_type"] == "events": breakdownFilter["breakdown_type"] = "event" - if _filter.get("breakdowns") is not None and isinstance(_filter["breakdowns"], list): - breakdowns = [] - for breakdown in _filter["breakdowns"]: - if isinstance(breakdown, dict) and ("value" in breakdown or "property" in breakdown): - breakdowns.append( - { - "type": breakdown.get("type", "event"), - "value": breakdown.get("value", breakdown.get("property")), - "normalize_url": breakdown.get("normalize_url", None), - "histogram_bin_count": breakdown.get("histogram_bin_count", None), - "group_type_index": breakdown.get("group_type_index", None), - } + if _filter.get("breakdowns") is not None: + if _insight_type(_filter) == "TRENDS": + # Trends support multiple breakdowns + breakdowns = [] + for breakdown in _filter["breakdowns"]: + if isinstance(breakdown, dict) and "property" in breakdown: + breakdowns.append( + { + "type": breakdown.get("type", "event"), + "property": breakdown.get("property"), + "normalize_url": breakdown.get("normalize_url", None), + "histogram_bin_count": breakdown.get("histogram_bin_count", None), + "group_type_index": breakdown.get("group_type_index", None), + } + ) + + if len(breakdowns) > 0: + # Multiple breakdowns accept up to three breakdowns + breakdownFilter["breakdowns"] = breakdowns[:3] + else: + if isinstance(_filter["breakdowns"], list) and len(_filter["breakdowns"]) == 1: + breakdownFilter["breakdown_type"] = _filter["breakdowns"][0].get("type", None) + breakdownFilter["breakdown"] = _filter["breakdowns"][0].get("property", None) + else: + raise Exception( + "Could not convert multi-breakdown property `breakdowns` - found more than one breakdown" ) - if len(breakdowns) > 0: - breakdownFilter["breakdowns"] = breakdowns[:3] - if breakdownFilter["breakdown"] is not None and breakdownFilter["breakdown_type"] is None: breakdownFilter["breakdown_type"] = "event" diff --git a/posthog/hogql_queries/legacy_compatibility/test/test_filter_to_query.py b/posthog/hogql_queries/legacy_compatibility/test/test_filter_to_query.py index 6fc6beb536101..310851e70ea9d 100644 --- a/posthog/hogql_queries/legacy_compatibility/test/test_filter_to_query.py +++ b/posthog/hogql_queries/legacy_compatibility/test/test_filter_to_query.py @@ -1343,7 +1343,7 @@ def test_breakdown_converts_multi(self): assert isinstance(query, TrendsQuery) self.assertEqual( query.breakdownFilter, - BreakdownFilter(breakdowns=[{"type": BreakdownType.EVENT, "value": "$browser"}]), + BreakdownFilter(breakdowns=[{"type": BreakdownType.EVENT, "property": "$browser"}]), ) filter = { @@ -1360,8 +1360,8 @@ def test_breakdown_converts_multi(self): query.breakdownFilter, BreakdownFilter( breakdowns=[ - {"type": BreakdownType.EVENT, "value": "$browser"}, - {"type": BreakdownType.SESSION, "value": "$session_duration"}, + {"type": BreakdownType.EVENT, "property": "$browser"}, + {"type": BreakdownType.SESSION, "property": "$session_duration"}, ] ), ) @@ -1646,10 +1646,10 @@ def test_lifecycle_filter(self): def test_multiple_breakdowns(self): filter = { "breakdowns": [ - {"type": "event", "value": "$url", "normalize_url": True}, - {"type": "group", "value": "$os", "group_type_index": 0}, - {"type": "session", "value": "$session_duration", "histogram_bin_count": 10}, - {"type": "person", "value": "extra_prop"}, + {"type": "event", "property": "$url", "normalize_url": True}, + {"type": "group", "property": "$os", "group_type_index": 0}, + {"type": "session", "property": "$session_duration", "histogram_bin_count": 10}, + {"type": "person", "property": "extra_prop"}, ] } @@ -1660,31 +1660,48 @@ def test_multiple_breakdowns(self): query.breakdownFilter, BreakdownFilter( breakdowns=[ - Breakdown(type=BreakdownType.EVENT, value="$url", normalize_url=True), - Breakdown(type=BreakdownType.GROUP, value="$os", group_type_index=0), - Breakdown(type=BreakdownType.SESSION, value="$session_duration", histogram_bin_count=10), + Breakdown(type=BreakdownType.EVENT, property="$url", normalize_url=True), + Breakdown(type=BreakdownType.GROUP, property="$os", group_type_index=0), + Breakdown(type=BreakdownType.SESSION, property="$session_duration", histogram_bin_count=10), ] ), ) - def test_legacy_multiple_breakdowns(self): + def test_funnels_multiple_breakdowns(self): filter = { + "insight": "FUNNELS", "breakdowns": [ - {"type": "event", "property": "$url"}, {"type": "session", "property": "$session_duration"}, - ] + ], } query = filter_to_query(filter) - assert isinstance(query, TrendsQuery) + assert isinstance(query, FunnelsQuery) self.assertEqual( query.breakdownFilter, BreakdownFilter( - breakdowns=[ - Breakdown(type=BreakdownType.EVENT, value="$url"), - Breakdown(type=BreakdownType.SESSION, value="$session_duration"), - ] + breakdown="$session_duration", + breakdown_type=BreakdownType.SESSION, + ), + ) + + def test_funnels_multiple_breakdowns_no_breakdown_type(self): + filter = { + "insight": "FUNNELS", + "breakdowns": [ + {"property": "prop"}, + ], + } + + query = filter_to_query(filter) + + assert isinstance(query, FunnelsQuery) + self.assertEqual( + query.breakdownFilter, + BreakdownFilter( + breakdown="prop", + breakdown_type=BreakdownType.EVENT, ), ) diff --git a/posthog/schema.py b/posthog/schema.py index b1051a853373c..cd957832fd843 100644 --- a/posthog/schema.py +++ b/posthog/schema.py @@ -1351,8 +1351,8 @@ class Breakdown(BaseModel): group_type_index: Optional[int] = None histogram_bin_count: Optional[int] = None normalize_url: Optional[bool] = None + property: str type: Optional[MultipleBreakdownType] = None - value: str class BreakdownFilter(BaseModel):