From 859100213d0c70d32f9c7d93f2b140fefd833b8f Mon Sep 17 00:00:00 2001 From: Taha Yassine Kraiem Date: Mon, 5 May 2025 16:15:41 +0200 Subject: [PATCH 1/6] fix(chalice): fixed empty error_id for table of errors (cherry picked from commit 4b1ca200b41c452c2a31856c429f01ea035a5e2f) --- api/chalicelib/core/errors/errors_ch.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/api/chalicelib/core/errors/errors_ch.py b/api/chalicelib/core/errors/errors_ch.py index 1c67e871d..f0845110b 100644 --- a/api/chalicelib/core/errors/errors_ch.py +++ b/api/chalicelib/core/errors/errors_ch.py @@ -338,14 +338,14 @@ def search(data: schemas.SearchErrorsSchema, project: schemas.ProjectContext, us SELECT details.error_id as error_id, name, message, users, total, sessions, last_occurrence, first_occurrence, chart - FROM (SELECT JSONExtractString(toString(`$properties`), 'error_id') AS error_id, + FROM (SELECT error_id, JSONExtractString(toString(`$properties`), 'name') AS name, JSONExtractString(toString(`$properties`), 'message') AS message, COUNT(DISTINCT user_id) AS users, COUNT(DISTINCT events.session_id) AS sessions, MAX(created_at) AS max_datetime, MIN(created_at) AS min_datetime, - COUNT(DISTINCT JSONExtractString(toString(`$properties`), 'error_id')) + COUNT(DISTINCT error_id) OVER() AS total FROM {MAIN_EVENTS_TABLE} AS events INNER JOIN (SELECT session_id, coalesce(user_id,toString(user_uuid)) AS user_id @@ -357,7 +357,7 @@ def search(data: schemas.SearchErrorsSchema, project: schemas.ProjectContext, us GROUP BY error_id, name, message ORDER BY {sort} {order} LIMIT %(errors_limit)s OFFSET %(errors_offset)s) AS details - INNER JOIN (SELECT JSONExtractString(toString(`$properties`), 'error_id') AS error_id, + INNER JOIN (SELECT error_id, toUnixTimestamp(MAX(created_at))*1000 AS last_occurrence, toUnixTimestamp(MIN(created_at))*1000 AS first_occurrence FROM {MAIN_EVENTS_TABLE} @@ -366,7 +366,7 @@ def search(data: schemas.SearchErrorsSchema, project: schemas.ProjectContext, us GROUP BY error_id) AS time_details ON details.error_id=time_details.error_id INNER JOIN (SELECT error_id, groupArray([timestamp, count]) AS chart - FROM (SELECT JSONExtractString(toString(`$properties`), 'error_id') AS error_id, + FROM (SELECT error_id, gs.generate_series AS timestamp, COUNT(DISTINCT session_id) AS count FROM generate_series(%(startDate)s, %(endDate)s, %(step_size)s) AS gs From 626c453a80bbaf443ae65879730b55772c1c7583 Mon Sep 17 00:00:00 2001 From: Taha Yassine Kraiem Date: Mon, 5 May 2025 16:48:02 +0200 Subject: [PATCH 2/6] refactor(DB): remove TTL for CH tables (cherry picked from commit d78b33dcd278f27c74ab716135dd6ad099b5b32e) # Conflicts: # ee/scripts/schema/db/init_dbs/clickhouse/create/init_schema.sql --- .../db/init_dbs/clickhouse/1.22.0/1.22.0.sql | 3 +- .../clickhouse/create/init_schema.sql | 28 ++++++------------- .../db/init_dbs/clickhouse/1.22.0/1.22.0.sql | 16 ++++------- .../clickhouse/create/init_schema.sql | 16 ++++------- 4 files changed, 20 insertions(+), 43 deletions(-) diff --git a/ee/scripts/schema/db/init_dbs/clickhouse/1.22.0/1.22.0.sql b/ee/scripts/schema/db/init_dbs/clickhouse/1.22.0/1.22.0.sql index 995914e03..49b5c808f 100644 --- a/ee/scripts/schema/db/init_dbs/clickhouse/1.22.0/1.22.0.sql +++ b/ee/scripts/schema/db/init_dbs/clickhouse/1.22.0/1.22.0.sql @@ -151,8 +151,7 @@ CREATE TABLE IF NOT EXISTS product_analytics.events _timestamp DateTime DEFAULT now() ) ENGINE = ReplacingMergeTree(_timestamp) ORDER BY (project_id, "$event_name", created_at, session_id) - TTL _timestamp + INTERVAL 1 MONTH , - _deleted_at + INTERVAL 1 DAY DELETE WHERE _deleted_at != '1970-01-01 00:00:00'; + TTL _deleted_at + INTERVAL 1 DAY DELETE WHERE _deleted_at != '1970-01-01 00:00:00'; -- The list of events that should not be ingested, -- according to a specific event_name and optional properties diff --git a/ee/scripts/schema/db/init_dbs/clickhouse/create/init_schema.sql b/ee/scripts/schema/db/init_dbs/clickhouse/create/init_schema.sql index eba6f793b..5f6a06511 100644 --- a/ee/scripts/schema/db/init_dbs/clickhouse/create/init_schema.sql +++ b/ee/scripts/schema/db/init_dbs/clickhouse/create/init_schema.sql @@ -9,8 +9,7 @@ CREATE TABLE IF NOT EXISTS experimental.autocomplete _timestamp DateTime DEFAULT now() ) ENGINE = ReplacingMergeTree(_timestamp) PARTITION BY toYYYYMM(_timestamp) - ORDER BY (project_id, type, value) - TTL _timestamp + INTERVAL 1 MONTH; + ORDER BY (project_id, type, value); CREATE TABLE IF NOT EXISTS experimental.events ( @@ -87,8 +86,7 @@ CREATE TABLE IF NOT EXISTS experimental.events _timestamp DateTime DEFAULT now() ) ENGINE = ReplacingMergeTree(_timestamp) PARTITION BY toYYYYMM(datetime) - ORDER BY (project_id, datetime, event_type, session_id, message_id) - TTL datetime + INTERVAL 1 MONTH; + ORDER BY (project_id, datetime, event_type, session_id, message_id); @@ -140,7 +138,6 @@ CREATE TABLE IF NOT EXISTS experimental.sessions ) ENGINE = ReplacingMergeTree(_timestamp) PARTITION BY toYYYYMMDD(datetime) ORDER BY (project_id, datetime, session_id) - TTL datetime + INTERVAL 1 MONTH SETTINGS index_granularity = 512; CREATE TABLE IF NOT EXISTS experimental.user_favorite_sessions @@ -152,8 +149,7 @@ CREATE TABLE IF NOT EXISTS experimental.user_favorite_sessions sign Int8 ) ENGINE = CollapsingMergeTree(sign) PARTITION BY toYYYYMM(_timestamp) - ORDER BY (project_id, user_id, session_id) - TTL _timestamp + INTERVAL 3 MONTH; + ORDER BY (project_id, user_id, session_id); CREATE TABLE IF NOT EXISTS experimental.user_viewed_sessions ( @@ -163,8 +159,7 @@ CREATE TABLE IF NOT EXISTS experimental.user_viewed_sessions _timestamp DateTime DEFAULT now() ) ENGINE = ReplacingMergeTree(_timestamp) PARTITION BY toYYYYMM(_timestamp) - ORDER BY (project_id, user_id, session_id) - TTL _timestamp + INTERVAL 3 MONTH; + ORDER BY (project_id, user_id, session_id); CREATE TABLE IF NOT EXISTS experimental.user_viewed_errors ( @@ -174,8 +169,7 @@ CREATE TABLE IF NOT EXISTS experimental.user_viewed_errors _timestamp DateTime DEFAULT now() ) ENGINE = ReplacingMergeTree(_timestamp) PARTITION BY toYYYYMM(_timestamp) - ORDER BY (project_id, user_id, error_id) - TTL _timestamp + INTERVAL 3 MONTH; + ORDER BY (project_id, user_id, error_id); CREATE TABLE IF NOT EXISTS experimental.issues ( @@ -188,8 +182,7 @@ CREATE TABLE IF NOT EXISTS experimental.issues _timestamp DateTime DEFAULT now() ) ENGINE = ReplacingMergeTree(_timestamp) PARTITION BY toYYYYMM(_timestamp) - ORDER BY (project_id, issue_id, type) - TTL _timestamp + INTERVAL 1 MONTH; + ORDER BY (project_id, issue_id, type); @@ -292,8 +285,7 @@ CREATE TABLE IF NOT EXISTS experimental.sessions_feature_flags _timestamp DateTime DEFAULT now() ) ENGINE = ReplacingMergeTree(_timestamp) PARTITION BY toYYYYMM(datetime) - ORDER BY (project_id, datetime, session_id, feature_flag_id, condition_id) - TTL datetime + INTERVAL 3 MONTH; + ORDER BY (project_id, datetime, session_id, feature_flag_id, condition_id); CREATE TABLE IF NOT EXISTS experimental.ios_events ( @@ -329,8 +321,7 @@ CREATE TABLE IF NOT EXISTS experimental.ios_events _timestamp DateTime DEFAULT now() ) ENGINE = ReplacingMergeTree(_timestamp) PARTITION BY toYYYYMM(datetime) - ORDER BY (project_id, datetime, event_type, session_id, message_id) - TTL datetime + INTERVAL 1 MONTH; + ORDER BY (project_id, datetime, event_type, session_id, message_id); SET allow_experimental_json_type = 1; @@ -540,8 +531,7 @@ message_id UInt64 _timestamp DateTime DEFAULT now() ) ENGINE = ReplacingMergeTree(_timestamp) ORDER BY (project_id, "$event_name", created_at, session_id) - TTL _timestamp + INTERVAL 1 MONTH , - _deleted_at + INTERVAL 1 DAY DELETE WHERE _deleted_at != '1970-01-01 00:00:00'; + TTL _deleted_at + INTERVAL 1 DAY DELETE WHERE _deleted_at != '1970-01-01 00:00:00'; -- The list of events that should not be ingested, -- according to a specific event_name and optional properties diff --git a/scripts/schema/db/init_dbs/clickhouse/1.22.0/1.22.0.sql b/scripts/schema/db/init_dbs/clickhouse/1.22.0/1.22.0.sql index 97eb1216d..279c9517e 100644 --- a/scripts/schema/db/init_dbs/clickhouse/1.22.0/1.22.0.sql +++ b/scripts/schema/db/init_dbs/clickhouse/1.22.0/1.22.0.sql @@ -9,8 +9,7 @@ CREATE TABLE IF NOT EXISTS experimental.autocomplete _timestamp DateTime DEFAULT now() ) ENGINE = ReplacingMergeTree(_timestamp) PARTITION BY toYYYYMM(_timestamp) - ORDER BY (project_id, type, value) - TTL _timestamp + INTERVAL 1 MONTH; + ORDER BY (project_id, type, value); CREATE TABLE IF NOT EXISTS experimental.events ( @@ -87,8 +86,7 @@ CREATE TABLE IF NOT EXISTS experimental.events _timestamp DateTime DEFAULT now() ) ENGINE = ReplacingMergeTree(_timestamp) PARTITION BY toYYYYMM(datetime) - ORDER BY (project_id, datetime, event_type, session_id, message_id) - TTL datetime + INTERVAL 1 MONTH; + ORDER BY (project_id, datetime, event_type, session_id, message_id); @@ -140,7 +138,6 @@ CREATE TABLE IF NOT EXISTS experimental.sessions ) ENGINE = ReplacingMergeTree(_timestamp) PARTITION BY toYYYYMMDD(datetime) ORDER BY (project_id, datetime, session_id) - TTL datetime + INTERVAL 1 MONTH SETTINGS index_granularity = 512; CREATE TABLE IF NOT EXISTS experimental.issues @@ -154,8 +151,7 @@ CREATE TABLE IF NOT EXISTS experimental.issues _timestamp DateTime DEFAULT now() ) ENGINE = ReplacingMergeTree(_timestamp) PARTITION BY toYYYYMM(_timestamp) - ORDER BY (project_id, issue_id, type) - TTL _timestamp + INTERVAL 1 MONTH; + ORDER BY (project_id, issue_id, type); CREATE TABLE IF NOT EXISTS experimental.ios_events ( @@ -191,8 +187,7 @@ CREATE TABLE IF NOT EXISTS experimental.ios_events _timestamp DateTime DEFAULT now() ) ENGINE = ReplacingMergeTree(_timestamp) PARTITION BY toYYYYMM(datetime) - ORDER BY (project_id, datetime, event_type, session_id, message_id) - TTL datetime + INTERVAL 1 MONTH; + ORDER BY (project_id, datetime, event_type, session_id, message_id); SET allow_experimental_json_type = 1; @@ -345,8 +340,7 @@ CREATE TABLE IF NOT EXISTS product_analytics.events _timestamp DateTime DEFAULT now() ) ENGINE = ReplacingMergeTree(_timestamp) ORDER BY (project_id, "$event_name", created_at, session_id) - TTL _timestamp + INTERVAL 1 MONTH , - _deleted_at + INTERVAL 1 DAY DELETE WHERE _deleted_at != '1970-01-01 00:00:00'; + TTL _deleted_at + INTERVAL 1 DAY DELETE WHERE _deleted_at != '1970-01-01 00:00:00'; -- The list of events that should not be ingested, -- according to a specific event_name and optional properties diff --git a/scripts/schema/db/init_dbs/clickhouse/create/init_schema.sql b/scripts/schema/db/init_dbs/clickhouse/create/init_schema.sql index 272ffd782..1283dc4e9 100644 --- a/scripts/schema/db/init_dbs/clickhouse/create/init_schema.sql +++ b/scripts/schema/db/init_dbs/clickhouse/create/init_schema.sql @@ -9,8 +9,7 @@ CREATE TABLE IF NOT EXISTS experimental.autocomplete _timestamp DateTime DEFAULT now() ) ENGINE = ReplacingMergeTree(_timestamp) PARTITION BY toYYYYMM(_timestamp) - ORDER BY (project_id, type, value) - TTL _timestamp + INTERVAL 1 MONTH; + ORDER BY (project_id, type, value); CREATE TABLE IF NOT EXISTS experimental.events ( @@ -87,8 +86,7 @@ CREATE TABLE IF NOT EXISTS experimental.events _timestamp DateTime DEFAULT now() ) ENGINE = ReplacingMergeTree(_timestamp) PARTITION BY toYYYYMM(datetime) - ORDER BY (project_id, datetime, event_type, session_id, message_id) - TTL datetime + INTERVAL 1 MONTH; + ORDER BY (project_id, datetime, event_type, session_id, message_id); @@ -140,7 +138,6 @@ CREATE TABLE IF NOT EXISTS experimental.sessions ) ENGINE = ReplacingMergeTree(_timestamp) PARTITION BY toYYYYMMDD(datetime) ORDER BY (project_id, datetime, session_id) - TTL datetime + INTERVAL 1 MONTH SETTINGS index_granularity = 512; CREATE TABLE IF NOT EXISTS experimental.user_favorite_sessions @@ -188,8 +185,7 @@ CREATE TABLE IF NOT EXISTS experimental.issues _timestamp DateTime DEFAULT now() ) ENGINE = ReplacingMergeTree(_timestamp) PARTITION BY toYYYYMM(_timestamp) - ORDER BY (project_id, issue_id, type) - TTL _timestamp + INTERVAL 1 MONTH; + ORDER BY (project_id, issue_id, type); CREATE TABLE IF NOT EXISTS experimental.ios_events ( @@ -225,8 +221,7 @@ CREATE TABLE IF NOT EXISTS experimental.ios_events _timestamp DateTime DEFAULT now() ) ENGINE = ReplacingMergeTree(_timestamp) PARTITION BY toYYYYMM(datetime) - ORDER BY (project_id, datetime, event_type, session_id, message_id) - TTL datetime + INTERVAL 1 MONTH; + ORDER BY (project_id, datetime, event_type, session_id, message_id); SET allow_experimental_json_type = 1; @@ -435,8 +430,7 @@ message_id UInt64 _timestamp DateTime DEFAULT now() ) ENGINE = ReplacingMergeTree(_timestamp) ORDER BY (project_id, "$event_name", created_at, session_id) - TTL _timestamp + INTERVAL 1 MONTH , - _deleted_at + INTERVAL 1 DAY DELETE WHERE _deleted_at != '1970-01-01 00:00:00'; + TTL _deleted_at + INTERVAL 1 DAY DELETE WHERE _deleted_at != '1970-01-01 00:00:00'; -- The list of events that should not be ingested, -- according to a specific event_name and optional properties From b477f9637d61068b3e7a17a97177489af13cc16e Mon Sep 17 00:00:00 2001 From: Andrey Babushkin <55714097+reyand43@users.noreply.github.com> Date: Mon, 5 May 2025 17:29:16 +0200 Subject: [PATCH 3/6] Highlight searched events (#3360) * add filtered events to search * removed consoles * changed styles to tailwind * changed styles to tailwind * fixed errors --- .../Player/ReplayPlayer/PlayerBlockHeader.tsx | 20 +++++++- frontend/app/components/Session/WebPlayer.tsx | 16 ++++++ ...tGroupWrapper.js => EventGroupWrapper.tsx} | 7 +-- .../Session_/EventsBlock/EventsBlock.tsx | 49 +++++++++++------- .../Session_/Player/Controls/EventsList.tsx | 18 ++++--- .../Player/Controls/checkEventWithFilters.ts | 51 +++++++++++++++++++ .../Player/Controls/timeline.module.css | 18 ------- frontend/app/mstore/sessionStore.ts | 12 +++-- frontend/app/mstore/uiPlayerStore.ts | 10 ++++ frontend/app/types/session/event.ts | 7 +++ 10 files changed, 158 insertions(+), 50 deletions(-) rename frontend/app/components/Session_/EventsBlock/{EventGroupWrapper.js => EventGroupWrapper.tsx} (96%) create mode 100644 frontend/app/components/Session_/Player/Controls/checkEventWithFilters.ts diff --git a/frontend/app/components/Session/Player/ReplayPlayer/PlayerBlockHeader.tsx b/frontend/app/components/Session/Player/ReplayPlayer/PlayerBlockHeader.tsx index c3784794b..ef769ec5f 100644 --- a/frontend/app/components/Session/Player/ReplayPlayer/PlayerBlockHeader.tsx +++ b/frontend/app/components/Session/Player/ReplayPlayer/PlayerBlockHeader.tsx @@ -16,12 +16,14 @@ import { IFRAME } from 'App/constants/storageKeys'; import stl from './playerBlockHeader.module.css'; import UserCard from './EventsBlock/UserCard'; import { useTranslation } from 'react-i18next'; +import { Switch } from 'antd'; const SESSIONS_ROUTE = sessionsRoute(); function PlayerBlockHeader(props: any) { const { t } = useTranslation(); const [hideBack, setHideBack] = React.useState(false); + const { uiPlayerStore } = useStore(); const { player, store } = React.useContext(PlayerContext); const { uxtestingStore, customFieldStore, projectsStore, sessionStore } = useStore(); @@ -123,9 +125,25 @@ function PlayerBlockHeader(props: any) { )} + {uiPlayerStore.showSearchEventsSwitchButton ? ( +
+ + + {t('Search Events Only')} + +
+ ) : null}
{ + return filters.length === 1 && filters[0].key === 'location' && filters[0].value[0] === ''; +} + function WebPlayer(props: any) { const { notesStore, @@ -38,6 +42,7 @@ function WebPlayer(props: any) { uxtestingStore, uiPlayerStore, integrationsStore, + searchStore, } = useStore(); const devTools = sessionStore.devTools const session = sessionStore.current; @@ -57,6 +62,17 @@ function WebPlayer(props: any) { const [fullView, setFullView] = useState(false); React.useEffect(() => { + if (searchStore.instance.filters?.length && !isDefaultEventsFilterSearch(searchStore.instance.filters)) { + uiPlayerStore.setSearchEventsSwitchButton(true); + uiPlayerStore.setShowOnlySearchEvents(true); + } else { + uiPlayerStore.setSearchEventsSwitchButton(false); + uiPlayerStore.setShowOnlySearchEvents(false); + } + }, [searchStore.instance.filters]); + + React.useEffect(() => { + openedAt.current = Date.now(); const handleActivation = () => { if (!document.hidden) { setWindowActive(true); diff --git a/frontend/app/components/Session_/EventsBlock/EventGroupWrapper.js b/frontend/app/components/Session_/EventsBlock/EventGroupWrapper.tsx similarity index 96% rename from frontend/app/components/Session_/EventsBlock/EventGroupWrapper.js rename to frontend/app/components/Session_/EventsBlock/EventGroupWrapper.tsx index a999ecc9f..92f89bcea 100644 --- a/frontend/app/components/Session_/EventsBlock/EventGroupWrapper.js +++ b/frontend/app/components/Session_/EventsBlock/EventGroupWrapper.tsx @@ -25,6 +25,7 @@ function EventGroupWrapper(props) { isLastInGroup, isSelected, isCurrent, + isSearched, isEditing, showSelection, isFirst, @@ -99,7 +100,7 @@ function EventGroupWrapper(props) { ); }; - const shadowColor = props.isPrev + const shadowColor = isSearched ? '#F0A930' : props.isPrev ? '#A7BFFF' : props.isCurrent ? '#394EFF' @@ -127,7 +128,7 @@ function EventGroupWrapper(props) { width: 10, height: 10, transform: 'rotate(45deg) translate(0, -50%)', - background: '#394EFF', + background: isSearched ? '#F0A930' : '#394EFF', zIndex: 99, borderRadius: '.15rem', }} @@ -169,6 +170,6 @@ function TabChange({ from, to, activeUrl, onClick }) {
); -} +}; export default observer(EventGroupWrapper); diff --git a/frontend/app/components/Session_/EventsBlock/EventsBlock.tsx b/frontend/app/components/Session_/EventsBlock/EventsBlock.tsx index fa206ebda..643673bf7 100644 --- a/frontend/app/components/Session_/EventsBlock/EventsBlock.tsx +++ b/frontend/app/components/Session_/EventsBlock/EventsBlock.tsx @@ -2,20 +2,20 @@ import { mergeEventLists, sortEvents } from 'Types/session'; import { TYPES } from 'Types/session/event'; import cn from 'classnames'; import { observer } from 'mobx-react-lite'; -import React from 'react'; +import React, { useEffect } from 'react'; import { VList, VListHandle } from 'virtua'; -import { Button } from 'antd' +import { Button } from 'antd'; import { PlayerContext } from 'App/components/Session/playerContext'; import { useStore } from 'App/mstore'; import { Icon } from 'UI'; -import { Search } from 'lucide-react' +import { Search } from 'lucide-react'; import EventGroupWrapper from './EventGroupWrapper'; import EventSearch from './EventSearch/EventSearch'; import styles from './eventsBlock.module.css'; import { useTranslation } from 'react-i18next'; -import { CloseOutlined } from "@ant-design/icons"; -import { Tooltip } from "antd"; -import { getDefaultFramework, frameworkIcons } from "../UnitStepsModal"; +import { CloseOutlined } from '@ant-design/icons'; +import { Tooltip } from 'antd'; +import { getDefaultFramework, frameworkIcons } from '../UnitStepsModal'; interface IProps { setActiveTab: (tab?: string) => void; @@ -25,7 +25,7 @@ const MODES = { SELECT: 'select', SEARCH: 'search', EXPORT: 'export', -} +}; function EventsBlock(props: IProps) { const defaultFramework = getDefaultFramework(); @@ -47,6 +47,7 @@ function EventsBlock(props: IProps) { const zoomStartTs = uiPlayerStore.timelineZoom.startTs; const zoomEndTs = uiPlayerStore.timelineZoom.endTs; const { store, player } = React.useContext(PlayerContext); + const [currentTimeEventIndex, setCurrentTimeEventIndex] = React.useState(0); const { time, @@ -102,10 +103,7 @@ function EventsBlock(props: IProps) { ? e.time >= zoomStartTs && e.time <= zoomEndTs : false : true - ); - } else { - return list - } + ).filter((e: any) => !e.noteId && e.type !== 'TABCHANGE' && uiPlayerStore.showOnlySearchEvents ? e.isHighlighted : true); }, [ filteredLength, query, @@ -114,6 +112,7 @@ function EventsBlock(props: IProps) { zoomEnabled, zoomStartTs, zoomEndTs, + uiPlayerStore.showOnlySearchEvents ]); const findLastFitting = React.useCallback( (time: number) => { @@ -137,7 +136,10 @@ function EventsBlock(props: IProps) { }, [usedEvents, time, endTime], ); - const currentTimeEventIndex = findLastFitting(time); + + useEffect(() => { + setCurrentTimeEventIndex(findLastFitting(time)); + }, []) const write = ({ target: { value }, @@ -193,6 +195,8 @@ function EventsBlock(props: IProps) { const isTabChange = 'type' in event && event.type === 'TABCHANGE'; const isCurrent = index === currentTimeEventIndex; const isPrev = index < currentTimeEventIndex; + const isSearched = event.isHighlighted + return ( setMode(MODES.SEARCH)} > -
{t('Search')} {usedEvents.length} {t('events')}
+
+ {t('Search')} {usedEvents.length} {t('events')} +
- + + - : null} + ) : null}
Object.values(tabStates)[0]?.eventList.filter((e) => e.time) || [], - [eventCount], + () => Object.values(tabStates)[0]?.eventList.filter((e) => { + if (uiPlayerStore.showOnlySearchEvents) { + return e.time && (e as any).isHighlighted + } else { + return e.time + } + }) || [], + [eventCount, uiPlayerStore.showOnlySearchEvents], ); - React.useEffect(() => { const hasDuplicates = events.some( (e, i) => @@ -33,7 +39,7 @@ function EventsList() {
))} @@ -53,7 +59,7 @@ function MobileEventsList() {
))} diff --git a/frontend/app/components/Session_/Player/Controls/checkEventWithFilters.ts b/frontend/app/components/Session_/Player/Controls/checkEventWithFilters.ts new file mode 100644 index 000000000..fb39264fb --- /dev/null +++ b/frontend/app/components/Session_/Player/Controls/checkEventWithFilters.ts @@ -0,0 +1,51 @@ +import FilterItem from '@/mstore/types/filterItem'; + +export const checkEventWithFilters = (event: Event, filters: FilterItem[]) => { + let result = false; + filters.forEach((filter) => { + if (filter.key.toUpperCase() === event.type.toUpperCase()) { + if (filter.operator) { + const operator = operators[filter.operator]; + if (operator) { + result = !!operator(event.label, filter.value); + } + } + } + }); + return result +}; + +const operators = { + is: (val: string, target: string[]) => target.some((t) => val.includes(t)), + isAny: () => true, + isNot: (val: string, target: string[]) => + !target.some((t) => val.includes(t)), + contains: (val: string, target: string[]) => + target.some((t) => val.includes(t)), + notContains: (val: string, target: string[]) => + !target.some((t) => val.includes(t)), + startsWith: (val: string, target: string[]) => + target.some((t) => val.startsWith(t)), + endsWith: (val: string, target: string[]) => + target.some((t) => val.endsWith(t)), + greaterThan: (val: number, target: number) => val > target, + greaterOrEqual: (val: number, target: number) => val >= target, + lessOrEqual: (val: number, target: number) => val <= target, + lessThan: (val: number, target: number) => val < target, + on: (val: string, target: string[]) => target.some((t) => val.includes(t)), + notOn: (val: string, target: string[]) => + !target.some((t) => val.includes(t)), + onAny: () => true, + selectorIs: (val: string, target: string[]) => target.some((t) => val.includes(t)), + selectorIsAny: () => true, + selectorIsNot: (val: string, target: string[]) => + !target.some((t) => val.includes(t)), + selectorContains: (val: string, target: string[]) => + target.some((t) => val.includes(t)), + selectorNotContains: (val: string, target: string[]) => + !target.some((t) => val.includes(t)), + selectorStartsWith: (val: string, target: string[]) => + target.some((t) => val.startsWith(t)), + selectorEndsWith: (val: string, target: string[]) => + target.some((t) => val.endsWith(t)), +}; diff --git a/frontend/app/components/Session_/Player/Controls/timeline.module.css b/frontend/app/components/Session_/Player/Controls/timeline.module.css index 4830dac96..a067d6b8e 100644 --- a/frontend/app/components/Session_/Player/Controls/timeline.module.css +++ b/frontend/app/components/Session_/Player/Controls/timeline.module.css @@ -49,24 +49,6 @@ z-index: 2; } - -.event { - position: absolute; - width: 2px; - height: 10px; - background: $main; - z-index: 3; - pointer-events: none; - /* top: 0; */ - /* bottom: 0; */ - /* &:hover { - width: 10px; - height: 10px; - margin-left: -6px; - z-index: 1; - };*/ -} - /* .event.click, .event.input { background: $green; } diff --git a/frontend/app/mstore/sessionStore.ts b/frontend/app/mstore/sessionStore.ts index a0301adee..c283a465f 100644 --- a/frontend/app/mstore/sessionStore.ts +++ b/frontend/app/mstore/sessionStore.ts @@ -15,9 +15,8 @@ import { loadFile } from 'App/player/web/network/loadFiles'; import { LAST_7_DAYS } from 'Types/app/period'; import { filterMap } from 'App/mstore/searchStore'; import { getDateRangeFromValue } from 'App/dateRange'; -import { clean as cleanParams } from '../api_client'; import { searchStore, searchStoreLive } from './index'; - +import { checkEventWithFilters } from '@/components/Session_/Player/Controls/checkEventWithFilters'; const range = getDateRangeFromValue(LAST_7_DAYS); const defaultDateFilters = { @@ -339,7 +338,14 @@ export default class SessionStore { const eventsData: Record = {}; try { const evData = await sessionService.getSessionEvents(sessionId); - Object.assign(eventsData, evData); + + Object.assign(eventsData, { + ...evData, + events: evData.events.map((e) => ({ + ...e, + isHighlighted: checkEventWithFilters(e, searchStore.instance.filters) + })) + }); } catch (e) { console.error('Failed to fetch events', e); } diff --git a/frontend/app/mstore/uiPlayerStore.ts b/frontend/app/mstore/uiPlayerStore.ts index 3f9466b08..172b85b38 100644 --- a/frontend/app/mstore/uiPlayerStore.ts +++ b/frontend/app/mstore/uiPlayerStore.ts @@ -53,6 +53,8 @@ export const blockValues = [ export default class UiPlayerStore { fullscreen = false; + showOnlySearchEvents = false; + showSearchEventsSwitchButton = false; bottomBlock = 0; @@ -145,4 +147,12 @@ export default class UiPlayerStore { setZoomTab = (tab: 'overview' | 'journey' | 'issues' | 'errors') => { this.zoomTab = tab; }; + + setShowOnlySearchEvents = (show: boolean) => { + this.showOnlySearchEvents = show; + }; + + setSearchEventsSwitchButton = (show: boolean) => { + this.showSearchEventsSwitchButton = show; + }; } diff --git a/frontend/app/types/session/event.ts b/frontend/app/types/session/event.ts index 982d3ca2b..2eeb568c4 100644 --- a/frontend/app/types/session/event.ts +++ b/frontend/app/types/session/event.ts @@ -51,6 +51,7 @@ interface IEvent { path: string; label: string; }; + isHighlighted?: boolean; } interface ConsoleEvent extends IEvent { @@ -118,6 +119,8 @@ class Event { messageId: IEvent['messageId']; + isHighlighted: IEvent['isHighlighted']; + constructor(event: IEvent) { Object.assign(this, { time: event.time, @@ -125,6 +128,7 @@ class Event { key: event.key, tabId: event.tabId, messageId: event.messageId, + isHighlighted: event.isHighlighted, target: { path: event.target?.path || event.targetPath, label: event.target?.label, @@ -178,12 +182,15 @@ export class Click extends Event { selector: string; + isHighlighted: boolean | undefined = false; + constructor(evt: ClickEvent, isClickRage?: boolean) { super(evt); this.targetContent = evt.targetContent; this.count = evt.count; this.hesitation = evt.hesitation; this.selector = evt.selector; + this.isHighlighted = evt.isHighlighted; if (isClickRage) { this.type = CLICKRAGE; } From 360d6ca382a602c7697a331dcf9671084a6bc4a6 Mon Sep 17 00:00:00 2001 From: nick-delirium Date: Mon, 5 May 2025 17:37:54 +0200 Subject: [PATCH 4/6] ui: fix endpointing sankey main node calculation --- frontend/app/components/Charts/SankeyChart.tsx | 6 ++++-- .../Dashboard/components/WidgetChart/WidgetChart.tsx | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/frontend/app/components/Charts/SankeyChart.tsx b/frontend/app/components/Charts/SankeyChart.tsx index 9f2ca31c4..4142e395e 100644 --- a/frontend/app/components/Charts/SankeyChart.tsx +++ b/frontend/app/components/Charts/SankeyChart.tsx @@ -34,11 +34,12 @@ interface Props { onChartClick?: (filters: any[]) => void; isUngrouped?: boolean; inGrid?: boolean; + startPoint: 'end' | 'start' } const EChartsSankey: React.FC = (props) => { const { t } = useTranslation(); - const { data, height = 240, onChartClick, isUngrouped } = props; + const { data, height = 240, onChartClick, isUngrouped, startPoint } = props; const chartRef = React.useRef(null); const [finalNodeCount, setFinalNodeCount] = React.useState(data.nodes.length); @@ -110,8 +111,9 @@ const EChartsSankey: React.FC = (props) => { if (echartNodes.length === 0) return; + const mainNodeLink = startPoint === 'end' ? echartNodes.findIndex(n => n.id === 0) : 0; const startNodeValue = echartLinks - .filter((link) => link.source === 0) + .filter((link) => startPoint === 'start' ? link.source === mainNodeLink : link.target === mainNodeLink) .reduce((sum, link) => sum + link.value, 0); Object.keys(nodeValues).forEach((nodeId) => { diff --git a/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx b/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx index 4b0313ef2..0460053a5 100644 --- a/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx +++ b/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx @@ -538,6 +538,7 @@ function WidgetChart(props: Props) { dashboardStore.drillDownFilter.merge({ filters, page: 1 }); }} isUngrouped={isUngrouped} + startPoint={metric.startType} /> ); } From 5ee2b125c8343730d178d0ec030de2d7e6afda57 Mon Sep 17 00:00:00 2001 From: nick-delirium Date: Tue, 6 May 2025 11:08:32 +0200 Subject: [PATCH 5/6] ui: clips, set playercontent size for clip player --- .../Session/Player/ClipPlayer/ClipPlayerContent.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/app/components/Session/Player/ClipPlayer/ClipPlayerContent.tsx b/frontend/app/components/Session/Player/ClipPlayer/ClipPlayerContent.tsx index ce4284d03..6bc61da82 100644 --- a/frontend/app/components/Session/Player/ClipPlayer/ClipPlayerContent.tsx +++ b/frontend/app/components/Session/Player/ClipPlayer/ClipPlayerContent.tsx @@ -56,9 +56,9 @@ function ClipPlayerContent(props: Props) { return (
-
+
From a3fdad3de170b68d63dde68b89dd316826123b45 Mon Sep 17 00:00:00 2001 From: nick-delirium Date: Tue, 6 May 2025 11:29:55 +0200 Subject: [PATCH 6/6] ui: missing } for eventsblock --- frontend/app/components/Session_/EventsBlock/EventsBlock.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/app/components/Session_/EventsBlock/EventsBlock.tsx b/frontend/app/components/Session_/EventsBlock/EventsBlock.tsx index 643673bf7..940c257cd 100644 --- a/frontend/app/components/Session_/EventsBlock/EventsBlock.tsx +++ b/frontend/app/components/Session_/EventsBlock/EventsBlock.tsx @@ -104,6 +104,7 @@ function EventsBlock(props: IProps) { : false : true ).filter((e: any) => !e.noteId && e.type !== 'TABCHANGE' && uiPlayerStore.showOnlySearchEvents ? e.isHighlighted : true); + } }, [ filteredLength, query,