diff --git a/frontend/__snapshots__/components-cards-insight-details--trends-world-map--dark.png b/frontend/__snapshots__/components-cards-insight-details--trends-world-map--dark.png index 15b60cc25575c1..6b08cf6fd3d087 100644 Binary files a/frontend/__snapshots__/components-cards-insight-details--trends-world-map--dark.png and b/frontend/__snapshots__/components-cards-insight-details--trends-world-map--dark.png differ diff --git a/frontend/__snapshots__/components-cards-insight-details--trends-world-map--light.png b/frontend/__snapshots__/components-cards-insight-details--trends-world-map--light.png index 06e1bfb343f045..975a66aad5e556 100644 Binary files a/frontend/__snapshots__/components-cards-insight-details--trends-world-map--light.png and b/frontend/__snapshots__/components-cards-insight-details--trends-world-map--light.png differ diff --git a/frontend/src/lib/components/ActivityLog/activityLogLogic.insight.test.tsx b/frontend/src/lib/components/ActivityLog/activityLogLogic.insight.test.tsx index fc98560ef7f207..c86e548e0b8ba7 100644 --- a/frontend/src/lib/components/ActivityLog/activityLogLogic.insight.test.tsx +++ b/frontend/src/lib/components/ActivityLog/activityLogLogic.insight.test.tsx @@ -4,6 +4,7 @@ import { render } from '@testing-library/react' import { MOCK_TEAM_ID } from 'lib/api.mock' import { makeTestSetup } from 'lib/components/ActivityLog/activityLogLogic.test.setup' +import { BreakdownFilter } from '~/queries/schema' import { ActivityScope } from '~/types' jest.mock('lib/colors') @@ -84,81 +85,104 @@ describe('the activity log logic', () => { }) it('can handle change of insight query', async () => { - const logic = await insightTestSetup('test insight', 'updated', [ - { - type: ActivityScope.INSIGHT, - action: 'changed', - field: 'query', - after: { - kind: 'TrendsQuery', - properties: { - type: 'AND', - values: [ - { - type: 'OR', - values: [ - { - type: 'event', - key: '$current_url', - operator: 'exact', - value: ['https://hedgebox.net/files/'], - }, - { - type: 'event', - key: '$geoip_country_code', - operator: 'exact', - value: ['US', 'AU'], - }, - ], - }, - ], - }, - filterTestAccounts: false, - interval: 'day', - dateRange: { - date_from: '-7d', - }, - series: [ + const insightMock = { + type: ActivityScope.INSIGHT, + action: 'changed', + field: 'query', + after: { + kind: 'TrendsQuery', + properties: { + type: 'AND', + values: [ { - kind: 'EventsNode', - name: '$pageview', - custom_name: 'Views', - event: '$pageview', - properties: [ + type: 'OR', + values: [ { type: 'event', - key: '$browser', + key: '$current_url', operator: 'exact', - value: 'Chrome', + value: ['https://hedgebox.net/files/'], }, { - type: 'cohort', - key: 'id', - value: 2, + type: 'event', + key: '$geoip_country_code', + operator: 'exact', + value: ['US', 'AU'], }, ], - limit: 100, }, ], - trendsFilter: { - display: 'ActionsAreaGraph', - }, - breakdownFilter: { - breakdown: '$geoip_country_code', - breakdown_type: 'event', + }, + filterTestAccounts: false, + interval: 'day', + dateRange: { + date_from: '-7d', + }, + series: [ + { + kind: 'EventsNode', + name: '$pageview', + custom_name: 'Views', + event: '$pageview', + properties: [ + { + type: 'event', + key: '$browser', + operator: 'exact', + value: 'Chrome', + }, + { + type: 'cohort', + key: 'id', + value: 2, + }, + ], + limit: 100, }, + ], + trendsFilter: { + display: 'ActionsAreaGraph', + }, + breakdownFilter: { + breakdown: '$geoip_country_code', + breakdown_type: 'event', }, }, - ]) - const actual = logic.values.humanizedActivity + } - const renderedDescription = render(<>{actual[0].description}>).container + let logic = await insightTestSetup('test insight', 'updated', [insightMock as any]) + let actual = logic.values.humanizedActivity + + let renderedDescription = render(<>{actual[0].description}>).container expect(renderedDescription).toHaveTextContent('peter changed query definition on test insight') - const renderedExtendedDescription = render(<>{actual[0].extendedDescription}>).container + let renderedExtendedDescription = render(<>{actual[0].extendedDescription}>).container expect(renderedExtendedDescription).toHaveTextContent( "Query summaryAShowing \"Views\"Pageviewcounted by total countwhere event'sBrowser= equals Chromeand person belongs to cohortID 2FiltersEvent'sCurrent URL= equals https://hedgebox.net/files/or event'sCountry Code= equals US or AUBreakdown byCountry Code" ) + ;(insightMock.after.breakdownFilter as BreakdownFilter) = { + breakdowns: [ + { + property: '$geoip_country_code', + type: 'event', + }, + { + property: '$session_duration', + type: 'session', + }, + ], + } + + logic = await insightTestSetup('test insight', 'updated', [insightMock as any]) + actual = logic.values.humanizedActivity + + renderedDescription = render(<>{actual[0].description}>).container + expect(renderedDescription).toHaveTextContent('peter changed query definition on test insight') + + renderedExtendedDescription = render(<>{actual[0].extendedDescription}>).container + expect(renderedExtendedDescription).toHaveTextContent( + "Query summaryAShowing \"Views\"Pageviewcounted by total countwhere event'sBrowser= equals Chromeand person belongs to cohortID 2FiltersEvent'sCurrent URL= equals https://hedgebox.net/files/or event'sCountry Code= equals US or AUBreakdown byCountry CodeSession duration" + ) }) it('can handle change of filters on a retention graph', async () => { diff --git a/frontend/src/lib/components/ActivityLog/complex.sql b/frontend/src/lib/components/ActivityLog/complex.sql new file mode 100644 index 00000000000000..377778eff333d2 --- /dev/null +++ b/frontend/src/lib/components/ActivityLog/complex.sql @@ -0,0 +1,27 @@ +SELECT + count() AS total, + toStartOfDay(min_timestamp) AS day_start, + breakdown_value AS breakdown_value +FROM + (SELECT + min(timestamp) AS min_timestamp, + argMin(breakdown_value, timestamp) AS breakdown_value + FROM + (SELECT + person_id, + timestamp, + ifNull(nullIf(toString(properties.$browser), ''), '$$_posthog_breakdown_null_$$') AS breakdown_value + FROM + events AS e SAMPLE 1 + WHERE + and(equals(event, '$pageview'), lessOrEquals(timestamp, assumeNotNull(toDateTime('2025-01-20 23:59:59')))) + ) + GROUP BY + person_id + ) +WHERE + greaterOrEquals(min_timestamp, toStartOfDay(assumeNotNull(toDateTime('2020-01-09 00:00:00')))) +GROUP BY + day_start, + breakdown_value +LIMIT 50000 diff --git a/frontend/src/lib/components/ActivityLog/full_query.sql b/frontend/src/lib/components/ActivityLog/full_query.sql new file mode 100644 index 00000000000000..b7129f83c64f46 --- /dev/null +++ b/frontend/src/lib/components/ActivityLog/full_query.sql @@ -0,0 +1,41 @@ +SELECT + arrayMap(number -> plus(toStartOfDay(assumeNotNull(toDateTime('2024-07-16 00:00:00'))), toIntervalDay(number)), range(0, plus(coalesce(dateDiff('day', toStartOfDay(assumeNotNull(toDateTime('2024-07-16 00:00:00'))), toStartOfDay(assumeNotNull(toDateTime('2024-07-23 23:59:59'))))), 1))) AS date, + arrayMap(_match_date -> arraySum(arraySlice(groupArray(count), indexOf(groupArray(day_start) AS _days_for_count, _match_date) AS _index, plus(minus(arrayLastIndex(x -> equals(x, _match_date), _days_for_count), _index), 1))), date) AS total +FROM + (SELECT + sum(total) AS count, + day_start + FROM (SELECT + count() AS total, + day_start, + breakdown_value + FROM ( + SELECT + min(timestamp) as day_start, + argMin(breakdown_value, timestamp) AS breakdown_value, + FROM + ( + SELECT + person_id, + toStartOfDay(timestamp) AS timestamp, + ifNull(nullIf(toString(person.properties.email), ''), '$$_posthog_breakdown_null_$$') AS breakdown_value + FROM + events AS e SAMPLE 1 + WHERE + and(lessOrEquals(timestamp, assumeNotNull(toDateTime('2024-07-23 23:59:59'))), equals(properties.$browser, 'Safari')) + ) + WHERE + greaterOrEquals(timestamp, toStartOfDay(assumeNotNull(toDateTime('2024-07-16 00:00:00')))) + GROUP BY + person_id + ) + GROUP BY + day_start, + breakdown_value) + GROUP BY + day_start + ORDER BY + day_start ASC) +ORDER BY + arraySum(total) DESC +LIMIT 50000 diff --git a/frontend/src/lib/components/ActivityLog/weekly.sql b/frontend/src/lib/components/ActivityLog/weekly.sql new file mode 100644 index 00000000000000..579249842508c5 --- /dev/null +++ b/frontend/src/lib/components/ActivityLog/weekly.sql @@ -0,0 +1,45 @@ +SELECT + arrayMap(number -> plus(toStartOfDay(assumeNotNull(toDateTime('2024-07-16 00:00:00'))), toIntervalDay(number)), range(0, plus(coalesce(dateDiff('day', toStartOfDay(assumeNotNull(toDateTime('2024-07-16 00:00:00'))), toStartOfDay(assumeNotNull(toDateTime('2024-07-23 23:59:59'))))), 1))) AS date, + arrayMap(_match_date -> arraySum(arraySlice(groupArray(count), indexOf(groupArray(day_start) AS _days_for_count, _match_date) AS _index, plus(minus(arrayLastIndex(x -> equals(x, _match_date), _days_for_count), _index), 1))), date) AS total +FROM + (SELECT + sum(total) AS count, + day_start + FROM + (SELECT + counts AS total, + toStartOfDay(timestamp) AS day_start + FROM + (SELECT + d.timestamp, + count(DISTINCT actor_id) AS counts + FROM + (SELECT + minus(toStartOfDay(assumeNotNull(toDateTime('2024-07-23 23:59:59'))), toIntervalDay(number)) AS timestamp + FROM + numbers(dateDiff('day', minus(toStartOfDay(assumeNotNull(toDateTime('2024-07-16 00:00:00'))), toIntervalDay(7)), assumeNotNull(toDateTime('2024-07-23 23:59:59')))) AS numbers) AS d + CROSS JOIN (SELECT + timestamp AS timestamp, + e.person_id AS actor_id + FROM + events AS e SAMPLE 1 + WHERE + and(equals(event, '$pageview'), greaterOrEquals(timestamp, minus(assumeNotNull(toDateTime('2024-07-16 00:00:00')), toIntervalDay(7))), lessOrEquals(timestamp, assumeNotNull(toDateTime('2024-07-23 23:59:59')))) + GROUP BY + timestamp, + actor_id) AS e + WHERE + and(lessOrEquals(e.timestamp, plus(d.timestamp, toIntervalDay(1))), greater(e.timestamp, minus(d.timestamp, toIntervalDay(6)))) + GROUP BY + d.timestamp + ORDER BY + d.timestamp ASC) + WHERE + and(greaterOrEquals(timestamp, toStartOfDay(assumeNotNull(toDateTime('2024-07-16 00:00:00')))), lessOrEquals(timestamp, assumeNotNull(toDateTime('2024-07-23 23:59:59'))))) + GROUP BY + day_start + ORDER BY + day_start ASC) +ORDER BY + arraySum(total) DESC +LIMIT 50000 diff --git a/frontend/src/lib/components/Cards/InsightCard/InsightDetails.tsx b/frontend/src/lib/components/Cards/InsightCard/InsightDetails.tsx index 3c5d5636e29d18..9a0038556dea6e 100644 --- a/frontend/src/lib/components/Cards/InsightCard/InsightDetails.tsx +++ b/frontend/src/lib/components/Cards/InsightCard/InsightDetails.tsx @@ -39,6 +39,7 @@ import { isLifecycleQuery, isPathsQuery, isTrendsQuery, + isValidBreakdown, } from '~/queries/utils' import { AnyPropertyFilter, @@ -155,11 +156,7 @@ function SeriesDisplay({ const { mathDefinitions } = useValues(mathsLogic) const filter = query.series[seriesIndex] - const hasBreakdown = - isInsightQueryWithBreakdown(query) && - query.breakdownFilter != null && - query.breakdownFilter.breakdown_type != null && - query.breakdownFilter.breakdown != null + const hasBreakdown = isInsightQueryWithBreakdown(query) && isValidBreakdown(query.breakdownFilter) const mathDefinition = mathDefinitions[ isLifecycleQuery(query) @@ -338,25 +335,26 @@ export function LEGACY_FilterBasedBreakdownSummary({ filters }: { filters: Parti } export function BreakdownSummary({ query }: { query: InsightQueryNode }): JSX.Element | null { - if ( - !isInsightQueryWithBreakdown(query) || - !query.breakdownFilter || - query.breakdownFilter.breakdown_type == null || - query.breakdownFilter.breakdown == null - ) { + if (!isInsightQueryWithBreakdown(query) || !isValidBreakdown(query.breakdownFilter)) { return null } - const { breakdown_type, breakdown } = query.breakdownFilter - const breakdownArray = Array.isArray(breakdown) ? breakdown : [breakdown] + const { breakdown_type, breakdown, breakdowns } = query.breakdownFilter return ( <>