diff --git a/ee/session_recordings/queries/test/__snapshots__/test_session_recording_list_from_query.ambr b/ee/session_recordings/queries/test/__snapshots__/test_session_recording_list_from_query.ambr new file mode 100644 index 0000000000000..bcd1ed1e3c8cb --- /dev/null +++ b/ee/session_recordings/queries/test/__snapshots__/test_session_recording_list_from_query.ambr @@ -0,0 +1,1649 @@ +# serializer version: 1 +# name: TestClickhouseSessionRecordingsListFromQuery.test_effect_of_poe_settings_on_query_generated_0_test_poe_v1_still_falls_back_to_person_subquery + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, %(hogql_val_0)s)) AS start_time, + max(toTimeZone(s.max_last_timestamp, %(hogql_val_1)s)) AS end_time, + dateDiff(%(hogql_val_2)s, start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, %(hogql_val_3)s)), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff(%(hogql_val_4)s, start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, %(hogql_val_5)s), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, %(hogql_val_6)s), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, %(hogql_val_7)s), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, %(hogql_val_8)s), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, %(hogql_val_9)s), now64(6, %(hogql_val_10)s)), greaterOrEquals(toTimeZone(events.timestamp, %(hogql_val_11)s), toDateTime64('2020-12-28 23:58:00.000000', 6, 'UTC')), ifNull(equals(nullIf(nullIf(mat_pp_rgInternal, ''), 'null'), %(hogql_val_12)s), 0)) + GROUP BY events.`$session_id` + HAVING 1))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 50000 + ''' +# --- +# name: TestClickhouseSessionRecordingsListFromQuery.test_effect_of_poe_settings_on_query_generated_1_test_poe_being_unavailable_we_fall_back_to_person_id_overrides + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, %(hogql_val_0)s)) AS start_time, + max(toTimeZone(s.max_last_timestamp, %(hogql_val_1)s)) AS end_time, + dateDiff(%(hogql_val_2)s, start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, %(hogql_val_3)s)), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff(%(hogql_val_4)s, start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, %(hogql_val_5)s), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, %(hogql_val_6)s), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, %(hogql_val_7)s), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + LEFT OUTER JOIN + (SELECT argMax(person_distinct_id_overrides.person_id, person_distinct_id_overrides.version) AS person_id, person_distinct_id_overrides.distinct_id AS distinct_id + FROM person_distinct_id_overrides + WHERE equals(person_distinct_id_overrides.team_id, 99999) + GROUP BY person_distinct_id_overrides.distinct_id + HAVING ifNull(equals(argMax(person_distinct_id_overrides.is_deleted, person_distinct_id_overrides.version), 0), 0) SETTINGS optimize_aggregation_in_order=1) AS events__override ON equals(events.distinct_id, events__override.distinct_id) + LEFT JOIN + (SELECT argMax(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(person.properties, %(hogql_val_8)s), ''), 'null'), '^"|"$', ''), person.version) AS properties___rgInternal, person.id AS id + FROM person + WHERE equals(person.team_id, 99999) + GROUP BY person.id + HAVING and(ifNull(equals(argMax(person.is_deleted, person.version), 0), 0), ifNull(less(argMax(toTimeZone(person.created_at, %(hogql_val_9)s), person.version), plus(now64(6, %(hogql_val_10)s), toIntervalDay(1))), 0)) SETTINGS optimize_aggregation_in_order=1) AS events__person ON equals(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id), events__person.id) + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, %(hogql_val_11)s), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, %(hogql_val_12)s), now64(6, %(hogql_val_13)s)), greaterOrEquals(toTimeZone(events.timestamp, %(hogql_val_14)s), toDateTime64('2020-12-28 23:58:00.000000', 6, 'UTC')), ifNull(equals(events__person.properties___rgInternal, %(hogql_val_15)s), 0)) + GROUP BY events.`$session_id` + HAVING 1))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 50000 + ''' +# --- +# name: TestClickhouseSessionRecordingsListFromQuery.test_effect_of_poe_settings_on_query_generated_2_test_poe_being_unavailable_we_fall_back_to_person_subquery_but_still_use_mat_props + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, %(hogql_val_0)s)) AS start_time, + max(toTimeZone(s.max_last_timestamp, %(hogql_val_1)s)) AS end_time, + dateDiff(%(hogql_val_2)s, start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, %(hogql_val_3)s)), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff(%(hogql_val_4)s, start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, %(hogql_val_5)s), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, %(hogql_val_6)s), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, %(hogql_val_7)s), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + LEFT OUTER JOIN + (SELECT argMax(person_distinct_id_overrides.person_id, person_distinct_id_overrides.version) AS person_id, person_distinct_id_overrides.distinct_id AS distinct_id + FROM person_distinct_id_overrides + WHERE equals(person_distinct_id_overrides.team_id, 99999) + GROUP BY person_distinct_id_overrides.distinct_id + HAVING ifNull(equals(argMax(person_distinct_id_overrides.is_deleted, person_distinct_id_overrides.version), 0), 0) SETTINGS optimize_aggregation_in_order=1) AS events__override ON equals(events.distinct_id, events__override.distinct_id) + LEFT JOIN + (SELECT argMax(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(person.properties, %(hogql_val_8)s), ''), 'null'), '^"|"$', ''), person.version) AS properties___rgInternal, person.id AS id + FROM person + WHERE equals(person.team_id, 99999) + GROUP BY person.id + HAVING and(ifNull(equals(argMax(person.is_deleted, person.version), 0), 0), ifNull(less(argMax(toTimeZone(person.created_at, %(hogql_val_9)s), person.version), plus(now64(6, %(hogql_val_10)s), toIntervalDay(1))), 0)) SETTINGS optimize_aggregation_in_order=1) AS events__person ON equals(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id), events__person.id) + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, %(hogql_val_11)s), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, %(hogql_val_12)s), now64(6, %(hogql_val_13)s)), greaterOrEquals(toTimeZone(events.timestamp, %(hogql_val_14)s), toDateTime64('2020-12-28 23:58:00.000000', 6, 'UTC')), ifNull(equals(events__person.properties___rgInternal, %(hogql_val_15)s), 0)) + GROUP BY events.`$session_id` + HAVING 1))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 50000 + ''' +# --- +# name: TestClickhouseSessionRecordingsListFromQuery.test_effect_of_poe_settings_on_query_generated_3_test_allow_denormalised_props_fix_does_not_stop_all_poe_processing + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, %(hogql_val_0)s)) AS start_time, + max(toTimeZone(s.max_last_timestamp, %(hogql_val_1)s)) AS end_time, + dateDiff(%(hogql_val_2)s, start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, %(hogql_val_3)s)), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff(%(hogql_val_4)s, start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, %(hogql_val_5)s), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, %(hogql_val_6)s), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, %(hogql_val_7)s), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, %(hogql_val_8)s), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, %(hogql_val_9)s), now64(6, %(hogql_val_10)s)), greaterOrEquals(toTimeZone(events.timestamp, %(hogql_val_11)s), toDateTime64('2020-12-28 23:58:00.000000', 6, 'UTC')), ifNull(equals(nullIf(nullIf(mat_pp_rgInternal, ''), 'null'), %(hogql_val_12)s), 0)) + GROUP BY events.`$session_id` + HAVING 1))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 50000 + ''' +# --- +# name: TestClickhouseSessionRecordingsListFromQuery.test_effect_of_poe_settings_on_query_generated_4_test_poe_v2_available_person_properties_are_used_in_replay_listing + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, %(hogql_val_0)s)) AS start_time, + max(toTimeZone(s.max_last_timestamp, %(hogql_val_1)s)) AS end_time, + dateDiff(%(hogql_val_2)s, start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, %(hogql_val_3)s)), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff(%(hogql_val_4)s, start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, %(hogql_val_5)s), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, %(hogql_val_6)s), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, %(hogql_val_7)s), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, %(hogql_val_8)s), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, %(hogql_val_9)s), now64(6, %(hogql_val_10)s)), greaterOrEquals(toTimeZone(events.timestamp, %(hogql_val_11)s), toDateTime64('2020-12-28 23:58:00.000000', 6, 'UTC')), ifNull(equals(nullIf(nullIf(mat_pp_rgInternal, ''), 'null'), %(hogql_val_12)s), 0)) + GROUP BY events.`$session_id` + HAVING 1))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 50000 + ''' +# --- +# name: TestClickhouseSessionRecordingsListFromQuery.test_event_filter_with_person_properties_materialized_00_poe_v2_and_materialized_columns_allowed_with_materialization + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0)) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestClickhouseSessionRecordingsListFromQuery.test_event_filter_with_person_properties_materialized_00_poe_v2_and_materialized_columns_allowed_with_materialization.1 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-28 23:58:00.000000', 6, 'UTC')), ifNull(equals(nullIf(nullIf(mat_pp_email, ''), 'null'), 'bla'), 0)) + GROUP BY events.`$session_id` + HAVING 1))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestClickhouseSessionRecordingsListFromQuery.test_event_filter_with_person_properties_materialized_01_poe_v2_and_materialized_columns_allowed_without_materialization + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0)) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestClickhouseSessionRecordingsListFromQuery.test_event_filter_with_person_properties_materialized_01_poe_v2_and_materialized_columns_allowed_without_materialization.1 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-28 23:58:00.000000', 6, 'UTC')), ifNull(equals(nullIf(nullIf(mat_pp_email, ''), 'null'), 'bla'), 0)) + GROUP BY events.`$session_id` + HAVING 1))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestClickhouseSessionRecordingsListFromQuery.test_event_filter_with_person_properties_materialized_02_poe_v2_and_materialized_columns_off_with_materialization + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0)) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestClickhouseSessionRecordingsListFromQuery.test_event_filter_with_person_properties_materialized_02_poe_v2_and_materialized_columns_off_with_materialization.1 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-28 23:58:00.000000', 6, 'UTC')), ifNull(equals(nullIf(nullIf(mat_pp_email, ''), 'null'), 'bla'), 0)) + GROUP BY events.`$session_id` + HAVING 1))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestClickhouseSessionRecordingsListFromQuery.test_event_filter_with_person_properties_materialized_03_poe_v2_and_materialized_columns_off_without_materialization + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0)) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestClickhouseSessionRecordingsListFromQuery.test_event_filter_with_person_properties_materialized_03_poe_v2_and_materialized_columns_off_without_materialization.1 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-28 23:58:00.000000', 6, 'UTC')), ifNull(equals(nullIf(nullIf(mat_pp_email, ''), 'null'), 'bla'), 0)) + GROUP BY events.`$session_id` + HAVING 1))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestClickhouseSessionRecordingsListFromQuery.test_event_filter_with_person_properties_materialized_04_poe_off_and_materialized_columns_allowed_with_materialization + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0)) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestClickhouseSessionRecordingsListFromQuery.test_event_filter_with_person_properties_materialized_04_poe_off_and_materialized_columns_allowed_with_materialization.1 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + LEFT OUTER JOIN + (SELECT argMax(person_distinct_id_overrides.person_id, person_distinct_id_overrides.version) AS person_id, person_distinct_id_overrides.distinct_id AS distinct_id + FROM person_distinct_id_overrides + WHERE equals(person_distinct_id_overrides.team_id, 99999) + GROUP BY person_distinct_id_overrides.distinct_id + HAVING ifNull(equals(argMax(person_distinct_id_overrides.is_deleted, person_distinct_id_overrides.version), 0), 0) SETTINGS optimize_aggregation_in_order=1) AS events__override ON equals(events.distinct_id, events__override.distinct_id) + LEFT JOIN + (SELECT person.id AS id, nullIf(nullIf(person.pmat_email, ''), 'null') AS properties___email + FROM person + WHERE and(equals(person.team_id, 99999), ifNull(in(tuple(person.id, person.version), + (SELECT person.id AS id, max(person.version) AS version + FROM person + WHERE equals(person.team_id, 99999) + GROUP BY person.id + HAVING and(ifNull(equals(argMax(person.is_deleted, person.version), 0), 0), ifNull(less(argMax(toTimeZone(person.created_at, 'UTC'), person.version), plus(now64(6, 'UTC'), toIntervalDay(1))), 0)))), 0)) SETTINGS optimize_aggregation_in_order=1) AS events__person ON equals(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id), events__person.id) + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-28 23:58:00.000000', 6, 'UTC')), ifNull(equals(events__person.properties___email, 'bla'), 0)) + GROUP BY events.`$session_id` + HAVING 1))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestClickhouseSessionRecordingsListFromQuery.test_event_filter_with_person_properties_materialized_05_poe_off_and_materialized_columns_allowed_without_materialization + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0)) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestClickhouseSessionRecordingsListFromQuery.test_event_filter_with_person_properties_materialized_05_poe_off_and_materialized_columns_allowed_without_materialization.1 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + LEFT OUTER JOIN + (SELECT argMax(person_distinct_id_overrides.person_id, person_distinct_id_overrides.version) AS person_id, person_distinct_id_overrides.distinct_id AS distinct_id + FROM person_distinct_id_overrides + WHERE equals(person_distinct_id_overrides.team_id, 99999) + GROUP BY person_distinct_id_overrides.distinct_id + HAVING ifNull(equals(argMax(person_distinct_id_overrides.is_deleted, person_distinct_id_overrides.version), 0), 0) SETTINGS optimize_aggregation_in_order=1) AS events__override ON equals(events.distinct_id, events__override.distinct_id) + LEFT JOIN + (SELECT person.id AS id, nullIf(nullIf(person.pmat_email, ''), 'null') AS properties___email + FROM person + WHERE and(equals(person.team_id, 99999), ifNull(in(tuple(person.id, person.version), + (SELECT person.id AS id, max(person.version) AS version + FROM person + WHERE equals(person.team_id, 99999) + GROUP BY person.id + HAVING and(ifNull(equals(argMax(person.is_deleted, person.version), 0), 0), ifNull(less(argMax(toTimeZone(person.created_at, 'UTC'), person.version), plus(now64(6, 'UTC'), toIntervalDay(1))), 0)))), 0)) SETTINGS optimize_aggregation_in_order=1) AS events__person ON equals(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id), events__person.id) + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-28 23:58:00.000000', 6, 'UTC')), ifNull(equals(events__person.properties___email, 'bla'), 0)) + GROUP BY events.`$session_id` + HAVING 1))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestClickhouseSessionRecordingsListFromQuery.test_event_filter_with_person_properties_materialized_06_poe_off_and_materialized_columns_not_allowed_with_materialization + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0)) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestClickhouseSessionRecordingsListFromQuery.test_event_filter_with_person_properties_materialized_06_poe_off_and_materialized_columns_not_allowed_with_materialization.1 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + LEFT OUTER JOIN + (SELECT argMax(person_distinct_id_overrides.person_id, person_distinct_id_overrides.version) AS person_id, person_distinct_id_overrides.distinct_id AS distinct_id + FROM person_distinct_id_overrides + WHERE equals(person_distinct_id_overrides.team_id, 99999) + GROUP BY person_distinct_id_overrides.distinct_id + HAVING ifNull(equals(argMax(person_distinct_id_overrides.is_deleted, person_distinct_id_overrides.version), 0), 0) SETTINGS optimize_aggregation_in_order=1) AS events__override ON equals(events.distinct_id, events__override.distinct_id) + LEFT JOIN + (SELECT person.id AS id, nullIf(nullIf(person.pmat_email, ''), 'null') AS properties___email + FROM person + WHERE and(equals(person.team_id, 99999), ifNull(in(tuple(person.id, person.version), + (SELECT person.id AS id, max(person.version) AS version + FROM person + WHERE equals(person.team_id, 99999) + GROUP BY person.id + HAVING and(ifNull(equals(argMax(person.is_deleted, person.version), 0), 0), ifNull(less(argMax(toTimeZone(person.created_at, 'UTC'), person.version), plus(now64(6, 'UTC'), toIntervalDay(1))), 0)))), 0)) SETTINGS optimize_aggregation_in_order=1) AS events__person ON equals(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id), events__person.id) + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-28 23:58:00.000000', 6, 'UTC')), ifNull(equals(events__person.properties___email, 'bla'), 0)) + GROUP BY events.`$session_id` + HAVING 1))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestClickhouseSessionRecordingsListFromQuery.test_event_filter_with_person_properties_materialized_07_poe_off_and_materialized_columns_not_allowed_without_materialization + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0)) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestClickhouseSessionRecordingsListFromQuery.test_event_filter_with_person_properties_materialized_07_poe_off_and_materialized_columns_not_allowed_without_materialization.1 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + LEFT OUTER JOIN + (SELECT argMax(person_distinct_id_overrides.person_id, person_distinct_id_overrides.version) AS person_id, person_distinct_id_overrides.distinct_id AS distinct_id + FROM person_distinct_id_overrides + WHERE equals(person_distinct_id_overrides.team_id, 99999) + GROUP BY person_distinct_id_overrides.distinct_id + HAVING ifNull(equals(argMax(person_distinct_id_overrides.is_deleted, person_distinct_id_overrides.version), 0), 0) SETTINGS optimize_aggregation_in_order=1) AS events__override ON equals(events.distinct_id, events__override.distinct_id) + LEFT JOIN + (SELECT person.id AS id, nullIf(nullIf(person.pmat_email, ''), 'null') AS properties___email + FROM person + WHERE and(equals(person.team_id, 99999), ifNull(in(tuple(person.id, person.version), + (SELECT person.id AS id, max(person.version) AS version + FROM person + WHERE equals(person.team_id, 99999) + GROUP BY person.id + HAVING and(ifNull(equals(argMax(person.is_deleted, person.version), 0), 0), ifNull(less(argMax(toTimeZone(person.created_at, 'UTC'), person.version), plus(now64(6, 'UTC'), toIntervalDay(1))), 0)))), 0)) SETTINGS optimize_aggregation_in_order=1) AS events__person ON equals(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id), events__person.id) + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-28 23:58:00.000000', 6, 'UTC')), ifNull(equals(events__person.properties___email, 'bla'), 0)) + GROUP BY events.`$session_id` + HAVING 1))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestClickhouseSessionRecordingsListFromQuery.test_event_filter_with_person_properties_materialized_08_poe_v1_and_materialized_columns_allowed_with_materialization + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0)) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestClickhouseSessionRecordingsListFromQuery.test_event_filter_with_person_properties_materialized_08_poe_v1_and_materialized_columns_allowed_with_materialization.1 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-28 23:58:00.000000', 6, 'UTC')), ifNull(equals(nullIf(nullIf(mat_pp_email, ''), 'null'), 'bla'), 0)) + GROUP BY events.`$session_id` + HAVING 1))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestClickhouseSessionRecordingsListFromQuery.test_event_filter_with_person_properties_materialized_09_poe_v1_and_materialized_columns_allowed_without_materialization + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0)) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestClickhouseSessionRecordingsListFromQuery.test_event_filter_with_person_properties_materialized_09_poe_v1_and_materialized_columns_allowed_without_materialization.1 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-28 23:58:00.000000', 6, 'UTC')), ifNull(equals(nullIf(nullIf(mat_pp_email, ''), 'null'), 'bla'), 0)) + GROUP BY events.`$session_id` + HAVING 1))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestClickhouseSessionRecordingsListFromQuery.test_event_filter_with_person_properties_materialized_10_poe_v1_and_not_materialized_columns_not_allowed_with_materialization + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0)) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestClickhouseSessionRecordingsListFromQuery.test_event_filter_with_person_properties_materialized_10_poe_v1_and_not_materialized_columns_not_allowed_with_materialization.1 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-28 23:58:00.000000', 6, 'UTC')), ifNull(equals(nullIf(nullIf(mat_pp_email, ''), 'null'), 'bla'), 0)) + GROUP BY events.`$session_id` + HAVING 1))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestClickhouseSessionRecordingsListFromQuery.test_event_filter_with_person_properties_materialized_11_poe_v1_and_not_materialized_columns_not_allowed_without_materialization + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0)) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestClickhouseSessionRecordingsListFromQuery.test_event_filter_with_person_properties_materialized_11_poe_v1_and_not_materialized_columns_not_allowed_without_materialization.1 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-28 23:58:00.000000', 6, 'UTC')), ifNull(equals(nullIf(nullIf(mat_pp_email, ''), 'null'), 'bla'), 0)) + GROUP BY events.`$session_id` + HAVING 1))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestClickhouseSessionRecordingsListFromQuery.test_person_id_filter_00_poe_v2_and_materialized_columns_allowed_with_materialization + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), in(s.session_id, + (SELECT DISTINCT events.`$session_id` AS `$session_id` + FROM events + WHERE and(equals(events.team_id, 99999), equals(events.person_id, '00000000-0000-0000-0000-000000000000'), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-01 13:46:23.000000', 6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-01 13:46:23.000000', 6, 'UTC')), notEmpty(events.`$session_id`)))), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0)) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestClickhouseSessionRecordingsListFromQuery.test_person_id_filter_01_poe_v2_and_materialized_columns_allowed_without_materialization + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), in(s.session_id, + (SELECT DISTINCT events.`$session_id` AS `$session_id` + FROM events + WHERE and(equals(events.team_id, 99999), equals(events.person_id, '00000000-0000-0000-0000-000000000000'), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-01 13:46:23.000000', 6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-01 13:46:23.000000', 6, 'UTC')), notEmpty(events.`$session_id`)))), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0)) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestClickhouseSessionRecordingsListFromQuery.test_person_id_filter_02_poe_v2_and_materialized_columns_off_with_materialization + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), in(s.session_id, + (SELECT DISTINCT events.`$session_id` AS `$session_id` + FROM events + WHERE and(equals(events.team_id, 99999), equals(events.person_id, '00000000-0000-0000-0000-000000000000'), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-01 13:46:23.000000', 6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-01 13:46:23.000000', 6, 'UTC')), notEmpty(events.`$session_id`)))), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0)) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestClickhouseSessionRecordingsListFromQuery.test_person_id_filter_03_poe_v2_and_materialized_columns_off_without_materialization + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), in(s.session_id, + (SELECT DISTINCT events.`$session_id` AS `$session_id` + FROM events + WHERE and(equals(events.team_id, 99999), equals(events.person_id, '00000000-0000-0000-0000-000000000000'), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-01 13:46:23.000000', 6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-01 13:46:23.000000', 6, 'UTC')), notEmpty(events.`$session_id`)))), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0)) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestClickhouseSessionRecordingsListFromQuery.test_person_id_filter_04_poe_off_and_materialized_columns_allowed_with_materialization + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), in(s.session_id, + (SELECT DISTINCT events.`$session_id` AS `$session_id` + FROM events + LEFT OUTER JOIN + (SELECT argMax(person_distinct_id_overrides.person_id, person_distinct_id_overrides.version) AS person_id, person_distinct_id_overrides.distinct_id AS distinct_id + FROM person_distinct_id_overrides + WHERE equals(person_distinct_id_overrides.team_id, 99999) + GROUP BY person_distinct_id_overrides.distinct_id + HAVING ifNull(equals(argMax(person_distinct_id_overrides.is_deleted, person_distinct_id_overrides.version), 0), 0) SETTINGS optimize_aggregation_in_order=1) AS events__override ON equals(events.distinct_id, events__override.distinct_id) + WHERE and(equals(events.team_id, 99999), ifNull(equals(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id), '00000000-0000-0000-0000-000000000000'), 0), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-01 13:46:23.000000', 6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-01 13:46:23.000000', 6, 'UTC')), notEmpty(events.`$session_id`)))), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0)) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestClickhouseSessionRecordingsListFromQuery.test_person_id_filter_05_poe_off_and_materialized_columns_allowed_without_materialization + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), in(s.session_id, + (SELECT DISTINCT events.`$session_id` AS `$session_id` + FROM events + LEFT OUTER JOIN + (SELECT argMax(person_distinct_id_overrides.person_id, person_distinct_id_overrides.version) AS person_id, person_distinct_id_overrides.distinct_id AS distinct_id + FROM person_distinct_id_overrides + WHERE equals(person_distinct_id_overrides.team_id, 99999) + GROUP BY person_distinct_id_overrides.distinct_id + HAVING ifNull(equals(argMax(person_distinct_id_overrides.is_deleted, person_distinct_id_overrides.version), 0), 0) SETTINGS optimize_aggregation_in_order=1) AS events__override ON equals(events.distinct_id, events__override.distinct_id) + WHERE and(equals(events.team_id, 99999), ifNull(equals(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id), '00000000-0000-0000-0000-000000000000'), 0), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-01 13:46:23.000000', 6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-01 13:46:23.000000', 6, 'UTC')), notEmpty(events.`$session_id`)))), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0)) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestClickhouseSessionRecordingsListFromQuery.test_person_id_filter_06_poe_off_and_materialized_columns_not_allowed_with_materialization + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), in(s.session_id, + (SELECT DISTINCT events.`$session_id` AS `$session_id` + FROM events + LEFT OUTER JOIN + (SELECT argMax(person_distinct_id_overrides.person_id, person_distinct_id_overrides.version) AS person_id, person_distinct_id_overrides.distinct_id AS distinct_id + FROM person_distinct_id_overrides + WHERE equals(person_distinct_id_overrides.team_id, 99999) + GROUP BY person_distinct_id_overrides.distinct_id + HAVING ifNull(equals(argMax(person_distinct_id_overrides.is_deleted, person_distinct_id_overrides.version), 0), 0) SETTINGS optimize_aggregation_in_order=1) AS events__override ON equals(events.distinct_id, events__override.distinct_id) + WHERE and(equals(events.team_id, 99999), ifNull(equals(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id), '00000000-0000-0000-0000-000000000000'), 0), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-01 13:46:23.000000', 6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-01 13:46:23.000000', 6, 'UTC')), notEmpty(events.`$session_id`)))), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0)) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestClickhouseSessionRecordingsListFromQuery.test_person_id_filter_07_poe_off_and_materialized_columns_not_allowed_without_materialization + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), in(s.session_id, + (SELECT DISTINCT events.`$session_id` AS `$session_id` + FROM events + LEFT OUTER JOIN + (SELECT argMax(person_distinct_id_overrides.person_id, person_distinct_id_overrides.version) AS person_id, person_distinct_id_overrides.distinct_id AS distinct_id + FROM person_distinct_id_overrides + WHERE equals(person_distinct_id_overrides.team_id, 99999) + GROUP BY person_distinct_id_overrides.distinct_id + HAVING ifNull(equals(argMax(person_distinct_id_overrides.is_deleted, person_distinct_id_overrides.version), 0), 0) SETTINGS optimize_aggregation_in_order=1) AS events__override ON equals(events.distinct_id, events__override.distinct_id) + WHERE and(equals(events.team_id, 99999), ifNull(equals(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id), '00000000-0000-0000-0000-000000000000'), 0), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-01 13:46:23.000000', 6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-01 13:46:23.000000', 6, 'UTC')), notEmpty(events.`$session_id`)))), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0)) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestClickhouseSessionRecordingsListFromQuery.test_person_id_filter_08_poe_v1_and_materialized_columns_allowed_with_materialization + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), in(s.session_id, + (SELECT DISTINCT events.`$session_id` AS `$session_id` + FROM events + LEFT OUTER JOIN + (SELECT argMax(person_distinct_id_overrides.person_id, person_distinct_id_overrides.version) AS person_id, person_distinct_id_overrides.distinct_id AS distinct_id + FROM person_distinct_id_overrides + WHERE equals(person_distinct_id_overrides.team_id, 99999) + GROUP BY person_distinct_id_overrides.distinct_id + HAVING ifNull(equals(argMax(person_distinct_id_overrides.is_deleted, person_distinct_id_overrides.version), 0), 0) SETTINGS optimize_aggregation_in_order=1) AS events__override ON equals(events.distinct_id, events__override.distinct_id) + WHERE and(equals(events.team_id, 99999), ifNull(equals(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id), '00000000-0000-0000-0000-000000000000'), 0), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-01 13:46:23.000000', 6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-01 13:46:23.000000', 6, 'UTC')), notEmpty(events.`$session_id`)))), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0)) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestClickhouseSessionRecordingsListFromQuery.test_person_id_filter_09_poe_v1_and_materialized_columns_allowed_without_materialization + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), in(s.session_id, + (SELECT DISTINCT events.`$session_id` AS `$session_id` + FROM events + LEFT OUTER JOIN + (SELECT argMax(person_distinct_id_overrides.person_id, person_distinct_id_overrides.version) AS person_id, person_distinct_id_overrides.distinct_id AS distinct_id + FROM person_distinct_id_overrides + WHERE equals(person_distinct_id_overrides.team_id, 99999) + GROUP BY person_distinct_id_overrides.distinct_id + HAVING ifNull(equals(argMax(person_distinct_id_overrides.is_deleted, person_distinct_id_overrides.version), 0), 0) SETTINGS optimize_aggregation_in_order=1) AS events__override ON equals(events.distinct_id, events__override.distinct_id) + WHERE and(equals(events.team_id, 99999), ifNull(equals(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id), '00000000-0000-0000-0000-000000000000'), 0), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-01 13:46:23.000000', 6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-01 13:46:23.000000', 6, 'UTC')), notEmpty(events.`$session_id`)))), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0)) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestClickhouseSessionRecordingsListFromQuery.test_person_id_filter_10_poe_v1_and_not_materialized_columns_not_allowed_with_materialization + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), in(s.session_id, + (SELECT DISTINCT events.`$session_id` AS `$session_id` + FROM events + LEFT OUTER JOIN + (SELECT argMax(person_distinct_id_overrides.person_id, person_distinct_id_overrides.version) AS person_id, person_distinct_id_overrides.distinct_id AS distinct_id + FROM person_distinct_id_overrides + WHERE equals(person_distinct_id_overrides.team_id, 99999) + GROUP BY person_distinct_id_overrides.distinct_id + HAVING ifNull(equals(argMax(person_distinct_id_overrides.is_deleted, person_distinct_id_overrides.version), 0), 0) SETTINGS optimize_aggregation_in_order=1) AS events__override ON equals(events.distinct_id, events__override.distinct_id) + WHERE and(equals(events.team_id, 99999), ifNull(equals(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id), '00000000-0000-0000-0000-000000000000'), 0), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-01 13:46:23.000000', 6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-01 13:46:23.000000', 6, 'UTC')), notEmpty(events.`$session_id`)))), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0)) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestClickhouseSessionRecordingsListFromQuery.test_person_id_filter_11_poe_v1_and_not_materialized_columns_not_allowed_without_materialization + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), in(s.session_id, + (SELECT DISTINCT events.`$session_id` AS `$session_id` + FROM events + LEFT OUTER JOIN + (SELECT argMax(person_distinct_id_overrides.person_id, person_distinct_id_overrides.version) AS person_id, person_distinct_id_overrides.distinct_id AS distinct_id + FROM person_distinct_id_overrides + WHERE equals(person_distinct_id_overrides.team_id, 99999) + GROUP BY person_distinct_id_overrides.distinct_id + HAVING ifNull(equals(argMax(person_distinct_id_overrides.is_deleted, person_distinct_id_overrides.version), 0), 0) SETTINGS optimize_aggregation_in_order=1) AS events__override ON equals(events.distinct_id, events__override.distinct_id) + WHERE and(equals(events.team_id, 99999), ifNull(equals(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id), '00000000-0000-0000-0000-000000000000'), 0), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-01 13:46:23.000000', 6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-01 13:46:23.000000', 6, 'UTC')), notEmpty(events.`$session_id`)))), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0)) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- diff --git a/ee/session_recordings/queries/test/test_session_recording_list_from_query.py b/ee/session_recordings/queries/test/test_session_recording_list_from_query.py new file mode 100644 index 0000000000000..94d54baaf52a2 --- /dev/null +++ b/ee/session_recordings/queries/test/test_session_recording_list_from_query.py @@ -0,0 +1,347 @@ +import re +from itertools import product +from uuid import uuid4 + +from dateutil.relativedelta import relativedelta +from django.utils.timezone import now +from freezegun import freeze_time +from parameterized import parameterized + +from ee.clickhouse.materialized_columns.columns import materialize +from posthog.clickhouse.client import sync_execute +from posthog.hogql.ast import CompareOperation, And, SelectQuery +from posthog.hogql.base import Expr +from posthog.hogql.context import HogQLContext +from posthog.hogql.printer import print_ast +from posthog.models import Person +from posthog.schema import PersonsOnEventsMode, RecordingsQuery +from posthog.session_recordings.queries.session_recording_list_from_query import SessionRecordingListFromQuery +from posthog.session_recordings.queries.test.session_replay_sql import produce_replay_summary +from posthog.session_recordings.sql.session_replay_event_sql import TRUNCATE_SESSION_REPLAY_EVENTS_TABLE_SQL +from posthog.test.base import ( + APIBaseTest, + ClickhouseTestMixin, + QueryMatchingTest, + snapshot_clickhouse_queries, + _create_event, +) + + +# The HogQL pair of TestClickhouseSessionRecordingsListFromSessionReplay can be renamed when delete the old one +@freeze_time("2021-01-01T13:46:23") +class TestClickhouseSessionRecordingsListFromQuery(ClickhouseTestMixin, APIBaseTest, QueryMatchingTest): + def _print_query(self, query: SelectQuery) -> str: + return print_ast( + query, + HogQLContext(team_id=self.team.pk, enable_select_queries=True), + "clickhouse", + pretty=True, + ) + + def tearDown(self) -> None: + sync_execute(TRUNCATE_SESSION_REPLAY_EVENTS_TABLE_SQL()) + + @property + def base_time(self): + return (now() - relativedelta(hours=1)).replace(microsecond=0, second=0) + + def create_event( + self, + distinct_id, + timestamp, + team=None, + event_name="$pageview", + properties=None, + ): + if team is None: + team = self.team + if properties is None: + properties = {"$os": "Windows 95", "$current_url": "aloha.com/2"} + return _create_event( + team=team, + event=event_name, + timestamp=timestamp, + distinct_id=distinct_id, + properties=properties, + ) + + @parameterized.expand( + [ + [ + "test_poe_v1_still_falls_back_to_person_subquery", + True, + False, + False, + PersonsOnEventsMode.PERSON_ID_NO_OVERRIDE_PROPERTIES_ON_EVENTS, + ], + [ + "test_poe_being_unavailable_we_fall_back_to_person_id_overrides", + False, + False, + False, + PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_JOINED, + ], + [ + "test_poe_being_unavailable_we_fall_back_to_person_subquery_but_still_use_mat_props", + False, + False, + False, + PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_JOINED, + ], + [ + "test_allow_denormalised_props_fix_does_not_stop_all_poe_processing", + False, + True, + False, + PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_ON_EVENTS, + ], + [ + "test_poe_v2_available_person_properties_are_used_in_replay_listing", + False, + True, + True, + PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_ON_EVENTS, + ], + ] + ) + def test_effect_of_poe_settings_on_query_generated( + self, + _name: str, + poe_v1: bool, + poe_v2: bool, + allow_denormalized_props: bool, + expected_poe_mode: PersonsOnEventsMode, + ) -> None: + with self.settings( + PERSON_ON_EVENTS_OVERRIDE=poe_v1, + PERSON_ON_EVENTS_V2_OVERRIDE=poe_v2, + ALLOW_DENORMALIZED_PROPS_IN_LISTING=allow_denormalized_props, + ): + assert self.team.person_on_events_mode == expected_poe_mode + materialize("events", "rgInternal", table_column="person_properties") + + query = RecordingsQuery.model_validate( + { + "properties": [ + { + "key": "rgInternal", + "value": ["false"], + "operator": "exact", + "type": "person", + } + ] + }, + ) + session_recording_list_instance = SessionRecordingListFromQuery( + query=query, team=self.team, hogql_query_modifiers=None + ) + + hogql_parsed_select = session_recording_list_instance.get_query() + printed_query = self._print_query(hogql_parsed_select) + + person_filtering_expr = self._matching_person_filter_expr_from(hogql_parsed_select) + + self._assert_is_events_person_filter(person_filtering_expr) + + if poe_v1 or poe_v2: + # Property used directly from event (from materialized column) + assert "ifNull(equals(nullIf(nullIf(mat_pp_rgInternal, ''), 'null')" in printed_query + else: + # We get the person property value from the persons JOIN + assert re.search( + r"argMax\(replaceRegexpAll\(nullIf\(nullIf\(JSONExtractRaw\(person\.properties, %\(hogql_val_\d+\)s\), ''\), 'null'\), '^\"|\"\$', ''\), person\.version\) AS properties___rgInternal", + printed_query, + ) + # Then we actually filter on that property value + assert re.search( + r"ifNull\(equals\(events__person\.properties___rgInternal, %\(hogql_val_\d+\)s\), 0\)", + printed_query, + ) + self.assertQueryMatchesSnapshot(printed_query) + + def _assert_is_pdi_filter(self, person_filtering_expr: list[Expr]) -> None: + assert person_filtering_expr[0].right.select_from.table.chain == ["person_distinct_ids"] + assert person_filtering_expr[0].right.where.left.chain == ["person", "properties", "rgInternal"] + + def _assert_is_events_person_filter(self, person_filtering_expr: list[Expr]) -> None: + assert person_filtering_expr[0].right.select_from.table.chain == ["events"] + event_person_condition = [ + x + for x in person_filtering_expr[0].right.where.exprs + if isinstance(x, CompareOperation) and x.left.chain == ["person", "properties", "rgInternal"] + ] + assert len(event_person_condition) == 1 + + def _matching_person_filter_expr_from(self, hogql_parsed_select: SelectQuery) -> list[Expr]: + where_conditions: list[Expr] = hogql_parsed_select.where.exprs + ands = [x for x in where_conditions if isinstance(x, And)] + assert len(ands) == 1 + and_comparisons = [x for x in ands[0].exprs if isinstance(x, CompareOperation)] + assert len(and_comparisons) == 1 + assert isinstance(and_comparisons[0].right, SelectQuery) + return and_comparisons + + settings_combinations = [ + ["poe v2 and materialized columns allowed", False, True, True], + ["poe v2 and materialized columns off", False, True, False], + ["poe off and materialized columns allowed", False, False, True], + ["poe off and materialized columns not allowed", False, False, False], + ["poe v1 and materialized columns allowed", True, False, True], + ["poe v1 and not materialized columns not allowed", True, False, False], + ] + + # Options for "materialize person columns" + materialization_options = [ + [" with materialization", True], + [" without materialization", False], + ] + + # Expand the parameter list to the product of all combinations with "materialize person columns" + # e.g. [a, b] x [c, d] = [a, c], [a, d], [b, c], [b, d] + test_case_combinations = [ + [f"{name}{mat_option}", poe_v1, poe, mat_columns, mat_person] + for (name, poe_v1, poe, mat_columns), (mat_option, mat_person) in product( + settings_combinations, materialization_options + ) + ] + + @parameterized.expand(test_case_combinations) + @snapshot_clickhouse_queries + def test_event_filter_with_person_properties_materialized( + self, + _name: str, + poe1_enabled: bool, + poe2_enabled: bool, + allow_denormalised_props: bool, + materialize_person_props: bool, + ) -> None: + # KLUDGE: I couldn't figure out how to use @also_test_with_materialized_columns(person_properties=["email"]) + # KLUDGE: and the parameterized.expand decorator at the same time, so we generate test case combos + # KLUDGE: for materialization on and off to test both sides the way the decorator would have + if materialize_person_props: + materialize("events", "email", table_column="person_properties") + materialize("person", "email") + + with self.settings( + PERSON_ON_EVENTS_OVERRIDE=poe1_enabled, + PERSON_ON_EVENTS_V2_OVERRIDE=poe2_enabled, + ALLOW_DENORMALIZED_PROPS_IN_LISTING=allow_denormalised_props, + ): + user_one = "test_event_filter_with_person_properties-user" + user_two = "test_event_filter_with_person_properties-user2" + session_id_one = f"test_event_filter_with_person_properties-1-{str(uuid4())}" + session_id_two = f"test_event_filter_with_person_properties-2-{str(uuid4())}" + + Person.objects.create(team=self.team, distinct_ids=[user_one], properties={"email": "bla"}) + Person.objects.create(team=self.team, distinct_ids=[user_two], properties={"email": "bla2"}) + + self._add_replay_with_pageview(session_id_one, user_one) + produce_replay_summary( + distinct_id=user_one, + session_id=session_id_one, + first_timestamp=(self.base_time + relativedelta(seconds=30)), + team_id=self.team.id, + ) + self._add_replay_with_pageview(session_id_two, user_two) + produce_replay_summary( + distinct_id=user_two, + session_id=session_id_two, + first_timestamp=(self.base_time + relativedelta(seconds=30)), + team_id=self.team.id, + ) + + match_everyone_filter = RecordingsQuery.model_validate( + {"properties": []}, + ) + + session_recording_list_instance = SessionRecordingListFromQuery( + query=match_everyone_filter, team=self.team, hogql_query_modifiers=None + ) + (session_recordings, _, _) = session_recording_list_instance.run() + + assert sorted([x["session_id"] for x in session_recordings]) == sorted([session_id_one, session_id_two]) + + match_bla_filter = RecordingsQuery.model_validate( + { + "properties": [ + { + "key": "email", + "value": ["bla"], + "operator": "exact", + "type": "person", + } + ] + }, + ) + + session_recording_list_instance = SessionRecordingListFromQuery( + query=match_bla_filter, team=self.team, hogql_query_modifiers=None + ) + (session_recordings, _, _) = session_recording_list_instance.run() + + assert len(session_recordings) == 1 + assert session_recordings[0]["session_id"] == session_id_one + + def _add_replay_with_pageview(self, session_id: str, user: str) -> None: + self.create_event( + user, + self.base_time, + properties={"$session_id": session_id, "$window_id": str(uuid4())}, + ) + produce_replay_summary( + distinct_id=user, + session_id=session_id, + first_timestamp=self.base_time, + team_id=self.team.id, + ) + + @parameterized.expand(test_case_combinations) + @snapshot_clickhouse_queries + def test_person_id_filter( + self, + _name: str, + poe2_enabled: bool, + poe1_enabled: bool, + allow_denormalised_props: bool, + materialize_person_props: bool, + ) -> None: + # KLUDGE: I couldn't figure out how to use @also_test_with_materialized_columns(person_properties=["email"]) + # KLUDGE: and the parameterized.expand decorator at the same time, so we generate test case combos + # KLUDGE: for materialization on and off to test both sides the way the decorator would have + if materialize_person_props: + # it shouldn't matter to this test whether any column is materialized + # but let's keep the tests in this file similar so we flush out any unexpected interactions + materialize("events", "email", table_column="person_properties") + materialize("person", "email") + + with self.settings( + PERSON_ON_EVENTS_OVERRIDE=poe1_enabled, + PERSON_ON_EVENTS_V2_OVERRIDE=poe2_enabled, + ALLOW_DENORMALIZED_PROPS_IN_LISTING=allow_denormalised_props, + ): + three_user_ids = ["person-1-distinct-1", "person-1-distinct-2", "person-2"] + session_id_one = f"test_person_id_filter-session-one" + session_id_two = f"test_person_id_filter-session-two" + session_id_three = f"test_person_id_filter-session-three" + + p = Person.objects.create( + team=self.team, + distinct_ids=[three_user_ids[0], three_user_ids[1]], + properties={"email": "bla"}, + ) + Person.objects.create( + team=self.team, + distinct_ids=[three_user_ids[2]], + properties={"email": "bla2"}, + ) + + self._add_replay_with_pageview(session_id_one, three_user_ids[0]) + self._add_replay_with_pageview(session_id_two, three_user_ids[1]) + self._add_replay_with_pageview(session_id_three, three_user_ids[2]) + + query = RecordingsQuery.model_validate({"person_uuid": str(p.uuid)}) + session_recording_list_instance = SessionRecordingListFromQuery( + query=query, team=self.team, hogql_query_modifiers=None + ) + (session_recordings, _, _) = session_recording_list_instance.run() + assert sorted([r["session_id"] for r in session_recordings]) == sorted([session_id_two, session_id_one]) diff --git a/ee/session_recordings/session_recording_playlist.py b/ee/session_recordings/session_recording_playlist.py index 8947e1c270ee4..a3dc50c1228f5 100644 --- a/ee/session_recordings/session_recording_playlist.py +++ b/ee/session_recordings/session_recording_playlist.py @@ -34,7 +34,13 @@ ClickHouseBurstRateThrottle, ClickHouseSustainedRateThrottle, ) -from posthog.session_recordings.session_recording_api import list_recordings_response +from posthog.schema import RecordingsQuery +from posthog.session_recordings.session_recording_api import ( + list_recordings_response, + list_recordings, + query_as_params_to_dict, + list_recordings_from_query, +) from posthog.utils import relative_date_parse logger = structlog.get_logger(__name__) @@ -224,10 +230,19 @@ def recordings(self, request: request.Request, *args: Any, **kwargs: Any) -> res .values_list("recording_id", flat=True) ) - filter = SessionRecordingsFilter(request=request, team=self.team) - filter = filter.shallow_clone({SESSION_RECORDINGS_FILTER_IDS: json.dumps(playlist_items)}) + use_query_type = (request.GET.get("as_query", "False")).lower() == "true" - return list_recordings_response(filter, request, self.get_serializer_context()) + if use_query_type: + data_dict = query_as_params_to_dict(request.GET.dict()) + query = RecordingsQuery.model_validate(data_dict) + query.session_ids = playlist_items + return list_recordings_response( + list_recordings_from_query(query, request, context=self.get_serializer_context()) + ) + else: + filter = SessionRecordingsFilter(request=request, team=self.team) + filter = filter.shallow_clone({SESSION_RECORDINGS_FILTER_IDS: json.dumps(playlist_items)}) + return list_recordings_response(list_recordings(filter, request, context=self.get_serializer_context())) # As of now, you can only "update" a session recording by adding or removing a recording from a static playlist @action( diff --git a/frontend/__snapshots__/replay-player-success--recent-recordings--dark.png b/frontend/__snapshots__/replay-player-success--recent-recordings--dark.png index 000613096ba38..c02be573f0f34 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 e46ba42f61155..ace7d3debd600 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 a8faf2bcdfdb3..03870e07340f4 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 1979e48fd60a1..576caf4f434a2 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-other-settings--settings-project--dark.png b/frontend/__snapshots__/scenes-other-settings--settings-project--dark.png index 03b9d8efb351c..23e20084b753d 100644 Binary files a/frontend/__snapshots__/scenes-other-settings--settings-project--dark.png and b/frontend/__snapshots__/scenes-other-settings--settings-project--dark.png differ diff --git a/frontend/__snapshots__/scenes-other-settings--settings-project--light.png b/frontend/__snapshots__/scenes-other-settings--settings-project--light.png index 6264d0d4f49f9..f7b7f6ebd06e6 100644 Binary files a/frontend/__snapshots__/scenes-other-settings--settings-project--light.png and b/frontend/__snapshots__/scenes-other-settings--settings-project--light.png differ diff --git a/frontend/__snapshots__/scenes-other-settings--settings-project-with-replay-features--dark.png b/frontend/__snapshots__/scenes-other-settings--settings-project-with-replay-features--dark.png index 5ce39a5222cc1..eb8542bfdcfcc 100644 Binary files a/frontend/__snapshots__/scenes-other-settings--settings-project-with-replay-features--dark.png and b/frontend/__snapshots__/scenes-other-settings--settings-project-with-replay-features--dark.png differ diff --git a/frontend/__snapshots__/scenes-other-settings--settings-project-with-replay-features--light.png b/frontend/__snapshots__/scenes-other-settings--settings-project-with-replay-features--light.png index 7c790add1284e..999b15cfdd917 100644 Binary files a/frontend/__snapshots__/scenes-other-settings--settings-project-with-replay-features--light.png and b/frontend/__snapshots__/scenes-other-settings--settings-project-with-replay-features--light.png differ diff --git a/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-all-options--dark.png b/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-all-options--dark.png index 03b9d8efb351c..23e20084b753d 100644 Binary files a/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-all-options--dark.png and b/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-all-options--dark.png differ diff --git a/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-all-options--light.png b/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-all-options--light.png index 6264d0d4f49f9..f7b7f6ebd06e6 100644 Binary files a/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-all-options--light.png and b/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-all-options--light.png differ diff --git a/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-password-only--dark.png b/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-password-only--dark.png index 03b9d8efb351c..23e20084b753d 100644 Binary files a/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-password-only--dark.png and b/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-password-only--dark.png differ diff --git a/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-password-only--light.png b/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-password-only--light.png index 6264d0d4f49f9..f7b7f6ebd06e6 100644 Binary files a/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-password-only--light.png and b/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-password-only--light.png differ diff --git a/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-sso-enforced-github--dark.png b/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-sso-enforced-github--dark.png index 03b9d8efb351c..23e20084b753d 100644 Binary files a/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-sso-enforced-github--dark.png and b/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-sso-enforced-github--dark.png differ diff --git a/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-sso-enforced-github--light.png b/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-sso-enforced-github--light.png index 6264d0d4f49f9..f7b7f6ebd06e6 100644 Binary files a/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-sso-enforced-github--light.png and b/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-sso-enforced-github--light.png differ diff --git a/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-sso-enforced-google--dark.png b/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-sso-enforced-google--dark.png index 03b9d8efb351c..23e20084b753d 100644 Binary files a/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-sso-enforced-google--dark.png and b/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-sso-enforced-google--dark.png differ diff --git a/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-sso-enforced-google--light.png b/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-sso-enforced-google--light.png index 6264d0d4f49f9..f7b7f6ebd06e6 100644 Binary files a/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-sso-enforced-google--light.png and b/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-sso-enforced-google--light.png differ diff --git a/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-sso-enforced-saml--dark.png b/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-sso-enforced-saml--dark.png index 03b9d8efb351c..23e20084b753d 100644 Binary files a/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-sso-enforced-saml--dark.png and b/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-sso-enforced-saml--dark.png differ diff --git a/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-sso-enforced-saml--light.png b/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-sso-enforced-saml--light.png index 6264d0d4f49f9..f7b7f6ebd06e6 100644 Binary files a/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-sso-enforced-saml--light.png and b/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-sso-enforced-saml--light.png differ diff --git a/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-sso-only--dark.png b/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-sso-only--dark.png index 03b9d8efb351c..23e20084b753d 100644 Binary files a/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-sso-only--dark.png and b/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-sso-only--dark.png differ diff --git a/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-sso-only--light.png b/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-sso-only--light.png index 6264d0d4f49f9..f7b7f6ebd06e6 100644 Binary files a/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-sso-only--light.png and b/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-sso-only--light.png differ diff --git a/frontend/__snapshots__/scenes-other-settings--settings-web-vitals--dark.png b/frontend/__snapshots__/scenes-other-settings--settings-web-vitals--dark.png index 3f4df5f5aef57..7da712a894a4c 100644 Binary files a/frontend/__snapshots__/scenes-other-settings--settings-web-vitals--dark.png and b/frontend/__snapshots__/scenes-other-settings--settings-web-vitals--dark.png differ diff --git a/frontend/__snapshots__/scenes-other-settings--settings-web-vitals--light.png b/frontend/__snapshots__/scenes-other-settings--settings-web-vitals--light.png index 5feb120039c45..cadfa35ff0a63 100644 Binary files a/frontend/__snapshots__/scenes-other-settings--settings-web-vitals--light.png and b/frontend/__snapshots__/scenes-other-settings--settings-web-vitals--light.png differ diff --git a/frontend/public/services/pineapple.png b/frontend/public/services/pineapple.png new file mode 100644 index 0000000000000..3fd475fb500d2 Binary files /dev/null and b/frontend/public/services/pineapple.png differ diff --git a/frontend/src/layout/navigation-3000/navigationLogic.tsx b/frontend/src/layout/navigation-3000/navigationLogic.tsx index be44236c6dc9b..8507fe104dade 100644 --- a/frontend/src/layout/navigation-3000/navigationLogic.tsx +++ b/frontend/src/layout/navigation-3000/navigationLogic.tsx @@ -1,17 +1,17 @@ import { - IconChat, IconCursorClick, IconDashboard, IconDatabase, - IconDecisionTree, IconGraph, IconHome, IconLive, IconLogomark, IconMegaphone, + IconMessage, IconNotebook, IconPeople, IconPieChart, + IconPlug, IconPlusSmall, IconRewindPlay, IconRocket, @@ -492,7 +492,7 @@ export const navigation3000Logic = kea([ { identifier: Scene.Surveys, label: 'Surveys', - icon: , + icon: , to: urls.surveys(), }, featureFlags[FEATURE_FLAGS.PRODUCT_INTRO_PAGES] !== 'test' || hasOnboardedFeatureFlags @@ -506,7 +506,7 @@ export const navigation3000Logic = kea([ { identifier: Scene.DataWarehouse, label: 'Data warehouse', - icon: , + icon: , to: isUsingSidebar ? undefined : urls.dataWarehouse(), }, featureFlags[FEATURE_FLAGS.SQL_EDITOR] @@ -529,8 +529,8 @@ export const navigation3000Logic = kea([ hasOnboardedAnyProduct ? { identifier: Scene.Pipeline, - label: 'Data pipeline', - icon: , + label: 'Data pipelines', + icon: , to: urls.pipeline(), } : null, diff --git a/frontend/src/layout/navigation-3000/sidepanel/panels/SidePanelSupport.tsx b/frontend/src/layout/navigation-3000/sidepanel/panels/SidePanelSupport.tsx index 927f6e16916af..d2bbaca004f2a 100644 --- a/frontend/src/layout/navigation-3000/sidepanel/panels/SidePanelSupport.tsx +++ b/frontend/src/layout/navigation-3000/sidepanel/panels/SidePanelSupport.tsx @@ -2,13 +2,13 @@ import { IconAI, IconChevronDown, IconDatabase, - IconDecisionTree, IconFeatures, IconGraph, IconHelmet, IconMap, IconMessage, IconPieChart, + IconPlug, IconRewindPlay, IconStack, IconTestTube, @@ -72,7 +72,7 @@ const PRODUCTS = [ { name: 'Data pipelines', slug: 'cdp', - icon: , + icon: , }, { name: 'Data warehouse', diff --git a/frontend/src/lib/components/DefinitionPopover/definitionPopoverLogic.ts b/frontend/src/lib/components/DefinitionPopover/definitionPopoverLogic.ts index 9781c1e8e2bbd..c85c57d67a883 100644 --- a/frontend/src/lib/components/DefinitionPopover/definitionPopoverLogic.ts +++ b/frontend/src/lib/components/DefinitionPopover/definitionPopoverLogic.ts @@ -134,9 +134,11 @@ export const definitionPopoverLogic = kea([ } if (!('distinct_id_field' in item)) { - const idField = Object.values(warehouseItem.fields).find((n) => n.name === 'id') - if (idField) { - warehouseItem['distinct_id_field'] = idField.name + const distinctIdField = + Object.values(warehouseItem.fields).find((n) => n.name === 'distinct_id') ?? + Object.values(warehouseItem.fields).find((n) => n.name === 'id') + if (distinctIdField) { + warehouseItem['distinct_id_field'] = distinctIdField.name } } diff --git a/frontend/src/lib/components/UniversalFilters/UniversalFilters.tsx b/frontend/src/lib/components/UniversalFilters/UniversalFilters.tsx index 117c6b678c59e..a253805e75260 100644 --- a/frontend/src/lib/components/UniversalFilters/UniversalFilters.tsx +++ b/frontend/src/lib/components/UniversalFilters/UniversalFilters.tsx @@ -3,6 +3,7 @@ import { LemonButton, LemonButtonProps, LemonDropdown, Popover } from '@posthog/ import { BindLogic, useActions, useValues } from 'kea' import { useState } from 'react' +import { AnyDataNode } from '~/queries/schema' import { UniversalFiltersGroup, UniversalFilterValue } from '~/types' import { TaxonomicPropertyFilter } from '../PropertyFilters/components/TaxonomicPropertyFilter' @@ -75,12 +76,14 @@ const Value = ({ onChange, onRemove, initiallyOpen = false, + metadataSource, }: { index: number filter: UniversalFilterValue onChange: (property: UniversalFilterValue) => void onRemove: () => void initiallyOpen?: boolean + metadataSource?: AnyDataNode }): JSX.Element => { const { rootKey, taxonomicPropertyFilterGroupTypes } = useValues(universalFiltersLogic) @@ -103,6 +106,7 @@ const Value = ({ onChange={(properties) => onChange({ ...filter, properties })} disablePopover taxonomicGroupTypes={[TaxonomicFilterGroupType.EventProperties]} + metadataSource={metadataSource} /> ) : isEditable ? ( ([ newValues.push(newFeatureFlagFilter) } else { const propertyType = - item.propertyFilterType ?? taxonomicFilterTypeToPropertyFilterType(taxonomicGroup.type) + item?.propertyFilterType ?? taxonomicFilterTypeToPropertyFilterType(taxonomicGroup.type) if (propertyKey && propertyType) { const newPropertyFilter = createDefaultPropertyFilter( {}, diff --git a/frontend/src/lib/constants.tsx b/frontend/src/lib/constants.tsx index 0459d616a4d5f..60e6358245b81 100644 --- a/frontend/src/lib/constants.tsx +++ b/frontend/src/lib/constants.tsx @@ -223,7 +223,6 @@ export const FEATURE_FLAGS = { MESSAGING: 'messaging', // owner @mariusandra #team-cdp SESSION_REPLAY_URL_BLOCKLIST: 'session-replay-url-blocklist', // owner: @richard-better #team-replay BILLING_TRIAL_FLOW: 'billing-trial-flow', // owner: @zach - DEAD_CLICKS_AUTOCAPTURE: 'dead-clicks-autocapture', // owner: @pauldambra #team-replay EDIT_DWH_SOURCE_CONFIG: 'edit_dwh_source_config', // owner: @Gilbert09 #team-data-warehouse AI_SURVEY_RESPONSE_SUMMARY: 'ai-survey-response-summary', // owner: @pauldambra CUSTOM_CHANNEL_TYPE_RULES: 'custom-channel-type-rules', // owner: @robbie-c #team-web-analytics @@ -233,6 +232,9 @@ export const FEATURE_FLAGS = { WEB_ANALYTICS_WARN_CUSTOM_EVENT_NO_SESSION: 'web-analytics-warn-custom-event-no-session', // owner: @robbie-c #team-web-analytics TWO_FACTOR_UI: 'two-factor-ui', // owner: @zach SITE_DESTINATIONS: 'site-destinations', // owner: @mariusandra #team-cdp + SITE_APP_FUNCTIONS: 'site-app-functions', // owner: @mariusandra #team-cdp + REPLAY_HOGQL_FILTERS: 'replay-hogql-filters', // owner: @pauldambra #team-replay + REPLAY_LIST_RECORDINGS_AS_QUERY: 'replay-list-recordings-as-query', // owner: @pauldambra #team-replay } as const export type FeatureFlagKey = (typeof FEATURE_FLAGS)[keyof typeof FEATURE_FLAGS] diff --git a/frontend/src/lib/utils/concurrencyController.ts b/frontend/src/lib/utils/concurrencyController.ts index 47683b954be86..941af92f33b74 100644 --- a/frontend/src/lib/utils/concurrencyController.ts +++ b/frontend/src/lib/utils/concurrencyController.ts @@ -1,5 +1,5 @@ +import FastPriorityQueue from 'fastpriorityqueue' import { promiseResolveReject } from 'lib/utils' - class ConcurrencyControllerItem { _debugTag?: string _runFn: () => Promise @@ -52,7 +52,9 @@ export class ConcurrencyController { _concurrencyLimit: number _current: ConcurrencyControllerItem[] = [] - private _queue: ConcurrencyControllerItem[] = [] + private _queue: FastPriorityQueue> = new FastPriorityQueue( + (a, b) => a._priority < b._priority + ) constructor(concurrencyLimit: number) { this._concurrencyLimit = concurrencyLimit @@ -79,7 +81,7 @@ export class ConcurrencyController { }): Promise => { const item = new ConcurrencyControllerItem(this, fn, abortController, priority, debugTag) - this._queue.push(item) + this._queue.add(item) this._tryRunNext() @@ -87,8 +89,7 @@ export class ConcurrencyController { } _runNext(): void { - this._queue.sort((a, b) => a._priority - b._priority) - const next = this._queue.shift() + const next = this._queue.poll() if (next) { next._runFn() .catch(() => { diff --git a/frontend/src/queries/nodes/DataNode/dataNodeLogic.ts b/frontend/src/queries/nodes/DataNode/dataNodeLogic.ts index 98c123d984ec1..b3cada69a6a89 100644 --- a/frontend/src/queries/nodes/DataNode/dataNodeLogic.ts +++ b/frontend/src/queries/nodes/DataNode/dataNodeLogic.ts @@ -28,7 +28,13 @@ import { userLogic } from 'scenes/userLogic' import { dataNodeCollectionLogic, DataNodeCollectionProps } from '~/queries/nodes/DataNode/dataNodeCollectionLogic' import { removeExpressionComment } from '~/queries/nodes/DataTable/utils' import { performQuery } from '~/queries/query' -import { DashboardFilter, HogQLVariable, QueryStatus } from '~/queries/schema' +import { + DashboardFilter, + ErrorTrackingQuery, + ErrorTrackingQueryResponse, + HogQLVariable, + QueryStatus, +} from '~/queries/schema' import { ActorsQuery, ActorsQueryResponse, @@ -42,7 +48,14 @@ import { PersonsNode, QueryTiming, } from '~/queries/schema' -import { isActorsQuery, isEventsQuery, isInsightActorsQuery, isInsightQueryNode, isPersonsNode } from '~/queries/utils' +import { + isActorsQuery, + isErrorTrackingQuery, + isEventsQuery, + isInsightActorsQuery, + isInsightQueryNode, + isPersonsNode, +} from '~/queries/utils' import type { dataNodeLogicType } from './dataNodeLogicType' @@ -504,11 +517,15 @@ export const dataNodeLogic = kea([ return null } - if ((isEventsQuery(query) || isActorsQuery(query)) && !responseError && !dataLoading) { - if ((response as EventsQueryResponse | ActorsQueryResponse)?.hasMore) { + if ( + (isEventsQuery(query) || isActorsQuery(query) || isErrorTrackingQuery(query)) && + !responseError && + !dataLoading + ) { + if ((response as EventsQueryResponse | ActorsQueryResponse | ErrorTrackingQueryResponse)?.hasMore) { const sortKey = query.orderBy?.[0] ?? 'timestamp DESC' - const typedResults = (response as EventsQueryResponse | ActorsQueryResponse)?.results if (isEventsQuery(query) && sortKey === 'timestamp DESC') { + const typedResults = (response as EventsQueryResponse)?.results const sortColumnIndex = query.select .map((hql) => removeExpressionComment(hql)) .indexOf('timestamp') @@ -527,11 +544,14 @@ export const dataNodeLogic = kea([ } } } else { + const typedResults = ( + response as EventsQueryResponse | ActorsQueryResponse | ErrorTrackingQueryResponse + )?.results return { ...query, offset: typedResults?.length || 0, limit: Math.max(100, Math.min(2 * (typedResults?.length || 100), LOAD_MORE_ROWS_LIMIT)), - } as EventsQuery | ActorsQuery + } as EventsQuery | ActorsQuery | ErrorTrackingQuery } } } diff --git a/frontend/src/queries/schema.json b/frontend/src/queries/schema.json index 0f43b7435613b..4cc9cdcdb99f1 100644 --- a/frontend/src/queries/schema.json +++ b/frontend/src/queries/schema.json @@ -366,6 +366,9 @@ }, { "$ref": "#/definitions/ExperimentTrendsQuery" + }, + { + "$ref": "#/definitions/RecordingsQuery" } ] }, @@ -480,6 +483,9 @@ }, { "$ref": "#/definitions/EventsQueryResponse" + }, + { + "$ref": "#/definitions/ErrorTrackingQueryResponse" } ] }, @@ -5458,7 +5464,10 @@ "$ref": "#/definitions/HogQLQueryModifiers", "description": "Modifiers used when performing the query" }, - "order": { + "offset": { + "type": "integer" + }, + "orderBy": { "enum": ["last_seen", "first_seen", "occurrences", "users", "sessions"], "type": "string" }, @@ -11212,6 +11221,7 @@ "type": "array" }, "date_from": { + "default": "-3d", "type": ["string", "null"] }, "date_to": { @@ -11247,10 +11257,12 @@ "type": "integer" }, "operand": { - "$ref": "#/definitions/FilterLogicalOperator" + "$ref": "#/definitions/FilterLogicalOperator", + "default": "AND" }, "order": { - "$ref": "#/definitions/RecordingOrder" + "$ref": "#/definitions/RecordingOrder", + "default": "start_time" }, "person_uuid": { "type": "string" diff --git a/frontend/src/queries/schema.ts b/frontend/src/queries/schema.ts index 4d0f0bb766a67..790bfd5b681c3 100644 --- a/frontend/src/queries/schema.ts +++ b/frontend/src/queries/schema.ts @@ -131,6 +131,7 @@ export type AnyDataNode = | ErrorTrackingQuery | ExperimentFunnelsQuery | ExperimentTrendsQuery + | RecordingsQuery /** * @discriminator kind @@ -211,6 +212,7 @@ export type AnyResponseType = | HogQLAutocompleteResponse | EventsNode['response'] | EventsQueryResponse + | ErrorTrackingQueryResponse /** @internal - no need to emit to schema.json. */ export interface DataNode = Record> extends Node { @@ -324,6 +326,9 @@ export type RecordingOrder = export interface RecordingsQuery extends DataNode { kind: NodeKind.RecordingsQuery + /** + * @default "-3d" + * */ date_from?: string | null date_to?: string | null events?: FilterType['events'] @@ -332,9 +337,15 @@ export interface RecordingsQuery extends DataNode { console_log_filters?: LogEntryPropertyFilter[] having_predicates?: AnyPropertyFilter[] // duration and snapshot_source filters filter_test_accounts?: boolean + /** + * @default "AND" + * */ operand?: FilterLogicalOperator session_ids?: string[] person_uuid?: string + /** + * @default "start_time" + * */ order?: RecordingOrder limit?: integer offset?: integer @@ -1920,13 +1931,14 @@ export interface ErrorTrackingQuery extends DataNode kind: NodeKind.ErrorTrackingQuery issueId?: string select?: HogQLExpression[] - order?: 'last_seen' | 'first_seen' | 'occurrences' | 'users' | 'sessions' + orderBy?: 'last_seen' | 'first_seen' | 'occurrences' | 'users' | 'sessions' dateRange: DateRange assignee?: integer | null filterGroup?: PropertyGroupFilter filterTestAccounts?: boolean searchQuery?: string limit?: integer + offset?: integer } export interface ErrorTrackingIssue { diff --git a/frontend/src/queries/utils.ts b/frontend/src/queries/utils.ts index a3fb30074268d..dc98b2ec2d3d1 100644 --- a/frontend/src/queries/utils.ts +++ b/frontend/src/queries/utils.ts @@ -13,6 +13,7 @@ import { DataVisualizationNode, DataWarehouseNode, DateRange, + ErrorTrackingQuery, EventsNode, EventsQuery, FunnelsQuery, @@ -142,6 +143,10 @@ export function isSessionAttributionExplorerQuery( return node?.kind === NodeKind.SessionAttributionExplorerQuery } +export function isErrorTrackingQuery(node?: Record | null): node is ErrorTrackingQuery { + return node?.kind === NodeKind.ErrorTrackingQuery +} + export function containsHogQLQuery(node?: Record | null): boolean { if (!node) { return false diff --git a/frontend/src/scenes/billing/ExportsUnsubscribeTable/exportsUnsubscribeTableLogic.tsx b/frontend/src/scenes/billing/ExportsUnsubscribeTable/exportsUnsubscribeTableLogic.tsx index 1763f5ee50ffe..48527d28af765 100644 --- a/frontend/src/scenes/billing/ExportsUnsubscribeTable/exportsUnsubscribeTableLogic.tsx +++ b/frontend/src/scenes/billing/ExportsUnsubscribeTable/exportsUnsubscribeTableLogic.tsx @@ -3,6 +3,7 @@ import { actions, afterMount, connect, kea, listeners, path, selectors } from 'k import { loaders } from 'kea-loaders' import api from 'lib/api' import { getCurrentTeamId } from 'lib/utils/getAppContext' +import { DESTINATION_TYPES } from 'scenes/pipeline/destinations/constants' import { pipelineDestinationsLogic } from 'scenes/pipeline/destinations/destinationsLogic' import { HogFunctionIcon } from 'scenes/pipeline/hogfunctions/HogFunctionIcon' import { pipelineAccessLogic } from 'scenes/pipeline/pipelineAccessLogic' @@ -29,17 +30,17 @@ export interface ItemToDisable { export const exportsUnsubscribeTableLogic = kea([ path(['scenes', 'pipeline', 'ExportsUnsubscribeTableLogic']), - connect({ + connect(() => ({ values: [ pipelineAccessLogic, ['canConfigurePlugins'], userLogic, ['user'], - pipelineDestinationsLogic, + pipelineDestinationsLogic({ types: DESTINATION_TYPES }), ['paidHogFunctions'], ], - actions: [pipelineDestinationsLogic, ['toggleNodeHogFunction']], - }), + actions: [pipelineDestinationsLogic({ types: DESTINATION_TYPES }), ['toggleNodeHogFunction']], + })), actions({ disablePlugin: (id: number) => ({ id }), diff --git a/frontend/src/scenes/data-warehouse/editor/editorSidebarLogic.ts b/frontend/src/scenes/data-warehouse/editor/editorSidebarLogic.ts index 8239bd166551d..c7128d138beea 100644 --- a/frontend/src/scenes/data-warehouse/editor/editorSidebarLogic.ts +++ b/frontend/src/scenes/data-warehouse/editor/editorSidebarLogic.ts @@ -2,6 +2,8 @@ import Fuse from 'fuse.js' import { connect, kea, path, selectors } from 'kea' import { router } from 'kea-router' import { subscriptions } from 'kea-subscriptions' +import { FEATURE_FLAGS } from 'lib/constants' +import { featureFlagLogic } from 'lib/logic/featureFlagLogic' import { databaseTableListLogic } from 'scenes/data-management/database/databaseTableListLogic' import { sceneLogic } from 'scenes/sceneLogic' import { Scene } from 'scenes/sceneTypes' @@ -14,6 +16,7 @@ import { DatabaseSchemaDataWarehouseTable, DatabaseSchemaTable } from '~/queries import { DataWarehouseSavedQuery, PipelineTab } from '~/types' import { dataWarehouseViewsLogic } from '../saved_queries/dataWarehouseViewsLogic' +import { viewLinkLogic } from '../viewLinkLogic' import { editorSceneLogic } from './editorSceneLogic' import type { editorSidebarLogicType } from './editorSidebarLogicType' import { multitabEditorLogic } from './multitabEditorLogic' @@ -39,6 +42,20 @@ const savedQueriesfuse = new Fuse([], { includeMatches: true, }) +const nonMaterializedViewsfuse = new Fuse([], { + keys: [{ name: 'name', weight: 2 }], + threshold: 0.3, + ignoreLocation: true, + includeMatches: true, +}) + +const materializedViewsfuse = new Fuse([], { + keys: [{ name: 'name', weight: 2 }], + threshold: 0.3, + ignoreLocation: true, + includeMatches: true, +}) + export const editorSidebarLogic = kea([ path(['data-warehouse', 'editor', 'editorSidebarLogic']), connect({ @@ -49,8 +66,17 @@ export const editorSidebarLogic = kea([ ['dataWarehouseSavedQueries', 'dataWarehouseSavedQueryMapById', 'dataWarehouseSavedQueriesLoading'], databaseTableListLogic, ['posthogTables', 'dataWarehouseTables', 'databaseLoading', 'views', 'viewsMapById'], + featureFlagLogic, + ['featureFlags'], + ], + actions: [ + editorSceneLogic, + ['selectSchema'], + dataWarehouseViewsLogic, + ['deleteDataWarehouseSavedQuery', 'runDataWarehouseSavedQuery'], + viewLinkLogic, + ['selectSourceTable', 'toggleJoinTableModal'], ], - actions: [editorSceneLogic, ['selectSchema'], dataWarehouseViewsLogic, ['deleteDataWarehouseSavedQuery']], }), selectors(({ actions }) => ({ contents: [ @@ -60,13 +86,19 @@ export const editorSidebarLogic = kea([ s.relevantPosthogTables, s.relevantDataWarehouseTables, s.databaseLoading, + s.relevantNonMaterializedViews, + s.relevantMaterializedViews, + s.featureFlags, ], ( relevantSavedQueries, dataWarehouseSavedQueriesLoading, relevantPosthogTables, relevantDataWarehouseTables, - databaseLoading + databaseLoading, + relevantNonMaterializedViews, + relevantMaterializedViews, + featureFlags ) => [ { key: 'data-warehouse-sources', @@ -85,6 +117,15 @@ export const editorSidebarLogic = kea([ onClick: () => { actions.selectSchema(table) }, + menuItems: [ + { + label: 'Add join', + onClick: () => { + actions.selectSourceTable(table.name) + actions.toggleJoinTableModal() + }, + }, + ], })), onAdd: () => { router.actions.push(urls.pipeline(PipelineTab.Sources)) @@ -107,13 +148,25 @@ export const editorSidebarLogic = kea([ onClick: () => { actions.selectSchema(table) }, + menuItems: [ + { + label: 'Add join', + onClick: () => { + actions.selectSourceTable(table.name) + actions.toggleJoinTableModal() + }, + }, + ], })), } as SidebarCategory, { key: 'data-warehouse-views', noun: ['view', 'views'], loading: dataWarehouseSavedQueriesLoading, - items: relevantSavedQueries.map(([savedQuery, matches]) => ({ + items: (featureFlags[FEATURE_FLAGS.DATA_MODELING] + ? relevantNonMaterializedViews + : relevantSavedQueries + ).map(([savedQuery, matches]) => ({ key: savedQuery.id, name: savedQuery.name, url: '', @@ -135,6 +188,23 @@ export const editorSidebarLogic = kea([ }).actions.createTab(savedQuery.query.query, savedQuery) }, }, + { + label: 'Add join', + onClick: () => { + actions.selectSourceTable(savedQuery.name) + actions.toggleJoinTableModal() + }, + }, + ...(featureFlags[FEATURE_FLAGS.DATA_MODELING] && !savedQuery.status + ? [ + { + label: 'Materialize', + onClick: () => { + actions.runDataWarehouseSavedQuery(savedQuery.id) + }, + }, + ] + : []), { label: 'Delete', status: 'danger', @@ -145,8 +215,77 @@ export const editorSidebarLogic = kea([ ], })), } as SidebarCategory, + ...(featureFlags[FEATURE_FLAGS.DATA_MODELING] + ? [ + { + key: 'data-warehouse-materialized-views', + noun: ['materialized view', 'materialized views'], + loading: dataWarehouseSavedQueriesLoading, + items: relevantMaterializedViews.map(([materializedView, matches]) => ({ + key: materializedView.id, + name: materializedView.name, + url: '', + searchMatch: matches + ? { + matchingFields: matches.map((match) => match.key), + nameHighlightRanges: matches.find((match) => match.key === 'name')?.indices, + } + : null, + onClick: () => { + actions.selectSchema(materializedView) + }, + menuItems: [ + { + label: 'Edit view definition', + onClick: () => { + multitabEditorLogic({ + key: `hogQLQueryEditor/${router.values.location.pathname}`, + }).actions.createTab(materializedView.query.query, materializedView) + }, + }, + { + label: 'Add join', + onClick: () => { + actions.selectSourceTable(materializedView.name) + actions.toggleJoinTableModal() + }, + }, + ...(featureFlags[FEATURE_FLAGS.DATA_MODELING] && materializedView.status + ? [ + { + label: 'Run', + onClick: () => { + actions.runDataWarehouseSavedQuery(materializedView.id) + }, + }, + ] + : []), + { + label: 'Delete', + status: 'danger', + onClick: () => { + actions.deleteDataWarehouseSavedQuery(materializedView.id) + }, + }, + ], + })), + }, + ] + : []), ], ], + nonMaterializedViews: [ + (s) => [s.dataWarehouseSavedQueries], + (views): DataWarehouseSavedQuery[] => { + return views.filter((view) => !view.status && !view.last_run_at) + }, + ], + materializedViews: [ + (s) => [s.dataWarehouseSavedQueries], + (views): DataWarehouseSavedQuery[] => { + return views.filter((view) => view.status || view.last_run_at) + }, + ], activeListItemKey: [ (s) => [s.activeScene, s.sceneParams], (activeScene, sceneParams): [string, number] | null => { @@ -188,6 +327,28 @@ export const editorSidebarLogic = kea([ return dataWarehouseSavedQueries.map((savedQuery) => [savedQuery, null]) }, ], + relevantNonMaterializedViews: [ + (s) => [s.nonMaterializedViews, navigation3000Logic.selectors.searchTerm], + (nonMaterializedViews, searchTerm): [DataWarehouseSavedQuery, FuseSearchMatch[] | null][] => { + if (searchTerm) { + return nonMaterializedViewsfuse + .search(searchTerm) + .map((result) => [result.item, result.matches as FuseSearchMatch[]]) + } + return nonMaterializedViews.map((view) => [view, null]) + }, + ], + relevantMaterializedViews: [ + (s) => [s.materializedViews, navigation3000Logic.selectors.searchTerm], + (materializedViews, searchTerm): [DataWarehouseSavedQuery, FuseSearchMatch[] | null][] => { + if (searchTerm) { + return materializedViewsfuse + .search(searchTerm) + .map((result) => [result.item, result.matches as FuseSearchMatch[]]) + } + return materializedViews.map((view) => [view, null]) + }, + ], })), subscriptions({ dataWarehouseTables: (dataWarehouseTables) => { diff --git a/frontend/src/scenes/error-tracking/ErrorTrackingConfigurationScene.tsx b/frontend/src/scenes/error-tracking/ErrorTrackingConfigurationScene.tsx index a7e105958f356..d381d62640a0f 100644 --- a/frontend/src/scenes/error-tracking/ErrorTrackingConfigurationScene.tsx +++ b/frontend/src/scenes/error-tracking/ErrorTrackingConfigurationScene.tsx @@ -18,6 +18,11 @@ export const scene: SceneExport = { export function ErrorTrackingConfigurationScene(): JSX.Element { const { missingSymbolSets, validSymbolSets } = useValues(errorTrackingSymbolSetLogic) + const { loadSymbolSets } = useActions(errorTrackingSymbolSetLogic) + + useEffect(() => { + loadSymbolSets() + }, [loadSymbolSets]) return (
@@ -27,18 +32,22 @@ export function ErrorTrackingConfigurationScene(): JSX.Element { automatically retrieves source maps where possible. Cases where it was not possible are listed below. Source maps can be uploaded retroactively but changes will only apply to all future exceptions ingested.

- {missingSymbolSets.length > 0 && } - {validSymbolSets.length > 0 && } + {missingSymbolSets.length > 0 && ( + + )} + {validSymbolSets.length > 0 && }
) } const SymbolSetTable = ({ + id, dataSource, pageSize, missing, }: { + id: string dataSource: ErrorTrackingSymbolSet[] pageSize: number missing?: boolean @@ -98,6 +107,7 @@ const SymbolSetTable = ({ return ( { export const Options = ({ isGroup = false }: { isGroup?: boolean }): JSX.Element => { const { dateRange, assignee, hasGroupActions } = useValues(errorTrackingLogic) const { setDateRange, setAssignee } = useActions(errorTrackingLogic) - const { order } = useValues(errorTrackingSceneLogic) - const { setOrder } = useActions(errorTrackingSceneLogic) + const { orderBy } = useValues(errorTrackingSceneLogic) + const { setOrderBy } = useActions(errorTrackingSceneLogic) return (
@@ -110,9 +110,9 @@ export const Options = ({ isGroup = false }: { isGroup?: boolean }): JSX.Element
Sort by: { } className="flex-1" to={urls.errorTrackingIssue(record.id)} + onClick={() => { + const issueLogic = errorTrackingIssueSceneLogic({ id: record.id }) + issueLogic.mount() + issueLogic.actions.setIssue(record) + }} />
) diff --git a/frontend/src/scenes/error-tracking/errorTrackingIssueSceneLogic.ts b/frontend/src/scenes/error-tracking/errorTrackingIssueSceneLogic.ts index 2c1a4b4bb2a0f..08f2de773227b 100644 --- a/frontend/src/scenes/error-tracking/errorTrackingIssueSceneLogic.ts +++ b/frontend/src/scenes/error-tracking/errorTrackingIssueSceneLogic.ts @@ -46,6 +46,7 @@ export const errorTrackingIssueSceneLogic = kea ({ tab }), setActiveEventUUID: (uuid: ErrorTrackingEvent['uuid']) => ({ uuid }), + setIssue: (issue: ErrorTrackingIssue) => ({ issue }), updateIssue: (issue: Partial>) => ({ issue }), }), @@ -89,6 +90,7 @@ export const errorTrackingIssueSceneLogic = kea issue, }, ], events: [ diff --git a/frontend/src/scenes/error-tracking/errorTrackingSceneLogic.ts b/frontend/src/scenes/error-tracking/errorTrackingSceneLogic.ts index e1128d177e0de..bd36ead868256 100644 --- a/frontend/src/scenes/error-tracking/errorTrackingSceneLogic.ts +++ b/frontend/src/scenes/error-tracking/errorTrackingSceneLogic.ts @@ -26,16 +26,16 @@ export const errorTrackingSceneLogic = kea([ }), actions({ - setOrder: (order: ErrorTrackingQuery['order']) => ({ order }), + setOrderBy: (orderBy: ErrorTrackingQuery['orderBy']) => ({ orderBy }), setSelectedIssueIds: (ids: string[]) => ({ ids }), }), reducers({ - order: [ - 'last_seen' as ErrorTrackingQuery['order'], + orderBy: [ + 'last_seen' as ErrorTrackingQuery['orderBy'], { persist: true }, { - setOrder: (_, { order }) => order, + setOrderBy: (_, { orderBy }) => orderBy, }, ], selectedIssueIds: [ @@ -49,7 +49,7 @@ export const errorTrackingSceneLogic = kea([ selectors({ query: [ (s) => [ - s.order, + s.orderBy, s.dateRange, s.assignee, s.filterTestAccounts, @@ -59,7 +59,7 @@ export const errorTrackingSceneLogic = kea([ s.hasGroupActions, ], ( - order, + orderBy, dateRange, assignee, filterTestAccounts, @@ -69,7 +69,7 @@ export const errorTrackingSceneLogic = kea([ hasGroupActions ): DataTableNode => errorTrackingQuery({ - order, + orderBy, dateRange, assignee, filterTestAccounts, diff --git a/frontend/src/scenes/error-tracking/errorTrackingSymbolSetLogic.tsx b/frontend/src/scenes/error-tracking/errorTrackingSymbolSetLogic.tsx index f52e94729b13a..e1c7cb57173cd 100644 --- a/frontend/src/scenes/error-tracking/errorTrackingSymbolSetLogic.tsx +++ b/frontend/src/scenes/error-tracking/errorTrackingSymbolSetLogic.tsx @@ -1,5 +1,5 @@ import { lemonToast } from '@posthog/lemon-ui' -import { actions, afterMount, kea, path, reducers, selectors } from 'kea' +import { actions, kea, path, reducers, selectors } from 'kea' import { forms } from 'kea-forms' import { loaders } from 'kea-loaders' import api from 'lib/api' @@ -103,8 +103,4 @@ export const errorTrackingSymbolSetLogic = kea( }, }, })), - - afterMount(({ actions }) => { - actions.loadSymbolSets() - }), ]) diff --git a/frontend/src/scenes/error-tracking/queries.ts b/frontend/src/scenes/error-tracking/queries.ts index 62542b659f140..781c8d75fa4e6 100644 --- a/frontend/src/scenes/error-tracking/queries.ts +++ b/frontend/src/scenes/error-tracking/queries.ts @@ -40,7 +40,7 @@ const toStartOfIntervalFn = { } export const errorTrackingQuery = ({ - order, + orderBy, dateRange, assignee, filterTestAccounts, @@ -49,7 +49,7 @@ export const errorTrackingQuery = ({ sparklineSelectedPeriod, columns, limit = 50, -}: Pick & { +}: Pick & { filterGroup: UniversalFiltersGroup sparklineSelectedPeriod: string | null columns: ('error' | 'volume' | 'occurrences' | 'sessions' | 'users' | 'assignee')[] @@ -69,7 +69,7 @@ export const errorTrackingQuery = ({ source: { kind: NodeKind.ErrorTrackingQuery, select: select, - order: order, + orderBy: orderBy, dateRange: dateRange, assignee: assignee, filterGroup: filterGroup as PropertyGroupFilter, diff --git a/frontend/src/scenes/pipeline/FrontendApps.tsx b/frontend/src/scenes/pipeline/FrontendApps.tsx index d6fad0d6ea6e8..ed1f984facc3c 100644 --- a/frontend/src/scenes/pipeline/FrontendApps.tsx +++ b/frontend/src/scenes/pipeline/FrontendApps.tsx @@ -13,29 +13,43 @@ import { NewButton } from './NewButton' import { SiteApp } from './types' import { appColumn, nameColumn, pipelinePluginBackedNodeMenuCommonItems } from './utils' -export function FrontendApps(): JSX.Element { +export interface FrontendAppsProps { + asLegacyList?: boolean +} + +export function FrontendApps({ asLegacyList }: FrontendAppsProps): JSX.Element { const { loading, frontendApps } = useValues(frontendAppsLogic) const { toggleEnabled, loadPluginConfigs } = useActions(frontendAppsLogic) - const shouldShowEmptyState = frontendApps.length === 0 && !loading + const shouldShowEmptyState = frontendApps.length === 0 && !loading && !asLegacyList return ( <> - } - /> - } - isEmpty={shouldShowEmptyState} - /> + {!asLegacyList && ( + } + /> + )} + {!asLegacyList && ( + } + isEmpty={shouldShowEmptyState} + /> + )} {!shouldShowEmptyState && ( <> + {!loading && asLegacyList && ( + <> +

Legacy Site apps

+

These site apps are using an older system and should eventually be migrated over.

+ + )} See all.

- +
diff --git a/frontend/src/scenes/pipeline/Pipeline.tsx b/frontend/src/scenes/pipeline/Pipeline.tsx index 5e689fea80e56..f5dd0a7907429 100644 --- a/frontend/src/scenes/pipeline/Pipeline.tsx +++ b/frontend/src/scenes/pipeline/Pipeline.tsx @@ -1,13 +1,16 @@ import { useValues } from 'kea' import { router } from 'kea-router' import { ActivityLog } from 'lib/components/ActivityLog/ActivityLog' +import { FEATURE_FLAGS } from 'lib/constants' import { ConcreteLemonTab, LemonTabs } from 'lib/lemon-ui/LemonTabs' +import { featureFlagLogic } from 'lib/logic/featureFlagLogic' import { SceneExport } from 'scenes/sceneTypes' import { urls } from 'scenes/urls' import { ActivityScope, PipelineTab } from '~/types' import { AppsManagement } from './AppsManagement' +import { DESTINATION_TYPES, SITE_APP_TYPES } from './destinations/constants' import { Destinations } from './destinations/Destinations' import { FrontendApps } from './FrontendApps' import { ImportApps } from './ImportApps' @@ -22,13 +25,21 @@ export function Pipeline(): JSX.Element { const { canGloballyManagePlugins } = useValues(pipelineAccessLogic) const { currentTab } = useValues(pipelineLogic) const { hasEnabledImportApps } = useValues(importAppsLogic) + const { featureFlags } = useValues(featureFlagLogic) const tabs: Pick, 'key' | 'content'>[] = [ { key: PipelineTab.Overview, content: }, { key: PipelineTab.Sources, content: }, { key: PipelineTab.Transformations, content: }, - { key: PipelineTab.Destinations, content: }, - { key: PipelineTab.SiteApps, content: }, + { key: PipelineTab.Destinations, content: }, + { + key: PipelineTab.SiteApps, + content: featureFlags[FEATURE_FLAGS.SITE_APP_FUNCTIONS] ? ( + + ) : ( + + ), + }, ] // Import apps are deprecated, we only show the tab if there are some still enabled diff --git a/frontend/src/scenes/pipeline/PipelineNode.tsx b/frontend/src/scenes/pipeline/PipelineNode.tsx index 1f610cebe7fa9..bd5512498c29a 100644 --- a/frontend/src/scenes/pipeline/PipelineNode.tsx +++ b/frontend/src/scenes/pipeline/PipelineNode.tsx @@ -41,7 +41,7 @@ const paramsToProps = ({ } return { - stage: PIPELINE_TAB_TO_NODE_STAGE[stage] || null, + stage: PIPELINE_TAB_TO_NODE_STAGE[stage as PipelineTab] || null, id: numericId && !isNaN(numericId) ? numericId : id, } } @@ -97,6 +97,11 @@ export function PipelineNode(params: { stage?: string; id?: string } = {}): JSX. ) } + if (stage === PipelineStage.SiteApp) { + delete tabToContent[PipelineNodeTab.Logs] + delete tabToContent[PipelineNodeTab.Metrics] + } + return ( <> diff --git a/frontend/src/scenes/pipeline/PipelineNodeNew.tsx b/frontend/src/scenes/pipeline/PipelineNodeNew.tsx index 5b26f115a9866..8e22d52f2642c 100644 --- a/frontend/src/scenes/pipeline/PipelineNodeNew.tsx +++ b/frontend/src/scenes/pipeline/PipelineNodeNew.tsx @@ -3,9 +3,11 @@ import { useActions, useValues } from 'kea' import { combineUrl, router } from 'kea-router' import { NotFound } from 'lib/components/NotFound' import { PayGateMini } from 'lib/components/PayGateMini/PayGateMini' +import { FEATURE_FLAGS } from 'lib/constants' import { LemonButton } from 'lib/lemon-ui/LemonButton' import { LemonTable } from 'lib/lemon-ui/LemonTable' import { LemonTableLink } from 'lib/lemon-ui/LemonTable/LemonTableLink' +import { featureFlagLogic } from 'lib/logic/featureFlagLogic' import { useEffect } from 'react' import { NewSourceWizardScene } from 'scenes/data-warehouse/new/NewSourceWizard' import { SceneExport } from 'scenes/sceneTypes' @@ -13,6 +15,7 @@ import { urls } from 'scenes/urls' import { AvailableFeature, PipelineStage, PluginType } from '~/types' +import { DESTINATION_TYPES, SITE_APP_TYPES } from './destinations/constants' import { NewDestinations } from './destinations/NewDestinations' import { frontendAppsLogic } from './frontendAppsLogic' import { HogFunctionConfiguration } from './hogfunctions/HogFunctionConfiguration' @@ -71,6 +74,7 @@ function convertPluginToTableEntry(plugin: PluginType): TableEntry { } export function PipelineNodeNew(params: { stage?: string; id?: string } = {}): JSX.Element { + const { featureFlags } = useValues(featureFlagLogic) const { stage, pluginId, batchExportDestination, hogFunctionId } = paramsToProps({ params }) if (!stage) { @@ -103,9 +107,13 @@ export function PipelineNodeNew(params: { stage?: string; id?: string } = {}): J if (stage === PipelineStage.Transformation) { return } else if (stage === PipelineStage.Destination) { - return + return } else if (stage === PipelineStage.SiteApp) { - return + return featureFlags[FEATURE_FLAGS.SITE_APP_FUNCTIONS] ? ( + + ) : ( + + ) } else if (stage === PipelineStage.Source) { return } diff --git a/frontend/src/scenes/pipeline/destinations/Destinations.tsx b/frontend/src/scenes/pipeline/destinations/Destinations.tsx index 226e173f40eaa..4f7e8cc4f37f5 100644 --- a/frontend/src/scenes/pipeline/destinations/Destinations.tsx +++ b/frontend/src/scenes/pipeline/destinations/Destinations.tsx @@ -9,62 +9,110 @@ import { updatedAtColumn } from 'lib/lemon-ui/LemonTable/columnUtils' import { LemonTableLink } from 'lib/lemon-ui/LemonTable/LemonTableLink' import { urls } from 'scenes/urls' -import { AvailableFeature, PipelineNodeTab, PipelineStage, ProductKey } from '~/types' +import { AvailableFeature, HogFunctionTypeType, PipelineNodeTab, PipelineStage, ProductKey } from '~/types' import { AppMetricSparkLine } from '../AppMetricSparkLine' +import { FrontendApps } from '../FrontendApps' import { HogFunctionIcon } from '../hogfunctions/HogFunctionIcon' import { HogFunctionStatusIndicator } from '../hogfunctions/HogFunctionStatusIndicator' +import { hogFunctionTypeToPipelineStage } from '../hogfunctions/urls' import { AppMetricSparkLineV2 } from '../metrics/AppMetricsV2Sparkline' import { NewButton } from '../NewButton' import { pipelineAccessLogic } from '../pipelineAccessLogic' -import { Destination, PipelineBackend } from '../types' +import { Destination, PipelineBackend, SiteApp } from '../types' import { pipelineNodeMenuCommonItems, RenderApp, RenderBatchExportIcon } from '../utils' import { DestinationsFilters } from './DestinationsFilters' import { destinationsFiltersLogic } from './destinationsFiltersLogic' import { pipelineDestinationsLogic } from './destinationsLogic' import { DestinationOptionsTable } from './NewDestinations' -export function Destinations(): JSX.Element { - const { destinations, loading } = useValues(pipelineDestinationsLogic({ syncFiltersWithUrl: true })) +export interface DestinationsProps { + types: HogFunctionTypeType[] +} + +export function Destinations({ types }: DestinationsProps): JSX.Element { + const { destinations, loading } = useValues(pipelineDestinationsLogic({ types })) return ( <> - } - /> - - } - isEmpty={destinations.length === 0 && !loading} + {types.includes('destination') ? ( + <> + } + /> + + } + isEmpty={destinations.length === 0 && !loading} + /> + + + ) : types.includes('site_app') ? ( + } /> - - + ) : ( + } + /> + )} + +
-

New destinations

- +

+ {types.includes('destination') + ? 'New destinations' + : types.includes('site_app') + ? 'New site app' + : 'New Hog function'} +

+ + {/* Old site-apps until we migrate everyone onto the new ones */} + {types.includes('site_app') ? : null} ) } export type DestinationsTableProps = { + types: HogFunctionTypeType[] hideFeedback?: boolean hideAddDestinationButton?: boolean } -export function DestinationsTable({ hideFeedback, hideAddDestinationButton }: DestinationsTableProps): JSX.Element { +export function DestinationsTable({ + hideFeedback, + hideAddDestinationButton, + types, +}: DestinationsTableProps): JSX.Element { const { canConfigurePlugins, canEnableDestination } = useValues(pipelineAccessLogic) - const { loading, filteredDestinations, destinations, hiddenDestinations } = useValues(pipelineDestinationsLogic) - const { toggleNode, deleteNode } = useActions(pipelineDestinationsLogic) - const { resetFilters } = useActions(destinationsFiltersLogic) + const { loading, filteredDestinations, destinations, hiddenDestinations } = useValues( + pipelineDestinationsLogic({ types }) + ) + const { toggleNode, deleteNode } = useActions(pipelineDestinationsLogic({ types })) + const { resetFilters } = useActions(destinationsFiltersLogic({ types })) + + const showFrequencyHistory = types.includes('destination') + const simpleName = + types.includes('destination') || types.includes('site_destination') + ? 'destination' + : types.includes('site_app') + ? 'site app' + : 'Hog function' return (
- + - {destination.backend === PipelineBackend.HogFunction ? ( - - ) : ( - - )} - - ) - }, - }, - updatedAtColumn() as LemonTableColumn, + ...(showFrequencyHistory + ? [ + { + title: 'Frequency', + key: 'interval', + render: function RenderFrequency(_, destination) { + return 'interval' in destination ? destination.interval : null + }, + } as LemonTableColumn, + ] + : []), + ...(showFrequencyHistory + ? [ + { + title: 'Last 7 days', + render: function RenderSuccessRate(_, destination) { + return ( + + {destination.backend === PipelineBackend.HogFunction ? ( + + ) : ( + + )} + + ) + }, + } as LemonTableColumn, + ] + : []), + updatedAtColumn() as LemonTableColumn, { title: 'Status', key: 'enabled', @@ -171,23 +227,23 @@ export function DestinationsTable({ hideFeedback, hideAddDestinationButton }: De items={[ { label: destination.enabled - ? 'Pause destination' - : 'Unpause destination', + ? `Pause ${simpleName}` + : `Unpause ${simpleName}`, onClick: () => toggleNode(destination, !destination.enabled), disabledReason: !canConfigurePlugins - ? 'You do not have permission to toggle destinations.' + ? `You do not have permission to toggle ${simpleName}s.` : !canEnableDestination(destination) && !destination.enabled - ? 'Data pipelines add-on is required for enabling new destinations' + ? `Data pipelines add-on is required for enabling new ${simpleName}s` : undefined, }, ...pipelineNodeMenuCommonItems(destination), { - label: 'Delete destination', + label: `Delete ${simpleName}`, status: 'danger' as const, // for typechecker happiness onClick: () => deleteNode(destination), disabledReason: canConfigurePlugins ? undefined - : 'You do not have permission to delete destinations.', + : `You do not have permission to delete ${simpleName}.`, }, ]} /> diff --git a/frontend/src/scenes/pipeline/destinations/DestinationsFilters.tsx b/frontend/src/scenes/pipeline/destinations/DestinationsFilters.tsx index d226968aed7cb..171d0ec5d2fa7 100644 --- a/frontend/src/scenes/pipeline/destinations/DestinationsFilters.tsx +++ b/frontend/src/scenes/pipeline/destinations/DestinationsFilters.tsx @@ -2,12 +2,13 @@ import { LemonCheckbox, LemonInput, LemonSelect, Link } from '@posthog/lemon-ui' import { useActions, useValues } from 'kea' import { NewButton } from 'scenes/pipeline/NewButton' -import { PipelineStage } from '~/types' +import { HogFunctionTypeType, PipelineStage } from '~/types' import { PipelineBackend } from '../types' import { destinationsFiltersLogic } from './destinationsFiltersLogic' export type DestinationsFiltersProps = { + types: HogFunctionTypeType[] hideSearch?: boolean hideShowPaused?: boolean hideKind?: boolean @@ -16,14 +17,15 @@ export type DestinationsFiltersProps = { } export function DestinationsFilters({ + types, hideSearch, hideShowPaused, hideKind, hideFeedback, hideAddDestinationButton = true, }: DestinationsFiltersProps): JSX.Element | null { - const { filters } = useValues(destinationsFiltersLogic) - const { setFilters, openFeedbackDialog } = useActions(destinationsFiltersLogic) + const { filters } = useValues(destinationsFiltersLogic({ types })) + const { setFilters, openFeedbackDialog } = useActions(destinationsFiltersLogic({ types })) return (
diff --git a/frontend/src/scenes/pipeline/destinations/NewDestinations.tsx b/frontend/src/scenes/pipeline/destinations/NewDestinations.tsx index a11d06489574f..7ac6e5f9564c8 100644 --- a/frontend/src/scenes/pipeline/destinations/NewDestinations.tsx +++ b/frontend/src/scenes/pipeline/destinations/NewDestinations.tsx @@ -5,7 +5,7 @@ import { PayGateButton } from 'lib/components/PayGateMini/PayGateButton' import { PayGateMini } from 'lib/components/PayGateMini/PayGateMini' import { LemonTableLink } from 'lib/lemon-ui/LemonTable/LemonTableLink' -import { AvailableFeature, PipelineStage } from '~/types' +import { AvailableFeature, HogFunctionTypeType, PipelineStage } from '~/types' import { pipelineAccessLogic } from '../pipelineAccessLogic' import { DestinationsFilters } from './DestinationsFilters' @@ -13,20 +13,24 @@ import { destinationsFiltersLogic } from './destinationsFiltersLogic' import { DestinationTag } from './DestinationTag' import { newDestinationsLogic } from './newDestinationsLogic' -export function NewDestinations(): JSX.Element { +export interface NewDestinationsProps { + types: HogFunctionTypeType[] +} + +export function NewDestinations({ types }: NewDestinationsProps): JSX.Element { return (
- - - + {types.includes('destination') ? : null} + +
) } -export function DestinationOptionsTable(): JSX.Element { - const { loading, filteredDestinations, hiddenDestinations } = useValues(newDestinationsLogic) +export function DestinationOptionsTable({ types }: NewDestinationsProps): JSX.Element { + const { loading, filteredDestinations, hiddenDestinations } = useValues(newDestinationsLogic({ types })) const { canEnableDestination } = useValues(pipelineAccessLogic) - const { resetFilters } = useActions(destinationsFiltersLogic) + const { resetFilters } = useActions(destinationsFiltersLogic({ types })) return ( <> diff --git a/frontend/src/scenes/pipeline/destinations/constants.ts b/frontend/src/scenes/pipeline/destinations/constants.ts index 0613f531e28f1..dda2e7d0fe3d0 100644 --- a/frontend/src/scenes/pipeline/destinations/constants.ts +++ b/frontend/src/scenes/pipeline/destinations/constants.ts @@ -1,4 +1,4 @@ import { HogFunctionTypeType } from '~/types' -export const getDestinationTypes = (featureFlagEnabled: boolean): HogFunctionTypeType[] => - featureFlagEnabled ? ['destination', 'site_destination'] : ['destination'] +export const DESTINATION_TYPES = ['destination', 'site_destination'] satisfies HogFunctionTypeType[] +export const SITE_APP_TYPES = ['site_app'] satisfies HogFunctionTypeType[] diff --git a/frontend/src/scenes/pipeline/destinations/destinationsFiltersLogic.tsx b/frontend/src/scenes/pipeline/destinations/destinationsFiltersLogic.tsx index 77b98d9cb1a68..469b33640cfbc 100644 --- a/frontend/src/scenes/pipeline/destinations/destinationsFiltersLogic.tsx +++ b/frontend/src/scenes/pipeline/destinations/destinationsFiltersLogic.tsx @@ -1,5 +1,5 @@ import { LemonDialog, LemonInput, LemonTextArea, lemonToast } from '@posthog/lemon-ui' -import { actions, connect, kea, listeners, path, reducers } from 'kea' +import { actions, connect, kea, key, listeners, path, props, reducers } from 'kea' import { actionToUrl, router, urlToAction } from 'kea-router' import { LemonField } from 'lib/lemon-ui/LemonField' import { featureFlagLogic } from 'lib/logic/featureFlagLogic' @@ -7,6 +7,8 @@ import { objectsEqual } from 'lib/utils' import posthog from 'posthog-js' import { userLogic } from 'scenes/userLogic' +import { HogFunctionTypeType } from '~/types' + import { PipelineBackend } from '../types' import type { destinationsFiltersLogicType } from './destinationsFiltersLogicType' @@ -17,8 +19,14 @@ export type DestinationsFilters = { showPaused?: boolean } +export interface DestinationsFiltersLogicProps { + types: HogFunctionTypeType[] +} + export const destinationsFiltersLogic = kea([ path(() => ['scenes', 'pipeline', 'destinations', 'destinationsFiltersLogic']), + props({} as DestinationsFiltersLogicProps), + key((props) => props.types.join(',') ?? ''), connect({ values: [userLogic, ['user'], featureFlagLogic, ['featureFlags']], }), diff --git a/frontend/src/scenes/pipeline/destinations/destinationsLogic.tsx b/frontend/src/scenes/pipeline/destinations/destinationsLogic.tsx index a62b36f289b66..bed880c886729 100644 --- a/frontend/src/scenes/pipeline/destinations/destinationsLogic.tsx +++ b/frontend/src/scenes/pipeline/destinations/destinationsLogic.tsx @@ -1,6 +1,6 @@ import { lemonToast } from '@posthog/lemon-ui' import FuseClass from 'fuse.js' -import { actions, afterMount, connect, kea, listeners, path, selectors } from 'kea' +import { actions, afterMount, connect, kea, key, listeners, path, props, selectors } from 'kea' import { loaders } from 'kea-loaders' import api from 'lib/api' import { FEATURE_FLAGS } from 'lib/constants' @@ -13,6 +13,7 @@ import { userLogic } from 'scenes/userLogic' import { BatchExportConfiguration, HogFunctionType, + HogFunctionTypeType, PipelineStage, PluginConfigTypeNew, PluginConfigWithPluginInfoNew, @@ -26,19 +27,25 @@ import { Destination, FunctionDestination, PipelineBackend, + SiteApp, WebhookDestination, } from '../types' import { captureBatchExportEvent, capturePluginEvent, loadPluginsFromUrl } from '../utils' -import { getDestinationTypes } from './constants' import { destinationsFiltersLogic } from './destinationsFiltersLogic' import type { pipelineDestinationsLogicType } from './destinationsLogicType' // Helping kea-typegen navigate the exported default class for Fuse -export interface Fuse extends FuseClass {} +export interface Fuse extends FuseClass {} + +export interface PipelineDestinationsLogicProps { + types: HogFunctionTypeType[] +} export const pipelineDestinationsLogic = kea([ path(['scenes', 'pipeline', 'destinationsLogic']), - connect({ + props({} as PipelineDestinationsLogicProps), + key((props: PipelineDestinationsLogicProps) => props.types.join(',')), + connect((props: PipelineDestinationsLogicProps) => ({ values: [ projectLogic, ['currentProjectId'], @@ -48,14 +55,14 @@ export const pipelineDestinationsLogic = kea([ ['canEnableDestination'], featureFlagLogic, ['featureFlags'], - destinationsFiltersLogic, + destinationsFiltersLogic(props), ['filters'], ], - }), + })), actions({ - toggleNode: (destination: Destination, enabled: boolean) => ({ destination, enabled }), + toggleNode: (destination: Destination | SiteApp, enabled: boolean) => ({ destination, enabled }), toggleNodeHogFunction: (destination: FunctionDestination, enabled: boolean) => ({ destination, enabled }), - deleteNode: (destination: Destination) => ({ destination }), + deleteNode: (destination: Destination | SiteApp) => ({ destination }), deleteNodeBatchExport: (destination: BatchExportDestination) => ({ destination }), deleteNodeHogFunction: (destination: FunctionDestination) => ({ destination }), deleteNodeWebhook: (destination: WebhookDestination) => ({ destination }), @@ -63,7 +70,7 @@ export const pipelineDestinationsLogic = kea([ updatePluginConfig: (pluginConfig: PluginConfigTypeNew) => ({ pluginConfig }), updateBatchExportConfig: (batchExportConfig: BatchExportConfiguration) => ({ batchExportConfig }), }), - loaders(({ values, actions }) => ({ + loaders(({ values, actions, props }) => ({ plugins: [ {} as Record, { @@ -167,7 +174,10 @@ export const pipelineDestinationsLogic = kea([ [] as HogFunctionType[], { loadHogFunctions: async () => { - const destinationTypes = getDestinationTypes(!!values.featureFlags[FEATURE_FLAGS.SITE_DESTINATIONS]) + const siteDesinationsEnabled = !!values.featureFlags[FEATURE_FLAGS.SITE_DESTINATIONS] + const destinationTypes = siteDesinationsEnabled + ? props.types + : props.types.filter((type) => type !== 'site_destination') return (await api.hogFunctions.list(undefined, destinationTypes)).results }, @@ -223,7 +233,14 @@ export const pipelineDestinationsLogic = kea([ ], destinations: [ (s) => [s.pluginConfigs, s.plugins, s.batchExportConfigs, s.hogFunctions, s.user, s.featureFlags], - (pluginConfigs, plugins, batchExportConfigs, hogFunctions, user, featureFlags): Destination[] => { + ( + pluginConfigs, + plugins, + batchExportConfigs, + hogFunctions, + user, + featureFlags + ): (Destination | SiteApp)[] => { // Migrations are shown only in impersonation mode, for us to be able to trigger them. const httpEnabled = featureFlags[FEATURE_FLAGS.BATCH_EXPORTS_POSTHOG_HTTP] || user?.is_impersonated || user?.is_staff @@ -243,7 +260,10 @@ export const pipelineDestinationsLogic = kea([ ) .concat(rawBatchExports) const convertedDestinations = rawDestinations.map((d) => - convertToPipelineNode(d, PipelineStage.Destination) + convertToPipelineNode( + d, + 'type' in d && d.type === 'site_app' ? PipelineStage.SiteApp : PipelineStage.Destination + ) ) const enabledFirst = convertedDestinations.sort((a, b) => Number(b.enabled) - Number(a.enabled)) return enabledFirst @@ -261,7 +281,7 @@ export const pipelineDestinationsLogic = kea([ filteredDestinations: [ (s) => [s.filters, s.destinations, s.destinationsFuse], - (filters, destinations, destinationsFuse): Destination[] => { + (filters, destinations, destinationsFuse): (Destination | SiteApp)[] => { const { search, showPaused, kind } = filters return (search ? destinationsFuse.search(search).map((x) => x.item) : destinations).filter((dest) => { @@ -278,7 +298,7 @@ export const pipelineDestinationsLogic = kea([ hiddenDestinations: [ (s) => [s.destinations, s.filteredDestinations], - (destinations, filteredDestinations): Destination[] => { + (destinations, filteredDestinations): (Destination | SiteApp)[] => { return destinations.filter((dest) => !filteredDestinations.includes(dest)) }, ], @@ -300,7 +320,7 @@ export const pipelineDestinationsLogic = kea([ deleteNode: ({ destination }) => { switch (destination.backend) { case PipelineBackend.Plugin: - actions.deleteNodeWebhook(destination) + actions.deleteNodeWebhook(destination as WebhookDestination) break case PipelineBackend.BatchExport: actions.deleteNodeBatchExport(destination) diff --git a/frontend/src/scenes/pipeline/destinations/newDestinationsLogic.tsx b/frontend/src/scenes/pipeline/destinations/newDestinationsLogic.tsx index 66c49d0570289..e4b6bd8db6c24 100644 --- a/frontend/src/scenes/pipeline/destinations/newDestinationsLogic.tsx +++ b/frontend/src/scenes/pipeline/destinations/newDestinationsLogic.tsx @@ -1,5 +1,5 @@ import FuseClass from 'fuse.js' -import { actions, afterMount, connect, kea, path, selectors } from 'kea' +import { actions, afterMount, connect, kea, key, path, props, selectors } from 'kea' import { loaders } from 'kea-loaders' import { combineUrl, router } from 'kea-router' import api from 'lib/api' @@ -18,10 +18,11 @@ import { import { humanizeBatchExportName } from '../batch-exports/utils' import { HogFunctionIcon } from '../hogfunctions/HogFunctionIcon' +import { hogFunctionTypeToPipelineStage } from '../hogfunctions/urls' import { PipelineBackend } from '../types' import { RenderBatchExportIcon } from '../utils' -import { getDestinationTypes } from './constants' import { destinationsFiltersLogic } from './destinationsFiltersLogic' +import { PipelineDestinationsLogicProps } from './destinationsLogic' import type { newDestinationsLogicType } from './newDestinationsLogicType' export type NewDestinationItemType = { @@ -38,18 +39,30 @@ export interface Fuse extends FuseClass {} export const newDestinationsLogic = kea([ path(() => ['scenes', 'pipeline', 'destinations', 'newDestinationsLogic']), - connect({ - values: [userLogic, ['user'], featureFlagLogic, ['featureFlags'], destinationsFiltersLogic, ['filters']], - }), + props({} as PipelineDestinationsLogicProps), + key((props) => props.types.join(',') ?? ''), + connect(({ types }: PipelineDestinationsLogicProps) => ({ + values: [ + userLogic, + ['user'], + featureFlagLogic, + ['featureFlags'], + destinationsFiltersLogic({ types }), + ['filters'], + ], + })), actions({ openFeedbackDialog: true, }), - loaders(({ values }) => ({ + loaders(({ props, values }) => ({ hogFunctionTemplates: [ {} as Record, { loadHogFunctionTemplates: async () => { - const destinationTypes = getDestinationTypes(!!values.featureFlags[FEATURE_FLAGS.SITE_DESTINATIONS]) + const siteDesinationsEnabled = !!values.featureFlags[FEATURE_FLAGS.SITE_DESTINATIONS] + const destinationTypes = siteDesinationsEnabled + ? props.types + : props.types.filter((type) => type !== 'site_destination') const templates = await api.hogFunctions.listTemplates(destinationTypes) return templates.results.reduce((acc, template) => { acc[template.id] = template @@ -62,9 +75,14 @@ export const newDestinationsLogic = kea([ selectors(() => ({ loading: [(s) => [s.hogFunctionTemplatesLoading], (hogFunctionTemplatesLoading) => hogFunctionTemplatesLoading], + types: [() => [(_, p) => p.types], (types) => types], batchExportServiceNames: [ - (s) => [s.user, s.featureFlags], - (user, featureFlags): BatchExportService['type'][] => { + (s) => [s.user, s.featureFlags, s.types], + (user, featureFlags, types): BatchExportService['type'][] => { + // Only add batch exports on the "destinations" page + if (!types.includes('destination')) { + return [] + } const httpEnabled = featureFlags[FEATURE_FLAGS.BATCH_EXPORTS_POSTHOG_HTTP] || user?.is_impersonated || user?.is_staff // HTTP is currently only used for Cloud to Cloud migrations and shouldn't be accessible to users @@ -84,7 +102,10 @@ export const newDestinationsLogic = kea([ description: hogFunction.description, backend: PipelineBackend.HogFunction as const, url: combineUrl( - urls.pipelineNodeNew(PipelineStage.Destination, `hog-${hogFunction.id}`), + urls.pipelineNodeNew( + hogFunctionTypeToPipelineStage(hogFunction.type), + `hog-${hogFunction.id}` + ), {}, hashParams ).url, diff --git a/frontend/src/scenes/pipeline/hogfunctions/HogFunctionInputs.tsx b/frontend/src/scenes/pipeline/hogfunctions/HogFunctionInputs.tsx index fee79b358a74d..31dc8190bfc63 100644 --- a/frontend/src/scenes/pipeline/hogfunctions/HogFunctionInputs.tsx +++ b/frontend/src/scenes/pipeline/hogfunctions/HogFunctionInputs.tsx @@ -390,7 +390,7 @@ export function HogFunctionInputWithSchema({ schema }: HogFunctionInputWithSchem {supportsTemplating && ( } noPadding className=" opacity-0 group-hover:opacity-100 p-1 transition-opacity" diff --git a/frontend/src/scenes/pipeline/hogfunctions/HogFunctionStatusIndicator.tsx b/frontend/src/scenes/pipeline/hogfunctions/HogFunctionStatusIndicator.tsx index 6a66d7025dbc2..81bd4b409a204 100644 --- a/frontend/src/scenes/pipeline/hogfunctions/HogFunctionStatusIndicator.tsx +++ b/frontend/src/scenes/pipeline/hogfunctions/HogFunctionStatusIndicator.tsx @@ -64,11 +64,16 @@ export function HogFunctionStatusIndicator({ hogFunction }: HogFunctionStatusInd return null } - const { tagType, display, description } = hogFunction.status?.state - ? displayMap[hogFunction.status.state] - : hogFunction.enabled - ? DEFAULT_DISPLAY - : DISABLED_MANUALLY_DISPLAY + const { tagType, display, description } = + hogFunction.type === 'site_app' || hogFunction.type === 'site_destination' + ? hogFunction.enabled + ? displayMap[HogWatcherState.healthy] + : DISABLED_MANUALLY_DISPLAY + : hogFunction.status?.state + ? displayMap[hogFunction.status.state] + : hogFunction.enabled + ? DEFAULT_DISPLAY + : DISABLED_MANUALLY_DISPLAY return ( - Mock out async functions + Mock out HTTP requests diff --git a/frontend/src/scenes/pipeline/hogfunctions/hogFunctionTestLogic.tsx b/frontend/src/scenes/pipeline/hogfunctions/hogFunctionTestLogic.tsx index c309442268f58..9caa8bc369165 100644 --- a/frontend/src/scenes/pipeline/hogfunctions/hogFunctionTestLogic.tsx +++ b/frontend/src/scenes/pipeline/hogfunctions/hogFunctionTestLogic.tsx @@ -77,7 +77,7 @@ export const hogFunctionTestLogic = kea([ forms(({ props, actions, values }) => ({ testInvocation: { defaults: { - mock_async_functions: true, + mock_async_functions: false, } as HogFunctionTestInvocationForm, alwaysShowErrors: true, errors: ({ globals }) => { diff --git a/frontend/src/scenes/pipeline/hogfunctions/urls.ts b/frontend/src/scenes/pipeline/hogfunctions/urls.ts index 1374d68fca347..a26ce4a331d55 100644 --- a/frontend/src/scenes/pipeline/hogfunctions/urls.ts +++ b/frontend/src/scenes/pipeline/hogfunctions/urls.ts @@ -7,10 +7,10 @@ export function hogFunctionNewUrl(type: HogFunctionTypeType, template?: string): ? urls.messagingProviderNew(template) : type === 'broadcast' ? urls.messagingBroadcastNew() - : urls.pipelineNodeNew(PipelineStage.Destination, template ? `hog-${template}` : undefined) + : urls.pipelineNodeNew(hogFunctionTypeToPipelineStage(type), template ? `hog-${template}` : undefined) } -export function hogFunctionUrl(type: HogFunctionTypeType, id?: string): string { +export function hogFunctionUrl(type: HogFunctionTypeType | PipelineStage, id?: string): string { if (type === 'email') { return id ? urls.messagingProvider(id) : urls.messagingProviders() } else if (type === 'broadcast') { @@ -18,9 +18,27 @@ export function hogFunctionUrl(type: HogFunctionTypeType, id?: string): string { } return id ? urls.pipelineNode( - PipelineStage.Destination, + hogFunctionTypeToPipelineStage(type), id.startsWith('hog-') ? id : `hog-${id}`, PipelineNodeTab.Configuration ) : urls.pipeline(PipelineTab.Destinations) } + +// Supports both hog function types and pipeline stages themselves as input +export function hogFunctionTypeToPipelineStage(type: string): PipelineStage { + switch (type) { + case 'site_destination': + return PipelineStage.Destination + case 'site-destination': + return PipelineStage.Destination + case 'destination': + return PipelineStage.Destination + case 'site_app': + return PipelineStage.SiteApp + case 'site-app': + return PipelineStage.SiteApp + default: + return PipelineStage.Destination + } +} diff --git a/frontend/src/scenes/pipeline/overviewLogic.tsx b/frontend/src/scenes/pipeline/overviewLogic.tsx index 2b104c3004b8e..60ce106cb9860 100644 --- a/frontend/src/scenes/pipeline/overviewLogic.tsx +++ b/frontend/src/scenes/pipeline/overviewLogic.tsx @@ -1,6 +1,7 @@ import { connect, kea, path } from 'kea' import { teamLogic } from 'scenes/teamLogic' +import { DESTINATION_TYPES } from './destinations/constants' import { pipelineDestinationsLogic } from './destinations/destinationsLogic' import type { pipelineOverviewLogicType } from './overviewLogicType' import { pipelineTransformationsLogic } from './transformationsLogic' @@ -13,13 +14,13 @@ export const pipelineOverviewLogic = kea([ ['currentTeamId'], pipelineTransformationsLogic, ['loading as transformationsLoading', 'transformations'], - pipelineDestinationsLogic, + pipelineDestinationsLogic({ types: DESTINATION_TYPES }), ['loading as destinationsLoading', 'destinations'], ], actions: [ pipelineTransformationsLogic, ['loadPlugins as loadTransformationPlugins', 'loadPluginConfigs as loadTransformationPluginConfigs'], - pipelineDestinationsLogic, + pipelineDestinationsLogic({ types: DESTINATION_TYPES }), [ 'loadPlugins as loadDestinationPlugins', 'loadPluginConfigs as loadDestinationPluginConfigs', diff --git a/frontend/src/scenes/pipeline/pipelineAccessLogic.tsx b/frontend/src/scenes/pipeline/pipelineAccessLogic.tsx index 58b4129c9523f..1d8875dbbedfa 100644 --- a/frontend/src/scenes/pipeline/pipelineAccessLogic.tsx +++ b/frontend/src/scenes/pipeline/pipelineAccessLogic.tsx @@ -5,7 +5,7 @@ import { AvailableFeature } from '~/types' import { canConfigurePlugins, canGloballyManagePlugins } from './access' import type { pipelineAccessLogicType } from './pipelineAccessLogicType' -import { Destination, NewDestinationItemType, PipelineBackend } from './types' +import { Destination, NewDestinationItemType, PipelineBackend, SiteApp } from './types' export const pipelineAccessLogic = kea([ path(['scenes', 'pipeline', 'pipelineAccessLogic']), @@ -25,11 +25,12 @@ export const pipelineAccessLogic = kea([ canEnableDestination: [ (s) => [s.canEnableNewDestinations], - (canEnableNewDestinations): ((destination: Destination | NewDestinationItemType) => boolean) => { - return (destination: Destination | NewDestinationItemType) => { + (canEnableNewDestinations): ((destination: Destination | NewDestinationItemType | SiteApp) => boolean) => { + return (destination: Destination | NewDestinationItemType | SiteApp) => { return destination.backend === PipelineBackend.HogFunction ? ('hog_function' in destination ? destination.hog_function.type === 'site_destination' || + destination.hog_function.type === 'site_app' || destination.hog_function.template?.status === 'free' : destination.status === 'free') || canEnableNewDestinations : canEnableNewDestinations diff --git a/frontend/src/scenes/pipeline/pipelineBatchExportConfigurationLogic.tsx b/frontend/src/scenes/pipeline/pipelineBatchExportConfigurationLogic.tsx index 7d0b3b405b43e..d2db4bc584484 100644 --- a/frontend/src/scenes/pipeline/pipelineBatchExportConfigurationLogic.tsx +++ b/frontend/src/scenes/pipeline/pipelineBatchExportConfigurationLogic.tsx @@ -10,6 +10,7 @@ import { DatabaseSchemaBatchExportTable } from '~/queries/schema' import { BatchExportConfiguration, BatchExportService, PipelineNodeTab, PipelineStage } from '~/types' import { humanizeBatchExportName } from './batch-exports/utils' +import { DESTINATION_TYPES } from './destinations/constants' import { pipelineDestinationsLogic } from './destinations/destinationsLogic' import { pipelineAccessLogic } from './pipelineAccessLogic' import type { pipelineBatchExportConfigurationLogicType } from './pipelineBatchExportConfigurationLogicType' @@ -410,7 +411,9 @@ export const pipelineBatchExportConfigurationLogic = kea { if (name[0] === 'json_config_file' && value) { diff --git a/frontend/src/scenes/pipeline/pipelinePluginConfigurationLogic.tsx b/frontend/src/scenes/pipeline/pipelinePluginConfigurationLogic.tsx index 265de21a1dbd4..721e447dc9f93 100644 --- a/frontend/src/scenes/pipeline/pipelinePluginConfigurationLogic.tsx +++ b/frontend/src/scenes/pipeline/pipelinePluginConfigurationLogic.tsx @@ -4,6 +4,7 @@ import { forms } from 'kea-forms' import { loaders } from 'kea-loaders' import { beforeUnload, router } from 'kea-router' import api from 'lib/api' +import { FEATURE_FLAGS } from 'lib/constants' import { featureFlagLogic } from 'lib/logic/featureFlagLogic' import { teamLogic } from 'scenes/teamLogic' import { urls } from 'scenes/urls' @@ -16,6 +17,7 @@ import { determineRequiredFields, getPluginConfigFormData, } from './configUtils' +import { DESTINATION_TYPES, SITE_APP_TYPES } from './destinations/constants' import { pipelineDestinationsLogic } from './destinations/destinationsLogic' import { frontendAppsLogic } from './frontendAppsLogic' import { importAppsLogic } from './importAppsLogic' @@ -169,9 +171,18 @@ export const pipelinePluginConfigurationLogic = kea( backend: PipelineBackend.HogFunction, interval: 'realtime', id: - candidate.type === 'destination' || candidate.type === 'site_destination' + candidate.type === 'destination' || + candidate.type === 'site_destination' || + candidate.type === 'site_app' ? `hog-${candidate.id}` : candidate.id, name: candidate.name, diff --git a/frontend/src/scenes/session-recordings/filters/RecordingsUniversalFilters.tsx b/frontend/src/scenes/session-recordings/filters/RecordingsUniversalFilters.tsx index 6dfa6d007949f..989b726851070 100644 --- a/frontend/src/scenes/session-recordings/filters/RecordingsUniversalFilters.tsx +++ b/frontend/src/scenes/session-recordings/filters/RecordingsUniversalFilters.tsx @@ -11,6 +11,7 @@ import { TestAccountFilter } from 'scenes/insights/filters/TestAccountFilter' import { actionsModel } from '~/models/actionsModel' import { cohortsModel } from '~/models/cohortsModel' import { AndOrFilterSelect } from '~/queries/nodes/InsightViz/PropertyGroupFilters/AndOrFilterSelect' +import { NodeKind } from '~/queries/schema' import { RecordingUniversalFilters, UniversalFiltersGroup } from '~/types' import { DurationFilter } from './DurationFilter' @@ -19,16 +20,32 @@ export const RecordingsUniversalFilters = ({ filters, setFilters, className, + allowReplayHogQLFilters = false, }: { filters: RecordingUniversalFilters setFilters: (filters: Partial) => void className?: string + allowReplayFlagsFilters?: boolean + allowReplayHogQLFilters?: boolean }): JSX.Element => { useMountedLogic(cohortsModel) useMountedLogic(actionsModel) const durationFilter = filters.duration[0] + const taxonomicGroupTypes = [ + TaxonomicFilterGroupType.Replay, + TaxonomicFilterGroupType.Events, + TaxonomicFilterGroupType.Actions, + TaxonomicFilterGroupType.Cohorts, + TaxonomicFilterGroupType.PersonProperties, + TaxonomicFilterGroupType.SessionProperties, + ] + + if (allowReplayHogQLFilters) { + taxonomicGroupTypes.push(TaxonomicFilterGroupType.HogQLExpression) + } + return (
@@ -102,14 +119,7 @@ export const RecordingsUniversalFilters = ({ setFilters({ filter_group: filterGroup })} > @@ -144,6 +154,7 @@ const RecordingsUniversalFilterGroup = (): JSX.Element => { onRemove={() => removeGroupValue(index)} onChange={(value) => replaceGroupValue(index, value)} initiallyOpen={allowInitiallyOpen} + metadataSource={{ kind: NodeKind.RecordingsQuery }} /> ) })} diff --git a/frontend/src/scenes/session-recordings/player/PlayerFrameOverlay.tsx b/frontend/src/scenes/session-recordings/player/PlayerFrameOverlay.tsx index 6337aafc995af..c1c68eac5cbc8 100644 --- a/frontend/src/scenes/session-recordings/player/PlayerFrameOverlay.tsx +++ b/frontend/src/scenes/session-recordings/player/PlayerFrameOverlay.tsx @@ -5,14 +5,11 @@ import clsx from 'clsx' import { useActions, useValues } from 'kea' import { IconErrorOutline, IconSync } from 'lib/lemon-ui/icons' import { LemonButton } from 'lib/lemon-ui/LemonButton' -import { useState } from 'react' import { sessionRecordingPlayerLogic } from 'scenes/session-recordings/player/sessionRecordingPlayerLogic' import { getCurrentExporterData } from '~/exporter/exporterViewLogic' import { SessionPlayerState } from '~/types' -import { PlayerUpNext } from './PlayerUpNext' - const PlayerFrameOverlayContent = (): JSX.Element | null => { const { currentPlayerState, endReached } = useValues(sessionRecordingPlayerLogic) let content = null @@ -80,26 +77,11 @@ const PlayerFrameOverlayContent = (): JSX.Element | null => { } export function PlayerFrameOverlay(): JSX.Element { - const { playlistLogic } = useValues(sessionRecordingPlayerLogic) const { togglePlayPause } = useActions(sessionRecordingPlayerLogic) - const [interrupted, setInterrupted] = useState(false) - return ( -
setInterrupted(true)} - onMouseOut={() => setInterrupted(false)} - > +
- {playlistLogic ? ( - setInterrupted(false)} - /> - ) : undefined}
) } diff --git a/frontend/src/scenes/session-recordings/player/PlayerUpNext.scss b/frontend/src/scenes/session-recordings/player/PlayerUpNext.scss index 1d573607ad8fd..b85d735b176ba 100644 --- a/frontend/src/scenes/session-recordings/player/PlayerUpNext.scss +++ b/frontend/src/scenes/session-recordings/player/PlayerUpNext.scss @@ -1,43 +1,14 @@ .PlayerUpNext { - position: absolute; - right: 1rem; - bottom: 1rem; z-index: 11; transition: 250ms transform ease-out; - - &--enter { - transform: translateY(200%); - } - - &--enter-active, - &--enter-done { - transform: translateY(0%); - } - - &--exit { - transform: translateY(0%); - } - - &--exit-active { - transform: translateY(200%); - } } .PlayerUpNextButton { - position: relative; display: flex; align-items: center; - min-height: 2.5rem; - padding: 0.25rem 0.75rem; overflow: hidden; - font-weight: 600; - line-height: 1.5rem; cursor: pointer; - background-color: rgb(255 255 255 / 75%); backdrop-filter: blur(5px); - border: 1px solid rgb(0 0 0 / 50%); - border-radius: var(--radius); - box-shadow: var(--shadow-elevation-3000); .PlayerUpNextButtonBackground { position: absolute; @@ -46,7 +17,7 @@ left: 0; width: 0; color: var(--primary-alt); - background-color: var(--bg-light); + background-color: var(--border-3000); } &.PlayerUpNextButton--animating { diff --git a/frontend/src/scenes/session-recordings/player/PlayerUpNext.tsx b/frontend/src/scenes/session-recordings/player/PlayerUpNext.tsx index 669dcb5f02b29..b16df4b1722eb 100644 --- a/frontend/src/scenes/session-recordings/player/PlayerUpNext.tsx +++ b/frontend/src/scenes/session-recordings/player/PlayerUpNext.tsx @@ -3,28 +3,39 @@ import './PlayerUpNext.scss' import { IconPlay } from '@posthog/icons' import clsx from 'clsx' import { BuiltLogic, useActions, useValues } from 'kea' +import { useKeyboardHotkeys } from 'lib/hooks/useKeyboardHotkeys' import { Tooltip } from 'lib/lemon-ui/Tooltip' import { useEffect, useRef, useState } from 'react' -import { CSSTransition } from 'react-transition-group' + +import { KeyboardShortcut } from '~/layout/navigation-3000/components/KeyboardShortcut' import { sessionRecordingsPlaylistLogicType } from '../playlist/sessionRecordingsPlaylistLogicType' import { sessionRecordingPlayerLogic } from './sessionRecordingPlayerLogic' export interface PlayerUpNextProps { playlistLogic: BuiltLogic - interrupted?: boolean - clearInterrupted?: () => void } -export function PlayerUpNext({ interrupted, clearInterrupted, playlistLogic }: PlayerUpNextProps): JSX.Element | null { +export function PlayerUpNext({ playlistLogic }: PlayerUpNextProps): JSX.Element | null { const timeoutRef = useRef() - const { endReached } = useValues(sessionRecordingPlayerLogic) - const { reportNextRecordingTriggered } = useActions(sessionRecordingPlayerLogic) + const { endReached, playNextAnimationInterrupted } = useValues(sessionRecordingPlayerLogic) + const { reportNextRecordingTriggered, setPlayNextAnimationInterrupted } = useActions(sessionRecordingPlayerLogic) const [animate, setAnimate] = useState(false) const { nextSessionRecording } = useValues(playlistLogic) const { setSelectedRecordingId } = useActions(playlistLogic) + useKeyboardHotkeys({ + n: { + action: () => { + if (nextSessionRecording?.id) { + reportNextRecordingTriggered(false) + setSelectedRecordingId(nextSessionRecording.id) + } + }, + }, + }) + const goToRecording = (automatic: boolean): void => { if (!nextSessionRecording?.id) { return @@ -38,41 +49,45 @@ export function PlayerUpNext({ interrupted, clearInterrupted, playlistLogic }: P if (endReached && nextSessionRecording?.id) { setAnimate(true) - clearInterrupted?.() + setPlayNextAnimationInterrupted(false) timeoutRef.current = setTimeout(() => { goToRecording(true) - }, 3000) // NOTE: Keep in sync with SCSS + }, 30000) // NOTE: Keep in sync with SCSS } return () => clearTimeout(timeoutRef.current) }, [endReached, !!nextSessionRecording]) useEffect(() => { - if (interrupted) { + if (playNextAnimationInterrupted) { clearTimeout(timeoutRef.current) setAnimate(false) } - }, [interrupted]) + }, [playNextAnimationInterrupted]) if (!nextSessionRecording) { return null } return ( - - -
-
goToRecording(false)} - > -
-
- Next recording -
+ + Play the next recording + + } + > +
+
goToRecording(false)} + > +
+
+ Play next
- - +
+ ) } diff --git a/frontend/src/scenes/session-recordings/player/SessionRecordingPlayer.tsx b/frontend/src/scenes/session-recordings/player/SessionRecordingPlayer.tsx index 5666b718cb5bf..6b28feca120ae 100644 --- a/frontend/src/scenes/session-recordings/player/SessionRecordingPlayer.tsx +++ b/frontend/src/scenes/session-recordings/player/SessionRecordingPlayer.tsx @@ -92,6 +92,7 @@ export function SessionRecordingPlayer(props: SessionRecordingPlayerProps): JSX. const { isFullScreen, explorerMode, isBuffering, messageTooLargeWarnings } = useValues( sessionRecordingPlayerLogic(logicProps) ) + const { setPlayNextAnimationInterrupted } = useActions(sessionRecordingPlayerLogic(logicProps)) const speedHotkeys = useMemo(() => createPlaybackSpeedKey(setSpeed), [setSpeed]) const { isVerticallyStacked, sidebarOpen, playbackMode } = useValues(playerSettingsLogic) @@ -185,6 +186,8 @@ export function SessionRecordingPlayer(props: SessionRecordingPlayerProps): JSX. `SessionRecordingPlayer--${size}` )} onClick={incrementClickCount} + onMouseMove={() => setPlayNextAnimationInterrupted(true)} + onMouseOut={() => setPlayNextAnimationInterrupted(false)} > {explorerMode ? ( diff --git a/frontend/src/scenes/session-recordings/player/controller/PlayerController.tsx b/frontend/src/scenes/session-recordings/player/controller/PlayerController.tsx index 35b2d1d18651a..919fa8b6bd042 100644 --- a/frontend/src/scenes/session-recordings/player/controller/PlayerController.tsx +++ b/frontend/src/scenes/session-recordings/player/controller/PlayerController.tsx @@ -1,4 +1,4 @@ -import { IconCollapse45, IconExpand45, IconPause, IconPlay, IconSearch } from '@posthog/icons' +import { IconClock, IconCollapse45, IconExpand45, IconPause, IconPlay, IconSearch } from '@posthog/icons' import clsx from 'clsx' import { useActions, useValues } from 'kea' import { useKeyboardHotkeys } from 'lib/hooks/useKeyboardHotkeys' @@ -10,11 +10,13 @@ import { SettingsMenu, SettingsToggle, } from 'scenes/session-recordings/components/PanelSettings' -import { playerSettingsLogic } from 'scenes/session-recordings/player/playerSettingsLogic' +import { playerSettingsLogic, TimestampFormat } from 'scenes/session-recordings/player/playerSettingsLogic' +import { PlayerUpNext } from 'scenes/session-recordings/player/PlayerUpNext' import { PLAYBACK_SPEEDS, sessionRecordingPlayerLogic, } from 'scenes/session-recordings/player/sessionRecordingPlayerLogic' +import { TimestampFormatToLabel } from 'scenes/session-recordings/utils' import { KeyboardShortcut } from '~/layout/navigation-3000/components/KeyboardShortcut' import { SessionPlayerState } from '~/types' @@ -117,12 +119,39 @@ function InspectDOM(): JSX.Element { } function PlayerBottomSettings(): JSX.Element { + const { timestampFormat } = useValues(playerSettingsLogic) + const { setTimestampFormat } = useActions(playerSettingsLogic) + return ( - - - - - + +
+ + + + setTimestampFormat(TimestampFormat.UTC), + active: timestampFormat === TimestampFormat.UTC, + }, + { + label: 'Device', + onClick: () => setTimestampFormat(TimestampFormat.Device), + active: timestampFormat === TimestampFormat.Device, + }, + { + label: 'Relative', + onClick: () => setTimestampFormat(TimestampFormat.Relative), + active: timestampFormat === TimestampFormat.Relative, + }, + ]} + icon={} + label={TimestampFormatToLabel[timestampFormat]} + /> + +
) } @@ -134,7 +163,11 @@ function FullScreen(): JSX.Element { setIsFullScreen(!isFullScreen)} - tooltip={`${!isFullScreen ? 'Go' : 'Exit'} full screen (F)`} + tooltip={ + <> + {!isFullScreen ? 'Go' : 'Exit'} full screen + + } > @@ -165,32 +198,38 @@ function Maximise(): JSX.Element { : } - className="text-2xl" + tooltip={ + <> + {isMaximised ? 'Open' : 'Close'} other panels + + } + icon={isMaximised ? : } /> ) } export function PlayerController(): JSX.Element { + const { playlistLogic } = useValues(sessionRecordingPlayerLogic) + return (
-
-
+
+
- -
- - - -
-
+
+ + + +
+
+ {playlistLogic ? : undefined}
+
) diff --git a/frontend/src/scenes/session-recordings/player/controller/PlayerControllerTime.tsx b/frontend/src/scenes/session-recordings/player/controller/PlayerControllerTime.tsx index 0c40085388cd4..417dba1109ce4 100644 --- a/frontend/src/scenes/session-recordings/player/controller/PlayerControllerTime.tsx +++ b/frontend/src/scenes/session-recordings/player/controller/PlayerControllerTime.tsx @@ -4,10 +4,12 @@ import { useActions, useValues } from 'kea' import { useKeyHeld } from 'lib/hooks/useKeyHeld' import { IconSkipBackward } from 'lib/lemon-ui/icons' import { capitalizeFirstLetter, colonDelimitedDuration } from 'lib/utils' -import { useCallback } from 'react' import { SimpleTimeLabel } from 'scenes/session-recordings/components/SimpleTimeLabel' import { ONE_FRAME_MS, sessionRecordingPlayerLogic } from 'scenes/session-recordings/player/sessionRecordingPlayerLogic' +import { KeyboardShortcut } from '~/layout/navigation-3000/components/KeyboardShortcut' +import { HotKeyOrModifier } from '~/types' + import { playerSettingsLogic, TimestampFormat } from '../playerSettingsLogic' import { seekbarLogic } from './seekbarLogic' @@ -16,39 +18,26 @@ export function Timestamp(): JSX.Element { useValues(sessionRecordingPlayerLogic) const { isScrubbing, scrubbingTime } = useValues(seekbarLogic(logicProps)) const { timestampFormat } = useValues(playerSettingsLogic) - const { setTimestampFormat } = useActions(playerSettingsLogic) const startTimeSeconds = ((isScrubbing ? scrubbingTime : currentPlayerTime) ?? 0) / 1000 const endTimeSeconds = Math.floor(sessionPlayerData.durationMs / 1000) const fixedUnits = endTimeSeconds > 3600 ? 3 : 2 - const rotateTimestampFormat = useCallback(() => { - setTimestampFormat( - timestampFormat === 'relative' - ? TimestampFormat.UTC - : timestampFormat === TimestampFormat.UTC - ? TimestampFormat.Device - : TimestampFormat.Relative - ) - }, [timestampFormat]) - return ( - - - {timestampFormat === TimestampFormat.Relative ? ( -
- {colonDelimitedDuration(startTimeSeconds, fixedUnits)} - / - {colonDelimitedDuration(endTimeSeconds, fixedUnits)} -
- ) : currentTimestamp ? ( - - ) : ( - '--/--/----, 00:00:00' - )} -
-
+
+ {timestampFormat === TimestampFormat.Relative ? ( +
+ {colonDelimitedDuration(startTimeSeconds, fixedUnits)} + / + {colonDelimitedDuration(endTimeSeconds, fixedUnits)} +
+ ) : currentTimestamp ? ( + + ) : ( + '--/--/----, 00:00:00' + )} +
) } @@ -58,10 +47,14 @@ export function SeekSkip({ direction }: { direction: 'forward' | 'backward' }): const altKeyHeld = useKeyHeld('Alt') const jumpTimeSeconds = altKeyHeld ? 1 : jumpTimeMs / 1000 - const altKeyName = navigator.platform.includes('Mac') ? '⌥' : 'Alt' - const arrowSymbol = direction === 'forward' ? '→' : '←' - const arrowName = direction === 'forward' ? 'right' : 'left' + const arrowKey: Partial> = {} + if (direction === 'forward') { + arrowKey.arrowright = true + } + if (direction === 'backward') { + arrowKey.arrowleft = true + } return ( {!altKeyHeld ? ( <> - {capitalizeFirstLetter(direction)} {jumpTimeSeconds}s ( - - {arrowSymbol} {arrowName} arrow - - )
+ {capitalizeFirstLetter(direction)} {jumpTimeSeconds}s +
) : null} - {capitalizeFirstLetter(direction)} 1 frame ({ONE_FRAME_MS}ms) ( - - {altKeyName} + {arrowSymbol} - - ) + {capitalizeFirstLetter(direction)} 1 frame ({ONE_FRAME_MS}ms){' '} +
} > diff --git a/frontend/src/scenes/session-recordings/player/sessionRecordingPlayerLogic.ts b/frontend/src/scenes/session-recordings/player/sessionRecordingPlayerLogic.ts index 56240e7ec1ee0..b9b06bfedc101 100644 --- a/frontend/src/scenes/session-recordings/player/sessionRecordingPlayerLogic.ts +++ b/frontend/src/scenes/session-recordings/player/sessionRecordingPlayerLogic.ts @@ -192,8 +192,15 @@ export const sessionRecordingPlayerLogic = kea( reportMessageTooLargeWarningSeen: (sessionRecordingId: string) => ({ sessionRecordingId }), setDebugSnapshotTypes: (types: EventType[]) => ({ types }), setDebugSnapshotIncrementalSources: (incrementalSources: IncrementalSource[]) => ({ incrementalSources }), + setPlayNextAnimationInterrupted: (interrupted: boolean) => ({ interrupted }), }), reducers(() => ({ + playNextAnimationInterrupted: [ + false, + { + setPlayNextAnimationInterrupted: (_, { interrupted }) => interrupted, + }, + ], reportedReplayerErrors: [ new Set(), { diff --git a/frontend/src/scenes/session-recordings/playlist/SessionRecordingsPlaylist.tsx b/frontend/src/scenes/session-recordings/playlist/SessionRecordingsPlaylist.tsx index cb0864f30131a..b3cf899123303 100644 --- a/frontend/src/scenes/session-recordings/playlist/SessionRecordingsPlaylist.tsx +++ b/frontend/src/scenes/session-recordings/playlist/SessionRecordingsPlaylist.tsx @@ -45,6 +45,7 @@ export function SessionRecordingsPlaylist(props: SessionRecordingPlaylistLogicPr const { featureFlags } = useValues(featureFlagLogic) const isTestingSaved = featureFlags[FEATURE_FLAGS.SAVED_NOT_PINNED] === 'test' + const allowReplayHogQLFilters = !!featureFlags[FEATURE_FLAGS.REPLAY_HOGQL_FILTERS] const pinnedDescription = isTestingSaved ? 'Saved' : 'Pinned' @@ -92,7 +93,12 @@ export function SessionRecordingsPlaylist(props: SessionRecordingPlaylistLogicPr
{!notebookNode && ( - + )} } + icon={} label={SortingKeyToLabel[filters.order || 'start_time']} /> ) diff --git a/frontend/src/scenes/session-recordings/playlist/sessionRecordingsPlaylistLogic.ts b/frontend/src/scenes/session-recordings/playlist/sessionRecordingsPlaylistLogic.ts index a74ae5fc08b09..c550d14f3bc8c 100644 --- a/frontend/src/scenes/session-recordings/playlist/sessionRecordingsPlaylistLogic.ts +++ b/frontend/src/scenes/session-recordings/playlist/sessionRecordingsPlaylistLogic.ts @@ -4,7 +4,7 @@ import { loaders } from 'kea-loaders' import { actionToUrl, router, urlToAction } from 'kea-router' import { subscriptions } from 'kea-subscriptions' import api from 'lib/api' -import { isAnyPropertyfilter } from 'lib/components/PropertyFilters/utils' +import { isAnyPropertyfilter, isHogQLPropertyFilter } from 'lib/components/PropertyFilters/utils' import { DEFAULT_UNIVERSAL_GROUP_FILTER } from 'lib/components/UniversalFilters/universalFiltersLogic' import { isActionFilter, @@ -12,6 +12,7 @@ import { isLogEntryPropertyFilter, 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 { eventUsageLogic } from 'lib/utils/eventUsageLogic' @@ -120,6 +121,8 @@ export function convertUniversalFiltersToRecordingsQuery(universalFilters: Recor actions.push(f) } else if (isLogEntryPropertyFilter(f)) { console_log_filters.push(f) + } else if (isHogQLPropertyFilter(f)) { + properties.push(f) } else if (isAnyPropertyfilter(f)) { if (isRecordingPropertyFilter(f)) { if (f.key === 'visited_page') { @@ -329,7 +332,9 @@ export const sessionRecordingsPlaylistLogic = kea { - const params: RecordingsQuery = { + // as_query is a temporary parameter as a flag + // to let the backend know not to convert the query to a legacy filter when processing + const params: RecordingsQuery & { as_query?: boolean } = { ...convertUniversalFiltersToRecordingsQuery(values.filters), person_uuid: props.personUUID ?? '', limit: RECORDINGS_LIMIT, @@ -347,6 +352,10 @@ export const sessionRecordingsPlaylistLogic = kea [(_, props) => props], (props): SessionRecordingPlaylistLogicProps => props], + listAPIAsQuery: [ + (s) => [s.featureFlags], + (featureFlags) => { + return !!featureFlags[FEATURE_FLAGS.REPLAY_LIST_RECORDINGS_AS_QUERY] + }, + ], + matchingEventsMatchType: [ (s) => [s.filters], (filters): MatchingEventsMatchType => { diff --git a/frontend/src/scenes/session-recordings/utils.ts b/frontend/src/scenes/session-recordings/utils.ts index c7e4f61264ed3..f3e29ae3ea52c 100644 --- a/frontend/src/scenes/session-recordings/utils.ts +++ b/frontend/src/scenes/session-recordings/utils.ts @@ -1,5 +1,11 @@ import { LegacyRecordingFilters, RecordingUniversalFilters, UniversalFiltersGroup, UniversalFilterValue } from '~/types' +export const TimestampFormatToLabel = { + relative: 'Relative', + utc: 'UTC', + device: 'Device', +} + export const isUniversalFilters = ( filters: RecordingUniversalFilters | LegacyRecordingFilters ): filters is RecordingUniversalFilters => { diff --git a/frontend/src/scenes/settings/SettingsMap.tsx b/frontend/src/scenes/settings/SettingsMap.tsx index 3e4946ea1f8ac..488502b6e164d 100644 --- a/frontend/src/scenes/settings/SettingsMap.tsx +++ b/frontend/src/scenes/settings/SettingsMap.tsx @@ -136,7 +136,6 @@ export const SETTINGS_MAP: SettingSection[] = [ id: 'dead-clicks-autocapture', title: 'Dead clicks autocapture', component: , - flag: 'DEAD_CLICKS_AUTOCAPTURE', }, ], }, diff --git a/frontend/src/scenes/surveys/surveyViewViz.tsx b/frontend/src/scenes/surveys/surveyViewViz.tsx index 8f71b703919a8..8e19c575fef10 100644 --- a/frontend/src/scenes/surveys/surveyViewViz.tsx +++ b/frontend/src/scenes/surveys/surveyViewViz.tsx @@ -1,3 +1,4 @@ +import { offset } from '@floating-ui/react' import { IconInfo, IconSparkles, @@ -5,8 +6,9 @@ import { IconThumbsDownFilled, IconThumbsUp, IconThumbsUpFilled, + IconX, } from '@posthog/icons' -import { LemonButton, LemonTable, Spinner } from '@posthog/lemon-ui' +import { LemonButton, LemonTable, Popover, Spinner } from '@posthog/lemon-ui' import { BindLogic, useActions, useValues } from 'kea' import { FlaggedFeature } from 'lib/components/FlaggedFeature' import { FEATURE_FLAGS } from 'lib/constants' @@ -21,6 +23,7 @@ import { insightLogic } from 'scenes/insights/insightLogic' import { LineGraph } from 'scenes/insights/views/LineGraph/LineGraph' import { PieChart } from 'scenes/insights/views/LineGraph/PieChart' import { PersonDisplay } from 'scenes/persons/PersonDisplay' +import { surveyDataProcessingLogic } from 'scenes/surveys/suveyDataProcessingLogic' import { GraphType } from '~/types' import { InsightLogicProps, SurveyQuestionType } from '~/types' @@ -634,29 +637,76 @@ export function OpenTextViz({ } function ResponseSummariesButton({ questionIndex }: { questionIndex: number | undefined }): JSX.Element { + const [popOverClosed, setPopOverClosed] = useState(false) + const { summarize } = useActions(surveyLogic) const { responseSummary, responseSummaryLoading } = useValues(surveyLogic) - + const { surveyDataProcessingAccepted, surveyDataProcessingRefused } = useValues(surveyDataProcessingLogic) + const { acceptSurveyDataProcessing, refuseSurveyDataProcessing } = useActions(surveyDataProcessingLogic) + + const summarizeButton = ( + summarize({ questionIndex })} + disabledReason={ + surveyDataProcessingRefused + ? 'OpenAI processing refused' + : responseSummaryLoading + ? 'Let me think...' + : responseSummary + ? 'Already summarized' + : undefined + } + icon={} + > + {responseSummaryLoading ? ( + <> + Let me think... + + + ) : ( + <>Summarize responses + )} + + ) return ( - summarize({ questionIndex })} - disabledReason={ - responseSummaryLoading ? 'Let me think...' : responseSummary ? 'already summarized' : undefined - } - icon={} - > - {responseSummaryLoading ? ( - <> - Let me think... - - - ) : ( - <>Summarize responses - )} - + {surveyDataProcessingAccepted ? ( + summarizeButton + ) : ( + +
+ } onClick={() => setPopOverClosed(true)} /> +
+
+

+ Uses OpenAI services to analyze your survey responses, +
+ This can include personal data of your users, +
+ if they include it in their responses. +
+ Your data won't be used for training models. +

+
+ acceptSurveyDataProcessing()}> + Got it, I accept OpenAI processing survey data + + refuseSurveyDataProcessing()}> + No thanks, I don't want OpenAI processing survey data + +
+ } + middleware={[offset(-12)]} + showArrow + visible={!popOverClosed && !surveyDataProcessingAccepted && !surveyDataProcessingRefused} + > + {summarizeButton} + + )} ) } @@ -686,7 +736,7 @@ function ResponseSummaryFeedback({ surveyId }: { surveyId: string }): JSX.Elemen return // Already rated } setRating(newRating) - posthog.capture('chat rating', { + posthog.capture('survey_resonse_rated', { survey_id: surveyId, answer_rating: rating, }) diff --git a/frontend/src/scenes/surveys/suveyDataProcessingLogic.ts b/frontend/src/scenes/surveys/suveyDataProcessingLogic.ts new file mode 100644 index 0000000000000..ceaa2dde22efd --- /dev/null +++ b/frontend/src/scenes/surveys/suveyDataProcessingLogic.ts @@ -0,0 +1,38 @@ +import { actions, kea, listeners, path, reducers } from 'kea' +import posthog from 'posthog-js' + +import type { surveyDataProcessingLogicType } from './suveyDataProcessingLogicType' + +export const surveyDataProcessingLogic = kea([ + path(['scenes', 'surveys', 'suveyDataProcessingLogic']), + actions({ + acceptSurveyDataProcessing: true, + refuseSurveyDataProcessing: true, + }), + reducers({ + surveyDataProcessingAccepted: [ + false, + { persist: true }, + { + acceptSurveyDataProcessing: () => true, + refuseSurveyDataProcessing: () => false, + }, + ], + surveyDataProcessingRefused: [ + false, + { persist: true }, + { + acceptSurveyDataProcessing: () => false, + refuseSurveyDataProcessing: () => true, + }, + ], + }), + listeners({ + acceptSurveyDataProcessing: () => { + posthog.capture('survey_data_processing_accepted') + }, + refuseSurveyDataProcessing: () => { + posthog.capture('survey_data_processing_refused') + }, + }), +]) diff --git a/frontend/src/scenes/web-analytics/webAnalyticsLogic.tsx b/frontend/src/scenes/web-analytics/webAnalyticsLogic.tsx index 85e0da0754f15..d5d93400466d3 100644 --- a/frontend/src/scenes/web-analytics/webAnalyticsLogic.tsx +++ b/frontend/src/scenes/web-analytics/webAnalyticsLogic.tsx @@ -216,6 +216,9 @@ const getDashboardItemId = (section: TileId, tab: string | undefined, isModal?: // pretend to be a new-AdHoc to get the correct behaviour elsewhere return `new-AdHoc.web-analytics.${section}.${tab || 'default'}.${isModal ? 'modal' : 'default'}` } + +const teamId = window.POSTHOG_APP_CONTEXT?.current_team?.id +const persistConfig = { persist: true, prefix: `${teamId}__` } export const webAnalyticsLogic = kea([ path(['scenes', 'webAnalytics', 'webAnalyticsSceneLogic']), connect(() => ({ @@ -280,7 +283,7 @@ export const webAnalyticsLogic = kea([ reducers({ webAnalyticsFilters: [ initialWebAnalyticsFilter, - { persist: true }, + persistConfig, { setWebAnalyticsFilters: (_, { webAnalyticsFilters }) => webAnalyticsFilters, togglePropertyFilter: (oldPropertyFilters, { key, value, type }): WebAnalyticsPropertyFilters => { @@ -352,7 +355,7 @@ export const webAnalyticsLogic = kea([ ], _graphsTab: [ null as string | null, - { persist: true }, + persistConfig, { setGraphsTab: (_, { tab }) => tab, togglePropertyFilter: (oldTab, { tabChange }) => tabChange?.graphsTab || oldTab, @@ -360,7 +363,7 @@ export const webAnalyticsLogic = kea([ ], _sourceTab: [ null as string | null, - { persist: true }, + persistConfig, { setSourceTab: (_, { tab }) => tab, togglePropertyFilter: (oldTab, { tabChange }) => tabChange?.sourceTab || oldTab, @@ -368,7 +371,7 @@ export const webAnalyticsLogic = kea([ ], _deviceTab: [ null as string | null, - { persist: true }, + persistConfig, { setDeviceTab: (_, { tab }) => tab, togglePropertyFilter: (oldTab, { tabChange }) => tabChange?.deviceTab || oldTab, @@ -376,7 +379,7 @@ export const webAnalyticsLogic = kea([ ], _pathTab: [ null as string | null, - { persist: true }, + persistConfig, { setPathTab: (_, { tab }) => tab, togglePropertyFilter: (oldTab, { tabChange }) => tabChange?.pathTab || oldTab, @@ -384,7 +387,7 @@ export const webAnalyticsLogic = kea([ ], _geographyTab: [ null as string | null, - { persist: true }, + persistConfig, { setGeographyTab: (_, { tab }) => tab, togglePropertyFilter: (oldTab, { tabChange }) => tabChange?.geographyTab || oldTab, @@ -392,7 +395,7 @@ export const webAnalyticsLogic = kea([ ], isPathCleaningEnabled: [ null as boolean | null, - { persist: true }, + persistConfig, { setIsPathCleaningEnabled: (_, { isPathCleaningEnabled }) => isPathCleaningEnabled, }, @@ -413,7 +416,7 @@ export const webAnalyticsLogic = kea([ dateTo: initialDateTo, interval: initialInterval, }, - { persist: true }, + persistConfig, { setDates: (_, { dateTo, dateFrom }) => ({ dateTo, @@ -443,21 +446,21 @@ export const webAnalyticsLogic = kea([ ], shouldFilterTestAccounts: [ false as boolean, - { persist: true }, + persistConfig, { setShouldFilterTestAccounts: (_, { shouldFilterTestAccounts }) => shouldFilterTestAccounts, }, ], shouldStripQueryParams: [ false as boolean, - { persist: true }, + persistConfig, { setShouldStripQueryParams: (_, { shouldStripQueryParams }) => shouldStripQueryParams, }, ], conversionGoal: [ null as WebAnalyticsConversionGoal | null, - { persist: true }, + persistConfig, { setConversionGoal: (_, { conversionGoal }) => conversionGoal, }, @@ -1290,7 +1293,7 @@ export const webAnalyticsLogic = kea([ colSpanClassName: 'md:col-span-1', }, query: errorTrackingQuery({ - order: 'users', + orderBy: 'users', dateRange: dateRange, filterTestAccounts: filterTestAccounts, filterGroup: replayFilters.filter_group, diff --git a/mypy-baseline.txt b/mypy-baseline.txt index 4f50be306d64b..87e0aa75b5f0a 100644 --- a/mypy-baseline.txt +++ b/mypy-baseline.txt @@ -1,4 +1,3 @@ -posthog/tasks/exports/ordered_csv_renderer.py:0: error: No return value expected [return-value] posthog/warehouse/models/ssh_tunnel.py:0: error: Incompatible types in assignment (expression has type "NoEncryption", variable has type "BestAvailableEncryption") [assignment] posthog/temporal/data_imports/pipelines/sql_database_v2/schema_types.py:0: error: Statement is unreachable [unreachable] posthog/temporal/data_imports/pipelines/sql_database_v2/schema_types.py:0: error: Non-overlapping equality check (left operand type: "Literal['text', 'double', 'bool', 'timestamp', 'bigint', 'json', 'decimal', 'wei', 'date', 'time'] | None", right operand type: "Literal['interval']") [comparison-overlap] @@ -121,7 +120,6 @@ posthog/hogql/database/schema/groups.py:0: note: Consider using "Mapping" instea posthog/hogql/database/schema/persons.py:0: error: Incompatible types in assignment (expression has type "Organization | None", variable has type "Organization") [assignment] posthog/models/team/team.py:0: error: Statement is unreachable [unreachable] posthog/models/team/team.py:0: error: Statement is unreachable [unreachable] -posthog/models/hog_functions/hog_function.py:0: error: Argument 1 to "get" of "dict" has incompatible type "str | None"; expected "str" [arg-type] posthog/models/user.py:0: error: Incompatible types in assignment (expression has type "type[User]", base class "BaseManager" defined the type as "type[_T]") [assignment] posthog/models/user.py:0: error: Cannot override class variable (previously declared on base class "AbstractBaseUser") with instance variable [misc] posthog/models/user.py:0: error: Incompatible types in assignment (expression has type "None", base class "AbstractUser" defined the type as "CharField[str | int | Combinable, str]") [assignment] @@ -155,6 +153,7 @@ posthog/hogql_queries/legacy_compatibility/filter_to_query.py:0: error: Dict ent posthog/hogql_queries/legacy_compatibility/filter_to_query.py:0: error: Dict entry 0 has incompatible type "str": "StickinessFilter"; expected "str": "TrendsFilter" [dict-item] posthog/session_recordings/models/session_recording.py:0: error: Argument "distinct_id" to "MissingPerson" has incompatible type "str | None"; expected "str" [arg-type] posthog/session_recordings/models/session_recording.py:0: error: Incompatible type for lookup 'persondistinctid__team_id': (got "Team", expected "str | int") [misc] +posthog/models/hog_functions/hog_function.py:0: error: Argument 1 to "get" of "dict" has incompatible type "str | None"; expected "str" [arg-type] ee/tasks/subscriptions/slack_subscriptions.py:0: error: Item "None" of "datetime | None" has no attribute "strftime" [union-attr] posthog/warehouse/models/table.py:0: error: Item "None" of "DataWarehouseCredential | None" has no attribute "access_key" [union-attr] posthog/warehouse/models/table.py:0: error: Item "None" of "DataWarehouseCredential | None" has no attribute "access_secret" [union-attr] @@ -270,7 +269,6 @@ posthog/tasks/update_survey_iteration.py:0: error: Incompatible types in assignm posthog/tasks/update_survey_iteration.py:0: error: Item "None" of "FeatureFlag | None" has no attribute "filters" [union-attr] posthog/tasks/update_survey_iteration.py:0: error: Item "None" of "FeatureFlag | None" has no attribute "filters" [union-attr] posthog/tasks/update_survey_iteration.py:0: error: Item "None" of "FeatureFlag | None" has no attribute "save" [union-attr] -posthog/permissions.py:0: error: Argument 2 to "feature_enabled" has incompatible type "str | None"; expected "str" [arg-type] posthog/models/event/util.py:0: error: Incompatible types in assignment (expression has type "str", variable has type "datetime") [assignment] posthog/models/event/util.py:0: error: Module has no attribute "utc" [attr-defined] posthog/event_usage.py:0: error: Argument 1 to "capture" has incompatible type "str | None"; expected "str" [arg-type] @@ -314,11 +312,7 @@ posthog/tasks/email.py:0: error: Module "django.utils.timezone" does not explici posthog/tasks/email.py:0: error: Argument "email" to "add_recipient" of "EmailMessage" has incompatible type "str | None"; expected "str" [arg-type] posthog/tasks/email.py:0: error: Argument 1 to "capture" has incompatible type "str | None"; expected "str" [arg-type] posthog/tasks/email.py:0: error: Incompatible types in assignment (expression has type "Team | None", variable has type "Team") [assignment] -posthog/api/documentation.py:0: error: Signature of "run_validation" incompatible with supertype "Field" [override] -posthog/api/documentation.py:0: note: Superclass: -posthog/api/documentation.py:0: note: def run_validation(self, data: Any = ...) -> Any -posthog/api/documentation.py:0: note: Subclass: -posthog/api/documentation.py:0: note: def run_validation(self, data: Any) -> Any +posthog/permissions.py:0: error: Argument 2 to "feature_enabled" has incompatible type "str | None"; expected "str" [arg-type] ee/tasks/subscriptions/email_subscriptions.py:0: error: Item "None" of "User | None" has no attribute "email" [union-attr] ee/tasks/subscriptions/email_subscriptions.py:0: error: Item "None" of "datetime | None" has no attribute "isoformat" [union-attr] ee/tasks/subscriptions/email_subscriptions.py:0: error: Item "None" of "datetime | None" has no attribute "strftime" [union-attr] @@ -332,28 +326,33 @@ posthog/models/property/util.py:0: error: Argument 2 to "get_materialized_column posthog/models/property/util.py:0: error: Argument 1 to "append" of "list" has incompatible type "str | int"; expected "str" [arg-type] posthog/models/property/util.py:0: error: Argument 1 to "append" of "list" has incompatible type "str | int"; expected "str" [arg-type] posthog/models/property/util.py:0: error: Argument 1 to "append" of "list" has incompatible type "str | int"; expected "str" [arg-type] -posthog/api/utils.py:0: error: Incompatible types in assignment (expression has type "type[EventDefinition]", variable has type "type[EnterpriseEventDefinition]") [assignment] -posthog/api/utils.py:0: error: Argument 1 to "UUID" has incompatible type "int | str"; expected "str | None" [arg-type] +posthog/api/documentation.py:0: error: Signature of "run_validation" incompatible with supertype "Field" [override] +posthog/api/documentation.py:0: note: Superclass: +posthog/api/documentation.py:0: note: def run_validation(self, data: Any = ...) -> Any +posthog/api/documentation.py:0: note: Subclass: +posthog/api/documentation.py:0: note: def run_validation(self, data: Any) -> Any posthog/queries/trends/util.py:0: error: Argument 1 to "translate_hogql" has incompatible type "str | None"; expected "str" [arg-type] posthog/queries/column_optimizer/foss_column_optimizer.py:0: error: Argument 2 to "get_materialized_column_for_property" has incompatible type "str"; expected "Literal['properties', 'group_properties', 'person_properties']" [arg-type] posthog/hogql/property.py:0: error: Incompatible type for lookup 'id': (got "str | int | list[str]", expected "str | int") [misc] posthog/hogql/property.py:0: error: Incompatible type for lookup 'pk': (got "str | float", expected "str | int") [misc] -posthog/api/capture.py:0: error: Module has no attribute "utc" [attr-defined] +posthog/api/utils.py:0: error: Incompatible types in assignment (expression has type "type[EventDefinition]", variable has type "type[EnterpriseEventDefinition]") [assignment] +posthog/api/utils.py:0: error: Argument 1 to "UUID" has incompatible type "int | str"; expected "str | None" [arg-type] posthog/hogql/filters.py:0: error: Incompatible default for argument "team" (default has type "None", argument has type "Team") [assignment] posthog/hogql/filters.py:0: note: PEP 484 prohibits implicit Optional. Accordingly, mypy has changed its default to no_implicit_optional=True posthog/hogql/filters.py:0: note: Use https://github.com/hauntsaninja/no_implicit_optional to automatically upgrade your codebase -posthog/api/organization.py:0: error: Incompatible return value type (got "int | None", expected "Level | None") [return-value] +posthog/api/capture.py:0: error: Module has no attribute "utc" [attr-defined] posthog/hogql/query.py:0: error: Incompatible types in assignment (expression has type "None", variable has type "str | SelectQuery | SelectSetQuery") [assignment] posthog/hogql/query.py:0: error: Incompatible types in assignment (expression has type "Expr", variable has type "SelectQuery | SelectSetQuery") [assignment] posthog/hogql/query.py:0: error: Argument 1 to "get_default_limit_for_context" has incompatible type "LimitContext | None"; expected "LimitContext" [arg-type] posthog/hogql/query.py:0: error: Subclass of "SelectQuery" and "SelectSetQuery" cannot exist: would have incompatible method signatures [unreachable] +posthog/api/organization.py:0: error: Incompatible return value type (got "int | None", expected "Level | None") [return-value] posthog/queries/person_query.py:0: error: Incompatible type for lookup 'pk': (got "str | int | list[str]", expected "str | int") [misc] -posthog/api/action.py:0: error: Argument 1 to has incompatible type "*tuple[str, ...]"; expected "type[BaseRenderer]" [arg-type] posthog/queries/event_query/event_query.py:0: error: Incompatible type for lookup 'pk': (got "str | int | list[str]", expected "str | int") [misc] posthog/hogql_queries/sessions_timeline_query_runner.py:0: error: Statement is unreachable [unreachable] posthog/hogql_queries/hogql_query_runner.py:0: error: Statement is unreachable [unreachable] posthog/hogql_queries/hogql_query_runner.py:0: error: Incompatible return value type (got "SelectQuery | SelectSetQuery", expected "SelectQuery") [return-value] posthog/hogql_queries/events_query_runner.py:0: error: Statement is unreachable [unreachable] +posthog/api/action.py:0: error: Argument 1 to has incompatible type "*tuple[str, ...]"; expected "type[BaseRenderer]" [arg-type] posthog/queries/breakdown_props.py:0: error: Argument 1 to "translate_hogql" has incompatible type "str | int"; expected "str" [arg-type] posthog/queries/breakdown_props.py:0: error: Incompatible type for lookup 'pk': (got "str | None", expected "str | int") [misc] posthog/queries/breakdown_props.py:0: error: Incompatible return value type (got "str | None", expected "str") [return-value] @@ -574,8 +573,6 @@ posthog/api/test/test_signup.py:0: error: Module "django.utils.timezone" does no posthog/api/test/test_signup.py:0: error: Module "django.utils.timezone" does not explicitly export attribute "datetime" [attr-defined] posthog/api/test/test_preflight.py:0: error: Module "django.utils.timezone" does not explicitly export attribute "datetime" [attr-defined] posthog/api/test/test_preflight.py:0: error: Module "django.utils.timezone" does not explicitly export attribute "datetime" [attr-defined] -posthog/api/test/test_personal_api_keys.py:0: error: Item "None" of "str | None" has no attribute "startswith" [union-attr] -posthog/api/test/test_personal_api_keys.py:0: error: Item "None" of "str | None" has no attribute "startswith" [union-attr] posthog/api/test/test_person.py:0: error: Argument "data" to "get" of "APIClient" has incompatible type "dict[str, object]"; expected "Mapping[str, str | bytes | int | Iterable[str | bytes | int]] | Iterable[tuple[str, str | bytes | int | Iterable[str | bytes | int]]] | None" [arg-type] posthog/api/test/test_organization_domain.py:0: error: Item "None" of "datetime | None" has no attribute "strftime" [union-attr] posthog/api/signup.py:0: error: Argument 1 to "create_user" of "UserManager" has incompatible type "str | None"; expected "str" [arg-type] @@ -722,7 +719,8 @@ posthog/helpers/full_text_search.py:0: error: Incompatible return value type (go posthog/helpers/full_text_search.py:0: error: Argument 1 to "reduce" has incompatible type "Callable[[SearchVector, SearchVector], CombinedExpression]"; expected "Callable[[SearchVector, SearchVector], SearchVector]" [arg-type] posthog/helpers/full_text_search.py:0: error: Incompatible return value type (got "CombinedExpression", expected "SearchVector") [return-value] posthog/async_migrations/test/test_runner.py:0: error: Item "None" of "datetime | None" has no attribute "day" [union-attr] -posthog/api/test/test_survey.py:0: error: Item "None" of "FeatureFlag | None" has no attribute "active" [union-attr] +posthog/api/test/test_personal_api_keys.py:0: error: Item "None" of "str | None" has no attribute "startswith" [union-attr] +posthog/api/test/test_personal_api_keys.py:0: error: Item "None" of "str | None" has no attribute "startswith" [union-attr] posthog/api/test/test_insight.py:0: error: Argument "data" to "get" of "APIClient" has incompatible type "dict[str, object]"; expected "Mapping[str, str | bytes | int | Iterable[str | bytes | int]] | Iterable[tuple[str, str | bytes | int | Iterable[str | bytes | int]]] | None" [arg-type] posthog/api/test/test_insight.py:0: error: Argument "data" to "get" of "APIClient" has incompatible type "dict[str, object]"; expected "Mapping[str, str | bytes | int | Iterable[str | bytes | int]] | Iterable[tuple[str, str | bytes | int | Iterable[str | bytes | int]]] | None" [arg-type] posthog/api/test/test_insight.py:0: error: Argument "data" to "get" of "APIClient" has incompatible type "dict[str, object]"; expected "Mapping[str, str | bytes | int | Iterable[str | bytes | int]] | Iterable[tuple[str, str | bytes | int | Iterable[str | bytes | int]]] | None" [arg-type] @@ -803,6 +801,7 @@ posthog/queries/app_metrics/test/test_app_metrics.py:0: error: Argument 3 to "Ap posthog/queries/app_metrics/test/test_app_metrics.py:0: error: Argument 3 to "AppMetricsErrorDetailsQuery" has incompatible type "AppMetricsRequestSerializer"; expected "AppMetricsErrorsRequestSerializer" [arg-type] posthog/queries/app_metrics/test/test_app_metrics.py:0: error: Argument 3 to "AppMetricsErrorDetailsQuery" has incompatible type "AppMetricsRequestSerializer"; expected "AppMetricsErrorsRequestSerializer" [arg-type] posthog/queries/app_metrics/historical_exports.py:0: error: Argument 1 to "loads" has incompatible type "str | None"; expected "str | bytes | bytearray" [arg-type] +posthog/api/test/test_survey.py:0: error: Item "None" of "FeatureFlag | None" has no attribute "active" [union-attr] posthog/api/test/test_decide.py:0: error: Item "None" of "User | None" has no attribute "toolbar_mode" [union-attr] posthog/api/test/test_decide.py:0: error: Item "None" of "User | None" has no attribute "save" [union-attr] posthog/api/test/test_authentication.py:0: error: Module has no attribute "utc" [attr-defined] diff --git a/package.json b/package.json index fc88d73076ccf..ca06eb6507b3d 100644 --- a/package.json +++ b/package.json @@ -132,6 +132,7 @@ "expr-eval": "^2.0.2", "express": "^4.17.1", "fast-deep-equal": "^3.1.3", + "fastpriorityqueue": "^0.7.5", "fflate": "^0.7.4", "fs-extra": "^10.0.0", "fuse.js": "^6.6.2", diff --git a/plugin-server/src/utils/utils.ts b/plugin-server/src/utils/utils.ts index bae7b6e78bc38..ccaf793c21c93 100644 --- a/plugin-server/src/utils/utils.ts +++ b/plugin-server/src/utils/utils.ts @@ -544,7 +544,6 @@ export const KNOWN_LIB_VALUES = new Set([ 'posthog-python', '', 'js', - 'posthog-js-lite', 'posthog-node', 'posthog-react-native', 'posthog-ruby', diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 46b9d8ad2f29c..30cdc4508d7f0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -217,6 +217,9 @@ dependencies: fast-deep-equal: specifier: ^3.1.3 version: 3.1.3 + fastpriorityqueue: + specifier: ^0.7.5 + version: 0.7.5 fflate: specifier: ^0.7.4 version: 0.7.4 @@ -12767,6 +12770,10 @@ packages: engines: {node: '>= 4.9.1'} dev: true + /fastpriorityqueue@0.7.5: + resolution: {integrity: sha512-3Pa0n9gwy8yIbEsT3m2j/E9DXgWvvjfiZjjqcJ+AdNKTAlVMIuFYrYG5Y3RHEM8O6cwv9hOpOWY/NaMfywoQVA==} + dev: false + /fastq@1.13.0: resolution: {integrity: sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==} dependencies: diff --git a/posthog/api/organization_domain.py b/posthog/api/organization_domain.py index e39b611c1fb32..d563c5dd3edf3 100644 --- a/posthog/api/organization_domain.py +++ b/posthog/api/organization_domain.py @@ -1,5 +1,6 @@ import re from typing import Any, cast +import posthoganalytics from rest_framework import exceptions, request, response, serializers from posthog.api.utils import action @@ -11,10 +12,29 @@ from posthog.models import OrganizationDomain from posthog.models.organization import Organization from posthog.permissions import OrganizationAdminWritePermissions +from posthog.event_usage import groups DOMAIN_REGEX = r"^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$" +def _capture_domain_event(request, domain: OrganizationDomain, event_type: str, properties: dict | None = None) -> None: + if not properties: + properties = {} + + properties.update( + { + "domain": domain.domain, + } + ) + + posthoganalytics.capture( + request.user.distinct_id, + f"organization domain {event_type}", + properties=properties, + groups=groups(domain.organization), + ) + + class OrganizationDomainSerializer(serializers.ModelSerializer): UPDATE_ONLY_WHEN_VERIFIED = ["jit_provisioning_enabled", "sso_enforcement"] @@ -96,3 +116,38 @@ def verify(self, request: request.Request, **kw) -> response.Response: serializer = self.get_serializer(instance=instance) return response.Response(serializer.data) + + def create(self, request: request.Request, *args: Any, **kwargs: Any) -> response.Response: + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + instance = serializer.save() + + _capture_domain_event( + request, + instance, + "created", + properties={ + "jit_provisioning_enabled": instance.jit_provisioning_enabled, + "sso_enforcement": instance.sso_enforcement or None, + }, + ) + + return response.Response(serializer.data, status=201) + + def destroy(self, request: request.Request, *args: Any, **kwargs: Any) -> response.Response: + instance = self.get_object() + + _capture_domain_event( + request, + instance, + "deleted", + properties={ + "is_verified": instance.is_verified, + "had_saml": instance.has_saml, + "had_jit_provisioning": instance.jit_provisioning_enabled, + "had_sso_enforcement": bool(instance.sso_enforcement), + }, + ) + + instance.delete() + return response.Response(status=204) diff --git a/posthog/api/proxy_record.py b/posthog/api/proxy_record.py index 8c8f9d488f07b..552835aa9562f 100644 --- a/posthog/api/proxy_record.py +++ b/posthog/api/proxy_record.py @@ -25,6 +25,20 @@ def generate_target_cname(organization_id, domain) -> str: return f"{digest}.{settings.PROXY_BASE_CNAME}" +def _capture_proxy_event(request, record: ProxyRecord, event_type: str) -> None: + organization = Organization.objects.get(id=record.organization_id) + posthoganalytics.capture( + request.user.distinct_id, + f"managed reverse proxy {event_type}", + properties={ + "proxy_record_id": record.id, + "domain": record.domain, + "target_cname": record.target_cname, + }, + groups=groups(organization), + ) + + class ProxyRecordSerializer(serializers.ModelSerializer): class Meta: model = ProxyRecord @@ -79,17 +93,7 @@ def create(self, request, *args, **kwargs): ) serializer = self.get_serializer(record) - organization = Organization.objects.get(id=record.organization_id) - posthoganalytics.capture( - request.user.distinct_id, - "managed reverse proxy created", - properties={ - "proxy_record_id": record.id, - "domain": record.domain, - "target_cname": record.target_cname, - }, - groups=groups(organization), - ) + _capture_proxy_event(request, record, "created") return Response(serializer.data) def destroy(self, request, *args, pk=None, **kwargs): @@ -120,6 +124,8 @@ def destroy(self, request, *args, pk=None, **kwargs): record.status = ProxyRecord.Status.DELETING record.save() + _capture_proxy_event(request, record, "deleted") + return Response( {"success": True}, status=status.HTTP_200_OK, diff --git a/posthog/api/test/__snapshots__/test_action.ambr b/posthog/api/test/__snapshots__/test_action.ambr index 2b453c3a24b20..8ac1823a033c1 100644 --- a/posthog/api/test/__snapshots__/test_action.ambr +++ b/posthog/api/test/__snapshots__/test_action.ambr @@ -156,12 +156,12 @@ LEFT OUTER JOIN "posthog_organizationmembership" ON ("ee_accesscontrol"."organization_member_id" = "posthog_organizationmembership"."id") WHERE (("ee_accesscontrol"."organization_member_id" IS NULL AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '99' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("posthog_organizationmembership"."user_id" = 99999 AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '99' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("ee_accesscontrol"."organization_member_id" IS NULL @@ -471,12 +471,12 @@ LEFT OUTER JOIN "posthog_organizationmembership" ON ("ee_accesscontrol"."organization_member_id" = "posthog_organizationmembership"."id") WHERE (("ee_accesscontrol"."organization_member_id" IS NULL AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '99' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("posthog_organizationmembership"."user_id" = 99999 AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '99' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("ee_accesscontrol"."organization_member_id" IS NULL @@ -667,12 +667,12 @@ LEFT OUTER JOIN "posthog_organizationmembership" ON ("ee_accesscontrol"."organization_member_id" = "posthog_organizationmembership"."id") WHERE (("ee_accesscontrol"."organization_member_id" IS NULL AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '99' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("posthog_organizationmembership"."user_id" = 99999 AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '99' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("ee_accesscontrol"."organization_member_id" IS NULL diff --git a/posthog/api/test/__snapshots__/test_annotation.ambr b/posthog/api/test/__snapshots__/test_annotation.ambr index 457607fc74fc8..9340e03a2a4d8 100644 --- a/posthog/api/test/__snapshots__/test_annotation.ambr +++ b/posthog/api/test/__snapshots__/test_annotation.ambr @@ -144,12 +144,12 @@ LEFT OUTER JOIN "posthog_organizationmembership" ON ("ee_accesscontrol"."organization_member_id" = "posthog_organizationmembership"."id") WHERE (("ee_accesscontrol"."organization_member_id" IS NULL AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '107' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("posthog_organizationmembership"."user_id" = 99999 AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '107' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("ee_accesscontrol"."organization_member_id" IS NULL @@ -454,12 +454,12 @@ LEFT OUTER JOIN "posthog_organizationmembership" ON ("ee_accesscontrol"."organization_member_id" = "posthog_organizationmembership"."id") WHERE (("ee_accesscontrol"."organization_member_id" IS NULL AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '107' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("posthog_organizationmembership"."user_id" = 99999 AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '107' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("ee_accesscontrol"."organization_member_id" IS NULL @@ -669,12 +669,12 @@ LEFT OUTER JOIN "posthog_organizationmembership" ON ("ee_accesscontrol"."organization_member_id" = "posthog_organizationmembership"."id") WHERE (("ee_accesscontrol"."organization_member_id" IS NULL AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '107' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("posthog_organizationmembership"."user_id" = 99999 AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '107' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("ee_accesscontrol"."organization_member_id" IS NULL diff --git a/posthog/api/test/__snapshots__/test_decide.ambr b/posthog/api/test/__snapshots__/test_decide.ambr index beb108518df40..a1f1ebcced5c8 100644 --- a/posthog/api/test/__snapshots__/test_decide.ambr +++ b/posthog/api/test/__snapshots__/test_decide.ambr @@ -754,12 +754,12 @@ INNER JOIN "posthog_team" ON ("ee_accesscontrol"."team_id" = "posthog_team"."id") WHERE (("ee_accesscontrol"."organization_member_id" IS NULL AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '253' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("posthog_organizationmembership"."user_id" = 99999 AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '253' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("ee_accesscontrol"."organization_member_id" IS NULL diff --git a/posthog/api/test/__snapshots__/test_element.ambr b/posthog/api/test/__snapshots__/test_element.ambr index 414a8a1831062..e3ce7d60cebca 100644 --- a/posthog/api/test/__snapshots__/test_element.ambr +++ b/posthog/api/test/__snapshots__/test_element.ambr @@ -151,12 +151,12 @@ LEFT OUTER JOIN "posthog_organizationmembership" ON ("ee_accesscontrol"."organization_member_id" = "posthog_organizationmembership"."id") WHERE (("ee_accesscontrol"."organization_member_id" IS NULL AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '272' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("posthog_organizationmembership"."user_id" = 99999 AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '272' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("ee_accesscontrol"."organization_member_id" IS NULL diff --git a/posthog/api/test/__snapshots__/test_feature_flag.ambr b/posthog/api/test/__snapshots__/test_feature_flag.ambr index 93dfa76ea2cdb..b51af7a796f7d 100644 --- a/posthog/api/test/__snapshots__/test_feature_flag.ambr +++ b/posthog/api/test/__snapshots__/test_feature_flag.ambr @@ -2001,12 +2001,12 @@ LEFT OUTER JOIN "posthog_organizationmembership" ON ("ee_accesscontrol"."organization_member_id" = "posthog_organizationmembership"."id") WHERE (("ee_accesscontrol"."organization_member_id" IS NULL AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '313' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("posthog_organizationmembership"."user_id" = 99999 AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '313' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("ee_accesscontrol"."organization_member_id" IS NULL @@ -2021,12 +2021,12 @@ AND "ee_accesscontrol"."team_id" = 99999) OR ("ee_accesscontrol"."organization_member_id" IS NULL AND "ee_accesscontrol"."resource" = 'feature_flag' - AND "ee_accesscontrol"."resource_id" = '130' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("posthog_organizationmembership"."user_id" = 99999 AND "ee_accesscontrol"."resource" = 'feature_flag' - AND "ee_accesscontrol"."resource_id" = '130' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999)) ''' diff --git a/posthog/api/test/__snapshots__/test_insight.ambr b/posthog/api/test/__snapshots__/test_insight.ambr index cea3c1e49dcab..01390b5f4b341 100644 --- a/posthog/api/test/__snapshots__/test_insight.ambr +++ b/posthog/api/test/__snapshots__/test_insight.ambr @@ -1380,12 +1380,12 @@ LEFT OUTER JOIN "posthog_organizationmembership" ON ("ee_accesscontrol"."organization_member_id" = "posthog_organizationmembership"."id") WHERE (("ee_accesscontrol"."organization_member_id" IS NULL AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '447' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("posthog_organizationmembership"."user_id" = 99999 AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '447' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("ee_accesscontrol"."organization_member_id" IS NULL @@ -1493,12 +1493,12 @@ LEFT OUTER JOIN "posthog_organizationmembership" ON ("ee_accesscontrol"."organization_member_id" = "posthog_organizationmembership"."id") WHERE (("ee_accesscontrol"."organization_member_id" IS NULL AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '447' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("posthog_organizationmembership"."user_id" = 99999 AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '447' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("ee_accesscontrol"."organization_member_id" IS NULL diff --git a/posthog/api/test/dashboards/__snapshots__/test_dashboard.ambr b/posthog/api/test/dashboards/__snapshots__/test_dashboard.ambr index 7d964fa88087f..dfd916657a89b 100644 --- a/posthog/api/test/dashboards/__snapshots__/test_dashboard.ambr +++ b/posthog/api/test/dashboards/__snapshots__/test_dashboard.ambr @@ -332,12 +332,12 @@ LEFT OUTER JOIN "posthog_organizationmembership" ON ("ee_accesscontrol"."organization_member_id" = "posthog_organizationmembership"."id") WHERE (("ee_accesscontrol"."organization_member_id" IS NULL AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '76' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("posthog_organizationmembership"."user_id" = 99999 AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '76' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("ee_accesscontrol"."organization_member_id" IS NULL @@ -352,12 +352,12 @@ AND "ee_accesscontrol"."team_id" = 99999) OR ("ee_accesscontrol"."organization_member_id" IS NULL AND "ee_accesscontrol"."resource" = 'dashboard' - AND "ee_accesscontrol"."resource_id" = '1' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("posthog_organizationmembership"."user_id" = 99999 AND "ee_accesscontrol"."resource" = 'dashboard' - AND "ee_accesscontrol"."resource_id" = '1' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999)) ''' @@ -1057,12 +1057,12 @@ LEFT OUTER JOIN "posthog_organizationmembership" ON ("ee_accesscontrol"."organization_member_id" = "posthog_organizationmembership"."id") WHERE (("ee_accesscontrol"."organization_member_id" IS NULL AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '76' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("posthog_organizationmembership"."user_id" = 99999 AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '76' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("ee_accesscontrol"."organization_member_id" IS NULL @@ -2298,12 +2298,12 @@ LEFT OUTER JOIN "posthog_organizationmembership" ON ("ee_accesscontrol"."organization_member_id" = "posthog_organizationmembership"."id") WHERE (("ee_accesscontrol"."organization_member_id" IS NULL AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '76' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("posthog_organizationmembership"."user_id" = 99999 AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '76' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("ee_accesscontrol"."organization_member_id" IS NULL @@ -2402,12 +2402,12 @@ LEFT OUTER JOIN "posthog_organizationmembership" ON ("ee_accesscontrol"."organization_member_id" = "posthog_organizationmembership"."id") WHERE (("ee_accesscontrol"."organization_member_id" IS NULL AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '76' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("posthog_organizationmembership"."user_id" = 99999 AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '76' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("ee_accesscontrol"."organization_member_id" IS NULL @@ -2530,52 +2530,52 @@ LEFT OUTER JOIN "posthog_organizationmembership" ON ("ee_accesscontrol"."organization_member_id" = "posthog_organizationmembership"."id") WHERE (("ee_accesscontrol"."organization_member_id" IS NULL AND "ee_accesscontrol"."resource" = 'dashboard' - AND "ee_accesscontrol"."resource_id" = '55' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("posthog_organizationmembership"."user_id" = 99999 AND "ee_accesscontrol"."resource" = 'dashboard' - AND "ee_accesscontrol"."resource_id" = '55' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("ee_accesscontrol"."organization_member_id" IS NULL AND "ee_accesscontrol"."resource" = 'dashboard' - AND "ee_accesscontrol"."resource_id" = '56' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("posthog_organizationmembership"."user_id" = 99999 AND "ee_accesscontrol"."resource" = 'dashboard' - AND "ee_accesscontrol"."resource_id" = '56' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("ee_accesscontrol"."organization_member_id" IS NULL AND "ee_accesscontrol"."resource" = 'dashboard' - AND "ee_accesscontrol"."resource_id" = '57' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("posthog_organizationmembership"."user_id" = 99999 AND "ee_accesscontrol"."resource" = 'dashboard' - AND "ee_accesscontrol"."resource_id" = '57' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("ee_accesscontrol"."organization_member_id" IS NULL AND "ee_accesscontrol"."resource" = 'dashboard' - AND "ee_accesscontrol"."resource_id" = '58' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("posthog_organizationmembership"."user_id" = 99999 AND "ee_accesscontrol"."resource" = 'dashboard' - AND "ee_accesscontrol"."resource_id" = '58' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("ee_accesscontrol"."organization_member_id" IS NULL AND "ee_accesscontrol"."resource" = 'dashboard' - AND "ee_accesscontrol"."resource_id" = '59' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("posthog_organizationmembership"."user_id" = 99999 AND "ee_accesscontrol"."resource" = 'dashboard' - AND "ee_accesscontrol"."resource_id" = '59' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999)) ''' @@ -3459,12 +3459,12 @@ LEFT OUTER JOIN "posthog_organizationmembership" ON ("ee_accesscontrol"."organization_member_id" = "posthog_organizationmembership"."id") WHERE (("ee_accesscontrol"."organization_member_id" IS NULL AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '76' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("posthog_organizationmembership"."user_id" = 99999 AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '76' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("ee_accesscontrol"."organization_member_id" IS NULL @@ -4634,12 +4634,12 @@ LEFT OUTER JOIN "posthog_organizationmembership" ON ("ee_accesscontrol"."organization_member_id" = "posthog_organizationmembership"."id") WHERE (("ee_accesscontrol"."organization_member_id" IS NULL AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '76' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("posthog_organizationmembership"."user_id" = 99999 AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '76' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("ee_accesscontrol"."organization_member_id" IS NULL @@ -4654,12 +4654,12 @@ AND "ee_accesscontrol"."team_id" = 99999) OR ("ee_accesscontrol"."organization_member_id" IS NULL AND "ee_accesscontrol"."resource" = 'dashboard' - AND "ee_accesscontrol"."resource_id" = '60' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("posthog_organizationmembership"."user_id" = 99999 AND "ee_accesscontrol"."resource" = 'dashboard' - AND "ee_accesscontrol"."resource_id" = '60' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999)) ''' @@ -5590,12 +5590,12 @@ LEFT OUTER JOIN "posthog_organizationmembership" ON ("ee_accesscontrol"."organization_member_id" = "posthog_organizationmembership"."id") WHERE (("ee_accesscontrol"."organization_member_id" IS NULL AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '76' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("posthog_organizationmembership"."user_id" = 99999 AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '76' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("ee_accesscontrol"."organization_member_id" IS NULL @@ -6140,12 +6140,12 @@ LEFT OUTER JOIN "posthog_organizationmembership" ON ("ee_accesscontrol"."organization_member_id" = "posthog_organizationmembership"."id") WHERE (("ee_accesscontrol"."organization_member_id" IS NULL AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '76' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("posthog_organizationmembership"."user_id" = 99999 AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '76' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("ee_accesscontrol"."organization_member_id" IS NULL @@ -6220,12 +6220,12 @@ LEFT OUTER JOIN "posthog_organizationmembership" ON ("ee_accesscontrol"."organization_member_id" = "posthog_organizationmembership"."id") WHERE (("ee_accesscontrol"."organization_member_id" IS NULL AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '76' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("posthog_organizationmembership"."user_id" = 99999 AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '76' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("ee_accesscontrol"."organization_member_id" IS NULL @@ -7580,12 +7580,12 @@ LEFT OUTER JOIN "posthog_organizationmembership" ON ("ee_accesscontrol"."organization_member_id" = "posthog_organizationmembership"."id") WHERE (("ee_accesscontrol"."organization_member_id" IS NULL AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '76' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("posthog_organizationmembership"."user_id" = 99999 AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '76' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("ee_accesscontrol"."organization_member_id" IS NULL @@ -8592,12 +8592,12 @@ LEFT OUTER JOIN "posthog_organizationmembership" ON ("ee_accesscontrol"."organization_member_id" = "posthog_organizationmembership"."id") WHERE (("ee_accesscontrol"."organization_member_id" IS NULL AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '76' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("posthog_organizationmembership"."user_id" = 99999 AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '76' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("ee_accesscontrol"."organization_member_id" IS NULL @@ -9328,12 +9328,12 @@ LEFT OUTER JOIN "posthog_organizationmembership" ON ("ee_accesscontrol"."organization_member_id" = "posthog_organizationmembership"."id") WHERE (("ee_accesscontrol"."organization_member_id" IS NULL AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '76' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("posthog_organizationmembership"."user_id" = 99999 AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '76' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("ee_accesscontrol"."organization_member_id" IS NULL @@ -9348,12 +9348,12 @@ AND "ee_accesscontrol"."team_id" = 99999) OR ("ee_accesscontrol"."organization_member_id" IS NULL AND "ee_accesscontrol"."resource" = 'dashboard' - AND "ee_accesscontrol"."resource_id" = '67' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("posthog_organizationmembership"."user_id" = 99999 AND "ee_accesscontrol"."resource" = 'dashboard' - AND "ee_accesscontrol"."resource_id" = '67' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999)) ''' @@ -10348,12 +10348,12 @@ LEFT OUTER JOIN "posthog_organizationmembership" ON ("ee_accesscontrol"."organization_member_id" = "posthog_organizationmembership"."id") WHERE (("ee_accesscontrol"."organization_member_id" IS NULL AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '76' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("posthog_organizationmembership"."user_id" = 99999 AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '76' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("ee_accesscontrol"."organization_member_id" IS NULL @@ -10898,12 +10898,12 @@ LEFT OUTER JOIN "posthog_organizationmembership" ON ("ee_accesscontrol"."organization_member_id" = "posthog_organizationmembership"."id") WHERE (("ee_accesscontrol"."organization_member_id" IS NULL AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '76' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("posthog_organizationmembership"."user_id" = 99999 AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '76' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("ee_accesscontrol"."organization_member_id" IS NULL @@ -10978,12 +10978,12 @@ LEFT OUTER JOIN "posthog_organizationmembership" ON ("ee_accesscontrol"."organization_member_id" = "posthog_organizationmembership"."id") WHERE (("ee_accesscontrol"."organization_member_id" IS NULL AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '76' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("posthog_organizationmembership"."user_id" = 99999 AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '76' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("ee_accesscontrol"."organization_member_id" IS NULL @@ -11186,22 +11186,22 @@ LEFT OUTER JOIN "posthog_organizationmembership" ON ("ee_accesscontrol"."organization_member_id" = "posthog_organizationmembership"."id") WHERE (("ee_accesscontrol"."organization_member_id" IS NULL AND "ee_accesscontrol"."resource" = 'dashboard' - AND "ee_accesscontrol"."resource_id" = '69' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("posthog_organizationmembership"."user_id" = 99999 AND "ee_accesscontrol"."resource" = 'dashboard' - AND "ee_accesscontrol"."resource_id" = '69' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("ee_accesscontrol"."organization_member_id" IS NULL AND "ee_accesscontrol"."resource" = 'dashboard' - AND "ee_accesscontrol"."resource_id" = '70' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("posthog_organizationmembership"."user_id" = 99999 AND "ee_accesscontrol"."resource" = 'dashboard' - AND "ee_accesscontrol"."resource_id" = '70' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999)) ''' diff --git a/posthog/api/test/notebooks/__snapshots__/test_notebook.ambr b/posthog/api/test/notebooks/__snapshots__/test_notebook.ambr index 6527545701a76..f585776717839 100644 --- a/posthog/api/test/notebooks/__snapshots__/test_notebook.ambr +++ b/posthog/api/test/notebooks/__snapshots__/test_notebook.ambr @@ -110,12 +110,12 @@ LEFT OUTER JOIN "posthog_organizationmembership" ON ("ee_accesscontrol"."organization_member_id" = "posthog_organizationmembership"."id") WHERE (("ee_accesscontrol"."organization_member_id" IS NULL AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '83' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("posthog_organizationmembership"."user_id" = 99999 AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '83' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("ee_accesscontrol"."organization_member_id" IS NULL @@ -552,12 +552,12 @@ LEFT OUTER JOIN "posthog_organizationmembership" ON ("ee_accesscontrol"."organization_member_id" = "posthog_organizationmembership"."id") WHERE (("ee_accesscontrol"."organization_member_id" IS NULL AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '83' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("posthog_organizationmembership"."user_id" = 99999 AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '83' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("ee_accesscontrol"."organization_member_id" IS NULL @@ -719,12 +719,12 @@ LEFT OUTER JOIN "posthog_organizationmembership" ON ("ee_accesscontrol"."organization_member_id" = "posthog_organizationmembership"."id") WHERE (("ee_accesscontrol"."organization_member_id" IS NULL AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '83' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("posthog_organizationmembership"."user_id" = 99999 AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '83' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("ee_accesscontrol"."organization_member_id" IS NULL diff --git a/posthog/api/test/test_organization_domain.py b/posthog/api/test/test_organization_domain.py index 82ded3bc1c7fe..33de9f45facfc 100644 --- a/posthog/api/test/test_organization_domain.py +++ b/posthog/api/test/test_organization_domain.py @@ -1,5 +1,5 @@ import datetime -from unittest.mock import patch +from unittest.mock import ANY, patch from zoneinfo import ZoneInfo import dns.resolver @@ -84,7 +84,8 @@ def test_cannot_list_or_retrieve_domains_for_other_org(self): # Create domains - def test_create_domain(self): + @patch("posthoganalytics.capture") + def test_create_domain(self, mock_capture): self.organization_membership.level = OrganizationMembership.Level.ADMIN self.organization.available_product_features = [ {"key": "automatic_provisioning", "name": "automatic_provisioning"} @@ -116,6 +117,18 @@ def test_create_domain(self): self.assertEqual(instance.last_verification_retry, None) self.assertEqual(instance.sso_enforcement, "") + # Verify the domain creation capture event was called + mock_capture.assert_any_call( + self.user.distinct_id, + "organization domain created", + properties={ + "domain": "the.posthog.com", + "jit_provisioning_enabled": False, + "sso_enforcement": None, + }, + groups={"instance": ANY, "organization": str(self.organization.id)}, + ) + def test_cant_create_domain_without_feature(self): self.organization_membership.level = OrganizationMembership.Level.ADMIN self.organization_membership.save() @@ -439,7 +452,8 @@ def test_cannot_update_domain_for_another_org(self): # Delete domains - def test_admin_can_delete_domain(self): + @patch("posthoganalytics.capture") + def test_admin_can_delete_domain(self, mock_capture): self.organization_membership.level = OrganizationMembership.Level.ADMIN self.organization_membership.save() @@ -449,6 +463,20 @@ def test_admin_can_delete_domain(self): self.assertFalse(OrganizationDomain.objects.filter(id=self.domain.id).exists()) + # Verify the domain deletion capture event was called + mock_capture.assert_any_call( + self.user.distinct_id, + "organization domain deleted", + properties={ + "domain": "myposthog.com", + "is_verified": False, + "had_saml": False, + "had_jit_provisioning": False, + "had_sso_enforcement": False, + }, + groups={"instance": ANY, "organization": str(self.organization.id)}, + ) + def test_only_admin_can_delete_domain(self): response = self.client.delete(f"/api/organizations/@current/domains/{self.domain.id}") self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) diff --git a/posthog/cdp/templates/__init__.py b/posthog/cdp/templates/__init__.py index a19d35a837a36..3434fab954d16 100644 --- a/posthog/cdp/templates/__init__.py +++ b/posthog/cdp/templates/__init__.py @@ -40,12 +40,17 @@ ) from .airtable.template_airtable import template as airtable from .brevo.template_brevo import template as brevo +from ._siteapps.template_early_access_features import template as early_access_features +from ._siteapps.template_hogdesk import template as hogdesk +from ._siteapps.template_notification_bar import template as notification_bar +from ._siteapps.template_pineapple_mode import template as pineapple_mode from ._internal.template_broadcast import template_new_broadcast as _broadcast -from ._internal.template_blank import blank_site_destination +from ._internal.template_blank import blank_site_destination, blank_site_app HOG_FUNCTION_TEMPLATES = [ _broadcast, blank_site_destination, + blank_site_app, slack, webhook, activecampaign, @@ -87,6 +92,10 @@ sendgrid, zapier, zendesk, + early_access_features, + hogdesk, + notification_bar, + pineapple_mode, ] diff --git a/posthog/cdp/templates/_internal/template_blank.py b/posthog/cdp/templates/_internal/template_blank.py index 88eb00f2ef582..4f141ad9fc3e8 100644 --- a/posthog/cdp/templates/_internal/template_blank.py +++ b/posthog/cdp/templates/_internal/template_blank.py @@ -51,3 +51,27 @@ }, ], ) + +blank_site_app: HogFunctionTemplate = HogFunctionTemplate( + status="client-side", + type="site_app", + id="template-blank-site-app", + name="New site app", + description="Run custom JavaScript on your website. Works only with posthog-js when opt_in_site_apps is set to true.", + icon_url="/static/hedgehog/builder-hog-03.png", + category=["Custom", "Analytics"], + hog=""" +export function onLoad({ inputs, posthog }) { + console.log(`Hello ${inputs.name} from your new Site App!`) +} +""".strip(), + inputs_schema=[ + { + "key": "name", + "type": "string", + "label": "Name", + "description": "What's your name?", + "default": "Max", + }, + ], +) diff --git a/posthog/cdp/templates/_siteapps/template_early_access_features.py b/posthog/cdp/templates/_siteapps/template_early_access_features.py new file mode 100644 index 0000000000000..8031e437ed542 --- /dev/null +++ b/posthog/cdp/templates/_siteapps/template_early_access_features.py @@ -0,0 +1,427 @@ +from posthog.cdp.templates.hog_function_template import HogFunctionTemplate + +template: HogFunctionTemplate = HogFunctionTemplate( + status="client-side", + type="site_app", + id="template-early-access-features", + name="Early Access Features App", + description="This app is used with Early Access Feature Management", + icon_url="https://raw.githubusercontent.com/PostHog/early-access-features-app/refs/heads/main/logo.png", + category=["Custom"], + hog=""" +const style = (inputs) => ` + .list-container { + flex: 1; + flex-direction: row; + overflow-y: auto; + } + + .info { + flex: 2; + } + + .list-item { + padding: 15px 30px; + height: 35%; + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + border-bottom: 1px solid #00000026; + + .list-item-name { + font-size: 18px; + } + + .list-item-description { + font-size: 14px; + } + + .list-item-documentation-link { + margin-top: 15px; + + .label { + text-decoration: none; + } + } + } + + .list-content { + margin-right: 20px; + } + + .beta-feature-button { + position: fixed; + bottom: 20px; + right: 20px; + font-weight: normal; + font-family: -apple-system, BlinkMacSystemFont, "Inter", "Segoe UI", "Roboto", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + text-align: left; + z-index: ${parseInt(inputs.zIndex) || 99999}; + display: flex; + justify-content: center; + align-items: center; + } + + .top-section { + padding: 15px 30px; + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + border-bottom: 1px solid #00000026; + } + + .beta-list-cancel { + cursor: pointer; + } + + .title { + font-size: 16px; + font-weight: bold; + } + + .popup { + position: fixed; + top: 50%; + left: 50%; + color: black; + transform: translate(-50%, -50%); + font-weight: normal; + font-family: -apple-system, BlinkMacSystemFont, "Inter", "Segoe UI", "Roboto", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + text-align: left; + z-index: ${parseInt(inputs.zIndex) || 99999}; + + display: none; + flex-direction: column; + background: white; + border: 1px solid #f0f0f0; + border-radius: 8px; + padding-top: 5px; + width: 40rem; + height: 50%; + box-shadow: -6px 0 16px -8px rgb(0 0 0 / 8%), -9px 0 28px 0 rgb(0 0 0 / 5%), -12px 0 48px 16px rgb(0 0 0 / 3%); + } + + .beta-feature-button { + width: 64px; + height: 64px; + border-radius: 100%; + text-align: center; + line-height: 60px; + font-size: 32px; + border: none; + cursor: pointer; + } + .beta-feature-button:hover { + filter: brightness(1.2); + } + + .empty-prompt { + flex: 1; + text-align: center; + margin-top: 20px; + } + + /* The switch - the box around the slider */ + .switch { + margin-left: 10px; + margin-right: 10px; + position: relative; + display: inline-block; + min-width: 50px; + height: 24px; + } + + /* Hide default HTML checkbox */ + .switch input { + opacity: 0; + width: 0; + height: 0; + } + + /* The slider */ + .slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: #00000026; + -webkit-transition: .4s; + transition: background-color .4s; + cursor: pointer; + } + + .slider:before { + position: absolute; + content: ""; + height: 20px; + width: 20px; + left: -10px; + bottom: -6px; + background-color: #ffffff; + -webkit-transition: .2s; + transition: .2s; + border: 2px solid #00000026; + } + + input:checked + .slider { + background-color: #00000026; + } + + input:focus + .slider { + box-shadow: 0 0 1px #00000026; + } + + input:checked + .slider:before { + -webkit-transform: translateX(26px); + -ms-transform: translateX(26px); + transform: translateX(26px); + background-color: #1d4aff; + } + + /* Rounded sliders */ + .slider.round { + border-radius: 20px; + height: 10px; + width: 30px; + background-color: #00000026; + } + + .slider.round:before { + border-radius: 50%; + } + + .loader-container { + display: flex; + justify-content: center; + align-items: center; + height: 50%; + width: 100%; + } + + .loader { + border: 8px solid #00000026; /* Light grey */ + border-top: 8px solid #1d4aff; /* Blue */ + border-radius: 50%; + width: 60px; + height: 60px; + animation: spin 2s linear infinite; + } + + @keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } + } +` + +interface PreviewItem { + name: string + description: string + flagKey: string + documentationUrl: string +} + +export function onLoad({ inputs, posthog }) { + if (inputs.domains) { + const domains = inputs.domains.split(',').map((domain) => domain.trim()) + if (domains.length > 0 && domains.indexOf(window.location.hostname) === -1) { + return + } + } + const shadow = createShadow(style(inputs)) + + function optIn(flagKey: string) { + posthog.updateEarlyAccessFeatureEnrollment(flagKey, true) + } + + function optOut(flagKey: string) { + posthog.updateEarlyAccessFeatureEnrollment(flagKey, false) + } + + function openbugBox() { + posthog.getEarlyAccessFeatures((previewItemData) => { + const betaListContainer = shadow.getElementById('list-container') + if (betaListContainer) { + const previewItems = listItemComponents(previewItemData) + const previewList = previewItems + ? ` +
+ ${previewItems} +
+ ` + : ` +
+ No beta features available +
+ ` + betaListContainer.innerHTML = previewList + + previewItemData.forEach((item, index) => { + const checkbox = shadow.querySelector('.checkbox-' + index) + checkbox?.addEventListener('click', (e) => { + if (e.target?.checked) { + optIn(item.flagKey) + } else { + optOut(item.flagKey) + } + }) + }) + } + }, true) // Force reload always + + Object.assign(listElement.style, { display: 'flex' }) + + const closeButton = shadow.querySelector('.beta-list-cancel') + closeButton?.addEventListener('click', (e) => { + e.preventDefault() + Object.assign(listElement.style, { display: 'none' }) + }) + + // // Hide when clicked outside + // const _betaList = document.getElementById('beta-list') + // document.addEventListener('click', function(event) { + // const isClickInside = _betaList?.contains(event.target) + + // if (!isClickInside) { + // // Object.assign(formElement.style, { display: 'none' }) + // } + // }); + } + + // TODO: Make this button a inputs option + const buttonElement = Object.assign(document.createElement('button'), { + className: 'beta-feature-button', + onclick: openbugBox, + title: inputs.buttonTitle || '', + }) + + buttonElement.innerHTML = ` + + + + + + ` + + Object.assign(buttonElement.style, { + color: inputs.buttonColor || 'white', + background: inputs.buttonBackground || '#1d4aff', + }) + + if (inputs.useButton === 'Yes') { + shadow.appendChild(buttonElement) + } + + const CloseButtonComponent = (width: number, height: number) => ` + + + + ` + + const BetaListComponent = ` +
+
Enable beta features
+
+ ${CloseButtonComponent(30, 30)} +
+
+
+
+
+
+
+ ` + + const betaListElement = document.createElement('div') + betaListElement.id = 'beta-list' + const listElement = Object.assign(betaListElement, { + className: 'popup', + innerHTML: BetaListComponent, + }) + + shadow.appendChild(listElement) + + if (inputs.selector) { + const clickListener = (e) => { + if (e.target.closest(inputs.selector)) { + openbugBox() + } + } + window.addEventListener('click', clickListener) + } + + const listItemComponents = (items?: PreviewItem[]) => { + if (items) { + return items + .map((item, index) => { + const checked = posthog.isFeatureEnabled(item.flagKey) + + const documentationLink = item.documentationUrl + ? ` + ` + : '' + return ` +
+
+ ${item.name} +
${item.description}
+ ${documentationLink} +
+ +
+ ` + }) + .join('') + } + return '' + } +} + +function createShadow(style?: string): ShadowRoot { + const div = document.createElement('div') + const shadow = div.attachShadow({ mode: 'open' }) + if (style) { + const styleElement = Object.assign(document.createElement('style'), { + innerText: style, + }) + shadow.appendChild(styleElement) + } + document.body.appendChild(div) + return shadow +} +""".strip(), + inputs_schema=[ + { + "key": "selector", + "label": "Selector", + "description": 'CSS selector to activate on. For example: "#my-beta-button" or "[data-attr=\'posthog-early-access-features-button\']"', + "type": "string", + "default": "", + }, + { + "key": "useButton", + "label": "Show features button on the page", + "description": "If enabled, a button will be shown on the page that will open the features modal.", + "type": "choice", + "choices": [ + { + "label": "Yes", + "value": "Yes", + }, + { + "label": "No", + "value": "No", + }, + ], + "default": "No", + }, + ], +) diff --git a/posthog/cdp/templates/_siteapps/template_hogdesk.py b/posthog/cdp/templates/_siteapps/template_hogdesk.py new file mode 100644 index 0000000000000..6949f37bb4ce7 --- /dev/null +++ b/posthog/cdp/templates/_siteapps/template_hogdesk.py @@ -0,0 +1,386 @@ +from posthog.cdp.templates.hog_function_template import HogFunctionTemplate + +template: HogFunctionTemplate = HogFunctionTemplate( + status="client-side", + type="site_app", + id="template-hogdesk", + name="HogDesk", + description="HogDesk bug reporter", + icon_url="https://raw.githubusercontent.com/PostHog/bug-report-app/refs/heads/main/logo.png", + category=["Custom"], + hog=""" +const style = (inputs) => ` + .form, .button, .thanks { + position: fixed; + bottom: 20px; + right: 20px; + color: black; + font-weight: normal; + font-family: -apple-system, BlinkMacSystemFont, "Inter", "Segoe UI", "Roboto", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + text-align: left; + z-index: ${parseInt(inputs.zIndex) || 99999}; + } + .button { + width: 64px; + height: 64px; + border-radius: 100%; + text-align: center; + line-height: 60px; + font-size: 32px; + border: none; + cursor: pointer; + } + .button:hover { + filter: brightness(1.2); + } + .form-submit[disabled] { + opacity: 0.6; + filter: grayscale(100%); + cursor: not-allowed; + } + .thanks { + background: white; + } + .form { + display: none; + flex-direction: column; + background: white; + border: 1px solid #f0f0f0; + border-radius: 8px; + padding-top: 5px; + max-width: 380px; + box-shadow: -6px 0 16px -8px rgb(0 0 0 / 8%), -9px 0 28px 0 rgb(0 0 0 / 5%), -12px 0 48px 16px rgb(0 0 0 / 3%); + } + .form textarea { + color: #2d2d2d; + font-size: 14px; + font-family: -apple-system, BlinkMacSystemFont, "Inter", "Segoe UI", "Roboto", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + margin-bottom: 10px; + background: white; + color: black; + border: none; + outline: none; + padding-left: 10px; + padding-right: 10px; + padding-top: 10px; + } + .form-submit { + box-sizing: border-box; + margin: 0; + font-family: inherit; + overflow: visible; + text-transform: none; + line-height: 1.5715; + position: relative; + display: inline-block; + font-weight: 400; + white-space: nowrap; + text-align: center; + border: 1px solid transparent; + cursor: pointer; + transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); + user-select: none; + touch-action: manipulation; + height: 32px; + padding: 4px 15px; + font-size: 14px; + border-radius: 4px; + outline: 0; + color: #fff; + border-color: #1d4aff; + background: #1d4aff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.12); + box-shadow: 0 2px 0 rgba(0, 0, 0, 0.045); + } + .form-submit:hover { + filter: brightness(1.2); + } + .form-cancel { + box-sizing: border-box; + margin: 0; + font-family: inherit; + overflow: visible; + text-transform: none; + line-height: 1.5715; + position: relative; + display: inline-block; + font-weight: 400; + white-space: nowrap; + text-align: center; + border: 1px solid transparent; + box-shadow: 0 2px 0 rgba(0, 0, 0, 0.015); + cursor: pointer; + transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); + user-select: none; + touch-action: manipulation; + height: 32px; + padding: 4px 15px; + font-size: 14px; + border-radius: 4px; + color: #2d2d2d; + border-color: rgba(0, 0, 0, 0.15); + background: #fff; + outline: 0; + } + .thanks { + display: none; + font-size: 14px; + padding: 20px; + border: 1px solid #f0f0f0; + border-radius: 8px; + box-shadow: -6px 0 16px -8px rgb(0 0 0 / 8%), -9px 0 28px 0 rgb(0 0 0 / 5%), -12px 0 48px 16px rgb(0 0 0 / 3%); + max-width: 340px; + margin-block-end: 1em; + } + .bolded { font-weight: 600; } + .bottom-section { + border-top: 1px solid #f0f0f0; + padding: 10px 16px; + } + .buttons { + display: flex; + justify-content: space-between; + } + .specific-issue { + padding-top: 10px; + font-size: 14px; + color: #747ea1; + font-family: -apple-system, BlinkMacSystemFont, "Inter", "Segoe UI", "Roboto", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + } + .specific-issue a:link { + color: #5879FF; + } + .specific-issue a:visited { + color: #5879FF; + } +` + +export function onLoad({ inputs, posthog }) { + if (inputs.domains) { + const domains = inputs.domains.split(',').map((domain) => domain.trim()) + if (domains.length > 0 && domains.indexOf(window.location.hostname) === -1) { + return + } + } + const shadow = createShadow(style(inputs)) + + function openbugBox() { + Object.assign(buttonElement.style, { display: 'none' }) + Object.assign(formElement.style, { display: 'flex' }) + + const closeButton = shadow.querySelector('.form-cancel') + closeButton.addEventListener('click', (e) => { + e.preventDefault() + Object.assign(formElement.style, { display: 'none' }) + }) + } + + const buttonElement = Object.assign(document.createElement('button'), { + className: 'button', + innerText: inputs.buttonText || '?', + onclick: openbugBox, + title: inputs.buttonTitle || '', + }) + Object.assign(buttonElement.style, { + color: inputs.buttonColor || 'black', + background: inputs.buttonBackground || '#1d8db9', + }) + + if (inputs.useButton === 'Yes') { + shadow.appendChild(buttonElement) + } + + const form = ` + + +
+
+ Close + +
+
+
+ ` + + const getSessionRecordingUrl = () => { + const sessionId = posthog?.sessionRecording?.sessionId + const LOOK_BACK = 30 + const recordingStartTime = Math.max( + Math.floor((new Date().getTime() - (posthog?.sessionManager?._sessionStartTimestamp || 0)) / 1000) - + LOOK_BACK, + 0 + ) + const api_host = posthog?.config?.api_host || 'https://app.posthog.com' + return sessionId ? `${api_host}/recordings/${sessionId}?t=${recordingStartTime}` : undefined + } + + const formElement = Object.assign(document.createElement('form'), { + className: 'form', + innerHTML: form, + onsubmit: function (e) { + e.preventDefault() + const sessionRecordingUrl = getSessionRecordingUrl() + posthog.capture(inputs.eventName || 'bug Sent', { + [inputs.bugProperty || '$bug']: this.bug.value, + sessionRecordingUrl: sessionRecordingUrl, + email: this.email.value + }) + Object.assign(formElement.style, { display: 'none' }) + Object.assign(thanksElement.style, { display: 'flex' }) + window.setTimeout(() => { + Object.assign(thanksElement.style, { display: 'none' }) + }, 3000) + formElement.reset() + }, + }) + const textarea = formElement.getElementsByClassName('bug-textarea')[0] as HTMLTextAreaElement + const emailInput = formElement.getElementsByClassName('bug-emailinput')[0] as HTMLInputElement + + const cancelButton = formElement.getElementsByClassName('form-cancel')[0] as HTMLElement + const submitButton = formElement.getElementsByClassName('form-submit')[0] as HTMLButtonElement + const footerArea = formElement.getElementsByClassName('specific-issue')[0] as HTMLElement + + Object.assign(submitButton.style, { + color: inputs.buttonColor || 'white', + background: inputs.buttonBackground || '#1d8db9', + borderColor: inputs.buttonBackground || '#1d8db9', + }) + + textarea.addEventListener('input', (e) => { + if (textarea.value.length > 0) { + submitButton.disabled = false + } else { + submitButton.disabled = true + } + }) + + textarea.setAttribute('placeholder', inputs.placeholderText || 'Help us improve') + cancelButton.innerText = inputs.cancelButtonText || 'Cancel' + submitButton.innerText = inputs.sendButtonText || 'Send bug' + if (inputs.footerHTML) { + footerArea.innerHTML = inputs.footerHTML + } else { + footerArea.style.display = 'none' + } + shadow.appendChild(formElement) + + if (inputs.selector) { + const clickListener = (e) => { + if (e.target.matches(inputs.selector)) { + openbugBox() + } + } + window.addEventListener('click', clickListener) + } + + console.log('Posthog - latest bug widget') + + const thanksElement = Object.assign(document.createElement('div'), { + className: 'thanks', + innerHTML: '
' + inputs.thanksText + '
' || 'Thank you!', + }) + shadow.appendChild(thanksElement) +} + +function createShadow(styleSheet: string): ShadowRoot { + const div = document.createElement('div') + const shadow = div.attachShadow({ mode: 'open' }) + if (styleSheet) { + const styleElement = Object.assign(document.createElement('style'), { + innerText: styleSheet, + }) + shadow.appendChild(styleElement) + } + document.body.appendChild(div) + return shadow +} +""".strip(), + inputs_schema=[ + { + "key": "domains", + "label": "Domains", + "description": 'Comma separated list of domains to activate on. Leave blank to enable all. For example: "localhost,app.posthog.com"', + "type": "string", + "default": "", + }, + { + "key": "selector", + "label": "Selector", + "description": 'CSS selector to activate on. For example: "#my-bug-button" or "[data-attr=\'posthog-bug-button\']"', + "type": "string", + "default": "", + }, + { + "key": "useButton", + "label": "Show bug button on the page", + "description": "Alternatively, any click on an element with the selector [data-attr='posthog-bug-button'] will open the bug widget", + "type": "choice", + "choices": [ + { + "label": "Yes", + "value": "Yes", + }, + { + "label": "No", + "value": "No", + }, + ], + "default": "Yes", + "required": False, + }, + {"key": "buttonText", "label": "Button text", "type": "string", "default": "✉️", "required": True}, + { + "key": "buttonTitle", + "label": "Button title", + "description": "The text that appears when you hover over the button", + "type": "string", + "default": "", + }, + {"key": "buttonBackground", "label": "Button background", "type": "string", "default": ""}, + {"key": "buttonColor", "label": "Button text color", "type": "string", "default": ""}, + {"key": "placeholderText", "label": "Placeholder text", "type": "string", "default": "Help us improve"}, + { + "key": "sendButtonText", + "label": "Send button text", + "type": "string", + "default": "Send bug", + "required": True, + }, + {"key": "cancelButtonText", "label": "Cancel button text", "type": "string", "default": "Cancel"}, + { + "key": "thanksText", + "label": "Thank you text", + "type": "string", + "default": "Thank you! Closing in 3 seconds...", + "required": True, + }, + { + "key": "footerHTML", + "label": "Footer HTML", + "description": "HTML to show in the footer of the bug widget. For example: \"More questions? Ask us anything\"", + "type": "string", + "default": "Have a specific issue? Contact support directly!", + }, + { + "key": "eventName", + "label": "bug event's event name", + "type": "string", + "default": "bug Sent", + "required": True, + }, + { + "key": "bugProperty", + "label": "bug event's bug property", + "type": "string", + "default": "$bug", + "required": True, + }, + { + "key": "zIndex", + "label": "z-index of the form and the button (default to 999999)", + "type": "string", + "default": "999999", + "required": True, + }, + ], +) diff --git a/posthog/cdp/templates/_siteapps/template_notification_bar.py b/posthog/cdp/templates/_siteapps/template_notification_bar.py new file mode 100644 index 0000000000000..c6dbb3a713ea3 --- /dev/null +++ b/posthog/cdp/templates/_siteapps/template_notification_bar.py @@ -0,0 +1,160 @@ +from posthog.cdp.templates.hog_function_template import HogFunctionTemplate + +template: HogFunctionTemplate = HogFunctionTemplate( + status="client-side", + type="site_app", + id="template-notification-bar", + name="Notification Bar", + description="Show a notification bar for your users", + icon_url="/static/hedgehog/list-hog.png", + category=["Custom", "Analytics"], + hog=""" +export function onLoad({ inputs }) { + if (inputs.domains) { + const domains = inputs.domains.split(',').map((domain) => domain.trim()) + if (domains.length > 0 && domains.indexOf(window.location.hostname) === -1) { + return + } + } + const localStorageKey = `notification-${inputs.notification}` + if (inputs.rememberClose === 'yes' && localStorage.getItem(localStorageKey)) { + return + } + const style = ` + .notification-bar-container { + min-height: 56px; + } + .notification-bar { + width: 100%; + min-height: 56px; + line-height: 36px; + font-size: 24px; + color: ${inputs.textColor || 'default'}; + background: ${inputs.backgroundColor || 'default'}; + font-weight: normal; + font-family: -apple-system, BlinkMacSystemFont, "Inter", "Segoe UI", "Roboto", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + text-align: center; + position: ${inputs.position === 'sticky' ? 'fixed' : 'absolute'}; + left: 0; + top: 0; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + z-index: 9999999; + } + .notification-bar a { + color: ${inputs.linkColor || inputs.textColor || 'default'}; + } + .notification-bar p { + margin: 0; + } + ` + const paragraph = Object.assign(document.createElement('p'), { + innerHTML: inputs.notification, + }) + const notificationElementContainer = Object.assign(document.createElement('div'), { + className: 'notification-bar-container', + }) + const notificationElement = Object.assign(document.createElement('div'), { + className: 'notification-bar', + onclick: (e) => { + if (!e.target.matches('a,button')) { + notificationElement.style.display = 'none' + notificationElementContainer.style.display = 'none' + window.localStorage.setItem(localStorageKey, 'true') + } + }, + title: inputs.buttonTitle || '', + }) + notificationElement.append(paragraph) + const shadow = createShadowRoot(style) + notificationElementContainer.appendChild(notificationElement) + shadow.appendChild(notificationElementContainer) + document.body.prepend(shadow) +} +function createShadowRoot(style) { + const div = document.createElement('div') + const shadow = div.attachShadow({ mode: 'open' }) + if (style) { + const styleElement = Object.assign(document.createElement('style'), { + innerText: style, + }) + shadow.appendChild(styleElement) + } + document.body.prepend(div) + return shadow +} +""".strip(), + inputs_schema=[ + { + "key": "domains", + "label": "Domains", + "description": 'Comma separated list of domains to activate on. Leave blank to enable all. For example: "localhost,app.posthog.com"', + "type": "string", + "default": "", + }, + { + "key": "notification", + "label": "HTML to show in the notification bar", + "type": "string", + "default": "🚀 Product 2.0! is out! Click here to learn more.", + "required": True, + }, + { + "key": "position", + "label": "Position of the notification bar", + "type": "choice", + "choices": [ + { + "label": "Sticky", + "value": "sticky", + }, + { + "label": "Top of page", + "value": "top-of-page", + }, + ], + "default": "sticky", + "required": True, + }, + { + "key": "backgroundColor", + "label": "Background color", + "type": "string", + "default": "#ebece8", + "required": True, + }, + { + "key": "textColor", + "label": "Text color", + "type": "string", + "default": "#333", + "required": True, + }, + { + "key": "linkColor", + "label": "Link color", + "type": "string", + "default": "#f64e00", + "required": True, + }, + { + "key": "rememberClose", + "label": "Remember close", + "type": "choice", + "choices": [ + { + "label": "Yes", + "value": "yes", + }, + { + "label": "No", + "value": "no", + }, + ], + "default": "yes", + "description": "Remember if the user has closed the notification bar, and don't show it again. This resets if you update the notification bar's text.", + }, + ], +) diff --git a/posthog/cdp/templates/_siteapps/template_pineapple_mode.py b/posthog/cdp/templates/_siteapps/template_pineapple_mode.py new file mode 100644 index 0000000000000..9f7453eb18c89 --- /dev/null +++ b/posthog/cdp/templates/_siteapps/template_pineapple_mode.py @@ -0,0 +1,168 @@ +from posthog.cdp.templates.hog_function_template import HogFunctionTemplate + +template: HogFunctionTemplate = HogFunctionTemplate( + status="client-side", + type="site_app", + id="template-pineapple-mode", + name="Pineapple Mode", + description="Make any website better by adding raining pineapples", + icon_url="/static/services/pineapple.png", + category=["Custom", "Analytics"], + hog=""" +const style = ` + .button { + position: fixed; + bottom: 20px; + right: 20px; + color: black; + font-weight: normal; + font-family: -apple-system, BlinkMacSystemFont, "Inter", "Segoe UI", "Roboto", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + text-align: left; + width: 48px; + height: 48px; + border-radius: 100%; + text-align: center; + line-height: 40px; + font-size: 32px; + border: none; + cursor: pointer; + z-index: 999999; + } + .button:hover { + filter: brightness(1.2); + } + .button.disabled { + opacity: 0.5; + filter: grayscale(100%); + } +` +export function onLoad({ inputs, posthog }) { + if (inputs.domains) { + const domains = inputs.domains.split(',').map((domain) => domain.trim()) + if (domains.length > 0 && domains.indexOf(window.location.hostname) === -1) { + return + } + } + const intensity = Math.max(1, Math.min(parseInt(inputs.intensity) || 5, 10)) + const emoji = inputs.emoji || '🍍' + const shadow = createShadow(style) + let buttonElement: HTMLButtonElement + let rainInterval + function toggle(): void { + if (rainInterval) { + window.clearInterval(rainInterval) + rainInterval = undefined + posthog.capture('Pineapple mode deactivated', inputs) + buttonElement?.classList.remove('disabled') + } else { + rainInterval = window.setInterval(() => makeItRain(shadow, emoji, intensity), 1000 / intensity) + posthog.capture('Pineapple mode activated', inputs) + buttonElement?.classList.add('disabled') + } + } + if (inputs.showButton) { + buttonElement = Object.assign(document.createElement('button'), { + className: 'button', + innerText: inputs.buttonText || emoji, + onclick: toggle, + }) + Object.assign(buttonElement.style, { + color: inputs.buttonColor || 'black', + background: inputs.buttonBackground || '#ccae05', + }) + shadow.appendChild(buttonElement) + } + if (inputs.startRaining) { + for (let i = 0; i < intensity * 2; i++) { + makeItRain(shadow, emoji, intensity) + } + toggle() + } +} +// Drops an emoji from the sky +function makeItRain(shadow: ShadowRoot, emoji: string, intensity: number) { + const div = document.createElement('div') + Object.assign(div.style, { + position: 'fixed', + left: `${(window.innerWidth - 30) * Math.random()}px`, + top: '-10px', + fontSize: '24px', + zIndex: 99999999, + pointerEvents: 'none', + }) + div.innerHTML = emoji + shadow.appendChild(div) + const duration = 300 * (10 - intensity) + Math.random() * 3001 + div.animate([{ top: '-10px' }, { top: `${window.innerHeight + 20}px` }], { + duration, + iterations: 1, + }) + window.setTimeout(() => div.remove(), duration + 1) +} +function createShadow(style?: string): ShadowRoot { + const div = document.createElement('div') + const shadow = div.attachShadow({ mode: 'open' }) + if (style) { + const styleElement = Object.assign(document.createElement('style'), { + innerText: style, + }) + shadow.appendChild(styleElement) + } + document.body.appendChild(div) + return shadow +} +""".strip(), + inputs_schema=[ + { + "key": "domains", + "type": "string", + "label": "Domains", + "description": 'Comma separated list of domains to activate on. Leave blank to enable all. For example: "localhost,app.posthog.com"', + "default": "", + }, + { + "key": "emoji", + "type": "string", + "label": "Emoji to use", + "default": "🍍", + "required": True, + }, + { + "key": "intensity", + "type": "string", + "label": "Intensity", + "default": "4", + "required": True, + "description": "Rainfall intensity (1-10)", + }, + { + "key": "startRaining", + "type": "boolean", + "label": "Start raining immediately", + "default": True, + "required": True, + }, + { + "key": "showButton", + "type": "boolean", + "label": "Show Floating Button", + "description": "Shows a button you can use to disable the pineapple mode", + "default": True, + }, + {"key": "buttonText", "type": "string", "label": "Button text, if enabled", "default": ""}, + { + "key": "buttonColor", + "type": "string", + "label": "Button text color", + "description": 'Any valid CSS color. For example: "#ff0000" or "red"', + "default": "black", + }, + { + "key": "buttonBackground", + "type": "string", + "label": "Button background", + "description": 'Any valid CSS background. For example: "red" or "url(\'...\')"', + "default": "#ccae05", + }, + ], +) diff --git a/posthog/cdp/templates/slack/template_slack.py b/posthog/cdp/templates/slack/template_slack.py index b3079485176c6..16bb0383c1c0b 100644 --- a/posthog/cdp/templates/slack/template_slack.py +++ b/posthog/cdp/templates/slack/template_slack.py @@ -24,7 +24,7 @@ } }); -if (res.status != 200 or not res.body.ok) { +if (res.status != 200 or res.body.ok == false) { throw Error(f'Failed to post message to Slack: {res.status}: {res.body}'); } """.strip(), diff --git a/posthog/hogql/constants.py b/posthog/hogql/constants.py index 9a7c463ba3ab0..01b09360a413f 100644 --- a/posthog/hogql/constants.py +++ b/posthog/hogql/constants.py @@ -100,6 +100,7 @@ class HogQLQuerySettings(BaseModel): optimize_aggregation_in_order: Optional[bool] = None date_time_output_format: Optional[str] = None date_time_input_format: Optional[str] = None + join_algorithm: Optional[str] = None # Settings applied on top of all HogQL queries. diff --git a/posthog/hogql_queries/error_tracking_query_runner.py b/posthog/hogql_queries/error_tracking_query_runner.py index 20eebdeb1ac2c..c6c2231d366b1 100644 --- a/posthog/hogql_queries/error_tracking_query_runner.py +++ b/posthog/hogql_queries/error_tracking_query_runner.py @@ -29,6 +29,7 @@ def __init__(self, *args, **kwargs): self.paginator = HogQLHasMorePaginator.from_limit_context( limit_context=LimitContext.QUERY, limit=self.query.limit if self.query.limit else None, + offset=self.query.offset, ) def to_query(self) -> ast.SelectQuery: @@ -184,11 +185,11 @@ def order_by(self): return ( [ ast.OrderExpr( - expr=ast.Field(chain=[self.query.order]), - order="ASC" if self.query.order == "first_seen" else "DESC", + expr=ast.Field(chain=[self.query.orderBy]), + order="ASC" if self.query.orderBy == "first_seen" else "DESC", ) ] - if self.query.order + if self.query.orderBy else None ) diff --git a/posthog/hogql_queries/insights/funnels/base.py b/posthog/hogql_queries/insights/funnels/base.py index 75815c634bb08..1c7fb05d13f69 100644 --- a/posthog/hogql_queries/insights/funnels/base.py +++ b/posthog/hogql_queries/insights/funnels/base.py @@ -37,6 +37,8 @@ ) from posthog.types import EntityNode, ExclusionEntityNode +JOIN_ALGOS = "direct,parallel_hash,hash,full_sorting_merge" + class FunnelBase(ABC): context: FunnelQueryContext diff --git a/posthog/hogql_queries/insights/funnels/funnel_trends_udf.py b/posthog/hogql_queries/insights/funnels/funnel_trends_udf.py index d3a372af506a7..40d15aae68a69 100644 --- a/posthog/hogql_queries/insights/funnels/funnel_trends_udf.py +++ b/posthog/hogql_queries/insights/funnels/funnel_trends_udf.py @@ -6,6 +6,7 @@ from posthog.hogql.constants import HogQLQuerySettings from posthog.hogql.parser import parse_select, parse_expr from posthog.hogql_queries.insights.funnels import FunnelTrends +from posthog.hogql_queries.insights.funnels.base import JOIN_ALGOS from posthog.hogql_queries.insights.utils.utils import get_start_of_interval_hogql_str from posthog.schema import BreakdownType, BreakdownAttributionType from posthog.utils import DATERANGE_MAP, relative_date_parse @@ -195,7 +196,9 @@ def get_query(self) -> ast.SelectQuery: """, {"fill_query": fill_query, "inner_select": inner_select}, ) - return cast(ast.SelectQuery, s) + s = cast(ast.SelectQuery, s) + s.settings = HogQLQuerySettings(join_algorithm=JOIN_ALGOS) + return s def _matching_events(self): if ( @@ -254,4 +257,5 @@ def actor_query( select_from=select_from, order_by=order_by, where=where, + settings=HogQLQuerySettings(join_algorithm=JOIN_ALGOS), ) diff --git a/posthog/hogql_queries/insights/funnels/funnel_udf.py b/posthog/hogql_queries/insights/funnels/funnel_udf.py index ac4fda03069d3..1bc07d685da02 100644 --- a/posthog/hogql_queries/insights/funnels/funnel_udf.py +++ b/posthog/hogql_queries/insights/funnels/funnel_udf.py @@ -1,9 +1,9 @@ from typing import cast, Optional from posthog.hogql import ast -from posthog.hogql.constants import DEFAULT_RETURNED_ROWS +from posthog.hogql.constants import DEFAULT_RETURNED_ROWS, HogQLQuerySettings from posthog.hogql.parser import parse_select, parse_expr -from posthog.hogql_queries.insights.funnels.base import FunnelBase +from posthog.hogql_queries.insights.funnels.base import FunnelBase, JOIN_ALGOS from posthog.schema import BreakdownType, BreakdownAttributionType from posthog.utils import DATERANGE_MAP @@ -169,8 +169,10 @@ def get_query(self) -> ast.SelectQuery: ) # Weird: unless you reference row_number in this outer block, it doesn't work correctly - s = parse_select( - f""" + s = cast( + ast.SelectQuery, + parse_select( + f""" SELECT {step_results2}, {mean_conversion_times}, @@ -182,10 +184,11 @@ def get_query(self) -> ast.SelectQuery: GROUP BY final_prop LIMIT {self.get_breakdown_limit() + 1 if use_breakdown_limit else DEFAULT_RETURNED_ROWS} """, - {"s": s}, + {"s": s}, + ), ) - - return cast(ast.SelectQuery, s) + s.settings = HogQLQuerySettings(join_algorithm=JOIN_ALGOS) + return s def _get_funnel_person_step_condition(self) -> ast.Expr: actorsQuery, breakdownType = ( @@ -294,4 +297,5 @@ def actor_query( select_from=select_from, order_by=order_by, where=where, + settings=HogQLQuerySettings(join_algorithm=JOIN_ALGOS), ) diff --git a/posthog/hogql_queries/insights/funnels/test/__snapshots__/test_funnel_correlation_actors_udf.ambr b/posthog/hogql_queries/insights/funnels/test/__snapshots__/test_funnel_correlation_actors_udf.ambr index 3f3fd82910546..bc580c8b79bd9 100644 --- a/posthog/hogql_queries/insights/funnels/test/__snapshots__/test_funnel_correlation_actors_udf.ambr +++ b/posthog/hogql_queries/insights/funnels/test/__snapshots__/test_funnel_correlation_actors_udf.ambr @@ -60,7 +60,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors ON equals(if(not(empty(event__override.distinct_id)), event__override.person_id, event.person_id), funnel_actors.actor_id) + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors ON equals(if(not(empty(event__override.distinct_id)), event__override.person_id, event.person_id), funnel_actors.actor_id) WHERE and(equals(event.team_id, 99999), greaterOrEquals(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2021-01-01 00:00:00', 6, 'UTC'))), less(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2021-01-08 23:59:59', 6, 'UTC'))), equals(event.team_id, 99999), greater(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), funnel_actors.first_timestamp), less(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), coalesce(funnel_actors.final_timestamp, plus(toTimeZone(funnel_actors.first_timestamp, 'UTC'), toIntervalDay(14)), assumeNotNull(parseDateTime64BestEffortOrNull('2021-01-08 23:59:59', 6, 'UTC')))), notIn(event.event, ['$pageview', 'insight analyzed']), equals(event.event, 'insight loaded'), ifNull(equals(funnel_actors.steps, 2), 0)) GROUP BY actor_id ORDER BY actor_id ASC) AS source @@ -99,7 +99,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors ON equals(if(not(empty(event__override.distinct_id)), event__override.person_id, event.person_id), funnel_actors.actor_id) + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors ON equals(if(not(empty(event__override.distinct_id)), event__override.person_id, event.person_id), funnel_actors.actor_id) WHERE and(equals(event.team_id, 99999), greaterOrEquals(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2021-01-01 00:00:00', 6, 'UTC'))), less(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2021-01-08 23:59:59', 6, 'UTC'))), equals(event.team_id, 99999), greater(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), funnel_actors.first_timestamp), less(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), coalesce(funnel_actors.final_timestamp, plus(toTimeZone(funnel_actors.first_timestamp, 'UTC'), toIntervalDay(14)), assumeNotNull(parseDateTime64BestEffortOrNull('2021-01-08 23:59:59', 6, 'UTC')))), notIn(event.event, ['$pageview', 'insight analyzed']), equals(event.event, 'insight loaded'), ifNull(equals(funnel_actors.steps, 2), 0)) GROUP BY actor_id ORDER BY actor_id ASC) AS source))) @@ -192,7 +192,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors ON equals(if(not(empty(event__override.distinct_id)), event__override.person_id, event.person_id), funnel_actors.actor_id) + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors ON equals(if(not(empty(event__override.distinct_id)), event__override.person_id, event.person_id), funnel_actors.actor_id) WHERE and(equals(event.team_id, 99999), greaterOrEquals(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2021-01-01 00:00:00', 6, 'UTC'))), less(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2021-01-08 23:59:59', 6, 'UTC'))), equals(event.team_id, 99999), greater(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), funnel_actors.first_timestamp), less(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), coalesce(funnel_actors.final_timestamp, plus(toTimeZone(funnel_actors.first_timestamp, 'UTC'), toIntervalDay(14)), assumeNotNull(parseDateTime64BestEffortOrNull('2021-01-08 23:59:59', 6, 'UTC')))), notIn(event.event, ['$pageview', 'insight analyzed', 'insight updated']), equals(event.event, 'insight loaded'), ifNull(notEquals(funnel_actors.steps, 3), 1)) GROUP BY actor_id ORDER BY actor_id ASC) AS source @@ -231,7 +231,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors ON equals(if(not(empty(event__override.distinct_id)), event__override.person_id, event.person_id), funnel_actors.actor_id) + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors ON equals(if(not(empty(event__override.distinct_id)), event__override.person_id, event.person_id), funnel_actors.actor_id) WHERE and(equals(event.team_id, 99999), greaterOrEquals(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2021-01-01 00:00:00', 6, 'UTC'))), less(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2021-01-08 23:59:59', 6, 'UTC'))), equals(event.team_id, 99999), greater(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), funnel_actors.first_timestamp), less(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), coalesce(funnel_actors.final_timestamp, plus(toTimeZone(funnel_actors.first_timestamp, 'UTC'), toIntervalDay(14)), assumeNotNull(parseDateTime64BestEffortOrNull('2021-01-08 23:59:59', 6, 'UTC')))), notIn(event.event, ['$pageview', 'insight analyzed', 'insight updated']), equals(event.event, 'insight loaded'), ifNull(notEquals(funnel_actors.steps, 3), 1)) GROUP BY actor_id ORDER BY actor_id ASC) AS source))) @@ -325,7 +325,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors WHERE ifNull(equals(funnel_actors.steps, 2), 0) GROUP BY funnel_actors.actor_id ORDER BY funnel_actors.actor_id ASC) AS source @@ -366,7 +366,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors WHERE ifNull(equals(funnel_actors.steps, 2), 0) GROUP BY funnel_actors.actor_id ORDER BY funnel_actors.actor_id ASC) AS source))) diff --git a/posthog/hogql_queries/insights/funnels/test/__snapshots__/test_funnel_correlation_udf.ambr b/posthog/hogql_queries/insights/funnels/test/__snapshots__/test_funnel_correlation_udf.ambr index 00797c892c6b5..1ef2e20dafbba 100644 --- a/posthog/hogql_queries/insights/funnels/test/__snapshots__/test_funnel_correlation_udf.ambr +++ b/posthog/hogql_queries/insights/funnels/test/__snapshots__/test_funnel_correlation_udf.ambr @@ -56,7 +56,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors ON equals(if(not(empty(event__override.distinct_id)), event__override.person_id, event.person_id), funnel_actors.actor_id) + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors ON equals(if(not(empty(event__override.distinct_id)), event__override.person_id, event.person_id), funnel_actors.actor_id) WHERE and(equals(event.team_id, 99999), greaterOrEquals(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-01 00:00:00', 6, 'UTC'))), less(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-14 23:59:59', 6, 'UTC'))), equals(event.team_id, 99999), greater(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), funnel_actors.first_timestamp), less(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), coalesce(funnel_actors.final_timestamp, plus(toTimeZone(funnel_actors.first_timestamp, 'UTC'), toIntervalDay(14)), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-14 23:59:59', 6, 'UTC')))), notIn(event.event, ['paid', 'user signed up']), notIn(event.event, [])) GROUP BY name LIMIT 100 @@ -108,7 +108,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors LIMIT 100 SETTINGS readonly=2, max_execution_time=60, allow_experimental_object_type=1, @@ -171,7 +171,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors JOIN (SELECT persons.id AS id, persons.properties AS person_props @@ -236,7 +236,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors LIMIT 100 SETTINGS readonly=2, max_execution_time=60, allow_experimental_object_type=1, @@ -309,7 +309,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors WHERE ifNull(equals(funnel_actors.steps, 2), 0) GROUP BY funnel_actors.actor_id ORDER BY funnel_actors.actor_id ASC) AS source @@ -350,7 +350,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors WHERE ifNull(equals(funnel_actors.steps, 2), 0) GROUP BY funnel_actors.actor_id ORDER BY funnel_actors.actor_id ASC) AS source))) @@ -444,7 +444,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors WHERE ifNull(notEquals(funnel_actors.steps, 2), 1) GROUP BY funnel_actors.actor_id ORDER BY funnel_actors.actor_id ASC) AS source @@ -485,7 +485,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors WHERE ifNull(notEquals(funnel_actors.steps, 2), 1) GROUP BY funnel_actors.actor_id ORDER BY funnel_actors.actor_id ASC) AS source))) @@ -579,7 +579,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors WHERE ifNull(equals(funnel_actors.steps, 2), 0) GROUP BY funnel_actors.actor_id ORDER BY funnel_actors.actor_id ASC) AS source @@ -620,7 +620,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors WHERE ifNull(equals(funnel_actors.steps, 2), 0) GROUP BY funnel_actors.actor_id ORDER BY funnel_actors.actor_id ASC) AS source))) @@ -714,7 +714,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors WHERE ifNull(notEquals(funnel_actors.steps, 2), 1) GROUP BY funnel_actors.actor_id ORDER BY funnel_actors.actor_id ASC) AS source @@ -755,7 +755,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors WHERE ifNull(notEquals(funnel_actors.steps, 2), 1) GROUP BY funnel_actors.actor_id ORDER BY funnel_actors.actor_id ASC) AS source))) @@ -839,7 +839,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors JOIN (SELECT persons.id AS id, persons.properties AS person_props @@ -904,7 +904,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors LIMIT 100 SETTINGS readonly=2, max_execution_time=60, allow_experimental_object_type=1, @@ -977,7 +977,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors WHERE ifNull(equals(funnel_actors.steps, 2), 0) GROUP BY funnel_actors.actor_id ORDER BY funnel_actors.actor_id ASC) AS source @@ -1018,7 +1018,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors WHERE ifNull(equals(funnel_actors.steps, 2), 0) GROUP BY funnel_actors.actor_id ORDER BY funnel_actors.actor_id ASC) AS source))) @@ -1112,7 +1112,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors WHERE ifNull(notEquals(funnel_actors.steps, 2), 1) GROUP BY funnel_actors.actor_id ORDER BY funnel_actors.actor_id ASC) AS source @@ -1153,7 +1153,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors WHERE ifNull(notEquals(funnel_actors.steps, 2), 1) GROUP BY funnel_actors.actor_id ORDER BY funnel_actors.actor_id ASC) AS source))) @@ -1247,7 +1247,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors WHERE ifNull(equals(funnel_actors.steps, 2), 0) GROUP BY funnel_actors.actor_id ORDER BY funnel_actors.actor_id ASC) AS source @@ -1288,7 +1288,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors WHERE ifNull(equals(funnel_actors.steps, 2), 0) GROUP BY funnel_actors.actor_id ORDER BY funnel_actors.actor_id ASC) AS source))) @@ -1382,7 +1382,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors WHERE ifNull(notEquals(funnel_actors.steps, 2), 1) GROUP BY funnel_actors.actor_id ORDER BY funnel_actors.actor_id ASC) AS source @@ -1423,7 +1423,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors WHERE ifNull(notEquals(funnel_actors.steps, 2), 1) GROUP BY funnel_actors.actor_id ORDER BY funnel_actors.actor_id ASC) AS source))) @@ -1502,7 +1502,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors ON equals(funnel_actors.actor_id, event.`$group_1`) + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors ON equals(funnel_actors.actor_id, event.`$group_1`) WHERE and(equals(event.team_id, 99999), greaterOrEquals(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-01 00:00:00', 6, 'UTC'))), less(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-14 23:59:59', 6, 'UTC'))), equals(event.team_id, 99999), greater(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), funnel_actors.first_timestamp), less(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), coalesce(funnel_actors.final_timestamp, plus(toTimeZone(funnel_actors.first_timestamp, 'UTC'), toIntervalDay(14)), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-14 23:59:59', 6, 'UTC')))), notIn(event.event, ['paid', 'user signed up']), in(event.event, ['positively_related', 'negatively_related']))) GROUP BY name, prop @@ -1549,7 +1549,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors LIMIT 100 SETTINGS readonly=2, max_execution_time=60, allow_experimental_object_type=1, @@ -1607,7 +1607,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors ON equals(funnel_actors.actor_id, event.`$group_1`) + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors ON equals(funnel_actors.actor_id, event.`$group_1`) WHERE and(equals(event.team_id, 99999), greaterOrEquals(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-01 00:00:00', 6, 'UTC'))), less(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-14 23:59:59', 6, 'UTC'))), equals(event.team_id, 99999), greater(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), funnel_actors.first_timestamp), less(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), coalesce(funnel_actors.final_timestamp, plus(toTimeZone(funnel_actors.first_timestamp, 'UTC'), toIntervalDay(14)), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-14 23:59:59', 6, 'UTC')))), notIn(event.event, ['paid', 'user signed up']), in(event.event, ['positively_related', 'negatively_related']))) GROUP BY name, prop @@ -1654,7 +1654,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors LIMIT 100 SETTINGS readonly=2, max_execution_time=60, allow_experimental_object_type=1, @@ -1707,7 +1707,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors ON equals(funnel_actors.actor_id, event.`$group_0`) + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors ON equals(funnel_actors.actor_id, event.`$group_0`) WHERE and(equals(event.team_id, 99999), greaterOrEquals(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-01 00:00:00', 6, 'UTC'))), less(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-14 23:59:59', 6, 'UTC'))), equals(event.team_id, 99999), greater(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), funnel_actors.first_timestamp), less(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), coalesce(funnel_actors.final_timestamp, plus(toTimeZone(funnel_actors.first_timestamp, 'UTC'), toIntervalDay(14)), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-14 23:59:59', 6, 'UTC')))), notIn(event.event, ['paid', 'user signed up']), notIn(event.event, [])) GROUP BY name LIMIT 100 @@ -1752,7 +1752,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors LIMIT 100 SETTINGS readonly=2, max_execution_time=60, allow_experimental_object_type=1, @@ -1807,7 +1807,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors ON equals(funnel_actors.actor_id, event.`$group_0`) + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors ON equals(funnel_actors.actor_id, event.`$group_0`) WHERE and(equals(event.team_id, 99999), greaterOrEquals(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-01 00:00:00', 6, 'UTC'))), less(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-14 23:59:59', 6, 'UTC'))), equals(event.team_id, 99999), greater(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), funnel_actors.first_timestamp), less(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), coalesce(funnel_actors.final_timestamp, plus(toTimeZone(funnel_actors.first_timestamp, 'UTC'), toIntervalDay(14)), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-14 23:59:59', 6, 'UTC')))), notIn(event.event, ['paid', 'user signed up']), equals(event.event, 'positively_related'), ifNull(equals(funnel_actors.steps, 2), 0)) GROUP BY actor_id ORDER BY actor_id ASC) AS source @@ -1874,7 +1874,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors ON equals(funnel_actors.actor_id, event.`$group_0`) + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors ON equals(funnel_actors.actor_id, event.`$group_0`) WHERE and(equals(event.team_id, 99999), greaterOrEquals(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-01 00:00:00', 6, 'UTC'))), less(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-14 23:59:59', 6, 'UTC'))), equals(event.team_id, 99999), greater(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), funnel_actors.first_timestamp), less(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), coalesce(funnel_actors.final_timestamp, plus(toTimeZone(funnel_actors.first_timestamp, 'UTC'), toIntervalDay(14)), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-14 23:59:59', 6, 'UTC')))), notIn(event.event, ['paid', 'user signed up']), equals(event.event, 'positively_related'), ifNull(notEquals(funnel_actors.steps, 2), 1)) GROUP BY actor_id ORDER BY actor_id ASC) AS source @@ -1941,7 +1941,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors ON equals(funnel_actors.actor_id, event.`$group_0`) + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors ON equals(funnel_actors.actor_id, event.`$group_0`) WHERE and(equals(event.team_id, 99999), greaterOrEquals(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-01 00:00:00', 6, 'UTC'))), less(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-14 23:59:59', 6, 'UTC'))), equals(event.team_id, 99999), greater(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), funnel_actors.first_timestamp), less(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), coalesce(funnel_actors.final_timestamp, plus(toTimeZone(funnel_actors.first_timestamp, 'UTC'), toIntervalDay(14)), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-14 23:59:59', 6, 'UTC')))), notIn(event.event, ['paid', 'user signed up']), equals(event.event, 'negatively_related'), ifNull(equals(funnel_actors.steps, 2), 0)) GROUP BY actor_id ORDER BY actor_id ASC) AS source @@ -2008,7 +2008,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors ON equals(funnel_actors.actor_id, event.`$group_0`) + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors ON equals(funnel_actors.actor_id, event.`$group_0`) WHERE and(equals(event.team_id, 99999), greaterOrEquals(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-01 00:00:00', 6, 'UTC'))), less(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-14 23:59:59', 6, 'UTC'))), equals(event.team_id, 99999), greater(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), funnel_actors.first_timestamp), less(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), coalesce(funnel_actors.final_timestamp, plus(toTimeZone(funnel_actors.first_timestamp, 'UTC'), toIntervalDay(14)), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-14 23:59:59', 6, 'UTC')))), notIn(event.event, ['paid', 'user signed up']), equals(event.event, 'negatively_related'), ifNull(notEquals(funnel_actors.steps, 2), 1)) GROUP BY actor_id ORDER BY actor_id ASC) AS source @@ -2081,7 +2081,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors ON equals(funnel_actors.actor_id, event.`$group_0`) + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors ON equals(funnel_actors.actor_id, event.`$group_0`) WHERE and(equals(event.team_id, 99999), greaterOrEquals(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-01 00:00:00', 6, 'UTC'))), less(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-14 23:59:59', 6, 'UTC'))), equals(event.team_id, 99999), greater(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), funnel_actors.first_timestamp), less(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), coalesce(funnel_actors.final_timestamp, plus(toTimeZone(funnel_actors.first_timestamp, 'UTC'), toIntervalDay(14)), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-14 23:59:59', 6, 'UTC')))), notIn(event.event, ['paid', 'user signed up']), notIn(event.event, [])) GROUP BY name LIMIT 100 @@ -2134,7 +2134,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors LIMIT 100 SETTINGS readonly=2, max_execution_time=60, allow_experimental_object_type=1, @@ -2189,7 +2189,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors ON equals(funnel_actors.actor_id, event.`$group_0`) + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors ON equals(funnel_actors.actor_id, event.`$group_0`) WHERE and(equals(event.team_id, 99999), greaterOrEquals(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-01 00:00:00', 6, 'UTC'))), less(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-14 23:59:59', 6, 'UTC'))), equals(event.team_id, 99999), greater(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), funnel_actors.first_timestamp), less(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), coalesce(funnel_actors.final_timestamp, plus(toTimeZone(funnel_actors.first_timestamp, 'UTC'), toIntervalDay(14)), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-14 23:59:59', 6, 'UTC')))), notIn(event.event, ['paid', 'user signed up']), equals(event.event, 'negatively_related'), ifNull(equals(funnel_actors.steps, 2), 0)) GROUP BY actor_id ORDER BY actor_id ASC) AS source @@ -2256,7 +2256,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors ON equals(funnel_actors.actor_id, event.`$group_0`) + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors ON equals(funnel_actors.actor_id, event.`$group_0`) WHERE and(equals(event.team_id, 99999), greaterOrEquals(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-01 00:00:00', 6, 'UTC'))), less(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-14 23:59:59', 6, 'UTC'))), equals(event.team_id, 99999), greater(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), funnel_actors.first_timestamp), less(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), coalesce(funnel_actors.final_timestamp, plus(toTimeZone(funnel_actors.first_timestamp, 'UTC'), toIntervalDay(14)), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-14 23:59:59', 6, 'UTC')))), notIn(event.event, ['paid', 'user signed up']), equals(event.event, 'negatively_related'), ifNull(notEquals(funnel_actors.steps, 2), 1)) GROUP BY actor_id ORDER BY actor_id ASC) AS source @@ -2321,7 +2321,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors ON equals(funnel_actors.actor_id, event.`$group_0`) + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors ON equals(funnel_actors.actor_id, event.`$group_0`) WHERE and(equals(event.team_id, 99999), greaterOrEquals(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-01 00:00:00', 6, 'UTC'))), less(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-14 23:59:59', 6, 'UTC'))), equals(event.team_id, 99999), greater(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), funnel_actors.first_timestamp), less(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), coalesce(funnel_actors.final_timestamp, plus(toTimeZone(funnel_actors.first_timestamp, 'UTC'), toIntervalDay(14)), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-14 23:59:59', 6, 'UTC')))), notIn(event.event, ['paid', 'user signed up']), notIn(event.event, [])) GROUP BY name LIMIT 100 @@ -2366,7 +2366,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors LIMIT 100 SETTINGS readonly=2, max_execution_time=60, allow_experimental_object_type=1, @@ -2421,7 +2421,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors ON equals(funnel_actors.actor_id, event.`$group_0`) + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors ON equals(funnel_actors.actor_id, event.`$group_0`) WHERE and(equals(event.team_id, 99999), greaterOrEquals(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-01 00:00:00', 6, 'UTC'))), less(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-14 23:59:59', 6, 'UTC'))), equals(event.team_id, 99999), greater(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), funnel_actors.first_timestamp), less(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), coalesce(funnel_actors.final_timestamp, plus(toTimeZone(funnel_actors.first_timestamp, 'UTC'), toIntervalDay(14)), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-14 23:59:59', 6, 'UTC')))), notIn(event.event, ['paid', 'user signed up']), equals(event.event, 'positively_related'), ifNull(equals(funnel_actors.steps, 2), 0)) GROUP BY actor_id ORDER BY actor_id ASC) AS source @@ -2488,7 +2488,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors ON equals(funnel_actors.actor_id, event.`$group_0`) + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors ON equals(funnel_actors.actor_id, event.`$group_0`) WHERE and(equals(event.team_id, 99999), greaterOrEquals(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-01 00:00:00', 6, 'UTC'))), less(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-14 23:59:59', 6, 'UTC'))), equals(event.team_id, 99999), greater(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), funnel_actors.first_timestamp), less(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), coalesce(funnel_actors.final_timestamp, plus(toTimeZone(funnel_actors.first_timestamp, 'UTC'), toIntervalDay(14)), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-14 23:59:59', 6, 'UTC')))), notIn(event.event, ['paid', 'user signed up']), equals(event.event, 'positively_related'), ifNull(notEquals(funnel_actors.steps, 2), 1)) GROUP BY actor_id ORDER BY actor_id ASC) AS source @@ -2555,7 +2555,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors ON equals(funnel_actors.actor_id, event.`$group_0`) + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors ON equals(funnel_actors.actor_id, event.`$group_0`) WHERE and(equals(event.team_id, 99999), greaterOrEquals(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-01 00:00:00', 6, 'UTC'))), less(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-14 23:59:59', 6, 'UTC'))), equals(event.team_id, 99999), greater(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), funnel_actors.first_timestamp), less(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), coalesce(funnel_actors.final_timestamp, plus(toTimeZone(funnel_actors.first_timestamp, 'UTC'), toIntervalDay(14)), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-14 23:59:59', 6, 'UTC')))), notIn(event.event, ['paid', 'user signed up']), equals(event.event, 'negatively_related'), ifNull(equals(funnel_actors.steps, 2), 0)) GROUP BY actor_id ORDER BY actor_id ASC) AS source @@ -2622,7 +2622,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors ON equals(funnel_actors.actor_id, event.`$group_0`) + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors ON equals(funnel_actors.actor_id, event.`$group_0`) WHERE and(equals(event.team_id, 99999), greaterOrEquals(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-01 00:00:00', 6, 'UTC'))), less(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-14 23:59:59', 6, 'UTC'))), equals(event.team_id, 99999), greater(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), funnel_actors.first_timestamp), less(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), coalesce(funnel_actors.final_timestamp, plus(toTimeZone(funnel_actors.first_timestamp, 'UTC'), toIntervalDay(14)), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-14 23:59:59', 6, 'UTC')))), notIn(event.event, ['paid', 'user signed up']), equals(event.event, 'negatively_related'), ifNull(notEquals(funnel_actors.steps, 2), 1)) GROUP BY actor_id ORDER BY actor_id ASC) AS source @@ -2695,7 +2695,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors ON equals(funnel_actors.actor_id, event.`$group_0`) + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors ON equals(funnel_actors.actor_id, event.`$group_0`) WHERE and(equals(event.team_id, 99999), greaterOrEquals(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-01 00:00:00', 6, 'UTC'))), less(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-14 23:59:59', 6, 'UTC'))), equals(event.team_id, 99999), greater(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), funnel_actors.first_timestamp), less(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), coalesce(funnel_actors.final_timestamp, plus(toTimeZone(funnel_actors.first_timestamp, 'UTC'), toIntervalDay(14)), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-14 23:59:59', 6, 'UTC')))), notIn(event.event, ['paid', 'user signed up']), notIn(event.event, [])) GROUP BY name LIMIT 100 @@ -2748,7 +2748,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors LIMIT 100 SETTINGS readonly=2, max_execution_time=60, allow_experimental_object_type=1, @@ -2803,7 +2803,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors ON equals(funnel_actors.actor_id, event.`$group_0`) + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors ON equals(funnel_actors.actor_id, event.`$group_0`) WHERE and(equals(event.team_id, 99999), greaterOrEquals(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-01 00:00:00', 6, 'UTC'))), less(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-14 23:59:59', 6, 'UTC'))), equals(event.team_id, 99999), greater(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), funnel_actors.first_timestamp), less(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), coalesce(funnel_actors.final_timestamp, plus(toTimeZone(funnel_actors.first_timestamp, 'UTC'), toIntervalDay(14)), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-14 23:59:59', 6, 'UTC')))), notIn(event.event, ['paid', 'user signed up']), equals(event.event, 'negatively_related'), ifNull(equals(funnel_actors.steps, 2), 0)) GROUP BY actor_id ORDER BY actor_id ASC) AS source @@ -2870,7 +2870,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors ON equals(funnel_actors.actor_id, event.`$group_0`) + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors ON equals(funnel_actors.actor_id, event.`$group_0`) WHERE and(equals(event.team_id, 99999), greaterOrEquals(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-01 00:00:00', 6, 'UTC'))), less(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-14 23:59:59', 6, 'UTC'))), equals(event.team_id, 99999), greater(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), funnel_actors.first_timestamp), less(toTimeZone(toDateTime(toTimeZone(event.timestamp, 'UTC'), 'UTC'), 'UTC'), coalesce(funnel_actors.final_timestamp, plus(toTimeZone(funnel_actors.first_timestamp, 'UTC'), toIntervalDay(14)), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-14 23:59:59', 6, 'UTC')))), notIn(event.event, ['paid', 'user signed up']), equals(event.event, 'negatively_related'), ifNull(notEquals(funnel_actors.steps, 2), 1)) GROUP BY actor_id ORDER BY actor_id ASC) AS source @@ -2938,7 +2938,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors LEFT JOIN (SELECT groups.key AS key, groups.properties AS properties @@ -2995,7 +2995,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors LIMIT 100 SETTINGS readonly=2, max_execution_time=60, allow_experimental_object_type=1, @@ -3057,7 +3057,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors WHERE ifNull(equals(funnel_actors.steps, 2), 0) GROUP BY funnel_actors.actor_id ORDER BY funnel_actors.actor_id ASC) AS source @@ -3131,7 +3131,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors WHERE ifNull(notEquals(funnel_actors.steps, 2), 1) GROUP BY funnel_actors.actor_id ORDER BY funnel_actors.actor_id ASC) AS source @@ -3205,7 +3205,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors WHERE ifNull(equals(funnel_actors.steps, 2), 0) GROUP BY funnel_actors.actor_id ORDER BY funnel_actors.actor_id ASC) AS source @@ -3279,7 +3279,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors WHERE ifNull(notEquals(funnel_actors.steps, 2), 1) GROUP BY funnel_actors.actor_id ORDER BY funnel_actors.actor_id ASC) AS source @@ -3347,7 +3347,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors LEFT JOIN (SELECT groups.key AS key, groups.properties AS properties @@ -3404,7 +3404,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors LIMIT 100 SETTINGS readonly=2, max_execution_time=60, allow_experimental_object_type=1, @@ -3460,7 +3460,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors LEFT JOIN (SELECT groups.key AS key, groups.properties AS properties @@ -3517,7 +3517,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors LIMIT 100 SETTINGS readonly=2, max_execution_time=60, allow_experimental_object_type=1, @@ -3579,7 +3579,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors WHERE ifNull(equals(funnel_actors.steps, 2), 0) GROUP BY funnel_actors.actor_id ORDER BY funnel_actors.actor_id ASC) AS source @@ -3653,7 +3653,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors WHERE ifNull(notEquals(funnel_actors.steps, 2), 1) GROUP BY funnel_actors.actor_id ORDER BY funnel_actors.actor_id ASC) AS source @@ -3727,7 +3727,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors WHERE ifNull(equals(funnel_actors.steps, 2), 0) GROUP BY funnel_actors.actor_id ORDER BY funnel_actors.actor_id ASC) AS source @@ -3801,7 +3801,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors WHERE ifNull(notEquals(funnel_actors.steps, 2), 1) GROUP BY funnel_actors.actor_id ORDER BY funnel_actors.actor_id ASC) AS source @@ -3869,7 +3869,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors LEFT JOIN (SELECT groups.key AS key, groups.properties AS properties @@ -3926,7 +3926,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors LIMIT 100 SETTINGS readonly=2, max_execution_time=60, allow_experimental_object_type=1, @@ -3982,7 +3982,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors LEFT JOIN (SELECT groups.key AS key, groups.properties AS properties @@ -4039,7 +4039,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors LIMIT 100 SETTINGS readonly=2, max_execution_time=60, allow_experimental_object_type=1, @@ -4101,7 +4101,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors WHERE ifNull(equals(funnel_actors.steps, 2), 0) GROUP BY funnel_actors.actor_id ORDER BY funnel_actors.actor_id ASC) AS source @@ -4175,7 +4175,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors WHERE ifNull(notEquals(funnel_actors.steps, 2), 1) GROUP BY funnel_actors.actor_id ORDER BY funnel_actors.actor_id ASC) AS source @@ -4249,7 +4249,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors WHERE ifNull(equals(funnel_actors.steps, 2), 0) GROUP BY funnel_actors.actor_id ORDER BY funnel_actors.actor_id ASC) AS source @@ -4323,7 +4323,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors WHERE ifNull(notEquals(funnel_actors.steps, 2), 1) GROUP BY funnel_actors.actor_id ORDER BY funnel_actors.actor_id ASC) AS source @@ -4391,7 +4391,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors LEFT JOIN (SELECT groups.key AS key, groups.properties AS properties @@ -4448,7 +4448,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors LIMIT 100 SETTINGS readonly=2, max_execution_time=60, allow_experimental_object_type=1, @@ -4504,7 +4504,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors LEFT JOIN (SELECT groups.key AS key, groups.properties AS properties @@ -4561,7 +4561,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors LIMIT 100 SETTINGS readonly=2, max_execution_time=60, allow_experimental_object_type=1, @@ -4623,7 +4623,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors WHERE ifNull(equals(funnel_actors.steps, 2), 0) GROUP BY funnel_actors.actor_id ORDER BY funnel_actors.actor_id ASC) AS source @@ -4697,7 +4697,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors WHERE ifNull(notEquals(funnel_actors.steps, 2), 1) GROUP BY funnel_actors.actor_id ORDER BY funnel_actors.actor_id ASC) AS source @@ -4771,7 +4771,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors WHERE ifNull(equals(funnel_actors.steps, 2), 0) GROUP BY funnel_actors.actor_id ORDER BY funnel_actors.actor_id ASC) AS source @@ -4845,7 +4845,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors WHERE ifNull(notEquals(funnel_actors.steps, 2), 1) GROUP BY funnel_actors.actor_id ORDER BY funnel_actors.actor_id ASC) AS source @@ -4913,7 +4913,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors LEFT JOIN (SELECT groups.key AS key, groups.properties AS properties @@ -4970,7 +4970,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors LIMIT 100 SETTINGS readonly=2, max_execution_time=60, allow_experimental_object_type=1, @@ -5026,7 +5026,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors LEFT JOIN (SELECT groups.key AS key, groups.properties AS properties @@ -5083,7 +5083,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors LIMIT 100 SETTINGS readonly=2, max_execution_time=60, allow_experimental_object_type=1, @@ -5145,7 +5145,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors WHERE ifNull(equals(funnel_actors.steps, 2), 0) GROUP BY funnel_actors.actor_id ORDER BY funnel_actors.actor_id ASC) AS source @@ -5219,7 +5219,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors WHERE ifNull(notEquals(funnel_actors.steps, 2), 1) GROUP BY funnel_actors.actor_id ORDER BY funnel_actors.actor_id ASC) AS source @@ -5293,7 +5293,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors WHERE ifNull(equals(funnel_actors.steps, 2), 0) GROUP BY funnel_actors.actor_id ORDER BY funnel_actors.actor_id ASC) AS source @@ -5367,7 +5367,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors WHERE ifNull(notEquals(funnel_actors.steps, 2), 1) GROUP BY funnel_actors.actor_id ORDER BY funnel_actors.actor_id ASC) AS source @@ -5435,7 +5435,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors LEFT JOIN (SELECT groups.key AS key, groups.properties AS properties @@ -5492,7 +5492,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS funnel_actors + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS funnel_actors LIMIT 100 SETTINGS readonly=2, max_execution_time=60, allow_experimental_object_type=1, diff --git a/posthog/hogql_queries/insights/funnels/test/__snapshots__/test_funnel_persons_udf.ambr b/posthog/hogql_queries/insights/funnels/test/__snapshots__/test_funnel_persons_udf.ambr index bfe0f81e29959..370ed267d8b65 100644 --- a/posthog/hogql_queries/insights/funnels/test/__snapshots__/test_funnel_persons_udf.ambr +++ b/posthog/hogql_queries/insights/funnels/test/__snapshots__/test_funnel_persons_udf.ambr @@ -44,7 +44,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS source + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS source INNER JOIN (SELECT person.id AS id FROM person @@ -122,7 +122,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 1), 0) - ORDER BY aggregation_target ASC) AS source + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS source INNER JOIN (SELECT person.id AS id FROM person @@ -200,7 +200,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(equals(step_reached, 1), 0) - ORDER BY aggregation_target ASC) AS source + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS source INNER JOIN (SELECT person.id AS id FROM person diff --git a/posthog/hogql_queries/insights/funnels/test/__snapshots__/test_funnel_strict_persons_udf.ambr b/posthog/hogql_queries/insights/funnels/test/__snapshots__/test_funnel_strict_persons_udf.ambr index 9451ffa4690f6..328abd6df2e65 100644 --- a/posthog/hogql_queries/insights/funnels/test/__snapshots__/test_funnel_strict_persons_udf.ambr +++ b/posthog/hogql_queries/insights/funnels/test/__snapshots__/test_funnel_strict_persons_udf.ambr @@ -44,7 +44,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS source + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS source INNER JOIN (SELECT person.id AS id FROM person @@ -122,7 +122,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 1), 0) - ORDER BY aggregation_target ASC) AS source + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS source INNER JOIN (SELECT person.id AS id FROM person @@ -200,7 +200,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(equals(step_reached, 1), 0) - ORDER BY aggregation_target ASC) AS source + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS source INNER JOIN (SELECT person.id AS id FROM person diff --git a/posthog/hogql_queries/insights/funnels/test/__snapshots__/test_funnel_strict_udf.ambr b/posthog/hogql_queries/insights/funnels/test/__snapshots__/test_funnel_strict_udf.ambr index a61e3cb06ab29..d869459cb3485 100644 --- a/posthog/hogql_queries/insights/funnels/test/__snapshots__/test_funnel_strict_udf.ambr +++ b/posthog/hogql_queries/insights/funnels/test/__snapshots__/test_funnel_strict_udf.ambr @@ -62,7 +62,8 @@ GROUP BY breakdown ORDER BY step_2 DESC, step_1 DESC) GROUP BY final_prop - LIMIT 26 SETTINGS readonly=2, + LIMIT 26 SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge', + readonly=2, max_execution_time=60, allow_experimental_object_type=1, format_csv_allow_double_quotes=0, @@ -142,7 +143,8 @@ GROUP BY breakdown ORDER BY step_2 DESC, step_1 DESC) GROUP BY final_prop - LIMIT 26 SETTINGS readonly=2, + LIMIT 26 SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge', + readonly=2, max_execution_time=60, allow_experimental_object_type=1, format_csv_allow_double_quotes=0, @@ -215,7 +217,8 @@ GROUP BY breakdown ORDER BY step_2 DESC, step_1 DESC) GROUP BY final_prop - LIMIT 26 SETTINGS readonly=2, + LIMIT 26 SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge', + readonly=2, max_execution_time=60, allow_experimental_object_type=1, format_csv_allow_double_quotes=0, @@ -296,7 +299,8 @@ GROUP BY breakdown ORDER BY step_3 DESC, step_2 DESC, step_1 DESC) GROUP BY final_prop - LIMIT 26 SETTINGS readonly=2, + LIMIT 26 SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge', + readonly=2, max_execution_time=60, allow_experimental_object_type=1, format_csv_allow_double_quotes=0, @@ -377,7 +381,8 @@ GROUP BY breakdown ORDER BY step_3 DESC, step_2 DESC, step_1 DESC) GROUP BY final_prop - LIMIT 26 SETTINGS readonly=2, + LIMIT 26 SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge', + readonly=2, max_execution_time=60, allow_experimental_object_type=1, format_csv_allow_double_quotes=0, @@ -465,7 +470,8 @@ GROUP BY breakdown ORDER BY step_3 DESC, step_2 DESC, step_1 DESC) GROUP BY final_prop - LIMIT 26 SETTINGS readonly=2, + LIMIT 26 SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge', + readonly=2, max_execution_time=60, allow_experimental_object_type=1, format_csv_allow_double_quotes=0, @@ -539,7 +545,7 @@ HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE and(ifNull(greaterOrEquals(step_reached, 0), 0), ifNull(equals(arrayFlatten(array(breakdown)), arrayFlatten(array('finance'))), isNull(arrayFlatten(array(breakdown))) and isNull(arrayFlatten(array('finance'))))) - ORDER BY aggregation_target ASC) AS source + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS source INNER JOIN (SELECT person.id AS id FROM person @@ -622,7 +628,7 @@ HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE and(ifNull(greaterOrEquals(step_reached, 1), 0), ifNull(equals(arrayFlatten(array(breakdown)), arrayFlatten(array('finance'))), isNull(arrayFlatten(array(breakdown))) and isNull(arrayFlatten(array('finance'))))) - ORDER BY aggregation_target ASC) AS source + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS source INNER JOIN (SELECT person.id AS id FROM person @@ -705,7 +711,7 @@ HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE and(ifNull(greaterOrEquals(step_reached, 0), 0), ifNull(equals(arrayFlatten(array(breakdown)), arrayFlatten(array('technology'))), isNull(arrayFlatten(array(breakdown))) and isNull(arrayFlatten(array('technology'))))) - ORDER BY aggregation_target ASC) AS source + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS source INNER JOIN (SELECT person.id AS id FROM person @@ -788,7 +794,7 @@ HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE and(ifNull(greaterOrEquals(step_reached, 1), 0), ifNull(equals(arrayFlatten(array(breakdown)), arrayFlatten(array('technology'))), isNull(arrayFlatten(array(breakdown))) and isNull(arrayFlatten(array('technology'))))) - ORDER BY aggregation_target ASC) AS source + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS source INNER JOIN (SELECT person.id AS id FROM person diff --git a/posthog/hogql_queries/insights/funnels/test/__snapshots__/test_funnel_trends_actors_udf.ambr b/posthog/hogql_queries/insights/funnels/test/__snapshots__/test_funnel_trends_actors_udf.ambr index 3070a213fe4da..cb4021c05d56d 100644 --- a/posthog/hogql_queries/insights/funnels/test/__snapshots__/test_funnel_trends_actors_udf.ambr +++ b/posthog/hogql_queries/insights/funnels/test/__snapshots__/test_funnel_trends_actors_udf.ambr @@ -42,7 +42,7 @@ GROUP BY aggregation_target SETTINGS date_time_output_format='iso', date_time_input_format='best_effort') WHERE and(ifNull(equals(success_bool, 1), 0), ifNull(equals(entrance_period_start, toDateTime64('2021-05-01 00:00:00.000000', 6, 'UTC')), 0)) - ORDER BY aggregation_target ASC) AS source + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS source INNER JOIN (SELECT person.id AS id FROM person @@ -118,7 +118,7 @@ GROUP BY aggregation_target SETTINGS date_time_output_format='iso', date_time_input_format='best_effort') WHERE and(ifNull(notEquals(success_bool, 1), 1), ifNull(equals(entrance_period_start, toDateTime64('2021-05-01 00:00:00.000000', 6, 'UTC')), 0)) - ORDER BY aggregation_target ASC) AS source + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS source INNER JOIN (SELECT person.id AS id FROM person @@ -194,7 +194,7 @@ GROUP BY aggregation_target SETTINGS date_time_output_format='iso', date_time_input_format='best_effort') WHERE and(ifNull(equals(success_bool, 1), 0), ifNull(equals(entrance_period_start, toDateTime64('2021-05-01 00:00:00.000000', 6, 'UTC')), 0)) - ORDER BY aggregation_target ASC) AS source + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS source INNER JOIN (SELECT person.id AS id FROM person diff --git a/posthog/hogql_queries/insights/funnels/test/__snapshots__/test_funnel_trends_udf.ambr b/posthog/hogql_queries/insights/funnels/test/__snapshots__/test_funnel_trends_udf.ambr index 90889f847b87b..b0b220a50a0bb 100644 --- a/posthog/hogql_queries/insights/funnels/test/__snapshots__/test_funnel_trends_udf.ambr +++ b/posthog/hogql_queries/insights/funnels/test/__snapshots__/test_funnel_trends_udf.ambr @@ -41,7 +41,8 @@ GROUP BY entrance_period_start, data.breakdown ORDER BY entrance_period_start ASC - LIMIT 1000 SETTINGS readonly=2, + LIMIT 1000 SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge', + readonly=2, max_execution_time=60, allow_experimental_object_type=1, format_csv_allow_double_quotes=0, @@ -93,7 +94,8 @@ GROUP BY entrance_period_start, data.breakdown ORDER BY entrance_period_start ASC - LIMIT 1000 SETTINGS readonly=2, + LIMIT 1000 SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge', + readonly=2, max_execution_time=60, allow_experimental_object_type=1, format_csv_allow_double_quotes=0, @@ -145,7 +147,8 @@ GROUP BY entrance_period_start, data.breakdown ORDER BY entrance_period_start ASC - LIMIT 1000 SETTINGS readonly=2, + LIMIT 1000 SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge', + readonly=2, max_execution_time=60, allow_experimental_object_type=1, format_csv_allow_double_quotes=0, diff --git a/posthog/hogql_queries/insights/funnels/test/__snapshots__/test_funnel_udf.ambr b/posthog/hogql_queries/insights/funnels/test/__snapshots__/test_funnel_udf.ambr index 76914332b11c4..ea0939ad56226 100644 --- a/posthog/hogql_queries/insights/funnels/test/__snapshots__/test_funnel_udf.ambr +++ b/posthog/hogql_queries/insights/funnels/test/__snapshots__/test_funnel_udf.ambr @@ -53,7 +53,8 @@ GROUP BY breakdown ORDER BY step_3 DESC, step_2 DESC, step_1 DESC) GROUP BY final_prop - LIMIT 100 SETTINGS readonly=2, + LIMIT 100 SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge', + readonly=2, max_execution_time=60, allow_experimental_object_type=1, format_csv_allow_double_quotes=0, @@ -104,7 +105,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 1), 0) - ORDER BY aggregation_target ASC) AS source + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS source INNER JOIN (SELECT argMax(toTimeZone(person.created_at, 'UTC'), person.version) AS created_at, person.id AS id @@ -187,7 +188,8 @@ GROUP BY breakdown ORDER BY step_3 DESC, step_2 DESC, step_1 DESC) GROUP BY final_prop - LIMIT 100 SETTINGS readonly=2, + LIMIT 100 SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge', + readonly=2, max_execution_time=60, allow_experimental_object_type=1, format_csv_allow_double_quotes=0, @@ -268,7 +270,8 @@ GROUP BY breakdown ORDER BY step_2 DESC, step_1 DESC) GROUP BY final_prop - LIMIT 100 SETTINGS readonly=2, + LIMIT 100 SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge', + readonly=2, max_execution_time=60, allow_experimental_object_type=1, format_csv_allow_double_quotes=0, @@ -343,7 +346,8 @@ GROUP BY breakdown ORDER BY step_3 DESC, step_2 DESC, step_1 DESC) GROUP BY final_prop - LIMIT 100 SETTINGS readonly=2, + LIMIT 100 SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge', + readonly=2, max_execution_time=60, allow_experimental_object_type=1, format_csv_allow_double_quotes=0, @@ -405,7 +409,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 0), 0) - ORDER BY aggregation_target ASC) AS source + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS source INNER JOIN (SELECT argMax(toTimeZone(person.created_at, 'UTC'), person.version) AS created_at, person.id AS id @@ -477,7 +481,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 1), 0) - ORDER BY aggregation_target ASC) AS source + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS source INNER JOIN (SELECT argMax(toTimeZone(person.created_at, 'UTC'), person.version) AS created_at, person.id AS id @@ -549,7 +553,7 @@ GROUP BY aggregation_target HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE ifNull(greaterOrEquals(step_reached, 2), 0) - ORDER BY aggregation_target ASC) AS source + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS source INNER JOIN (SELECT argMax(toTimeZone(person.created_at, 'UTC'), person.version) AS created_at, person.id AS id @@ -629,7 +633,8 @@ GROUP BY breakdown ORDER BY step_2 DESC, step_1 DESC) GROUP BY final_prop - LIMIT 100 SETTINGS readonly=2, + LIMIT 100 SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge', + readonly=2, max_execution_time=60, allow_experimental_object_type=1, format_csv_allow_double_quotes=0, @@ -687,7 +692,8 @@ GROUP BY breakdown ORDER BY step_2 DESC, step_1 DESC) GROUP BY final_prop - LIMIT 100 SETTINGS readonly=2, + LIMIT 100 SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge', + readonly=2, max_execution_time=60, allow_experimental_object_type=1, format_csv_allow_double_quotes=0, @@ -760,7 +766,8 @@ GROUP BY breakdown ORDER BY step_2 DESC, step_1 DESC) GROUP BY final_prop - LIMIT 26 SETTINGS readonly=2, + LIMIT 26 SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge', + readonly=2, max_execution_time=60, allow_experimental_object_type=1, format_csv_allow_double_quotes=0, @@ -840,7 +847,8 @@ GROUP BY breakdown ORDER BY step_2 DESC, step_1 DESC) GROUP BY final_prop - LIMIT 26 SETTINGS readonly=2, + LIMIT 26 SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge', + readonly=2, max_execution_time=60, allow_experimental_object_type=1, format_csv_allow_double_quotes=0, @@ -913,7 +921,8 @@ GROUP BY breakdown ORDER BY step_2 DESC, step_1 DESC) GROUP BY final_prop - LIMIT 26 SETTINGS readonly=2, + LIMIT 26 SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge', + readonly=2, max_execution_time=60, allow_experimental_object_type=1, format_csv_allow_double_quotes=0, @@ -994,7 +1003,8 @@ GROUP BY breakdown ORDER BY step_3 DESC, step_2 DESC, step_1 DESC) GROUP BY final_prop - LIMIT 26 SETTINGS readonly=2, + LIMIT 26 SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge', + readonly=2, max_execution_time=60, allow_experimental_object_type=1, format_csv_allow_double_quotes=0, @@ -1075,7 +1085,8 @@ GROUP BY breakdown ORDER BY step_3 DESC, step_2 DESC, step_1 DESC) GROUP BY final_prop - LIMIT 26 SETTINGS readonly=2, + LIMIT 26 SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge', + readonly=2, max_execution_time=60, allow_experimental_object_type=1, format_csv_allow_double_quotes=0, @@ -1163,7 +1174,8 @@ GROUP BY breakdown ORDER BY step_3 DESC, step_2 DESC, step_1 DESC) GROUP BY final_prop - LIMIT 26 SETTINGS readonly=2, + LIMIT 26 SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge', + readonly=2, max_execution_time=60, allow_experimental_object_type=1, format_csv_allow_double_quotes=0, @@ -1237,7 +1249,7 @@ HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE and(ifNull(greaterOrEquals(step_reached, 0), 0), ifNull(equals(arrayFlatten(array(breakdown)), arrayFlatten(array('finance'))), isNull(arrayFlatten(array(breakdown))) and isNull(arrayFlatten(array('finance'))))) - ORDER BY aggregation_target ASC) AS source + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS source INNER JOIN (SELECT person.id AS id FROM person @@ -1320,7 +1332,7 @@ HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE and(ifNull(greaterOrEquals(step_reached, 1), 0), ifNull(equals(arrayFlatten(array(breakdown)), arrayFlatten(array('finance'))), isNull(arrayFlatten(array(breakdown))) and isNull(arrayFlatten(array('finance'))))) - ORDER BY aggregation_target ASC) AS source + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS source INNER JOIN (SELECT person.id AS id FROM person @@ -1403,7 +1415,7 @@ HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE and(ifNull(greaterOrEquals(step_reached, 0), 0), ifNull(equals(arrayFlatten(array(breakdown)), arrayFlatten(array('technology'))), isNull(arrayFlatten(array(breakdown))) and isNull(arrayFlatten(array('technology'))))) - ORDER BY aggregation_target ASC) AS source + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS source INNER JOIN (SELECT person.id AS id FROM person @@ -1486,7 +1498,7 @@ HAVING ifNull(greaterOrEquals(step_reached, 0), 0)) WHERE and(ifNull(greaterOrEquals(step_reached, 1), 0), ifNull(equals(arrayFlatten(array(breakdown)), arrayFlatten(array('technology'))), isNull(arrayFlatten(array(breakdown))) and isNull(arrayFlatten(array('technology'))))) - ORDER BY aggregation_target ASC) AS source + ORDER BY aggregation_target ASC SETTINGS join_algorithm='direct,parallel_hash,hash,full_sorting_merge') AS source INNER JOIN (SELECT person.id AS id FROM person diff --git a/posthog/hogql_queries/insights/trends/test/__snapshots__/test_trends.ambr b/posthog/hogql_queries/insights/trends/test/__snapshots__/test_trends.ambr index 8d72ab1b70121..4b096b060262e 100644 --- a/posthog/hogql_queries/insights/trends/test/__snapshots__/test_trends.ambr +++ b/posthog/hogql_queries/insights/trends/test/__snapshots__/test_trends.ambr @@ -851,49 +851,14 @@ # --- # name: TestTrends.test_dau_with_breakdown_filtering_with_sampling.1 ''' - SELECT groupArray(1)(date)[1] AS date, - arrayFold((acc, x) -> arrayMap(i -> plus(acc[i], x[i]), range(1, plus(length(date), 1))), groupArray(ifNull(total, 0)), arrayWithConstant(length(date), reinterpretAsFloat64(0))) AS total, - if(ifNull(ifNull(greaterOrEquals(row_number, 25), 0), 0), '$$_posthog_breakdown_other_$$', breakdown_value) AS breakdown_value - FROM - (SELECT arrayMap(number -> plus(toStartOfDay(assumeNotNull(parseDateTime64BestEffortOrNull('2019-12-28 00:00:00', 6, 'UTC'))), toIntervalDay(number)), range(0, plus(coalesce(dateDiff('day', toStartOfDay(assumeNotNull(parseDateTime64BestEffortOrNull('2019-12-28 00:00:00', 6, 'UTC'))), toStartOfDay(assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-04 23:59:59', 6, 'UTC'))))), 1))) AS date, - arrayMap(_match_date -> arraySum(arraySlice(groupArray(ifNull(count, 0)), indexOf(groupArray(day_start) AS _days_for_count, _match_date) AS _index, plus(minus(arrayLastIndex(x -> ifNull(equals(x, _match_date), isNull(x) - and isNull(_match_date)), _days_for_count), _index), 1))), date) AS total, - breakdown_value AS breakdown_value, - rowNumberInAllBlocks() AS row_number - FROM - (SELECT sum(total) AS count, - day_start AS day_start, - breakdown_value AS breakdown_value - FROM - (SELECT count(DISTINCT if(not(empty(e__override.distinct_id)), e__override.person_id, e.person_id)) AS total, - toStartOfDay(toTimeZone(e.timestamp, 'UTC')) AS day_start, - ifNull(nullIf(toString(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(e.properties, '$some_property'), ''), 'null'), '^"|"$', '')), ''), '$$_posthog_breakdown_null_$$') AS breakdown_value - FROM events AS e SAMPLE 1.0 - LEFT OUTER JOIN - (SELECT argMax(person_distinct_id_overrides.person_id, person_distinct_id_overrides.version) AS person_id, - person_distinct_id_overrides.distinct_id AS distinct_id - FROM person_distinct_id_overrides - WHERE equals(person_distinct_id_overrides.team_id, 99999) - GROUP BY person_distinct_id_overrides.distinct_id - HAVING ifNull(equals(argMax(person_distinct_id_overrides.is_deleted, person_distinct_id_overrides.version), 0), 0) SETTINGS optimize_aggregation_in_order=1) AS e__override ON equals(e.distinct_id, e__override.distinct_id) - WHERE and(equals(e.team_id, 99999), greaterOrEquals(toTimeZone(e.timestamp, 'UTC'), toStartOfDay(assumeNotNull(parseDateTime64BestEffortOrNull('2019-12-28 00:00:00', 6, 'UTC')))), lessOrEquals(toTimeZone(e.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-04 23:59:59', 6, 'UTC'))), equals(e.event, 'sign up')) - GROUP BY day_start, - breakdown_value) - GROUP BY day_start, - breakdown_value - ORDER BY day_start ASC, breakdown_value ASC) - GROUP BY breakdown_value - ORDER BY if(ifNull(equals(breakdown_value, '$$_posthog_breakdown_other_$$'), 0), 2, if(ifNull(equals(breakdown_value, '$$_posthog_breakdown_null_$$'), 0), 1, 0)) ASC, arraySum(total) DESC, breakdown_value ASC) - WHERE isNotNull(breakdown_value) - GROUP BY breakdown_value - ORDER BY if(ifNull(equals(breakdown_value, '$$_posthog_breakdown_other_$$'), 0), 2, if(ifNull(equals(breakdown_value, '$$_posthog_breakdown_null_$$'), 0), 1, 0)) ASC, arraySum(total) DESC, breakdown_value ASC - LIMIT 50000 SETTINGS readonly=2, - max_execution_time=60, - allow_experimental_object_type=1, - format_csv_allow_double_quotes=0, - max_ast_elements=4000000, - max_expanded_ast_elements=4000000, - max_bytes_before_external_group_by=0 + /* celery:posthog.tasks.tasks.sync_insight_caching_state */ + SELECT team_id, + date_diff('second', max(timestamp), now()) AS age + FROM events + WHERE timestamp > date_sub(DAY, 3, now()) + AND timestamp < now() + GROUP BY team_id + ORDER BY age; ''' # --- # name: TestTrends.test_dau_with_breakdown_filtering_with_sampling.10 @@ -944,10 +909,10 @@ (SELECT argMax(person_distinct_id_overrides.person_id, person_distinct_id_overrides.version) AS person_id, person_distinct_id_overrides.distinct_id AS distinct_id FROM person_distinct_id_overrides - WHERE equals(person_distinct_id_overrides.team_id, 2) + WHERE equals(person_distinct_id_overrides.team_id, 99999) GROUP BY person_distinct_id_overrides.distinct_id HAVING ifNull(equals(argMax(person_distinct_id_overrides.is_deleted, person_distinct_id_overrides.version), 0), 0) SETTINGS optimize_aggregation_in_order=1) AS e__override ON equals(e.distinct_id, e__override.distinct_id) - WHERE and(equals(e.team_id, 2), greaterOrEquals(toTimeZone(e.timestamp, 'UTC'), toStartOfDay(assumeNotNull(parseDateTime64BestEffortOrNull('2019-12-28 00:00:00', 6, 'UTC')))), lessOrEquals(toTimeZone(e.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-04 23:59:59', 6, 'UTC'))), equals(e.event, 'sign up')) + WHERE and(equals(e.team_id, 99999), greaterOrEquals(toTimeZone(e.timestamp, 'UTC'), toStartOfDay(assumeNotNull(parseDateTime64BestEffortOrNull('2019-12-28 00:00:00', 6, 'UTC')))), lessOrEquals(toTimeZone(e.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-04 23:59:59', 6, 'UTC'))), equals(e.event, 'sign up')) GROUP BY day_start, breakdown_value) GROUP BY day_start, @@ -991,10 +956,10 @@ (SELECT argMax(person_distinct_id_overrides.person_id, person_distinct_id_overrides.version) AS person_id, person_distinct_id_overrides.distinct_id AS distinct_id FROM person_distinct_id_overrides - WHERE equals(person_distinct_id_overrides.team_id, 2) + WHERE equals(person_distinct_id_overrides.team_id, 99999) GROUP BY person_distinct_id_overrides.distinct_id HAVING ifNull(equals(argMax(person_distinct_id_overrides.is_deleted, person_distinct_id_overrides.version), 0), 0) SETTINGS optimize_aggregation_in_order=1) AS e__override ON equals(e.distinct_id, e__override.distinct_id) - WHERE and(equals(e.team_id, 2), greaterOrEquals(toTimeZone(e.timestamp, 'UTC'), toStartOfDay(assumeNotNull(parseDateTime64BestEffortOrNull('2019-12-28 00:00:00', 6, 'UTC')))), lessOrEquals(toTimeZone(e.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-04 23:59:59', 6, 'UTC'))), equals(e.event, 'sign up')) + WHERE and(equals(e.team_id, 99999), greaterOrEquals(toTimeZone(e.timestamp, 'UTC'), toStartOfDay(assumeNotNull(parseDateTime64BestEffortOrNull('2019-12-28 00:00:00', 6, 'UTC')))), lessOrEquals(toTimeZone(e.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-04 23:59:59', 6, 'UTC'))), equals(e.event, 'sign up')) GROUP BY day_start, breakdown_value) GROUP BY day_start, @@ -1038,10 +1003,10 @@ (SELECT argMax(person_distinct_id_overrides.person_id, person_distinct_id_overrides.version) AS person_id, person_distinct_id_overrides.distinct_id AS distinct_id FROM person_distinct_id_overrides - WHERE equals(person_distinct_id_overrides.team_id, 2) + WHERE equals(person_distinct_id_overrides.team_id, 99999) GROUP BY person_distinct_id_overrides.distinct_id HAVING ifNull(equals(argMax(person_distinct_id_overrides.is_deleted, person_distinct_id_overrides.version), 0), 0) SETTINGS optimize_aggregation_in_order=1) AS e__override ON equals(e.distinct_id, e__override.distinct_id) - WHERE and(equals(e.team_id, 2), greaterOrEquals(toTimeZone(e.timestamp, 'UTC'), toStartOfDay(assumeNotNull(parseDateTime64BestEffortOrNull('2019-12-28 00:00:00', 6, 'UTC')))), lessOrEquals(toTimeZone(e.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-04 23:59:59', 6, 'UTC'))), equals(e.event, 'sign up')) + WHERE and(equals(e.team_id, 99999), greaterOrEquals(toTimeZone(e.timestamp, 'UTC'), toStartOfDay(assumeNotNull(parseDateTime64BestEffortOrNull('2019-12-28 00:00:00', 6, 'UTC')))), lessOrEquals(toTimeZone(e.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-04 23:59:59', 6, 'UTC'))), equals(e.event, 'sign up')) GROUP BY day_start, breakdown_value_1) GROUP BY day_start, @@ -1085,10 +1050,10 @@ (SELECT argMax(person_distinct_id_overrides.person_id, person_distinct_id_overrides.version) AS person_id, person_distinct_id_overrides.distinct_id AS distinct_id FROM person_distinct_id_overrides - WHERE equals(person_distinct_id_overrides.team_id, 2) + WHERE equals(person_distinct_id_overrides.team_id, 99999) GROUP BY person_distinct_id_overrides.distinct_id HAVING ifNull(equals(argMax(person_distinct_id_overrides.is_deleted, person_distinct_id_overrides.version), 0), 0) SETTINGS optimize_aggregation_in_order=1) AS e__override ON equals(e.distinct_id, e__override.distinct_id) - WHERE and(equals(e.team_id, 2), greaterOrEquals(toTimeZone(e.timestamp, 'UTC'), toStartOfDay(assumeNotNull(parseDateTime64BestEffortOrNull('2019-12-28 00:00:00', 6, 'UTC')))), lessOrEquals(toTimeZone(e.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-04 23:59:59', 6, 'UTC'))), equals(e.event, 'sign up')) + WHERE and(equals(e.team_id, 99999), greaterOrEquals(toTimeZone(e.timestamp, 'UTC'), toStartOfDay(assumeNotNull(parseDateTime64BestEffortOrNull('2019-12-28 00:00:00', 6, 'UTC')))), lessOrEquals(toTimeZone(e.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-04 23:59:59', 6, 'UTC'))), equals(e.event, 'sign up')) GROUP BY day_start, breakdown_value_1) GROUP BY day_start, @@ -1110,143 +1075,38 @@ # --- # name: TestTrends.test_dau_with_breakdown_filtering_with_sampling.2 ''' - SELECT groupArray(1)(date)[1] AS date, - arrayFold((acc, x) -> arrayMap(i -> plus(acc[i], x[i]), range(1, plus(length(date), 1))), groupArray(ifNull(total, 0)), arrayWithConstant(length(date), reinterpretAsFloat64(0))) AS total, - if(ifNull(ifNull(greaterOrEquals(row_number, 25), 0), 0), '$$_posthog_breakdown_other_$$', breakdown_value) AS breakdown_value - FROM - (SELECT arrayMap(number -> plus(toStartOfDay(assumeNotNull(parseDateTime64BestEffortOrNull('2019-12-28 00:00:00', 6, 'UTC'))), toIntervalDay(number)), range(0, plus(coalesce(dateDiff('day', toStartOfDay(assumeNotNull(parseDateTime64BestEffortOrNull('2019-12-28 00:00:00', 6, 'UTC'))), toStartOfDay(assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-04 23:59:59', 6, 'UTC'))))), 1))) AS date, - arrayMap(_match_date -> arraySum(arraySlice(groupArray(ifNull(count, 0)), indexOf(groupArray(day_start) AS _days_for_count, _match_date) AS _index, plus(minus(arrayLastIndex(x -> ifNull(equals(x, _match_date), isNull(x) - and isNull(_match_date)), _days_for_count), _index), 1))), date) AS total, - breakdown_value AS breakdown_value, - rowNumberInAllBlocks() AS row_number - FROM - (SELECT sum(total) AS count, - day_start AS day_start, - breakdown_value AS breakdown_value - FROM - (SELECT count(DISTINCT if(not(empty(e__override.distinct_id)), e__override.person_id, e.person_id)) AS total, - toStartOfDay(toTimeZone(e.timestamp, 'UTC')) AS day_start, - ifNull(nullIf(toString(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(e.properties, '$some_property'), ''), 'null'), '^"|"$', '')), ''), '$$_posthog_breakdown_null_$$') AS breakdown_value - FROM events AS e SAMPLE 1.0 - LEFT OUTER JOIN - (SELECT argMax(person_distinct_id_overrides.person_id, person_distinct_id_overrides.version) AS person_id, - person_distinct_id_overrides.distinct_id AS distinct_id - FROM person_distinct_id_overrides - WHERE equals(person_distinct_id_overrides.team_id, 99999) - GROUP BY person_distinct_id_overrides.distinct_id - HAVING ifNull(equals(argMax(person_distinct_id_overrides.is_deleted, person_distinct_id_overrides.version), 0), 0) SETTINGS optimize_aggregation_in_order=1) AS e__override ON equals(e.distinct_id, e__override.distinct_id) - WHERE and(equals(e.team_id, 99999), greaterOrEquals(toTimeZone(e.timestamp, 'UTC'), toStartOfDay(assumeNotNull(parseDateTime64BestEffortOrNull('2019-12-28 00:00:00', 6, 'UTC')))), lessOrEquals(toTimeZone(e.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-04 23:59:59', 6, 'UTC'))), equals(e.event, 'sign up')) - GROUP BY day_start, - breakdown_value) - GROUP BY day_start, - breakdown_value - ORDER BY day_start ASC, breakdown_value ASC) - GROUP BY breakdown_value - ORDER BY if(ifNull(equals(breakdown_value, '$$_posthog_breakdown_other_$$'), 0), 2, if(ifNull(equals(breakdown_value, '$$_posthog_breakdown_null_$$'), 0), 1, 0)) ASC, arraySum(total) DESC, breakdown_value ASC) - WHERE isNotNull(breakdown_value) - GROUP BY breakdown_value - ORDER BY if(ifNull(equals(breakdown_value, '$$_posthog_breakdown_other_$$'), 0), 2, if(ifNull(equals(breakdown_value, '$$_posthog_breakdown_null_$$'), 0), 1, 0)) ASC, arraySum(total) DESC, breakdown_value ASC - LIMIT 50000 SETTINGS readonly=2, - max_execution_time=60, - allow_experimental_object_type=1, - format_csv_allow_double_quotes=0, - max_ast_elements=4000000, - max_expanded_ast_elements=4000000, - max_bytes_before_external_group_by=0 + /* celery:posthog.tasks.tasks.sync_insight_caching_state */ + SELECT team_id, + date_diff('second', max(timestamp), now()) AS age + FROM events + WHERE timestamp > date_sub(DAY, 3, now()) + AND timestamp < now() + GROUP BY team_id + ORDER BY age; ''' # --- # name: TestTrends.test_dau_with_breakdown_filtering_with_sampling.3 ''' - SELECT groupArray(1)(date)[1] AS date, - arrayFold((acc, x) -> arrayMap(i -> plus(acc[i], x[i]), range(1, plus(length(date), 1))), groupArray(ifNull(total, 0)), arrayWithConstant(length(date), reinterpretAsFloat64(0))) AS total, - arrayMap(i -> if(ifNull(ifNull(greaterOrEquals(row_number, 25), 0), 0), '$$_posthog_breakdown_other_$$', i), breakdown_value) AS breakdown_value - FROM - (SELECT arrayMap(number -> plus(toStartOfDay(assumeNotNull(parseDateTime64BestEffortOrNull('2019-12-28 00:00:00', 6, 'UTC'))), toIntervalDay(number)), range(0, plus(coalesce(dateDiff('day', toStartOfDay(assumeNotNull(parseDateTime64BestEffortOrNull('2019-12-28 00:00:00', 6, 'UTC'))), toStartOfDay(assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-04 23:59:59', 6, 'UTC'))))), 1))) AS date, - arrayMap(_match_date -> arraySum(arraySlice(groupArray(ifNull(count, 0)), indexOf(groupArray(day_start) AS _days_for_count, _match_date) AS _index, plus(minus(arrayLastIndex(x -> ifNull(equals(x, _match_date), isNull(x) - and isNull(_match_date)), _days_for_count), _index), 1))), date) AS total, - breakdown_value AS breakdown_value, - rowNumberInAllBlocks() AS row_number - FROM - (SELECT sum(total) AS count, - day_start AS day_start, - [ifNull(toString(breakdown_value_1), '$$_posthog_breakdown_null_$$')] AS breakdown_value - FROM - (SELECT count(DISTINCT if(not(empty(e__override.distinct_id)), e__override.person_id, e.person_id)) AS total, - toStartOfDay(toTimeZone(e.timestamp, 'UTC')) AS day_start, - ifNull(nullIf(toString(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(e.properties, '$some_property'), ''), 'null'), '^"|"$', '')), ''), '$$_posthog_breakdown_null_$$') AS breakdown_value_1 - FROM events AS e SAMPLE 1.0 - LEFT OUTER JOIN - (SELECT argMax(person_distinct_id_overrides.person_id, person_distinct_id_overrides.version) AS person_id, - person_distinct_id_overrides.distinct_id AS distinct_id - FROM person_distinct_id_overrides - WHERE equals(person_distinct_id_overrides.team_id, 99999) - GROUP BY person_distinct_id_overrides.distinct_id - HAVING ifNull(equals(argMax(person_distinct_id_overrides.is_deleted, person_distinct_id_overrides.version), 0), 0) SETTINGS optimize_aggregation_in_order=1) AS e__override ON equals(e.distinct_id, e__override.distinct_id) - WHERE and(equals(e.team_id, 99999), greaterOrEquals(toTimeZone(e.timestamp, 'UTC'), toStartOfDay(assumeNotNull(parseDateTime64BestEffortOrNull('2019-12-28 00:00:00', 6, 'UTC')))), lessOrEquals(toTimeZone(e.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-04 23:59:59', 6, 'UTC'))), equals(e.event, 'sign up')) - GROUP BY day_start, - breakdown_value_1) - GROUP BY day_start, - breakdown_value_1 - ORDER BY day_start ASC, breakdown_value ASC) - GROUP BY breakdown_value - ORDER BY if(has(breakdown_value, '$$_posthog_breakdown_other_$$'), 2, if(has(breakdown_value, '$$_posthog_breakdown_null_$$'), 1, 0)) ASC, arraySum(total) DESC, breakdown_value ASC) - WHERE arrayExists(x -> isNotNull(x), breakdown_value) - GROUP BY breakdown_value - ORDER BY if(has(breakdown_value, '$$_posthog_breakdown_other_$$'), 2, if(has(breakdown_value, '$$_posthog_breakdown_null_$$'), 1, 0)) ASC, arraySum(total) DESC, breakdown_value ASC - LIMIT 50000 SETTINGS readonly=2, - max_execution_time=60, - allow_experimental_object_type=1, - format_csv_allow_double_quotes=0, - max_ast_elements=4000000, - max_expanded_ast_elements=4000000, - max_bytes_before_external_group_by=0 + /* celery:posthog.tasks.tasks.sync_insight_caching_state */ + SELECT team_id, + date_diff('second', max(timestamp), now()) AS age + FROM events + WHERE timestamp > date_sub(DAY, 3, now()) + AND timestamp < now() + GROUP BY team_id + ORDER BY age; ''' # --- # name: TestTrends.test_dau_with_breakdown_filtering_with_sampling.4 ''' - SELECT groupArray(1)(date)[1] AS date, - arrayFold((acc, x) -> arrayMap(i -> plus(acc[i], x[i]), range(1, plus(length(date), 1))), groupArray(ifNull(total, 0)), arrayWithConstant(length(date), reinterpretAsFloat64(0))) AS total, - arrayMap(i -> if(ifNull(ifNull(greaterOrEquals(row_number, 25), 0), 0), '$$_posthog_breakdown_other_$$', i), breakdown_value) AS breakdown_value - FROM - (SELECT arrayMap(number -> plus(toStartOfDay(assumeNotNull(parseDateTime64BestEffortOrNull('2019-12-28 00:00:00', 6, 'UTC'))), toIntervalDay(number)), range(0, plus(coalesce(dateDiff('day', toStartOfDay(assumeNotNull(parseDateTime64BestEffortOrNull('2019-12-28 00:00:00', 6, 'UTC'))), toStartOfDay(assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-04 23:59:59', 6, 'UTC'))))), 1))) AS date, - arrayMap(_match_date -> arraySum(arraySlice(groupArray(ifNull(count, 0)), indexOf(groupArray(day_start) AS _days_for_count, _match_date) AS _index, plus(minus(arrayLastIndex(x -> ifNull(equals(x, _match_date), isNull(x) - and isNull(_match_date)), _days_for_count), _index), 1))), date) AS total, - breakdown_value AS breakdown_value, - rowNumberInAllBlocks() AS row_number - FROM - (SELECT sum(total) AS count, - day_start AS day_start, - [ifNull(toString(breakdown_value_1), '$$_posthog_breakdown_null_$$')] AS breakdown_value - FROM - (SELECT count(DISTINCT if(not(empty(e__override.distinct_id)), e__override.person_id, e.person_id)) AS total, - toStartOfDay(toTimeZone(e.timestamp, 'UTC')) AS day_start, - ifNull(nullIf(toString(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(e.properties, '$some_property'), ''), 'null'), '^"|"$', '')), ''), '$$_posthog_breakdown_null_$$') AS breakdown_value_1 - FROM events AS e SAMPLE 1.0 - LEFT OUTER JOIN - (SELECT argMax(person_distinct_id_overrides.person_id, person_distinct_id_overrides.version) AS person_id, - person_distinct_id_overrides.distinct_id AS distinct_id - FROM person_distinct_id_overrides - WHERE equals(person_distinct_id_overrides.team_id, 99999) - GROUP BY person_distinct_id_overrides.distinct_id - HAVING ifNull(equals(argMax(person_distinct_id_overrides.is_deleted, person_distinct_id_overrides.version), 0), 0) SETTINGS optimize_aggregation_in_order=1) AS e__override ON equals(e.distinct_id, e__override.distinct_id) - WHERE and(equals(e.team_id, 99999), greaterOrEquals(toTimeZone(e.timestamp, 'UTC'), toStartOfDay(assumeNotNull(parseDateTime64BestEffortOrNull('2019-12-28 00:00:00', 6, 'UTC')))), lessOrEquals(toTimeZone(e.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-04 23:59:59', 6, 'UTC'))), equals(e.event, 'sign up')) - GROUP BY day_start, - breakdown_value_1) - GROUP BY day_start, - breakdown_value_1 - ORDER BY day_start ASC, breakdown_value ASC) - GROUP BY breakdown_value - ORDER BY if(has(breakdown_value, '$$_posthog_breakdown_other_$$'), 2, if(has(breakdown_value, '$$_posthog_breakdown_null_$$'), 1, 0)) ASC, arraySum(total) DESC, breakdown_value ASC) - WHERE arrayExists(x -> isNotNull(x), breakdown_value) - GROUP BY breakdown_value - ORDER BY if(has(breakdown_value, '$$_posthog_breakdown_other_$$'), 2, if(has(breakdown_value, '$$_posthog_breakdown_null_$$'), 1, 0)) ASC, arraySum(total) DESC, breakdown_value ASC - LIMIT 50000 SETTINGS readonly=2, - max_execution_time=60, - allow_experimental_object_type=1, - format_csv_allow_double_quotes=0, - max_ast_elements=4000000, - max_expanded_ast_elements=4000000, - max_bytes_before_external_group_by=0 + /* celery:posthog.tasks.tasks.sync_insight_caching_state */ + SELECT team_id, + date_diff('second', max(timestamp), now()) AS age + FROM events + WHERE timestamp > date_sub(DAY, 3, now()) + AND timestamp < now() + GROUP BY team_id + ORDER BY age; ''' # --- # name: TestTrends.test_dau_with_breakdown_filtering_with_sampling.5 diff --git a/posthog/hogql_queries/test/__snapshots__/test_error_tracking_query_runner.ambr b/posthog/hogql_queries/test/__snapshots__/test_error_tracking_query_runner.ambr index dd53fefcdb46c..a23c828bb8864 100644 --- a/posthog/hogql_queries/test/__snapshots__/test_error_tracking_query_runner.ambr +++ b/posthog/hogql_queries/test/__snapshots__/test_error_tracking_query_runner.ambr @@ -134,6 +134,50 @@ max_bytes_before_external_group_by=0 ''' # --- +# name: TestErrorTrackingQueryRunner.test_ordering + ''' + SELECT count(DISTINCT events.uuid) AS occurrences, + count(DISTINCT events.`$session_id`) AS sessions, + count(DISTINCT events.distinct_id) AS users, + max(toTimeZone(events.timestamp, 'UTC')) AS last_seen, + min(toTimeZone(events.timestamp, 'UTC')) AS first_seen, + replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$exception_issue_id'), ''), 'null'), '^"|"$', '') AS id + FROM events + WHERE and(equals(events.team_id, 99999), equals(events.event, '$exception'), isNotNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$exception_issue_id'), ''), 'null'), '^"|"$', '')), 1) + GROUP BY replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$exception_issue_id'), ''), 'null'), '^"|"$', '') + ORDER BY last_seen DESC + LIMIT 101 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0 + ''' +# --- +# name: TestErrorTrackingQueryRunner.test_ordering.1 + ''' + SELECT count(DISTINCT events.uuid) AS occurrences, + count(DISTINCT events.`$session_id`) AS sessions, + count(DISTINCT events.distinct_id) AS users, + max(toTimeZone(events.timestamp, 'UTC')) AS last_seen, + min(toTimeZone(events.timestamp, 'UTC')) AS first_seen, + replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$exception_issue_id'), ''), 'null'), '^"|"$', '') AS id + FROM events + WHERE and(equals(events.team_id, 99999), equals(events.event, '$exception'), isNotNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$exception_issue_id'), ''), 'null'), '^"|"$', '')), 1) + GROUP BY replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$exception_issue_id'), ''), 'null'), '^"|"$', '') + ORDER BY first_seen ASC + LIMIT 101 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0 + ''' +# --- # name: TestErrorTrackingQueryRunner.test_search_query ''' SELECT count(DISTINCT events.uuid) AS occurrences, diff --git a/posthog/hogql_queries/test/test_error_tracking_query_runner.py b/posthog/hogql_queries/test/test_error_tracking_query_runner.py index 0071779a0e18c..2b8bb4e78f83a 100644 --- a/posthog/hogql_queries/test/test_error_tracking_query_runner.py +++ b/posthog/hogql_queries/test/test_error_tracking_query_runner.py @@ -1,6 +1,9 @@ from unittest import TestCase from freezegun import freeze_time +from dateutil.relativedelta import relativedelta +from django.utils.timezone import now + from posthog.hogql_queries.error_tracking_query_runner import ErrorTrackingQueryRunner, search_tokenizer from posthog.schema import ( ErrorTrackingQuery, @@ -187,7 +190,7 @@ class TestErrorTrackingQueryRunner(ClickhouseTestMixin, APIBaseTest): issue_two = "01936e80-5e69-7e70-b837-871f5cdad28b" issue_three = "01936e80-aa51-746f-aec4-cdf16a5c5332" - def create_events_and_issue(self, issue_id, distinct_ids, exception_list=None): + def create_events_and_issue(self, issue_id, distinct_ids, timestamp=None, exception_list=None): event_properties = {"$exception_issue_id": issue_id} if exception_list: event_properties["$exception_list"] = exception_list @@ -198,6 +201,7 @@ def create_events_and_issue(self, issue_id, distinct_ids, exception_list=None): event="$exception", team=self.team, properties=event_properties, + timestamp=timestamp, ) ErrorTrackingIssue.objects.create(id=issue_id, team=self.team) @@ -224,9 +228,14 @@ def setUp(self): self.create_events_and_issue( issue_id=self.issue_one, distinct_ids=[self.distinct_id_one, self.distinct_id_two], + timestamp=now() - relativedelta(hours=3), + ) + self.create_events_and_issue( + issue_id=self.issue_two, distinct_ids=[self.distinct_id_one], timestamp=now() - relativedelta(hours=2) + ) + self.create_events_and_issue( + issue_id=self.issue_three, distinct_ids=[self.distinct_id_two], timestamp=now() - relativedelta(hours=1) ) - self.create_events_and_issue(issue_id=self.issue_two, distinct_ids=[self.distinct_id_one]) - self.create_events_and_issue(issue_id=self.issue_three, distinct_ids=[self.distinct_id_two]) flush_persons_and_events() @@ -453,6 +462,24 @@ def test_hogql_filters(self): # two errors exist for person with distinct_id_two self.assertEqual(len(results), 2) + @snapshot_clickhouse_queries + def test_ordering(self): + runner = ErrorTrackingQueryRunner( + team=self.team, + query=ErrorTrackingQuery(kind="ErrorTrackingQuery", dateRange=DateRange(), orderBy="last_seen"), + ) + + results = self._calculate(runner)["results"] + self.assertEqual([r["id"] for r in results], [self.issue_three, self.issue_two, self.issue_one]) + + runner = ErrorTrackingQueryRunner( + team=self.team, + query=ErrorTrackingQuery(kind="ErrorTrackingQuery", dateRange=DateRange(), orderBy="first_seen"), + ) + + results = self._calculate(runner)["results"] + self.assertEqual([r["id"] for r in results], [self.issue_one, self.issue_two, self.issue_three]) + # def test_merges_and_defaults_groups(self): # ErrorTrackingGroup.objects.create( # team=self.team, diff --git a/posthog/queries/test/__snapshots__/test_trends.ambr b/posthog/queries/test/__snapshots__/test_trends.ambr index ad6da12dcdceb..9e2cc7eda3a1a 100644 --- a/posthog/queries/test/__snapshots__/test_trends.ambr +++ b/posthog/queries/test/__snapshots__/test_trends.ambr @@ -865,6 +865,42 @@ # --- # name: TestTrends.test_dau_with_breakdown_filtering_with_sampling.1 ''' + /* celery:posthog.tasks.tasks.sync_insight_caching_state */ + SELECT team_id, + date_diff('second', max(timestamp), now()) AS age + FROM events + WHERE timestamp > date_sub(DAY, 3, now()) + AND timestamp < now() + GROUP BY team_id + ORDER BY age; + ''' +# --- +# name: TestTrends.test_dau_with_breakdown_filtering_with_sampling.10 + ''' + /* celery:posthog.tasks.tasks.sync_insight_caching_state */ + SELECT team_id, + date_diff('second', max(timestamp), now()) AS age + FROM events + WHERE timestamp > date_sub(DAY, 3, now()) + AND timestamp < now() + GROUP BY team_id + ORDER BY age; + ''' +# --- +# name: TestTrends.test_dau_with_breakdown_filtering_with_sampling.11 + ''' + /* celery:posthog.tasks.tasks.sync_insight_caching_state */ + SELECT team_id, + date_diff('second', max(timestamp), now()) AS age + FROM events + WHERE timestamp > date_sub(DAY, 3, now()) + AND timestamp < now() + GROUP BY team_id + ORDER BY age; + ''' +# --- +# name: TestTrends.test_dau_with_breakdown_filtering_with_sampling.12 + ''' SELECT replaceRegexpAll(JSONExtractRaw(properties, '$some_property'), '^"|"$', '') AS value, count(*) as count @@ -879,7 +915,7 @@ OFFSET 0 ''' # --- -# name: TestTrends.test_dau_with_breakdown_filtering_with_sampling.2 +# name: TestTrends.test_dau_with_breakdown_filtering_with_sampling.13 ''' SELECT groupArray(day_start) as date, @@ -931,7 +967,7 @@ ORDER BY breakdown_value ''' # --- -# name: TestTrends.test_dau_with_breakdown_filtering_with_sampling.3 +# name: TestTrends.test_dau_with_breakdown_filtering_with_sampling.14 ''' SELECT replaceRegexpAll(JSONExtractRaw(properties, '$some_property'), '^"|"$', '') AS value, @@ -947,7 +983,7 @@ OFFSET 0 ''' # --- -# name: TestTrends.test_dau_with_breakdown_filtering_with_sampling.4 +# name: TestTrends.test_dau_with_breakdown_filtering_with_sampling.15 ''' SELECT groupArray(day_start) as date, @@ -999,6 +1035,102 @@ ORDER BY breakdown_value ''' # --- +# name: TestTrends.test_dau_with_breakdown_filtering_with_sampling.2 + ''' + /* celery:posthog.tasks.tasks.sync_insight_caching_state */ + SELECT team_id, + date_diff('second', max(timestamp), now()) AS age + FROM events + WHERE timestamp > date_sub(DAY, 3, now()) + AND timestamp < now() + GROUP BY team_id + ORDER BY age; + ''' +# --- +# name: TestTrends.test_dau_with_breakdown_filtering_with_sampling.3 + ''' + /* celery:posthog.tasks.tasks.sync_insight_caching_state */ + SELECT team_id, + date_diff('second', max(timestamp), now()) AS age + FROM events + WHERE timestamp > date_sub(DAY, 3, now()) + AND timestamp < now() + GROUP BY team_id + ORDER BY age; + ''' +# --- +# name: TestTrends.test_dau_with_breakdown_filtering_with_sampling.4 + ''' + /* celery:posthog.tasks.tasks.sync_insight_caching_state */ + SELECT team_id, + date_diff('second', max(timestamp), now()) AS age + FROM events + WHERE timestamp > date_sub(DAY, 3, now()) + AND timestamp < now() + GROUP BY team_id + ORDER BY age; + ''' +# --- +# name: TestTrends.test_dau_with_breakdown_filtering_with_sampling.5 + ''' + /* celery:posthog.tasks.tasks.sync_insight_caching_state */ + SELECT team_id, + date_diff('second', max(timestamp), now()) AS age + FROM events + WHERE timestamp > date_sub(DAY, 3, now()) + AND timestamp < now() + GROUP BY team_id + ORDER BY age; + ''' +# --- +# name: TestTrends.test_dau_with_breakdown_filtering_with_sampling.6 + ''' + /* celery:posthog.tasks.tasks.sync_insight_caching_state */ + SELECT team_id, + date_diff('second', max(timestamp), now()) AS age + FROM events + WHERE timestamp > date_sub(DAY, 3, now()) + AND timestamp < now() + GROUP BY team_id + ORDER BY age; + ''' +# --- +# name: TestTrends.test_dau_with_breakdown_filtering_with_sampling.7 + ''' + /* celery:posthog.tasks.tasks.sync_insight_caching_state */ + SELECT team_id, + date_diff('second', max(timestamp), now()) AS age + FROM events + WHERE timestamp > date_sub(DAY, 3, now()) + AND timestamp < now() + GROUP BY team_id + ORDER BY age; + ''' +# --- +# name: TestTrends.test_dau_with_breakdown_filtering_with_sampling.8 + ''' + /* celery:posthog.tasks.tasks.sync_insight_caching_state */ + SELECT team_id, + date_diff('second', max(timestamp), now()) AS age + FROM events + WHERE timestamp > date_sub(DAY, 3, now()) + AND timestamp < now() + GROUP BY team_id + ORDER BY age; + ''' +# --- +# name: TestTrends.test_dau_with_breakdown_filtering_with_sampling.9 + ''' + /* celery:posthog.tasks.tasks.sync_insight_caching_state */ + SELECT team_id, + date_diff('second', max(timestamp), now()) AS age + FROM events + WHERE timestamp > date_sub(DAY, 3, now()) + AND timestamp < now() + GROUP BY team_id + ORDER BY age; + ''' +# --- # name: TestTrends.test_filter_events_by_precalculated_cohort ''' diff --git a/posthog/schema.py b/posthog/schema.py index 98e1e2f972554..a60f6ae8ac5ab 100644 --- a/posthog/schema.py +++ b/posthog/schema.py @@ -763,7 +763,7 @@ class ErrorTrackingIssue(BaseModel): volume: Optional[Any] = None -class Order(StrEnum): +class OrderBy(StrEnum): LAST_SEEN = "last_seen" FIRST_SEEN = "first_seen" OCCURRENCES = "occurrences" @@ -5558,6 +5558,7 @@ class AnyResponseType( HogQLAutocompleteResponse, Any, EventsQueryResponse, + ErrorTrackingQueryResponse, ] ] ): @@ -5569,6 +5570,7 @@ class AnyResponseType( HogQLAutocompleteResponse, Any, EventsQueryResponse, + ErrorTrackingQueryResponse, ] @@ -5660,7 +5662,7 @@ class RecordingsQuery(BaseModel): ) actions: Optional[list[dict[str, Any]]] = None console_log_filters: Optional[list[LogEntryPropertyFilter]] = None - date_from: Optional[str] = None + date_from: Optional[str] = "-3d" date_to: Optional[str] = None events: Optional[list[dict[str, Any]]] = None filter_test_accounts: Optional[bool] = None @@ -5689,8 +5691,8 @@ class RecordingsQuery(BaseModel): default=None, description="Modifiers used when performing the query" ) offset: Optional[int] = None - operand: Optional[FilterLogicalOperator] = None - order: Optional[RecordingOrder] = None + operand: Optional[FilterLogicalOperator] = FilterLogicalOperator.AND_ + order: Optional[RecordingOrder] = RecordingOrder.START_TIME person_uuid: Optional[str] = None properties: Optional[ list[ @@ -5920,7 +5922,8 @@ class ErrorTrackingQuery(BaseModel): modifiers: Optional[HogQLQueryModifiers] = Field( default=None, description="Modifiers used when performing the query" ) - order: Optional[Order] = None + offset: Optional[int] = None + orderBy: Optional[OrderBy] = None response: Optional[ErrorTrackingQueryResponse] = None searchQuery: Optional[str] = None select: Optional[list[str]] = None @@ -6918,6 +6921,7 @@ class HogQLAutocomplete(BaseModel): ErrorTrackingQuery, ExperimentFunnelsQuery, ExperimentTrendsQuery, + RecordingsQuery, ] ] = Field(default=None, description="Query in whose context to validate.") startPosition: int = Field(..., description="Start position of the editor word") @@ -6961,6 +6965,7 @@ class HogQLMetadata(BaseModel): ErrorTrackingQuery, ExperimentFunnelsQuery, ExperimentTrendsQuery, + RecordingsQuery, ] ] = Field( default=None, diff --git a/posthog/session_recordings/queries/session_recording_list_from_filters.py b/posthog/session_recordings/queries/session_recording_list_from_filters.py index b9ea443d3c9e5..38522ae4d18ce 100644 --- a/posthog/session_recordings/queries/session_recording_list_from_filters.py +++ b/posthog/session_recordings/queries/session_recording_list_from_filters.py @@ -264,18 +264,9 @@ def _where_predicates(self) -> Union[ast.And, ast.Or]: console_logs_subquery = ast.SelectQuery( select=[ast.Field(chain=["log_source_id"])], select_from=ast.JoinExpr(table=ast.Field(chain=["console_logs_log_entries"])), - where=ast.And( + where=self._filter.ast_operand( exprs=[ - self._filter.ast_operand( - exprs=[ - property_to_expr(self._filter.console_log_filters, team=self._team), - ] - ), - ast.CompareOperation( - op=ast.CompareOperationOp.Eq, - left=ast.Field(chain=["log_source"]), - right=ast.Constant(value="session_replay"), - ), + property_to_expr(self._filter.console_log_filters, team=self._team), ] ), ) diff --git a/posthog/session_recordings/queries/session_recording_list_from_query.py b/posthog/session_recordings/queries/session_recording_list_from_query.py new file mode 100644 index 0000000000000..fe08fb85590f2 --- /dev/null +++ b/posthog/session_recordings/queries/session_recording_list_from_query.py @@ -0,0 +1,787 @@ +import re +from typing import Any, NamedTuple, cast, Optional, Union +from datetime import datetime, timedelta, UTC + +import posthoganalytics + +from posthog.constants import PropertyOperatorType +from posthog.hogql import ast +from posthog.hogql.ast import CompareOperation +from posthog.hogql.constants import HogQLGlobalSettings +from posthog.hogql.parser import parse_select +from posthog.hogql.property import property_to_expr, action_to_expr +from posthog.hogql.query import execute_hogql_query +from posthog.hogql_queries.insights.paginators import HogQLHasMorePaginator +from posthog.hogql_queries.legacy_compatibility.filter_to_query import MathAvailability, legacy_entity_to_node +from posthog.hogql_queries.utils.query_date_range import QueryDateRange +from posthog.models import Team, Entity, Action +from posthog.models.filters.mixins.utils import cached_property +from posthog.schema import ( + QueryTiming, + HogQLQueryModifiers, + PersonsOnEventsMode, + RecordingsQuery, + DateRange, + NodeKind, + EventsNode, + ActionsNode, + PropertyGroupFilterValue, + FilterLogicalOperator, + RecordingOrder, + PersonPropertyFilter, + EventPropertyFilter, + GroupPropertyFilter, + HogQLPropertyFilter, +) +from posthog.session_recordings.queries.session_replay_events import ttl_days + +import structlog + +from posthog.types import AnyPropertyFilter + +logger = structlog.get_logger(__name__) + + +def is_event_property(p: AnyPropertyFilter) -> bool: + p_type = getattr(p, "type", None) + p_key = getattr(p, "key", "") + return p_type == "event" or (p_type == "hogql" and bool(re.search(r"(? bool: + p_type = getattr(p, "type", None) + p_key = getattr(p, "key", "") + return p_type == "person" or (p_type == "hogql" and "person.properties" in p_key) + + +def is_group_property(p: AnyPropertyFilter) -> bool: + p_type = getattr(p, "type", None) + return p_type == "group" + + +def is_cohort_property(p: AnyPropertyFilter) -> bool: + p_type = getattr(p, "type", None) + return bool(p_type and "cohort" in p_type) + + +class SessionRecordingQueryResult(NamedTuple): + results: list + has_more_recording: bool + timings: list[QueryTiming] | None = None + + +class UnexpectedQueryProperties(Exception): + def __init__(self, remaining_properties: list[AnyPropertyFilter] | None): + self.remaining_properties = remaining_properties + super().__init__(f"Unexpected properties in query: {remaining_properties}") + + +def _strip_person_and_event_and_cohort_properties( + properties: list[AnyPropertyFilter] | None, +) -> list[AnyPropertyFilter] | None: + if not properties: + return None + + properties_to_keep = [ + g + for g in properties + if not is_event_property(g) + and not is_person_property(g) + and not is_group_property(g) + and not is_cohort_property(g) + ] + + return properties_to_keep + + +class SessionRecordingListFromQuery: + SESSION_RECORDINGS_DEFAULT_LIMIT = 50 + + _team: Team + _query: RecordingsQuery + + BASE_QUERY: str = """ + SELECT s.session_id, + any(s.team_id), + any(s.distinct_id), + min(s.min_first_timestamp) as start_time, + max(s.max_last_timestamp) as end_time, + dateDiff('SECOND', start_time, end_time) as duration, + argMinMerge(s.first_url) as first_url, + sum(s.click_count) as click_count, + sum(s.keypress_count) as keypress_count, + sum(s.mouse_activity_count) as mouse_activity_count, + sum(s.active_milliseconds)/1000 as active_seconds, + (duration - active_seconds) as inactive_seconds, + sum(s.console_log_count) as console_log_count, + sum(s.console_warn_count) as console_warn_count, + sum(s.console_error_count) as console_error_count, + {ongoing_selection}, + round(( + ((sum(s.active_milliseconds) / 1000 + sum(s.click_count) + sum(s.keypress_count) + sum(s.console_error_count))) -- intent + / + ((sum(s.mouse_activity_count) + dateDiff('SECOND', start_time, end_time) + sum(s.console_error_count) + sum(s.console_log_count) + sum(s.console_warn_count))) + * 100 + ), 2) as activity_score + FROM raw_session_replay_events s + WHERE {where_predicates} + GROUP BY session_id + HAVING {having_predicates} + ORDER BY {order_by} DESC + """ + + @staticmethod + def _data_to_return(results: list[Any] | None) -> list[dict[str, Any]]: + default_columns = [ + "session_id", + "team_id", + "distinct_id", + "start_time", + "end_time", + "duration", + "first_url", + "click_count", + "keypress_count", + "mouse_activity_count", + "active_seconds", + "inactive_seconds", + "console_log_count", + "console_warn_count", + "console_error_count", + "ongoing", + "activity_score", + ] + + return [ + { + **dict(zip(default_columns, row[: len(default_columns)])), + } + for row in results or [] + ] + + def __init__( + self, + team: Team, + query: RecordingsQuery, + hogql_query_modifiers: Optional[HogQLQueryModifiers], + **_, + ): + self._team = team + + self._query = query + if self._query.filter_test_accounts: + self._query.properties = self._query.properties or [] + self._query.properties += self._test_account_filters + + self._paginator = HogQLHasMorePaginator( + limit=query.limit or self.SESSION_RECORDINGS_DEFAULT_LIMIT, offset=query.offset or 0 + ) + self._hogql_query_modifiers = hogql_query_modifiers + + @cached_property + def _test_account_filters(self) -> list[AnyPropertyFilter]: + prop_filters: list[AnyPropertyFilter] = [] + for prop in self._team.test_account_filters: + match prop["type"]: + case "person": + prop_filters.append(PersonPropertyFilter(**prop)) + case "event": + prop_filters.append(EventPropertyFilter(**prop)) + case "group": + prop_filters.append(GroupPropertyFilter(**prop)) + case "hogql": + prop_filters.append(HogQLPropertyFilter(**prop)) + + return prop_filters + + @property + def ttl_days(self): + return ttl_days(self._team) + + def run(self) -> SessionRecordingQueryResult: + query = self.get_query() + + paginated_response = self._paginator.execute_hogql_query( + # TODO I guess the paginator needs to know how to handle union queries or all callers are supposed to collapse them or .... 🤷 + query=cast(ast.SelectQuery, query), + team=self._team, + query_type="SessionRecordingListQuery", + modifiers=self._hogql_query_modifiers, + settings=HogQLGlobalSettings(allow_experimental_analyzer=False), # This needs to be turned on eventually + ) + + return SessionRecordingQueryResult( + results=(self._data_to_return(self._paginator.results)), + has_more_recording=self._paginator.has_more(), + timings=paginated_response.timings, + ) + + def get_query(self): + return parse_select( + self.BASE_QUERY, + { + # Check if the most recent _timestamp is within five minutes of the current time + # proxy for a live session + "ongoing_selection": ast.Alias( + alias="ongoing", + expr=ast.CompareOperation( + left=ast.Call(name="max", args=[ast.Field(chain=["s", "_timestamp"])]), + right=ast.Constant( + # provided in a placeholder, so we can pass now from python to make tests easier 🙈 + value=datetime.now(UTC) - timedelta(minutes=5), + ), + op=ast.CompareOperationOp.GtEq, + ), + ), + "order_by": self._order_by_clause(), + "where_predicates": self._where_predicates(), + "having_predicates": self._having_predicates() or ast.Constant(value=True), + }, + ) + + def _order_by_clause(self) -> ast.Field: + # KLUDGE: we only need a default here because mypy is silly + order_by = self._query.order.value if self._query.order else RecordingOrder.START_TIME + return ast.Field(chain=[order_by]) + + @cached_property + def query_date_range(self): + return QueryDateRange( + date_range=DateRange(date_from=self._query.date_from, date_to=self._query.date_to), + team=self._team, + interval=None, + now=datetime.now(), + ) + + def _where_predicates(self) -> Union[ast.And, ast.Or]: + exprs: list[ast.Expr] = [ + ast.CompareOperation( + op=ast.CompareOperationOp.GtEq, + left=ast.Field(chain=["s", "min_first_timestamp"]), + right=ast.Constant(value=datetime.now(UTC) - timedelta(days=self.ttl_days)), + ) + ] + + person_id_compare_operation = PersonsIdCompareOperation(self._team, self._query, self.ttl_days).get_operation() + if person_id_compare_operation: + exprs.append(person_id_compare_operation) + + # we check for session_ids type not for truthiness since we want to allow empty lists + if isinstance(self._query.session_ids, list): + exprs.append( + ast.CompareOperation( + op=ast.CompareOperationOp.In, + left=ast.Field(chain=["session_id"]), + right=ast.Constant(value=self._query.session_ids), + ) + ) + + query_date_from = self.query_date_range.date_from() + if query_date_from: + exprs.append( + ast.CompareOperation( + op=ast.CompareOperationOp.GtEq, + left=ast.Field(chain=["s", "min_first_timestamp"]), + right=ast.Constant(value=query_date_from), + ) + ) + + query_date_to = self.query_date_range.date_to() + if query_date_to: + exprs.append( + ast.CompareOperation( + op=ast.CompareOperationOp.LtEq, + left=ast.Field(chain=["s", "min_first_timestamp"]), + right=ast.Constant(value=query_date_to), + ) + ) + + optional_exprs: list[ast.Expr] = [] + + # if in PoE mode then we should be pushing person property queries into here + events_sub_query = ReplayFiltersEventsSubQuery(self._team, self._query).get_query_for_session_id_matching() + if events_sub_query: + optional_exprs.append( + ast.CompareOperation( + op=ast.CompareOperationOp.In, + left=ast.Field(chain=["s", "session_id"]), + right=events_sub_query, + ) + ) + + # we want to avoid a join to persons since we don't ever need to select from them, + # so we create our own persons sub query here + # if PoE mode is on then this will be handled in the events subquery, and we don't need to do anything here + person_subquery = PersonsPropertiesSubQuery(self._team, self._query, self.ttl_days).get_query() + if person_subquery: + optional_exprs.append( + ast.CompareOperation( + op=ast.CompareOperationOp.In, + left=ast.Field(chain=["s", "distinct_id"]), + right=person_subquery, + ) + ) + + cohort_subquery = CohortPropertyGroupsSubQuery(self._team, self._query, self.ttl_days).get_query() + if cohort_subquery: + optional_exprs.append( + ast.CompareOperation( + op=ast.CompareOperationOp.In, + left=ast.Field(chain=["s", "distinct_id"]), + right=cohort_subquery, + ) + ) + + remaining_properties = _strip_person_and_event_and_cohort_properties(self._query.properties) + if remaining_properties: + posthoganalytics.capture_exception(UnexpectedQueryProperties(remaining_properties)) + optional_exprs.append(property_to_expr(remaining_properties, team=self._team, scope="replay")) + + if self._query.console_log_filters: + console_logs_subquery = ast.SelectQuery( + select=[ast.Field(chain=["log_source_id"])], + select_from=ast.JoinExpr(table=ast.Field(chain=["console_logs_log_entries"])), + where=property_to_expr( + # convert to a property group so we can insert the correct operand + PropertyGroupFilterValue( + type=FilterLogicalOperator.AND_ + if self.property_operand == "AND" + else FilterLogicalOperator.OR_, + values=self._query.console_log_filters, + ), + team=self._team, + ), + ) + + optional_exprs.append( + ast.CompareOperation( + op=ast.CompareOperationOp.In, + left=ast.Field(chain=["session_id"]), + right=console_logs_subquery, + ) + ) + + if optional_exprs: + exprs.append(self.ast_operand(exprs=optional_exprs)) + + return ast.And(exprs=exprs) + + def _having_predicates(self) -> ast.Expr | None: + return ( + property_to_expr(self._query.having_predicates, team=self._team, scope="replay") + if self._query.having_predicates + else None + ) + + @cached_property + def property_operand(self): + return PropertyOperatorType.AND if self._query.operand == "AND" else PropertyOperatorType.OR + + @cached_property + def ast_operand(self) -> type[Union[ast.And, ast.Or]]: + return ast.And if self.property_operand == "AND" else ast.Or + + +def poe_is_active(team: Team) -> bool: + return team.person_on_events_mode is not None and team.person_on_events_mode != PersonsOnEventsMode.DISABLED + + +class PersonsPropertiesSubQuery: + _team: Team + _query: RecordingsQuery + _ttl_days: int + + def __init__(self, team: Team, query: RecordingsQuery, ttl_days: int): + self._team = team + self._query = query + self._ttl_days = ttl_days + + def get_query(self) -> ast.SelectQuery | ast.SelectSetQuery | None: + if self.person_properties and not poe_is_active(self._team): + return parse_select( + """ + SELECT distinct_id + FROM person_distinct_ids + WHERE {where_predicates} + """, + { + "where_predicates": self._where_predicates, + }, + ) + else: + return None + + @cached_property + def property_operand(self): + return PropertyOperatorType.AND if self._query.operand == "AND" else PropertyOperatorType.OR + + @cached_property + def person_properties(self) -> PropertyGroupFilterValue | None: + person_property_groups = [g for g in (self._query.properties or []) if is_person_property(g)] + return ( + PropertyGroupFilterValue( + type=FilterLogicalOperator.AND_ if self.property_operand == "AND" else FilterLogicalOperator.OR_, + values=person_property_groups, + ) + if person_property_groups + else None + ) + + @cached_property + def _where_predicates(self) -> ast.Expr: + return ( + property_to_expr(self.person_properties, team=self._team) + if self.person_properties + else ast.Constant(value=True) + ) + + +class CohortPropertyGroupsSubQuery: + _team: Team + _query: RecordingsQuery + _ttl_days: int + + raw_cohort_to_distinct_id = """ + SELECT + distinct_id +FROM raw_person_distinct_ids +WHERE distinct_id in (SELECT distinct_id FROM raw_person_distinct_ids WHERE 1=1 AND {cohort_predicate}) +GROUP BY distinct_id +HAVING argMax(is_deleted, version) = 0 AND {cohort_predicate} + """ + + def __init__(self, team: Team, query: RecordingsQuery, ttl_days: int): + self._team = team + self._query = query + self._ttl_days = ttl_days + + def get_query(self) -> ast.SelectQuery | ast.SelectSetQuery | None: + if self.cohort_properties: + return parse_select( + self.raw_cohort_to_distinct_id, + {"cohort_predicate": property_to_expr(self.cohort_properties, team=self._team, scope="replay")}, + ) + + return None + + @cached_property + def property_operand(self): + return PropertyOperatorType.AND if self._query.operand == "AND" else PropertyOperatorType.OR + + @cached_property + def cohort_properties(self) -> PropertyGroupFilterValue | None: + cohort_property_groups = [g for g in (self._query.properties or []) if is_cohort_property(g)] + return ( + PropertyGroupFilterValue( + type=FilterLogicalOperator.AND_ if self.property_operand == "AND" else FilterLogicalOperator.OR_, + values=cohort_property_groups, + ) + if cohort_property_groups + else None + ) + + +class PersonsIdCompareOperation: + _team: Team + _query: RecordingsQuery + _ttl_days: int + + def __init__(self, team: Team, query: RecordingsQuery, ttl_days: int): + self._team = team + self._query = query + self._ttl_days = ttl_days + + def get_operation(self) -> CompareOperation | None: + q = self.get_query() + if not q: + return None + + if poe_is_active(self._team): + return ast.CompareOperation( + op=ast.CompareOperationOp.In, + left=ast.Field(chain=["session_id"]), + right=q, + ) + else: + return ast.CompareOperation( + op=ast.CompareOperationOp.In, + left=ast.Field(chain=["distinct_id"]), + right=q, + ) + + @cached_property + def query_date_range(self): + return QueryDateRange( + date_range=DateRange(date_from=self._query.date_from, date_to=self._query.date_to, explicitDate=True), + team=self._team, + interval=None, + now=datetime.now(), + ) + + def get_query(self) -> ast.SelectQuery | ast.SelectSetQuery | None: + if not self._query.person_uuid: + return None + + # anchor to python now so that tests can freeze time + now = datetime.utcnow() + + if poe_is_active(self._team): + return parse_select( + """ + select + distinct `$session_id` + from + events + where + person_id = {person_id} + and timestamp <= {now} + and timestamp >= {ttl_date} + and timestamp >= {date_from} + and timestamp <= {date_to} + and notEmpty(`$session_id`) + """, + { + "person_id": ast.Constant(value=self._query.person_uuid), + "ttl_days": ast.Constant(value=self._ttl_days), + "date_from": ast.Constant(value=self.query_date_range.date_from()), + "date_to": ast.Constant(value=self.query_date_range.date_to()), + "now": ast.Constant(value=now), + "ttl_date": ast.Constant(value=now - timedelta(days=self._ttl_days)), + }, + ) + else: + return parse_select( + """ + SELECT distinct_id + FROM person_distinct_ids + WHERE person_id = {person_id} + """, + { + "person_id": ast.Constant(value=self._query.person_uuid), + }, + ) + + +def _entity_to_expr(entity: EventsNode | ActionsNode) -> ast.Expr: + # KLUDGE: we should be able to use NodeKind.ActionsNode here but mypy :shrug: + if entity.kind == "ActionsNode": + action = Action.objects.get(pk=entity.id) + return action_to_expr(action) + else: + if entity.event is None: + return ast.Constant(value=True) + + return ast.CompareOperation( + op=ast.CompareOperationOp.Eq, + left=ast.Field(chain=["events", "event"]), + right=ast.Constant(value=entity.name), + ) + + +class ReplayFiltersEventsSubQuery: + _team: Team + _query: RecordingsQuery + + @cached_property + def query_date_range(self): + return QueryDateRange( + date_range=DateRange(date_from=self._query.date_from, date_to=self._query.date_to, explicitDate=True), + team=self._team, + interval=None, + now=datetime.now(), + ) + + @property + def ttl_days(self): + return ttl_days(self._team) + + def __init__( + self, + team: Team, + query: RecordingsQuery, + hogql_query_modifiers: Optional[HogQLQueryModifiers] = None, + ): + self._team = team + self._query = query + self._hogql_query_modifiers = hogql_query_modifiers + + @cached_property + def _event_predicates(self): + event_exprs: list[ast.Expr] = [] + event_names: set[int | str] = set() + + for entity in self.entities: + if entity.kind == NodeKind.ACTIONS_NODE: + action = Action.objects.get(pk=int(entity.id), team__project_id=self._team.project_id) + event_names.update([ae for ae in action.get_step_events() if ae and ae not in event_names]) + else: + if entity.event and entity.event not in event_names: + event_names.add(entity.event) + + entity_exprs = [_entity_to_expr(entity=entity)] + + if entity.properties: + entity_exprs.append(property_to_expr(entity.properties, team=self._team, scope="replay_entity")) + + event_exprs.append(ast.And(exprs=entity_exprs)) + + return event_exprs, list(event_names) + + def _select_from_events(self, select_expr: ast.Expr) -> ast.SelectQuery: + return ast.SelectQuery( + select=[select_expr], + select_from=ast.JoinExpr( + table=ast.Field(chain=["events"]), + ), + where=self._where_predicates(), + having=self._having_predicates(), + group_by=[ast.Field(chain=["$session_id"])], + ) + + def get_query_for_session_id_matching(self) -> ast.SelectQuery | ast.SelectSetQuery | None: + use_poe = poe_is_active(self._team) and self.person_properties + + if self.entities or self.event_properties or self.group_properties or use_poe: + return self._select_from_events(ast.Alias(alias="session_id", expr=ast.Field(chain=["$session_id"]))) + else: + return None + + def get_query_for_event_id_matching(self) -> ast.SelectQuery | ast.SelectSetQuery: + return self._select_from_events(ast.Call(name="groupUniqArray", args=[ast.Field(chain=["uuid"])])) + + def get_event_ids_for_session(self) -> SessionRecordingQueryResult: + query = self.get_query_for_event_id_matching() + + hogql_query_response = execute_hogql_query( + query=query, + team=self._team, + query_type="SessionRecordingMatchingEventsForSessionQuery", + modifiers=self._hogql_query_modifiers, + ) + + flattened_results = [str(uuid) for row in hogql_query_response.results for uuid in row[0]] + + return SessionRecordingQueryResult( + results=flattened_results, + has_more_recording=False, + timings=hogql_query_response.timings, + ) + + def _where_predicates(self) -> ast.Expr: + exprs: list[ast.Expr] = [ + ast.Call( + name="notEmpty", + args=[ast.Field(chain=["$session_id"])], + ), + # regardless of any other filters limit between TTL and current time + ast.CompareOperation( + op=ast.CompareOperationOp.GtEq, + left=ast.Field(chain=["timestamp"]), + right=ast.Constant(value=datetime.now() - timedelta(days=self.ttl_days)), + ), + ast.CompareOperation( + op=ast.CompareOperationOp.LtEq, + left=ast.Field(chain=["timestamp"]), + right=ast.Call(name="now", args=[]), + ), + ] + + # TRICKY: we're adding a buffer to the date range to ensure we get all the events + # you can start sending us events before the session starts + if self._query.date_from: + exprs.append( + ast.CompareOperation( + op=ast.CompareOperationOp.GtEq, + left=ast.Field(chain=["timestamp"]), + right=ast.Constant(value=self.query_date_range.date_from() - timedelta(minutes=2)), + ) + ) + + # but we don't want to include events after date_to if provided + if self._query.date_to: + exprs.append( + ast.CompareOperation( + op=ast.CompareOperationOp.LtEq, + left=ast.Field(chain=["timestamp"]), + right=ast.Constant(value=self.query_date_range.date_to()), + ) + ) + + (event_where_exprs, _) = self._event_predicates + if event_where_exprs: + # we OR all events in the where and use hasAll / hasAny in the HAVING clause + exprs.append(ast.Or(exprs=event_where_exprs)) + + if self.event_properties: + exprs.append(property_to_expr(self.event_properties, team=self._team, scope="replay")) + + if self.group_properties: + exprs.append(property_to_expr(self.group_properties, team=self._team)) + + if self._team.person_on_events_mode and self.person_properties: + exprs.append(property_to_expr(self.person_properties, team=self._team, scope="event")) + + if self._query.session_ids: + exprs.append( + ast.CompareOperation( + op=ast.CompareOperationOp.In, + left=ast.Field(chain=["$session_id"]), + right=ast.Constant(value=self._query.session_ids), + ) + ) + + return ast.And(exprs=exprs) + + def _having_predicates(self) -> ast.Expr: + (_, event_names) = self._event_predicates + + if event_names: + return ast.Call( + name="hasAll" if self.property_operand == PropertyOperatorType.AND else "hasAny", + args=[ + ast.Call(name="groupUniqArray", args=[ast.Field(chain=["event"])]), + # KLUDGE: sorting only so that snapshot tests are consistent + ast.Constant(value=sorted(event_names)), + ], + ) + + return ast.Constant(value=True) + + @cached_property + def action_entities(self): + # TODO what do we send to the API instead to avoid needing to do this + return [legacy_entity_to_node(Entity(e), True, MathAvailability.Unavailable) for e in self._query.actions or []] + + @cached_property + def event_entities(self): + # TODO what do we send to the API instead to avoid needing to do this + # TODO is this overkill since it feels like we only need a few things off the entity + return [legacy_entity_to_node(Entity(e), True, MathAvailability.Unavailable) for e in self._query.events or []] + + @cached_property + def entities(self): + return self.action_entities + self.event_entities + + @cached_property + def event_properties(self): + return [g for g in (self._query.properties or []) if is_event_property(g)] + + @cached_property + def group_properties(self): + return [g for g in (self._query.properties or []) if is_group_property(g)] + + @cached_property + def property_operand(self): + return PropertyOperatorType.AND if self._query.operand == "AND" else PropertyOperatorType.OR + + @cached_property + def person_properties(self) -> PropertyGroupFilterValue | None: + person_property_groups = [g for g in (self._query.properties or []) if is_person_property(g)] + return ( + PropertyGroupFilterValue( + type=FilterLogicalOperator.AND_ if self.property_operand == "AND" else FilterLogicalOperator.OR_, + values=person_property_groups, + ) + if person_property_groups + else None + ) diff --git a/posthog/session_recordings/queries/test/__snapshots__/test_session_recording_list_from_filters.ambr b/posthog/session_recordings/queries/test/__snapshots__/test_session_recording_list_from_filters.ambr index 2eae8db4446f2..82e70f3063a2b 100644 --- a/posthog/session_recordings/queries/test/__snapshots__/test_session_recording_list_from_filters.ambr +++ b/posthog/session_recordings/queries/test/__snapshots__/test_session_recording_list_from_filters.ambr @@ -2683,7 +2683,7 @@ allow_experimental_analyzer=0 ''' # --- -# name: TestSessionRecordingsListFromFilters.test_filter_for_recordings_by_console_text +# name: TestSessionRecordingsListFromFilters.test_filter_for_recordings_by_console_text_0__key_level_value_warn_error_operator_exact_type_log_entry_key_message_value_message_4_operator_icontains_type_log_entry_ ''' SELECT s.session_id AS session_id, any(s.team_id), @@ -2706,10 +2706,10 @@ WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-14 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 20:00:00.000000', 6, 'UTC')), 0), in(s.session_id, (SELECT console_logs_log_entries.log_source_id AS log_source_id FROM - (SELECT log_entries.log_source_id AS log_source_id, log_entries.level AS level, log_entries.message AS message, log_entries.log_source AS log_source + (SELECT log_entries.log_source_id AS log_source_id, log_entries.level AS level, log_entries.message AS message FROM log_entries WHERE and(equals(log_entries.team_id, 99999), equals(log_entries.log_source, 'session_replay'))) AS console_logs_log_entries - WHERE and(or(or(ifNull(equals(console_logs_log_entries.level, 'warn'), 0), ifNull(equals(console_logs_log_entries.level, 'error'), 0)), ifNull(equals(console_logs_log_entries.message, 'message 4'), 0)), ifNull(equals(console_logs_log_entries.log_source, 'session_replay'), 0))))) + WHERE or(or(ifNull(equals(console_logs_log_entries.level, 'warn'), 0), ifNull(equals(console_logs_log_entries.level, 'error'), 0)), ifNull(ilike(console_logs_log_entries.message, '%message 4%'), 0))))) GROUP BY s.session_id HAVING 1 ORDER BY start_time DESC @@ -2724,7 +2724,7 @@ allow_experimental_analyzer=0 ''' # --- -# name: TestSessionRecordingsListFromFilters.test_filter_for_recordings_by_console_text.1 +# name: TestSessionRecordingsListFromFilters.test_filter_for_recordings_by_console_text_1__key_level_value_warn_error_operator_exact_type_log_entry_key_message_value_message_5_operator_icontains_type_log_entry_ ''' SELECT s.session_id AS session_id, any(s.team_id), @@ -2747,10 +2747,10 @@ WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-14 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 20:00:00.000000', 6, 'UTC')), 0), in(s.session_id, (SELECT console_logs_log_entries.log_source_id AS log_source_id FROM - (SELECT log_entries.log_source_id AS log_source_id, log_entries.level AS level, log_entries.message AS message, log_entries.log_source AS log_source + (SELECT log_entries.log_source_id AS log_source_id, log_entries.level AS level, log_entries.message AS message FROM log_entries WHERE and(equals(log_entries.team_id, 99999), equals(log_entries.log_source, 'session_replay'))) AS console_logs_log_entries - WHERE and(and(or(ifNull(equals(console_logs_log_entries.level, 'warn'), 0), ifNull(equals(console_logs_log_entries.level, 'error'), 0)), ifNull(ilike(console_logs_log_entries.message, '%message 5%'), 0)), ifNull(equals(console_logs_log_entries.log_source, 'session_replay'), 0))))) + WHERE and(or(ifNull(equals(console_logs_log_entries.level, 'warn'), 0), ifNull(equals(console_logs_log_entries.level, 'error'), 0)), ifNull(ilike(console_logs_log_entries.message, '%message 5%'), 0))))) GROUP BY s.session_id HAVING 1 ORDER BY start_time DESC @@ -2765,7 +2765,7 @@ allow_experimental_analyzer=0 ''' # --- -# name: TestSessionRecordingsListFromFilters.test_filter_for_recordings_by_console_text.2 +# name: TestSessionRecordingsListFromFilters.test_filter_for_recordings_by_console_text_2__key_level_value_info_operator_exact_type_log_entry_key_message_value_message_5_operator_icontains_type_log_entry_ ''' SELECT s.session_id AS session_id, any(s.team_id), @@ -2788,10 +2788,10 @@ WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-14 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 20:00:00.000000', 6, 'UTC')), 0), in(s.session_id, (SELECT console_logs_log_entries.log_source_id AS log_source_id FROM - (SELECT log_entries.log_source_id AS log_source_id, log_entries.level AS level, log_entries.message AS message, log_entries.log_source AS log_source + (SELECT log_entries.log_source_id AS log_source_id, log_entries.level AS level, log_entries.message AS message FROM log_entries WHERE and(equals(log_entries.team_id, 99999), equals(log_entries.log_source, 'session_replay'))) AS console_logs_log_entries - WHERE and(and(ifNull(equals(console_logs_log_entries.level, 'info'), 0), ifNull(ilike(console_logs_log_entries.message, '%message 5%'), 0)), ifNull(equals(console_logs_log_entries.log_source, 'session_replay'), 0))))) + WHERE and(ifNull(equals(console_logs_log_entries.level, 'info'), 0), ifNull(ilike(console_logs_log_entries.message, '%message 5%'), 0))))) GROUP BY s.session_id HAVING 1 ORDER BY start_time DESC @@ -2899,10 +2899,10 @@ WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-14 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 20:00:00.000000', 6, 'UTC')), 0), in(s.session_id, (SELECT console_logs_log_entries.log_source_id AS log_source_id FROM - (SELECT log_entries.log_source_id AS log_source_id, log_entries.level AS level, log_entries.log_source AS log_source + (SELECT log_entries.log_source_id AS log_source_id, log_entries.level AS level FROM log_entries WHERE and(equals(log_entries.team_id, 99999), equals(log_entries.log_source, 'session_replay'))) AS console_logs_log_entries - WHERE and(ifNull(equals(console_logs_log_entries.level, 'error'), 0), ifNull(equals(console_logs_log_entries.log_source, 'session_replay'), 0))))) + WHERE ifNull(equals(console_logs_log_entries.level, 'error'), 0)))) GROUP BY s.session_id HAVING 1 ORDER BY start_time DESC @@ -2940,10 +2940,10 @@ WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-14 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 20:00:00.000000', 6, 'UTC')), 0), in(s.session_id, (SELECT console_logs_log_entries.log_source_id AS log_source_id FROM - (SELECT log_entries.log_source_id AS log_source_id, log_entries.level AS level, log_entries.log_source AS log_source + (SELECT log_entries.log_source_id AS log_source_id, log_entries.level AS level FROM log_entries WHERE and(equals(log_entries.team_id, 99999), equals(log_entries.log_source, 'session_replay'))) AS console_logs_log_entries - WHERE and(ifNull(equals(console_logs_log_entries.level, 'info'), 0), ifNull(equals(console_logs_log_entries.log_source, 'session_replay'), 0))))) + WHERE ifNull(equals(console_logs_log_entries.level, 'info'), 0)))) GROUP BY s.session_id HAVING 1 ORDER BY start_time DESC @@ -2981,10 +2981,10 @@ WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-14 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 20:00:00.000000', 6, 'UTC')), 0), in(s.session_id, (SELECT console_logs_log_entries.log_source_id AS log_source_id FROM - (SELECT log_entries.log_source_id AS log_source_id, log_entries.level AS level, log_entries.log_source AS log_source + (SELECT log_entries.log_source_id AS log_source_id, log_entries.level AS level FROM log_entries WHERE and(equals(log_entries.team_id, 99999), equals(log_entries.log_source, 'session_replay'))) AS console_logs_log_entries - WHERE and(ifNull(equals(console_logs_log_entries.level, 'info'), 0), ifNull(equals(console_logs_log_entries.log_source, 'session_replay'), 0))))) + WHERE ifNull(equals(console_logs_log_entries.level, 'info'), 0)))) GROUP BY s.session_id HAVING 1 ORDER BY start_time DESC @@ -3022,10 +3022,10 @@ WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-14 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 20:00:00.000000', 6, 'UTC')), 0), in(s.session_id, (SELECT console_logs_log_entries.log_source_id AS log_source_id FROM - (SELECT log_entries.log_source_id AS log_source_id, log_entries.level AS level, log_entries.log_source AS log_source + (SELECT log_entries.log_source_id AS log_source_id, log_entries.level AS level FROM log_entries WHERE and(equals(log_entries.team_id, 99999), equals(log_entries.log_source, 'session_replay'))) AS console_logs_log_entries - WHERE and(ifNull(equals(console_logs_log_entries.level, 'warn'), 0), ifNull(equals(console_logs_log_entries.log_source, 'session_replay'), 0))))) + WHERE ifNull(equals(console_logs_log_entries.level, 'warn'), 0)))) GROUP BY s.session_id HAVING 1 ORDER BY start_time DESC @@ -3063,10 +3063,10 @@ WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-14 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 20:00:00.000000', 6, 'UTC')), 0), in(s.session_id, (SELECT console_logs_log_entries.log_source_id AS log_source_id FROM - (SELECT log_entries.log_source_id AS log_source_id, log_entries.level AS level, log_entries.log_source AS log_source + (SELECT log_entries.log_source_id AS log_source_id, log_entries.level AS level FROM log_entries WHERE and(equals(log_entries.team_id, 99999), equals(log_entries.log_source, 'session_replay'))) AS console_logs_log_entries - WHERE and(ifNull(equals(console_logs_log_entries.level, 'warn'), 0), ifNull(equals(console_logs_log_entries.log_source, 'session_replay'), 0))))) + WHERE ifNull(equals(console_logs_log_entries.level, 'warn'), 0)))) GROUP BY s.session_id HAVING 1 ORDER BY start_time DESC @@ -3104,10 +3104,10 @@ WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-14 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 20:00:00.000000', 6, 'UTC')), 0), in(s.session_id, (SELECT console_logs_log_entries.log_source_id AS log_source_id FROM - (SELECT log_entries.log_source_id AS log_source_id, log_entries.level AS level, log_entries.log_source AS log_source + (SELECT log_entries.log_source_id AS log_source_id, log_entries.level AS level FROM log_entries WHERE and(equals(log_entries.team_id, 99999), equals(log_entries.log_source, 'session_replay'))) AS console_logs_log_entries - WHERE and(ifNull(equals(console_logs_log_entries.level, 'info'), 0), ifNull(equals(console_logs_log_entries.log_source, 'session_replay'), 0))))) + WHERE ifNull(equals(console_logs_log_entries.level, 'info'), 0)))) GROUP BY s.session_id HAVING 1 ORDER BY start_time DESC @@ -3145,10 +3145,10 @@ WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-14 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 20:00:00.000000', 6, 'UTC')), 0), in(s.session_id, (SELECT console_logs_log_entries.log_source_id AS log_source_id FROM - (SELECT log_entries.log_source_id AS log_source_id, log_entries.level AS level, log_entries.log_source AS log_source + (SELECT log_entries.log_source_id AS log_source_id, log_entries.level AS level FROM log_entries WHERE and(equals(log_entries.team_id, 99999), equals(log_entries.log_source, 'session_replay'))) AS console_logs_log_entries - WHERE and(or(ifNull(equals(console_logs_log_entries.level, 'warn'), 0), ifNull(equals(console_logs_log_entries.level, 'error'), 0)), ifNull(equals(console_logs_log_entries.log_source, 'session_replay'), 0))))) + WHERE or(ifNull(equals(console_logs_log_entries.level, 'warn'), 0), ifNull(equals(console_logs_log_entries.level, 'error'), 0))))) GROUP BY s.session_id HAVING 1 ORDER BY start_time DESC @@ -3186,10 +3186,10 @@ WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-14 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 20:00:00.000000', 6, 'UTC')), 0), in(s.session_id, (SELECT console_logs_log_entries.log_source_id AS log_source_id FROM - (SELECT log_entries.log_source_id AS log_source_id, log_entries.level AS level, log_entries.log_source AS log_source + (SELECT log_entries.log_source_id AS log_source_id, log_entries.level AS level FROM log_entries WHERE and(equals(log_entries.team_id, 99999), equals(log_entries.log_source, 'session_replay'))) AS console_logs_log_entries - WHERE and(ifNull(equals(console_logs_log_entries.level, 'info'), 0), ifNull(equals(console_logs_log_entries.log_source, 'session_replay'), 0))))) + WHERE ifNull(equals(console_logs_log_entries.level, 'info'), 0)))) GROUP BY s.session_id HAVING 1 ORDER BY start_time DESC @@ -3793,6 +3793,41 @@ allow_experimental_analyzer=0 ''' # --- +# name: TestSessionRecordingsListFromFilters.test_listing_ignores_future_replays + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2023-08-30 11:55:01.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2023-08-09 12:00:01.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2023-08-23 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2023-08-30 12:00:01.000000', 6, 'UTC')), 0)) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- # name: TestSessionRecordingsListFromFilters.test_multiple_event_filters ''' SELECT s.session_id AS session_id, @@ -4113,7 +4148,130 @@ allow_experimental_analyzer=0 ''' # --- -# name: TestSessionRecordingsListFromFilters.test_operand_or_filters +# name: TestSessionRecordingsListFromFilters.test_operand_or_filters_0__key_level_value_warn_operator_exact_type_log_entry_key_message_value_random_operator_exact_type_log_entry_ + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-25 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 13:46:23.000000', 6, 'UTC')), 0), in(s.session_id, + (SELECT console_logs_log_entries.log_source_id AS log_source_id + FROM + (SELECT log_entries.log_source_id AS log_source_id, log_entries.level AS level, log_entries.message AS message + FROM log_entries + WHERE and(equals(log_entries.team_id, 99999), equals(log_entries.log_source, 'session_replay'))) AS console_logs_log_entries + WHERE and(ifNull(equals(console_logs_log_entries.level, 'warn'), 0), ifNull(equals(console_logs_log_entries.message, 'random'), 0))))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromFilters.test_operand_or_filters_1__key_level_value_info_operator_exact_type_log_entry_key_message_value_random_operator_exact_type_log_entry_ + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-25 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 13:46:23.000000', 6, 'UTC')), 0), in(s.session_id, + (SELECT console_logs_log_entries.log_source_id AS log_source_id + FROM + (SELECT log_entries.log_source_id AS log_source_id, log_entries.level AS level, log_entries.message AS message + FROM log_entries + WHERE and(equals(log_entries.team_id, 99999), equals(log_entries.log_source, 'session_replay'))) AS console_logs_log_entries + WHERE and(ifNull(equals(console_logs_log_entries.level, 'info'), 0), ifNull(equals(console_logs_log_entries.message, 'random'), 0))))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromFilters.test_operand_or_filters_2__key_level_value_warn_operator_exact_type_log_entry_ + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-25 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 13:46:23.000000', 6, 'UTC')), 0), in(s.session_id, + (SELECT console_logs_log_entries.log_source_id AS log_source_id + FROM + (SELECT log_entries.log_source_id AS log_source_id, log_entries.level AS level + FROM log_entries + WHERE and(equals(log_entries.team_id, 99999), equals(log_entries.log_source, 'session_replay'))) AS console_logs_log_entries + WHERE ifNull(equals(console_logs_log_entries.level, 'warn'), 0)))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromFilters.test_operand_or_filters_3__key_message_value_random_operator_exact_type_log_entry_ ''' SELECT s.session_id AS session_id, any(s.team_id), @@ -4136,10 +4294,10 @@ WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-25 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 13:46:23.000000', 6, 'UTC')), 0), in(s.session_id, (SELECT console_logs_log_entries.log_source_id AS log_source_id FROM - (SELECT log_entries.log_source_id AS log_source_id, log_entries.level AS level, log_entries.message AS message, log_entries.log_source AS log_source + (SELECT log_entries.log_source_id AS log_source_id, log_entries.message AS message FROM log_entries WHERE and(equals(log_entries.team_id, 99999), equals(log_entries.log_source, 'session_replay'))) AS console_logs_log_entries - WHERE and(and(ifNull(equals(console_logs_log_entries.level, 'warn'), 0), ifNull(equals(console_logs_log_entries.message, 'random'), 0)), ifNull(equals(console_logs_log_entries.log_source, 'session_replay'), 0))))) + WHERE ifNull(equals(console_logs_log_entries.message, 'random'), 0)))) GROUP BY s.session_id HAVING 1 ORDER BY start_time DESC @@ -4154,7 +4312,7 @@ allow_experimental_analyzer=0 ''' # --- -# name: TestSessionRecordingsListFromFilters.test_operand_or_filters.1 +# name: TestSessionRecordingsListFromFilters.test_operand_or_filters_4__key_level_value_warn_operator_exact_type_log_entry_key_message_value_random_operator_exact_type_log_entry_ ''' SELECT s.session_id AS session_id, any(s.team_id), @@ -4177,10 +4335,10 @@ WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-25 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 13:46:23.000000', 6, 'UTC')), 0), in(s.session_id, (SELECT console_logs_log_entries.log_source_id AS log_source_id FROM - (SELECT log_entries.log_source_id AS log_source_id, log_entries.level AS level, log_entries.message AS message, log_entries.log_source AS log_source + (SELECT log_entries.log_source_id AS log_source_id, log_entries.level AS level, log_entries.message AS message FROM log_entries WHERE and(equals(log_entries.team_id, 99999), equals(log_entries.log_source, 'session_replay'))) AS console_logs_log_entries - WHERE and(or(ifNull(equals(console_logs_log_entries.level, 'warn'), 0), ifNull(equals(console_logs_log_entries.message, 'random'), 0)), ifNull(equals(console_logs_log_entries.log_source, 'session_replay'), 0))))) + WHERE or(ifNull(equals(console_logs_log_entries.level, 'warn'), 0), ifNull(equals(console_logs_log_entries.message, 'random'), 0))))) GROUP BY s.session_id HAVING 1 ORDER BY start_time DESC diff --git a/posthog/session_recordings/queries/test/__snapshots__/test_session_recording_list_from_query.ambr b/posthog/session_recordings/queries/test/__snapshots__/test_session_recording_list_from_query.ambr new file mode 100644 index 0000000000000..34f0b42e60970 --- /dev/null +++ b/posthog/session_recordings/queries/test/__snapshots__/test_session_recording_list_from_query.ambr @@ -0,0 +1,5853 @@ +# serializer version: 1 +# name: TestSessionRecordingsListFromQuery.test_action_filter + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2023-01-03 23:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2022-12-14 00:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2023-01-01 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2023-01-04 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2022-12-14 00:00:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2022-12-31 23:58:00.000000', 6, 'UTC')), and(equals(events.event, 'custom-event'), and(ifNull(equals(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$browser'), ''), 'null'), '^"|"$', ''), 'Firefox'), 0), ifNull(equals(nullIf(nullIf(events.`$session_id`, ''), 'null'), 'test_action_filter-session-one'), 0), ifNull(equals(nullIf(nullIf(events.`$window_id`, ''), 'null'), 'test_action_filter-window-id'), 0)))) + GROUP BY events.`$session_id` + HAVING hasAll(groupUniqArray(events.event), ['custom-event'])))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_action_filter.1 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2023-01-03 23:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2022-12-14 00:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2023-01-01 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2023-01-04 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2022-12-14 00:00:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2022-12-31 23:58:00.000000', 6, 'UTC')), and(equals(events.event, 'custom-event'), and(ifNull(equals(nullIf(nullIf(events.`$session_id`, ''), 'null'), 'test_action_filter-session-one'), 0), ifNull(equals(nullIf(nullIf(events.`$window_id`, ''), 'null'), 'test_action_filter-window-id'), 0)))) + GROUP BY events.`$session_id` + HAVING hasAll(groupUniqArray(events.event), ['custom-event'])))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_action_filter.2 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2023-01-03 23:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2022-12-14 00:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2023-01-01 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2023-01-04 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2022-12-14 00:00:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2022-12-31 23:58:00.000000', 6, 'UTC')), and(and(equals(events.event, 'custom-event'), and(ifNull(equals(nullIf(nullIf(events.`$session_id`, ''), 'null'), 'test_action_filter-session-one'), 0), ifNull(equals(nullIf(nullIf(events.`$window_id`, ''), 'null'), 'test_action_filter-window-id'), 0))), ifNull(equals(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$browser'), ''), 'null'), '^"|"$', ''), 'Firefox'), 0))) + GROUP BY events.`$session_id` + HAVING hasAll(groupUniqArray(events.event), ['custom-event'])))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_action_filter.3 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2023-01-03 23:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2022-12-14 00:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2023-01-01 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2023-01-04 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2022-12-14 00:00:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2022-12-31 23:58:00.000000', 6, 'UTC')), and(and(equals(events.event, 'custom-event'), and(ifNull(equals(nullIf(nullIf(events.`$session_id`, ''), 'null'), 'test_action_filter-session-one'), 0), ifNull(equals(nullIf(nullIf(events.`$window_id`, ''), 'null'), 'test_action_filter-window-id'), 0))), ifNull(equals(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$browser'), ''), 'null'), '^"|"$', ''), 'Chrome'), 0))) + GROUP BY events.`$session_id` + HAVING hasAll(groupUniqArray(events.event), ['custom-event'])))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_all_filters_at_once + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), in(s.session_id, + (SELECT DISTINCT events.`$session_id` AS `$session_id` + FROM events + LEFT OUTER JOIN + (SELECT argMax(person_distinct_id_overrides.person_id, person_distinct_id_overrides.version) AS person_id, person_distinct_id_overrides.distinct_id AS distinct_id + FROM person_distinct_id_overrides + WHERE equals(person_distinct_id_overrides.team_id, 99999) + GROUP BY person_distinct_id_overrides.distinct_id + HAVING ifNull(equals(argMax(person_distinct_id_overrides.is_deleted, person_distinct_id_overrides.version), 0), 0) SETTINGS optimize_aggregation_in_order=1) AS events__override ON equals(events.distinct_id, events__override.distinct_id) + WHERE and(equals(events.team_id, 99999), ifNull(equals(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id), '00000000-0000-0000-0000-000000000000'), 0), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-01 13:46:23.000000', 6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-22 00:00:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-04 00:00:00.000000', 6, 'UTC')), notEmpty(events.`$session_id`)))), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-22 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-04 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-21 23:58:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-04 00:00:00.000000', 6, 'UTC')), or(equals(events.event, 'custom-event'), equals(events.event, '$pageview'))) + GROUP BY events.`$session_id` + HAVING hasAll(groupUniqArray(events.event), ['$pageview', 'custom-event'])))) + GROUP BY s.session_id + HAVING ifNull(greater(duration, 60.0), 0) + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_any_event_filter_with_properties + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-21 19:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-18 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-17 23:58:00.000000', 6, 'UTC')), 1) + GROUP BY events.`$session_id` + HAVING 1))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_any_event_filter_with_properties.1 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-21 19:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-18 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-17 23:58:00.000000', 6, 'UTC')), and(1, ifNull(equals(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$browser'), ''), 'null'), '^"|"$', ''), 'Chrome'), 0))) + GROUP BY events.`$session_id` + HAVING 1))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_any_event_filter_with_properties.2 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-21 19:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-18 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-17 23:58:00.000000', 6, 'UTC')), and(1, ifNull(equals(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$browser'), ''), 'null'), '^"|"$', ''), 'Firefox'), 0))) + GROUP BY events.`$session_id` + HAVING 1))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_any_event_filter_with_properties_materialized + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-21 19:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-18 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-17 23:58:00.000000', 6, 'UTC')), 1) + GROUP BY events.`$session_id` + HAVING 1))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_any_event_filter_with_properties_materialized.1 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-21 19:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-18 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-17 23:58:00.000000', 6, 'UTC')), and(1, ifNull(equals(nullIf(nullIf(events.`mat_$browser`, ''), 'null'), 'Chrome'), 0))) + GROUP BY events.`$session_id` + HAVING 1))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_any_event_filter_with_properties_materialized.2 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-21 19:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-18 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-17 23:58:00.000000', 6, 'UTC')), and(1, ifNull(equals(nullIf(nullIf(events.`mat_$browser`, ''), 'null'), 'Firefox'), 0))) + GROUP BY events.`$session_id` + HAVING 1))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_basic_query + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0)) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_basic_query_active_sessions + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0)) + GROUP BY s.session_id + HAVING ifNull(greater(duration, 60.0), 0) + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_basic_query_active_sessions.1 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0)) + GROUP BY s.session_id + HAVING ifNull(greater(active_seconds, '60'), 0) + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_basic_query_active_sessions.2 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0)) + GROUP BY s.session_id + HAVING ifNull(greater(inactive_seconds, '60'), 0) + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_basic_query_with_ordering + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0)) + GROUP BY s.session_id + HAVING 1 + ORDER BY active_seconds DESC + LIMIT 4 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_basic_query_with_ordering.1 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0)) + GROUP BY s.session_id + HAVING 1 + ORDER BY console_error_count DESC + LIMIT 4 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_basic_query_with_ordering.2 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0)) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 4 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_basic_query_with_paging + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0)) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 2 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_basic_query_with_paging.1 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0)) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 2 + OFFSET 1 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_basic_query_with_paging.2 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0)) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 2 + OFFSET 2 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_date_from_filter + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0)) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_date_from_filter.1 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-30 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0)) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_date_from_filter_cannot_search_before_ttl + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 12:41:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 12:46:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-12 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0)) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_date_from_filter_cannot_search_before_ttl.1 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 12:41:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 12:46:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0)) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_date_from_filter_cannot_search_before_ttl.2 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 12:41:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 12:46:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-10 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0)) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_date_to_filter + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-28 23:59:59.999999', 6, 'UTC')), 0)) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_date_to_filter.1 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 23:59:59.999999', 6, 'UTC')), 0)) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_duration_filter + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0)) + GROUP BY s.session_id + HAVING ifNull(greater(duration, 60.0), 0) + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_duration_filter.1 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0)) + GROUP BY s.session_id + HAVING ifNull(less(duration, 60.0), 0) + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_event_filter + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-28 23:58:00.000000', 6, 'UTC')), equals(events.event, '$pageview')) + GROUP BY events.`$session_id` + HAVING hasAll(groupUniqArray(events.event), ['$pageview'])))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_event_filter.1 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-28 23:58:00.000000', 6, 'UTC')), equals(events.event, '$autocapture')) + GROUP BY events.`$session_id` + HAVING hasAll(groupUniqArray(events.event), ['$autocapture'])))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_event_filter_has_ttl_applied_too + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-28 23:58:00.000000', 6, 'UTC')), equals(events.event, '$pageview')) + GROUP BY events.`$session_id` + HAVING hasAll(groupUniqArray(events.event), ['$pageview'])))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_event_filter_has_ttl_applied_too.1 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0)) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_event_filter_with_active_sessions + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-28 23:58:00.000000', 6, 'UTC')), equals(events.event, '$pageview')) + GROUP BY events.`$session_id` + HAVING hasAll(groupUniqArray(events.event), ['$pageview'])))) + GROUP BY s.session_id + HAVING ifNull(greater(duration, 60.0), 0) + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_event_filter_with_active_sessions.1 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-28 23:58:00.000000', 6, 'UTC')), equals(events.event, '$pageview')) + GROUP BY events.`$session_id` + HAVING hasAll(groupUniqArray(events.event), ['$pageview'])))) + GROUP BY s.session_id + HAVING ifNull(greater(active_seconds, 60.0), 0) + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_event_filter_with_group_filter + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-21 19:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-18 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + LEFT JOIN + (SELECT argMax(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(groups.group_properties, 'name'), ''), 'null'), '^"|"$', ''), toTimeZone(groups._timestamp, 'UTC')) AS properties___name, groups.group_type_index AS index, groups.group_key AS key + FROM groups + WHERE and(equals(groups.team_id, 99999), equals(index, 1)) + GROUP BY groups.group_type_index, groups.group_key) AS events__group_1 ON equals(events.`$group_1`, events__group_1.key) + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-17 23:58:00.000000', 6, 'UTC')), and(equals(events.event, '$pageview'), ifNull(equals(events__group_1.properties___name, 'org one'), 0))) + GROUP BY events.`$session_id` + HAVING hasAll(groupUniqArray(events.event), ['$pageview'])))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_event_filter_with_group_filter.1 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-21 19:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-18 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + LEFT JOIN + (SELECT argMax(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(groups.group_properties, 'name'), ''), 'null'), '^"|"$', ''), toTimeZone(groups._timestamp, 'UTC')) AS properties___name, groups.group_type_index AS index, groups.group_key AS key + FROM groups + WHERE and(equals(groups.team_id, 99999), equals(index, 1)) + GROUP BY groups.group_type_index, groups.group_key) AS events__group_1 ON equals(events.`$group_1`, events__group_1.key) + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-17 23:58:00.000000', 6, 'UTC')), ifNull(equals(events__group_1.properties___name, 'org one'), 0)) + GROUP BY events.`$session_id` + HAVING 1))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_event_filter_with_group_filter.2 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-21 19:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-18 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + LEFT JOIN + (SELECT argMax(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(groups.group_properties, 'name'), ''), 'null'), '^"|"$', ''), toTimeZone(groups._timestamp, 'UTC')) AS properties___name, groups.group_type_index AS index, groups.group_key AS key + FROM groups + WHERE and(equals(groups.team_id, 99999), equals(index, 2)) + GROUP BY groups.group_type_index, groups.group_key) AS events__group_2 ON equals(events.`$group_2`, events__group_2.key) + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-17 23:58:00.000000', 6, 'UTC')), ifNull(equals(events__group_2.properties___name, 'org one'), 0)) + GROUP BY events.`$session_id` + HAVING 1))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_event_filter_with_hogql_event_properties_test_accounts_excluded + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-21 19:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-18 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-17 23:58:00.000000', 6, 'UTC')), equals(events.event, '$pageview')) + GROUP BY events.`$session_id` + HAVING hasAll(groupUniqArray(events.event), ['$pageview'])))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_event_filter_with_hogql_event_properties_test_accounts_excluded.1 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-21 19:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-18 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + LEFT OUTER JOIN + (SELECT argMax(person_distinct_id_overrides.person_id, person_distinct_id_overrides.version) AS person_id, person_distinct_id_overrides.distinct_id AS distinct_id + FROM person_distinct_id_overrides + WHERE equals(person_distinct_id_overrides.team_id, 99999) + GROUP BY person_distinct_id_overrides.distinct_id + HAVING ifNull(equals(argMax(person_distinct_id_overrides.is_deleted, person_distinct_id_overrides.version), 0), 0) SETTINGS optimize_aggregation_in_order=1) AS events__override ON equals(events.distinct_id, events__override.distinct_id) + LEFT JOIN + (SELECT person.id AS id, replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(person.properties, 'email'), ''), 'null'), '^"|"$', '') AS properties___email + FROM person + WHERE and(equals(person.team_id, 99999), ifNull(in(tuple(person.id, person.version), + (SELECT person.id AS id, max(person.version) AS version + FROM person + WHERE equals(person.team_id, 99999) + GROUP BY person.id + HAVING and(ifNull(equals(argMax(person.is_deleted, person.version), 0), 0), ifNull(less(argMax(toTimeZone(person.created_at, 'UTC'), person.version), plus(now64(6, 'UTC'), toIntervalDay(1))), 0)))), 0)) SETTINGS optimize_aggregation_in_order=1) AS events__person ON equals(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id), events__person.id) + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-17 23:58:00.000000', 6, 'UTC')), equals(events.event, '$pageview'), ifNull(equals(events__person.properties___email, 'bla'), 0)) + GROUP BY events.`$session_id` + HAVING hasAll(groupUniqArray(events.event), ['$pageview'])))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_event_filter_with_hogql_event_properties_test_accounts_excluded.2 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-21 19:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-18 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + LEFT OUTER JOIN + (SELECT argMax(person_distinct_id_overrides.person_id, person_distinct_id_overrides.version) AS person_id, person_distinct_id_overrides.distinct_id AS distinct_id + FROM person_distinct_id_overrides + WHERE equals(person_distinct_id_overrides.team_id, 99999) + GROUP BY person_distinct_id_overrides.distinct_id + HAVING ifNull(equals(argMax(person_distinct_id_overrides.is_deleted, person_distinct_id_overrides.version), 0), 0) SETTINGS optimize_aggregation_in_order=1) AS events__override ON equals(events.distinct_id, events__override.distinct_id) + LEFT JOIN + (SELECT person.id AS id, replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(person.properties, 'email'), ''), 'null'), '^"|"$', '') AS properties___email + FROM person + WHERE and(equals(person.team_id, 99999), ifNull(in(tuple(person.id, person.version), + (SELECT person.id AS id, max(person.version) AS version + FROM person + WHERE equals(person.team_id, 99999) + GROUP BY person.id + HAVING and(ifNull(equals(argMax(person.is_deleted, person.version), 0), 0), ifNull(less(argMax(toTimeZone(person.created_at, 'UTC'), person.version), plus(now64(6, 'UTC'), toIntervalDay(1))), 0)))), 0)) SETTINGS optimize_aggregation_in_order=1) AS events__person ON equals(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id), events__person.id) + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-17 23:58:00.000000', 6, 'UTC')), ifNull(equals(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$browser'), ''), 'null'), '^"|"$', ''), 'Chrome'), 0), ifNull(equals(events__person.properties___email, 'bla'), 0)) + GROUP BY events.`$session_id` + HAVING 1))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_event_filter_with_hogql_event_properties_test_accounts_excluded_materialized + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-21 19:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-18 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-17 23:58:00.000000', 6, 'UTC')), equals(events.event, '$pageview')) + GROUP BY events.`$session_id` + HAVING hasAll(groupUniqArray(events.event), ['$pageview'])))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_event_filter_with_hogql_event_properties_test_accounts_excluded_materialized.1 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-21 19:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-18 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + LEFT OUTER JOIN + (SELECT argMax(person_distinct_id_overrides.person_id, person_distinct_id_overrides.version) AS person_id, person_distinct_id_overrides.distinct_id AS distinct_id + FROM person_distinct_id_overrides + WHERE equals(person_distinct_id_overrides.team_id, 99999) + GROUP BY person_distinct_id_overrides.distinct_id + HAVING ifNull(equals(argMax(person_distinct_id_overrides.is_deleted, person_distinct_id_overrides.version), 0), 0) SETTINGS optimize_aggregation_in_order=1) AS events__override ON equals(events.distinct_id, events__override.distinct_id) + LEFT JOIN + (SELECT person.id AS id, nullIf(nullIf(person.pmat_email, ''), 'null') AS properties___email + FROM person + WHERE and(equals(person.team_id, 99999), ifNull(in(tuple(person.id, person.version), + (SELECT person.id AS id, max(person.version) AS version + FROM person + WHERE equals(person.team_id, 99999) + GROUP BY person.id + HAVING and(ifNull(equals(argMax(person.is_deleted, person.version), 0), 0), ifNull(less(argMax(toTimeZone(person.created_at, 'UTC'), person.version), plus(now64(6, 'UTC'), toIntervalDay(1))), 0)))), 0)) SETTINGS optimize_aggregation_in_order=1) AS events__person ON equals(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id), events__person.id) + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-17 23:58:00.000000', 6, 'UTC')), equals(events.event, '$pageview'), ifNull(equals(events__person.properties___email, 'bla'), 0)) + GROUP BY events.`$session_id` + HAVING hasAll(groupUniqArray(events.event), ['$pageview'])))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_event_filter_with_hogql_event_properties_test_accounts_excluded_materialized.2 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-21 19:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-18 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + LEFT OUTER JOIN + (SELECT argMax(person_distinct_id_overrides.person_id, person_distinct_id_overrides.version) AS person_id, person_distinct_id_overrides.distinct_id AS distinct_id + FROM person_distinct_id_overrides + WHERE equals(person_distinct_id_overrides.team_id, 99999) + GROUP BY person_distinct_id_overrides.distinct_id + HAVING ifNull(equals(argMax(person_distinct_id_overrides.is_deleted, person_distinct_id_overrides.version), 0), 0) SETTINGS optimize_aggregation_in_order=1) AS events__override ON equals(events.distinct_id, events__override.distinct_id) + LEFT JOIN + (SELECT person.id AS id, nullIf(nullIf(person.pmat_email, ''), 'null') AS properties___email + FROM person + WHERE and(equals(person.team_id, 99999), ifNull(in(tuple(person.id, person.version), + (SELECT person.id AS id, max(person.version) AS version + FROM person + WHERE equals(person.team_id, 99999) + GROUP BY person.id + HAVING and(ifNull(equals(argMax(person.is_deleted, person.version), 0), 0), ifNull(less(argMax(toTimeZone(person.created_at, 'UTC'), person.version), plus(now64(6, 'UTC'), toIntervalDay(1))), 0)))), 0)) SETTINGS optimize_aggregation_in_order=1) AS events__person ON equals(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id), events__person.id) + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-17 23:58:00.000000', 6, 'UTC')), ifNull(equals(nullIf(nullIf(events.`mat_$browser`, ''), 'null'), 'Chrome'), 0), ifNull(equals(events__person.properties___email, 'bla'), 0)) + GROUP BY events.`$session_id` + HAVING 1))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_event_filter_with_hogql_person_properties + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + LEFT OUTER JOIN + (SELECT argMax(person_distinct_id_overrides.person_id, person_distinct_id_overrides.version) AS person_id, person_distinct_id_overrides.distinct_id AS distinct_id + FROM person_distinct_id_overrides + WHERE equals(person_distinct_id_overrides.team_id, 99999) + GROUP BY person_distinct_id_overrides.distinct_id + HAVING ifNull(equals(argMax(person_distinct_id_overrides.is_deleted, person_distinct_id_overrides.version), 0), 0) SETTINGS optimize_aggregation_in_order=1) AS events__override ON equals(events.distinct_id, events__override.distinct_id) + LEFT JOIN + (SELECT person.id AS id, replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(person.properties, 'email'), ''), 'null'), '^"|"$', '') AS properties___email + FROM person + WHERE and(equals(person.team_id, 99999), ifNull(in(tuple(person.id, person.version), + (SELECT person.id AS id, max(person.version) AS version + FROM person + WHERE equals(person.team_id, 99999) + GROUP BY person.id + HAVING and(ifNull(equals(argMax(person.is_deleted, person.version), 0), 0), ifNull(less(argMax(toTimeZone(person.created_at, 'UTC'), person.version), plus(now64(6, 'UTC'), toIntervalDay(1))), 0)))), 0)) SETTINGS optimize_aggregation_in_order=1) AS events__person ON equals(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id), events__person.id) + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-28 23:58:00.000000', 6, 'UTC')), and(equals(events.event, '$pageview'), ifNull(equals(events__person.properties___email, 'bla'), 0))) + GROUP BY events.`$session_id` + HAVING hasAll(groupUniqArray(events.event), ['$pageview'])))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_event_filter_with_hogql_person_properties.1 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + LEFT OUTER JOIN + (SELECT argMax(person_distinct_id_overrides.person_id, person_distinct_id_overrides.version) AS person_id, person_distinct_id_overrides.distinct_id AS distinct_id + FROM person_distinct_id_overrides + WHERE equals(person_distinct_id_overrides.team_id, 99999) + GROUP BY person_distinct_id_overrides.distinct_id + HAVING ifNull(equals(argMax(person_distinct_id_overrides.is_deleted, person_distinct_id_overrides.version), 0), 0) SETTINGS optimize_aggregation_in_order=1) AS events__override ON equals(events.distinct_id, events__override.distinct_id) + LEFT JOIN + (SELECT person.id AS id, replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(person.properties, 'email'), ''), 'null'), '^"|"$', '') AS properties___email + FROM person + WHERE and(equals(person.team_id, 99999), ifNull(in(tuple(person.id, person.version), + (SELECT person.id AS id, max(person.version) AS version + FROM person + WHERE equals(person.team_id, 99999) + GROUP BY person.id + HAVING and(ifNull(equals(argMax(person.is_deleted, person.version), 0), 0), ifNull(less(argMax(toTimeZone(person.created_at, 'UTC'), person.version), plus(now64(6, 'UTC'), toIntervalDay(1))), 0)))), 0)) SETTINGS optimize_aggregation_in_order=1) AS events__person ON equals(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id), events__person.id) + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-28 23:58:00.000000', 6, 'UTC')), and(equals(events.event, '$pageview'), ifNull(equals(events__person.properties___email, 'something else'), 0))) + GROUP BY events.`$session_id` + HAVING hasAll(groupUniqArray(events.event), ['$pageview'])))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_event_filter_with_hogql_properties + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-28 23:58:00.000000', 6, 'UTC')), and(equals(events.event, '$pageview'), ifNull(equals(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$browser'), ''), 'null'), '^"|"$', ''), 'Chrome'), 0))) + GROUP BY events.`$session_id` + HAVING hasAll(groupUniqArray(events.event), ['$pageview'])))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_event_filter_with_hogql_properties.1 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-28 23:58:00.000000', 6, 'UTC')), and(equals(events.event, '$pageview'), ifNull(equals(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$browser'), ''), 'null'), '^"|"$', ''), 'Firefox'), 0))) + GROUP BY events.`$session_id` + HAVING hasAll(groupUniqArray(events.event), ['$pageview'])))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_event_filter_with_hogql_properties_materialized + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-28 23:58:00.000000', 6, 'UTC')), and(equals(events.event, '$pageview'), ifNull(equals(nullIf(nullIf(events.`mat_$browser`, ''), 'null'), 'Chrome'), 0))) + GROUP BY events.`$session_id` + HAVING hasAll(groupUniqArray(events.event), ['$pageview'])))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_event_filter_with_hogql_properties_materialized.1 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-28 23:58:00.000000', 6, 'UTC')), and(equals(events.event, '$pageview'), ifNull(equals(nullIf(nullIf(events.`mat_$browser`, ''), 'null'), 'Firefox'), 0))) + GROUP BY events.`$session_id` + HAVING hasAll(groupUniqArray(events.event), ['$pageview'])))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_event_filter_with_matching_on_session_id + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-28 23:58:00.000000', 6, 'UTC')), equals(events.event, '$pageview')) + GROUP BY events.`$session_id` + HAVING hasAll(groupUniqArray(events.event), ['$pageview'])))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_event_filter_with_matching_on_session_id.1 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-28 23:58:00.000000', 6, 'UTC')), equals(events.event, '$autocapture')) + GROUP BY events.`$session_id` + HAVING hasAll(groupUniqArray(events.event), ['$autocapture'])))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_event_filter_with_properties + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-28 23:58:00.000000', 6, 'UTC')), and(equals(events.event, '$pageview'), ifNull(equals(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$browser'), ''), 'null'), '^"|"$', ''), 'Chrome'), 0))) + GROUP BY events.`$session_id` + HAVING hasAll(groupUniqArray(events.event), ['$pageview'])))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_event_filter_with_properties.1 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-28 23:58:00.000000', 6, 'UTC')), and(equals(events.event, '$pageview'), ifNull(equals(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$browser'), ''), 'null'), '^"|"$', ''), 'Firefox'), 0))) + GROUP BY events.`$session_id` + HAVING hasAll(groupUniqArray(events.event), ['$pageview'])))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_event_filter_with_properties.2 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-28 23:58:00.000000', 6, 'UTC')), and(equals(events.event, 'a_different_event'), ifNull(equals(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$browser'), ''), 'null'), '^"|"$', ''), 'Chrome'), 0))) + GROUP BY events.`$session_id` + HAVING hasAll(groupUniqArray(events.event), ['a_different_event'])))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_event_filter_with_properties.3 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-28 23:58:00.000000', 6, 'UTC')), and(equals(events.event, 'a_different_event'), ifNull(equals(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$browser'), ''), 'null'), '^"|"$', ''), 'Safari'), 0))) + GROUP BY events.`$session_id` + HAVING hasAll(groupUniqArray(events.event), ['a_different_event'])))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_event_filter_with_properties_materialized + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-28 23:58:00.000000', 6, 'UTC')), and(equals(events.event, '$pageview'), ifNull(equals(nullIf(nullIf(events.`mat_$browser`, ''), 'null'), 'Chrome'), 0))) + GROUP BY events.`$session_id` + HAVING hasAll(groupUniqArray(events.event), ['$pageview'])))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_event_filter_with_properties_materialized.1 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-28 23:58:00.000000', 6, 'UTC')), and(equals(events.event, '$pageview'), ifNull(equals(nullIf(nullIf(events.`mat_$browser`, ''), 'null'), 'Firefox'), 0))) + GROUP BY events.`$session_id` + HAVING hasAll(groupUniqArray(events.event), ['$pageview'])))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_event_filter_with_properties_materialized.2 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-28 23:58:00.000000', 6, 'UTC')), and(equals(events.event, 'a_different_event'), ifNull(equals(nullIf(nullIf(events.`mat_$browser`, ''), 'null'), 'Chrome'), 0))) + GROUP BY events.`$session_id` + HAVING hasAll(groupUniqArray(events.event), ['a_different_event'])))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_event_filter_with_properties_materialized.3 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-28 23:58:00.000000', 6, 'UTC')), and(equals(events.event, 'a_different_event'), ifNull(equals(nullIf(nullIf(events.`mat_$browser`, ''), 'null'), 'Safari'), 0))) + GROUP BY events.`$session_id` + HAVING hasAll(groupUniqArray(events.event), ['a_different_event'])))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_event_filter_with_test_accounts_excluded + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-21 19:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-18 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + LEFT OUTER JOIN + (SELECT argMax(person_distinct_id_overrides.person_id, person_distinct_id_overrides.version) AS person_id, person_distinct_id_overrides.distinct_id AS distinct_id + FROM person_distinct_id_overrides + WHERE equals(person_distinct_id_overrides.team_id, 99999) + GROUP BY person_distinct_id_overrides.distinct_id + HAVING ifNull(equals(argMax(person_distinct_id_overrides.is_deleted, person_distinct_id_overrides.version), 0), 0) SETTINGS optimize_aggregation_in_order=1) AS events__override ON equals(events.distinct_id, events__override.distinct_id) + LEFT JOIN + (SELECT person.id AS id, replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(person.properties, 'email'), ''), 'null'), '^"|"$', '') AS properties___email + FROM person + WHERE and(equals(person.team_id, 99999), ifNull(in(tuple(person.id, person.version), + (SELECT person.id AS id, max(person.version) AS version + FROM person + WHERE equals(person.team_id, 99999) + GROUP BY person.id + HAVING and(ifNull(equals(argMax(person.is_deleted, person.version), 0), 0), ifNull(less(argMax(toTimeZone(person.created_at, 'UTC'), person.version), plus(now64(6, 'UTC'), toIntervalDay(1))), 0)))), 0)) SETTINGS optimize_aggregation_in_order=1) AS events__person ON equals(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id), events__person.id) + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-17 23:58:00.000000', 6, 'UTC')), equals(events.event, '$pageview'), and(ifNull(equals(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, 'is_internal_user'), ''), 'null'), '^"|"$', ''), 'false'), 0), ifNull(equals(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$browser'), ''), 'null'), '^"|"$', ''), 'Chrome'), 0)), ifNull(notILike(events__person.properties___email, '%@posthog.com%'), 1)) + GROUP BY events.`$session_id` + HAVING hasAll(groupUniqArray(events.event), ['$pageview'])))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_event_filter_with_test_accounts_excluded.1 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-21 19:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-18 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-17 23:58:00.000000', 6, 'UTC')), equals(events.event, '$pageview')) + GROUP BY events.`$session_id` + HAVING hasAll(groupUniqArray(events.event), ['$pageview'])))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_event_filter_with_test_accounts_excluded_materialized + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-21 19:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-18 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + LEFT OUTER JOIN + (SELECT argMax(person_distinct_id_overrides.person_id, person_distinct_id_overrides.version) AS person_id, person_distinct_id_overrides.distinct_id AS distinct_id + FROM person_distinct_id_overrides + WHERE equals(person_distinct_id_overrides.team_id, 99999) + GROUP BY person_distinct_id_overrides.distinct_id + HAVING ifNull(equals(argMax(person_distinct_id_overrides.is_deleted, person_distinct_id_overrides.version), 0), 0) SETTINGS optimize_aggregation_in_order=1) AS events__override ON equals(events.distinct_id, events__override.distinct_id) + LEFT JOIN + (SELECT person.id AS id, nullIf(nullIf(person.pmat_email, ''), 'null') AS properties___email + FROM person + WHERE and(equals(person.team_id, 99999), ifNull(in(tuple(person.id, person.version), + (SELECT person.id AS id, max(person.version) AS version + FROM person + WHERE equals(person.team_id, 99999) + GROUP BY person.id + HAVING and(ifNull(equals(argMax(person.is_deleted, person.version), 0), 0), ifNull(less(argMax(toTimeZone(person.created_at, 'UTC'), person.version), plus(now64(6, 'UTC'), toIntervalDay(1))), 0)))), 0)) SETTINGS optimize_aggregation_in_order=1) AS events__person ON equals(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id), events__person.id) + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-17 23:58:00.000000', 6, 'UTC')), equals(events.event, '$pageview'), and(ifNull(equals(nullIf(nullIf(events.mat_is_internal_user, ''), 'null'), 'false'), 0), ifNull(equals(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$browser'), ''), 'null'), '^"|"$', ''), 'Chrome'), 0)), ifNull(notILike(events__person.properties___email, '%@posthog.com%'), 1)) + GROUP BY events.`$session_id` + HAVING hasAll(groupUniqArray(events.event), ['$pageview'])))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_event_filter_with_test_accounts_excluded_materialized.1 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-21 19:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-18 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-17 23:58:00.000000', 6, 'UTC')), equals(events.event, '$pageview')) + GROUP BY events.`$session_id` + HAVING hasAll(groupUniqArray(events.event), ['$pageview'])))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_event_filter_with_two_events_and_multiple_teams + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-21 19:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-18 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-17 23:58:00.000000', 6, 'UTC')), or(equals(events.event, '$pageview'), equals(events.event, '$pageleave'))) + GROUP BY events.`$session_id` + HAVING hasAll(groupUniqArray(events.event), ['$pageleave', '$pageview'])))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_filter_for_recordings_by_console_text_0__key_level_value_warn_error_operator_exact_type_log_entry_key_message_value_message_4_operator_icontains_type_log_entry_ + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-21 19:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-18 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT console_logs_log_entries.log_source_id AS log_source_id + FROM + (SELECT log_entries.log_source_id AS log_source_id, log_entries.level AS level, log_entries.message AS message + FROM log_entries + WHERE and(equals(log_entries.team_id, 99999), equals(log_entries.log_source, 'session_replay'))) AS console_logs_log_entries + WHERE or(or(ifNull(equals(console_logs_log_entries.level, 'warn'), 0), ifNull(equals(console_logs_log_entries.level, 'error'), 0)), ifNull(ilike(console_logs_log_entries.message, '%message 4%'), 0))))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_filter_for_recordings_by_console_text_1__key_level_value_warn_error_operator_exact_type_log_entry_key_message_value_message_4_operator_icontains_type_log_entry_ + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-21 19:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-18 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT console_logs_log_entries.log_source_id AS log_source_id + FROM + (SELECT log_entries.log_source_id AS log_source_id, log_entries.level AS level, log_entries.message AS message + FROM log_entries + WHERE and(equals(log_entries.team_id, 99999), equals(log_entries.log_source, 'session_replay'))) AS console_logs_log_entries + WHERE and(or(ifNull(equals(console_logs_log_entries.level, 'warn'), 0), ifNull(equals(console_logs_log_entries.level, 'error'), 0)), ifNull(ilike(console_logs_log_entries.message, '%message 4%'), 0))))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_filter_for_recordings_by_console_text_2__key_level_value_warn_error_operator_exact_type_log_entry_key_message_value_message_5_operator_icontains_type_log_entry_ + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-21 19:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-18 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT console_logs_log_entries.log_source_id AS log_source_id + FROM + (SELECT log_entries.log_source_id AS log_source_id, log_entries.level AS level, log_entries.message AS message + FROM log_entries + WHERE and(equals(log_entries.team_id, 99999), equals(log_entries.log_source, 'session_replay'))) AS console_logs_log_entries + WHERE and(or(ifNull(equals(console_logs_log_entries.level, 'warn'), 0), ifNull(equals(console_logs_log_entries.level, 'error'), 0)), ifNull(ilike(console_logs_log_entries.message, '%message 5%'), 0))))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_filter_for_recordings_by_console_text_3__key_level_value_info_operator_exact_type_log_entry_key_message_value_message_5_operator_icontains_type_log_entry_ + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-21 19:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-18 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT console_logs_log_entries.log_source_id AS log_source_id + FROM + (SELECT log_entries.log_source_id AS log_source_id, log_entries.level AS level, log_entries.message AS message + FROM log_entries + WHERE and(equals(log_entries.team_id, 99999), equals(log_entries.log_source, 'session_replay'))) AS console_logs_log_entries + WHERE and(ifNull(equals(console_logs_log_entries.level, 'info'), 0), ifNull(ilike(console_logs_log_entries.message, '%message 5%'), 0))))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_filter_for_recordings_by_snapshot_source + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0)) + GROUP BY s.session_id + HAVING ifNull(equals(argMinMerge(s.snapshot_source), 'web'), 0) + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_filter_for_recordings_by_snapshot_source.1 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0)) + GROUP BY s.session_id + HAVING ifNull(equals(argMinMerge(s.snapshot_source), 'mobile'), 0) + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_filter_for_recordings_with_console_errors + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-21 19:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-18 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT console_logs_log_entries.log_source_id AS log_source_id + FROM + (SELECT log_entries.log_source_id AS log_source_id, log_entries.level AS level + FROM log_entries + WHERE and(equals(log_entries.team_id, 99999), equals(log_entries.log_source, 'session_replay'))) AS console_logs_log_entries + WHERE ifNull(equals(console_logs_log_entries.level, 'error'), 0)))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_filter_for_recordings_with_console_errors.1 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-21 19:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-18 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT console_logs_log_entries.log_source_id AS log_source_id + FROM + (SELECT log_entries.log_source_id AS log_source_id, log_entries.level AS level + FROM log_entries + WHERE and(equals(log_entries.team_id, 99999), equals(log_entries.log_source, 'session_replay'))) AS console_logs_log_entries + WHERE ifNull(equals(console_logs_log_entries.level, 'info'), 0)))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_filter_for_recordings_with_console_logs + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-21 19:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-18 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT console_logs_log_entries.log_source_id AS log_source_id + FROM + (SELECT log_entries.log_source_id AS log_source_id, log_entries.level AS level + FROM log_entries + WHERE and(equals(log_entries.team_id, 99999), equals(log_entries.log_source, 'session_replay'))) AS console_logs_log_entries + WHERE ifNull(equals(console_logs_log_entries.level, 'info'), 0)))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_filter_for_recordings_with_console_logs.1 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-21 19:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-18 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT console_logs_log_entries.log_source_id AS log_source_id + FROM + (SELECT log_entries.log_source_id AS log_source_id, log_entries.level AS level + FROM log_entries + WHERE and(equals(log_entries.team_id, 99999), equals(log_entries.log_source, 'session_replay'))) AS console_logs_log_entries + WHERE ifNull(equals(console_logs_log_entries.level, 'warn'), 0)))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_filter_for_recordings_with_console_warns + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-21 19:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-18 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT console_logs_log_entries.log_source_id AS log_source_id + FROM + (SELECT log_entries.log_source_id AS log_source_id, log_entries.level AS level + FROM log_entries + WHERE and(equals(log_entries.team_id, 99999), equals(log_entries.log_source, 'session_replay'))) AS console_logs_log_entries + WHERE ifNull(equals(console_logs_log_entries.level, 'warn'), 0)))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_filter_for_recordings_with_console_warns.1 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-21 19:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-18 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT console_logs_log_entries.log_source_id AS log_source_id + FROM + (SELECT log_entries.log_source_id AS log_source_id, log_entries.level AS level + FROM log_entries + WHERE and(equals(log_entries.team_id, 99999), equals(log_entries.log_source, 'session_replay'))) AS console_logs_log_entries + WHERE ifNull(equals(console_logs_log_entries.level, 'info'), 0)))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_filter_for_recordings_with_mixed_console_counts + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-21 19:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-18 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT console_logs_log_entries.log_source_id AS log_source_id + FROM + (SELECT log_entries.log_source_id AS log_source_id, log_entries.level AS level + FROM log_entries + WHERE and(equals(log_entries.team_id, 99999), equals(log_entries.log_source, 'session_replay'))) AS console_logs_log_entries + WHERE or(ifNull(equals(console_logs_log_entries.level, 'warn'), 0), ifNull(equals(console_logs_log_entries.level, 'error'), 0))))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_filter_for_recordings_with_mixed_console_counts.1 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-21 19:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-18 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT console_logs_log_entries.log_source_id AS log_source_id + FROM + (SELECT log_entries.log_source_id AS log_source_id, log_entries.level AS level + FROM log_entries + WHERE and(equals(log_entries.team_id, 99999), equals(log_entries.log_source, 'session_replay'))) AS console_logs_log_entries + WHERE ifNull(equals(console_logs_log_entries.level, 'info'), 0)))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_filter_on_session_ids + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), + ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), + in(s.session_id, + ['00000000-0000-0000-0000-000000000000', '00000000-0000-0000-0000-000000000001' /* ... */], + ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), + ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0)) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_filter_on_session_ids.1 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), + ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), + in(s.session_id, + ['00000000-0000-0000-0000-000000000000', '00000000-0000-0000-0000-000000000001' /* ... */], + ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), + ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0)) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_filter_with_cohort_properties + ''' + + SELECT count(DISTINCT person_id) + FROM cohortpeople + WHERE team_id = 99999 + AND cohort_id = 99999 + AND version = NULL + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_filter_with_cohort_properties.1 + ''' + /* cohort_calculation: */ + SELECT count(DISTINCT person_id) + FROM cohortpeople + WHERE team_id = 99999 + AND cohort_id = 99999 + AND version = 0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_filter_with_cohort_properties.2 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-08-21 19:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-07-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-08-18 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-08-21 23:59:59.999999', 6, 'UTC')), 0), in(s.distinct_id, + (SELECT person_distinct_id2.distinct_id AS distinct_id + FROM person_distinct_id2 + WHERE and(equals(person_distinct_id2.team_id, 99999), in(person_distinct_id2.distinct_id, + (SELECT person_distinct_id2.distinct_id AS distinct_id + FROM person_distinct_id2 + WHERE and(equals(person_distinct_id2.team_id, 99999), 1, in(person_distinct_id2.person_id, + (SELECT cohortpeople.person_id AS person_id + FROM cohortpeople + WHERE and(equals(cohortpeople.team_id, 99999), equals(cohortpeople.cohort_id, 99999), equals(cohortpeople.version, 0)))))))) + GROUP BY person_distinct_id2.distinct_id + HAVING and(ifNull(equals(argMax(person_distinct_id2.is_deleted, person_distinct_id2.version), 0), 0), in(person_distinct_id2.person_id, + (SELECT cohortpeople.person_id AS person_id + FROM cohortpeople + WHERE and(equals(cohortpeople.team_id, 99999), equals(cohortpeople.cohort_id, 99999), equals(cohortpeople.version, 0)))))))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_filter_with_events_and_cohorts + ''' + + SELECT count(DISTINCT person_id) + FROM cohortpeople + WHERE team_id = 99999 + AND cohort_id = 99999 + AND version = NULL + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_filter_with_events_and_cohorts.1 + ''' + /* cohort_calculation: */ + SELECT count(DISTINCT person_id) + FROM cohortpeople + WHERE team_id = 99999 + AND cohort_id = 99999 + AND version = 0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_filter_with_events_and_cohorts.2 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-08-21 19:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-07-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-08-18 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-08-21 23:59:59.999999', 6, 'UTC')), 0), and(in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-07-31 20:00:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-08-17 23:58:00.000000', 6, 'UTC')), equals(events.event, '$pageview')) + GROUP BY events.`$session_id` + HAVING hasAll(groupUniqArray(events.event), ['$pageview']))), in(s.distinct_id, + (SELECT person_distinct_id2.distinct_id AS distinct_id + FROM person_distinct_id2 + WHERE and(equals(person_distinct_id2.team_id, 99999), in(person_distinct_id2.distinct_id, + (SELECT person_distinct_id2.distinct_id AS distinct_id + FROM person_distinct_id2 + WHERE and(equals(person_distinct_id2.team_id, 99999), 1, in(person_distinct_id2.person_id, + (SELECT cohortpeople.person_id AS person_id + FROM cohortpeople + WHERE and(equals(cohortpeople.team_id, 99999), equals(cohortpeople.cohort_id, 99999), equals(cohortpeople.version, 0)))))))) + GROUP BY person_distinct_id2.distinct_id + HAVING and(ifNull(equals(argMax(person_distinct_id2.is_deleted, person_distinct_id2.version), 0), 0), in(person_distinct_id2.person_id, + (SELECT cohortpeople.person_id AS person_id + FROM cohortpeople + WHERE and(equals(cohortpeople.team_id, 99999), equals(cohortpeople.cohort_id, 99999), equals(cohortpeople.version, 0))))))))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_filter_with_events_and_cohorts.3 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-08-21 19:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-07-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-08-18 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-08-21 23:59:59.999999', 6, 'UTC')), 0), and(in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-07-31 20:00:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-08-17 23:58:00.000000', 6, 'UTC')), equals(events.event, 'custom_event')) + GROUP BY events.`$session_id` + HAVING hasAll(groupUniqArray(events.event), ['custom_event']))), in(s.distinct_id, + (SELECT person_distinct_id2.distinct_id AS distinct_id + FROM person_distinct_id2 + WHERE and(equals(person_distinct_id2.team_id, 99999), in(person_distinct_id2.distinct_id, + (SELECT person_distinct_id2.distinct_id AS distinct_id + FROM person_distinct_id2 + WHERE and(equals(person_distinct_id2.team_id, 99999), 1, in(person_distinct_id2.person_id, + (SELECT cohortpeople.person_id AS person_id + FROM cohortpeople + WHERE and(equals(cohortpeople.team_id, 99999), equals(cohortpeople.cohort_id, 99999), equals(cohortpeople.version, 0)))))))) + GROUP BY person_distinct_id2.distinct_id + HAVING and(ifNull(equals(argMax(person_distinct_id2.is_deleted, person_distinct_id2.version), 0), 0), in(person_distinct_id2.person_id, + (SELECT cohortpeople.person_id AS person_id + FROM cohortpeople + WHERE and(equals(cohortpeople.team_id, 99999), equals(cohortpeople.cohort_id, 99999), equals(cohortpeople.version, 0))))))))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_filter_with_person_properties_exact + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + LEFT OUTER JOIN + (SELECT argMax(person_distinct_id_overrides.person_id, person_distinct_id_overrides.version) AS person_id, person_distinct_id_overrides.distinct_id AS distinct_id + FROM person_distinct_id_overrides + WHERE equals(person_distinct_id_overrides.team_id, 99999) + GROUP BY person_distinct_id_overrides.distinct_id + HAVING ifNull(equals(argMax(person_distinct_id_overrides.is_deleted, person_distinct_id_overrides.version), 0), 0) SETTINGS optimize_aggregation_in_order=1) AS events__override ON equals(events.distinct_id, events__override.distinct_id) + LEFT JOIN + (SELECT person.id AS id, replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(person.properties, 'email'), ''), 'null'), '^"|"$', '') AS properties___email + FROM person + WHERE and(equals(person.team_id, 99999), ifNull(in(tuple(person.id, person.version), + (SELECT person.id AS id, max(person.version) AS version + FROM person + WHERE equals(person.team_id, 99999) + GROUP BY person.id + HAVING and(ifNull(equals(argMax(person.is_deleted, person.version), 0), 0), ifNull(less(argMax(toTimeZone(person.created_at, 'UTC'), person.version), plus(now64(6, 'UTC'), toIntervalDay(1))), 0)))), 0)) SETTINGS optimize_aggregation_in_order=1) AS events__person ON equals(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id), events__person.id) + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-28 23:58:00.000000', 6, 'UTC')), ifNull(equals(events__person.properties___email, 'bla@gmail.com'), 0)) + GROUP BY events.`$session_id` + HAVING 1))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_filter_with_person_properties_not_contains + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + LEFT OUTER JOIN + (SELECT argMax(person_distinct_id_overrides.person_id, person_distinct_id_overrides.version) AS person_id, person_distinct_id_overrides.distinct_id AS distinct_id + FROM person_distinct_id_overrides + WHERE equals(person_distinct_id_overrides.team_id, 99999) + GROUP BY person_distinct_id_overrides.distinct_id + HAVING ifNull(equals(argMax(person_distinct_id_overrides.is_deleted, person_distinct_id_overrides.version), 0), 0) SETTINGS optimize_aggregation_in_order=1) AS events__override ON equals(events.distinct_id, events__override.distinct_id) + LEFT JOIN + (SELECT person.id AS id, replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(person.properties, 'email'), ''), 'null'), '^"|"$', '') AS properties___email + FROM person + WHERE and(equals(person.team_id, 99999), ifNull(in(tuple(person.id, person.version), + (SELECT person.id AS id, max(person.version) AS version + FROM person + WHERE equals(person.team_id, 99999) + GROUP BY person.id + HAVING and(ifNull(equals(argMax(person.is_deleted, person.version), 0), 0), ifNull(less(argMax(toTimeZone(person.created_at, 'UTC'), person.version), plus(now64(6, 'UTC'), toIntervalDay(1))), 0)))), 0)) SETTINGS optimize_aggregation_in_order=1) AS events__person ON equals(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id), events__person.id) + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-28 23:58:00.000000', 6, 'UTC')), ifNull(notILike(events__person.properties___email, '%gmail.com%'), 1)) + GROUP BY events.`$session_id` + HAVING 1))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_filter_with_static_and_dynamic_cohort_properties + ''' + + SELECT count(DISTINCT person_id) + FROM person_static_cohort + WHERE team_id = 99999 + AND cohort_id = 99999 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_filter_with_static_and_dynamic_cohort_properties.1 + ''' + + SELECT count(DISTINCT person_id) + FROM cohortpeople + WHERE team_id = 99999 + AND cohort_id = 99999 + AND version = NULL + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_filter_with_static_and_dynamic_cohort_properties.2 + ''' + /* cohort_calculation: */ + SELECT count(DISTINCT person_id) + FROM cohortpeople + WHERE team_id = 99999 + AND cohort_id = 99999 + AND version = 0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_filter_with_static_and_dynamic_cohort_properties.3 + ''' + + SELECT count(DISTINCT person_id) + FROM cohortpeople + WHERE team_id = 99999 + AND cohort_id = 99999 + AND version = NULL + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_filter_with_static_and_dynamic_cohort_properties.4 + ''' + /* cohort_calculation: */ + SELECT count(DISTINCT person_id) + FROM cohortpeople + WHERE team_id = 99999 + AND cohort_id = 99999 + AND version = 0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_filter_with_static_and_dynamic_cohort_properties.5 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-08-21 19:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-07-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-08-18 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-08-21 23:59:59.999999', 6, 'UTC')), 0), in(s.distinct_id, + (SELECT person_distinct_id2.distinct_id AS distinct_id + FROM person_distinct_id2 + WHERE and(equals(person_distinct_id2.team_id, 99999), in(person_distinct_id2.distinct_id, + (SELECT person_distinct_id2.distinct_id AS distinct_id + FROM person_distinct_id2 + WHERE and(equals(person_distinct_id2.team_id, 99999), 1, in(person_distinct_id2.person_id, + (SELECT person_static_cohort.person_id AS person_id + FROM person_static_cohort + WHERE and(equals(person_static_cohort.team_id, 99999), equals(person_static_cohort.cohort_id, 99999)))))))) + GROUP BY person_distinct_id2.distinct_id + HAVING and(ifNull(equals(argMax(person_distinct_id2.is_deleted, person_distinct_id2.version), 0), 0), in(person_distinct_id2.person_id, + (SELECT person_static_cohort.person_id AS person_id + FROM person_static_cohort + WHERE and(equals(person_static_cohort.team_id, 99999), equals(person_static_cohort.cohort_id, 99999)))))))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_filter_with_static_and_dynamic_cohort_properties.6 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-08-21 19:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-07-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-08-18 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-08-21 23:59:59.999999', 6, 'UTC')), 0), in(s.distinct_id, + (SELECT person_distinct_id2.distinct_id AS distinct_id + FROM person_distinct_id2 + WHERE and(equals(person_distinct_id2.team_id, 99999), in(person_distinct_id2.distinct_id, + (SELECT person_distinct_id2.distinct_id AS distinct_id + FROM person_distinct_id2 + WHERE and(equals(person_distinct_id2.team_id, 99999), 1, in(person_distinct_id2.person_id, + (SELECT cohortpeople.person_id AS person_id + FROM cohortpeople + WHERE and(equals(cohortpeople.team_id, 99999), equals(cohortpeople.cohort_id, 99999), equals(cohortpeople.version, 0)))))))) + GROUP BY person_distinct_id2.distinct_id + HAVING and(ifNull(equals(argMax(person_distinct_id2.is_deleted, person_distinct_id2.version), 0), 0), in(person_distinct_id2.person_id, + (SELECT cohortpeople.person_id AS person_id + FROM cohortpeople + WHERE and(equals(cohortpeople.team_id, 99999), equals(cohortpeople.cohort_id, 99999), equals(cohortpeople.version, 0)))))))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_filter_with_static_and_dynamic_cohort_properties.7 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-08-21 19:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-07-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-08-18 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-08-21 23:59:59.999999', 6, 'UTC')), 0), in(s.distinct_id, + (SELECT person_distinct_id2.distinct_id AS distinct_id + FROM person_distinct_id2 + WHERE and(equals(person_distinct_id2.team_id, 99999), in(person_distinct_id2.distinct_id, + (SELECT person_distinct_id2.distinct_id AS distinct_id + FROM person_distinct_id2 + WHERE and(equals(person_distinct_id2.team_id, 99999), 1, and(in(person_distinct_id2.person_id, + (SELECT cohortpeople.person_id AS person_id + FROM cohortpeople + WHERE and(equals(cohortpeople.team_id, 99999), equals(cohortpeople.cohort_id, 99999), equals(cohortpeople.version, 0)))), in(person_distinct_id2.person_id, + (SELECT person_static_cohort.person_id AS person_id + FROM person_static_cohort + WHERE and(equals(person_static_cohort.team_id, 99999), equals(person_static_cohort.cohort_id, 99999))))))))) + GROUP BY person_distinct_id2.distinct_id + HAVING and(ifNull(equals(argMax(person_distinct_id2.is_deleted, person_distinct_id2.version), 0), 0), and(in(person_distinct_id2.person_id, + (SELECT cohortpeople.person_id AS person_id + FROM cohortpeople + WHERE and(equals(cohortpeople.team_id, 99999), equals(cohortpeople.cohort_id, 99999), equals(cohortpeople.version, 0)))), in(person_distinct_id2.person_id, + (SELECT person_static_cohort.person_id AS person_id + FROM person_static_cohort + WHERE and(equals(person_static_cohort.team_id, 99999), equals(person_static_cohort.cohort_id, 99999))))))))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_listing_ignores_future_replays + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2023-08-30 11:55:01.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2023-08-09 12:00:01.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2023-08-27 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2023-08-30 23:59:59.999999', 6, 'UTC')), 0)) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_multiple_event_filters + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-28 23:58:00.000000', 6, 'UTC')), or(equals(events.event, '$pageview'), equals(events.event, 'new-event'))) + GROUP BY events.`$session_id` + HAVING hasAll(groupUniqArray(events.event), ['$pageview', 'new-event'])))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_multiple_event_filters.1 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-28 23:58:00.000000', 6, 'UTC')), or(equals(events.event, '$pageview'), equals(events.event, 'new-event2'))) + GROUP BY events.`$session_id` + HAVING hasAll(groupUniqArray(events.event), ['$pageview', 'new-event2'])))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_multiple_event_filters.2 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-28 23:58:00.000000', 6, 'UTC')), or(equals(events.event, '$pageview'), equals(events.event, 'new-event2'))) + GROUP BY events.`$session_id` + HAVING hasAny(groupUniqArray(events.event), ['$pageview', 'new-event2'])))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_multiple_event_filters.3 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-28 23:58:00.000000', 6, 'UTC')), or(and(equals(events.event, '$pageview'), ifNull(equals(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, 'foo'), ''), 'null'), '^"|"$', ''), 'bar'), 0)), and(equals(events.event, '$pageview'), ifNull(equals(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, 'bar'), ''), 'null'), '^"|"$', ''), 'foo'), 0)))) + GROUP BY events.`$session_id` + HAVING hasAll(groupUniqArray(events.event), ['$pageview'])))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_multiple_event_filters.4 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-28 23:58:00.000000', 6, 'UTC')), or(and(equals(events.event, '$pageview'), ifNull(equals(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, 'foo'), ''), 'null'), '^"|"$', ''), 'bar'), 0)), and(equals(events.event, 'new-event'), ifNull(equals(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, 'foo'), ''), 'null'), '^"|"$', ''), 'bar'), 0)))) + GROUP BY events.`$session_id` + HAVING hasAll(groupUniqArray(events.event), ['$pageview', 'new-event'])))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_multiple_event_filters.5 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-28 23:58:00.000000', 6, 'UTC')), or(and(equals(events.event, '$pageview'), ifNull(equals(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, 'foo'), ''), 'null'), '^"|"$', ''), 'bar'), 0)), and(equals(events.event, 'new-event'), ifNull(equals(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, 'foo'), ''), 'null'), '^"|"$', ''), 'bar'), 0)))) + GROUP BY events.`$session_id` + HAVING hasAny(groupUniqArray(events.event), ['$pageview', 'new-event'])))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_operand_or_event_filters + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-28 23:58:00.000000', 6, 'UTC')), or(equals(events.event, '$pageview'), equals(events.event, 'custom_event'))) + GROUP BY events.`$session_id` + HAVING hasAll(groupUniqArray(events.event), ['$pageview', 'custom_event'])))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_operand_or_event_filters.1 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-28 23:58:00.000000', 6, 'UTC')), or(equals(events.event, '$pageview'), equals(events.event, 'custom_event'))) + GROUP BY events.`$session_id` + HAVING hasAny(groupUniqArray(events.event), ['$pageview', 'custom_event'])))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_operand_or_filters_0__key_level_value_warn_operator_exact_type_log_entry_key_message_value_random_operator_exact_type_log_entry_ + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT console_logs_log_entries.log_source_id AS log_source_id + FROM + (SELECT log_entries.log_source_id AS log_source_id, log_entries.level AS level, log_entries.message AS message + FROM log_entries + WHERE and(equals(log_entries.team_id, 99999), equals(log_entries.log_source, 'session_replay'))) AS console_logs_log_entries + WHERE and(ifNull(equals(console_logs_log_entries.level, 'warn'), 0), ifNull(equals(console_logs_log_entries.message, 'random'), 0))))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_operand_or_filters_1__key_level_value_info_operator_exact_type_log_entry_key_message_value_random_operator_exact_type_log_entry_ + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT console_logs_log_entries.log_source_id AS log_source_id + FROM + (SELECT log_entries.log_source_id AS log_source_id, log_entries.level AS level, log_entries.message AS message + FROM log_entries + WHERE and(equals(log_entries.team_id, 99999), equals(log_entries.log_source, 'session_replay'))) AS console_logs_log_entries + WHERE and(ifNull(equals(console_logs_log_entries.level, 'info'), 0), ifNull(equals(console_logs_log_entries.message, 'random'), 0))))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_operand_or_filters_2__key_level_value_warn_operator_exact_type_log_entry_ + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT console_logs_log_entries.log_source_id AS log_source_id + FROM + (SELECT log_entries.log_source_id AS log_source_id, log_entries.level AS level + FROM log_entries + WHERE and(equals(log_entries.team_id, 99999), equals(log_entries.log_source, 'session_replay'))) AS console_logs_log_entries + WHERE ifNull(equals(console_logs_log_entries.level, 'warn'), 0)))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_operand_or_filters_3__key_message_value_random_operator_exact_type_log_entry_ + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT console_logs_log_entries.log_source_id AS log_source_id + FROM + (SELECT log_entries.log_source_id AS log_source_id, log_entries.message AS message + FROM log_entries + WHERE and(equals(log_entries.team_id, 99999), equals(log_entries.log_source, 'session_replay'))) AS console_logs_log_entries + WHERE ifNull(equals(console_logs_log_entries.message, 'random'), 0)))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_operand_or_filters_4__key_level_value_warn_operator_exact_type_log_entry_key_message_value_random_operator_exact_type_log_entry_ + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT console_logs_log_entries.log_source_id AS log_source_id + FROM + (SELECT log_entries.log_source_id AS log_source_id, log_entries.level AS level, log_entries.message AS message + FROM log_entries + WHERE and(equals(log_entries.team_id, 99999), equals(log_entries.log_source, 'session_replay'))) AS console_logs_log_entries + WHERE or(ifNull(equals(console_logs_log_entries.level, 'warn'), 0), ifNull(equals(console_logs_log_entries.message, 'random'), 0))))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_operand_or_mandatory_filters + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), in(s.session_id, + (SELECT DISTINCT events.`$session_id` AS `$session_id` + FROM events + LEFT OUTER JOIN + (SELECT argMax(person_distinct_id_overrides.person_id, person_distinct_id_overrides.version) AS person_id, person_distinct_id_overrides.distinct_id AS distinct_id + FROM person_distinct_id_overrides + WHERE equals(person_distinct_id_overrides.team_id, 99999) + GROUP BY person_distinct_id_overrides.distinct_id + HAVING ifNull(equals(argMax(person_distinct_id_overrides.is_deleted, person_distinct_id_overrides.version), 0), 0) SETTINGS optimize_aggregation_in_order=1) AS events__override ON equals(events.distinct_id, events__override.distinct_id) + WHERE and(equals(events.team_id, 99999), ifNull(equals(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id), '00000000-0000-0000-0000-000000000000'), 0), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-01 13:46:23.000000', 6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-01 13:46:23.000000', 6, 'UTC')), notEmpty(events.`$session_id`)))), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-28 23:58:00.000000', 6, 'UTC')), equals(events.event, '$pageview')) + GROUP BY events.`$session_id` + HAVING hasAny(groupUniqArray(events.event), ['$pageview'])))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_operand_or_mandatory_filters.1 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), in(s.session_id, + (SELECT DISTINCT events.`$session_id` AS `$session_id` + FROM events + LEFT OUTER JOIN + (SELECT argMax(person_distinct_id_overrides.person_id, person_distinct_id_overrides.version) AS person_id, person_distinct_id_overrides.distinct_id AS distinct_id + FROM person_distinct_id_overrides + WHERE equals(person_distinct_id_overrides.team_id, 99999) + GROUP BY person_distinct_id_overrides.distinct_id + HAVING ifNull(equals(argMax(person_distinct_id_overrides.is_deleted, person_distinct_id_overrides.version), 0), 0) SETTINGS optimize_aggregation_in_order=1) AS events__override ON equals(events.distinct_id, events__override.distinct_id) + WHERE and(equals(events.team_id, 99999), ifNull(equals(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id), '00000000-0000-0000-0000-000000000000'), 0), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-01 13:46:23.000000', 6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-01 13:46:23.000000', 6, 'UTC')), notEmpty(events.`$session_id`)))), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-28 23:58:00.000000', 6, 'UTC')), equals(events.event, '$pageview')) + GROUP BY events.`$session_id` + HAVING hasAny(groupUniqArray(events.event), ['$pageview'])))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_operand_or_mandatory_filters.2 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), in(s.session_id, ['session_id_one']), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-28 23:58:00.000000', 6, 'UTC')), equals(events.event, '$pageview'), in(events.`$session_id`, ['session_id_one'])) + GROUP BY events.`$session_id` + HAVING hasAny(groupUniqArray(events.event), ['$pageview'])))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_operand_or_mandatory_filters.3 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), in(s.session_id, ['session_id_two']), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-28 23:58:00.000000', 6, 'UTC')), equals(events.event, '$pageview'), in(events.`$session_id`, ['session_id_two'])) + GROUP BY events.`$session_id` + HAVING hasAny(groupUniqArray(events.event), ['$pageview'])))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_operand_or_person_filters + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + LEFT OUTER JOIN + (SELECT argMax(person_distinct_id_overrides.person_id, person_distinct_id_overrides.version) AS person_id, person_distinct_id_overrides.distinct_id AS distinct_id + FROM person_distinct_id_overrides + WHERE equals(person_distinct_id_overrides.team_id, 99999) + GROUP BY person_distinct_id_overrides.distinct_id + HAVING ifNull(equals(argMax(person_distinct_id_overrides.is_deleted, person_distinct_id_overrides.version), 0), 0) SETTINGS optimize_aggregation_in_order=1) AS events__override ON equals(events.distinct_id, events__override.distinct_id) + LEFT JOIN + (SELECT person.id AS id, replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(person.properties, 'email'), ''), 'null'), '^"|"$', '') AS properties___email + FROM person + WHERE and(equals(person.team_id, 99999), ifNull(in(tuple(person.id, person.version), + (SELECT person.id AS id, max(person.version) AS version + FROM person + WHERE equals(person.team_id, 99999) + GROUP BY person.id + HAVING and(ifNull(equals(argMax(person.is_deleted, person.version), 0), 0), ifNull(less(argMax(toTimeZone(person.created_at, 'UTC'), person.version), plus(now64(6, 'UTC'), toIntervalDay(1))), 0)))), 0)) SETTINGS optimize_aggregation_in_order=1) AS events__person ON equals(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id), events__person.id) + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-28 23:58:00.000000', 6, 'UTC')), and(ifNull(equals(events__person.properties___email, 'test@posthog.com'), 0), ifNull(equals(events__person.properties___email, 'david@posthog.com'), 0))) + GROUP BY events.`$session_id` + HAVING 1))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_operand_or_person_filters.1 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + LEFT OUTER JOIN + (SELECT argMax(person_distinct_id_overrides.person_id, person_distinct_id_overrides.version) AS person_id, person_distinct_id_overrides.distinct_id AS distinct_id + FROM person_distinct_id_overrides + WHERE equals(person_distinct_id_overrides.team_id, 99999) + GROUP BY person_distinct_id_overrides.distinct_id + HAVING ifNull(equals(argMax(person_distinct_id_overrides.is_deleted, person_distinct_id_overrides.version), 0), 0) SETTINGS optimize_aggregation_in_order=1) AS events__override ON equals(events.distinct_id, events__override.distinct_id) + LEFT JOIN + (SELECT person.id AS id, replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(person.properties, 'email'), ''), 'null'), '^"|"$', '') AS properties___email + FROM person + WHERE and(equals(person.team_id, 99999), ifNull(in(tuple(person.id, person.version), + (SELECT person.id AS id, max(person.version) AS version + FROM person + WHERE equals(person.team_id, 99999) + GROUP BY person.id + HAVING and(ifNull(equals(argMax(person.is_deleted, person.version), 0), 0), ifNull(less(argMax(toTimeZone(person.created_at, 'UTC'), person.version), plus(now64(6, 'UTC'), toIntervalDay(1))), 0)))), 0)) SETTINGS optimize_aggregation_in_order=1) AS events__person ON equals(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id), events__person.id) + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-28 23:58:00.000000', 6, 'UTC')), or(ifNull(equals(events__person.properties___email, 'test@posthog.com'), 0), ifNull(equals(events__person.properties___email, 'david@posthog.com'), 0))) + GROUP BY events.`$session_id` + HAVING 1))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_ordering + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-21 19:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-18 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 23:59:59.999999', 6, 'UTC')), 0)) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_ordering.1 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-21 19:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-18 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 23:59:59.999999', 6, 'UTC')), 0)) + GROUP BY s.session_id + HAVING 1 + ORDER BY mouse_activity_count DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_person_id_filter + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), in(s.session_id, + (SELECT DISTINCT events.`$session_id` AS `$session_id` + FROM events + LEFT OUTER JOIN + (SELECT argMax(person_distinct_id_overrides.person_id, person_distinct_id_overrides.version) AS person_id, person_distinct_id_overrides.distinct_id AS distinct_id + FROM person_distinct_id_overrides + WHERE equals(person_distinct_id_overrides.team_id, 99999) + GROUP BY person_distinct_id_overrides.distinct_id + HAVING ifNull(equals(argMax(person_distinct_id_overrides.is_deleted, person_distinct_id_overrides.version), 0), 0) SETTINGS optimize_aggregation_in_order=1) AS events__override ON equals(events.distinct_id, events__override.distinct_id) + WHERE and(equals(events.team_id, 99999), ifNull(equals(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id), '00000000-0000-0000-0000-000000000000'), 0), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-01 13:46:23.000000', 6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-01 13:46:23.000000', 6, 'UTC')), notEmpty(events.`$session_id`)))), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0)) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_sessions_with_current_data + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-01 13:41:23.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-11 13:46:23.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-29 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-01 23:59:59.999999', 6, 'UTC')), 0)) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_top_level_event_host_property_test_account_filter + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-21 19:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-18 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-17 23:58:00.000000', 6, 'UTC')), equals(events.event, '$pageview')) + GROUP BY events.`$session_id` + HAVING hasAll(groupUniqArray(events.event), ['$pageview'])))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_top_level_event_host_property_test_account_filter.1 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-21 19:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-18 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-17 23:58:00.000000', 6, 'UTC')), ifNull(not(match(toString(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$host'), ''), 'null'), '^"|"$', '')), '^(localhost|127\\.0\\.0\\.1)($|:)')), 1)) + GROUP BY events.`$session_id` + HAVING 1))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_top_level_event_host_property_test_account_filter_materialized + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-21 19:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-18 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-17 23:58:00.000000', 6, 'UTC')), equals(events.event, '$pageview')) + GROUP BY events.`$session_id` + HAVING hasAll(groupUniqArray(events.event), ['$pageview'])))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_top_level_event_host_property_test_account_filter_materialized.1 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-21 19:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-18 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-17 23:58:00.000000', 6, 'UTC')), ifNull(not(match(toString(nullIf(nullIf(events.`mat_$host`, ''), 'null')), '^(localhost|127\\.0\\.0\\.1)($|:)')), 1)) + GROUP BY events.`$session_id` + HAVING 1))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_top_level_event_property_test_account_filter + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-21 19:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-18 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-17 23:58:00.000000', 6, 'UTC')), equals(events.event, '$pageview')) + GROUP BY events.`$session_id` + HAVING hasAll(groupUniqArray(events.event), ['$pageview'])))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_top_level_event_property_test_account_filter.1 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-21 19:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-18 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-17 23:58:00.000000', 6, 'UTC')), ifNull(equals(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, 'is_internal_user'), ''), 'null'), '^"|"$', ''), 'false'), 0)) + GROUP BY events.`$session_id` + HAVING 1))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_top_level_event_property_test_account_filter_allowing_denormalized_props + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-21 19:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-18 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-17 23:58:00.000000', 6, 'UTC')), equals(events.event, '$pageview')) + GROUP BY events.`$session_id` + HAVING hasAll(groupUniqArray(events.event), ['$pageview'])))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_top_level_event_property_test_account_filter_allowing_denormalized_props.1 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-21 19:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-18 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-17 23:58:00.000000', 6, 'UTC')), ifNull(equals(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, 'is_internal_user'), ''), 'null'), '^"|"$', ''), 'false'), 0)) + GROUP BY events.`$session_id` + HAVING 1))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_top_level_event_property_test_account_filter_allowing_denormalized_props_materialized + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-21 19:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-18 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-17 23:58:00.000000', 6, 'UTC')), equals(events.event, '$pageview')) + GROUP BY events.`$session_id` + HAVING hasAll(groupUniqArray(events.event), ['$pageview'])))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_top_level_event_property_test_account_filter_allowing_denormalized_props_materialized.1 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-21 19:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-18 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-17 23:58:00.000000', 6, 'UTC')), ifNull(equals(nullIf(nullIf(events.mat_is_internal_user, ''), 'null'), 'false'), 0)) + GROUP BY events.`$session_id` + HAVING 1))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_top_level_event_property_test_account_filter_materialized + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-21 19:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-18 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-17 23:58:00.000000', 6, 'UTC')), equals(events.event, '$pageview')) + GROUP BY events.`$session_id` + HAVING hasAll(groupUniqArray(events.event), ['$pageview'])))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_top_level_event_property_test_account_filter_materialized.1 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-21 19:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-18 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-17 23:58:00.000000', 6, 'UTC')), ifNull(equals(nullIf(nullIf(events.mat_is_internal_user, ''), 'null'), 'false'), 0)) + GROUP BY events.`$session_id` + HAVING 1))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_top_level_hogql_event_property_test_account_filter + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-21 19:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-18 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-17 23:58:00.000000', 6, 'UTC')), equals(events.event, '$pageview')) + GROUP BY events.`$session_id` + HAVING hasAll(groupUniqArray(events.event), ['$pageview'])))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_top_level_hogql_event_property_test_account_filter.1 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-21 19:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-18 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-17 23:58:00.000000', 6, 'UTC')), ifNull(equals(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, 'is_internal_user'), ''), 'null'), '^"|"$', ''), 'true'), 0)) + GROUP BY events.`$session_id` + HAVING 1))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_top_level_hogql_event_property_test_account_filter_materialized + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-21 19:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-18 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-17 23:58:00.000000', 6, 'UTC')), equals(events.event, '$pageview')) + GROUP BY events.`$session_id` + HAVING hasAll(groupUniqArray(events.event), ['$pageview'])))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_top_level_hogql_event_property_test_account_filter_materialized.1 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-21 19:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-18 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-17 23:58:00.000000', 6, 'UTC')), ifNull(equals(nullIf(nullIf(events.mat_is_internal_user, ''), 'null'), 'true'), 0)) + GROUP BY events.`$session_id` + HAVING 1))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_top_level_hogql_person_property_test_account_filter + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-21 19:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-18 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-17 23:58:00.000000', 6, 'UTC')), equals(events.event, '$pageview')) + GROUP BY events.`$session_id` + HAVING hasAll(groupUniqArray(events.event), ['$pageview'])))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_top_level_hogql_person_property_test_account_filter.1 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-21 19:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-18 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + LEFT OUTER JOIN + (SELECT argMax(person_distinct_id_overrides.person_id, person_distinct_id_overrides.version) AS person_id, person_distinct_id_overrides.distinct_id AS distinct_id + FROM person_distinct_id_overrides + WHERE equals(person_distinct_id_overrides.team_id, 99999) + GROUP BY person_distinct_id_overrides.distinct_id + HAVING ifNull(equals(argMax(person_distinct_id_overrides.is_deleted, person_distinct_id_overrides.version), 0), 0) SETTINGS optimize_aggregation_in_order=1) AS events__override ON equals(events.distinct_id, events__override.distinct_id) + LEFT JOIN + (SELECT person.id AS id, replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(person.properties, 'email'), ''), 'null'), '^"|"$', '') AS properties___email + FROM person + WHERE and(equals(person.team_id, 99999), ifNull(in(tuple(person.id, person.version), + (SELECT person.id AS id, max(person.version) AS version + FROM person + WHERE equals(person.team_id, 99999) + GROUP BY person.id + HAVING and(ifNull(equals(argMax(person.is_deleted, person.version), 0), 0), ifNull(less(argMax(toTimeZone(person.created_at, 'UTC'), person.version), plus(now64(6, 'UTC'), toIntervalDay(1))), 0)))), 0)) SETTINGS optimize_aggregation_in_order=1) AS events__person ON equals(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id), events__person.id) + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-17 23:58:00.000000', 6, 'UTC')), ifNull(equals(events__person.properties___email, 'bla'), 0)) + GROUP BY events.`$session_id` + HAVING 1))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_top_level_hogql_person_property_test_account_filter_materialized + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-21 19:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-18 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-17 23:58:00.000000', 6, 'UTC')), equals(events.event, '$pageview')) + GROUP BY events.`$session_id` + HAVING hasAll(groupUniqArray(events.event), ['$pageview'])))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_top_level_hogql_person_property_test_account_filter_materialized.1 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-21 19:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-18 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + LEFT OUTER JOIN + (SELECT argMax(person_distinct_id_overrides.person_id, person_distinct_id_overrides.version) AS person_id, person_distinct_id_overrides.distinct_id AS distinct_id + FROM person_distinct_id_overrides + WHERE equals(person_distinct_id_overrides.team_id, 99999) + GROUP BY person_distinct_id_overrides.distinct_id + HAVING ifNull(equals(argMax(person_distinct_id_overrides.is_deleted, person_distinct_id_overrides.version), 0), 0) SETTINGS optimize_aggregation_in_order=1) AS events__override ON equals(events.distinct_id, events__override.distinct_id) + LEFT JOIN + (SELECT person.id AS id, nullIf(nullIf(person.pmat_email, ''), 'null') AS properties___email + FROM person + WHERE and(equals(person.team_id, 99999), ifNull(in(tuple(person.id, person.version), + (SELECT person.id AS id, max(person.version) AS version + FROM person + WHERE equals(person.team_id, 99999) + GROUP BY person.id + HAVING and(ifNull(equals(argMax(person.is_deleted, person.version), 0), 0), ifNull(less(argMax(toTimeZone(person.created_at, 'UTC'), person.version), plus(now64(6, 'UTC'), toIntervalDay(1))), 0)))), 0)) SETTINGS optimize_aggregation_in_order=1) AS events__person ON equals(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id), events__person.id) + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-17 23:58:00.000000', 6, 'UTC')), ifNull(equals(events__person.properties___email, 'bla'), 0)) + GROUP BY events.`$session_id` + HAVING 1))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_top_level_person_property_test_account_filter + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-21 19:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-18 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-17 23:58:00.000000', 6, 'UTC')), equals(events.event, '$pageview')) + GROUP BY events.`$session_id` + HAVING hasAll(groupUniqArray(events.event), ['$pageview'])))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_top_level_person_property_test_account_filter.1 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-21 19:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-18 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + LEFT OUTER JOIN + (SELECT argMax(person_distinct_id_overrides.person_id, person_distinct_id_overrides.version) AS person_id, person_distinct_id_overrides.distinct_id AS distinct_id + FROM person_distinct_id_overrides + WHERE equals(person_distinct_id_overrides.team_id, 99999) + GROUP BY person_distinct_id_overrides.distinct_id + HAVING ifNull(equals(argMax(person_distinct_id_overrides.is_deleted, person_distinct_id_overrides.version), 0), 0) SETTINGS optimize_aggregation_in_order=1) AS events__override ON equals(events.distinct_id, events__override.distinct_id) + LEFT JOIN + (SELECT person.id AS id, replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(person.properties, 'email'), ''), 'null'), '^"|"$', '') AS properties___email + FROM person + WHERE and(equals(person.team_id, 99999), ifNull(in(tuple(person.id, person.version), + (SELECT person.id AS id, max(person.version) AS version + FROM person + WHERE equals(person.team_id, 99999) + GROUP BY person.id + HAVING and(ifNull(equals(argMax(person.is_deleted, person.version), 0), 0), ifNull(less(argMax(toTimeZone(person.created_at, 'UTC'), person.version), plus(now64(6, 'UTC'), toIntervalDay(1))), 0)))), 0)) SETTINGS optimize_aggregation_in_order=1) AS events__person ON equals(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id), events__person.id) + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-17 23:58:00.000000', 6, 'UTC')), ifNull(equals(events__person.properties___email, 'bla'), 0)) + GROUP BY events.`$session_id` + HAVING 1))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_top_level_person_property_test_account_filter_materialized + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-21 19:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-18 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-17 23:58:00.000000', 6, 'UTC')), equals(events.event, '$pageview')) + GROUP BY events.`$session_id` + HAVING hasAll(groupUniqArray(events.event), ['$pageview'])))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- +# name: TestSessionRecordingsListFromQuery.test_top_level_person_property_test_account_filter_materialized.1 + ''' + SELECT s.session_id AS session_id, + any(s.team_id), + any(s.distinct_id), + min(toTimeZone(s.min_first_timestamp, 'UTC')) AS start_time, + max(toTimeZone(s.max_last_timestamp, 'UTC')) AS end_time, + dateDiff('SECOND', start_time, end_time) AS duration, + argMinMerge(s.first_url) AS first_url, + sum(s.click_count) AS click_count, + sum(s.keypress_count) AS keypress_count, + sum(s.mouse_activity_count) AS mouse_activity_count, + divide(sum(s.active_milliseconds), 1000) AS active_seconds, + minus(duration, active_seconds) AS inactive_seconds, + sum(s.console_log_count) AS console_log_count, + sum(s.console_warn_count) AS console_warn_count, + sum(s.console_error_count) AS console_error_count, + ifNull(greaterOrEquals(max(toTimeZone(s._timestamp, 'UTC')), toDateTime64('2021-01-21 19:55:00.000000', 6, 'UTC')), 0) AS ongoing, + round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score + FROM session_replay_events AS s + WHERE and(equals(s.team_id, 99999), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), 0), ifNull(greaterOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-18 00:00:00.000000', 6, 'UTC')), 0), ifNull(lessOrEquals(toTimeZone(s.min_first_timestamp, 'UTC'), toDateTime64('2021-01-21 23:59:59.999999', 6, 'UTC')), 0), in(s.session_id, + (SELECT events.`$session_id` AS session_id + FROM events + LEFT OUTER JOIN + (SELECT argMax(person_distinct_id_overrides.person_id, person_distinct_id_overrides.version) AS person_id, person_distinct_id_overrides.distinct_id AS distinct_id + FROM person_distinct_id_overrides + WHERE equals(person_distinct_id_overrides.team_id, 99999) + GROUP BY person_distinct_id_overrides.distinct_id + HAVING ifNull(equals(argMax(person_distinct_id_overrides.is_deleted, person_distinct_id_overrides.version), 0), 0) SETTINGS optimize_aggregation_in_order=1) AS events__override ON equals(events.distinct_id, events__override.distinct_id) + LEFT JOIN + (SELECT person.id AS id, nullIf(nullIf(person.pmat_email, ''), 'null') AS properties___email + FROM person + WHERE and(equals(person.team_id, 99999), ifNull(in(tuple(person.id, person.version), + (SELECT person.id AS id, max(person.version) AS version + FROM person + WHERE equals(person.team_id, 99999) + GROUP BY person.id + HAVING and(ifNull(equals(argMax(person.is_deleted, person.version), 0), 0), ifNull(less(argMax(toTimeZone(person.created_at, 'UTC'), person.version), plus(now64(6, 'UTC'), toIntervalDay(1))), 0)))), 0)) SETTINGS optimize_aggregation_in_order=1) AS events__person ON equals(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id), events__person.id) + WHERE and(equals(events.team_id, 99999), notEmpty(events.`$session_id`), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2020-12-31 20:00:00.000000', 6, 'UTC')), lessOrEquals(toTimeZone(events.timestamp, 'UTC'), now64(6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2021-01-17 23:58:00.000000', 6, 'UTC')), ifNull(equals(events__person.properties___email, 'bla'), 0)) + GROUP BY events.`$session_id` + HAVING 1))) + GROUP BY s.session_id + HAVING 1 + ORDER BY start_time DESC + LIMIT 51 + OFFSET 0 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0, + allow_experimental_analyzer=0 + ''' +# --- diff --git a/posthog/session_recordings/queries/test/test_session_recording_list_from_filters.py b/posthog/session_recordings/queries/test/test_session_recording_list_from_filters.py index 3633092a40e25..f8ce2daccf3d5 100644 --- a/posthog/session_recordings/queries/test/test_session_recording_list_from_filters.py +++ b/posthog/session_recordings/queries/test/test_session_recording_list_from_filters.py @@ -1,10 +1,13 @@ from datetime import datetime +from typing import Literal from unittest.mock import ANY from uuid import uuid4 from dateutil.relativedelta import relativedelta from django.utils.timezone import now from freezegun import freeze_time +from parameterized import parameterized + from posthog.clickhouse.client import sync_execute from posthog.clickhouse.log_entries import TRUNCATE_LOG_ENTRIES_TABLE_SQL from posthog.constants import AvailableFeature @@ -78,8 +81,8 @@ def create_event( properties=properties, ) - def _filter_recordings_by(self, recordings_filter: dict) -> SessionRecordingQueryResult: - the_filter = SessionRecordingsFilter(team=self.team, data=recordings_filter) + def _filter_recordings_by(self, recordings_filter: dict | None = None) -> SessionRecordingQueryResult: + the_filter = SessionRecordingsFilter(team=self.team, data=recordings_filter or {}) session_recording_list_instance = SessionRecordingListFromFilters( filter=the_filter, team=self.team, hogql_query_modifiers=None ) @@ -781,6 +784,26 @@ def test_ttl_days(self): with freeze_time("2023-09-05T12:00:01Z"): assert ttl_days(self.team) == 35 + @snapshot_clickhouse_queries + def test_listing_ignores_future_replays(self): + with freeze_time("2023-08-29T12:00:01Z"): + produce_replay_summary(team_id=self.team.id, session_id="29th Aug") + + with freeze_time("2023-09-01T12:00:01Z"): + produce_replay_summary(team_id=self.team.id, session_id="1st-sep") + + with freeze_time("2023-09-02T12:00:01Z"): + produce_replay_summary(team_id=self.team.id, session_id="2nd-sep") + + with freeze_time("2023-09-03T12:00:01Z"): + produce_replay_summary(team_id=self.team.id, session_id="3rd-sep") + + with freeze_time("2023-08-30T12:00:01Z"): + recordings = self._filter_recordings_by() + + # recordings in the future don't show + assert [s["session_id"] for s in recordings.results] == ["29th Aug"] + @snapshot_clickhouse_queries def test_filter_on_session_ids(self): user = "test_session_ids-user" @@ -1613,8 +1636,53 @@ def test_operand_or_event_filters(self): assert len(session_recordings) == 2 assert sorted([r["session_id"] for r in session_recordings]) == sorted([session_id_two, session_id_one]) + @parameterized.expand( + [ + # Case 1: Neither has WARN and message "random" + ( + '[{"key": "level", "value": ["warn"], "operator": "exact", "type": "log_entry"}, {"key": "message", "value": "random", "operator": "exact", "type": "log_entry"}]', + "AND", + 0, + [], + ), + # Case 2: AND only matches one recording + ( + '[{"key": "level", "value": ["info"], "operator": "exact", "type": "log_entry"}, {"key": "message", "value": "random", "operator": "exact", "type": "log_entry"}]', + "AND", + 1, + ["both_log_filters"], + ), + # Case 3: Only one is WARN level + ( + '[{"key": "level", "value": ["warn"], "operator": "exact", "type": "log_entry"}]', + "AND", + 1, + ["one_log_filter"], + ), + # Case 4: Only one has message "random" + ( + '[{"key": "message", "value": "random", "operator": "exact", "type": "log_entry"}]', + "AND", + 1, + ["both_log_filters"], + ), + # Case 5: OR matches both + ( + '[{"key": "level", "value": ["warn"], "operator": "exact", "type": "log_entry"}, {"key": "message", "value": "random", "operator": "exact", "type": "log_entry"}]', + "OR", + 2, + ["both_log_filters", "one_log_filter"], + ), + ] + ) @snapshot_clickhouse_queries - def test_operand_or_filters(self): + def test_operand_or_filters( + self, + console_log_filters: str, + operand: Literal["AND", "OR"], + expected_count: int, + expected_session_ids: list[str], + ) -> None: user = "test_operand_or_filter-user" Person.objects.create(team=self.team, distinct_ids=[user], properties={"email": "bla"}) @@ -1624,13 +1692,10 @@ def test_operand_or_filters(self): session_id=session_with_both_log_filters, first_timestamp=self.an_hour_ago, team_id=self.team.id, - console_warn_count=1, - log_messages={ - "warn": [ - "random", - ], - }, + console_log_count=1, + log_messages={"info": ["random"]}, ) + session_with_one_log_filter = "one_log_filter" produce_replay_summary( distinct_id="user", @@ -1638,29 +1703,15 @@ def test_operand_or_filters(self): first_timestamp=self.an_hour_ago, team_id=self.team.id, console_warn_count=1, - log_messages={ - "warn": [ - "warn", - ], - }, + log_messages={"warn": ["warn"]}, ) - (session_recordings, _, _) = self._filter_recordings_by( - { - "console_log_filters": '[{"key": "level", "value": ["warn"], "operator": "exact", "type": "log_entry"}, {"key": "message", "value": "random", "operator": "exact", "type": "log_entry"}]', - "operand": "AND", - } + session_recordings, _, _ = self._filter_recordings_by( + {"console_log_filters": console_log_filters, "operand": operand} ) - assert len(session_recordings) == 1 - assert session_recordings[0]["session_id"] == session_with_both_log_filters - (session_recordings, _, _) = self._filter_recordings_by( - { - "console_log_filters": '[{"key": "level", "value": ["warn"], "operator": "exact", "type": "log_entry"}, {"key": "message", "value": "random", "operator": "exact", "type": "log_entry"}]', - "operand": "OR", - } - ) - assert len(session_recordings) == 2 + assert len(session_recordings) == expected_count + assert sorted([rec["session_id"] for rec in session_recordings]) == sorted(expected_session_ids) @snapshot_clickhouse_queries def test_operand_or_mandatory_filters(self): @@ -3028,19 +3079,42 @@ def test_filter_for_recordings_with_mixed_console_counts(self): ] ) + @parameterized.expand( + [ + # Case 1: OR operand, message 4 matches in warn and error + ( + '[{"key": "level", "value": ["warn", "error"], "operator": "exact", "type": "log_entry"}, {"key": "message", "value": "message 4", "operator": "icontains", "type": "log_entry"}]', + "OR", + ["with-errors-session", "with-two-session", "with-warns-session", "with-logs-session"], + ), + # Case 2: AND operand, message 5 matches only in warn + ( + '[{"key": "level", "value": ["warn", "error"], "operator": "exact", "type": "log_entry"}, {"key": "message", "value": "message 5", "operator": "icontains", "type": "log_entry"}]', + "AND", + ["with-warns-session"], + ), + # Case 3: AND operand, message 5 does not match log level "info" + ( + '[{"key": "level", "value": ["info"], "operator": "exact", "type": "log_entry"}, {"key": "message", "value": "message 5", "operator": "icontains", "type": "log_entry"}]', + "AND", + [], + ), + ] + ) @snapshot_clickhouse_queries @freeze_time("2021-01-21T20:00:00.000Z") - def test_filter_for_recordings_by_console_text(self): + def test_filter_for_recordings_by_console_text( + self, + console_log_filters: str, + operand: Literal["AND", "OR"], + expected_session_ids: list[str], + ) -> None: Person.objects.create(team=self.team, distinct_ids=["user"], properties={"email": "bla"}) - with_logs_session_id = "with-logs-session" - with_warns_session_id = "with-warns-session" - with_errors_session_id = "with-errors-session" - with_two_session_id = "with-two-session" - + # Create sessions produce_replay_summary( distinct_id="user", - session_id=with_logs_session_id, + session_id="with-logs-session", first_timestamp=self.an_hour_ago, team_id=self.team.id, console_log_count=4, @@ -3055,7 +3129,7 @@ def test_filter_for_recordings_by_console_text(self): ) produce_replay_summary( distinct_id="user", - session_id=with_warns_session_id, + session_id="with-warns-session", first_timestamp=self.an_hour_ago, team_id=self.team.id, console_warn_count=5, @@ -3071,7 +3145,7 @@ def test_filter_for_recordings_by_console_text(self): ) produce_replay_summary( distinct_id="user", - session_id=with_errors_session_id, + session_id="with-errors-session", first_timestamp=self.an_hour_ago, team_id=self.team.id, console_error_count=4, @@ -3086,7 +3160,7 @@ def test_filter_for_recordings_by_console_text(self): ) produce_replay_summary( distinct_id="user", - session_id=with_two_session_id, + session_id="with-two-session", first_timestamp=self.an_hour_ago, team_id=self.team.id, console_error_count=4, @@ -3101,46 +3175,24 @@ def test_filter_for_recordings_by_console_text(self): "info": ["log message 1", "log message 2", "log message 3"], }, ) - - (session_recordings, _, _) = self._filter_recordings_by( - { - # there are 5 warn and 4 error logs, message 4 matches in both - "console_log_filters": '[{"key": "level", "value": ["warn", "error"], "operator": "exact", "type": "log_entry"}, {"key": "message", "value": "message 4", "operator": "exact", "type": "log_entry"}]', - "operand": "OR", - } - ) - - assert sorted([sr["session_id"] for sr in session_recordings]) == sorted( - [ - with_errors_session_id, - with_two_session_id, - with_warns_session_id, - ] - ) - - (session_recordings, _, _) = self._filter_recordings_by( - { - # there are 5 warn and 4 error logs, message 5 matches only matches in warn - "console_log_filters": '[{"key": "level", "value": ["warn", "error"], "operator": "exact", "type": "log_entry"}, {"key": "message", "value": "message 5", "operator": "icontains", "type": "log_entry"}]', - "operand": "AND", - } - ) - - assert sorted([sr["session_id"] for sr in session_recordings]) == sorted( - [ - with_warns_session_id, - ] + produce_replay_summary( + distinct_id="user", + session_id="with-no-matches-session", + first_timestamp=self.an_hour_ago, + team_id=self.team.id, + console_error_count=4, + console_log_count=3, + log_messages={ + "info": ["log message 1", "log message 2", "log message 3"], + }, ) - (session_recordings, _, _) = self._filter_recordings_by( - { - # message 5 does not match log level "info" - "console_log_filters": '[{"key": "level", "value": ["info"], "operator": "exact", "type": "log_entry"}, {"key": "message", "value": "message 5", "operator": "icontains", "type": "log_entry"}]', - "operand": "AND", - } + # Perform the filtering and validate results + session_recordings, _, _ = self._filter_recordings_by( + {"console_log_filters": console_log_filters, "operand": operand} ) - assert sorted([sr["session_id"] for sr in session_recordings]) == [] + assert sorted([sr["session_id"] for sr in session_recordings]) == sorted(expected_session_ids) @snapshot_clickhouse_queries def test_filter_for_recordings_by_snapshot_source(self): diff --git a/posthog/session_recordings/queries/test/test_session_recording_list_from_query.py b/posthog/session_recordings/queries/test/test_session_recording_list_from_query.py new file mode 100644 index 0000000000000..abfd8ab98b956 --- /dev/null +++ b/posthog/session_recordings/queries/test/test_session_recording_list_from_query.py @@ -0,0 +1,4170 @@ +from datetime import datetime +from typing import Literal +from unittest.mock import ANY +from uuid import uuid4 + +from dateutil.relativedelta import relativedelta +from django.utils.timezone import now +from freezegun import freeze_time +from parameterized import parameterized + +from posthog.clickhouse.client import sync_execute +from posthog.clickhouse.log_entries import TRUNCATE_LOG_ENTRIES_TABLE_SQL +from posthog.constants import AvailableFeature +from posthog.models import Cohort, GroupTypeMapping, Person +from posthog.models.action import Action +from posthog.models.group.util import create_group +from posthog.models.team import Team +from posthog.schema import RecordingsQuery +from posthog.session_recordings.queries.session_recording_list_from_query import ( + SessionRecordingQueryResult, +) +from posthog.session_recordings.queries.session_recording_list_from_query import SessionRecordingListFromQuery +from posthog.session_recordings.queries.session_replay_events import ttl_days +from posthog.session_recordings.queries.test.session_replay_sql import ( + produce_replay_summary, +) +from posthog.session_recordings.session_recording_api import query_as_params_to_dict +from posthog.session_recordings.sql.session_replay_event_sql import ( + TRUNCATE_SESSION_REPLAY_EVENTS_TABLE_SQL, +) +from posthog.test.base import ( + APIBaseTest, + ClickhouseTestMixin, + _create_event, + also_test_with_materialized_columns, + flush_persons_and_events, + snapshot_clickhouse_queries, +) + + +@freeze_time("2021-01-01T13:46:23") +class TestSessionRecordingsListFromQuery(ClickhouseTestMixin, APIBaseTest): + def setUp(self): + super().setUp() + sync_execute(TRUNCATE_SESSION_REPLAY_EVENTS_TABLE_SQL()) + sync_execute(TRUNCATE_LOG_ENTRIES_TABLE_SQL) + + def create_action(self, name, team_id=None, properties=None): + if team_id is None: + team_id = self.team.pk + if properties is None: + properties = [] + action = Action.objects.create( + team_id=team_id, + name=name, + steps_json=[ + { + "event": name, + "properties": properties, + } + ], + ) + return action + + def create_event( + self, + distinct_id, + timestamp, + team=None, + event_name="$pageview", + properties=None, + ): + if team is None: + team = self.team + if properties is None: + properties = {"$os": "Windows 95", "$current_url": "aloha.com/2"} + return _create_event( + team=team, + event=event_name, + timestamp=timestamp, + distinct_id=distinct_id, + properties=properties, + ) + + def _filter_recordings_by(self, recordings_filter: dict | None = None) -> SessionRecordingQueryResult: + the_query = RecordingsQuery.model_validate(query_as_params_to_dict(recordings_filter or {})) + session_recording_list_instance = SessionRecordingListFromQuery( + query=the_query, team=self.team, hogql_query_modifiers=None + ) + return session_recording_list_instance.run() + + @property + def an_hour_ago(self): + return (now() - relativedelta(hours=1)).replace(microsecond=0, second=0) + + @snapshot_clickhouse_queries + def test_basic_query(self): + user = "test_basic_query-user" + Person.objects.create(team=self.team, distinct_ids=[user], properties={"email": "bla"}) + + session_id_one = f"test_basic_query-{str(uuid4())}" + session_id_two = f"test_basic_query-{str(uuid4())}" + + produce_replay_summary( + session_id=session_id_one, + team_id=self.team.pk, + # can CH handle a timestamp with no T + first_timestamp=self.an_hour_ago.isoformat().replace("T", " "), + last_timestamp=(self.an_hour_ago + relativedelta(seconds=20)).isoformat().replace("T", " "), + distinct_id=user, + first_url="https://example.io/home", + click_count=2, + keypress_count=2, + mouse_activity_count=2, + active_milliseconds=50 * 1000 * 0.5, # 50% of the total expected duration + ) + + produce_replay_summary( + session_id=session_id_one, + team_id=self.team.pk, + # can CH handle a timestamp with no T + first_timestamp=(self.an_hour_ago + relativedelta(seconds=10)), + last_timestamp=(self.an_hour_ago + relativedelta(seconds=50)), + distinct_id=user, + first_url="https://a-different-url.com", + click_count=2, + keypress_count=2, + mouse_activity_count=2, + active_milliseconds=0, # 30% of the total expected duration + ) + + produce_replay_summary( + session_id=session_id_two, + team_id=self.team.pk, + # can CH handle a timestamp with no T + first_timestamp=(self.an_hour_ago + relativedelta(seconds=20)), + last_timestamp=(self.an_hour_ago + relativedelta(seconds=2000)), + distinct_id=user, + first_url="https://another-url.com", + click_count=2, + keypress_count=2, + mouse_activity_count=2, + active_milliseconds=1980 * 1000 * 0.4, # 40% of the total expected duration + ) + + session_recordings, more_recordings_available, _ = self._filter_recordings_by() + + assert session_recordings == [ + { + "session_id": session_id_two, + "activity_score": 40.16, + "team_id": self.team.pk, + "distinct_id": user, + "click_count": 2, + "keypress_count": 2, + "mouse_activity_count": 2, + "duration": 1980, + "active_seconds": 792.0, + "inactive_seconds": 1188.0, + "start_time": self.an_hour_ago + relativedelta(seconds=20), + "end_time": self.an_hour_ago + relativedelta(seconds=2000), + "first_url": "https://another-url.com", + "console_log_count": 0, + "console_warn_count": 0, + "console_error_count": 0, + "ongoing": 1, + }, + { + "session_id": session_id_one, + "activity_score": 61.11, + "team_id": self.team.pk, + "distinct_id": user, + "click_count": 4, + "keypress_count": 4, + "mouse_activity_count": 4, + "duration": 50, + "active_seconds": 25.0, + "inactive_seconds": 25.0, + "start_time": self.an_hour_ago, + "end_time": self.an_hour_ago + relativedelta(seconds=50), + "first_url": "https://example.io/home", + "console_log_count": 0, + "console_warn_count": 0, + "console_error_count": 0, + "ongoing": 1, + }, + ] + + assert more_recordings_available is False + + @snapshot_clickhouse_queries + def test_basic_query_active_sessions( + self, + ): + user = "test_basic_query-user" + Person.objects.create(team=self.team, distinct_ids=[user], properties={"email": "bla"}) + + session_id_total_is_61 = f"test_basic_query_active_sessions-total-{str(uuid4())}" + session_id_active_is_61 = f"test_basic_query_active_sessions-active-{str(uuid4())}" + session_id_inactive_is_61 = f"test_basic_query_active_sessions-inactive-{str(uuid4())}" + + produce_replay_summary( + session_id=session_id_total_is_61, + team_id=self.team.pk, + # can CH handle a timestamp with no T + first_timestamp=self.an_hour_ago.isoformat().replace("T", " "), + last_timestamp=(self.an_hour_ago + relativedelta(seconds=61)).isoformat().replace("T", " "), + distinct_id=user, + first_url="https://example.io/home", + click_count=2, + keypress_count=2, + mouse_activity_count=2, + active_milliseconds=59000, + ) + + produce_replay_summary( + session_id=session_id_active_is_61, + team_id=self.team.pk, + # can CH handle a timestamp with no T + first_timestamp=self.an_hour_ago, + last_timestamp=(self.an_hour_ago + relativedelta(seconds=59)), + distinct_id=user, + first_url="https://a-different-url.com", + click_count=2, + keypress_count=2, + mouse_activity_count=2, + active_milliseconds=61000, + ) + + produce_replay_summary( + session_id=session_id_inactive_is_61, + team_id=self.team.pk, + # can CH handle a timestamp with no T + first_timestamp=self.an_hour_ago, + last_timestamp=(self.an_hour_ago + relativedelta(seconds=61)), + distinct_id=user, + first_url="https://a-different-url.com", + click_count=0, + keypress_count=0, + mouse_activity_count=0, + active_milliseconds=0, + ) + + (session_recordings, _, _) = self._filter_recordings_by( + { + "having_predicates": '[{"type":"recording","key":"duration","value":60,"operator":"gt"}]', + } + ) + + assert sorted( + [(s["session_id"], s["duration"], s["active_seconds"]) for s in session_recordings], + key=lambda x: x[0], + ) == [ + (session_id_inactive_is_61, 61, 0.0), + (session_id_total_is_61, 61, 59.0), + ] + + (session_recordings, _, _) = self._filter_recordings_by( + { + "having_predicates": '[{"type":"recording","key":"active_seconds","value":"60","operator":"gt"}]', + } + ) + + assert [(s["session_id"], s["duration"], s["active_seconds"]) for s in session_recordings] == [ + (session_id_active_is_61, 59, 61.0) + ] + + (session_recordings, _, _) = self._filter_recordings_by( + { + "having_predicates": '[{"type":"recording","key":"inactive_seconds","value":"60","operator":"gt"}]', + } + ) + + assert [(s["session_id"], s["duration"], s["inactive_seconds"]) for s in session_recordings] == [ + (session_id_inactive_is_61, 61, 61.0) + ] + + @snapshot_clickhouse_queries + def test_sessions_with_current_data( + self, + ): + user = "test_sessions_with_current_data-user" + Person.objects.create(team=self.team, distinct_ids=[user], properties={"email": "bla"}) + + session_id_inactive = f"test_sessions_with_current_data-inactive-{str(uuid4())}" + session_id_active = f"test_sessions_with_current_data-active-{str(uuid4())}" + + produce_replay_summary( + session_id=session_id_inactive, + team_id=self.team.pk, + first_timestamp=self.an_hour_ago, + last_timestamp=self.an_hour_ago + relativedelta(seconds=60), + distinct_id=user, + first_url="https://example.io/home", + click_count=2, + keypress_count=2, + mouse_activity_count=2, + active_milliseconds=59000, + kafka_timestamp=(datetime.utcnow() - relativedelta(minutes=6)), + ) + + produce_replay_summary( + session_id=session_id_active, + team_id=self.team.pk, + first_timestamp=self.an_hour_ago, + last_timestamp=self.an_hour_ago + relativedelta(seconds=60), + distinct_id=user, + first_url="https://a-different-url.com", + click_count=2, + keypress_count=2, + mouse_activity_count=2, + active_milliseconds=61000, + kafka_timestamp=(datetime.utcnow() - relativedelta(minutes=3)), + ) + + (session_recordings, _, _) = self._filter_recordings_by({}) + assert sorted( + [(s["session_id"], s["ongoing"]) for s in session_recordings], + key=lambda x: x[0], + ) == [ + (session_id_active, 1), + (session_id_inactive, 0), + ] + + @snapshot_clickhouse_queries + def test_basic_query_with_paging(self): + user = "test_basic_query_with_paging-user" + Person.objects.create(team=self.team, distinct_ids=[user], properties={"email": "bla"}) + + session_id_one = f"id_one_test_basic_query_with_paging-{str(uuid4())}" + session_id_two = f"id_two_test_basic_query_with_paging-{str(uuid4())}" + + produce_replay_summary( + session_id=session_id_one, + team_id=self.team.pk, + # can CH handle a timestamp with no T + first_timestamp=self.an_hour_ago.isoformat().replace("T", " "), + last_timestamp=(self.an_hour_ago + relativedelta(seconds=20)).isoformat().replace("T", " "), + distinct_id=user, + first_url="https://example.io/home", + click_count=2, + keypress_count=2, + mouse_activity_count=2, + active_milliseconds=50 * 1000 * 0.5, # 50% of the total expected duration + ) + + produce_replay_summary( + session_id=session_id_one, + team_id=self.team.pk, + # can CH handle a timestamp with no T + first_timestamp=(self.an_hour_ago + relativedelta(seconds=10)), + last_timestamp=(self.an_hour_ago + relativedelta(seconds=50)), + distinct_id=user, + first_url="https://a-different-url.com", + click_count=2, + keypress_count=2, + mouse_activity_count=2, + active_milliseconds=0, # 30% of the total expected duration + ) + + produce_replay_summary( + session_id=session_id_two, + team_id=self.team.pk, + # can CH handle a timestamp with no T + first_timestamp=(self.an_hour_ago + relativedelta(seconds=20)), + last_timestamp=(self.an_hour_ago + relativedelta(seconds=2000)), + distinct_id=user, + first_url="https://another-url.com", + click_count=2, + keypress_count=2, + mouse_activity_count=2, + active_milliseconds=1980 * 1000 * 0.4, # 40% of the total expected duration + ) + + (session_recordings, more_recordings_available, _) = self._filter_recordings_by({"limit": 1, "offset": 0}) + + assert session_recordings == [ + { + "activity_score": 40.16, + "session_id": session_id_two, + "team_id": self.team.pk, + "distinct_id": user, + "click_count": 2, + "keypress_count": 2, + "mouse_activity_count": 2, + "duration": 1980, + "active_seconds": 792.0, + "inactive_seconds": 1188.0, + "start_time": self.an_hour_ago + relativedelta(seconds=20), + "end_time": self.an_hour_ago + relativedelta(seconds=2000), + "first_url": "https://another-url.com", + "console_log_count": 0, + "console_warn_count": 0, + "console_error_count": 0, + "ongoing": 1, + } + ] + + assert more_recordings_available is True + + (session_recordings, more_recordings_available, _) = self._filter_recordings_by({"limit": 1, "offset": 1}) + + assert session_recordings == [ + { + "session_id": session_id_one, + "activity_score": 61.11, + "team_id": self.team.pk, + "distinct_id": user, + "click_count": 4, + "keypress_count": 4, + "mouse_activity_count": 4, + "duration": 50, + "active_seconds": 25.0, + "inactive_seconds": 25.0, + "start_time": self.an_hour_ago, + "end_time": self.an_hour_ago + relativedelta(seconds=50), + "first_url": "https://example.io/home", + "console_log_count": 0, + "console_warn_count": 0, + "console_error_count": 0, + "ongoing": 1, + }, + ] + + assert more_recordings_available is False + + (session_recordings, more_recordings_available, _) = self._filter_recordings_by({"limit": 1, "offset": 2}) + + assert session_recordings == [] + + assert more_recordings_available is False + + @snapshot_clickhouse_queries + def test_basic_query_with_ordering(self): + user = "test_basic_query_with_ordering-user" + Person.objects.create(team=self.team, distinct_ids=[user], properties={"email": "bla"}) + + session_id_one = f"test_basic_query_with_ordering-session-1-{str(uuid4())}" + session_id_two = f"test_basic_query_with_ordering-session-2-{str(uuid4())}" + + session_one_start = self.an_hour_ago + relativedelta(seconds=10) + produce_replay_summary( + session_id=session_id_one, + team_id=self.team.pk, + # can CH handle a timestamp with no T + first_timestamp=session_one_start, + last_timestamp=(self.an_hour_ago + relativedelta(seconds=50)), + distinct_id=user, + console_error_count=1000, + active_milliseconds=1, # most errors, but the least activity + ) + + produce_replay_summary( + session_id=session_id_one, + team_id=self.team.pk, + # can CH handle a timestamp with no T + first_timestamp=session_one_start, + last_timestamp=(self.an_hour_ago + relativedelta(seconds=50)), + distinct_id=user, + console_error_count=12, + active_milliseconds=1, # most errors, but the least activity + ) + + session_two_start = self.an_hour_ago + produce_replay_summary( + session_id=session_id_two, + team_id=self.team.pk, + # starts before session one + first_timestamp=session_two_start, + last_timestamp=(self.an_hour_ago + relativedelta(seconds=50)), + distinct_id=user, + console_error_count=430, + active_milliseconds=1000, # most activity, but the least errors + ) + + (session_recordings) = self._filter_recordings_by({"limit": 3, "offset": 0, "order": "active_seconds"}) + + ordered_by_activity = [(r["session_id"], r["active_seconds"]) for r in session_recordings.results] + assert ordered_by_activity == [(session_id_two, 1.0), (session_id_one, 0.002)] + + (session_recordings) = self._filter_recordings_by({"limit": 3, "offset": 0, "order": "console_error_count"}) + + ordered_by_errors = [(r["session_id"], r["console_error_count"]) for r in session_recordings.results] + assert ordered_by_errors == [(session_id_one, 1012), (session_id_two, 430)] + + (session_recordings) = self._filter_recordings_by({"limit": 3, "offset": 0, "order": "start_time"}) + + ordered_by_default = [(r["session_id"], r["start_time"]) for r in session_recordings.results] + assert ordered_by_default == [(session_id_one, session_one_start), (session_id_two, session_two_start)] + + def test_first_url_selection(self): + user = "test_first_url_selection-user" + Person.objects.create(team=self.team, distinct_ids=[user], properties={"email": "bla"}) + + session_id_one = f"first-url-on-first-event-{str(uuid4())}" + session_id_two = f"first-url-not-on-first-event-{str(uuid4())}" + session_id_three = f"no-url-from-many-{str(uuid4())}" + session_id_four = f"events-inserted-out-of-order-{str(uuid4())}" + + # session one has the first url on the first event + produce_replay_summary( + session_id=session_id_one, + team_id=self.team.pk, + # can CH handle a timestamp with no T + first_timestamp=self.an_hour_ago, + last_timestamp=self.an_hour_ago + relativedelta(seconds=20), + first_url="https://on-first-event.com", + ) + + produce_replay_summary( + session_id=session_id_one, + team_id=self.team.pk, + first_timestamp=self.an_hour_ago + relativedelta(seconds=10), + last_timestamp=self.an_hour_ago + relativedelta(seconds=20), + first_url="https://on-second-event.com", + ) + + produce_replay_summary( + session_id=session_id_one, + team_id=self.team.pk, + first_timestamp=self.an_hour_ago + relativedelta(seconds=20), + last_timestamp=self.an_hour_ago + relativedelta(seconds=40), + first_url="https://on-third-event.com", + ) + + # session two has no URL on the first event + produce_replay_summary( + session_id=session_id_two, + team_id=self.team.pk, + first_timestamp=(self.an_hour_ago + relativedelta(seconds=10)), + last_timestamp=(self.an_hour_ago + relativedelta(seconds=50)), + first_url=None, + ) + + produce_replay_summary( + session_id=session_id_two, + team_id=self.team.pk, + first_timestamp=(self.an_hour_ago + relativedelta(seconds=20)), + last_timestamp=(self.an_hour_ago + relativedelta(seconds=30)), + first_url="https://first-is-on-second-event.com", + ) + + produce_replay_summary( + session_id=session_id_two, + team_id=self.team.pk, + first_timestamp=(self.an_hour_ago + relativedelta(seconds=25)), + last_timestamp=(self.an_hour_ago + relativedelta(seconds=30)), + first_url="https://another-on-the-session.com", + ) + + # session three has no URLs + produce_replay_summary( + session_id=session_id_three, + team_id=self.team.pk, + first_timestamp=self.an_hour_ago, + last_timestamp=self.an_hour_ago + relativedelta(seconds=50), + distinct_id=user, + first_url=None, + ) + + produce_replay_summary( + session_id=session_id_three, + team_id=self.team.pk, + first_timestamp=(self.an_hour_ago + relativedelta(seconds=10)), + last_timestamp=self.an_hour_ago + relativedelta(seconds=50), + distinct_id=user, + first_url=None, + ) + + produce_replay_summary( + session_id=session_id_three, + team_id=self.team.pk, + first_timestamp=(self.an_hour_ago + relativedelta(seconds=20)), + last_timestamp=self.an_hour_ago + relativedelta(seconds=60), + distinct_id=user, + first_url=None, + ) + + # session four events are received out of order + produce_replay_summary( + session_id=session_id_four, + team_id=self.team.pk, + first_timestamp=self.an_hour_ago + relativedelta(seconds=20), + last_timestamp=self.an_hour_ago + relativedelta(seconds=25), + first_url="https://on-first-received-event.com", + ) + produce_replay_summary( + session_id=session_id_four, + team_id=self.team.pk, + first_timestamp=self.an_hour_ago + relativedelta(seconds=10), + last_timestamp=self.an_hour_ago + relativedelta(seconds=25), + first_url="https://on-second-received-event-but-actually-first.com", + ) + + session_recordings, more_recordings_available, _ = self._filter_recordings_by() + + assert sorted( + [{"session_id": r["session_id"], "first_url": r["first_url"]} for r in session_recordings], + key=lambda x: x["session_id"], + ) == sorted( + [ + { + "session_id": session_id_one, + "first_url": "https://on-first-event.com", + }, + { + "session_id": session_id_two, + "first_url": "https://first-is-on-second-event.com", + }, + { + "session_id": session_id_three, + "first_url": None, + }, + { + "session_id": session_id_four, + "first_url": "https://on-second-received-event-but-actually-first.com", + }, + ], + # mypy unhappy about this lambda when first_url can be None 🤷️ + key=lambda x: x["session_id"], # type: ignore + ) + + def test_recordings_dont_leak_data_between_teams(self): + another_team = Team.objects.create(organization=self.organization) + user = "test_recordings_dont_leak_data_between_teams-user" + Person.objects.create(team=self.team, distinct_ids=[user], properties={"email": "bla"}) + Person.objects.create(team=another_team, distinct_ids=[user], properties={"email": "bla"}) + + session_id_one = f"test_recordings_dont_leak_data_between_teams-1-{str(uuid4())}" + session_id_two = f"test_recordings_dont_leak_data_between_teams-2-{str(uuid4())}" + + produce_replay_summary( + session_id=session_id_one, + team_id=another_team.pk, + distinct_id=user, + first_timestamp=self.an_hour_ago, + last_timestamp=self.an_hour_ago + relativedelta(seconds=20), + click_count=2, + keypress_count=2, + mouse_activity_count=2, + active_milliseconds=20 * 1000 * 0.5, # 50% of the total expected duration + ) + + produce_replay_summary( + session_id=session_id_two, + team_id=self.team.pk, + distinct_id=user, + first_timestamp=self.an_hour_ago, + last_timestamp=self.an_hour_ago + relativedelta(seconds=20), + click_count=2, + keypress_count=2, + mouse_activity_count=2, + active_milliseconds=20 * 1000 * 0.5, # 50% of the total expected duration + ) + + (session_recordings, _, _) = self._filter_recordings_by() + + assert [{"session": r["session_id"], "user": r["distinct_id"]} for r in session_recordings] == [ + {"session": session_id_two, "user": user} + ] + + @snapshot_clickhouse_queries + def test_event_filter(self): + user = "test_event_filter-user" + Person.objects.create(team=self.team, distinct_ids=[user], properties={"email": "bla"}) + session_id_one = f"test_event_filter-{str(uuid4())}" + produce_replay_summary( + distinct_id=user, + session_id=session_id_one, + first_timestamp=self.an_hour_ago, + team_id=self.team.id, + ) + self.create_event( + user, + self.an_hour_ago, + properties={"$session_id": session_id_one, "$window_id": str(uuid4())}, + ) + produce_replay_summary( + distinct_id=user, + session_id=session_id_one, + first_timestamp=self.an_hour_ago, + team_id=self.team.id, + ) + + (session_recordings, _, _) = self._filter_recordings_by( + { + "events": [ + { + "id": "$pageview", + "type": "events", + "order": 0, + "name": "$pageview", + } + ] + } + ) + + assert len(session_recordings) == 1 + assert session_recordings[0]["session_id"] == session_id_one + + (session_recordings, _, _) = self._filter_recordings_by( + { + "events": [ + { + "id": "$autocapture", + "type": "events", + "order": 0, + "name": "$autocapture", + } + ] + } + ) + assert session_recordings == [] + + @snapshot_clickhouse_queries + def test_event_filter_has_ttl_applied_too(self): + user = "test_event_filter_has_ttl_applied_too-user" + Person.objects.create(team=self.team, distinct_ids=[user], properties={"email": "bla"}) + session_id_one = f"test_event_filter_has_ttl_applied_too-{str(uuid4())}" + + # this is artificially incorrect data, the session events are within TTL + produce_replay_summary( + distinct_id=user, + session_id=session_id_one, + first_timestamp=self.an_hour_ago, + team_id=self.team.id, + ) + # but the page view event is outside TTL + self.create_event( + user, + self.an_hour_ago - relativedelta(days=SessionRecordingListFromQuery.SESSION_RECORDINGS_DEFAULT_LIMIT + 1), + properties={"$session_id": session_id_one, "$window_id": str(uuid4())}, + ) + + (session_recordings, _, _) = self._filter_recordings_by( + { + "events": [ + { + "id": "$pageview", + "type": "events", + "order": 0, + "name": "$pageview", + } + ] + } + ) + assert len(session_recordings) == 0 + + (session_recordings, _, _) = self._filter_recordings_by({}) + # without an event filter the recording is present, showing that the TTL was applied to the events table too + # we want this to limit the amount of event data we query + assert len(session_recordings) == 1 + assert session_recordings[0]["session_id"] == session_id_one + + @snapshot_clickhouse_queries + def test_ttl_days(self): + assert ttl_days(self.team) == 21 + + with self.is_cloud(True): + # Far enough in the future from `days_since_blob_ingestion` but not paid + with freeze_time("2023-09-01T12:00:01Z"): + assert ttl_days(self.team) == 30 + + self.team.organization.available_product_features = [ + {"key": AvailableFeature.RECORDINGS_PLAYLISTS, "name": AvailableFeature.RECORDINGS_PLAYLISTS} + ] + + # Far enough in the future from `days_since_blob_ingestion` but paid + with freeze_time("2023-12-01T12:00:01Z"): + assert ttl_days(self.team) == 90 + + # Not far enough in the future from `days_since_blob_ingestion` + with freeze_time("2023-09-05T12:00:01Z"): + assert ttl_days(self.team) == 35 + + @snapshot_clickhouse_queries + def test_listing_ignores_future_replays(self): + with freeze_time("2023-08-29T12:00:01Z"): + produce_replay_summary(team_id=self.team.id, session_id="29th Aug") + + with freeze_time("2023-09-01T12:00:01Z"): + produce_replay_summary(team_id=self.team.id, session_id="1st-sep") + + with freeze_time("2023-09-02T12:00:01Z"): + produce_replay_summary(team_id=self.team.id, session_id="2nd-sep") + + with freeze_time("2023-09-03T12:00:01Z"): + produce_replay_summary(team_id=self.team.id, session_id="3rd-sep") + + with freeze_time("2023-08-30T12:00:01Z"): + recordings = self._filter_recordings_by() + + # recordings in the future don't show + assert [s["session_id"] for s in recordings.results] == ["29th Aug"] + + @snapshot_clickhouse_queries + def test_filter_on_session_ids(self): + user = "test_session_ids-user" + Person.objects.create(team=self.team, distinct_ids=[user], properties={"email": "bla"}) + + first_session_id = str(uuid4()) + second_session_id = str(uuid4()) + third_session_id = str(uuid4()) + + produce_replay_summary( + session_id=first_session_id, + team_id=self.team.pk, + first_timestamp=self.an_hour_ago, + last_timestamp=(self.an_hour_ago + relativedelta(minutes=5)), + distinct_id=user, + first_url="https://example.io/home", + click_count=2, + keypress_count=2, + mouse_activity_count=2, + active_milliseconds=59000, + ) + + produce_replay_summary( + session_id=second_session_id, + team_id=self.team.pk, + first_timestamp=self.an_hour_ago, + last_timestamp=(self.an_hour_ago + relativedelta(minutes=1)), + distinct_id=user, + first_url="https://example.io/home", + click_count=2, + keypress_count=2, + mouse_activity_count=2, + active_milliseconds=61000, + ) + + produce_replay_summary( + session_id=third_session_id, + team_id=self.team.pk, + first_timestamp=self.an_hour_ago, + last_timestamp=(self.an_hour_ago + relativedelta(minutes=10)), + distinct_id=user, + first_url="https://example.io/home", + click_count=0, + keypress_count=0, + mouse_activity_count=0, + active_milliseconds=0, + ) + + (session_recordings, _, _) = self._filter_recordings_by( + { + "session_ids": [first_session_id], + } + ) + + assert len(session_recordings) == 1 + assert session_recordings[0]["session_id"] == first_session_id + + (session_recordings, _, _) = self._filter_recordings_by( + { + "session_ids": [first_session_id, second_session_id], + } + ) + + assert sorted([s["session_id"] for s in session_recordings]) == sorted( + [ + first_session_id, + second_session_id, + ] + ) + + @snapshot_clickhouse_queries + def test_event_filter_with_active_sessions( + self, + ): + user = "test_basic_query-user" + Person.objects.create(team=self.team, distinct_ids=[user], properties={"email": "bla"}) + + session_id_total_is_61 = f"test_basic_query_active_sessions-total-{str(uuid4())}" + session_id_active_is_61 = f"test_basic_query_active_sessions-active-{str(uuid4())}" + + self.create_event( + user, + self.an_hour_ago, + properties={ + "$session_id": session_id_total_is_61, + "$window_id": str(uuid4()), + }, + ) + produce_replay_summary( + session_id=session_id_total_is_61, + team_id=self.team.pk, + # can CH handle a timestamp with no T + first_timestamp=self.an_hour_ago.isoformat().replace("T", " "), + last_timestamp=(self.an_hour_ago + relativedelta(seconds=61)).isoformat().replace("T", " "), + distinct_id=user, + first_url="https://example.io/home", + click_count=2, + keypress_count=2, + mouse_activity_count=2, + active_milliseconds=59000, + ) + + self.create_event( + user, + self.an_hour_ago, + properties={ + "$session_id": session_id_active_is_61, + "$window_id": str(uuid4()), + }, + ) + produce_replay_summary( + session_id=session_id_active_is_61, + team_id=self.team.pk, + # can CH handle a timestamp with no T + first_timestamp=self.an_hour_ago, + last_timestamp=(self.an_hour_ago + relativedelta(seconds=59)), + distinct_id=user, + first_url="https://a-different-url.com", + click_count=2, + keypress_count=2, + mouse_activity_count=2, + active_milliseconds=61000, + ) + + (session_recordings, _, _) = self._filter_recordings_by( + { + "events": [ + { + "id": "$pageview", + "type": "events", + "order": 0, + "name": "$pageview", + } + ], + "having_predicates": '[{"type":"recording","key":"duration","value":60,"operator":"gt"}]', + } + ) + + assert [(s["session_id"], s["duration"], s["active_seconds"]) for s in session_recordings] == [ + (session_id_total_is_61, 61, 59.0) + ] + + (session_recordings, _, _) = self._filter_recordings_by( + { + "events": [ + { + "id": "$pageview", + "type": "events", + "order": 0, + "name": "$pageview", + } + ], + "having_predicates": '[{"type":"recording","key":"active_seconds","value":60,"operator":"gt"}]', + } + ) + + assert [(s["session_id"], s["duration"], s["active_seconds"]) for s in session_recordings] == [ + (session_id_active_is_61, 59, 61.0) + ] + + @also_test_with_materialized_columns(["$current_url", "$browser"]) + @snapshot_clickhouse_queries + def test_event_filter_with_properties(self): + user = "test_event_filter_with_properties-user" + Person.objects.create(team=self.team, distinct_ids=[user], properties={"email": "bla"}) + session_id_one = f"test_event_filter_with_properties-{str(uuid4())}" + produce_replay_summary( + distinct_id=user, + session_id=session_id_one, + first_timestamp=self.an_hour_ago, + team_id=self.team.id, + ) + self.create_event( + user, + self.an_hour_ago, + properties={ + "$browser": "Chrome", + "$session_id": session_id_one, + "$window_id": str(uuid4()), + }, + ) + self.create_event( + user, + self.an_hour_ago, + event_name="a_different_event", + properties={ + "$browser": "Safari", + "$session_id": session_id_one, + "$window_id": str(uuid4()), + }, + ) + produce_replay_summary( + distinct_id=user, + session_id=session_id_one, + first_timestamp=(self.an_hour_ago + relativedelta(seconds=30)), + team_id=self.team.id, + ) + + (session_recordings, _, _) = self._filter_recordings_by( + { + "events": [ + { + "id": "$pageview", + "type": "events", + "order": 0, + "name": "$pageview", + "properties": [ + { + "key": "$browser", + "value": ["Chrome"], + "operator": "exact", + "type": "event", + } + ], + } + ] + } + ) + assert len(session_recordings) == 1 + assert session_recordings[0]["session_id"] == session_id_one + + (session_recordings, _, _) = self._filter_recordings_by( + { + "events": [ + { + "id": "$pageview", + "type": "events", + "order": 0, + "name": "$pageview", + "properties": [ + { + "key": "$browser", + "value": ["Firefox"], + "operator": "exact", + "type": "event", + } + ], + } + ] + } + ) + assert session_recordings == [] + + (session_recordings, _, _) = self._filter_recordings_by( + { + "events": [ + { + "id": "a_different_event", + "type": "events", + "order": 0, + "name": "a_different_event", + "properties": [ + { + "key": "$browser", + "value": ["Chrome"], + "operator": "exact", + "type": "event", + } + ], + } + ] + } + ) + assert len(session_recordings) == 0 + + (session_recordings, _, _) = self._filter_recordings_by( + { + "events": [ + { + "id": "a_different_event", + "type": "events", + "order": 0, + "name": "a_different_event", + "properties": [ + { + "key": "$browser", + "value": ["Safari"], + "operator": "exact", + "type": "event", + } + ], + } + ] + } + ) + assert len(session_recordings) == 1 + assert session_recordings[0]["session_id"] == session_id_one + + @snapshot_clickhouse_queries + def test_multiple_event_filters(self): + session_id = f"test_multiple_event_filters-{str(uuid4())}" + user = "test_multiple_event_filters-user" + Person.objects.create(team=self.team, distinct_ids=[user], properties={"email": "bla"}) + produce_replay_summary( + distinct_id=user, + session_id=session_id, + first_timestamp=self.an_hour_ago, + team_id=self.team.id, + ) + + self.create_event( + user, + self.an_hour_ago, + properties={"$session_id": session_id, "$window_id": "1", "foo": "bar"}, + ) + self.create_event( + user, + self.an_hour_ago, + properties={"$session_id": session_id, "$window_id": "1", "bar": "foo"}, + ) + self.create_event( + user, + self.an_hour_ago, + properties={"$session_id": session_id, "$window_id": "1", "bar": "foo"}, + event_name="new-event", + ) + produce_replay_summary( + distinct_id=user, + session_id=session_id, + first_timestamp=(self.an_hour_ago + relativedelta(seconds=30)), + team_id=self.team.id, + ) + + (session_recordings, _, _) = self._filter_recordings_by( + { + "events": [ + { + "id": "$pageview", + "type": "events", + "order": 0, + "name": "$pageview", + }, + { + "id": "new-event", + "type": "events", + "order": 0, + "name": "new-event", + }, + ] + } + ) + + assert len(session_recordings) == 1 + assert session_recordings[0]["session_id"] == session_id + + (session_recordings, _, _) = self._filter_recordings_by( + { + "events": [ + { + "id": "$pageview", + "type": "events", + "order": 0, + "name": "$pageview", + }, + { + "id": "new-event2", + "type": "events", + "order": 0, + "name": "new-event2", + }, + ] + } + ) + assert session_recordings == [] + + # it uses hasAny instead of hasAll because of the OR filter + (session_recordings, _, _) = self._filter_recordings_by( + { + "events": [ + { + "id": "$pageview", + "type": "events", + "order": 0, + "name": "$pageview", + }, + { + "id": "new-event2", + "type": "events", + "order": 0, + "name": "new-event2", + }, + ], + "operand": "OR", + } + ) + assert len(session_recordings) == 1 + + # two events with the same name + (session_recordings, _, _) = self._filter_recordings_by( + { + "events": [ + { + "id": "$pageview", + "type": "events", + "name": "$pageview", + "properties": [{"key": "foo", "value": ["bar"], "operator": "exact", "type": "event"}], + }, + { + "id": "$pageview", + "type": "events", + "name": "$pageview", + "properties": [{"key": "bar", "value": ["foo"], "operator": "exact", "type": "event"}], + }, + ], + "operand": "AND", + } + ) + assert len(session_recordings) == 1 + + # two events with different names + (session_recordings, _, _) = self._filter_recordings_by( + { + "events": [ + { + "id": "$pageview", + "type": "events", + "name": "$pageview", + "properties": [{"key": "foo", "value": ["bar"], "operator": "exact", "type": "event"}], + }, + { + "id": "new-event", + "type": "events", + "name": "new-event", + "properties": [{"key": "foo", "value": ["bar"], "operator": "exact", "type": "event"}], + }, + ], + "operand": "AND", + } + ) + assert len(session_recordings) == 0 + + # two events with different names + (session_recordings, _, _) = self._filter_recordings_by( + { + "events": [ + { + "id": "$pageview", + "type": "events", + "name": "$pageview", + "properties": [{"key": "foo", "value": ["bar"], "operator": "exact", "type": "event"}], + }, + { + "id": "new-event", + "type": "events", + "name": "new-event", + "properties": [{"key": "foo", "value": ["bar"], "operator": "exact", "type": "event"}], + }, + ], + "operand": "OR", + } + ) + assert len(session_recordings) == 1 + + @snapshot_clickhouse_queries + @also_test_with_materialized_columns(["$session_id", "$browser"], person_properties=["email"]) + @freeze_time("2023-01-04") + def test_action_filter(self): + user = "test_action_filter-user" + Person.objects.create(team=self.team, distinct_ids=[user], properties={"email": "bla"}) + session_id_one = f"test_action_filter-session-one" + window_id = "test_action_filter-window-id" + action_with_properties = self.create_action( + "custom-event", + properties=[ + {"key": "$browser", "value": "Firefox"}, + {"key": "$session_id", "value": session_id_one}, + {"key": "$window_id", "value": window_id}, + ], + ) + action_without_properties = self.create_action( + name="custom-event", + properties=[ + {"key": "$session_id", "value": session_id_one}, + {"key": "$window_id", "value": window_id}, + ], + ) + + produce_replay_summary( + distinct_id=user, + session_id=session_id_one, + first_timestamp=self.an_hour_ago, + team_id=self.team.id, + ) + self.create_event( + user, + self.an_hour_ago, + event_name="custom-event", + properties={ + "$browser": "Chrome", + "$session_id": session_id_one, + "$window_id": window_id, + }, + ) + produce_replay_summary( + distinct_id=user, + session_id=session_id_one, + first_timestamp=(self.an_hour_ago + relativedelta(seconds=30)), + team_id=self.team.id, + ) + + (session_recordings, _, _) = self._filter_recordings_by( + { + "actions": [ + { + "id": action_with_properties.id, + "type": "actions", + "order": 1, + "name": "custom-event", + } + ] + } + ) + assert session_recordings == [] + + (session_recordings, _, _) = self._filter_recordings_by( + { + "actions": [ + { + "id": action_without_properties.id, + "type": "actions", + "order": 1, + "name": "custom-event", + } + ] + } + ) + + assert len(session_recordings) == 1 + assert session_recordings[0]["session_id"] == session_id_one + + # Adding properties to an action + (session_recordings, _, _) = self._filter_recordings_by( + { + "actions": [ + { + "id": action_without_properties.id, + "type": "actions", + "order": 1, + "name": "custom-event", + "properties": [ + { + "key": "$browser", + "value": ["Firefox"], + "operator": "exact", + "type": "event", + } + ], + } + ] + } + ) + assert session_recordings == [] + + # Adding matching properties to an action + (session_recordings, _, _) = self._filter_recordings_by( + { + "actions": [ + { + "id": action_without_properties.id, + "type": "actions", + "order": 1, + "name": "custom-event", + "properties": [ + { + "key": "$browser", + "value": ["Chrome"], + "operator": "exact", + "type": "event", + } + ], + } + ] + } + ) + + assert len(session_recordings) == 1 + assert session_recordings[0]["session_id"] == session_id_one + + def test_all_sessions_recording_object_keys_with_entity_filter(self): + user = "test_all_sessions_recording_object_keys_with_entity_filter-user" + Person.objects.create(team=self.team, distinct_ids=[user], properties={"email": "bla"}) + session_id = f"test_all_sessions_recording_object_keys_with_entity_filter-{str(uuid4())}" + window_id = str(uuid4()) + + produce_replay_summary( + distinct_id=user, + session_id=session_id, + first_timestamp=self.an_hour_ago, + last_timestamp=(self.an_hour_ago + relativedelta(seconds=60)), + team_id=self.team.id, + first_url="https://recieved-out-of-order.com/second", + ) + self.create_event( + user, + self.an_hour_ago, + properties={"$session_id": session_id, "$window_id": window_id}, + ) + produce_replay_summary( + distinct_id=user, + session_id=session_id, + first_timestamp=self.an_hour_ago, + last_timestamp=(self.an_hour_ago + relativedelta(seconds=30)), + team_id=self.team.id, + first_url="https://recieved-out-of-order.com/first", + ) + + (session_recordings, _, _) = self._filter_recordings_by( + { + "events": [ + { + "id": "$pageview", + "type": "events", + "order": 0, + "name": "$pageview", + } + ] + } + ) + + assert session_recordings == [ + { + "activity_score": 0, + "session_id": session_id, + "distinct_id": user, + "duration": 60, + "start_time": self.an_hour_ago, + "end_time": self.an_hour_ago + relativedelta(seconds=60), + "active_seconds": 0.0, + "click_count": 0, + "first_url": "https://recieved-out-of-order.com/first", + "inactive_seconds": 60.0, + "keypress_count": 0, + "mouse_activity_count": 0, + "team_id": self.team.id, + "console_log_count": 0, + "console_warn_count": 0, + "console_error_count": 0, + "ongoing": 1, + } + ] + + @snapshot_clickhouse_queries + def test_duration_filter(self): + user = "test_duration_filter-user" + Person.objects.create(team=self.team, distinct_ids=[user], properties={"email": "bla"}) + + session_id_one = "session one is 29 seconds long" + produce_replay_summary( + distinct_id=user, + session_id=session_id_one, + first_timestamp=self.an_hour_ago, + last_timestamp=(self.an_hour_ago + relativedelta(seconds=29)), + team_id=self.team.id, + ) + + session_id_two = "session two is 61 seconds long" + produce_replay_summary( + distinct_id=user, + session_id=session_id_two, + first_timestamp=self.an_hour_ago, + last_timestamp=(self.an_hour_ago + relativedelta(seconds=61)), + team_id=self.team.id, + ) + + (session_recordings, _, _) = self._filter_recordings_by( + {"having_predicates": '[{"type":"recording","key":"duration","value":60,"operator":"gt"}]'} + ) + assert [r["session_id"] for r in session_recordings] == [session_id_two] + + (session_recordings, _, _) = self._filter_recordings_by( + {"having_predicates": '[{"type":"recording","key":"duration","value":60,"operator":"lt"}]'} + ) + assert [r["session_id"] for r in session_recordings] == [session_id_one] + + @snapshot_clickhouse_queries + def test_operand_or_person_filters(self): + user = "test_operand_or_filter-user" + Person.objects.create(team=self.team, distinct_ids=[user], properties={"email": "test@posthog.com"}) + + second_user = "test_operand_or_filter-second_user" + Person.objects.create(team=self.team, distinct_ids=[second_user], properties={"email": "david@posthog.com"}) + + session_id_one = "session_id_one" + produce_replay_summary( + distinct_id=user, + session_id=session_id_one, + first_timestamp=self.an_hour_ago, + last_timestamp=(self.an_hour_ago + relativedelta(seconds=30)), + team_id=self.team.id, + ) + + session_id_two = "session_id_two" + produce_replay_summary( + distinct_id=second_user, + session_id=session_id_two, + first_timestamp=self.an_hour_ago, + last_timestamp=(self.an_hour_ago + relativedelta(seconds=30)), + team_id=self.team.id, + ) + + (session_recordings, _, _) = self._filter_recordings_by( + { + "properties": [ + { + "key": "email", + "value": ["test@posthog.com"], + "operator": "exact", + "type": "person", + }, + { + "key": "email", + "value": ["david@posthog.com"], + "operator": "exact", + "type": "person", + }, + ], + "operand": "AND", + } + ) + assert len(session_recordings) == 0 + + (session_recordings, _, _) = self._filter_recordings_by( + { + "properties": [ + { + "key": "email", + "value": ["test@posthog.com"], + "operator": "exact", + "type": "person", + }, + { + "key": "email", + "value": ["david@posthog.com"], + "operator": "exact", + "type": "person", + }, + ], + "operand": "OR", + } + ) + assert len(session_recordings) == 2 + assert sorted([r["session_id"] for r in session_recordings]) == sorted([session_id_one, session_id_two]) + + @snapshot_clickhouse_queries + def test_operand_or_event_filters(self): + user = "test_operand_or_filter-user" + Person.objects.create(team=self.team, distinct_ids=[user], properties={"email": "test@posthog.com"}) + + second_user = "test_operand_or_filter-second_user" + Person.objects.create(team=self.team, distinct_ids=[second_user], properties={"email": "david@posthog.com"}) + + session_id_one = "session_id_one" + produce_replay_summary( + distinct_id=user, + session_id=session_id_one, + first_timestamp=self.an_hour_ago, + last_timestamp=(self.an_hour_ago + relativedelta(seconds=30)), + team_id=self.team.id, + ) + self.create_event( + user, + self.an_hour_ago + relativedelta(seconds=10), + properties={"$session_id": session_id_one}, + ) + + session_id_two = "session_id_two" + produce_replay_summary( + distinct_id=second_user, + session_id=session_id_two, + first_timestamp=self.an_hour_ago, + last_timestamp=(self.an_hour_ago + relativedelta(seconds=30)), + team_id=self.team.id, + ) + self.create_event( + user, + self.an_hour_ago + relativedelta(seconds=10), + event_name="custom_event", + properties={"$session_id": session_id_two}, + ) + + session_id_three = "session_id_three" + produce_replay_summary( + distinct_id=second_user, + session_id=session_id_three, + first_timestamp=self.an_hour_ago, + last_timestamp=(self.an_hour_ago + relativedelta(seconds=30)), + team_id=self.team.id, + ) + + (session_recordings, _, _) = self._filter_recordings_by( + { + "events": [ + { + "id": "$pageview", + "type": "events", + "order": 0, + "name": "$pageview", + }, + { + "id": "custom_event", + "type": "events", + "order": 0, + "name": "custom_event", + }, + ], + "operand": "AND", + } + ) + assert len(session_recordings) == 0 + + (session_recordings, _, _) = self._filter_recordings_by( + { + "events": [ + { + "id": "$pageview", + "type": "events", + "order": 0, + "name": "$pageview", + }, + { + "id": "custom_event", + "type": "events", + "order": 0, + "name": "custom_event", + }, + ], + "operand": "OR", + } + ) + assert len(session_recordings) == 2 + assert sorted([r["session_id"] for r in session_recordings]) == sorted([session_id_two, session_id_one]) + + @parameterized.expand( + [ + # Case 1: Neither has WARN and message "random" + ( + '[{"key": "level", "value": ["warn"], "operator": "exact", "type": "log_entry"}, {"key": "message", "value": "random", "operator": "exact", "type": "log_entry"}]', + "AND", + 0, + [], + ), + # Case 2: AND only matches one recording + ( + '[{"key": "level", "value": ["info"], "operator": "exact", "type": "log_entry"}, {"key": "message", "value": "random", "operator": "exact", "type": "log_entry"}]', + "AND", + 1, + ["both_log_filters"], + ), + # Case 3: Only one is WARN level + ( + '[{"key": "level", "value": ["warn"], "operator": "exact", "type": "log_entry"}]', + "AND", + 1, + ["one_log_filter"], + ), + # Case 4: Only one has message "random" + ( + '[{"key": "message", "value": "random", "operator": "exact", "type": "log_entry"}]', + "AND", + 1, + ["both_log_filters"], + ), + # Case 5: OR matches both + ( + '[{"key": "level", "value": ["warn"], "operator": "exact", "type": "log_entry"}, {"key": "message", "value": "random", "operator": "exact", "type": "log_entry"}]', + "OR", + 2, + ["both_log_filters", "one_log_filter"], + ), + ] + ) + @snapshot_clickhouse_queries + def test_operand_or_filters( + self, + console_log_filters: str, + operand: Literal["AND", "OR"], + expected_count: int, + expected_session_ids: list[str], + ) -> None: + user = "test_operand_or_filter-user" + Person.objects.create(team=self.team, distinct_ids=[user], properties={"email": "bla"}) + + session_with_both_log_filters = "both_log_filters" + produce_replay_summary( + distinct_id="user", + session_id=session_with_both_log_filters, + first_timestamp=self.an_hour_ago, + team_id=self.team.id, + console_log_count=1, + log_messages={"info": ["random"]}, + ) + + session_with_one_log_filter = "one_log_filter" + produce_replay_summary( + distinct_id="user", + session_id=session_with_one_log_filter, + first_timestamp=self.an_hour_ago, + team_id=self.team.id, + console_warn_count=1, + log_messages={"warn": ["warn"]}, + ) + + session_recordings, _, _ = self._filter_recordings_by( + {"console_log_filters": console_log_filters, "operand": operand} + ) + + assert len(session_recordings) == expected_count + assert sorted([rec["session_id"] for rec in session_recordings]) == sorted(expected_session_ids) + + @snapshot_clickhouse_queries + def test_operand_or_mandatory_filters(self): + user = "test_operand_or_filter-user" + person = Person.objects.create(team=self.team, distinct_ids=[user], properties={"email": "bla"}) + + second_user = "test_operand_or_filter-second_user" + second_person = Person.objects.create(team=self.team, distinct_ids=[second_user], properties={"email": "bla"}) + + session_id_one = "session_id_one" + produce_replay_summary( + distinct_id=user, + session_id=session_id_one, + first_timestamp=self.an_hour_ago, + last_timestamp=(self.an_hour_ago + relativedelta(seconds=30)), + team_id=self.team.id, + ) + self.create_event( + user, + self.an_hour_ago + relativedelta(seconds=10), + properties={"$session_id": session_id_one}, + ) + + session_id_two = "session_id_two" + produce_replay_summary( + distinct_id=second_user, + session_id=session_id_two, + first_timestamp=self.an_hour_ago, + last_timestamp=(self.an_hour_ago + relativedelta(seconds=30)), + team_id=self.team.id, + ) + + # person or event filter -> person matches, event matches -> returns session + (session_recordings, _, _) = self._filter_recordings_by( + { + "person_uuid": str(person.uuid), + "events": [ + { + "id": "$pageview", + "type": "events", + "order": 0, + "name": "$pageview", + } + ], + "operand": "OR", + } + ) + assert len(session_recordings) == 1 + assert session_recordings[0]["session_id"] == session_id_one + + # person or event filter -> person does not match, event matches -> does not return session + (session_recordings, _, _) = self._filter_recordings_by( + { + "person_uuid": str(second_person.uuid), + "events": [ + { + "id": "$pageview", + "type": "events", + "order": 0, + "name": "$pageview", + } + ], + "operand": "OR", + } + ) + assert len(session_recordings) == 0 + + # session_id or event filter -> person matches, event matches -> returns session + (session_recordings, _, _) = self._filter_recordings_by( + { + "session_ids": [session_id_one], + "events": [ + { + "id": "$pageview", + "type": "events", + "order": 0, + "name": "$pageview", + } + ], + "operand": "OR", + } + ) + assert len(session_recordings) == 1 + assert session_recordings[0]["session_id"] == session_id_one + + # session_id or event filter -> person does not match, event matches -> does not return session + (session_recordings, _, _) = self._filter_recordings_by( + { + "session_ids": [session_id_two], + "events": [ + { + "id": "$pageview", + "type": "events", + "order": 0, + "name": "$pageview", + } + ], + "operand": "OR", + } + ) + assert len(session_recordings) == 0 + + @snapshot_clickhouse_queries + def test_date_from_filter(self): + user = "test_date_from_filter-user" + Person.objects.create(team=self.team, distinct_ids=[user], properties={"email": "bla"}) + + produce_replay_summary( + distinct_id=user, + session_id="three days before base time", + first_timestamp=(self.an_hour_ago - relativedelta(days=3, seconds=100)), + last_timestamp=(self.an_hour_ago - relativedelta(days=3)), + team_id=self.team.id, + ) + produce_replay_summary( + distinct_id=user, + session_id="two days before base time", + first_timestamp=(self.an_hour_ago - relativedelta(days=2, seconds=100)), + last_timestamp=(self.an_hour_ago - relativedelta(days=2)), + team_id=self.team.id, + ) + + (session_recordings, _, _) = self._filter_recordings_by({"date_from": self.an_hour_ago.strftime("%Y-%m-%d")}) + assert session_recordings == [] + + (session_recordings, _, _) = self._filter_recordings_by( + {"date_from": (self.an_hour_ago - relativedelta(days=2)).strftime("%Y-%m-%d")} + ) + assert len(session_recordings) == 1 + assert session_recordings[0]["session_id"] == "two days before base time" + + @snapshot_clickhouse_queries + def test_date_from_filter_cannot_search_before_ttl(self): + with freeze_time(self.an_hour_ago): + user = "test_date_from_filter_cannot_search_before_ttl-user" + Person.objects.create(team=self.team, distinct_ids=[user], properties={"email": "bla"}) + + produce_replay_summary( + distinct_id=user, + session_id="storage is past ttl", + first_timestamp=(self.an_hour_ago - relativedelta(days=22)), + # an illegally long session but it started 22 days ago + last_timestamp=(self.an_hour_ago - relativedelta(days=3)), + team_id=self.team.id, + ) + produce_replay_summary( + distinct_id=user, + session_id="storage is not past ttl", + first_timestamp=(self.an_hour_ago - relativedelta(days=19)), + last_timestamp=(self.an_hour_ago - relativedelta(days=2)), + team_id=self.team.id, + ) + + (session_recordings, _, _) = self._filter_recordings_by( + {"date_from": (self.an_hour_ago - relativedelta(days=20)).strftime("%Y-%m-%d")} + ) + assert len(session_recordings) == 1 + assert session_recordings[0]["session_id"] == "storage is not past ttl" + + (session_recordings, _, _) = self._filter_recordings_by( + {"date_from": (self.an_hour_ago - relativedelta(days=21)).strftime("%Y-%m-%d")} + ) + assert len(session_recordings) == 1 + assert session_recordings[0]["session_id"] == "storage is not past ttl" + + (session_recordings, _, _) = self._filter_recordings_by( + {"date_from": (self.an_hour_ago - relativedelta(days=22)).strftime("%Y-%m-%d")} + ) + assert len(session_recordings) == 1 + assert session_recordings[0]["session_id"] == "storage is not past ttl" + + @snapshot_clickhouse_queries + def test_date_to_filter(self): + user = "test_date_to_filter-user" + Person.objects.create(team=self.team, distinct_ids=[user], properties={"email": "bla"}) + produce_replay_summary( + distinct_id=user, + session_id="three days before base time", + first_timestamp=(self.an_hour_ago - relativedelta(days=3, seconds=100)), + last_timestamp=(self.an_hour_ago - relativedelta(days=3)), + team_id=self.team.id, + ) + produce_replay_summary( + distinct_id=user, + session_id="two days before base time", + first_timestamp=(self.an_hour_ago - relativedelta(days=2, seconds=100)), + last_timestamp=(self.an_hour_ago - relativedelta(days=2)), + team_id=self.team.id, + ) + + (session_recordings, _, _) = self._filter_recordings_by( + {"date_to": (self.an_hour_ago - relativedelta(days=4)).strftime("%Y-%m-%d")} + ) + assert session_recordings == [] + + (session_recordings, _, _) = self._filter_recordings_by( + {"date_to": (self.an_hour_ago - relativedelta(days=3)).strftime("%Y-%m-%d")} + ) + + assert len(session_recordings) == 1 + assert [s["session_id"] for s in session_recordings] == ["three days before base time"] + + def test_recording_that_spans_time_bounds(self): + user = "test_recording_that_spans_time_bounds-user" + Person.objects.create(team=self.team, distinct_ids=[user], properties={"email": "bla"}) + day_line = datetime(2021, 11, 5) + session_id = f"session-one-{user}" + produce_replay_summary( + distinct_id=user, + session_id=session_id, + first_timestamp=(day_line - relativedelta(hours=3)), + last_timestamp=(day_line + relativedelta(hours=3)), + team_id=self.team.id, + ) + + (session_recordings, _, _) = self._filter_recordings_by( + { + "date_to": day_line.strftime("%Y-%m-%d"), + "date_from": (day_line - relativedelta(days=10)).strftime("%Y-%m-%d"), + } + ) + + assert len(session_recordings) == 1 + assert session_recordings[0]["session_id"] == session_id + assert session_recordings[0]["duration"] == 6 * 60 * 60 + + @snapshot_clickhouse_queries + def test_person_id_filter(self): + three_user_ids = [str(uuid4()) for _ in range(3)] + session_id_one = f"test_person_id_filter-{str(uuid4())}" + session_id_two = f"test_person_id_filter-{str(uuid4())}" + p = Person.objects.create( + team=self.team, + distinct_ids=[three_user_ids[0], three_user_ids[1]], + properties={"email": "bla"}, + ) + produce_replay_summary( + distinct_id=three_user_ids[0], + session_id=session_id_one, + team_id=self.team.id, + ) + produce_replay_summary( + distinct_id=three_user_ids[1], + session_id=session_id_two, + team_id=self.team.id, + ) + produce_replay_summary( + distinct_id=three_user_ids[2], + session_id=str(uuid4()), + team_id=self.team.id, + ) + + (session_recordings, _, _) = self._filter_recordings_by({"person_uuid": str(p.uuid)}) + assert sorted([r["session_id"] for r in session_recordings]) == sorted([session_id_two, session_id_one]) + + @snapshot_clickhouse_queries + def test_all_filters_at_once(self): + three_user_ids = [str(uuid4()) for _ in range(3)] + target_session_id = f"test_all_filters_at_once-{str(uuid4())}" + + p = Person.objects.create( + team=self.team, + distinct_ids=[three_user_ids[0], three_user_ids[1]], + properties={"email": "bla"}, + ) + custom_event_action = self.create_action(name="custom-event") + + produce_replay_summary( + distinct_id=three_user_ids[0], + session_id=target_session_id, + first_timestamp=(self.an_hour_ago - relativedelta(days=3)), + team_id=self.team.id, + ) + produce_replay_summary( + # does not match because of user distinct id + distinct_id=three_user_ids[2], + session_id=target_session_id, + first_timestamp=(self.an_hour_ago - relativedelta(days=3)), + team_id=self.team.id, + ) + self.create_event( + three_user_ids[0], + self.an_hour_ago - relativedelta(days=3), + properties={"$session_id": target_session_id}, + ) + self.create_event( + three_user_ids[0], + self.an_hour_ago - relativedelta(days=3), + event_name="custom-event", + properties={"$browser": "Chrome", "$session_id": target_session_id}, + ) + produce_replay_summary( + distinct_id=three_user_ids[1], + session_id=target_session_id, + first_timestamp=(self.an_hour_ago - relativedelta(days=3) + relativedelta(hours=6)), + team_id=self.team.id, + ) + produce_replay_summary( + distinct_id=three_user_ids[1], + # does not match because of session id + session_id=str(uuid4()), + first_timestamp=(self.an_hour_ago - relativedelta(days=3) + relativedelta(hours=6)), + team_id=self.team.id, + ) + + flush_persons_and_events() + + (session_recordings, _, _) = self._filter_recordings_by( + { + "person_uuid": str(p.uuid), + "date_to": (self.an_hour_ago + relativedelta(days=3)).strftime("%Y-%m-%d"), + "date_from": (self.an_hour_ago - relativedelta(days=10)).strftime("%Y-%m-%d"), + "having_predicates": '[{"type":"recording","key":"duration","value":60,"operator":"gt"}]', + "events": [ + { + "id": "$pageview", + "type": "events", + "order": 0, + "name": "$pageview", + } + ], + "actions": [ + { + "id": custom_event_action.id, + "type": "actions", + "order": 1, + "name": "custom-event", + } + ], + } + ) + + assert len(session_recordings) == 1 + assert session_recordings[0]["session_id"] == target_session_id + + def test_teams_dont_leak_event_filter(self): + user = "test_teams_dont_leak_event_filter-user" + Person.objects.create(team=self.team, distinct_ids=[user], properties={"email": "bla"}) + another_team = Team.objects.create(organization=self.organization) + + session_id = f"test_teams_dont_leak_event_filter-{str(uuid4())}" + produce_replay_summary( + distinct_id=user, + session_id=session_id, + first_timestamp=self.an_hour_ago, + team_id=self.team.id, + ) + self.create_event(1, self.an_hour_ago + relativedelta(seconds=15), team=another_team) + produce_replay_summary( + distinct_id=user, + session_id=session_id, + first_timestamp=(self.an_hour_ago + relativedelta(seconds=30)), + team_id=self.team.id, + ) + + (session_recordings, _, _) = self._filter_recordings_by( + { + "events": [ + { + "id": "$pageview", + "type": "events", + "order": 0, + "name": "$pageview", + } + ] + } + ) + assert session_recordings == [] + + @snapshot_clickhouse_queries + @also_test_with_materialized_columns(person_properties=["email"]) + def test_filter_with_person_properties_exact(self): + session_id_one, session_id_two = self._two_sessions_two_persons( + "test_filter_with_person_properties_exact", + session_one_person_properties={"email": "bla@gmail.com"}, + session_two_person_properties={"email": "bla2@hotmail.com"}, + ) + + query_results: SessionRecordingQueryResult = self._filter_recordings_by( + { + "properties": [ + { + "key": "email", + "value": ["bla@gmail.com"], + "operator": "exact", + "type": "person", + } + ] + } + ) + + assert [x["session_id"] for x in query_results.results] == [session_id_one] + + @snapshot_clickhouse_queries + @also_test_with_materialized_columns(person_properties=["email"]) + def test_filter_with_person_properties_not_contains(self): + session_id_one, session_id_two = self._two_sessions_two_persons( + "test_filter_with_person_properties_not_contains", + session_one_person_properties={"email": "bla@gmail.com"}, + session_two_person_properties={"email": "bla2@hotmail.com"}, + ) + + query_results: SessionRecordingQueryResult = self._filter_recordings_by( + {"properties": [{"key": "email", "value": "gmail.com", "operator": "not_icontains", "type": "person"}]} + ) + + assert [x["session_id"] for x in query_results.results] == [session_id_two] + + def _two_sessions_two_persons( + self, label: str, session_one_person_properties: dict, session_two_person_properties: dict + ) -> tuple[str, str]: + sessions = [] + + for i in range(2): + user = f"{label}-user-{i}" + session = f"{label}-session-{i}" + sessions.append(session) + + Person.objects.create( + team=self.team, + distinct_ids=[user], + properties=session_one_person_properties if i == 0 else session_two_person_properties, + ) + + produce_replay_summary( + distinct_id=user, + session_id=session, + first_timestamp=self.an_hour_ago, + team_id=self.team.id, + ) + produce_replay_summary( + distinct_id=user, + session_id=session, + first_timestamp=(self.an_hour_ago + relativedelta(seconds=30)), + team_id=self.team.id, + ) + + return sessions[0], sessions[1] + + @snapshot_clickhouse_queries + @also_test_with_materialized_columns(person_properties=["$some_prop"]) + def test_filter_with_cohort_properties(self): + with self.settings(USE_PRECALCULATED_CH_COHORT_PEOPLE=True): + with freeze_time("2021-08-21T20:00:00.000Z"): + user_one = "test_filter_with_cohort_properties-user" + user_two = "test_filter_with_cohort_properties-user2" + session_id_one = f"test_filter_with_cohort_properties-1-{str(uuid4())}" + session_id_two = f"test_filter_with_cohort_properties-2-{str(uuid4())}" + + Person.objects.create(team=self.team, distinct_ids=[user_one], properties={"email": "bla"}) + Person.objects.create( + team=self.team, + distinct_ids=[user_two], + properties={"email": "bla2", "$some_prop": "some_val"}, + ) + cohort = Cohort.objects.create( + team=self.team, + name="cohort1", + groups=[ + { + "properties": [ + { + "key": "$some_prop", + "value": "some_val", + "type": "person", + } + ] + } + ], + ) + cohort.calculate_people_ch(pending_version=0) + + produce_replay_summary( + distinct_id=user_one, + session_id=session_id_one, + first_timestamp=self.an_hour_ago, + team_id=self.team.id, + ) + # self.create_event(user_one, self.base_time, team=self.team) + produce_replay_summary( + distinct_id=user_one, + session_id=session_id_one, + first_timestamp=self.an_hour_ago + relativedelta(seconds=30), + team_id=self.team.id, + ) + produce_replay_summary( + distinct_id=user_two, + session_id=session_id_two, + first_timestamp=self.an_hour_ago, + team_id=self.team.id, + ) + # self.create_event(user_two, self.base_time, team=self.team) + produce_replay_summary( + distinct_id=user_two, + session_id=session_id_two, + first_timestamp=self.an_hour_ago + relativedelta(seconds=30), + team_id=self.team.id, + ) + + (session_recordings, _, _) = self._filter_recordings_by( + { + "properties": [ + { + "key": "id", + "value": cohort.pk, + "operator": "in", + "type": "cohort", + } + ] + } + ) + + assert [x["session_id"] for x in session_recordings] == [session_id_two] + + @snapshot_clickhouse_queries + @also_test_with_materialized_columns(person_properties=["$some_prop"]) + def test_filter_with_static_and_dynamic_cohort_properties(self): + with self.settings(USE_PRECALCULATED_CH_COHORT_PEOPLE=True): + with freeze_time("2021-08-21T20:00:00.000Z"): + user_one = "test_filter_with_cohort_properties-user-in-static-cohort" + user_two = "test_filter_with_cohort_properties-user2-in-dynamic-cohort" + user_three = "test_filter_with_cohort_properties-user3-in-both-cohort" + + session_id_one = ( + f"in-static-cohort-test_filter_with_static_and_dynamic_cohort_properties-1-{str(uuid4())}" + ) + session_id_two = ( + f"in-dynamic-cohort-test_filter_with_static_and_dynamic_cohort_properties-2-{str(uuid4())}" + ) + session_id_three = ( + f"in-both-cohort-test_filter_with_static_and_dynamic_cohort_properties-3-{str(uuid4())}" + ) + + Person.objects.create(team=self.team, distinct_ids=[user_one], properties={"email": "in@static.cohort"}) + Person.objects.create( + team=self.team, + distinct_ids=[user_two], + properties={"email": "in@dynamic.cohort", "$some_prop": "some_val"}, + ) + Person.objects.create( + team=self.team, + distinct_ids=[user_three], + properties={"email": "in@both.cohorts", "$some_prop": "some_val"}, + ) + + dynamic_cohort = Cohort.objects.create( + team=self.team, + name="cohort1", + groups=[ + { + "properties": [ + { + "key": "$some_prop", + "value": "some_val", + "type": "person", + } + ] + } + ], + ) + + static_cohort = Cohort.objects.create(team=self.team, name="a static cohort", groups=[], is_static=True) + static_cohort.insert_users_by_list([user_one, user_three]) + + dynamic_cohort.calculate_people_ch(pending_version=0) + static_cohort.calculate_people_ch(pending_version=0) + + replay_summaries = [ + (user_one, session_id_one), + (user_two, session_id_two), + (user_three, session_id_three), + ] + for distinct_id, session_id in replay_summaries: + produce_replay_summary( + distinct_id=distinct_id, + session_id=session_id, + first_timestamp=self.an_hour_ago, + team_id=self.team.id, + ) + produce_replay_summary( + distinct_id=distinct_id, + session_id=session_id, + first_timestamp=self.an_hour_ago + relativedelta(seconds=30), + team_id=self.team.id, + ) + + (session_recordings, _, _) = self._filter_recordings_by( + { + "properties": [ + { + "key": "id", + "value": static_cohort.pk, + "operator": "in", + "type": "cohort", + }, + ] + } + ) + + assert sorted([x["session_id"] for x in session_recordings]) == sorted( + [session_id_one, session_id_three] + ) + + (session_recordings, _, _) = self._filter_recordings_by( + { + "properties": [ + { + "key": "id", + "value": dynamic_cohort.pk, + "operator": "in", + "type": "cohort", + }, + ] + } + ) + + assert sorted([x["session_id"] for x in session_recordings]) == sorted( + [session_id_two, session_id_three] + ) + + (session_recordings, _, _) = self._filter_recordings_by( + { + "properties": [ + { + "key": "id", + "value": dynamic_cohort.pk, + "operator": "in", + "type": "cohort", + }, + { + "key": "id", + "value": static_cohort.pk, + "operator": "in", + "type": "cohort", + }, + ] + } + ) + + assert sorted([x["session_id"] for x in session_recordings]) == [session_id_three] + + @snapshot_clickhouse_queries + @also_test_with_materialized_columns(person_properties=["$some_prop"]) + def test_filter_with_events_and_cohorts(self): + with self.settings(USE_PRECALCULATED_CH_COHORT_PEOPLE=True): + with freeze_time("2021-08-21T20:00:00.000Z"): + user_one = "test_filter_with_events_and_cohorts-user" + user_two = "test_filter_with_events_and_cohorts-user2" + session_id_one = f"test_filter_with_events_and_cohorts-1-{str(uuid4())}" + session_id_two = f"test_filter_with_events_and_cohorts-2-{str(uuid4())}" + + Person.objects.create(team=self.team, distinct_ids=[user_one], properties={"email": "bla"}) + Person.objects.create( + team=self.team, + distinct_ids=[user_two], + properties={"email": "bla2", "$some_prop": "some_val"}, + ) + cohort = Cohort.objects.create( + team=self.team, + name="cohort1", + groups=[ + { + "properties": [ + { + "key": "$some_prop", + "value": "some_val", + "type": "person", + } + ] + } + ], + ) + cohort.calculate_people_ch(pending_version=0) + + produce_replay_summary( + distinct_id=user_one, + session_id=session_id_one, + first_timestamp=self.an_hour_ago, + team_id=self.team.id, + ) + self.create_event( + user_one, + self.an_hour_ago, + team=self.team, + event_name="custom_event", + properties={"$session_id": session_id_one}, + ) + produce_replay_summary( + distinct_id=user_one, + session_id=session_id_one, + first_timestamp=self.an_hour_ago + relativedelta(seconds=30), + team_id=self.team.id, + ) + produce_replay_summary( + distinct_id=user_two, + session_id=session_id_two, + first_timestamp=self.an_hour_ago, + team_id=self.team.id, + ) + self.create_event( + user_two, + self.an_hour_ago, + team=self.team, + event_name="custom_event", + properties={"$session_id": session_id_two}, + ) + produce_replay_summary( + distinct_id=user_two, + session_id=session_id_two, + first_timestamp=self.an_hour_ago + relativedelta(seconds=30), + team_id=self.team.id, + ) + + (session_recordings, _, _) = self._filter_recordings_by( + { + # has to be in the cohort and pageview has to be in the events + # test data has one user in the cohort but no pageviews + "properties": [ + { + "key": "id", + "value": cohort.pk, + "operator": "in", + "type": "cohort", + } + ], + "events": [ + { + "id": "$pageview", + "type": "events", + "order": 0, + "name": "$pageview", + } + ], + } + ) + + assert [s["session_id"] for s in session_recordings] == [] + + (session_recordings, _, _) = self._filter_recordings_by( + { + "properties": [ + { + "key": "id", + "value": cohort.pk, + "operator": "in", + "type": "cohort", + } + ], + "events": [ + { + "id": "custom_event", + "type": "events", + "order": 0, + "name": "custom_event", + } + ], + } + ) + + assert [x["session_id"] for x in session_recordings] == [session_id_two] + + @snapshot_clickhouse_queries + @also_test_with_materialized_columns(["$current_url"]) + def test_event_filter_with_matching_on_session_id(self): + user_distinct_id = "test_event_filter_with_matching_on_session_id-user" + Person.objects.create(team=self.team, distinct_ids=[user_distinct_id], properties={"email": "bla"}) + session_id = f"test_event_filter_with_matching_on_session_id-1-{str(uuid4())}" + + self.create_event( + user_distinct_id, + self.an_hour_ago, + event_name="$pageview", + properties={"$session_id": session_id}, + ) + self.create_event( + user_distinct_id, + self.an_hour_ago, + event_name="$autocapture", + properties={"$session_id": str(uuid4())}, + ) + + produce_replay_summary( + distinct_id=user_distinct_id, + session_id=session_id, + first_timestamp=self.an_hour_ago, + team_id=self.team.id, + ) + produce_replay_summary( + distinct_id=user_distinct_id, + session_id=session_id, + first_timestamp=self.an_hour_ago + relativedelta(seconds=30), + team_id=self.team.id, + ) + + (session_recordings, _, _) = self._filter_recordings_by( + { + "events": [ + { + "id": "$pageview", + "type": "events", + "order": 0, + "name": "$pageview", + } + ] + } + ) + + assert len(session_recordings) == 1 + assert session_recordings[0]["session_id"] == session_id + + (session_recordings, _, _) = self._filter_recordings_by( + { + "events": [ + { + "id": "$autocapture", + "type": "events", + "order": 0, + "name": "$autocapture", + } + ] + } + ) + assert session_recordings == [] + + @also_test_with_materialized_columns(event_properties=["$current_url", "$browser"], person_properties=["email"]) + @snapshot_clickhouse_queries + def test_event_filter_with_hogql_properties(self): + user = "test_event_filter_with_hogql_properties-user" + + Person.objects.create(team=self.team, distinct_ids=[user], properties={"email": "bla"}) + + session_id = f"test_event_filter_with_hogql_properties-1-{str(uuid4())}" + self.create_event( + user, + self.an_hour_ago, + properties={ + "$browser": "Chrome", + "$session_id": session_id, + "$window_id": str(uuid4()), + }, + ) + + produce_replay_summary( + distinct_id=user, + session_id=session_id, + first_timestamp=self.an_hour_ago, + team_id=self.team.id, + ) + produce_replay_summary( + distinct_id=user, + session_id=session_id, + first_timestamp=self.an_hour_ago + relativedelta(seconds=30), + team_id=self.team.id, + ) + + (session_recordings, _, _) = self._filter_recordings_by( + { + "events": [ + { + "id": "$pageview", + "type": "events", + "order": 0, + "name": "$pageview", + "properties": [ + {"key": "properties.$browser == 'Chrome'", "type": "hogql"}, + ], + } + ] + } + ) + + assert len(session_recordings) == 1 + assert session_recordings[0]["session_id"] == session_id + + (session_recordings, _, _) = self._filter_recordings_by( + { + "events": [ + { + "id": "$pageview", + "type": "events", + "order": 0, + "name": "$pageview", + "properties": [{"key": "properties.$browser == 'Firefox'", "type": "hogql"}], + } + ] + } + ) + + assert session_recordings == [] + + @snapshot_clickhouse_queries + def test_event_filter_with_hogql_person_properties(self): + user = "test_event_filter_with_hogql_properties-user" + + Person.objects.create(team=self.team, distinct_ids=[user], properties={"email": "bla"}) + + session_id = f"test_event_filter_with_hogql_properties-1-{str(uuid4())}" + self.create_event( + user, + self.an_hour_ago, + properties={ + "$browser": "Chrome", + "$session_id": session_id, + "$window_id": str(uuid4()), + }, + ) + + produce_replay_summary( + distinct_id=user, + session_id=session_id, + first_timestamp=self.an_hour_ago, + team_id=self.team.id, + ) + produce_replay_summary( + distinct_id=user, + session_id=session_id, + first_timestamp=self.an_hour_ago + relativedelta(seconds=30), + team_id=self.team.id, + ) + + (session_recordings, _, _) = self._filter_recordings_by( + { + "events": [ + { + "id": "$pageview", + "type": "events", + "order": 0, + "name": "$pageview", + "properties": [ + { + "key": "person.properties.email == 'bla'", + "type": "hogql", + }, + ], + } + ] + } + ) + + assert len(session_recordings) == 1 + assert session_recordings[0]["session_id"] == session_id + + (session_recordings, _, _) = self._filter_recordings_by( + { + "events": [ + { + "id": "$pageview", + "type": "events", + "order": 0, + "name": "$pageview", + "properties": [ + { + "key": "person.properties.email == 'something else'", + "type": "hogql", + }, + ], + } + ] + } + ) + + assert session_recordings == [] + + @also_test_with_materialized_columns(["$current_url", "$browser"]) + @snapshot_clickhouse_queries + @freeze_time("2021-01-21T20:00:00.000Z") + def test_any_event_filter_with_properties(self): + Person.objects.create(team=self.team, distinct_ids=["user"], properties={"email": "bla"}) + + page_view_session_id = f"pageview-session-{str(uuid4())}" + my_custom_event_session_id = f"my-custom-event-session-{str(uuid4())}" + non_matching__event_session_id = f"non-matching-event-session-{str(uuid4())}" + + self.create_event( + "user", + self.an_hour_ago, + properties={ + "$browser": "Chrome", + "$session_id": page_view_session_id, + "$window_id": "1", + }, + event_name="$pageview", + ) + + self.create_event( + "user", + self.an_hour_ago, + properties={ + "$browser": "Chrome", + "$session_id": my_custom_event_session_id, + "$window_id": "1", + }, + event_name="my-custom-event", + ) + + self.create_event( + "user", + self.an_hour_ago, + properties={ + "$browser": "Safari", + "$session_id": non_matching__event_session_id, + "$window_id": "1", + }, + event_name="my-non-matching-event", + ) + + produce_replay_summary( + distinct_id="user", + session_id=page_view_session_id, + first_timestamp=self.an_hour_ago, + team_id=self.team.id, + ) + produce_replay_summary( + distinct_id="user", + session_id=my_custom_event_session_id, + first_timestamp=self.an_hour_ago + relativedelta(seconds=30), + team_id=self.team.id, + ) + produce_replay_summary( + distinct_id="user", + session_id=non_matching__event_session_id, + first_timestamp=self.an_hour_ago + relativedelta(seconds=30), + team_id=self.team.id, + ) + + (session_recordings, _, _) = self._filter_recordings_by( + { + "events": [ + { + # an id of null means "match any event" + "id": None, + "type": "events", + "order": 0, + "name": "All events", + "properties": [], + } + ] + } + ) + + assert sorted( + [sr["session_id"] for sr in session_recordings], + ) == [ + my_custom_event_session_id, + non_matching__event_session_id, + page_view_session_id, + ] + + (session_recordings, _, _) = self._filter_recordings_by( + { + "events": [ + { + # an id of null means "match any event" + "id": None, + "type": "events", + "order": 0, + "name": "All events", + "properties": [ + { + "key": "$browser", + "value": ["Chrome"], + "operator": "exact", + "type": "event", + } + ], + } + ] + } + ) + + assert sorted( + [sr["session_id"] for sr in session_recordings], + ) == [ + my_custom_event_session_id, + page_view_session_id, + ] + + (session_recordings, _, _) = self._filter_recordings_by( + { + "events": [ + { + "id": None, + "type": "events", + "order": 0, + "name": "All events", + "properties": [ + { + "key": "$browser", + "value": ["Firefox"], + "operator": "exact", + "type": "event", + } + ], + } + ] + } + ) + assert session_recordings == [] + + @snapshot_clickhouse_queries + @freeze_time("2021-01-21T20:00:00.000Z") + def test_filter_for_recordings_with_console_logs(self): + Person.objects.create(team=self.team, distinct_ids=["user"], properties={"email": "bla"}) + + with_logs_session_id = f"with-logs-session-{str(uuid4())}" + without_logs_session_id = f"no-logs-session-{str(uuid4())}" + + produce_replay_summary( + distinct_id="user", + session_id=with_logs_session_id, + first_timestamp=self.an_hour_ago, + team_id=self.team.id, + console_log_count=4, + log_messages={ + "info": [ + "info", + "info", + "info", + ], + }, + ) + + produce_replay_summary( + distinct_id="user", + session_id=without_logs_session_id, + first_timestamp=self.an_hour_ago, + team_id=self.team.id, + ) + + # (session_recordings, _, _) = self._filter_recordings_by({"console_logs": ["info"]}) + + (session_recordings, _, _) = self._filter_recordings_by( + { + "console_log_filters": '[{"key": "level", "value": ["info"], "operator": "exact", "type": "log_entry"}]', + "operand": "AND", + } + ) + + actual = sorted( + [(sr["session_id"], sr["console_log_count"]) for sr in session_recordings], + key=lambda x: x[0], + ) + + assert actual == [ + (with_logs_session_id, 4), + ] + + (session_recordings, _, _) = self._filter_recordings_by( + { + "console_log_filters": '[{"key": "level", "value": ["warn"], "operator": "exact", "type": "log_entry"}]', + "operand": "AND", + } + ) + assert session_recordings == [] + + @snapshot_clickhouse_queries + @freeze_time("2021-01-21T20:00:00.000Z") + def test_filter_for_recordings_with_console_warns(self): + Person.objects.create(team=self.team, distinct_ids=["user"], properties={"email": "bla"}) + + with_logs_session_id = f"with-logs-session-{str(uuid4())}" + without_logs_session_id = f"no-logs-session-{str(uuid4())}" + + produce_replay_summary( + distinct_id="user", + session_id=with_logs_session_id, + first_timestamp=self.an_hour_ago, + team_id=self.team.id, + console_warn_count=4, + log_messages={ + "warn": [ + "warn", + "warn", + "warn", + "warn", + ], + }, + ) + produce_replay_summary( + distinct_id="user", + session_id=without_logs_session_id, + first_timestamp=self.an_hour_ago, + team_id=self.team.id, + ) + + (session_recordings, _, _) = self._filter_recordings_by( + { + "console_log_filters": '[{"key": "level", "value": ["warn"], "operator": "exact", "type": "log_entry"}]', + "operand": "AND", + } + ) + + assert sorted( + [(sr["session_id"], sr["console_warn_count"]) for sr in session_recordings], + key=lambda x: x[0], + ) == [ + (with_logs_session_id, 4), + ] + + (session_recordings, _, _) = self._filter_recordings_by( + { + "console_log_filters": '[{"key": "level", "value": ["info"], "operator": "exact", "type": "log_entry"}]', + "operand": "AND", + } + ) + + assert session_recordings == [] + + @snapshot_clickhouse_queries + @freeze_time("2021-01-21T20:00:00.000Z") + def test_filter_for_recordings_with_console_errors(self): + Person.objects.create(team=self.team, distinct_ids=["user"], properties={"email": "bla"}) + + with_logs_session_id = f"with-logs-session-{str(uuid4())}" + without_logs_session_id = f"no-logs-session-{str(uuid4())}" + + produce_replay_summary( + distinct_id="user", + session_id=with_logs_session_id, + first_timestamp=self.an_hour_ago, + team_id=self.team.id, + console_error_count=4, + log_messages={ + "error": [ + "error", + "error", + "error", + "error", + ], + }, + ) + produce_replay_summary( + distinct_id="user", + session_id=without_logs_session_id, + first_timestamp=self.an_hour_ago, + team_id=self.team.id, + ) + + (session_recordings, _, _) = self._filter_recordings_by( + { + "console_log_filters": '[{"key": "level", "value": ["error"], "operator": "exact", "type": "log_entry"}]', + "operand": "AND", + } + ) + + assert sorted( + [(sr["session_id"], sr["console_error_count"]) for sr in session_recordings], + key=lambda x: x[0], + ) == [ + (with_logs_session_id, 4), + ] + + (session_recordings, _, _) = self._filter_recordings_by( + { + "console_log_filters": '[{"key": "level", "value": ["info"], "operator": "exact", "type": "log_entry"}]', + "operand": "AND", + } + ) + + assert session_recordings == [] + + @snapshot_clickhouse_queries + @freeze_time("2021-01-21T20:00:00.000Z") + def test_filter_for_recordings_with_mixed_console_counts(self): + Person.objects.create(team=self.team, distinct_ids=["user"], properties={"email": "bla"}) + + with_logs_session_id = f"with-logs-session-{str(uuid4())}" + with_warns_session_id = f"with-warns-session-{str(uuid4())}" + with_errors_session_id = f"with-errors-session-{str(uuid4())}" + with_two_session_id = f"with-two-session-{str(uuid4())}" + + produce_replay_summary( + distinct_id="user", + session_id=with_logs_session_id, + first_timestamp=self.an_hour_ago, + team_id=self.team.id, + console_log_count=4, + log_messages={ + "info": [ + "info", + "info", + "info", + "info", + ], + }, + ) + produce_replay_summary( + distinct_id="user", + session_id=with_warns_session_id, + first_timestamp=self.an_hour_ago, + team_id=self.team.id, + console_warn_count=4, + log_messages={ + "warn": [ + "warn", + "warn", + "warn", + "warn", + ], + }, + ) + produce_replay_summary( + distinct_id="user", + session_id=with_errors_session_id, + first_timestamp=self.an_hour_ago, + team_id=self.team.id, + console_error_count=4, + log_messages={ + "error": [ + "error", + "error", + "error", + "error", + ], + }, + ) + produce_replay_summary( + distinct_id="user", + session_id=with_two_session_id, + first_timestamp=self.an_hour_ago, + team_id=self.team.id, + console_error_count=4, + console_log_count=3, + log_messages={ + "error": [ + "error", + "error", + "error", + "error", + ], + "info": [ + "info", + "info", + "info", + ], + }, + ) + + (session_recordings, _, _) = self._filter_recordings_by( + { + "console_log_filters": '[{"key": "level", "value": ["warn", "error"], "operator": "exact", "type": "log_entry"}]', + "operand": "AND", + } + ) + + assert sorted([sr["session_id"] for sr in session_recordings]) == sorted( + [ + with_errors_session_id, + with_two_session_id, + with_warns_session_id, + ] + ) + + (session_recordings, _, _) = self._filter_recordings_by( + { + "console_log_filters": '[{"key": "level", "value": ["info"], "operator": "exact", "type": "log_entry"}]', + "operand": "AND", + } + ) + + assert sorted([sr["session_id"] for sr in session_recordings]) == sorted( + [ + with_two_session_id, + with_logs_session_id, + ] + ) + + @parameterized.expand( + [ + # Case 1: OR operand, message 4 matches in warn and error + ( + '[{"key": "level", "value": ["warn", "error"], "operator": "exact", "type": "log_entry"}, {"key": "message", "value": "message 4", "operator": "icontains", "type": "log_entry"}]', + "OR", + ["with-errors-session", "with-two-session", "with-warns-session", "with-logs-session"], + ), + # Case 2: AND operand, message 4 matches in log, warn, and error + ( + '[{"key": "level", "value": ["warn", "error"], "operator": "exact", "type": "log_entry"}, {"key": "message", "value": "message 4", "operator": "icontains", "type": "log_entry"}]', + "AND", + ["with-errors-session", "with-two-session", "with-warns-session"], + ), + # Case 2: AND operand, message 5 matches only in warn + ( + '[{"key": "level", "value": ["warn", "error"], "operator": "exact", "type": "log_entry"}, {"key": "message", "value": "message 5", "operator": "icontains", "type": "log_entry"}]', + "AND", + ["with-warns-session"], + ), + # Case 3: AND operand, message 5 does not match log level "info" + ( + '[{"key": "level", "value": ["info"], "operator": "exact", "type": "log_entry"}, {"key": "message", "value": "message 5", "operator": "icontains", "type": "log_entry"}]', + "AND", + [], + ), + ] + ) + @snapshot_clickhouse_queries + @freeze_time("2021-01-21T20:00:00.000Z") + def test_filter_for_recordings_by_console_text( + self, + console_log_filters: str, + operand: Literal["AND", "OR"], + expected_session_ids: list[str], + ) -> None: + Person.objects.create(team=self.team, distinct_ids=["user"], properties={"email": "bla"}) + + # Create sessions + produce_replay_summary( + distinct_id="user", + session_id="with-logs-session", + first_timestamp=self.an_hour_ago, + team_id=self.team.id, + console_log_count=4, + log_messages={ + "info": [ + "log message 1", + "log message 2", + "log message 3", + "log message 4", + ] + }, + ) + produce_replay_summary( + distinct_id="user", + session_id="with-warns-session", + first_timestamp=self.an_hour_ago, + team_id=self.team.id, + console_warn_count=5, + log_messages={ + "warn": [ + "warn message 1", + "warn message 2", + "warn message 3", + "warn message 4", + "warn message 5", + ] + }, + ) + produce_replay_summary( + distinct_id="user", + session_id="with-errors-session", + first_timestamp=self.an_hour_ago, + team_id=self.team.id, + console_error_count=4, + log_messages={ + "error": [ + "error message 1", + "error message 2", + "error message 3", + "error message 4", + ] + }, + ) + produce_replay_summary( + distinct_id="user", + session_id="with-two-session", + first_timestamp=self.an_hour_ago, + team_id=self.team.id, + console_error_count=4, + console_log_count=3, + log_messages={ + "error": [ + "error message 1", + "error message 2", + "error message 3", + "error message 4", + ], + "info": ["log message 1", "log message 2", "log message 3"], + }, + ) + produce_replay_summary( + distinct_id="user", + session_id="with-no-matches-session", + first_timestamp=self.an_hour_ago, + team_id=self.team.id, + console_error_count=4, + console_log_count=3, + log_messages={ + "info": ["log message 1", "log message 2", "log message 3"], + }, + ) + + # Perform the filtering and validate results + session_recordings, _, _ = self._filter_recordings_by( + {"console_log_filters": console_log_filters, "operand": operand} + ) + + assert sorted([sr["session_id"] for sr in session_recordings]) == sorted(expected_session_ids) + + @snapshot_clickhouse_queries + def test_filter_for_recordings_by_snapshot_source(self): + user = "test_duration_filter-user" + Person.objects.create(team=self.team, distinct_ids=[user], properties={"email": "bla"}) + + session_id_one = "session one id" + produce_replay_summary( + distinct_id=user, + session_id=session_id_one, + team_id=self.team.id, + snapshot_source="web", + ) + + session_id_two = "session two id" + produce_replay_summary( + distinct_id=user, + session_id=session_id_two, + team_id=self.team.id, + snapshot_source="mobile", + ) + + (session_recordings, _, _) = self._filter_recordings_by( + { + "having_predicates": '[{"key": "snapshot_source", "value": ["web"], "operator": "exact", "type": "recording"}]' + } + ) + assert [r["session_id"] for r in session_recordings] == [session_id_one] + + (session_recordings, _, _) = self._filter_recordings_by( + { + "having_predicates": '[{"key": "snapshot_source", "value": ["mobile"], "operator": "exact", "type": "recording"}]' + } + ) + assert [r["session_id"] for r in session_recordings] == [session_id_two] + + @also_test_with_materialized_columns( + event_properties=["is_internal_user"], + person_properties=["email"], + verify_no_jsonextract=False, + ) + @freeze_time("2021-01-21T20:00:00.000Z") + @snapshot_clickhouse_queries + def test_event_filter_with_test_accounts_excluded(self): + self.team.test_account_filters = [ + { + "key": "email", + "value": "@posthog.com", + "operator": "not_icontains", + "type": "person", + }, + { + "key": "is_internal_user", + "value": ["false"], + "operator": "exact", + "type": "event", + }, + {"key": "properties.$browser == 'Chrome'", "type": "hogql"}, + ] + self.team.save() + + Person.objects.create(team=self.team, distinct_ids=["user"], properties={"email": "bla"}) + + produce_replay_summary( + distinct_id="user", + session_id="1", + first_timestamp=self.an_hour_ago, + team_id=self.team.id, + ) + self.create_event( + "user", + self.an_hour_ago, + properties={ + "$session_id": "1", + "$window_id": "1", + "is_internal_user": "true", + }, + ) + produce_replay_summary( + distinct_id="user", + session_id="1", + first_timestamp=self.an_hour_ago + relativedelta(seconds=30), + team_id=self.team.id, + ) + + (session_recordings, _, _) = self._filter_recordings_by( + { + "events": [ + { + "id": "$pageview", + "type": "events", + "order": 0, + "name": "$pageview", + } + ], + "filter_test_accounts": True, + } + ) + self.assertEqual(len(session_recordings), 0) + + (session_recordings, _, _) = self._filter_recordings_by( + { + "events": [ + { + "id": "$pageview", + "type": "events", + "order": 0, + "name": "$pageview", + } + ], + "filter_test_accounts": False, + } + ) + self.assertEqual(len(session_recordings), 1) + + @also_test_with_materialized_columns( + event_properties=["$browser"], + person_properties=["email"], + verify_no_jsonextract=False, + ) + @freeze_time("2021-01-21T20:00:00.000Z") + @snapshot_clickhouse_queries + def test_event_filter_with_hogql_event_properties_test_accounts_excluded(self): + self.team.test_account_filters = [ + {"key": "properties.$browser == 'Chrome'", "type": "hogql"}, + ] + self.team.save() + + Person.objects.create(team=self.team, distinct_ids=["user"], properties={"email": "bla"}) + Person.objects.create( + team=self.team, + distinct_ids=["user2"], + properties={"email": "not-the-other-one"}, + ) + + produce_replay_summary( + distinct_id="user", + session_id="1", + first_timestamp=self.an_hour_ago, + team_id=self.team.id, + ) + self.create_event( + "user", + self.an_hour_ago, + properties={"$session_id": "1", "$window_id": "1", "$browser": "Chrome"}, + ) + produce_replay_summary( + distinct_id="user", + session_id="1", + first_timestamp=self.an_hour_ago + relativedelta(seconds=30), + team_id=self.team.id, + ) + + produce_replay_summary( + distinct_id="user2", + session_id="2", + first_timestamp=self.an_hour_ago, + team_id=self.team.id, + ) + self.create_event( + "user2", + self.an_hour_ago, + properties={"$session_id": "2", "$window_id": "1", "$browser": "Firefox"}, + ) + + # there are 2 pageviews + (session_recordings, _, _) = self._filter_recordings_by( + { + # pageview that matches the hogql test_accounts filter + "events": [ + { + "id": "$pageview", + "type": "events", + "order": 0, + "name": "$pageview", + } + ], + "filter_test_accounts": False, + } + ) + self.assertEqual(len(session_recordings), 2) + + self.team.test_account_filters = [ + {"key": "person.properties.email == 'bla'", "type": "hogql"}, + ] + self.team.save() + + (session_recordings, _, _) = self._filter_recordings_by( + { + # only 1 pageview that matches the hogql test_accounts filter + "events": [ + { + "id": "$pageview", + "type": "events", + "order": 0, + "name": "$pageview", + } + ], + "filter_test_accounts": True, + } + ) + self.assertEqual(len(session_recordings), 1) + + self.team.test_account_filters = [ + {"key": "properties.$browser == 'Chrome'", "type": "hogql"}, + {"key": "person.properties.email == 'bla'", "type": "hogql"}, + ] + self.team.save() + + # one user sessions matches the person + event test_account filter + (session_recordings, _, _) = self._filter_recordings_by( + { + "filter_test_accounts": True, + } + ) + self.assertEqual(len(session_recordings), 1) + + # TRICKY: we had to disable use of materialized columns for part of the query generation + # due to RAM usage issues on the EU cluster + @also_test_with_materialized_columns(event_properties=["is_internal_user"], verify_no_jsonextract=False) + @freeze_time("2021-01-21T20:00:00.000Z") + @snapshot_clickhouse_queries + def test_top_level_event_property_test_account_filter(self): + """ + This is a regression test. A user with an $ip test account filter + reported the filtering wasn't working. + + The filter wasn't triggering the "should join events check", and so we didn't apply the filter at all + """ + self.team.test_account_filters = [ + { + "key": "is_internal_user", + "value": ["false"], + "operator": "exact", + "type": "event", + }, + ] + self.team.save() + + Person.objects.create(team=self.team, distinct_ids=["user"], properties={"email": "bla"}) + Person.objects.create( + team=self.team, + distinct_ids=["user2"], + properties={"email": "not-the-other-one"}, + ) + + produce_replay_summary( + distinct_id="user", + session_id="1", + first_timestamp=self.an_hour_ago, + team_id=self.team.id, + ) + self.create_event( + "user", + self.an_hour_ago, + properties={ + "$session_id": "1", + "$window_id": "1", + "is_internal_user": False, + }, + ) + produce_replay_summary( + distinct_id="user", + session_id="1", + first_timestamp=self.an_hour_ago + relativedelta(seconds=30), + team_id=self.team.id, + ) + + produce_replay_summary( + distinct_id="user2", + session_id="2", + first_timestamp=self.an_hour_ago, + team_id=self.team.id, + ) + self.create_event( + "user2", + self.an_hour_ago, + properties={ + "$session_id": "2", + "$window_id": "1", + "is_internal_user": True, + }, + ) + + # there are 2 pageviews + (session_recordings, _, _) = self._filter_recordings_by( + { + # pageview that matches the hogql test_accounts filter + "events": [ + { + "id": "$pageview", + "type": "events", + "order": 0, + "name": "$pageview", + } + ], + "filter_test_accounts": False, + } + ) + self.assertEqual(len(session_recordings), 2) + + (session_recordings, _, _) = self._filter_recordings_by( + { + # only 1 pageview that matches the test_accounts filter + "filter_test_accounts": True, + } + ) + self.assertEqual(len(session_recordings), 1) + + # TRICKY: we had to disable use of materialized columns for part of the query generation + # due to RAM usage issues on the EU cluster + @also_test_with_materialized_columns(event_properties=["is_internal_user"], verify_no_jsonextract=True) + @freeze_time("2021-01-21T20:00:00.000Z") + @snapshot_clickhouse_queries + def test_top_level_event_property_test_account_filter_allowing_denormalized_props(self): + """ + This is a duplicate of the test test_top_level_event_property_test_account_filter + but with denormalized props allowed + """ + + with self.settings(ALLOW_DENORMALIZED_PROPS_IN_LISTING=True): + self.team.test_account_filters = [ + { + "key": "is_internal_user", + "value": ["false"], + "operator": "exact", + "type": "event", + }, + ] + self.team.save() + + Person.objects.create(team=self.team, distinct_ids=["user"], properties={"email": "bla"}) + Person.objects.create( + team=self.team, + distinct_ids=["user2"], + properties={"email": "not-the-other-one"}, + ) + + produce_replay_summary( + distinct_id="user", + session_id="1", + first_timestamp=self.an_hour_ago, + team_id=self.team.id, + ) + self.create_event( + "user", + self.an_hour_ago, + properties={ + "$session_id": "1", + "$window_id": "1", + "is_internal_user": False, + }, + ) + produce_replay_summary( + distinct_id="user", + session_id="1", + first_timestamp=self.an_hour_ago + relativedelta(seconds=30), + team_id=self.team.id, + ) + + produce_replay_summary( + distinct_id="user2", + session_id="2", + first_timestamp=self.an_hour_ago, + team_id=self.team.id, + ) + self.create_event( + "user2", + self.an_hour_ago, + properties={ + "$session_id": "2", + "$window_id": "1", + "is_internal_user": True, + }, + ) + + # there are 2 pageviews + (session_recordings, _, _) = self._filter_recordings_by( + { + # pageview that matches the hogql test_accounts filter + "events": [ + { + "id": "$pageview", + "type": "events", + "order": 0, + "name": "$pageview", + } + ], + "filter_test_accounts": False, + } + ) + self.assertEqual(len(session_recordings), 2) + + (session_recordings, _, _) = self._filter_recordings_by( + { + # only 1 pageview that matches the test_accounts filter + "filter_test_accounts": True, + } + ) + self.assertEqual(len(session_recordings), 1) + + @also_test_with_materialized_columns(event_properties=["is_internal_user"]) + @freeze_time("2021-01-21T20:00:00.000Z") + @snapshot_clickhouse_queries + def test_top_level_hogql_event_property_test_account_filter(self): + """ + This is a regression test. A user with an $ip test account filter + reported the filtering wasn't working. + + The filter wasn't triggering the "should join events" check, and so we didn't apply the filter at all + """ + self.team.test_account_filters = [ + {"key": "properties.is_internal_user == 'true'", "type": "hogql"}, + ] + self.team.save() + + Person.objects.create(team=self.team, distinct_ids=["user"], properties={"email": "bla"}) + Person.objects.create( + team=self.team, + distinct_ids=["user2"], + properties={"email": "not-the-other-one"}, + ) + + produce_replay_summary( + distinct_id="user", + session_id="1", + first_timestamp=self.an_hour_ago, + team_id=self.team.id, + ) + self.create_event( + "user", + self.an_hour_ago, + properties={ + "$session_id": "1", + "$window_id": "1", + "is_internal_user": False, + }, + ) + produce_replay_summary( + distinct_id="user", + session_id="1", + first_timestamp=self.an_hour_ago + relativedelta(seconds=30), + team_id=self.team.id, + ) + + produce_replay_summary( + distinct_id="user2", + session_id="2", + first_timestamp=self.an_hour_ago, + team_id=self.team.id, + ) + self.create_event( + "user2", + self.an_hour_ago, + properties={ + "$session_id": "2", + "$window_id": "1", + "is_internal_user": True, + }, + ) + + # there are 2 pageviews + (session_recordings, _, _) = self._filter_recordings_by( + { + # pageview that matches the hogql test_accounts filter + "events": [ + { + "id": "$pageview", + "type": "events", + "order": 0, + "name": "$pageview", + } + ], + "filter_test_accounts": False, + } + ) + self.assertEqual(len(session_recordings), 2) + + (session_recordings, _, _) = self._filter_recordings_by( + { + # only 1 pageview that matches the test_accounts filter + "filter_test_accounts": True, + } + ) + self.assertEqual(len(session_recordings), 1) + + @also_test_with_materialized_columns(person_properties=["email"], verify_no_jsonextract=False) + @freeze_time("2021-01-21T20:00:00.000Z") + @snapshot_clickhouse_queries + def test_top_level_hogql_person_property_test_account_filter(self): + """ + This is a regression test. A user with an $ip test account filter + reported the filtering wasn't working. + + The filter wasn't triggering the "should join events" check, and so we didn't apply the filter at all + """ + self.team.test_account_filters = [ + {"key": "person.properties.email == 'bla'", "type": "hogql"}, + ] + self.team.save() + + Person.objects.create(team=self.team, distinct_ids=["user"], properties={"email": "bla"}) + Person.objects.create( + team=self.team, + distinct_ids=["user2"], + properties={"email": "not-the-other-one"}, + ) + + produce_replay_summary( + distinct_id="user", + session_id="1", + first_timestamp=self.an_hour_ago, + team_id=self.team.id, + ) + self.create_event( + "user", + self.an_hour_ago, + properties={ + "$session_id": "1", + "$window_id": "1", + "is_internal_user": False, + }, + ) + produce_replay_summary( + distinct_id="user", + session_id="1", + first_timestamp=self.an_hour_ago + relativedelta(seconds=30), + team_id=self.team.id, + ) + + produce_replay_summary( + distinct_id="user2", + session_id="2", + first_timestamp=self.an_hour_ago, + team_id=self.team.id, + ) + self.create_event( + "user2", + self.an_hour_ago, + properties={ + "$session_id": "2", + "$window_id": "1", + "is_internal_user": True, + }, + ) + + # there are 2 pageviews + (session_recordings, _, _) = self._filter_recordings_by( + { + # pageview that matches the hogql test_accounts filter + "events": [ + { + "id": "$pageview", + "type": "events", + "order": 0, + "name": "$pageview", + } + ], + "filter_test_accounts": False, + } + ) + self.assertEqual(len(session_recordings), 2) + + (session_recordings, _, _) = self._filter_recordings_by( + { + # only 1 pageview that matches the test_accounts filter + "filter_test_accounts": True, + } + ) + self.assertEqual(len(session_recordings), 1) + + @also_test_with_materialized_columns(person_properties=["email"], verify_no_jsonextract=False) + @freeze_time("2021-01-21T20:00:00.000Z") + @snapshot_clickhouse_queries + def test_top_level_person_property_test_account_filter(self): + """ + This is a regression test. A user with an $ip test account filter + reported the filtering wasn't working. + + The filter wasn't triggering the "should join events" check, and so we didn't apply the filter at all + """ + self.team.test_account_filters = [{"key": "email", "value": ["bla"], "operator": "exact", "type": "person"}] + self.team.save() + + Person.objects.create(team=self.team, distinct_ids=["user"], properties={"email": "bla"}) + Person.objects.create( + team=self.team, + distinct_ids=["user2"], + properties={"email": "not-the-other-one"}, + ) + + produce_replay_summary( + distinct_id="user", + session_id="1", + first_timestamp=self.an_hour_ago, + team_id=self.team.id, + ) + self.create_event( + "user", + self.an_hour_ago, + properties={ + "event": "something that won't match", + "$session_id": "1", + "$window_id": "1", + "is_internal_user": False, + }, + ) + + self.create_event( + "user", + self.an_hour_ago, + properties={ + "$session_id": "1", + "$window_id": "1", + "is_internal_user": False, + }, + ) + produce_replay_summary( + distinct_id="user", + session_id="1", + first_timestamp=self.an_hour_ago + relativedelta(seconds=30), + team_id=self.team.id, + ) + + produce_replay_summary( + distinct_id="user2", + session_id="2", + first_timestamp=self.an_hour_ago, + team_id=self.team.id, + ) + self.create_event( + "user2", + self.an_hour_ago, + properties={ + "$session_id": "2", + "$window_id": "1", + "is_internal_user": True, + }, + ) + + # there are 2 pageviews + (session_recordings, _, _) = self._filter_recordings_by( + { + # pageview that matches the hogql test_accounts filter + "events": [ + { + "id": "$pageview", + "type": "events", + "order": 0, + "name": "$pageview", + } + ], + "filter_test_accounts": False, + } + ) + self.assertEqual(len(session_recordings), 2) + + (session_recordings, _, _) = self._filter_recordings_by( + { + # only 1 pageview that matches the test_accounts filter + "filter_test_accounts": True, + } + ) + self.assertEqual(len(session_recordings), 1) + + @freeze_time("2021-01-21T20:00:00.000Z") + @snapshot_clickhouse_queries + def test_event_filter_with_two_events_and_multiple_teams(self): + another_team = Team.objects.create(organization=self.organization) + + # two teams, user with the same properties + Person.objects.create(team=self.team, distinct_ids=["user"], properties={"email": "bla"}) + Person.objects.create(team=another_team, distinct_ids=["user"], properties={"email": "bla"}) + + # a recording session with a pageview and a pageleave + self._a_session_with_two_events(self.team, "1") + self._a_session_with_two_events(another_team, "2") + + (session_recordings, _, _) = self._filter_recordings_by( + { + "events": [ + { + "id": "$pageview", + "type": "events", + "order": 0, + "name": "$pageview", + }, + { + "id": "$pageleave", + "type": "events", + "order": 0, + "name": "$pageleave", + }, + ], + } + ) + + self.assertEqual([sr["session_id"] for sr in session_recordings], ["1"]) + + def _a_session_with_two_events(self, team: Team, session_id: str) -> None: + produce_replay_summary( + distinct_id="user", + session_id=session_id, + first_timestamp=self.an_hour_ago, + team_id=team.pk, + ) + self.create_event( + "user", + self.an_hour_ago, + team=team, + event_name="$pageview", + properties={"$session_id": session_id, "$window_id": "1"}, + ) + self.create_event( + "user", + self.an_hour_ago, + team=team, + event_name="$pageleave", + properties={"$session_id": session_id, "$window_id": "1"}, + ) + + @freeze_time("2021-01-21T20:00:00.000Z") + @snapshot_clickhouse_queries + def test_event_filter_with_group_filter(self): + Person.objects.create(team=self.team, distinct_ids=["user"], properties={"email": "bla"}) + session_id = f"test_event_filter_with_group_filter-ONE-{uuid4()}" + different_group_session = f"test_event_filter_with_group_filter-TWO-{uuid4()}" + + produce_replay_summary( + distinct_id="user", + session_id=session_id, + first_timestamp=self.an_hour_ago, + team_id=self.team.pk, + ) + produce_replay_summary( + distinct_id="user", + session_id=different_group_session, + first_timestamp=self.an_hour_ago, + team_id=self.team.pk, + ) + + GroupTypeMapping.objects.create( + team=self.team, project_id=self.team.project_id, group_type="project", group_type_index=0 + ) + create_group( + team_id=self.team.pk, + group_type_index=0, + group_key="project:1", + properties={"name": "project one"}, + ) + + GroupTypeMapping.objects.create( + team=self.team, project_id=self.team.project_id, group_type="organization", group_type_index=1 + ) + create_group( + team_id=self.team.pk, + group_type_index=1, + group_key="org:1", + properties={"name": "org one"}, + ) + + self.create_event( + "user", + self.an_hour_ago, + team=self.team, + event_name="$pageview", + properties={ + "$session_id": session_id, + "$window_id": "1", + "$group_1": "org:1", + }, + ) + self.create_event( + "user", + self.an_hour_ago, + team=self.team, + event_name="$pageview", + properties={ + "$session_id": different_group_session, + "$window_id": "1", + "$group_0": "project:1", + }, + ) + + (session_recordings, _, _) = self._filter_recordings_by( + { + "events": [ + { + "id": "$pageview", + "type": "events", + "order": 0, + "name": "$pageview", + "properties": [ + { + "key": "name", + "value": ["org one"], + "operator": "exact", + "type": "group", + "group_type_index": 1, + } + ], + } + ], + } + ) + + assert [sr["session_id"] for sr in session_recordings] == [session_id] + + (session_recordings, _, _) = self._filter_recordings_by( + { + "properties": [ + { + "key": "name", + "value": ["org one"], + "operator": "exact", + "type": "group", + "group_type_index": 1, + } + ], + } + ) + assert [sr["session_id"] for sr in session_recordings] == [session_id] + + (session_recordings, _, _) = self._filter_recordings_by( + { + "properties": [ + { + "key": "name", + "value": ["org one"], + "operator": "exact", + "type": "group", + "group_type_index": 2, + } + ], + } + ) + assert session_recordings == [] + + @freeze_time("2021-01-21T20:00:00.000Z") + @snapshot_clickhouse_queries + def test_ordering(self): + session_id_one = f"test_ordering-one" + session_id_two = f"test_ordering-two" + session_id_three = f"test_ordering-three" + + produce_replay_summary( + session_id=session_id_one, + team_id=self.team.id, + mouse_activity_count=50, + first_timestamp=(self.an_hour_ago + relativedelta(seconds=60)), + ) + produce_replay_summary( + session_id=session_id_two, + team_id=self.team.id, + mouse_activity_count=100, + first_timestamp=self.an_hour_ago, + ) + produce_replay_summary( + session_id=session_id_three, + team_id=self.team.id, + mouse_activity_count=10, + first_timestamp=(self.an_hour_ago + relativedelta(minutes=10)), + ) + + (session_recordings, _, _) = self._filter_recordings_by({"order": "start_time"}) + assert [r["session_id"] for r in session_recordings] == [session_id_three, session_id_one, session_id_two] + + (session_recordings, _, _) = self._filter_recordings_by({"order": "mouse_activity_count"}) + assert [r["session_id"] for r in session_recordings] == [session_id_two, session_id_one, session_id_three] + + @also_test_with_materialized_columns(event_properties=["$host"], verify_no_jsonextract=False) + @freeze_time("2021-01-21T20:00:00.000Z") + @snapshot_clickhouse_queries + def test_top_level_event_host_property_test_account_filter(self): + """ + This is a regression test. See: https://posthoghelp.zendesk.com/agent/tickets/18059 + """ + self.team.test_account_filters = [ + {"key": "$host", "type": "event", "value": "^(localhost|127\\.0\\.0\\.1)($|:)", "operator": "not_regex"}, + ] + self.team.save() + + Person.objects.create(team=self.team, distinct_ids=["user"], properties={"email": "bla"}) + Person.objects.create( + team=self.team, + distinct_ids=["user2"], + properties={"email": "not-the-other-one"}, + ) + + produce_replay_summary( + distinct_id="user", + session_id="1", + first_timestamp=self.an_hour_ago, + team_id=self.team.id, + ensure_analytics_event_in_session=False, + ) + # the session needs to have multiple matching or not matching events + for _ in range(10): + self.create_event( + "user", + self.an_hour_ago, + properties={ + "$session_id": "1", + "$window_id": "1", + "$host": "localhost", + }, + ) + + produce_replay_summary( + distinct_id="user", + session_id="1", + first_timestamp=self.an_hour_ago + relativedelta(seconds=30), + team_id=self.team.id, + click_count=10, + ensure_analytics_event_in_session=False, + ) + + for _ in range(10): + self.create_event( + "user2", + self.an_hour_ago, + properties={ + "$session_id": "2", + "$window_id": "1", + "$host": "example.com", + }, + ) + produce_replay_summary( + distinct_id="user2", + session_id="2", + first_timestamp=self.an_hour_ago, + team_id=self.team.id, + click_count=10, + ensure_analytics_event_in_session=False, + ) + + # there are 2 pageviews + (session_recordings, _, _) = self._filter_recordings_by( + { + # pageview that matches the hogql test_accounts filter + "events": [ + { + "id": "$pageview", + "type": "events", + "order": 0, + "name": "$pageview", + } + ], + "filter_test_accounts": False, + } + ) + self.assertEqual(len(session_recordings), 2) + + (session_recordings, _, _) = self._filter_recordings_by( + { + # only 1 pageview that matches the test_accounts filter + "filter_test_accounts": True, + } + ) + assert session_recordings == [ + { + "active_seconds": 0.0, + "activity_score": 0.28, + "click_count": 10, # in the bug this value was 10 X number of events in the session + "console_error_count": 0, + "console_log_count": 0, + "console_warn_count": 0, + "distinct_id": "user2", + "duration": 3600, + "end_time": ANY, + "first_url": "https://not-provided-by-test.com", + "inactive_seconds": 3600.0, + "keypress_count": 0, + "mouse_activity_count": 0, + "session_id": "2", + "start_time": ANY, + "team_id": self.team.id, + "ongoing": 1, + } + ] diff --git a/posthog/session_recordings/session_recording_api.py b/posthog/session_recordings/session_recording_api.py index 83aa9564b2acb..0b9ea6e40d48c 100644 --- a/posthog/session_recordings/session_recording_api.py +++ b/posthog/session_recordings/session_recording_api.py @@ -4,6 +4,7 @@ from collections.abc import Generator from contextlib import contextmanager from datetime import UTC, datetime, timedelta +from json import JSONDecodeError from typing import Any, Optional, cast import posthoganalytics @@ -36,7 +37,7 @@ ClickHouseSustainedRateThrottle, PersonalApiKeyRateThrottle, ) -from posthog.schema import HogQLQueryModifiers, QueryTiming +from posthog.schema import HogQLQueryModifiers, QueryTiming, RecordingsQuery from posthog.session_recordings.models.session_recording import SessionRecording from posthog.session_recordings.models.session_recording_event import ( SessionRecordingViewed, @@ -45,6 +46,7 @@ ReplayFiltersEventsSubQuery, SessionRecordingListFromFilters, ) +from posthog.session_recordings.queries.session_recording_list_from_query import SessionRecordingListFromQuery from posthog.session_recordings.queries.session_recording_properties import ( SessionRecordingProperties, ) @@ -240,10 +242,8 @@ def validate(self, data): return data -def list_recordings_response( - filter: SessionRecordingsFilter, request: request.Request, serializer_context: dict[str, Any] -) -> Response: - (recordings, timings) = list_recordings(filter, request, context=serializer_context) +def list_recordings_response(listing_result: tuple[dict, dict]) -> Response: + (recordings, timings) = listing_result response = Response(recordings) response.headers["Server-Timing"] = ", ".join( f"{key};dur={round(duration, ndigits=2)}" for key, duration in timings.items() @@ -294,6 +294,22 @@ class SnapshotsSustainedRateThrottle(PersonalApiKeyRateThrottle): rate = "600/hour" +def query_as_params_to_dict(params_dict: dict) -> dict: + """ + before (if ever) we convert this to a query runner that takes a post + we need to convert to a valid dict from the data that arrived in query params + """ + converted = {} + for key in params_dict: + try: + converted[key] = json.loads(params_dict[key]) if isinstance(params_dict[key], str) else params_dict[key] + except JSONDecodeError: + converted[key] = params_dict[key] + + converted.pop("as_query", None) + return converted + + # NOTE: Could we put the sharing stuff in the shared mixin :thinking: class SessionRecordingViewSet(TeamAndOrgViewSetMixin, viewsets.GenericViewSet, UpdateModelMixin): scope_object = "session_recording" @@ -320,9 +336,19 @@ def safely_get_object(self, queryset) -> SessionRecording: return recording def list(self, request: request.Request, *args: Any, **kwargs: Any) -> Response: - filter = SessionRecordingsFilter(request=request, team=self.team) - self._maybe_report_recording_list_filters_changed(request, team=self.team) - return list_recordings_response(filter, request, self.get_serializer_context()) + use_query_type = (request.GET.get("as_query", "False")).lower() == "true" + if use_query_type: + data_dict = query_as_params_to_dict(request.GET.dict()) + query = RecordingsQuery.model_validate(data_dict) + # a little duplication for now + self._maybe_report_recording_list_filters_changed(request, team=self.team) + return list_recordings_response( + list_recordings_from_query(query, request, context=self.get_serializer_context()) + ) + else: + filter = SessionRecordingsFilter(request=request, team=self.team) + self._maybe_report_recording_list_filters_changed(request, team=self.team) + return list_recordings_response(list_recordings(filter, request, context=self.get_serializer_context())) @extend_schema( exclude=True, @@ -794,6 +820,107 @@ def _send_realtime_snapshots_to_client( raise exceptions.ValidationError(f"Invalid version: {version}") +# TODO i guess this becomes the query runner for our _internal_ use of RecordingsQuery +def list_recordings_from_query( + query: RecordingsQuery, request: request.Request, context: dict[str, Any] +) -> tuple[dict, dict]: + """ + As we can store recordings in S3 or in Clickhouse we need to do a few things here + + A. If filter.session_ids is specified: + 1. We first try to load them directly from Postgres if they have been persisted to S3 (they might have fell out of CH) + 2. Any that couldn't be found are then loaded from Clickhouse + B. Otherwise we just load all values from Clickhouse + 2. Once loaded we convert them to SessionRecording objects in case we have any other persisted data + """ + + all_session_ids = query.session_ids + + recordings: list[SessionRecording] = [] + more_recordings_available = False + team = context["get_team"]() + hogql_timings: list[QueryTiming] | None = None + + timer = ServerTimingsGathered() + + if all_session_ids: + with timer("load_persisted_recordings"): + # If we specify the session ids (like from pinned recordings) we can optimise by only going to Postgres + sorted_session_ids = sorted(all_session_ids) + + persisted_recordings_queryset = SessionRecording.objects.filter( + team=team, session_id__in=sorted_session_ids + ).exclude(object_storage_path=None) + + persisted_recordings = persisted_recordings_queryset.all() + + recordings = recordings + list(persisted_recordings) + + remaining_session_ids = list(set(all_session_ids) - {x.session_id for x in persisted_recordings}) + query.session_ids = remaining_session_ids + + if (all_session_ids and query.session_ids) or not all_session_ids: + distinct_id = str(cast(User, request.user).distinct_id) + modifiers = safely_read_modifiers_overrides(distinct_id, team) + + with timer("load_recordings_from_hogql"): + (ch_session_recordings, more_recordings_available, hogql_timings) = SessionRecordingListFromQuery( + query=query, team=team, hogql_query_modifiers=modifiers + ).run() + + with timer("build_recordings"): + recordings_from_clickhouse = SessionRecording.get_or_build_from_clickhouse(team, ch_session_recordings) + recordings = recordings + recordings_from_clickhouse + + recordings = [x for x in recordings if not x.deleted] + + # If we have specified session_ids we need to sort them by the order they were specified + if all_session_ids: + recordings = sorted( + recordings, + key=lambda x: cast(list[str], all_session_ids).index(x.session_id), + ) + + if not request.user.is_authenticated: # for mypy + raise exceptions.NotAuthenticated() + + # Update the viewed status for all loaded recordings + with timer("load_viewed_recordings"): + viewed_session_recordings = set( + SessionRecordingViewed.objects.filter(team=team, user=request.user).values_list("session_id", flat=True) + ) + + with timer("load_persons"): + # Get the related persons for all the recordings + distinct_ids = sorted([x.distinct_id for x in recordings]) + person_distinct_ids = PersonDistinctId.objects.filter(distinct_id__in=distinct_ids, team=team).select_related( + "person" + ) + + with timer("process_persons"): + distinct_id_to_person = {} + for person_distinct_id in person_distinct_ids: + person_distinct_id.person._distinct_ids = [ + person_distinct_id.distinct_id + ] # Stop the person from loading all distinct ids + distinct_id_to_person[person_distinct_id.distinct_id] = person_distinct_id.person + + for recording in recordings: + recording.viewed = recording.session_id in viewed_session_recordings + person = distinct_id_to_person.get(recording.distinct_id) + if person: + recording.person = person + + session_recording_serializer = SessionRecordingSerializer(recordings, context=context, many=True) + results = session_recording_serializer.data + + all_timings = _generate_timings(hogql_timings, timer) + return ( + {"results": results, "has_next": more_recordings_available, "version": 4}, + all_timings, + ) + + def list_recordings( filter: SessionRecordingsFilter, request: request.Request, context: dict[str, Any] ) -> tuple[dict, dict]: @@ -865,7 +992,7 @@ def list_recordings( with timer("load_persons"): # Get the related persons for all the recordings - distinct_ids = sorted([x.distinct_id for x in recordings]) + distinct_ids = sorted([x.distinct_id for x in recordings if x.distinct_id]) person_distinct_ids = PersonDistinctId.objects.filter(distinct_id__in=distinct_ids, team=team).select_related( "person" ) @@ -880,7 +1007,7 @@ def list_recordings( for recording in recordings: recording.viewed = recording.session_id in viewed_session_recordings - person = distinct_id_to_person.get(recording.distinct_id) + person = distinct_id_to_person.get(recording.distinct_id) if recording.distinct_id else None if person: recording.person = person diff --git a/posthog/session_recordings/test/__snapshots__/test_session_recordings.ambr b/posthog/session_recordings/test/__snapshots__/test_session_recordings.ambr index 179684d289047..88a534a569646 100644 --- a/posthog/session_recordings/test/__snapshots__/test_session_recordings.ambr +++ b/posthog/session_recordings/test/__snapshots__/test_session_recordings.ambr @@ -640,12 +640,12 @@ LEFT OUTER JOIN "posthog_organizationmembership" ON ("ee_accesscontrol"."organization_member_id" = "posthog_organizationmembership"."id") WHERE (("ee_accesscontrol"."organization_member_id" IS NULL AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '450' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("posthog_organizationmembership"."user_id" = 99999 AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '450' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("ee_accesscontrol"."organization_member_id" IS NULL @@ -1690,12 +1690,12 @@ LEFT OUTER JOIN "posthog_organizationmembership" ON ("ee_accesscontrol"."organization_member_id" = "posthog_organizationmembership"."id") WHERE (("ee_accesscontrol"."organization_member_id" IS NULL AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '457' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("posthog_organizationmembership"."user_id" = 99999 AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '457' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("ee_accesscontrol"."organization_member_id" IS NULL @@ -2445,12 +2445,12 @@ LEFT OUTER JOIN "posthog_organizationmembership" ON ("ee_accesscontrol"."organization_member_id" = "posthog_organizationmembership"."id") WHERE (("ee_accesscontrol"."organization_member_id" IS NULL AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '457' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("posthog_organizationmembership"."user_id" = 99999 AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '457' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("ee_accesscontrol"."organization_member_id" IS NULL @@ -3136,12 +3136,12 @@ LEFT OUTER JOIN "posthog_organizationmembership" ON ("ee_accesscontrol"."organization_member_id" = "posthog_organizationmembership"."id") WHERE (("ee_accesscontrol"."organization_member_id" IS NULL AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '457' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("posthog_organizationmembership"."user_id" = 99999 AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '457' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("ee_accesscontrol"."organization_member_id" IS NULL @@ -3890,12 +3890,12 @@ LEFT OUTER JOIN "posthog_organizationmembership" ON ("ee_accesscontrol"."organization_member_id" = "posthog_organizationmembership"."id") WHERE (("ee_accesscontrol"."organization_member_id" IS NULL AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '457' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("posthog_organizationmembership"."user_id" = 99999 AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '457' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("ee_accesscontrol"."organization_member_id" IS NULL @@ -4608,12 +4608,12 @@ LEFT OUTER JOIN "posthog_organizationmembership" ON ("ee_accesscontrol"."organization_member_id" = "posthog_organizationmembership"."id") WHERE (("ee_accesscontrol"."organization_member_id" IS NULL AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '457' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("posthog_organizationmembership"."user_id" = 99999 AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '457' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("ee_accesscontrol"."organization_member_id" IS NULL @@ -5408,12 +5408,12 @@ LEFT OUTER JOIN "posthog_organizationmembership" ON ("ee_accesscontrol"."organization_member_id" = "posthog_organizationmembership"."id") WHERE (("ee_accesscontrol"."organization_member_id" IS NULL AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '457' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("posthog_organizationmembership"."user_id" = 99999 AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '457' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("ee_accesscontrol"."organization_member_id" IS NULL @@ -5673,12 +5673,12 @@ LEFT OUTER JOIN "posthog_organizationmembership" ON ("ee_accesscontrol"."organization_member_id" = "posthog_organizationmembership"."id") WHERE (("ee_accesscontrol"."organization_member_id" IS NULL AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '457' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("posthog_organizationmembership"."user_id" = 99999 AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '457' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("ee_accesscontrol"."organization_member_id" IS NULL @@ -6107,12 +6107,12 @@ LEFT OUTER JOIN "posthog_organizationmembership" ON ("ee_accesscontrol"."organization_member_id" = "posthog_organizationmembership"."id") WHERE (("ee_accesscontrol"."organization_member_id" IS NULL AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '457' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("posthog_organizationmembership"."user_id" = 99999 AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '457' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("ee_accesscontrol"."organization_member_id" IS NULL @@ -6573,12 +6573,12 @@ LEFT OUTER JOIN "posthog_organizationmembership" ON ("ee_accesscontrol"."organization_member_id" = "posthog_organizationmembership"."id") WHERE (("ee_accesscontrol"."organization_member_id" IS NULL AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '457' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("posthog_organizationmembership"."user_id" = 99999 AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '457' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("ee_accesscontrol"."organization_member_id" IS NULL @@ -7267,12 +7267,12 @@ LEFT OUTER JOIN "posthog_organizationmembership" ON ("ee_accesscontrol"."organization_member_id" = "posthog_organizationmembership"."id") WHERE (("ee_accesscontrol"."organization_member_id" IS NULL AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '457' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("posthog_organizationmembership"."user_id" = 99999 AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '457' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("ee_accesscontrol"."organization_member_id" IS NULL @@ -8018,12 +8018,12 @@ LEFT OUTER JOIN "posthog_organizationmembership" ON ("ee_accesscontrol"."organization_member_id" = "posthog_organizationmembership"."id") WHERE (("ee_accesscontrol"."organization_member_id" IS NULL AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '457' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("posthog_organizationmembership"."user_id" = 99999 AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '457' + AND "ee_accesscontrol"."resource_id" = '99999' AND "ee_accesscontrol"."role_id" IS NULL AND "ee_accesscontrol"."team_id" = 99999) OR ("ee_accesscontrol"."organization_member_id" IS NULL diff --git a/posthog/session_recordings/test/test_session_recordings.py b/posthog/session_recordings/test/test_session_recordings.py index e1be77900953d..f3c7d7edfa38d 100644 --- a/posthog/session_recordings/test/test_session_recordings.py +++ b/posthog/session_recordings/test/test_session_recordings.py @@ -19,6 +19,7 @@ from posthog.models.filters.session_recordings_filter import SessionRecordingsFilter from posthog.models.property import Property from posthog.models.team import Team +from posthog.schema import RecordingsQuery, LogEntryPropertyFilter from posthog.session_recordings.models.session_recording_event import ( SessionRecordingViewed, ) @@ -171,18 +172,24 @@ def test_can_list_recordings_even_when_the_person_has_multiple_distinct_ids(self @patch("posthoganalytics.capture") @patch("posthog.session_recordings.session_recording_api.SessionRecordingListFromFilters") - def test_console_log_filters_are_correctly_passed_to_listing(self, mock_summary_lister, mock_capture): + @patch("posthog.session_recordings.session_recording_api.list_recordings_from_query") + def test_console_log_filters_are_correctly_passed_to_listing_when_filters_are_used( + self, mock_query_lister, mock_summary_lister, mock_capture + ): mock_summary_lister.return_value.run.return_value = ([], False) + mock_query_lister.return_value.run.return_value = ([], False) params_string = urlencode( { - "console_log_filters": '[{"key": "console_log_level", "value": ["warn", "error"], "operator": "exact", "type": "recording"}]', + "console_log_filters": '[{"key": "console_log_level", "value": ["warn", "error"], "operator": "exact", "type": "log_entry"}]', "user_modified_filters": '{"my_filter": "something"}', + "as_query": False, } ) self.client.get(f"/api/projects/{self.team.id}/session_recordings?{params_string}") assert len(mock_summary_lister.call_args_list) == 1 + assert len(mock_query_lister.call_args_list) == 0 filter_passed_to_mock: SessionRecordingsFilter = mock_summary_lister.call_args_list[0].kwargs["filter"] console_filter = cast(Property, filter_passed_to_mock.console_log_filters.values[0]) assert console_filter.value == ["warn", "error"] @@ -197,6 +204,44 @@ def test_console_log_filters_are_correctly_passed_to_listing(self, mock_summary_ groups=ANY, ) + @patch("posthoganalytics.capture") + @patch("posthog.session_recordings.session_recording_api.SessionRecordingListFromFilters") + @patch("posthog.session_recordings.session_recording_api.list_recordings_from_query") + def test_console_log_filters_are_correctly_passed_to_listing_when_query_is_used( + self, mock_query_lister, mock_summary_lister, mock_capture + ): + mock_summary_lister.return_value.run.return_value = ([], False) + mock_query_lister.return_value = ([], False) + + params_string = urlencode( + { + "console_log_filters": '[{"key": "console_log_level", "value": ["warn", "error"], "operator": "exact", "type": "log_entry"}]', + "user_modified_filters": '{"my_filter": "something"}', + "as_query": True, + } + ) + self.client.get(f"/api/projects/{self.team.id}/session_recordings?{params_string}") + + assert len(mock_summary_lister.call_args_list) == 0 + assert len(mock_query_lister.call_args_list) == 1 + query_passed_to_mock: RecordingsQuery = mock_query_lister.call_args_list[0][0][0] + maybe_the_filter = ( + query_passed_to_mock.console_log_filters[0] if query_passed_to_mock.console_log_filters else None + ) + assert maybe_the_filter is not None + console_filter = cast(LogEntryPropertyFilter, maybe_the_filter) + assert console_filter.value == ["warn", "error"] + assert mock_capture.call_args_list[0] == call( + self.user.distinct_id, + "recording list filters changed", + properties={ + "$current_url": ANY, + "$session_id": ANY, + "partial_filter_chosen_my_filter": "something", + }, + groups=ANY, + ) + @snapshot_postgres_queries def test_listing_recordings_is_not_nplus1_for_persons(self): with freeze_time("2022-06-03T12:00:00.000Z"): diff --git a/posthog/settings/temporal.py b/posthog/settings/temporal.py index 34450437c6dcd..33daed600cebf 100644 --- a/posthog/settings/temporal.py +++ b/posthog/settings/temporal.py @@ -16,9 +16,15 @@ MAX_CONCURRENT_ACTIVITIES: int | None = get_from_env("MAX_CONCURRENT_ACTIVITIES", None, optional=True, type_cast=int) BATCH_EXPORT_S3_UPLOAD_CHUNK_SIZE_BYTES: int = 1024 * 1024 * 50 # 50MB +BATCH_EXPORT_S3_RECORD_BATCH_QUEUE_MAX_SIZE_BYTES: int = get_from_env( + "BATCH_EXPORT_S3_RECORD_BATCH_QUEUE_MAX_SIZE_BYTES", 0, type_cast=int +) BATCH_EXPORT_SNOWFLAKE_UPLOAD_CHUNK_SIZE_BYTES: int = 1024 * 1024 * 100 # 100MB BATCH_EXPORT_POSTGRES_UPLOAD_CHUNK_SIZE_BYTES: int = 1024 * 1024 * 50 # 50MB BATCH_EXPORT_BIGQUERY_UPLOAD_CHUNK_SIZE_BYTES: int = 1024 * 1024 * 100 # 100MB +BATCH_EXPORT_BIGQUERY_RECORD_BATCH_QUEUE_MAX_SIZE_BYTES: int = get_from_env( + "BATCH_EXPORT_BIGQUERY_RECORD_BATCH_QUEUE_MAX_SIZE_BYTES", 0, type_cast=int +) BATCH_EXPORT_HTTP_UPLOAD_CHUNK_SIZE_BYTES: int = 1024 * 1024 * 50 # 50MB BATCH_EXPORT_HTTP_BATCH_SIZE: int = 5000 BATCH_EXPORT_BUFFER_QUEUE_MAX_SIZE_BYTES: int = 1024 * 1024 * 300 # 300MB diff --git a/posthog/tasks/exports/ordered_csv_renderer.py b/posthog/tasks/exports/ordered_csv_renderer.py index 5b70e9bed911c..4e5ed7eddc78d 100644 --- a/posthog/tasks/exports/ordered_csv_renderer.py +++ b/posthog/tasks/exports/ordered_csv_renderer.py @@ -18,7 +18,7 @@ def tablize(self, data: Any, header: Any = None, labels: Any = None) -> Generato header = data.header if not data: - return [] + return # First, flatten the data (i.e., convert it to a list of # dictionaries that are each exactly one level deep). The key for diff --git a/posthog/tasks/test/__snapshots__/test_usage_report.ambr b/posthog/tasks/test/__snapshots__/test_usage_report.ambr index 83f4787642836..2230c532da5ca 100644 --- a/posthog/tasks/test/__snapshots__/test_usage_report.ambr +++ b/posthog/tasks/test/__snapshots__/test_usage_report.ambr @@ -3,7 +3,7 @@ ''' SELECT team_id, - multiIf(event LIKE 'helicone%', 'helicone_events', event LIKE 'langfuse%', 'langfuse_events', event LIKE 'keywords_ai%', 'keywords_ai_events', event LIKE 'traceloop%', 'traceloop_events', JSONExtractString(properties, '$lib') = 'web', 'web_events', JSONExtractString(properties, '$lib') = 'posthog-js-lite', 'web_lite_events', JSONExtractString(properties, '$lib') = 'posthog-node', 'node_events', JSONExtractString(properties, '$lib') = 'posthog-android', 'android_events', JSONExtractString(properties, '$lib') = 'posthog-flutter', 'flutter_events', JSONExtractString(properties, '$lib') = 'posthog-ios', 'ios_events', JSONExtractString(properties, '$lib') = 'posthog-go', 'go_events', JSONExtractString(properties, '$lib') = 'posthog-java', 'java_events', JSONExtractString(properties, '$lib') = 'posthog-react-native', 'react_native_events', JSONExtractString(properties, '$lib') = 'posthog-ruby', 'ruby_events', JSONExtractString(properties, '$lib') = 'posthog-python', 'python_events', JSONExtractString(properties, '$lib') = 'posthog-php', 'php_events', 'other') AS metric, + multiIf(event LIKE 'helicone%', 'helicone_events', event LIKE 'langfuse%', 'langfuse_events', event LIKE 'keywords_ai%', 'keywords_ai_events', event LIKE 'traceloop%', 'traceloop_events', JSONExtractString(properties, '$lib') = 'web', 'web_events', JSONExtractString(properties, '$lib') = 'js', 'web_lite_events', JSONExtractString(properties, '$lib') = 'posthog-node', 'node_events', JSONExtractString(properties, '$lib') = 'posthog-android', 'android_events', JSONExtractString(properties, '$lib') = 'posthog-flutter', 'flutter_events', JSONExtractString(properties, '$lib') = 'posthog-ios', 'ios_events', JSONExtractString(properties, '$lib') = 'posthog-go', 'go_events', JSONExtractString(properties, '$lib') = 'posthog-java', 'java_events', JSONExtractString(properties, '$lib') = 'posthog-react-native', 'react_native_events', JSONExtractString(properties, '$lib') = 'posthog-ruby', 'ruby_events', JSONExtractString(properties, '$lib') = 'posthog-python', 'python_events', JSONExtractString(properties, '$lib') = 'posthog-php', 'php_events', 'other') AS metric, count(1) as count FROM events WHERE timestamp BETWEEN '2022-01-10 00:00:00' AND '2022-01-10 23:59:59' diff --git a/posthog/tasks/test/test_usage_report.py b/posthog/tasks/test/test_usage_report.py index 96293722e7fae..ca5a46af1f914 100644 --- a/posthog/tasks/test/test_usage_report.py +++ b/posthog/tasks/test/test_usage_report.py @@ -353,7 +353,7 @@ def _create_sample_usage_data(self) -> None: # Add events for each SDK sdks = [ "web", - "posthog-js-lite", + "js", "posthog-node", "posthog-android", "posthog-flutter", diff --git a/posthog/tasks/usage_report.py b/posthog/tasks/usage_report.py index 4b72ce933ab46..9bd8619317fa6 100644 --- a/posthog/tasks/usage_report.py +++ b/posthog/tasks/usage_report.py @@ -473,7 +473,7 @@ def get_all_event_metrics_in_period(begin: datetime, end: datetime) -> dict[str, event LIKE 'keywords_ai%%', 'keywords_ai_events', event LIKE 'traceloop%%', 'traceloop_events', {lib_expression} = 'web', 'web_events', - {lib_expression} = 'posthog-js-lite', 'web_lite_events', + {lib_expression} = 'js', 'web_lite_events', {lib_expression} = 'posthog-node', 'node_events', {lib_expression} = 'posthog-android', 'android_events', {lib_expression} = 'posthog-flutter', 'flutter_events', diff --git a/posthog/temporal/batch_exports/bigquery_batch_export.py b/posthog/temporal/batch_exports/bigquery_batch_export.py index e99ba77f3c1bf..ae5a7f58733c9 100644 --- a/posthog/temporal/batch_exports/bigquery_batch_export.py +++ b/posthog/temporal/batch_exports/bigquery_batch_export.py @@ -3,9 +3,7 @@ import contextlib import dataclasses import datetime as dt -import functools import json -import operator import pyarrow as pa import structlog @@ -30,28 +28,26 @@ default_fields, execute_batch_export_insert_activity, get_data_interval, - raise_on_produce_task_failure, start_batch_export_run, - start_produce_batch_export_record_batches, ) from posthog.temporal.batch_exports.heartbeat import ( BatchExportRangeHeartbeatDetails, DateRange, should_resume_from_activity_heartbeat, ) -from posthog.temporal.batch_exports.metrics import ( - get_bytes_exported_metric, - get_rows_exported_metric, +from posthog.temporal.batch_exports.spmc import ( + Consumer, + Producer, + RecordBatchQueue, + run_consumer_loop, + wait_for_schema_or_producer, ) from posthog.temporal.batch_exports.temporary_file import ( - BatchExportWriter, - FlushCallable, - JSONLBatchExportWriter, - ParquetBatchExportWriter, + BatchExportTemporaryFile, + WriterFormat, ) from posthog.temporal.batch_exports.utils import ( JsonType, - cast_record_batch_json_columns, set_status_to_running_task, ) from posthog.temporal.common.clickhouse import get_client @@ -60,6 +56,20 @@ logger = structlog.get_logger() +NON_RETRYABLE_ERROR_TYPES = [ + # Raised on missing permissions. + "Forbidden", + # Invalid token. + "RefreshError", + # Usually means the dataset or project doesn't exist. + "NotFound", + # Raised when something about dataset is wrong (not alphanumeric, too long, etc). + "BadRequest", + # Raised when table_id isn't valid. Sadly, `ValueError` is rather generic, but we + # don't anticipate a `ValueError` thrown from our own export code. + "ValueError", +] + def get_bigquery_fields_from_record_schema( record_schema: pa.Schema, known_json_columns: list[str] @@ -346,6 +356,50 @@ def bigquery_default_fields() -> list[BatchExportField]: return batch_export_fields +class BigQueryConsumer(Consumer): + """Implementation of a SPMC pipeline Consumer for BigQuery batch exports.""" + + def __init__( + self, + heartbeater: Heartbeater, + heartbeat_details: BigQueryHeartbeatDetails, + data_interval_start: dt.datetime | str | None, + bigquery_client: BigQueryClient, + bigquery_table: bigquery.Table, + table_schema: list[BatchExportField], + ): + super().__init__(heartbeater, heartbeat_details, data_interval_start) + self.bigquery_client = bigquery_client + self.bigquery_table = bigquery_table + self.table_schema = table_schema + + async def flush( + self, + batch_export_file: BatchExportTemporaryFile, + records_since_last_flush: int, + bytes_since_last_flush: int, + flush_counter: int, + last_date_range: DateRange, + is_last: bool, + error: Exception | None, + ): + """Implement flushing by loading batch export files to BigQuery""" + await self.logger.adebug( + "Loading %s records of size %s bytes to BigQuery table '%s'", + records_since_last_flush, + bytes_since_last_flush, + self.bigquery_table, + ) + + await self.bigquery_client.load_jsonl_file(batch_export_file, self.bigquery_table, self.table_schema) + + await self.logger.adebug("Loaded %s to BigQuery table '%s'", records_since_last_flush, self.bigquery_table) + self.rows_exported_counter.add(records_since_last_flush) + self.bytes_exported_counter.add(bytes_since_last_flush) + + self.heartbeat_details.track_done_range(last_date_range, self.data_interval_start) + + @activity.defn async def insert_into_bigquery_activity(inputs: BigQueryInsertInputs) -> RecordsCompleted: """Activity streams data from ClickHouse to BigQuery.""" @@ -399,43 +453,38 @@ async def insert_into_bigquery_activity(inputs: BigQueryInsertInputs) -> Records ) data_interval_end = dt.datetime.fromisoformat(inputs.data_interval_end) full_range = (data_interval_start, data_interval_end) - queue, produce_task = start_produce_batch_export_record_batches( - client=client, + + queue = RecordBatchQueue(max_size_bytes=settings.BATCH_EXPORT_BIGQUERY_RECORD_BATCH_QUEUE_MAX_SIZE_BYTES) + producer = Producer(clickhouse_client=client) + producer_task = producer.start( + queue=queue, model_name=model_name, is_backfill=inputs.is_backfill, team_id=inputs.team_id, full_range=full_range, done_ranges=done_ranges, - exclude_events=inputs.exclude_events, - include_events=inputs.include_events, fields=fields, destination_default_fields=bigquery_default_fields(), use_latest_schema=True, + exclude_events=inputs.exclude_events, + include_events=inputs.include_events, extra_query_parameters=extra_query_parameters, ) - - get_schema_task = asyncio.create_task(queue.get_schema()) - - await asyncio.wait( - [get_schema_task, produce_task], - return_when=asyncio.FIRST_COMPLETED, + records_completed = 0 + + record_batch_schema = await wait_for_schema_or_producer(queue, producer_task) + if record_batch_schema is None: + return records_completed + + record_batch_schema = pa.schema( + # NOTE: For some reason, some batches set non-nullable fields as non-nullable, whereas other + # record batches have them as nullable. + # Until we figure it out, we set all fields to nullable. There are some fields we know + # are not nullable, but I'm opting for the more flexible option until we out why schemas differ + # between batches. + [field.with_nullable(True) for field in record_batch_schema if field.name != "_inserted_at"] ) - # Finishing producing happens sequentially after putting to queue and setting the schema. - # So, either we finished producing and setting the schema tasks, or we finished without - # putting anything in the queue. - if get_schema_task.done(): - # In the first case, we'll land here. - # The schema is available, and the queue is not empty, so we can start the batch export. - record_batch_schema = get_schema_task.result() - else: - # In the second case, we'll land here: We finished producing without putting anything. - # Since we finished producing with an empty queue, there is nothing to batch export. - # We could have also failed, so we need to re-raise that exception to allow a retry if - # that's the case. - await raise_on_produce_task_failure(produce_task) - return 0 - if inputs.use_json_type is True: json_type = "JSON" json_columns = ["properties", "set", "set_once", "person_properties"] @@ -461,9 +510,6 @@ async def insert_into_bigquery_activity(inputs: BigQueryInsertInputs) -> Records else: schema = get_bigquery_fields_from_record_schema(record_batch_schema, known_json_columns=json_columns) - rows_exported = get_rows_exported_metric() - bytes_exported = get_bytes_exported_metric() - # TODO: Expose this as a configuration parameter # Currently, only allow merging persons model, as it's required. # Although all exports could potentially benefit from merging, merging can have an impact on cost, @@ -492,62 +538,23 @@ async def insert_into_bigquery_activity(inputs: BigQueryInsertInputs) -> Records delete=requires_merge, ) as bigquery_stage_table, ): - - async def flush_to_bigquery( - local_results_file, - records_since_last_flush: int, - bytes_since_last_flush: int, - flush_counter: int, - last_date_range, - last: bool, - error: Exception | None, - ): - table = bigquery_stage_table if requires_merge else bigquery_table - await logger.adebug( - "Loading %s records of size %s bytes to BigQuery table '%s'", - records_since_last_flush, - bytes_since_last_flush, - table, - ) - - await bq_client.load_jsonl_file(local_results_file, table, schema) - - await logger.adebug("Loading to BigQuery table '%s' finished", table) - rows_exported.add(records_since_last_flush) - bytes_exported.add(bytes_since_last_flush) - - details.track_done_range(last_date_range, data_interval_start) - heartbeater.set_from_heartbeat_details(details) - - flush_tasks = [] - while not queue.empty() or not produce_task.done(): - await logger.adebug("Starting record batch writer") - flush_start_event = asyncio.Event() - task = asyncio.create_task( - consume_batch_export_record_batches( - queue, - produce_task, - flush_start_event, - flush_to_bigquery, - json_columns, - settings.BATCH_EXPORT_BIGQUERY_UPLOAD_CHUNK_SIZE_BYTES, - ) - ) - - await flush_start_event.wait() - - flush_tasks.append(task) - - await logger.adebug("Finished producing, now waiting on any pending flush tasks") - await asyncio.wait(flush_tasks) - - await raise_on_produce_task_failure(produce_task) - await logger.adebug("Successfully consumed all record batches") - - details.complete_done_ranges(inputs.data_interval_end) - heartbeater.set_from_heartbeat_details(details) - - records_total = functools.reduce(operator.add, (task.result() for task in flush_tasks)) + records_completed = await run_consumer_loop( + queue=queue, + consumer_cls=BigQueryConsumer, + producer_task=producer_task, + heartbeater=heartbeater, + heartbeat_details=details, + data_interval_end=data_interval_end, + data_interval_start=data_interval_start, + schema=record_batch_schema, + writer_format=WriterFormat.JSONL, + max_bytes=settings.BATCH_EXPORT_BIGQUERY_UPLOAD_CHUNK_SIZE_BYTES, + non_retryable_error_types=NON_RETRYABLE_ERROR_TYPES, + json_columns=json_columns, + bigquery_client=bq_client, + bigquery_table=bigquery_stage_table if requires_merge else bigquery_table, + table_schema=schema, + ) if requires_merge: merge_key = ( @@ -560,98 +567,7 @@ async def flush_to_bigquery( merge_key=merge_key, ) - return records_total - - -async def consume_batch_export_record_batches( - queue: asyncio.Queue, - produce_task: asyncio.Task, - flush_start_event: asyncio.Event, - flush_to_bigquery: FlushCallable, - json_columns: list[str], - max_bytes: int, -): - """Consume batch export record batches from queue into a writing loop. - - Each record will be written to a temporary file, and flushed after - configured `max_bytes`. Flush is done on context manager exit by - `JSONLBatchExportWriter`. - - This coroutine reports when flushing will start by setting the - `flush_start_event`. This is used by the main thread to start a new writer - task as flushing is about to begin, since that can be too slow to do - sequentially. - - If there are not enough events to fill up `max_bytes`, the writing - loop will detect that there are no more events produced and shut itself off - by using the `done_event`, which should be set by the queue producer. - - Arguments: - queue: The queue we will be listening on for record batches. - produce_task: Producer task we check to be done if queue is empty, as - that would indicate we have finished reading record batches before - hitting the flush limit, so we have to break early. - flush_to_start_event: Event set by us when flushing is to about to - start. - json_columns: Used to cast columns of the record batch to JSON. - max_bytes: Max bytes to write before flushing. - - Returns: - Number of total records written and flushed in this task. - """ - writer = JSONLBatchExportWriter( - max_bytes=max_bytes, - flush_callable=flush_to_bigquery, - ) - - async with writer.open_temporary_file(): - await logger.adebug("Starting record batch writing loop") - while True: - try: - record_batch = queue.get_nowait() - except asyncio.QueueEmpty: - if produce_task.done(): - await logger.adebug("Empty queue with no more events being produced, closing writer loop") - flush_start_event.set() - # Exit context manager to trigger flush - break - else: - await asyncio.sleep(0.1) - continue - - record_batch = cast_record_batch_json_columns(record_batch, json_columns=json_columns) - await writer.write_record_batch(record_batch, flush=False) - - if writer.should_flush(): - await logger.adebug("Writer finished, ready to flush events") - flush_start_event.set() - # Exit context manager to trigger flush - break - - await logger.adebug("Completed %s records", writer.records_total) - return writer.records_total - - -def get_batch_export_writer( - inputs: BigQueryInsertInputs, flush_callable: FlushCallable, max_bytes: int, schema: pa.Schema | None = None -) -> BatchExportWriter: - """Return the `BatchExportWriter` corresponding to the inputs for this BigQuery batch export.""" - writer: BatchExportWriter - - if inputs.use_json_type is False: - # JSON field is not supported with Parquet - writer = ParquetBatchExportWriter( - max_bytes=max_bytes, - flush_callable=flush_callable, - schema=schema, - ) - else: - writer = JSONLBatchExportWriter( - max_bytes=settings.BATCH_EXPORT_BIGQUERY_UPLOAD_CHUNK_SIZE_BYTES, - flush_callable=flush_callable, - ) - - return writer + return records_completed @workflow.defn(name="bigquery-export", failure_exception_types=[workflow.NondeterminismError]) @@ -729,18 +645,6 @@ async def run(self, inputs: BigQueryBatchExportInputs): insert_into_bigquery_activity, insert_inputs, interval=inputs.interval, - non_retryable_error_types=[ - # Raised on missing permissions. - "Forbidden", - # Invalid token. - "RefreshError", - # Usually means the dataset or project doesn't exist. - "NotFound", - # Raised when something about dataset is wrong (not alphanumeric, too long, etc). - "BadRequest", - # Raised when table_id isn't valid. Sadly, `ValueError` is rather generic, but we - # don't anticipate a `ValueError` thrown from our own export code. - "ValueError", - ], + non_retryable_error_types=NON_RETRYABLE_ERROR_TYPES, finish_inputs=finish_inputs, ) diff --git a/posthog/temporal/batch_exports/redshift_batch_export.py b/posthog/temporal/batch_exports/redshift_batch_export.py index d9d634d78858c..3b02efddb5a0b 100644 --- a/posthog/temporal/batch_exports/redshift_batch_export.py +++ b/posthog/temporal/batch_exports/redshift_batch_export.py @@ -32,6 +32,11 @@ start_batch_export_run, start_produce_batch_export_record_batches, ) +from posthog.temporal.batch_exports.heartbeat import ( + BatchExportRangeHeartbeatDetails, + DateRange, + should_resume_from_activity_heartbeat, +) from posthog.temporal.batch_exports.metrics import get_rows_exported_metric from posthog.temporal.batch_exports.postgres_batch_export import ( Fields, @@ -47,11 +52,6 @@ from posthog.temporal.common.clickhouse import get_client from posthog.temporal.common.heartbeat import Heartbeater from posthog.temporal.common.logger import configure_temporal_worker_logger -from posthog.temporal.batch_exports.heartbeat import ( - BatchExportRangeHeartbeatDetails, - DateRange, - should_resume_from_activity_heartbeat, -) def remove_escaped_whitespace_recursive(value): @@ -715,6 +715,8 @@ async def run(self, inputs: RedshiftBatchExportInputs): "StringDataRightTruncation", # Raised by our PostgreSQL client when failing to connect after several attempts. "PostgreSQLConnectionError", + # Column missing in Redshift, likely the schema was altered. + "UndefinedColumn", ], finish_inputs=finish_inputs, ) diff --git a/posthog/temporal/batch_exports/s3_batch_export.py b/posthog/temporal/batch_exports/s3_batch_export.py index 41383c8e17114..7201af91d2b5a 100644 --- a/posthog/temporal/batch_exports/s3_batch_export.py +++ b/posthog/temporal/batch_exports/s3_batch_export.py @@ -1,12 +1,13 @@ import asyncio +import collections.abc import contextlib import dataclasses import datetime as dt import io import json +import operator import posixpath import typing -import collections.abc import aioboto3 import botocore.exceptions @@ -30,36 +31,62 @@ default_fields, execute_batch_export_insert_activity, get_data_interval, - iter_model_records, start_batch_export_run, wait_for_delta_past_data_interval_end, ) -from posthog.temporal.batch_exports.metrics import ( - get_bytes_exported_metric, - get_rows_exported_metric, +from posthog.temporal.batch_exports.heartbeat import ( + BatchExportRangeHeartbeatDetails, + DateRange, + HeartbeatParseError, + should_resume_from_activity_heartbeat, +) +from posthog.temporal.batch_exports.spmc import ( + Consumer, + Producer, + RecordBatchQueue, + run_consumer_loop, + wait_for_schema_or_producer, ) from posthog.temporal.batch_exports.temporary_file import ( BatchExportTemporaryFile, - BatchExportWriter, - FlushCallable, - JSONLBatchExportWriter, - ParquetBatchExportWriter, - UnsupportedFileFormatError, + WriterFormat, ) from posthog.temporal.batch_exports.utils import ( - apeek_first_and_rewind, - cast_record_batch_json_columns, set_status_to_running_task, ) from posthog.temporal.common.clickhouse import get_client from posthog.temporal.common.heartbeat import Heartbeater from posthog.temporal.common.logger import bind_temporal_worker_logger -from posthog.temporal.batch_exports.heartbeat import ( - BatchExportRangeHeartbeatDetails, - DateRange, - HeartbeatParseError, - should_resume_from_activity_heartbeat, -) + +NON_RETRYABLE_ERROR_TYPES = [ + # S3 parameter validation failed. + "ParamValidationError", + # This error usually indicates credentials are incorrect or permissions are missing. + "ClientError", + # An S3 bucket doesn't exist. + "NoSuchBucket", + # Couldn't connect to custom S3 endpoint + "EndpointConnectionError", + # Input contained an empty S3 endpoint URL + "EmptyS3EndpointURLError", + # User provided an invalid S3 key + "InvalidS3Key", + # All consumers failed with non-retryable errors. + "RecordBatchConsumerNonRetryableExceptionGroup", +] + +FILE_FORMAT_EXTENSIONS = { + "Parquet": "parquet", + "JSONLines": "jsonl", +} + +COMPRESSION_EXTENSIONS = { + "gzip": "gz", + "snappy": "sz", + "brotli": "br", + "ztsd": "zst", + "lz4": "lz4", +} def get_allowed_template_variables(inputs) -> dict[str, str]: @@ -78,20 +105,6 @@ def get_allowed_template_variables(inputs) -> dict[str, str]: } -FILE_FORMAT_EXTENSIONS = { - "Parquet": "parquet", - "JSONLines": "jsonl", -} - -COMPRESSION_EXTENSIONS = { - "gzip": "gz", - "snappy": "sz", - "brotli": "br", - "ztsd": "zst", - "lz4": "lz4", -} - - def get_s3_key(inputs) -> str: """Return an S3 key given S3InsertInputs.""" template_variables = get_allowed_template_variables(inputs) @@ -199,6 +212,7 @@ def __init__( self.kms_key_id = kms_key_id self.upload_id: str | None = None self.parts: list[Part] = [] + self.pending_parts: list[Part] = [] if self.endpoint_url == "": raise EmptyS3EndpointURLError() @@ -214,7 +228,7 @@ def to_state(self) -> S3MultiPartUploadState: @property def part_number(self): """Return the current part number.""" - return len(self.parts) + return len(self.parts) + len(self.pending_parts) def is_upload_in_progress(self) -> bool: """Whether this S3MultiPartUpload is in progress or not.""" @@ -272,12 +286,13 @@ async def complete(self) -> str: if self.is_upload_in_progress() is False: raise NoUploadInProgressError() + sorted_parts = sorted(self.parts, key=operator.itemgetter("PartNumber")) async with self.s3_client() as s3_client: response = await s3_client.complete_multipart_upload( Bucket=self.bucket_name, Key=self.key, UploadId=self.upload_id, - MultipartUpload={"Parts": self.parts}, + MultipartUpload={"Parts": sorted_parts}, ) self.upload_id = None @@ -311,6 +326,8 @@ async def upload_part( ): """Upload a part of this multi-part upload.""" next_part_number = self.part_number + 1 + part = {"PartNumber": next_part_number, "ETag": ""} + self.pending_parts.append(part) if rewind is True: body.rewind() @@ -335,7 +352,9 @@ async def upload_part( finally: reader.detach() # BufferedReader closes the file otherwise. - self.parts.append({"PartNumber": next_part_number, "ETag": etag}) + self.pending_parts.pop(self.pending_parts.index(part)) + part["ETag"] = etag + self.parts.append(part) async def upload_part_retryable( self, @@ -441,6 +460,52 @@ def append_upload_state(self, upload_state: S3MultiPartUploadState): self.upload_state.parts.append(part) +class S3Consumer(Consumer): + def __init__( + self, + heartbeater: Heartbeater, + heartbeat_details: S3HeartbeatDetails, + data_interval_start: dt.datetime | str | None, + s3_upload: S3MultiPartUpload, + ): + super().__init__(heartbeater, heartbeat_details, data_interval_start) + self.heartbeat_details: S3HeartbeatDetails = heartbeat_details + self.s3_upload = s3_upload + + async def flush( + self, + batch_export_file: BatchExportTemporaryFile, + records_since_last_flush: int, + bytes_since_last_flush: int, + flush_counter: int, + last_date_range: DateRange, + is_last: bool, + error: Exception | None, + ): + if error is not None: + await self.logger.adebug("Error while writing part %d", self.s3_upload.part_number + 1, exc_info=error) + await self.logger.awarning( + "An error was detected while writing part %d. Partial part will not be uploaded in case it can be retried.", + self.s3_upload.part_number + 1, + ) + return + + await self.logger.adebug( + "Uploading part %s containing %s records with size %s bytes", + self.s3_upload.part_number + 1, + records_since_last_flush, + bytes_since_last_flush, + ) + + await self.s3_upload.upload_part(batch_export_file) + + self.rows_exported_counter.add(records_since_last_flush) + self.bytes_exported_counter.add(bytes_since_last_flush) + + self.heartbeat_details.track_done_range(last_date_range, self.data_interval_start) + self.heartbeat_details.append_upload_state(self.s3_upload.to_state()) + + @dataclasses.dataclass class S3InsertInputs: """Inputs for S3 exports.""" @@ -576,143 +641,85 @@ async def insert_into_s3_activity(inputs: S3InsertInputs) -> RecordsCompleted: raise ConnectionError("Cannot establish connection to ClickHouse") s3_upload, details = await initialize_and_resume_multipart_upload(inputs) - - # TODO: Switch to single-producer multiple consumer done_ranges: list[DateRange] = details.done_ranges - if done_ranges: - data_interval_start: str | None = done_ranges[-1][1].isoformat() - else: - data_interval_start = inputs.data_interval_start model: BatchExportModel | BatchExportSchema | None = None if inputs.batch_export_schema is None and "batch_export_model" in { field.name for field in dataclasses.fields(inputs) }: model = inputs.batch_export_model + if model is not None: + model_name = model.name + extra_query_parameters = model.schema["values"] if model.schema is not None else None + fields = model.schema["fields"] if model.schema is not None else None + else: + model_name = "events" + extra_query_parameters = None + fields = None else: model = inputs.batch_export_schema + model_name = "custom" + extra_query_parameters = model["values"] if model is not None else {} + fields = model["fields"] if model is not None else None - record_iterator = iter_model_records( - model=model, - client=client, + data_interval_start = ( + dt.datetime.fromisoformat(inputs.data_interval_start) if inputs.data_interval_start else None + ) + data_interval_end = dt.datetime.fromisoformat(inputs.data_interval_end) + full_range = (data_interval_start, data_interval_end) + + queue = RecordBatchQueue(max_size_bytes=settings.BATCH_EXPORT_S3_RECORD_BATCH_QUEUE_MAX_SIZE_BYTES) + producer = Producer(clickhouse_client=client) + producer_task = producer.start( + queue=queue, + model_name=model_name, + is_backfill=inputs.is_backfill, team_id=inputs.team_id, - interval_start=data_interval_start, - interval_end=inputs.data_interval_end, + full_range=full_range, + done_ranges=done_ranges, + fields=fields, + destination_default_fields=s3_default_fields(), exclude_events=inputs.exclude_events, include_events=inputs.include_events, - is_backfill=inputs.is_backfill, - destination_default_fields=s3_default_fields(), + extra_query_parameters=extra_query_parameters, ) - - first_record_batch, record_iterator = await apeek_first_and_rewind(record_iterator) - records_completed = 0 - if first_record_batch is None: - return records_completed - async with s3_upload as s3_upload: + record_batch_schema = await wait_for_schema_or_producer(queue, producer_task) + if record_batch_schema is None: + return records_completed - async def flush_to_s3( - local_results_file, - records_since_last_flush: int, - bytes_since_last_flush: int, - flush_counter: int, - last_date_range: DateRange, - last: bool, - error: Exception | None, - ): - if error is not None: - await logger.adebug("Error while writing part %d", s3_upload.part_number + 1, exc_info=error) - await logger.awarning( - "An error was detected while writing part %d. Partial part will not be uploaded in case it can be retried.", - s3_upload.part_number + 1, - ) - return - - await logger.adebug( - "Uploading %s part %s containing %s records with size %s bytes", - "last " if last else "", - s3_upload.part_number + 1, - records_since_last_flush, - bytes_since_last_flush, - ) - - await s3_upload.upload_part(local_results_file) - - rows_exported.add(records_since_last_flush) - bytes_exported.add(bytes_since_last_flush) - - details.track_done_range(last_date_range, data_interval_start) - details.append_upload_state(s3_upload.to_state()) - heartbeater.set_from_heartbeat_details(details) - - first_record_batch = cast_record_batch_json_columns(first_record_batch) - column_names = first_record_batch.column_names - column_names.pop(column_names.index("_inserted_at")) - - schema = pa.schema( - # NOTE: For some reason, some batches set non-nullable fields as non-nullable, whereas other - # record batches have them as nullable. - # Until we figure it out, we set all fields to nullable. There are some fields we know - # are not nullable, but I'm opting for the more flexible option until we out why schemas differ - # between batches. - [field.with_nullable(True) for field in first_record_batch.select(column_names).schema] - ) + record_batch_schema = pa.schema( + # NOTE: For some reason, some batches set non-nullable fields as non-nullable, whereas other + # record batches have them as nullable. + # Until we figure it out, we set all fields to nullable. There are some fields we know + # are not nullable, but I'm opting for the more flexible option until we out why schemas differ + # between batches. + [field.with_nullable(True) for field in record_batch_schema if field.name != "_inserted_at"] + ) - writer = get_batch_export_writer( - inputs, - flush_callable=flush_to_s3, + async with s3_upload as s3_upload: + records_completed = await run_consumer_loop( + queue=queue, + consumer_cls=S3Consumer, + producer_task=producer_task, + heartbeater=heartbeater, + heartbeat_details=details, + data_interval_end=data_interval_end, + data_interval_start=data_interval_start, + schema=record_batch_schema, + writer_format=WriterFormat.from_str(inputs.file_format, "S3"), max_bytes=settings.BATCH_EXPORT_S3_UPLOAD_CHUNK_SIZE_BYTES, - schema=schema, + s3_upload=s3_upload, + writer_file_kwargs={"compression": inputs.compression}, + non_retryable_error_types=NON_RETRYABLE_ERROR_TYPES, ) - async with writer.open_temporary_file(): - rows_exported = get_rows_exported_metric() - bytes_exported = get_bytes_exported_metric() - - async for record_batch in record_iterator: - record_batch = cast_record_batch_json_columns(record_batch) - - await writer.write_record_batch(record_batch) - - details.complete_done_ranges(inputs.data_interval_end) - heartbeater.set_from_heartbeat_details(details) - - records_completed = writer.records_total await s3_upload.complete() return records_completed -def get_batch_export_writer( - inputs: S3InsertInputs, flush_callable: FlushCallable, max_bytes: int, schema: pa.Schema | None = None -) -> BatchExportWriter: - """Return the `BatchExportWriter` corresponding to configured `file_format`. - - Raises: - UnsupportedFileFormatError: If no writer exists for given `file_format`. - """ - writer: BatchExportWriter - - if inputs.file_format == "Parquet": - writer = ParquetBatchExportWriter( - max_bytes=max_bytes, - flush_callable=flush_callable, - compression=inputs.compression, - schema=schema, - ) - elif inputs.file_format == "JSONLines": - writer = JSONLBatchExportWriter( - max_bytes=max_bytes, - flush_callable=flush_callable, - compression=inputs.compression, - ) - else: - raise UnsupportedFileFormatError(inputs.file_format, "S3") - - return writer - - @workflow.defn(name="s3-export", failure_exception_types=[workflow.NondeterminismError]) class S3BatchExportWorkflow(PostHogWorkflow): """A Temporal Workflow to export ClickHouse data into S3. @@ -789,19 +796,6 @@ async def run(self, inputs: S3BatchExportInputs): insert_into_s3_activity, insert_inputs, interval=inputs.interval, - non_retryable_error_types=[ - # S3 parameter validation failed. - "ParamValidationError", - # This error usually indicates credentials are incorrect or permissions are missing. - "ClientError", - # An S3 bucket doesn't exist. - "NoSuchBucket", - # Couldn't connect to custom S3 endpoint - "EndpointConnectionError", - # Input contained an empty S3 endpoint URL - "EmptyS3EndpointURLError", - # User provided an invalid S3 key - "InvalidS3Key", - ], + non_retryable_error_types=NON_RETRYABLE_ERROR_TYPES, finish_inputs=finish_inputs, ) diff --git a/posthog/temporal/batch_exports/spmc.py b/posthog/temporal/batch_exports/spmc.py new file mode 100644 index 0000000000000..34a503646a3e4 --- /dev/null +++ b/posthog/temporal/batch_exports/spmc.py @@ -0,0 +1,631 @@ +import abc +import asyncio +import collections.abc +import datetime as dt +import operator +import typing +import uuid + +import pyarrow as pa +import structlog +import temporalio.common +from django.conf import settings + +from posthog.temporal.batch_exports.heartbeat import BatchExportRangeHeartbeatDetails +from posthog.temporal.batch_exports.metrics import get_bytes_exported_metric, get_rows_exported_metric +from posthog.temporal.batch_exports.sql import ( + SELECT_FROM_EVENTS_VIEW, + SELECT_FROM_EVENTS_VIEW_BACKFILL, + SELECT_FROM_EVENTS_VIEW_RECENT, + SELECT_FROM_EVENTS_VIEW_UNBOUNDED, + SELECT_FROM_PERSONS_VIEW, + SELECT_FROM_PERSONS_VIEW_BACKFILL, + SELECT_FROM_PERSONS_VIEW_BACKFILL_NEW, + SELECT_FROM_PERSONS_VIEW_NEW, +) +from posthog.temporal.batch_exports.temporary_file import ( + BatchExportTemporaryFile, + BytesSinceLastFlush, + DateRange, + FlushCounter, + IsLast, + RecordsSinceLastFlush, + WriterFormat, + get_batch_export_writer, +) +from posthog.temporal.batch_exports.utils import ( + cast_record_batch_json_columns, + cast_record_batch_schema_json_columns, +) +from posthog.temporal.common.clickhouse import ClickHouseClient +from posthog.temporal.common.heartbeat import Heartbeater + +logger = structlog.get_logger() + + +class RecordBatchQueue(asyncio.Queue): + """A queue of pyarrow RecordBatch instances limited by bytes.""" + + def __init__(self, max_size_bytes: int = 0) -> None: + super().__init__(maxsize=max_size_bytes) + self._bytes_size = 0 + self._schema_set = asyncio.Event() + self.record_batch_schema = None + # This is set by `asyncio.Queue.__init__` calling `_init` + self._queue: collections.deque + + def _get(self) -> pa.RecordBatch: + """Override parent `_get` to keep track of bytes.""" + item = self._queue.popleft() + self._bytes_size -= item.get_total_buffer_size() + return item + + def _put(self, item: pa.RecordBatch) -> None: + """Override parent `_put` to keep track of bytes.""" + self._bytes_size += item.get_total_buffer_size() + + if not self._schema_set.is_set(): + self.set_schema(item) + + self._queue.append(item) + + def set_schema(self, record_batch: pa.RecordBatch) -> None: + """Used to keep track of schema of events in queue.""" + self.record_batch_schema = record_batch.schema + self._schema_set.set() + + async def get_schema(self) -> pa.Schema: + """Return the schema of events in queue. + + Currently, this is not enforced. It's purely for reporting to users of + the queue what do the record batches look like. It's up to the producer + to ensure all record batches have the same schema. + """ + await self._schema_set.wait() + return self.record_batch_schema + + def qsize(self) -> int: + """Size in bytes of record batches in the queue. + + This is used to determine when the queue is full, so it returns the + number of bytes. + """ + return self._bytes_size + + +class TaskNotDoneError(Exception): + """Raised when a task that should be done, isn't.""" + + def __init__(self, task: str): + super().__init__(f"Expected task '{task}' to be done by now") + + +class RecordBatchTaskError(Exception): + """Raised when an error occurs during consumption of record batches.""" + + def __init__(self): + super().__init__("The record batch consumer encountered an error during execution") + + +async def raise_on_task_failure(task: asyncio.Task) -> None: + """Raise `RecordBatchProducerError` if a producer task failed. + + We will also raise a `TaskNotDone` if the producer is not done, as this + should only be called after producer is done to check its exception. + """ + if not task.done(): + raise TaskNotDoneError(task.get_name()) + + if task.exception() is None: + return + + exc = task.exception() + await logger.aexception("%s task failed", task.get_name(), exc_info=exc) + raise RecordBatchTaskError() from exc + + +async def wait_for_schema_or_producer(queue: RecordBatchQueue, producer_task: asyncio.Task) -> pa.Schema | None: + """Wait for a queue schema to be set or a producer to finish. + + If the queue's schema is set first, we will return that, otherwise we return + `None`. + + A queue's schema will be set sequentially on the first record batch produced. + So, after waiting for both tasks, either we finished setting the schema and + have partially or fully produced record batches, or we finished without putting + anything in the queue, and the queue's schema has not been set. + """ + record_batch_schema = None + + get_schema_task = asyncio.create_task(queue.get_schema()) + + await asyncio.wait( + [get_schema_task, producer_task], + return_when=asyncio.FIRST_COMPLETED, + ) + + if get_schema_task.done(): + # The schema is available, and the queue is not empty, so we can continue + # with the rest of the the batch export. + record_batch_schema = get_schema_task.result() + else: + # We finished producing without putting anything in the queue and there is + # nothing to batch export. We could have also failed, so we need to re-raise + # that exception to allow a retry if that's the case. If we don't fail, it + # is safe to finish the batch export early. + await raise_on_task_failure(producer_task) + + return record_batch_schema + + +class Consumer: + """Async consumer for batch exports. + + Attributes: + flush_start_event: Event set when this consumer's flush method starts. + heartbeater: A batch export's heartbeater used for tracking progress. + heartbeat_details: A batch export's heartbeat details passed to the + heartbeater used for tracking progress. + data_interval_start: The beginning of the batch export period. + logger: Provided consumer logger. + """ + + def __init__( + self, + heartbeater: Heartbeater, + heartbeat_details: BatchExportRangeHeartbeatDetails, + data_interval_start: dt.datetime | str | None, + ): + self.flush_start_event = asyncio.Event() + self.heartbeater = heartbeater + self.heartbeat_details = heartbeat_details + self.data_interval_start = data_interval_start + self.logger = logger + + @property + def rows_exported_counter(self) -> temporalio.common.MetricCounter: + """Access the rows exported metric counter.""" + return get_rows_exported_metric() + + @property + def bytes_exported_counter(self) -> temporalio.common.MetricCounter: + """Access the bytes exported metric counter.""" + return get_bytes_exported_metric() + + @abc.abstractmethod + async def flush( + self, + batch_export_file: BatchExportTemporaryFile, + records_since_last_flush: RecordsSinceLastFlush, + bytes_since_last_flush: BytesSinceLastFlush, + flush_counter: FlushCounter, + last_date_range: DateRange, + is_last: IsLast, + error: Exception | None, + ): + """Method called on reaching `max_bytes` when running the consumer. + + Each batch export should override this method with their own implementation + of flushing, as each destination will have different requirements for + flushing data. + + Arguments: + batch_export_file: The temporary file containing data to flush. + records_since_last_flush: How many records were written in the temporary + file. + bytes_since_last_flush: How many records were written in the temporary + file. + error: If any error occurs while writing the temporary file. + """ + pass + + async def start( + self, + queue: RecordBatchQueue, + producer_task: asyncio.Task, + writer_format: WriterFormat, + max_bytes: int, + schema: pa.Schema, + json_columns: collections.abc.Sequence[str], + **kwargs, + ) -> int: + """Start consuming record batches from queue. + + Record batches will be written to a temporary file defined by `writer_format` + and the file will be flushed upon reaching at least `max_bytes`. + + Returns: + Total number of records in all consumed record batches. + """ + await logger.adebug("Starting record batch consumer") + + schema = cast_record_batch_schema_json_columns(schema, json_columns=json_columns) + writer = get_batch_export_writer(writer_format, self.flush, schema=schema, max_bytes=max_bytes, **kwargs) + + record_batches_count = 0 + + async with writer.open_temporary_file(): + await self.logger.adebug("Starting record batch writing loop") + while True: + try: + record_batch = queue.get_nowait() + record_batches_count += 1 + except asyncio.QueueEmpty: + if producer_task.done(): + await self.logger.adebug( + "Empty queue with no more events being produced, closing writer loop and flushing" + ) + self.flush_start_event.set() + # Exit context manager to trigger flush + break + else: + await asyncio.sleep(0.1) + continue + + record_batch = cast_record_batch_json_columns(record_batch, json_columns=json_columns) + await writer.write_record_batch(record_batch, flush=False) + + if writer.should_flush(): + await self.logger.adebug("Writer finished, ready to flush events") + self.flush_start_event.set() + # Exit context manager to trigger flush + break + + for _ in range(record_batches_count): + queue.task_done() + + await self.logger.adebug("Consumed %s records", writer.records_total) + self.heartbeater.set_from_heartbeat_details(self.heartbeat_details) + return writer.records_total + + +class RecordBatchConsumerRetryableExceptionGroup(ExceptionGroup): + """ExceptionGroup raised when at least one task fails with a retryable exception.""" + + def derive(self, excs): + return RecordBatchConsumerRetryableExceptionGroup(self.message, excs) + + +class RecordBatchConsumerNonRetryableExceptionGroup(ExceptionGroup): + """ExceptionGroup raised when all tasks fail with non-retryable exception.""" + + def derive(self, excs): + return RecordBatchConsumerNonRetryableExceptionGroup(self.message, excs) + + +async def run_consumer_loop( + queue: RecordBatchQueue, + consumer_cls: type[Consumer], + producer_task: asyncio.Task, + heartbeater: Heartbeater, + heartbeat_details: BatchExportRangeHeartbeatDetails, + data_interval_end: dt.datetime | str, + data_interval_start: dt.datetime | str | None, + schema: pa.Schema, + writer_format: WriterFormat, + max_bytes: int, + json_columns: collections.abc.Sequence[str] = ("properties", "person_properties", "set", "set_once"), + writer_file_kwargs: collections.abc.Mapping[str, typing.Any] | None = None, + non_retryable_error_types: collections.abc.Sequence[str] = (), + **kwargs, +) -> int: + """Run record batch consumers in a loop. + + When a consumer starts flushing, a new consumer will be started, and so on in + a loop. Once there is nothing left to consumer from the `RecordBatchQueue`, no + more consumers will be started, and any pending consumers are awaited. + + Returns: + Number of records exported. Not the number of record batches, but the + number of records in all record batches. + + Raises: + RecordBatchConsumerRetryableExceptionGroup: When at least one consumer task + fails with a retryable error. + RecordBatchConsumerNonRetryableExceptionGroup: When all consumer tasks fail + with non-retryable errors. + """ + consumer_tasks_pending: set[asyncio.Task] = set() + consumer_tasks_done = set() + consumer_number = 0 + records_completed = 0 + + def consumer_done_callback(task: asyncio.Task): + nonlocal records_completed + nonlocal consumer_tasks_done + nonlocal consumer_tasks_pending + + try: + records_completed += task.result() + except: + pass + + consumer_tasks_pending.remove(task) + consumer_tasks_done.add(task) + + await logger.adebug("Starting record batch consumer loop") + while not queue.empty() or not producer_task.done(): + consumer = consumer_cls(heartbeater, heartbeat_details, data_interval_start, **kwargs) + consumer_task = asyncio.create_task( + consumer.start( + queue=queue, + producer_task=producer_task, + writer_format=writer_format, + max_bytes=max_bytes, + schema=schema, + json_columns=json_columns, + **writer_file_kwargs or {}, + ), + name=f"record_batch_consumer_{consumer_number}", + ) + consumer_tasks_pending.add(consumer_task) + consumer_task.add_done_callback(consumer_done_callback) + consumer_number += 1 + + while not consumer.flush_start_event.is_set() and not consumer_task.done(): + # Block until we either start flushing or we are done consuming. + # Flush start should always happen first unless the consumer task fails. + await asyncio.sleep(0) + + if consumer_task.done(): + consumer_task_exception = consumer_task.exception() + + if consumer_task_exception is not None: + raise consumer_task_exception + + await logger.adebug("Finished producing, now waiting on any pending consumer tasks") + if consumer_tasks_pending: + await asyncio.wait(consumer_tasks_pending) + + retryable = [] + non_retryable = [] + for task in consumer_tasks_done: + try: + await raise_on_task_failure(task) + + except Exception as e: + # TODO: Handle exception types instead of checking for exception names. + # We are losing some precision by not handling exception types with + # `except`, but using a sequence of strings keeps us in line with + # Temporal. Not a good reason though, but right now we would need to + # search for a handful of exception types, so this is a quicker tradeoff + # as we already have the list of strings for each destination. + if e.__class__.__name__ in non_retryable_error_types: + await logger.aexception("Consumer task %s has failed with a non-retryable %s", task, e, exc_info=e) + non_retryable.append(e) + + else: + await logger.aexception("Consumer task %s has failed with a retryable %s", task, e, exc_info=e) + retryable.append(e) + + if retryable: + raise RecordBatchConsumerRetryableExceptionGroup( + "At least one unhandled retryable errors in a RecordBatch consumer TaskGroup", retryable + non_retryable + ) + elif non_retryable: + raise RecordBatchConsumerNonRetryableExceptionGroup( + "Unhandled non-retryable errors in a RecordBatch consumer TaskGroup", retryable + non_retryable + ) + + await raise_on_task_failure(producer_task) + await logger.adebug("Successfully consumed all record batches") + + heartbeat_details.complete_done_ranges(data_interval_end) + heartbeater.set_from_heartbeat_details(heartbeat_details) + + return records_completed + + +class BatchExportField(typing.TypedDict): + """A field to be queried from ClickHouse. + + Attributes: + expression: A ClickHouse SQL expression that declares the field required. + alias: An alias to apply to the expression (after an 'AS' keyword). + """ + + expression: str + alias: str + + +def default_fields() -> list[BatchExportField]: + """Return list of default batch export Fields.""" + return [ + BatchExportField(expression="uuid", alias="uuid"), + BatchExportField(expression="team_id", alias="team_id"), + BatchExportField(expression="timestamp", alias="timestamp"), + BatchExportField(expression="_inserted_at", alias="_inserted_at"), + BatchExportField(expression="created_at", alias="created_at"), + BatchExportField(expression="event", alias="event"), + BatchExportField(expression="properties", alias="properties"), + BatchExportField(expression="distinct_id", alias="distinct_id"), + BatchExportField(expression="set", alias="set"), + BatchExportField( + expression="set_once", + alias="set_once", + ), + ] + + +class Producer: + """Async producer for batch exports. + + Attributes: + clickhouse_client: ClickHouse client used to produce RecordBatches. + _task: Used to keep track of producer background task. + """ + + def __init__(self, clickhouse_client: ClickHouseClient): + self.clickhouse_client = clickhouse_client + self._task: asyncio.Task | None = None + + @property + def task(self) -> asyncio.Task: + if self._task is None: + raise ValueError("Producer task is not initialized, have you called `Producer.start()`?") + return self._task + + def start( + self, + queue: RecordBatchQueue, + model_name: str, + is_backfill: bool, + team_id: int, + full_range: tuple[dt.datetime | None, dt.datetime], + done_ranges: list[tuple[dt.datetime, dt.datetime]], + fields: list[BatchExportField] | None = None, + destination_default_fields: list[BatchExportField] | None = None, + use_latest_schema: bool = False, + **parameters, + ) -> asyncio.Task: + if fields is None: + if destination_default_fields is None: + fields = default_fields() + else: + fields = destination_default_fields + + if model_name == "persons": + if is_backfill and full_range[0] is None: + if use_latest_schema: + query = SELECT_FROM_PERSONS_VIEW_BACKFILL_NEW + else: + query = SELECT_FROM_PERSONS_VIEW_BACKFILL + else: + if use_latest_schema: + query = SELECT_FROM_PERSONS_VIEW_NEW + else: + query = SELECT_FROM_PERSONS_VIEW + else: + if parameters.get("exclude_events", None): + parameters["exclude_events"] = list(parameters["exclude_events"]) + else: + parameters["exclude_events"] = [] + + if parameters.get("include_events", None): + parameters["include_events"] = list(parameters["include_events"]) + else: + parameters["include_events"] = [] + + start_at, end_at = full_range + + if start_at: + is_5_min_batch_export = (end_at - start_at) == dt.timedelta(seconds=300) + else: + is_5_min_batch_export = False + + if is_5_min_batch_export and not is_backfill: + query_template = SELECT_FROM_EVENTS_VIEW_RECENT + elif str(team_id) in settings.UNCONSTRAINED_TIMESTAMP_TEAM_IDS: + query_template = SELECT_FROM_EVENTS_VIEW_UNBOUNDED + elif is_backfill: + query_template = SELECT_FROM_EVENTS_VIEW_BACKFILL + else: + query_template = SELECT_FROM_EVENTS_VIEW + lookback_days = settings.OVERRIDE_TIMESTAMP_TEAM_IDS.get( + team_id, settings.DEFAULT_TIMESTAMP_LOOKBACK_DAYS + ) + parameters["lookback_days"] = lookback_days + + if "_inserted_at" not in [field["alias"] for field in fields]: + control_fields = [BatchExportField(expression="_inserted_at", alias="_inserted_at")] + else: + control_fields = [] + + query_fields = ",".join(f"{field['expression']} AS {field['alias']}" for field in fields + control_fields) + + query = query_template.substitute(fields=query_fields) + + parameters["team_id"] = team_id + + extra_query_parameters = parameters.pop("extra_query_parameters", {}) or {} + parameters = {**parameters, **extra_query_parameters} + + self._task = asyncio.create_task( + self.produce_batch_export_record_batches_from_range( + query=query, full_range=full_range, done_ranges=done_ranges, queue=queue, query_parameters=parameters + ), + name="record_batch_producer", + ) + + return self.task + + async def produce_batch_export_record_batches_from_range( + self, + query: str, + full_range: tuple[dt.datetime | None, dt.datetime], + done_ranges: collections.abc.Sequence[tuple[dt.datetime, dt.datetime]], + queue: RecordBatchQueue, + query_parameters: dict[str, typing.Any], + ): + for interval_start, interval_end in generate_query_ranges(full_range, done_ranges): + if interval_start is not None: + query_parameters["interval_start"] = interval_start.strftime("%Y-%m-%d %H:%M:%S.%f") + query_parameters["interval_end"] = interval_end.strftime("%Y-%m-%d %H:%M:%S.%f") + query_id = uuid.uuid4() + + await self.clickhouse_client.aproduce_query_as_arrow_record_batches( + query, queue=queue, query_parameters=query_parameters, query_id=str(query_id) + ) + + +def generate_query_ranges( + remaining_range: tuple[dt.datetime | None, dt.datetime], + done_ranges: collections.abc.Sequence[tuple[dt.datetime, dt.datetime]], +) -> typing.Iterator[tuple[dt.datetime | None, dt.datetime]]: + """Recursively yield ranges of dates that need to be queried. + + There are essentially 3 scenarios we are expecting: + 1. The batch export just started, so we expect `done_ranges` to be an empty + list, and thus should return the `remaining_range`. + 2. The batch export crashed mid-execution, so we have some `done_ranges` that + do not completely add up to the full range. In this case we need to yield + ranges in between all the done ones. + 3. The batch export crashed right after we finish, so we have a full list of + `done_ranges` adding up to the `remaining_range`. In this case we should not + yield anything. + + Case 1 is fairly trivial and we can simply return `remaining_range` if we get + an empty `done_ranges`. + + Case 2 is more complicated and we can expect that the ranges produced by this + function will lead to duplicate events selected, as our batch export query is + inclusive in the lower bound. Since multiple rows may have the same + `inserted_at` we cannot simply skip an `inserted_at` value, as there may be a + row that hasn't been exported as it with the same `inserted_at` as a row that + has been exported. So this function will return ranges with `inserted_at` + values that were already exported for at least one event. Ideally, this is + *only* one event, but we can never be certain. + """ + if len(done_ranges) == 0: + yield remaining_range + return + + epoch = dt.datetime.fromtimestamp(0, tz=dt.UTC) + list_done_ranges: list[tuple[dt.datetime, dt.datetime]] = list(done_ranges) + + list_done_ranges.sort(key=operator.itemgetter(0)) + + while True: + try: + next_range: tuple[dt.datetime | None, dt.datetime] = list_done_ranges.pop(0) + except IndexError: + if remaining_range[0] != remaining_range[1]: + # If they were equal it would mean we have finished. + yield remaining_range + + return + else: + candidate_end_at = next_range[0] if next_range[0] is not None else epoch + + candidate_start_at = remaining_range[0] + remaining_range = (next_range[1], remaining_range[1]) + + if candidate_start_at is not None and candidate_start_at >= candidate_end_at: + # We have landed within a done range. + continue + + if candidate_start_at is None and candidate_end_at == epoch: + # We have landed within the first done range of a backfill. + continue + + yield (candidate_start_at, candidate_end_at) diff --git a/posthog/temporal/batch_exports/sql.py b/posthog/temporal/batch_exports/sql.py new file mode 100644 index 0000000000000..7cb3922268ead --- /dev/null +++ b/posthog/temporal/batch_exports/sql.py @@ -0,0 +1,173 @@ +from string import Template + +SELECT_FROM_PERSONS_VIEW = """ +SELECT + persons.team_id AS team_id, + persons.distinct_id AS distinct_id, + persons.person_id AS person_id, + persons.properties AS properties, + persons.person_distinct_id_version AS person_distinct_id_version, + persons.person_version AS person_version, + persons._inserted_at AS _inserted_at +FROM + persons_batch_export( + team_id={team_id}, + interval_start={interval_start}, + interval_end={interval_end} + ) AS persons +FORMAT ArrowStream +SETTINGS + max_bytes_before_external_group_by=50000000000, + max_bytes_before_external_sort=50000000000, + optimize_aggregation_in_order=1 +""" + +# This is an updated version of the view that we will use going forward +# We will migrate each batch export destination over one at a time to migitate +# risk, and once this is done we can clean this up. +SELECT_FROM_PERSONS_VIEW_NEW = """ +SELECT + persons.team_id AS team_id, + persons.distinct_id AS distinct_id, + persons.person_id AS person_id, + persons.properties AS properties, + persons.person_distinct_id_version AS person_distinct_id_version, + persons.person_version AS person_version, + persons.created_at AS created_at, + persons._inserted_at AS _inserted_at +FROM + persons_batch_export( + team_id={team_id}, + interval_start={interval_start}, + interval_end={interval_end} + ) AS persons +FORMAT ArrowStream +SETTINGS + max_bytes_before_external_group_by=50000000000, + max_bytes_before_external_sort=50000000000, + optimize_aggregation_in_order=1 +""" + +SELECT_FROM_PERSONS_VIEW_BACKFILL = """ +SELECT + persons.team_id AS team_id, + persons.distinct_id AS distinct_id, + persons.person_id AS person_id, + persons.properties AS properties, + persons.person_distinct_id_version AS person_distinct_id_version, + persons.person_version AS person_version, + persons._inserted_at AS _inserted_at +FROM + persons_batch_export_backfill( + team_id={team_id}, + interval_end={interval_end} + ) AS persons +FORMAT ArrowStream +SETTINGS + max_bytes_before_external_group_by=50000000000, + max_bytes_before_external_sort=50000000000, + optimize_aggregation_in_order=1 +""" + +# This is an updated version of the view that we will use going forward +# We will migrate each batch export destination over one at a time to migitate +# risk, and once this is done we can clean this up. +SELECT_FROM_PERSONS_VIEW_BACKFILL_NEW = """ +SELECT + persons.team_id AS team_id, + persons.distinct_id AS distinct_id, + persons.person_id AS person_id, + persons.properties AS properties, + persons.person_distinct_id_version AS person_distinct_id_version, + persons.person_version AS person_version, + persons.created_at AS created_at, + persons._inserted_at AS _inserted_at +FROM + persons_batch_export_backfill( + team_id={team_id}, + interval_end={interval_end} + ) AS persons +FORMAT ArrowStream +SETTINGS + max_bytes_before_external_group_by=50000000000, + max_bytes_before_external_sort=50000000000, + optimize_aggregation_in_order=1 +""" + +SELECT_FROM_EVENTS_VIEW = Template( + """ +SELECT + $fields +FROM + events_batch_export( + team_id={team_id}, + lookback_days={lookback_days}, + interval_start={interval_start}, + interval_end={interval_end}, + include_events={include_events}::Array(String), + exclude_events={exclude_events}::Array(String) + ) AS events +FORMAT ArrowStream +SETTINGS + -- This is half of configured MAX_MEMORY_USAGE for batch exports. + max_bytes_before_external_sort=50000000000 +""" +) + +SELECT_FROM_EVENTS_VIEW_RECENT = Template( + """ +SELECT + $fields +FROM + events_batch_export_recent( + team_id={team_id}, + interval_start={interval_start}, + interval_end={interval_end}, + include_events={include_events}::Array(String), + exclude_events={exclude_events}::Array(String) + ) AS events +FORMAT ArrowStream +SETTINGS + -- This is half of configured MAX_MEMORY_USAGE for batch exports. + max_bytes_before_external_sort=50000000000, + max_replica_delay_for_distributed_queries=1 +""" +) + +SELECT_FROM_EVENTS_VIEW_UNBOUNDED = Template( + """ +SELECT + $fields +FROM + events_batch_export_unbounded( + team_id={team_id}, + interval_start={interval_start}, + interval_end={interval_end}, + include_events={include_events}::Array(String), + exclude_events={exclude_events}::Array(String) + ) AS events +FORMAT ArrowStream +SETTINGS + -- This is half of configured MAX_MEMORY_USAGE for batch exports. + max_bytes_before_external_sort=50000000000 +""" +) + +SELECT_FROM_EVENTS_VIEW_BACKFILL = Template( + """ +SELECT + $fields +FROM + events_batch_export_backfill( + team_id={team_id}, + interval_start={interval_start}, + interval_end={interval_end}, + include_events={include_events}::Array(String), + exclude_events={exclude_events}::Array(String) + ) AS events +FORMAT ArrowStream +SETTINGS + -- This is half of configured MAX_MEMORY_USAGE for batch exports. + max_bytes_before_external_sort=50000000000 +""" +) diff --git a/posthog/temporal/batch_exports/temporary_file.py b/posthog/temporal/batch_exports/temporary_file.py index 54beae9f9b1d5..c6a30ebc93a1b 100644 --- a/posthog/temporal/batch_exports/temporary_file.py +++ b/posthog/temporal/batch_exports/temporary_file.py @@ -6,6 +6,7 @@ import contextlib import csv import datetime as dt +import enum import gzip import json import tempfile @@ -466,6 +467,48 @@ async def flush(self, is_last: bool = False) -> None: self.end_at_since_last_flush = None +class WriterFormat(enum.StrEnum): + JSONL = enum.auto() + PARQUET = enum.auto() + CSV = enum.auto() + + @staticmethod + def from_str(format_str: str, destination: str): + match format_str.upper(): + case "JSONL" | "JSONLINES": + return WriterFormat.JSONL + case "PARQUET": + return WriterFormat.PARQUET + case "CSV": + return WriterFormat.CSV + case _: + raise UnsupportedFileFormatError(format_str, destination) + + +def get_batch_export_writer(writer_format: WriterFormat, flush_callable: FlushCallable, max_bytes: int, **kwargs): + match writer_format: + case WriterFormat.CSV: + return CSVBatchExportWriter( + max_bytes=max_bytes, + flush_callable=flush_callable, + **kwargs, + ) + + case WriterFormat.JSONL: + return JSONLBatchExportWriter( + max_bytes=max_bytes, + flush_callable=flush_callable, + **kwargs, + ) + + case WriterFormat.PARQUET: + return ParquetBatchExportWriter( + max_bytes=max_bytes, + flush_callable=flush_callable, + **kwargs, + ) + + class JSONLBatchExportWriter(BatchExportWriter): """A `BatchExportWriter` for JSONLines format. @@ -478,6 +521,7 @@ def __init__( self, max_bytes: int, flush_callable: FlushCallable, + schema: pa.Schema | None = None, compression: None | str = None, default: typing.Callable = str, ): @@ -549,6 +593,7 @@ def __init__( max_bytes: int, flush_callable: FlushCallable, field_names: collections.abc.Sequence[str], + schema: pa.Schema | None = None, extras_action: typing.Literal["raise", "ignore"] = "ignore", delimiter: str = ",", quote_char: str = '"', diff --git a/posthog/temporal/batch_exports/utils.py b/posthog/temporal/batch_exports/utils.py index c54e983795838..d9bbda0657ef1 100644 --- a/posthog/temporal/batch_exports/utils.py +++ b/posthog/temporal/batch_exports/utils.py @@ -191,7 +191,7 @@ def __arrow_ext_scalar_class__(self): def cast_record_batch_json_columns( record_batch: pa.RecordBatch, - json_columns: collections.abc.Sequence = ("properties", "person_properties", "set", "set_once"), + json_columns: collections.abc.Sequence[str] = ("properties", "person_properties", "set", "set_once"), ) -> pa.RecordBatch: """Cast json_columns in record_batch to JsonType. @@ -215,6 +215,27 @@ def cast_record_batch_json_columns( ) +def cast_record_batch_schema_json_columns( + schema: pa.Schema, + json_columns: collections.abc.Sequence[str] = ("properties", "person_properties", "set", "set_once"), +): + column_names = set(schema.names) + intersection = column_names & set(json_columns) + new_fields = [] + + for field in schema: + if field.name not in intersection or not pa.types.is_string(field.type): + new_fields.append(field) + continue + + casted_field = field.with_type(JsonType()) + new_fields.append(casted_field) + + new_schema = pa.schema(new_fields) + + return new_schema + + _Result = typing.TypeVar("_Result") FutureLike = ( asyncio.Future[_Result] | collections.abc.Coroutine[None, typing.Any, _Result] | collections.abc.Awaitable[_Result] diff --git a/posthog/temporal/tests/batch_exports/test_batch_exports.py b/posthog/temporal/tests/batch_exports/test_batch_exports.py index b8236af8322c9..1303cdb178399 100644 --- a/posthog/temporal/tests/batch_exports/test_batch_exports.py +++ b/posthog/temporal/tests/batch_exports/test_batch_exports.py @@ -4,14 +4,12 @@ import operator from random import randint -import pyarrow as pa import pytest from django.test import override_settings from posthog.batch_exports.service import BatchExportModel from posthog.temporal.batch_exports.batch_exports import ( RecordBatchProducerError, - RecordBatchQueue, TaskNotDoneError, generate_query_ranges, get_data_interval, @@ -743,57 +741,6 @@ async def test_start_produce_batch_export_record_batches_handles_duplicates(clic assert_records_match_events(records, events) -async def test_record_batch_queue_tracks_bytes(): - """Test `RecordBatchQueue` tracks bytes from `RecordBatch`.""" - records = [{"test": 1}, {"test": 2}, {"test": 3}] - record_batch = pa.RecordBatch.from_pylist(records) - - queue = RecordBatchQueue() - - await queue.put(record_batch) - assert record_batch.get_total_buffer_size() == queue.qsize() - - item = await queue.get() - - assert item == record_batch - assert queue.qsize() == 0 - - -async def test_record_batch_queue_raises_queue_full(): - """Test `QueueFull` is raised when we put too many bytes.""" - records = [{"test": 1}, {"test": 2}, {"test": 3}] - record_batch = pa.RecordBatch.from_pylist(records) - record_batch_size = record_batch.get_total_buffer_size() - - queue = RecordBatchQueue(max_size_bytes=record_batch_size) - - await queue.put(record_batch) - assert record_batch.get_total_buffer_size() == queue.qsize() - - with pytest.raises(asyncio.QueueFull): - queue.put_nowait(record_batch) - - item = await queue.get() - - assert item == record_batch - assert queue.qsize() == 0 - - -async def test_record_batch_queue_sets_schema(): - """Test `RecordBatchQueue` sets a schema from first `RecordBatch`.""" - records = [{"test": 1}, {"test": 2}, {"test": 3}] - record_batch = pa.RecordBatch.from_pylist(records) - - queue = RecordBatchQueue() - - await queue.put(record_batch) - - assert queue._schema_set.is_set() - - schema = await queue.get_schema() - assert schema == record_batch.schema - - async def test_raise_on_produce_task_failure_raises_record_batch_producer_error(): """Test a `RecordBatchProducerError` is raised with the right cause.""" cause = ValueError("Oh no!") diff --git a/posthog/temporal/tests/batch_exports/test_s3_batch_export_workflow.py b/posthog/temporal/tests/batch_exports/test_s3_batch_export_workflow.py index d5d8d26b40373..979869c4b31d7 100644 --- a/posthog/temporal/tests/batch_exports/test_s3_batch_export_workflow.py +++ b/posthog/temporal/tests/batch_exports/test_s3_batch_export_workflow.py @@ -28,10 +28,10 @@ ) from posthog.temporal.batch_exports.s3_batch_export import ( FILE_FORMAT_EXTENSIONS, - S3HeartbeatDetails, IntermittentUploadPartTimeoutError, S3BatchExportInputs, S3BatchExportWorkflow, + S3HeartbeatDetails, S3InsertInputs, S3MultiPartUpload, get_s3_key, @@ -1589,7 +1589,7 @@ def __init__(self, *args, **kwargs): assert run.records_completed is None assert ( run.latest_error - == "IntermittentUploadPartTimeoutError: An intermittent `RequestTimeout` was raised while attempting to upload part 1" + == "RecordBatchConsumerRetryableExceptionGroup: At least one unhandled retryable errors in a RecordBatch consumer TaskGroup (1 sub-exception)" ) run = runs[1] diff --git a/posthog/temporal/tests/batch_exports/test_spmc.py b/posthog/temporal/tests/batch_exports/test_spmc.py new file mode 100644 index 0000000000000..7fd41dc15de28 --- /dev/null +++ b/posthog/temporal/tests/batch_exports/test_spmc.py @@ -0,0 +1,131 @@ +import asyncio +import datetime as dt +import random + +import pyarrow as pa +import pytest + +from posthog.temporal.batch_exports.spmc import Producer, RecordBatchQueue +from posthog.temporal.tests.utils.events import generate_test_events_in_clickhouse + +pytestmark = [pytest.mark.asyncio, pytest.mark.django_db] + + +async def test_record_batch_queue_tracks_bytes(): + """Test `RecordBatchQueue` tracks bytes from `RecordBatch`.""" + records = [{"test": 1}, {"test": 2}, {"test": 3}] + record_batch = pa.RecordBatch.from_pylist(records) + + queue = RecordBatchQueue() + + await queue.put(record_batch) + assert record_batch.get_total_buffer_size() == queue.qsize() + + item = await queue.get() + + assert item == record_batch + assert queue.qsize() == 0 + + +async def test_record_batch_queue_raises_queue_full(): + """Test `QueueFull` is raised when we put too many bytes.""" + records = [{"test": 1}, {"test": 2}, {"test": 3}] + record_batch = pa.RecordBatch.from_pylist(records) + record_batch_size = record_batch.get_total_buffer_size() + + queue = RecordBatchQueue(max_size_bytes=record_batch_size) + + await queue.put(record_batch) + assert record_batch.get_total_buffer_size() == queue.qsize() + + with pytest.raises(asyncio.QueueFull): + queue.put_nowait(record_batch) + + item = await queue.get() + + assert item == record_batch + assert queue.qsize() == 0 + + +async def test_record_batch_queue_sets_schema(): + """Test `RecordBatchQueue` sets a schema from first `RecordBatch`.""" + records = [{"test": 1}, {"test": 2}, {"test": 3}] + record_batch = pa.RecordBatch.from_pylist(records) + + queue = RecordBatchQueue() + + await queue.put(record_batch) + + assert queue._schema_set.is_set() + + schema = await queue.get_schema() + assert schema == record_batch.schema + + +async def get_record_batch_from_queue(queue, produce_task): + while not queue.empty() or not produce_task.done(): + try: + record_batch = queue.get_nowait() + except asyncio.QueueEmpty: + if produce_task.done(): + break + else: + await asyncio.sleep(0.1) + continue + + return record_batch + return None + + +async def get_all_record_batches_from_queue(queue, produce_task): + records = [] + while not queue.empty() or not produce_task.done(): + record_batch = await get_record_batch_from_queue(queue, produce_task) + if record_batch is None: + break + + for record in record_batch.to_pylist(): + records.append(record) + return records + + +async def test_record_batch_producer_uses_extra_query_parameters(clickhouse_client): + """Test RecordBatch Producer uses a HogQL value.""" + team_id = random.randint(1, 1000000) + data_interval_end = dt.datetime.fromisoformat("2023-04-25T14:31:00.000000+00:00") + data_interval_start = dt.datetime.fromisoformat("2023-04-25T14:30:00.000000+00:00") + + (events, _, _) = await generate_test_events_in_clickhouse( + client=clickhouse_client, + team_id=team_id, + start_time=data_interval_start, + end_time=data_interval_end, + count=10, + count_outside_range=0, + count_other_team=0, + duplicate=False, + properties={"$browser": "Chrome", "$os": "Mac OS X", "custom": 3}, + ) + + queue = RecordBatchQueue() + producer = Producer(clickhouse_client=clickhouse_client) + producer_task = producer.start( + queue=queue, + team_id=team_id, + is_backfill=False, + model_name="events", + full_range=(data_interval_start, data_interval_end), + done_ranges=[], + fields=[ + {"expression": "JSONExtractInt(properties, %(hogql_val_0)s)", "alias": "custom_prop"}, + ], + extra_query_parameters={"hogql_val_0": "custom"}, + ) + + records = await get_all_record_batches_from_queue(queue, producer_task) + + for expected, record in zip(events, records): + if expected["properties"] is None: + raise ValueError("Empty properties") + + assert record["custom_prop"] == expected["properties"]["custom"] diff --git a/posthog/test/base.py b/posthog/test/base.py index 0615e2a546046..59a3bd9c71cb1 100644 --- a/posthog/test/base.py +++ b/posthog/test/base.py @@ -262,6 +262,15 @@ def clean_varying_query_parts(query, replace_all_numbers): "SELECT distinct_id, 1 as value", query, ) + + # rbac has some varying IDs we can replace + # e.g. AND "ee_accesscontrol"."resource_id" = '450' + query = re.sub( + r"\"resource_id\" = '\d+'", + "\"resource_id\" = '99999'", + query, + ) + return query diff --git a/requirements-dev.in b/requirements-dev.in index 7eefc098086bb..3a8488c259d5a 100644 --- a/requirements-dev.in +++ b/requirements-dev.in @@ -23,6 +23,7 @@ Faker==17.5.0 fakeredis[lua]==2.23.3 freezegun==1.2.2 inline-snapshot==0.12.* +multidict==6.0.5 # Not used by us directly, but code won't run on Ubuntu 24.04 unless we resolve this to 6.0.5 packaging==24.1 black~=23.9.1 boto3-stubs[s3] diff --git a/requirements-dev.txt b/requirements-dev.txt index 7068de849f11b..41a109d8cb97f 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -163,6 +163,10 @@ googleapis-common-protos==1.60.0 # via # -c requirements.txt # opentelemetry-exporter-otlp-proto-grpc +greenlet==3.1.1 + # via + # -c requirements.txt + # sqlalchemy grpcio==1.63.2 # via # -c requirements.txt @@ -275,9 +279,10 @@ marshmallow==3.23.1 # via dataclasses-json mdurl==0.1.2 # via markdown-it-py -multidict==6.0.2 +multidict==6.0.5 # via # -c requirements.txt + # -r requirements-dev.in # aiohttp # yarl multiprocess==0.70.16 diff --git a/requirements.in b/requirements.in index 3b827de57dbb6..264da25775f89 100644 --- a/requirements.in +++ b/requirements.in @@ -51,6 +51,7 @@ langfuse==2.52.1 langgraph==0.2.34 langsmith==0.1.132 lzstring==1.0.4 +multidict==6.0.5 # Not used by us directly, but code won't run on Ubuntu 24.04 unless we resolve this to 6.0.5 natsort==8.4.0 nanoid==2.0.0 numpy==1.23.3 diff --git a/requirements.txt b/requirements.txt index da95ee2a7d75d..3c09a5963c90e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -267,6 +267,8 @@ googleapis-common-protos==1.60.0 # via # google-api-core # grpcio-status +greenlet==3.1.1 + # via sqlalchemy grpcio==1.63.2 # via # -r requirements.in @@ -389,8 +391,9 @@ more-itertools==9.0.0 # simple-salesforce msgpack==1.1.0 # via langgraph-checkpoint -multidict==6.0.2 +multidict==6.0.5 # via + # -r requirements.in # aiohttp # yarl nanoid==2.0.0 diff --git a/rust/cymbal/src/types/mod.rs b/rust/cymbal/src/types/mod.rs index 01850217127f0..9e2354494e339 100644 --- a/rust/cymbal/src/types/mod.rs +++ b/rust/cymbal/src/types/mod.rs @@ -49,6 +49,11 @@ pub struct Exception { pub struct RawErrProps { #[serde(rename = "$exception_list")] pub exception_list: Vec, + #[serde( + rename = "$exception_fingerprint", + skip_serializing_if = "Option::is_none" + )] + pub fingerprint: Option, // Clients can send us fingerprints, which we'll use if present #[serde(flatten)] // A catch-all for all the properties we don't "care" about, so when we send back to kafka we don't lose any info pub other: HashMap, @@ -57,6 +62,7 @@ pub struct RawErrProps { pub struct FingerprintedErrProps { pub exception_list: Vec, pub fingerprint: String, + pub proposed_fingerprint: String, // We suggest a fingerprint, based on hashes, but let users override client-side pub other: HashMap, } @@ -67,6 +73,8 @@ pub struct OutputErrProps { pub exception_list: Vec, #[serde(rename = "$exception_fingerprint")] pub fingerprint: String, + #[serde(rename = "$exception_proposed_fingerprint")] + pub proposed_fingerprint: String, #[serde(rename = "$exception_issue_id")] pub issue_id: Uuid, #[serde(flatten)] @@ -119,7 +127,8 @@ impl RawErrProps { pub fn to_fingerprinted(self, fingerprint: String) -> FingerprintedErrProps { FingerprintedErrProps { exception_list: self.exception_list, - fingerprint, + fingerprint: self.fingerprint.unwrap_or(fingerprint.clone()), + proposed_fingerprint: fingerprint, other: self.other, } } @@ -131,6 +140,7 @@ impl FingerprintedErrProps { exception_list: self.exception_list, fingerprint: self.fingerprint, issue_id, + proposed_fingerprint: self.proposed_fingerprint, other: self.other, } } diff --git a/rust/cymbal/tests/static/python_err_props.json b/rust/cymbal/tests/static/python_err_props.json index 1a110b81964b1..df4caf67e728e 100644 --- a/rust/cymbal/tests/static/python_err_props.json +++ b/rust/cymbal/tests/static/python_err_props.json @@ -733,6 +733,5 @@ "$ip": "185.140.230.43", "$lib_version__minor": 6, "$lib": "posthog-python", - "$lib_version__major": 3, - "$exception_fingerprint": ["ConnectionRefusedError", "[Errno 111] Connection refused", "_new_conn"] + "$lib_version__major": 3 }