From f8c927512756a060e8bb697eb82fd4ca8d1452a3 Mon Sep 17 00:00:00 2001 From: Taha Yassine Kraiem Date: Fri, 16 May 2025 14:19:11 +0200 Subject: [PATCH 01/22] refactor(chalice): refactored events --- .../core/autocomplete/autocomplete.py | 56 ++--- .../core/autocomplete/autocomplete_ch.py | 18 +- api/chalicelib/core/events/__init__.py | 11 + .../core/{ => events}/events_mobile.py | 4 +- api/chalicelib/core/events/events_pg.py | 201 ++++++++++++++++++ .../core/metrics/heatmaps/heatmaps_ch.py | 2 +- .../modules/significance/significance.py | 107 +++++----- .../modules/significance/significance_ch.py | 36 ++-- api/chalicelib/core/sessions/sessions_ch.py | 63 +++--- .../core/sessions/sessions_legacy_mobil.py | 84 ++++---- api/chalicelib/core/sessions/sessions_pg.py | 155 +++++++------- .../core/sessions/sessions_replay.py | 4 +- api/env.default | 3 +- api/env.dev | 3 +- ee/api/.gitignore | 3 +- ee/api/clean-dev.sh | 3 +- 16 files changed, 481 insertions(+), 272 deletions(-) create mode 100644 api/chalicelib/core/events/__init__.py rename api/chalicelib/core/{ => events}/events_mobile.py (95%) create mode 100644 api/chalicelib/core/events/events_pg.py diff --git a/api/chalicelib/core/autocomplete/autocomplete.py b/api/chalicelib/core/autocomplete/autocomplete.py index 648f0e652..8b5222769 100644 --- a/api/chalicelib/core/autocomplete/autocomplete.py +++ b/api/chalicelib/core/autocomplete/autocomplete.py @@ -1,9 +1,9 @@ import logging + import schemas -from chalicelib.core import countries, events, metadata +from chalicelib.core import countries, metadata from chalicelib.utils import helper from chalicelib.utils import pg_client -from chalicelib.utils.event_filter_definition import Event logger = logging.getLogger(__name__) TABLE = "public.autocomplete" @@ -112,10 +112,10 @@ def __generic_query(typename, value_length=None): LIMIT 10;""" -def __generic_autocomplete(event: Event): +def __generic_autocomplete(event: str): def f(project_id, value, key=None, source=None): with pg_client.PostgresClient() as cur: - query = __generic_query(event.ui_type, value_length=len(value)) + query = __generic_query(event, value_length=len(value)) params = {"project_id": project_id, "value": helper.string_to_sql_like(value), "svalue": helper.string_to_sql_like("^" + value)} cur.execute(cur.mogrify(query, params)) @@ -148,8 +148,8 @@ def __errors_query(source=None, value_length=None): return f"""((SELECT DISTINCT ON(lg.message) lg.message AS value, source, - '{events.EventType.ERROR.ui_type}' AS type - FROM {events.EventType.ERROR.table} INNER JOIN public.errors AS lg USING (error_id) LEFT JOIN public.sessions AS s USING(session_id) + '{schemas.EventType.ERROR}' AS type + FROM events.errors INNER JOIN public.errors AS lg USING (error_id) LEFT JOIN public.sessions AS s USING(session_id) WHERE s.project_id = %(project_id)s AND lg.message ILIKE %(svalue)s @@ -160,8 +160,8 @@ def __errors_query(source=None, value_length=None): (SELECT DISTINCT ON(lg.name) lg.name AS value, source, - '{events.EventType.ERROR.ui_type}' AS type - FROM {events.EventType.ERROR.table} INNER JOIN public.errors AS lg USING (error_id) LEFT JOIN public.sessions AS s USING(session_id) + '{schemas.EventType.ERROR}' AS type + FROM events.errors INNER JOIN public.errors AS lg USING (error_id) LEFT JOIN public.sessions AS s USING(session_id) WHERE s.project_id = %(project_id)s AND lg.name ILIKE %(svalue)s @@ -172,8 +172,8 @@ def __errors_query(source=None, value_length=None): (SELECT DISTINCT ON(lg.message) lg.message AS value, source, - '{events.EventType.ERROR.ui_type}' AS type - FROM {events.EventType.ERROR.table} INNER JOIN public.errors AS lg USING (error_id) LEFT JOIN public.sessions AS s USING(session_id) + '{schemas.EventType.ERROR}' AS type + FROM events.errors INNER JOIN public.errors AS lg USING (error_id) LEFT JOIN public.sessions AS s USING(session_id) WHERE s.project_id = %(project_id)s AND lg.message ILIKE %(value)s @@ -184,8 +184,8 @@ def __errors_query(source=None, value_length=None): (SELECT DISTINCT ON(lg.name) lg.name AS value, source, - '{events.EventType.ERROR.ui_type}' AS type - FROM {events.EventType.ERROR.table} INNER JOIN public.errors AS lg USING (error_id) LEFT JOIN public.sessions AS s USING(session_id) + '{schemas.EventType.ERROR}' AS type + FROM events.errors INNER JOIN public.errors AS lg USING (error_id) LEFT JOIN public.sessions AS s USING(session_id) WHERE s.project_id = %(project_id)s AND lg.name ILIKE %(value)s @@ -195,8 +195,8 @@ def __errors_query(source=None, value_length=None): return f"""((SELECT DISTINCT ON(lg.message) lg.message AS value, source, - '{events.EventType.ERROR.ui_type}' AS type - FROM {events.EventType.ERROR.table} INNER JOIN public.errors AS lg USING (error_id) LEFT JOIN public.sessions AS s USING(session_id) + '{schemas.EventType.ERROR}' AS type + FROM events.errors INNER JOIN public.errors AS lg USING (error_id) LEFT JOIN public.sessions AS s USING(session_id) WHERE s.project_id = %(project_id)s AND lg.message ILIKE %(svalue)s @@ -207,8 +207,8 @@ def __errors_query(source=None, value_length=None): (SELECT DISTINCT ON(lg.name) lg.name AS value, source, - '{events.EventType.ERROR.ui_type}' AS type - FROM {events.EventType.ERROR.table} INNER JOIN public.errors AS lg USING (error_id) LEFT JOIN public.sessions AS s USING(session_id) + '{schemas.EventType.ERROR}' AS type + FROM events.errors INNER JOIN public.errors AS lg USING (error_id) LEFT JOIN public.sessions AS s USING(session_id) WHERE s.project_id = %(project_id)s AND lg.name ILIKE %(svalue)s @@ -233,8 +233,8 @@ def __search_errors_mobile(project_id, value, key=None, source=None): if len(value) > 2: query = f"""(SELECT DISTINCT ON(lg.reason) lg.reason AS value, - '{events.EventType.CRASH_MOBILE.ui_type}' AS type - FROM {events.EventType.CRASH_MOBILE.table} INNER JOIN public.crashes_ios AS lg USING (crash_ios_id) LEFT JOIN public.sessions AS s USING(session_id) + '{schemas.EventType.ERROR_MOBILE}' AS type + FROM events_common.crashes INNER JOIN public.crashes_ios AS lg USING (crash_ios_id) LEFT JOIN public.sessions AS s USING(session_id) WHERE s.project_id = %(project_id)s AND lg.project_id = %(project_id)s @@ -243,8 +243,8 @@ def __search_errors_mobile(project_id, value, key=None, source=None): UNION ALL (SELECT DISTINCT ON(lg.name) lg.name AS value, - '{events.EventType.CRASH_MOBILE.ui_type}' AS type - FROM {events.EventType.CRASH_MOBILE.table} INNER JOIN public.crashes_ios AS lg USING (crash_ios_id) LEFT JOIN public.sessions AS s USING(session_id) + '{schemas.EventType.ERROR_MOBILE}' AS type + FROM events_common.crashes INNER JOIN public.crashes_ios AS lg USING (crash_ios_id) LEFT JOIN public.sessions AS s USING(session_id) WHERE s.project_id = %(project_id)s AND lg.project_id = %(project_id)s @@ -253,8 +253,8 @@ def __search_errors_mobile(project_id, value, key=None, source=None): UNION ALL (SELECT DISTINCT ON(lg.reason) lg.reason AS value, - '{events.EventType.CRASH_MOBILE.ui_type}' AS type - FROM {events.EventType.CRASH_MOBILE.table} INNER JOIN public.crashes_ios AS lg USING (crash_ios_id) LEFT JOIN public.sessions AS s USING(session_id) + '{schemas.EventType.ERROR_MOBILE}' AS type + FROM events_common.crashes INNER JOIN public.crashes_ios AS lg USING (crash_ios_id) LEFT JOIN public.sessions AS s USING(session_id) WHERE s.project_id = %(project_id)s AND lg.project_id = %(project_id)s @@ -263,8 +263,8 @@ def __search_errors_mobile(project_id, value, key=None, source=None): UNION ALL (SELECT DISTINCT ON(lg.name) lg.name AS value, - '{events.EventType.CRASH_MOBILE.ui_type}' AS type - FROM {events.EventType.CRASH_MOBILE.table} INNER JOIN public.crashes_ios AS lg USING (crash_ios_id) LEFT JOIN public.sessions AS s USING(session_id) + '{schemas.EventType.ERROR_MOBILE}' AS type + FROM events_common.crashes INNER JOIN public.crashes_ios AS lg USING (crash_ios_id) LEFT JOIN public.sessions AS s USING(session_id) WHERE s.project_id = %(project_id)s AND lg.project_id = %(project_id)s @@ -273,8 +273,8 @@ def __search_errors_mobile(project_id, value, key=None, source=None): else: query = f"""(SELECT DISTINCT ON(lg.reason) lg.reason AS value, - '{events.EventType.CRASH_MOBILE.ui_type}' AS type - FROM {events.EventType.CRASH_MOBILE.table} INNER JOIN public.crashes_ios AS lg USING (crash_ios_id) LEFT JOIN public.sessions AS s USING(session_id) + '{schemas.EventType.ERROR_MOBILE}' AS type + FROM events_common.crashes INNER JOIN public.crashes_ios AS lg USING (crash_ios_id) LEFT JOIN public.sessions AS s USING(session_id) WHERE s.project_id = %(project_id)s AND lg.project_id = %(project_id)s @@ -283,8 +283,8 @@ def __search_errors_mobile(project_id, value, key=None, source=None): UNION ALL (SELECT DISTINCT ON(lg.name) lg.name AS value, - '{events.EventType.CRASH_MOBILE.ui_type}' AS type - FROM {events.EventType.CRASH_MOBILE.table} INNER JOIN public.crashes_ios AS lg USING (crash_ios_id) LEFT JOIN public.sessions AS s USING(session_id) + '{schemas.EventType.ERROR_MOBILE}' AS type + FROM events_common.crashes INNER JOIN public.crashes_ios AS lg USING (crash_ios_id) LEFT JOIN public.sessions AS s USING(session_id) WHERE s.project_id = %(project_id)s AND lg.project_id = %(project_id)s diff --git a/api/chalicelib/core/autocomplete/autocomplete_ch.py b/api/chalicelib/core/autocomplete/autocomplete_ch.py index cbabf3ff5..daba725ce 100644 --- a/api/chalicelib/core/autocomplete/autocomplete_ch.py +++ b/api/chalicelib/core/autocomplete/autocomplete_ch.py @@ -1,9 +1,9 @@ import logging + import schemas -from chalicelib.core import countries, events, metadata +from chalicelib.core import countries, metadata from chalicelib.utils import ch_client from chalicelib.utils import helper, exp_ch_helper -from chalicelib.utils.event_filter_definition import Event logger = logging.getLogger(__name__) TABLE = "experimental.autocomplete" @@ -113,7 +113,7 @@ def __generic_query(typename, value_length=None): LIMIT 10;""" -def __generic_autocomplete(event: Event): +def __generic_autocomplete(event: str): def f(project_id, value, key=None, source=None): with ch_client.ClickHouseClient() as cur: query = __generic_query(event.ui_type, value_length=len(value)) @@ -149,7 +149,7 @@ def __pg_errors_query(source=None, value_length=None): return f"""((SELECT DISTINCT ON(message) message AS value, source, - '{events.EventType.ERROR.ui_type}' AS type + '{schemas.EventType.ERROR}' AS type FROM {MAIN_TABLE} WHERE project_id = %(project_id)s @@ -161,7 +161,7 @@ def __pg_errors_query(source=None, value_length=None): (SELECT DISTINCT ON(name) name AS value, source, - '{events.EventType.ERROR.ui_type}' AS type + '{schemas.EventType.ERROR}' AS type FROM {MAIN_TABLE} WHERE project_id = %(project_id)s @@ -172,7 +172,7 @@ def __pg_errors_query(source=None, value_length=None): (SELECT DISTINCT ON(message) message AS value, source, - '{events.EventType.ERROR.ui_type}' AS type + '{schemas.EventType.ERROR}' AS type FROM {MAIN_TABLE} WHERE project_id = %(project_id)s @@ -183,7 +183,7 @@ def __pg_errors_query(source=None, value_length=None): (SELECT DISTINCT ON(name) name AS value, source, - '{events.EventType.ERROR.ui_type}' AS type + '{schemas.EventType.ERROR}' AS type FROM {MAIN_TABLE} WHERE project_id = %(project_id)s @@ -193,7 +193,7 @@ def __pg_errors_query(source=None, value_length=None): return f"""((SELECT DISTINCT ON(message) message AS value, source, - '{events.EventType.ERROR.ui_type}' AS type + '{schemas.EventType.ERROR}' AS type FROM {MAIN_TABLE} WHERE project_id = %(project_id)s @@ -204,7 +204,7 @@ def __pg_errors_query(source=None, value_length=None): (SELECT DISTINCT ON(name) name AS value, source, - '{events.EventType.ERROR.ui_type}' AS type + '{schemas.EventType.ERROR}' AS type FROM {MAIN_TABLE} WHERE project_id = %(project_id)s diff --git a/api/chalicelib/core/events/__init__.py b/api/chalicelib/core/events/__init__.py new file mode 100644 index 000000000..d4df51f72 --- /dev/null +++ b/api/chalicelib/core/events/__init__.py @@ -0,0 +1,11 @@ +import logging + +from decouple import config + +logger = logging.getLogger(__name__) + +if config("EXP_EVENTS_REPLAY", cast=bool, default=False): + logger.info(">>> Using experimental events replay") + from . import events_ch as events +else: + from . import events_pg as events diff --git a/api/chalicelib/core/events_mobile.py b/api/chalicelib/core/events/events_mobile.py similarity index 95% rename from api/chalicelib/core/events_mobile.py rename to api/chalicelib/core/events/events_mobile.py index 166a7b633..346ab7832 100644 --- a/api/chalicelib/core/events_mobile.py +++ b/api/chalicelib/core/events/events_mobile.py @@ -1,5 +1,5 @@ from chalicelib.utils import pg_client, helper -from chalicelib.core import events +from . import events def get_customs_by_session_id(session_id, project_id): @@ -58,7 +58,7 @@ def get_crashes_by_session_id(session_id): with pg_client.PostgresClient() as cur: cur.execute(cur.mogrify(f""" SELECT cr.*,uc.*, cr.timestamp - s.start_ts AS time - FROM {events.EventType.CRASH_MOBILE.table} AS cr + FROM events_common.crashes AS cr INNER JOIN public.crashes_ios AS uc USING (crash_ios_id) INNER JOIN public.sessions AS s USING (session_id) WHERE diff --git a/api/chalicelib/core/events/events_pg.py b/api/chalicelib/core/events/events_pg.py new file mode 100644 index 000000000..62c599b6e --- /dev/null +++ b/api/chalicelib/core/events/events_pg.py @@ -0,0 +1,201 @@ +from functools import cache +from typing import Optional + +import schemas +from chalicelib.core import issues +from chalicelib.core.autocomplete import autocomplete +from chalicelib.core.sessions import sessions_metas +from chalicelib.utils import pg_client, helper +from chalicelib.utils.TimeUTC import TimeUTC +from chalicelib.utils.event_filter_definition import SupportedFilter + + +def get_customs_by_session_id(session_id, project_id): + with pg_client.PostgresClient() as cur: + cur.execute(cur.mogrify(""" \ + SELECT c.*, + 'CUSTOM' AS type + FROM events_common.customs AS c + WHERE c.session_id = %(session_id)s + ORDER BY c.timestamp;""", + {"project_id": project_id, "session_id": session_id}) + ) + rows = cur.fetchall() + return helper.dict_to_camel_case(rows) + + +def __merge_cells(rows, start, count, replacement): + rows[start] = replacement + rows = rows[:start + 1] + rows[start + count:] + return rows + + +def __get_grouped_clickrage(rows, session_id, project_id): + click_rage_issues = issues.get_by_session_id(session_id=session_id, issue_type="click_rage", project_id=project_id) + if len(click_rage_issues) == 0: + return rows + + for c in click_rage_issues: + merge_count = c.get("payload") + if merge_count is not None: + merge_count = merge_count.get("Count", 3) + else: + merge_count = 3 + for i in range(len(rows)): + if rows[i]["timestamp"] == c["timestamp"]: + rows = __merge_cells(rows=rows, + start=i, + count=merge_count, + replacement={**rows[i], "type": "CLICKRAGE", "count": merge_count}) + break + return rows + + +def get_by_session_id(session_id, project_id, group_clickrage=False, event_type: Optional[schemas.EventType] = None): + with pg_client.PostgresClient() as cur: + rows = [] + if event_type is None or event_type == schemas.EventType.CLICK: + cur.execute(cur.mogrify(""" \ + SELECT c.*, + 'CLICK' AS type + FROM events.clicks AS c + WHERE c.session_id = %(session_id)s + ORDER BY c.timestamp;""", + {"project_id": project_id, "session_id": session_id}) + ) + rows += cur.fetchall() + if group_clickrage: + rows = __get_grouped_clickrage(rows=rows, session_id=session_id, project_id=project_id) + if event_type is None or event_type == schemas.EventType.INPUT: + cur.execute(cur.mogrify(""" + SELECT i.*, + 'INPUT' AS type + FROM events.inputs AS i + WHERE i.session_id = %(session_id)s + ORDER BY i.timestamp;""", + {"project_id": project_id, "session_id": session_id}) + ) + rows += cur.fetchall() + if event_type is None or event_type == schemas.EventType.LOCATION: + cur.execute(cur.mogrify(""" \ + SELECT l.*, + l.path AS value, + l.path AS url, + 'LOCATION' AS type + FROM events.pages AS l + WHERE + l.session_id = %(session_id)s + ORDER BY l.timestamp;""", {"project_id": project_id, "session_id": session_id})) + rows += cur.fetchall() + rows = helper.list_to_camel_case(rows) + rows = sorted(rows, key=lambda k: (k["timestamp"], k["messageId"])) + return rows + + +def _search_tags(project_id, value, key=None, source=None): + with pg_client.PostgresClient() as cur: + query = f""" + SELECT public.tags.name + 'TAG' AS type + FROM public.tags + WHERE public.tags.project_id = %(project_id)s + ORDER BY SIMILARITY(public.tags.name, %(value)s) DESC + LIMIT 10 + """ + query = cur.mogrify(query, {'project_id': project_id, 'value': value}) + cur.execute(query) + results = helper.list_to_camel_case(cur.fetchall()) + return results + + +@cache +def supported_types(): + return { + schemas.EventType.CLICK: SupportedFilter(get=autocomplete.__generic_autocomplete(schemas.EventType.CLICK), + query=autocomplete.__generic_query(typename=schemas.EventType.CLICK)), + schemas.EventType.INPUT: SupportedFilter(get=autocomplete.__generic_autocomplete(schemas.EventType.INPUT), + query=autocomplete.__generic_query(typename=schemas.EventType.INPUT)), + schemas.EventType.LOCATION: SupportedFilter(get=autocomplete.__generic_autocomplete(schemas.EventType.LOCATION), + query=autocomplete.__generic_query( + typename=schemas.EventType.LOCATION)), + schemas.EventType.CUSTOM: SupportedFilter(get=autocomplete.__generic_autocomplete(schemas.EventType.CUSTOM), + query=autocomplete.__generic_query( + typename=schemas.EventType.CUSTOM)), + schemas.EventType.REQUEST: SupportedFilter(get=autocomplete.__generic_autocomplete(schemas.EventType.REQUEST), + query=autocomplete.__generic_query( + typename=schemas.EventType.REQUEST)), + schemas.EventType.GRAPHQL: SupportedFilter(get=autocomplete.__generic_autocomplete(schemas.EventType.GRAPHQL), + query=autocomplete.__generic_query( + typename=schemas.EventType.GRAPHQL)), + schemas.EventType.STATE_ACTION: SupportedFilter( + get=autocomplete.__generic_autocomplete(schemas.EventType.STATEACTION), + query=autocomplete.__generic_query( + typename=schemas.EventType.STATE_ACTION)), + schemas.EventType.TAG: SupportedFilter(get=_search_tags, query=None), + schemas.EventType.ERROR: SupportedFilter(get=autocomplete.__search_errors, + query=None), + schemas.FilterType.METADATA: SupportedFilter(get=autocomplete.__search_metadata, + query=None), + # MOBILE + schemas.EventType.CLICK_MOBILE: SupportedFilter( + get=autocomplete.__generic_autocomplete(schemas.EventType.CLICK_MOBILE), + query=autocomplete.__generic_query( + typename=schemas.EventType.CLICK_MOBILE)), + schemas.EventType.SWIPE_MOBILE: SupportedFilter( + get=autocomplete.__generic_autocomplete(schemas.EventType.SWIPE_MOBILE), + query=autocomplete.__generic_query( + typename=schemas.EventType.SWIPE_MOBILE)), + schemas.EventType.INPUT_MOBILE: SupportedFilter( + get=autocomplete.__generic_autocomplete(schemas.EventType.INPUT_MOBILE), + query=autocomplete.__generic_query( + typename=schemas.EventType.INPUT_MOBILE)), + schemas.EventType.VIEW_MOBILE: SupportedFilter( + get=autocomplete.__generic_autocomplete(schemas.EventType.VIEW_MOBILE), + query=autocomplete.__generic_query( + typename=schemas.EventType.VIEW_MOBILE)), + schemas.EventType.CUSTOM_MOBILE: SupportedFilter( + get=autocomplete.__generic_autocomplete(schemas.EventType.CUSTOM_MOBILE), + query=autocomplete.__generic_query( + typename=schemas.EventType.CUSTOM_MOBILE)), + schemas.EventType.REQUEST_MOBILE: SupportedFilter( + get=autocomplete.__generic_autocomplete(schemas.EventType.REQUEST_MOBILE), + query=autocomplete.__generic_query( + typename=schemas.EventType.REQUEST_MOBILE)), + schemas.EventType.ERROR_MOBILE: SupportedFilter(get=autocomplete.__search_errors_mobile, + query=None), + } + + +def get_errors_by_session_id(session_id, project_id): + with pg_client.PostgresClient() as cur: + cur.execute(cur.mogrify(f"""\ + SELECT er.*,ur.*, er.timestamp - s.start_ts AS time + FROM events.errors AS er INNER JOIN public.errors AS ur USING (error_id) INNER JOIN public.sessions AS s USING (session_id) + WHERE er.session_id = %(session_id)s AND s.project_id=%(project_id)s + ORDER BY timestamp;""", {"session_id": session_id, "project_id": project_id})) + errors = cur.fetchall() + for e in errors: + e["stacktrace_parsed_at"] = TimeUTC.datetime_to_timestamp(e["stacktrace_parsed_at"]) + return helper.list_to_camel_case(errors) + + +def search(text, event_type, project_id, source, key): + if not event_type: + return {"data": autocomplete.__get_autocomplete_table(text, project_id)} + + if event_type in supported_types().keys(): + rows = supported_types()[event_type].get(project_id=project_id, value=text, key=key, source=source) + elif event_type + "_MOBILE" in supported_types().keys(): + rows = supported_types()[event_type + "_MOBILE"].get(project_id=project_id, value=text, key=key, source=source) + elif event_type in sessions_metas.supported_types().keys(): + return sessions_metas.search(text, event_type, project_id) + elif event_type.endswith("_IOS") \ + and event_type[:-len("_IOS")] in sessions_metas.supported_types().keys(): + return sessions_metas.search(text, event_type, project_id) + elif event_type.endswith("_MOBILE") \ + and event_type[:-len("_MOBILE")] in sessions_metas.supported_types().keys(): + return sessions_metas.search(text, event_type, project_id) + else: + return {"errors": ["unsupported event"]} + + return {"data": rows} diff --git a/api/chalicelib/core/metrics/heatmaps/heatmaps_ch.py b/api/chalicelib/core/metrics/heatmaps/heatmaps_ch.py index 519f6ddcb..e7399f230 100644 --- a/api/chalicelib/core/metrics/heatmaps/heatmaps_ch.py +++ b/api/chalicelib/core/metrics/heatmaps/heatmaps_ch.py @@ -3,7 +3,7 @@ import logging from decouple import config import schemas -from chalicelib.core import events +from chalicelib.core.events import events from chalicelib.core.metrics.modules import sessions, sessions_mobs from chalicelib.utils import sql_helper as sh diff --git a/api/chalicelib/core/metrics/modules/significance/significance.py b/api/chalicelib/core/metrics/modules/significance/significance.py index 38815c806..8352feb94 100644 --- a/api/chalicelib/core/metrics/modules/significance/significance.py +++ b/api/chalicelib/core/metrics/modules/significance/significance.py @@ -7,7 +7,8 @@ from typing import List from psycopg2.extras import RealDictRow import schemas -from chalicelib.core import events, metadata +from chalicelib.core import metadata +from chalicelib.core.events import events from chalicelib.utils import pg_client, helper from chalicelib.utils import sql_helper as sh @@ -76,10 +77,10 @@ def get_stages_and_events(filter_d: schemas.CardSeriesFilterSchema, project_id) values["maxDuration"] = f.value[1] elif filter_type == schemas.FilterType.REFERRER: # events_query_part = events_query_part + f"INNER JOIN events.pages AS p USING(session_id)" - filter_extra_from = [f"INNER JOIN {events.EventType.LOCATION.table} AS p USING(session_id)"] + filter_extra_from = [f"INNER JOIN {"events.pages"} AS p USING(session_id)"] first_stage_extra_constraints.append( sh.multi_conditions(f"p.base_referrer {op} %({f_k})s", f.value, is_not=is_not, value_key=f_k)) - elif filter_type == events.EventType.METADATA.ui_type: + elif filter_type == schemas.FilterType.METADATA: if meta_keys is None: meta_keys = metadata.get(project_id=project_id) meta_keys = {m["key"]: m["index"] for m in meta_keys} @@ -121,31 +122,31 @@ def get_stages_and_events(filter_d: schemas.CardSeriesFilterSchema, project_id) op = sh.get_sql_operator(s.operator) # event_type = s["type"].upper() event_type = s.type - if event_type == events.EventType.CLICK.ui_type: - next_table = events.EventType.CLICK.table - next_col_name = events.EventType.CLICK.column - elif event_type == events.EventType.INPUT.ui_type: - next_table = events.EventType.INPUT.table - next_col_name = events.EventType.INPUT.column - elif event_type == events.EventType.LOCATION.ui_type: - next_table = events.EventType.LOCATION.table - next_col_name = events.EventType.LOCATION.column - elif event_type == events.EventType.CUSTOM.ui_type: - next_table = events.EventType.CUSTOM.table - next_col_name = events.EventType.CUSTOM.column + if event_type == schemas.EventType.CLICK: + next_table = "events.clicks" + next_col_name = "label" + elif event_type == schemas.EventType.INPUT: + next_table = "events.inputs" + next_col_name = "label" + elif event_type == schemas.EventType.LOCATION: + next_table = "events.pages" + next_col_name = "path" + elif event_type == schemas.EventType.CUSTOM: + next_table = "events_common.customs" + next_col_name = "name" # IOS -------------- - elif event_type == events.EventType.CLICK_MOBILE.ui_type: - next_table = events.EventType.CLICK_MOBILE.table - next_col_name = events.EventType.CLICK_MOBILE.column - elif event_type == events.EventType.INPUT_MOBILE.ui_type: - next_table = events.EventType.INPUT_MOBILE.table - next_col_name = events.EventType.INPUT_MOBILE.column - elif event_type == events.EventType.VIEW_MOBILE.ui_type: - next_table = events.EventType.VIEW_MOBILE.table - next_col_name = events.EventType.VIEW_MOBILE.column - elif event_type == events.EventType.CUSTOM_MOBILE.ui_type: - next_table = events.EventType.CUSTOM_MOBILE.table - next_col_name = events.EventType.CUSTOM_MOBILE.column + elif event_type == schemas.EventType.CLICK_MOBILE: + next_table = "events_ios.taps" + next_col_name = "label" + elif event_type == schemas.EventType.INPUT_MOBILE: + next_table = "events_ios.inputs" + next_col_name = "label" + elif event_type == schemas.EventType.VIEW_MOBILE: + next_table = "events_ios.views" + next_col_name = "name" + elif event_type == schemas.EventType.CUSTOM_MOBILE: + next_table = "events_common.customs" + next_col_name = "name" else: logger.warning(f"=================UNDEFINED:{event_type}") continue @@ -297,10 +298,10 @@ def get_simple_funnel(filter_d: schemas.CardSeriesFilterSchema, project: schemas values["maxDuration"] = f.value[1] elif filter_type == schemas.FilterType.REFERRER: # events_query_part = events_query_part + f"INNER JOIN events.pages AS p USING(session_id)" - filter_extra_from = [f"INNER JOIN {events.EventType.LOCATION.table} AS p USING(session_id)"] + filter_extra_from = [f"INNER JOIN {"events.pages"} AS p USING(session_id)"] first_stage_extra_constraints.append( sh.multi_conditions(f"p.base_referrer {op} %({f_k})s", f.value, is_not=is_not, value_key=f_k)) - elif filter_type == events.EventType.METADATA.ui_type: + elif filter_type == schemas.FilterType.METADATA: if meta_keys is None: meta_keys = metadata.get(project_id=project.project_id) meta_keys = {m["key"]: m["index"] for m in meta_keys} @@ -342,31 +343,31 @@ def get_simple_funnel(filter_d: schemas.CardSeriesFilterSchema, project: schemas op = sh.get_sql_operator(s.operator) # event_type = s["type"].upper() event_type = s.type - if event_type == events.EventType.CLICK.ui_type: - next_table = events.EventType.CLICK.table - next_col_name = events.EventType.CLICK.column - elif event_type == events.EventType.INPUT.ui_type: - next_table = events.EventType.INPUT.table - next_col_name = events.EventType.INPUT.column - elif event_type == events.EventType.LOCATION.ui_type: - next_table = events.EventType.LOCATION.table - next_col_name = events.EventType.LOCATION.column - elif event_type == events.EventType.CUSTOM.ui_type: - next_table = events.EventType.CUSTOM.table - next_col_name = events.EventType.CUSTOM.column + if event_type == schemas.EventType.CLICK: + next_table = "events.clicks" + next_col_name = "label" + elif event_type == schemas.EventType.INPUT: + next_table = "events.inputs" + next_col_name = "label" + elif event_type == schemas.EventType.LOCATION: + next_table = "events.pages" + next_col_name = "path" + elif event_type == schemas.EventType.CUSTOM: + next_table = "events_common.customs" + next_col_name = "name" # IOS -------------- - elif event_type == events.EventType.CLICK_MOBILE.ui_type: - next_table = events.EventType.CLICK_MOBILE.table - next_col_name = events.EventType.CLICK_MOBILE.column - elif event_type == events.EventType.INPUT_MOBILE.ui_type: - next_table = events.EventType.INPUT_MOBILE.table - next_col_name = events.EventType.INPUT_MOBILE.column - elif event_type == events.EventType.VIEW_MOBILE.ui_type: - next_table = events.EventType.VIEW_MOBILE.table - next_col_name = events.EventType.VIEW_MOBILE.column - elif event_type == events.EventType.CUSTOM_MOBILE.ui_type: - next_table = events.EventType.CUSTOM_MOBILE.table - next_col_name = events.EventType.CUSTOM_MOBILE.column + elif event_type == schemas.EventType.CLICK_MOBILE: + next_table = "events_ios.taps" + next_col_name = "label" + elif event_type == schemas.EventType.INPUT_MOBILE: + next_table = "events_ios.inputs" + next_col_name = "label" + elif event_type == schemas.EventType.VIEW_MOBILE: + next_table = "events_ios.views" + next_col_name = "name" + elif event_type == schemas.EventType.CUSTOM_MOBILE: + next_table = "events_common.customs" + next_col_name = "name" else: logger.warning(f"=================UNDEFINED:{event_type}") continue diff --git a/api/chalicelib/core/metrics/modules/significance/significance_ch.py b/api/chalicelib/core/metrics/modules/significance/significance_ch.py index 0cceaf928..5d4e89182 100644 --- a/api/chalicelib/core/metrics/modules/significance/significance_ch.py +++ b/api/chalicelib/core/metrics/modules/significance/significance_ch.py @@ -8,7 +8,7 @@ from chalicelib.utils import ch_client from chalicelib.utils import exp_ch_helper from chalicelib.utils import helper from chalicelib.utils import sql_helper as sh -from chalicelib.core import events +from chalicelib.core.events import events logger = logging.getLogger(__name__) @@ -82,7 +82,7 @@ def get_simple_funnel(filter_d: schemas.CardSeriesFilterSchema, project: schemas elif filter_type == schemas.FilterType.REFERRER: constraints.append( sh.multi_conditions(f"s.base_referrer {op} %({f_k})s", f.value, is_not=is_not, value_key=f_k)) - elif filter_type == events.EventType.METADATA.ui_type: + elif filter_type == schemas.FilterType.METADATA: if meta_keys is None: meta_keys = metadata.get(project_id=project.project_id) meta_keys = {m["key"]: m["index"] for m in meta_keys} @@ -125,29 +125,29 @@ def get_simple_funnel(filter_d: schemas.CardSeriesFilterSchema, project: schemas e_k = f"e_value{i}" event_type = s.type next_event_type = exp_ch_helper.get_event_type(event_type, platform=platform) - if event_type == events.EventType.CLICK.ui_type: + if event_type == schemas.EventType.CLICK: if platform == "web": - next_col_name = events.EventType.CLICK.column + next_col_name = "label" if not is_any: if schemas.ClickEventExtraOperator.has_value(s.operator): specific_condition = sh.multi_conditions(f"selector {op} %({e_k})s", s.value, value_key=e_k) else: - next_col_name = events.EventType.CLICK_MOBILE.column - elif event_type == events.EventType.INPUT.ui_type: - next_col_name = events.EventType.INPUT.column - elif event_type == events.EventType.LOCATION.ui_type: + next_col_name = "label" + elif event_type == schemas.EventType.INPUT: + next_col_name = "label" + elif event_type == schemas.EventType.LOCATION: next_col_name = 'url_path' - elif event_type == events.EventType.CUSTOM.ui_type: - next_col_name = events.EventType.CUSTOM.column + elif event_type == schemas.EventType.CUSTOM: + next_col_name = "name" # IOS -------------- - elif event_type == events.EventType.CLICK_MOBILE.ui_type: - next_col_name = events.EventType.CLICK_MOBILE.column - elif event_type == events.EventType.INPUT_MOBILE.ui_type: - next_col_name = events.EventType.INPUT_MOBILE.column - elif event_type == events.EventType.VIEW_MOBILE.ui_type: - next_col_name = events.EventType.VIEW_MOBILE.column - elif event_type == events.EventType.CUSTOM_MOBILE.ui_type: - next_col_name = events.EventType.CUSTOM_MOBILE.column + elif event_type == schemas.EventType.CLICK_MOBILE: + next_col_name = "label" + elif event_type == schemas.EventType.INPUT_MOBILE: + next_col_name = "label" + elif event_type == schemas.EventType.VIEW_MOBILE: + next_col_name = "name" + elif event_type == schemas.EventType.CUSTOM_MOBILE: + next_col_name = "name" else: logger.warning(f"=================UNDEFINED:{event_type}") continue diff --git a/api/chalicelib/core/sessions/sessions_ch.py b/api/chalicelib/core/sessions/sessions_ch.py index 8d1929c70..3801609dc 100644 --- a/api/chalicelib/core/sessions/sessions_ch.py +++ b/api/chalicelib/core/sessions/sessions_ch.py @@ -2,7 +2,8 @@ import logging from typing import List, Union import schemas -from chalicelib.core import events, metadata +from chalicelib.core import metadata +from chalicelib.core.events import events from . import performance_event, sessions_legacy from chalicelib.utils import pg_client, helper, metrics_helper, ch_client, exp_ch_helper from chalicelib.utils import sql_helper as sh @@ -521,7 +522,7 @@ def search_query_parts_ch(data: schemas.SessionsSearchPayloadSchema, error_statu ss_constraints.append( sh.multi_conditions(f"ms.base_referrer {op} toString(%({f_k})s)", f.value, is_not=is_not, value_key=f_k)) - elif filter_type == events.EventType.METADATA.ui_type: + elif filter_type == schemas.EventType.METADATA: # get metadata list only if you need it if meta_keys is None: meta_keys = metadata.get(project_id=project_id) @@ -668,10 +669,10 @@ def search_query_parts_ch(data: schemas.SessionsSearchPayloadSchema, error_statu **sh.multi_values(event.source, value_key=s_k), e_k: event.value[0] if len(event.value) > 0 else event.value} - if event_type == events.EventType.CLICK.ui_type: + if event_type == schemas.EventType.CLICK: event_from = event_from % f"{MAIN_EVENTS_TABLE} AS main " if platform == "web": - _column = events.EventType.CLICK.column + _column = "label" event_where.append( f"main.`$event_name`='{exp_ch_helper.get_event_type(event_type, platform=platform)}'") events_conditions.append({"type": event_where[-1]}) @@ -718,7 +719,7 @@ def search_query_parts_ch(data: schemas.SessionsSearchPayloadSchema, error_statu ) events_conditions[-1]["condition"] = event_where[-1] else: - _column = events.EventType.CLICK_MOBILE.column + _column = "label" event_where.append( f"main.`$event_name`='{exp_ch_helper.get_event_type(event_type, platform=platform)}'") events_conditions.append({"type": event_where[-1]}) @@ -737,10 +738,10 @@ def search_query_parts_ch(data: schemas.SessionsSearchPayloadSchema, error_statu ) events_conditions[-1]["condition"] = event_where[-1] - elif event_type == events.EventType.INPUT.ui_type: + elif event_type == schemas.EventType.INPUT: event_from = event_from % f"{MAIN_EVENTS_TABLE} AS main " if platform == "web": - _column = events.EventType.INPUT.column + _column = "label" event_where.append( f"main.`$event_name`='{exp_ch_helper.get_event_type(event_type, platform=platform)}'") events_conditions.append({"type": event_where[-1]}) @@ -765,7 +766,7 @@ def search_query_parts_ch(data: schemas.SessionsSearchPayloadSchema, error_statu full_args = {**full_args, **sh.multi_values(event.source, value_key=f"custom{i}")} else: - _column = events.EventType.INPUT_MOBILE.column + _column = "label" event_where.append( f"main.`$event_name`='{exp_ch_helper.get_event_type(event_type, platform=platform)}'") events_conditions.append({"type": event_where[-1]}) @@ -785,7 +786,7 @@ def search_query_parts_ch(data: schemas.SessionsSearchPayloadSchema, error_statu events_conditions[-1]["condition"] = event_where[-1] - elif event_type == events.EventType.LOCATION.ui_type: + elif event_type == schemas.EventType.LOCATION: event_from = event_from % f"{MAIN_EVENTS_TABLE} AS main " if platform == "web": _column = 'url_path' @@ -807,7 +808,7 @@ def search_query_parts_ch(data: schemas.SessionsSearchPayloadSchema, error_statu ) events_conditions[-1]["condition"] = event_where[-1] else: - _column = events.EventType.VIEW_MOBILE.column + _column = "name" event_where.append( f"main.`$event_name`='{exp_ch_helper.get_event_type(event_type, platform=platform)}'") events_conditions.append({"type": event_where[-1]}) @@ -824,9 +825,9 @@ def search_query_parts_ch(data: schemas.SessionsSearchPayloadSchema, error_statu event_where.append(sh.multi_conditions(f"main.{_column} {op} %({e_k})s", event.value, value_key=e_k)) events_conditions[-1]["condition"] = event_where[-1] - elif event_type == events.EventType.CUSTOM.ui_type: + elif event_type == schemas.EventType.CUSTOM: event_from = event_from % f"{MAIN_EVENTS_TABLE} AS main " - _column = events.EventType.CUSTOM.column + _column = "name" event_where.append( f"main.`$event_name`='{exp_ch_helper.get_event_type(event_type, platform=platform)}'") events_conditions.append({"type": event_where[-1]}) @@ -844,7 +845,7 @@ def search_query_parts_ch(data: schemas.SessionsSearchPayloadSchema, error_statu "main", "$properties", _column, op, event.value, e_k )) events_conditions[-1]["condition"] = event_where[-1] - elif event_type == events.EventType.REQUEST.ui_type: + elif event_type == schemas.EventType.REQUEST: event_from = event_from % f"{MAIN_EVENTS_TABLE} AS main " _column = 'url_path' event_where.append( @@ -865,9 +866,9 @@ def search_query_parts_ch(data: schemas.SessionsSearchPayloadSchema, error_statu )) events_conditions[-1]["condition"] = event_where[-1] - elif event_type == events.EventType.STATEACTION.ui_type: + elif event_type == schemas.EventType.STATE_ACTION: event_from = event_from % f"{MAIN_EVENTS_TABLE} AS main " - _column = events.EventType.STATEACTION.column + _column = "name" event_where.append( f"main.`$event_name`='{exp_ch_helper.get_event_type(event_type, platform=platform)}'") events_conditions.append({"type": event_where[-1]}) @@ -886,7 +887,7 @@ def search_query_parts_ch(data: schemas.SessionsSearchPayloadSchema, error_statu )) events_conditions[-1]["condition"] = event_where[-1] # TODO: isNot for ERROR - elif event_type == events.EventType.ERROR.ui_type: + elif event_type == schemas.EventType.ERROR: event_from = event_from % f"{MAIN_EVENTS_TABLE} AS main" events_extra_join = f"SELECT * FROM {MAIN_EVENTS_TABLE} AS main1 WHERE main1.project_id=%(project_id)s" event_where.append( @@ -911,8 +912,8 @@ def search_query_parts_ch(data: schemas.SessionsSearchPayloadSchema, error_statu events_conditions[-1]["condition"] = " AND ".join(events_conditions[-1]["condition"]) # ----- Mobile - elif event_type == events.EventType.CLICK_MOBILE.ui_type: - _column = events.EventType.CLICK_MOBILE.column + elif event_type == schemas.EventType.CLICK_MOBILE: + _column = "label" event_where.append( f"main.`$event_name`='{exp_ch_helper.get_event_type(event_type, platform=platform)}'") events_conditions.append({"type": event_where[-1]}) @@ -930,8 +931,8 @@ def search_query_parts_ch(data: schemas.SessionsSearchPayloadSchema, error_statu "main", "$properties", _column, op, event.value, e_k )) events_conditions[-1]["condition"] = event_where[-1] - elif event_type == events.EventType.INPUT_MOBILE.ui_type: - _column = events.EventType.INPUT_MOBILE.column + elif event_type == schemas.EventType.INPUT_MOBILE: + _column = "label" event_where.append( f"main.`$event_name`='{exp_ch_helper.get_event_type(event_type, platform=platform)}'") events_conditions.append({"type": event_where[-1]}) @@ -949,8 +950,8 @@ def search_query_parts_ch(data: schemas.SessionsSearchPayloadSchema, error_statu "main", "$properties", _column, op, event.value, e_k )) events_conditions[-1]["condition"] = event_where[-1] - elif event_type == events.EventType.VIEW_MOBILE.ui_type: - _column = events.EventType.VIEW_MOBILE.column + elif event_type == schemas.EventType.VIEW_MOBILE: + _column = "name" event_where.append( f"main.`$event_name`='{exp_ch_helper.get_event_type(event_type, platform=platform)}'") events_conditions.append({"type": event_where[-1]}) @@ -968,8 +969,8 @@ def search_query_parts_ch(data: schemas.SessionsSearchPayloadSchema, error_statu "main", "$properties", _column, op, event.value, e_k )) events_conditions[-1]["condition"] = event_where[-1] - elif event_type == events.EventType.CUSTOM_MOBILE.ui_type: - _column = events.EventType.CUSTOM_MOBILE.column + elif event_type == schemas.EventType.CUSTOM_MOBILE: + _column = "name" event_where.append( f"main.`$event_name`='{exp_ch_helper.get_event_type(event_type, platform=platform)}'") events_conditions.append({"type": event_where[-1]}) @@ -988,7 +989,7 @@ def search_query_parts_ch(data: schemas.SessionsSearchPayloadSchema, error_statu )) events_conditions[-1]["condition"] = event_where[-1] - elif event_type == events.EventType.REQUEST_MOBILE.ui_type: + elif event_type == schemas.EventType.REQUEST_MOBILE: event_from = event_from % f"{MAIN_EVENTS_TABLE} AS main " _column = 'url_path' event_where.append( @@ -1008,8 +1009,8 @@ def search_query_parts_ch(data: schemas.SessionsSearchPayloadSchema, error_statu "main", "$properties", _column, op, event.value, e_k )) events_conditions[-1]["condition"] = event_where[-1] - elif event_type == events.EventType.CRASH_MOBILE.ui_type: - _column = events.EventType.CRASH_MOBILE.column + elif event_type == schemas.EventType.ERROR_MOBILE: + _column = "name" event_where.append( f"main.`$event_name`='{exp_ch_helper.get_event_type(event_type, platform=platform)}'") events_conditions.append({"type": event_where[-1]}) @@ -1028,8 +1029,8 @@ def search_query_parts_ch(data: schemas.SessionsSearchPayloadSchema, error_statu "main", "$properties", _column, op, event.value, e_k )) events_conditions[-1]["condition"] = event_where[-1] - elif event_type == events.EventType.SWIPE_MOBILE.ui_type and platform != "web": - _column = events.EventType.SWIPE_MOBILE.column + elif event_type == schemas.EventType.SWIPE_MOBILE and platform != "web": + _column = "label" event_where.append( f"main.`$event_name`='{exp_ch_helper.get_event_type(event_type, platform=platform)}'") events_conditions.append({"type": event_where[-1]}) @@ -1230,7 +1231,7 @@ def search_query_parts_ch(data: schemas.SessionsSearchPayloadSchema, error_statu full_args = {**full_args, **sh.multi_values(f.value, value_key=e_k_f)} if f.type == schemas.GraphqlFilterType.GRAPHQL_NAME: event_where.append(json_condition( - "main", "$properties", events.EventType.GRAPHQL.column, op, f.value, e_k_f + "main", "$properties", "name", op, f.value, e_k_f )) events_conditions[-1]["condition"].append(event_where[-1]) elif f.type == schemas.GraphqlFilterType.GRAPHQL_METHOD: @@ -1253,7 +1254,7 @@ def search_query_parts_ch(data: schemas.SessionsSearchPayloadSchema, error_statu events_conditions[-1]["condition"] = " AND ".join(events_conditions[-1]["condition"]) elif event_type == schemas.EventType.EVENT: event_from = event_from % f"{MAIN_EVENTS_TABLE} AS main " - _column = events.EventType.CLICK.column + _column = "label" event_where.append(f"main.`$event_name`=%({e_k})s AND main.session_id>0") events_conditions.append({"type": event_where[-1], "condition": ""}) diff --git a/api/chalicelib/core/sessions/sessions_legacy_mobil.py b/api/chalicelib/core/sessions/sessions_legacy_mobil.py index 69044812c..e9b0cffe1 100644 --- a/api/chalicelib/core/sessions/sessions_legacy_mobil.py +++ b/api/chalicelib/core/sessions/sessions_legacy_mobil.py @@ -2,7 +2,8 @@ import ast import logging import schemas -from chalicelib.core import events, metadata, projects +from chalicelib.core import metadata, projects +from chalicelib.core.events import events from chalicelib.core.sessions import performance_event, sessions_favorite, sessions_legacy from chalicelib.utils import pg_client, helper, ch_client, exp_ch_helper from chalicelib.utils import sql_helper as sh @@ -410,7 +411,7 @@ def search_query_parts_ch(data: schemas.SessionsSearchPayloadSchema, error_statu ss_constraints.append( _multiple_conditions(f"ms.base_referrer {op} toString(%({f_k})s)", f.value, is_not=is_not, value_key=f_k)) - elif filter_type == events.EventType.METADATA.ui_type: + elif filter_type == schemas.EventType.METADATA: # get metadata list only if you need it if meta_keys is None: meta_keys = metadata.get(project_id=project_id) @@ -556,10 +557,10 @@ def search_query_parts_ch(data: schemas.SessionsSearchPayloadSchema, error_statu **_multiple_values(event.value, value_key=e_k), **_multiple_values(event.source, value_key=s_k)} - if event_type == events.EventType.CLICK.ui_type: + if event_type == schemas.EventType.CLICK: event_from = event_from % f"{MAIN_EVENTS_TABLE} AS main " if platform == "web": - _column = events.EventType.CLICK.column + _column = "label" event_where.append( f"main.event_type='{exp_ch_helper.get_event_type(event_type, platform=platform)}'") events_conditions.append({"type": event_where[-1]}) @@ -581,7 +582,7 @@ def search_query_parts_ch(data: schemas.SessionsSearchPayloadSchema, error_statu value_key=e_k)) events_conditions[-1]["condition"] = event_where[-1] else: - _column = events.EventType.CLICK_MOBILE.column + _column = "label" event_where.append( f"main.event_type='{exp_ch_helper.get_event_type(event_type, platform=platform)}'") events_conditions.append({"type": event_where[-1]}) @@ -598,10 +599,10 @@ def search_query_parts_ch(data: schemas.SessionsSearchPayloadSchema, error_statu value_key=e_k)) events_conditions[-1]["condition"] = event_where[-1] - elif event_type == events.EventType.INPUT.ui_type: + elif event_type == schemas.EventType.INPUT: event_from = event_from % f"{MAIN_EVENTS_TABLE} AS main " if platform == "web": - _column = events.EventType.INPUT.column + _column = "label" event_where.append( f"main.event_type='{exp_ch_helper.get_event_type(event_type, platform=platform)}'") events_conditions.append({"type": event_where[-1]}) @@ -622,7 +623,7 @@ def search_query_parts_ch(data: schemas.SessionsSearchPayloadSchema, error_statu value_key=f"custom{i}")) full_args = {**full_args, **_multiple_values(event.source, value_key=f"custom{i}")} else: - _column = events.EventType.INPUT_MOBILE.column + _column = "label" event_where.append( f"main.event_type='{exp_ch_helper.get_event_type(event_type, platform=platform)}'") events_conditions.append({"type": event_where[-1]}) @@ -639,7 +640,7 @@ def search_query_parts_ch(data: schemas.SessionsSearchPayloadSchema, error_statu value_key=e_k)) events_conditions[-1]["condition"] = event_where[-1] - elif event_type == events.EventType.LOCATION.ui_type: + elif event_type == schemas.EventType.LOCATION: event_from = event_from % f"{MAIN_EVENTS_TABLE} AS main " if platform == "web": _column = 'url_path' @@ -659,7 +660,7 @@ def search_query_parts_ch(data: schemas.SessionsSearchPayloadSchema, error_statu event.value, value_key=e_k)) events_conditions[-1]["condition"] = event_where[-1] else: - _column = events.EventType.VIEW_MOBILE.column + _column = "name" event_where.append( f"main.event_type='{exp_ch_helper.get_event_type(event_type, platform=platform)}'") events_conditions.append({"type": event_where[-1]}) @@ -675,9 +676,9 @@ def search_query_parts_ch(data: schemas.SessionsSearchPayloadSchema, error_statu event_where.append(_multiple_conditions(f"main.{_column} {op} %({e_k})s", event.value, value_key=e_k)) events_conditions[-1]["condition"] = event_where[-1] - elif event_type == events.EventType.CUSTOM.ui_type: + elif event_type == schemas.EventType.CUSTOM: event_from = event_from % f"{MAIN_EVENTS_TABLE} AS main " - _column = events.EventType.CUSTOM.column + _column = "name" event_where.append(f"main.event_type='{exp_ch_helper.get_event_type(event_type, platform=platform)}'") events_conditions.append({"type": event_where[-1]}) if not is_any: @@ -691,7 +692,7 @@ def search_query_parts_ch(data: schemas.SessionsSearchPayloadSchema, error_statu event_where.append(_multiple_conditions(f"main.{_column} {op} %({e_k})s", event.value, value_key=e_k)) events_conditions[-1]["condition"] = event_where[-1] - elif event_type == events.EventType.REQUEST.ui_type: + elif event_type == schemas.EventType.REQUEST: event_from = event_from % f"{MAIN_EVENTS_TABLE} AS main " _column = 'url_path' event_where.append(f"main.event_type='{exp_ch_helper.get_event_type(event_type, platform=platform)}'") @@ -708,9 +709,9 @@ def search_query_parts_ch(data: schemas.SessionsSearchPayloadSchema, error_statu value_key=e_k)) events_conditions[-1]["condition"] = event_where[-1] - elif event_type == events.EventType.STATEACTION.ui_type: + elif event_type == schemas.EventType.STATE_ACTION: event_from = event_from % f"{MAIN_EVENTS_TABLE} AS main " - _column = events.EventType.STATEACTION.column + _column = "name" event_where.append(f"main.event_type='{exp_ch_helper.get_event_type(event_type, platform=platform)}'") events_conditions.append({"type": event_where[-1]}) if not is_any: @@ -725,7 +726,7 @@ def search_query_parts_ch(data: schemas.SessionsSearchPayloadSchema, error_statu event.value, value_key=e_k)) events_conditions[-1]["condition"] = event_where[-1] # TODO: isNot for ERROR - elif event_type == events.EventType.ERROR.ui_type: + elif event_type == schemas.EventType.ERROR: event_from = event_from % f"{MAIN_EVENTS_TABLE} AS main" events_extra_join = f"SELECT * FROM {MAIN_EVENTS_TABLE} AS main1 WHERE main1.project_id=%(project_id)s" event_where.append(f"main.event_type='{exp_ch_helper.get_event_type(event_type, platform=platform)}'") @@ -746,8 +747,8 @@ def search_query_parts_ch(data: schemas.SessionsSearchPayloadSchema, error_statu events_conditions[-1]["condition"] = " AND ".join(events_conditions[-1]["condition"]) # ----- Mobile - elif event_type == events.EventType.CLICK_MOBILE.ui_type: - _column = events.EventType.CLICK_MOBILE.column + elif event_type == schemas.EventType.CLICK_MOBILE: + _column = "label" event_where.append(f"main.event_type='{exp_ch_helper.get_event_type(event_type, platform=platform)}'") events_conditions.append({"type": event_where[-1]}) if not is_any: @@ -761,8 +762,8 @@ def search_query_parts_ch(data: schemas.SessionsSearchPayloadSchema, error_statu event_where.append(_multiple_conditions(f"main.{_column} {op} %({e_k})s", event.value, value_key=e_k)) events_conditions[-1]["condition"] = event_where[-1] - elif event_type == events.EventType.INPUT_MOBILE.ui_type: - _column = events.EventType.INPUT_MOBILE.column + elif event_type == schemas.EventType.INPUT_MOBILE: + _column = "label" event_where.append(f"main.event_type='{exp_ch_helper.get_event_type(event_type, platform=platform)}'") events_conditions.append({"type": event_where[-1]}) if not is_any: @@ -776,8 +777,8 @@ def search_query_parts_ch(data: schemas.SessionsSearchPayloadSchema, error_statu event_where.append(_multiple_conditions(f"main.{_column} {op} %({e_k})s", event.value, value_key=e_k)) events_conditions[-1]["condition"] = event_where[-1] - elif event_type == events.EventType.VIEW_MOBILE.ui_type: - _column = events.EventType.VIEW_MOBILE.column + elif event_type == schemas.EventType.VIEW_MOBILE: + _column = "name" event_where.append(f"main.event_type='{exp_ch_helper.get_event_type(event_type, platform=platform)}'") events_conditions.append({"type": event_where[-1]}) if not is_any: @@ -791,8 +792,8 @@ def search_query_parts_ch(data: schemas.SessionsSearchPayloadSchema, error_statu event_where.append(_multiple_conditions(f"main.{_column} {op} %({e_k})s", event.value, value_key=e_k)) events_conditions[-1]["condition"] = event_where[-1] - elif event_type == events.EventType.CUSTOM_MOBILE.ui_type: - _column = events.EventType.CUSTOM_MOBILE.column + elif event_type == schemas.EventType.CUSTOM_MOBILE: + _column = "name" event_where.append(f"main.event_type='{exp_ch_helper.get_event_type(event_type, platform=platform)}'") events_conditions.append({"type": event_where[-1]}) if not is_any: @@ -806,7 +807,7 @@ def search_query_parts_ch(data: schemas.SessionsSearchPayloadSchema, error_statu event_where.append(_multiple_conditions(f"main.{_column} {op} %({e_k})s", event.value, value_key=e_k)) events_conditions[-1]["condition"] = event_where[-1] - elif event_type == events.EventType.REQUEST_MOBILE.ui_type: + elif event_type == schemas.EventType.REQUEST_MOBILE: event_from = event_from % f"{MAIN_EVENTS_TABLE} AS main " _column = 'url_path' event_where.append(f"main.event_type='{exp_ch_helper.get_event_type(event_type, platform=platform)}'") @@ -822,8 +823,8 @@ def search_query_parts_ch(data: schemas.SessionsSearchPayloadSchema, error_statu event_where.append(_multiple_conditions(f"main.{_column} {op} %({e_k})s", event.value, value_key=e_k)) events_conditions[-1]["condition"] = event_where[-1] - elif event_type == events.EventType.CRASH_MOBILE.ui_type: - _column = events.EventType.CRASH_MOBILE.column + elif event_type == schemas.EventType.ERROR_MOBILE: + _column = "name" event_where.append(f"main.event_type='{exp_ch_helper.get_event_type(event_type, platform=platform)}'") events_conditions.append({"type": event_where[-1]}) if not is_any: @@ -837,8 +838,8 @@ def search_query_parts_ch(data: schemas.SessionsSearchPayloadSchema, error_statu event_where.append(_multiple_conditions(f"main.{_column} {op} %({e_k})s", event.value, value_key=e_k)) events_conditions[-1]["condition"] = event_where[-1] - elif event_type == events.EventType.SWIPE_MOBILE.ui_type and platform != "web": - _column = events.EventType.SWIPE_MOBILE.column + elif event_type == schemas.EventType.SWIPE_MOBILE and platform != "web": + _column = "label" event_where.append(f"main.event_type='{exp_ch_helper.get_event_type(event_type, platform=platform)}'") events_conditions.append({"type": event_where[-1]}) if not is_any: @@ -992,7 +993,7 @@ def search_query_parts_ch(data: schemas.SessionsSearchPayloadSchema, error_statu full_args = {**full_args, **_multiple_values(f.value, value_key=e_k_f)} if f.type == schemas.GraphqlFilterType.GRAPHQL_NAME: event_where.append( - _multiple_conditions(f"main.{events.EventType.GRAPHQL.column} {op} %({e_k_f})s", f.value, + _multiple_conditions(f"main.name {op} %({e_k_f})s", f.value, value_key=e_k_f)) events_conditions[-1]["condition"].append(event_where[-1]) elif f.type == schemas.GraphqlFilterType.GRAPHQL_METHOD: @@ -1221,7 +1222,7 @@ def search_query_parts_ch(data: schemas.SessionsSearchPayloadSchema, error_statu c.value = helper.values_for_operator(value=c.value, op=c.operator) full_args = {**full_args, **_multiple_values(c.value, value_key=e_k)} - if c.type == events.EventType.LOCATION.ui_type: + if c.type == schemas.EventType.LOCATION: _extra_or_condition.append( _multiple_conditions(f"extra_event.url_path {op} %({e_k})s", c.value, value_key=e_k)) @@ -1358,18 +1359,15 @@ def get_user_sessions(project_id, user_id, start_date, end_date): def get_session_user(project_id, user_id): with pg_client.PostgresClient() as cur: query = cur.mogrify( - """\ - SELECT - user_id, - count(*) as session_count, - max(start_ts) as last_seen, - min(start_ts) as first_seen - FROM - "public".sessions - WHERE - project_id = %(project_id)s - AND user_id = %(userId)s - AND duration is not null + """ \ + SELECT user_id, + count(*) as session_count, + max(start_ts) as last_seen, + min(start_ts) as first_seen + FROM "public".sessions + WHERE project_id = %(project_id)s + AND user_id = %(userId)s + AND duration is not null GROUP BY user_id; """, {"project_id": project_id, "userId": user_id} diff --git a/api/chalicelib/core/sessions/sessions_pg.py b/api/chalicelib/core/sessions/sessions_pg.py index 3032affcb..2c404d82e 100644 --- a/api/chalicelib/core/sessions/sessions_pg.py +++ b/api/chalicelib/core/sessions/sessions_pg.py @@ -2,7 +2,8 @@ import logging from typing import List, Union import schemas -from chalicelib.core import events, metadata +from chalicelib.core.events import events +from chalicelib.core import metadata from . import performance_event from chalicelib.utils import pg_client, helper, metrics_helper from chalicelib.utils import sql_helper as sh @@ -439,7 +440,7 @@ def search_query_parts(data: schemas.SessionsSearchPayloadSchema, error_status, extra_constraints.append( sh.multi_conditions(f"s.base_referrer {op} %({f_k})s", f.value, is_not=is_not, value_key=f_k)) - elif filter_type == events.EventType.METADATA.ui_type: + elif filter_type == schemas.EventType.METADATA: # get metadata list only if you need it if meta_keys is None: meta_keys = metadata.get(project_id=project_id) @@ -580,36 +581,36 @@ def search_query_parts(data: schemas.SessionsSearchPayloadSchema, error_status, **sh.multi_values(event.value, value_key=e_k), **sh.multi_values(event.source, value_key=s_k)} - if event_type == events.EventType.CLICK.ui_type: + if event_type == schemas.EventType.CLICK: if platform == "web": - event_from = event_from % f"{events.EventType.CLICK.table} AS main " + event_from = event_from % f"events.clicks AS main " if not is_any: if schemas.ClickEventExtraOperator.has_value(event.operator): event_where.append( sh.multi_conditions(f"main.selector {op} %({e_k})s", event.value, value_key=e_k)) else: event_where.append( - sh.multi_conditions(f"main.{events.EventType.CLICK.column} {op} %({e_k})s", event.value, + sh.multi_conditions(f"main.label {op} %({e_k})s", event.value, value_key=e_k)) else: - event_from = event_from % f"{events.EventType.CLICK_MOBILE.table} AS main " + event_from = event_from % f"events_ios.taps AS main " if not is_any: event_where.append( - sh.multi_conditions(f"main.{events.EventType.CLICK_MOBILE.column} {op} %({e_k})s", + sh.multi_conditions(f"main.label {op} %({e_k})s", event.value, value_key=e_k)) - elif event_type == events.EventType.TAG.ui_type: - event_from = event_from % f"{events.EventType.TAG.table} AS main " + elif event_type == schemas.EventType.TAG: + event_from = event_from % f"events.tags AS main " if not is_any: event_where.append( sh.multi_conditions(f"main.tag_id = %({e_k})s", event.value, value_key=e_k)) - elif event_type == events.EventType.INPUT.ui_type: + elif event_type == schemas.EventType.INPUT: if platform == "web": - event_from = event_from % f"{events.EventType.INPUT.table} AS main " + event_from = event_from % f"events.inputs AS main " if not is_any: event_where.append( - sh.multi_conditions(f"main.{events.EventType.INPUT.column} {op} %({e_k})s", event.value, + sh.multi_conditions(f"main.label {op} %({e_k})s", event.value, value_key=e_k)) if event.source is not None and len(event.source) > 0: event_where.append(sh.multi_conditions(f"main.value ILIKE %(custom{i})s", event.source, @@ -617,53 +618,53 @@ def search_query_parts(data: schemas.SessionsSearchPayloadSchema, error_status, full_args = {**full_args, **sh.multi_values(event.source, value_key=f"custom{i}")} else: - event_from = event_from % f"{events.EventType.INPUT_MOBILE.table} AS main " + event_from = event_from % f"events_ios.inputs AS main " if not is_any: event_where.append( - sh.multi_conditions(f"main.{events.EventType.INPUT_MOBILE.column} {op} %({e_k})s", + sh.multi_conditions(f"main.label {op} %({e_k})s", event.value, value_key=e_k)) - elif event_type == events.EventType.LOCATION.ui_type: + elif event_type == schemas.EventType.LOCATION: if platform == "web": - event_from = event_from % f"{events.EventType.LOCATION.table} AS main " + event_from = event_from % f"events.pages AS main " if not is_any: event_where.append( - sh.multi_conditions(f"main.{events.EventType.LOCATION.column} {op} %({e_k})s", + sh.multi_conditions(f"main.path {op} %({e_k})s", event.value, value_key=e_k)) else: - event_from = event_from % f"{events.EventType.VIEW_MOBILE.table} AS main " + event_from = event_from % f"events_ios.views AS main " if not is_any: event_where.append( - sh.multi_conditions(f"main.{events.EventType.VIEW_MOBILE.column} {op} %({e_k})s", + sh.multi_conditions(f"main.name {op} %({e_k})s", event.value, value_key=e_k)) - elif event_type == events.EventType.CUSTOM.ui_type: - event_from = event_from % f"{events.EventType.CUSTOM.table} AS main " + elif event_type == schemas.EventType.CUSTOM: + event_from = event_from % f"events_common.customs AS main " if not is_any: event_where.append( - sh.multi_conditions(f"main.{events.EventType.CUSTOM.column} {op} %({e_k})s", event.value, + sh.multi_conditions(f"main.name {op} %({e_k})s", event.value, value_key=e_k)) - elif event_type == events.EventType.REQUEST.ui_type: - event_from = event_from % f"{events.EventType.REQUEST.table} AS main " + elif event_type == schemas.EventType.REQUEST: + event_from = event_from % f"events_common.requests AS main " if not is_any: event_where.append( - sh.multi_conditions(f"main.{events.EventType.REQUEST.column} {op} %({e_k})s", event.value, + sh.multi_conditions(f"main.path {op} %({e_k})s", event.value, value_key=e_k)) - # elif event_type == events.event_type.GRAPHQL.ui_type: + # elif event_type == schemas.event_type.GRAPHQL: # event_from = event_from % f"{events.event_type.GRAPHQL.table} AS main " # if not is_any: # event_where.append( # _multiple_conditions(f"main.{events.event_type.GRAPHQL.column} {op} %({e_k})s", event.value, # value_key=e_k)) - elif event_type == events.EventType.STATEACTION.ui_type: - event_from = event_from % f"{events.EventType.STATEACTION.table} AS main " + elif event_type == schemas.EventType.STATE_ACTION: + event_from = event_from % f"events.state_actions AS main " if not is_any: event_where.append( - sh.multi_conditions(f"main.{events.EventType.STATEACTION.column} {op} %({e_k})s", + sh.multi_conditions(f"main.name {op} %({e_k})s", event.value, value_key=e_k)) - elif event_type == events.EventType.ERROR.ui_type: - event_from = event_from % f"{events.EventType.ERROR.table} AS main INNER JOIN public.errors AS main1 USING(error_id)" + elif event_type == schemas.EventType.ERROR: + event_from = event_from % f"events.errors AS main INNER JOIN public.errors AS main1 USING(error_id)" event.source = list(set(event.source)) if not is_any and event.value not in [None, "*", ""]: event_where.append( @@ -674,59 +675,59 @@ def search_query_parts(data: schemas.SessionsSearchPayloadSchema, error_status, # ----- Mobile - elif event_type == events.EventType.CLICK_MOBILE.ui_type: - event_from = event_from % f"{events.EventType.CLICK_MOBILE.table} AS main " + elif event_type == schemas.EventType.CLICK_MOBILE: + event_from = event_from % f"events_ios.taps AS main " if not is_any: event_where.append( - sh.multi_conditions(f"main.{events.EventType.CLICK_MOBILE.column} {op} %({e_k})s", + sh.multi_conditions(f"main.label {op} %({e_k})s", event.value, value_key=e_k)) - elif event_type == events.EventType.INPUT_MOBILE.ui_type: - event_from = event_from % f"{events.EventType.INPUT_MOBILE.table} AS main " + elif event_type == schemas.EventType.INPUT_MOBILE: + event_from = event_from % f"events_ios.inputs AS main " if not is_any: event_where.append( - sh.multi_conditions(f"main.{events.EventType.INPUT_MOBILE.column} {op} %({e_k})s", + sh.multi_conditions(f"main.label {op} %({e_k})s", event.value, value_key=e_k)) if event.source is not None and len(event.source) > 0: event_where.append(sh.multi_conditions(f"main.value ILIKE %(custom{i})s", event.source, value_key="custom{i}")) full_args = {**full_args, **sh.multi_values(event.source, f"custom{i}")} - elif event_type == events.EventType.VIEW_MOBILE.ui_type: - event_from = event_from % f"{events.EventType.VIEW_MOBILE.table} AS main " + elif event_type == schemas.EventType.VIEW_MOBILE: + event_from = event_from % f"events_ios.views AS main " if not is_any: event_where.append( - sh.multi_conditions(f"main.{events.EventType.VIEW_MOBILE.column} {op} %({e_k})s", + sh.multi_conditions(f"main.name {op} %({e_k})s", event.value, value_key=e_k)) - elif event_type == events.EventType.CUSTOM_MOBILE.ui_type: - event_from = event_from % f"{events.EventType.CUSTOM_MOBILE.table} AS main " + elif event_type == schemas.EventType.CUSTOM_MOBILE: + event_from = event_from % f"events_common.customs AS main " if not is_any: event_where.append( - sh.multi_conditions(f"main.{events.EventType.CUSTOM_MOBILE.column} {op} %({e_k})s", + sh.multi_conditions(f"main.name {op} %({e_k})s", event.value, value_key=e_k)) - elif event_type == events.EventType.REQUEST_MOBILE.ui_type: - event_from = event_from % f"{events.EventType.REQUEST_MOBILE.table} AS main " + elif event_type == schemas.EventType.REQUEST_MOBILE: + event_from = event_from % f"events_common.requests AS main " if not is_any: event_where.append( - sh.multi_conditions(f"main.{events.EventType.REQUEST_MOBILE.column} {op} %({e_k})s", + sh.multi_conditions(f"main.path {op} %({e_k})s", event.value, value_key=e_k)) - elif event_type == events.EventType.CRASH_MOBILE.ui_type: - event_from = event_from % f"{events.EventType.CRASH_MOBILE.table} AS main INNER JOIN public.crashes_ios AS main1 USING(crash_ios_id)" + elif event_type == schemas.EventType.ERROR_MOBILE: + event_from = event_from % f"events_common.crashes AS main INNER JOIN public.crashes_ios AS main1 USING(crash_ios_id)" if not is_any and event.value not in [None, "*", ""]: event_where.append( sh.multi_conditions(f"(main1.reason {op} %({e_k})s OR main1.name {op} %({e_k})s)", event.value, value_key=e_k)) - elif event_type == events.EventType.SWIPE_MOBILE.ui_type and platform != "web": - event_from = event_from % f"{events.EventType.SWIPE_MOBILE.table} AS main " + elif event_type == schemas.EventType.SWIPE_MOBILE and platform != "web": + event_from = event_from % f"events_ios.swipes AS main " if not is_any: event_where.append( - sh.multi_conditions(f"main.{events.EventType.SWIPE_MOBILE.column} {op} %({e_k})s", + sh.multi_conditions(f"main.label {op} %({e_k})s", event.value, value_key=e_k)) elif event_type == schemas.PerformanceEventType.FETCH_FAILED: - event_from = event_from % f"{events.EventType.REQUEST.table} AS main " + event_from = event_from % f"events_common.requests AS main " if not is_any: event_where.append( - sh.multi_conditions(f"main.{events.EventType.REQUEST.column} {op} %({e_k})s", + sh.multi_conditions(f"main.path {op} %({e_k})s", event.value, value_key=e_k)) col = performance_event.get_col(event_type) colname = col["column"] @@ -751,7 +752,7 @@ def search_query_parts(data: schemas.SessionsSearchPayloadSchema, error_status, schemas.PerformanceEventType.LOCATION_AVG_CPU_LOAD, schemas.PerformanceEventType.LOCATION_AVG_MEMORY_USAGE ]: - event_from = event_from % f"{events.EventType.LOCATION.table} AS main " + event_from = event_from % f"events.pages AS main " col = performance_event.get_col(event_type) colname = col["column"] tname = "main" @@ -762,7 +763,7 @@ def search_query_parts(data: schemas.SessionsSearchPayloadSchema, error_status, f"{tname}.timestamp <= %(endDate)s"] if not is_any: event_where.append( - sh.multi_conditions(f"main.{events.EventType.LOCATION.column} {op} %({e_k})s", + sh.multi_conditions(f"main.path {op} %({e_k})s", event.value, value_key=e_k)) e_k += "_custom" full_args = {**full_args, **sh.multi_values(event.source, value_key=e_k)} @@ -772,7 +773,7 @@ def search_query_parts(data: schemas.SessionsSearchPayloadSchema, error_status, event.source, value_key=e_k)) elif event_type == schemas.EventType.REQUEST_DETAILS: - event_from = event_from % f"{events.EventType.REQUEST.table} AS main " + event_from = event_from % f"events_common.requests AS main " apply = False for j, f in enumerate(event.filters): is_any = sh.isAny_opreator(f.operator) @@ -784,7 +785,7 @@ def search_query_parts(data: schemas.SessionsSearchPayloadSchema, error_status, full_args = {**full_args, **sh.multi_values(f.value, value_key=e_k_f)} if f.type == schemas.FetchFilterType.FETCH_URL: event_where.append( - sh.multi_conditions(f"main.{events.EventType.REQUEST.column} {op} %({e_k_f})s::text", + sh.multi_conditions(f"main.path {op} %({e_k_f})s::text", f.value, value_key=e_k_f)) apply = True elif f.type == schemas.FetchFilterType.FETCH_STATUS_CODE: @@ -816,7 +817,7 @@ def search_query_parts(data: schemas.SessionsSearchPayloadSchema, error_status, if not apply: continue elif event_type == schemas.EventType.GRAPHQL: - event_from = event_from % f"{events.EventType.GRAPHQL.table} AS main " + event_from = event_from % f"events.graphql AS main " for j, f in enumerate(event.filters): is_any = sh.isAny_opreator(f.operator) if is_any or len(f.value) == 0: @@ -827,7 +828,7 @@ def search_query_parts(data: schemas.SessionsSearchPayloadSchema, error_status, full_args = {**full_args, **sh.multi_values(f.value, value_key=e_k_f)} if f.type == schemas.GraphqlFilterType.GRAPHQL_NAME: event_where.append( - sh.multi_conditions(f"main.{events.EventType.GRAPHQL.column} {op} %({e_k_f})s", f.value, + sh.multi_conditions(f"main.name {op} %({e_k_f})s", f.value, value_key=e_k_f)) elif f.type == schemas.GraphqlFilterType.GRAPHQL_METHOD: event_where.append( @@ -908,7 +909,7 @@ def search_query_parts(data: schemas.SessionsSearchPayloadSchema, error_status, # b"s.user_os in ('Chrome OS','Fedora','Firefox OS','Linux','Mac OS X','Ubuntu','Windows')") if errors_only: - extra_from += f" INNER JOIN {events.EventType.ERROR.table} AS er USING (session_id) INNER JOIN public.errors AS ser USING (error_id)" + extra_from += f" INNER JOIN events.errors AS er USING (session_id) INNER JOIN public.errors AS ser USING (error_id)" extra_constraints.append("ser.source = 'js_exception'") extra_constraints.append("ser.project_id = %(project_id)s") # if error_status != schemas.ErrorStatus.all: @@ -984,9 +985,9 @@ def search_query_parts(data: schemas.SessionsSearchPayloadSchema, error_status, c.value = helper.values_for_operator(value=c.value, op=c.operator) full_args = {**full_args, **sh.multi_values(c.value, value_key=e_k)} - if c.type == events.EventType.LOCATION.ui_type: + if c.type == schemas.EventType.LOCATION: _extra_or_condition.append( - sh.multi_conditions(f"ev.{events.EventType.LOCATION.column} {op} %({e_k})s", + sh.multi_conditions(f"ev.path {op} %({e_k})s", c.value, value_key=e_k)) else: logger.warning(f"unsupported extra_event type:${c.type}") @@ -1044,18 +1045,15 @@ def get_user_sessions(project_id, user_id, start_date, end_date): def get_session_user(project_id, user_id): with pg_client.PostgresClient() as cur: query = cur.mogrify( - """\ - SELECT - user_id, - count(*) as session_count, - max(start_ts) as last_seen, - min(start_ts) as first_seen - FROM - "public".sessions - WHERE - project_id = %(project_id)s - AND user_id = %(userId)s - AND duration is not null + """ \ + SELECT user_id, + count(*) as session_count, + max(start_ts) as last_seen, + min(start_ts) as first_seen + FROM "public".sessions + WHERE project_id = %(project_id)s + AND user_id = %(userId)s + AND duration is not null GROUP BY user_id; """, {"project_id": project_id, "userId": user_id} @@ -1074,11 +1072,10 @@ def count_all(): def session_exists(project_id, session_id): with pg_client.PostgresClient() as cur: - query = cur.mogrify("""SELECT 1 - FROM public.sessions - WHERE session_id=%(session_id)s - AND project_id=%(project_id)s - LIMIT 1;""", + query = cur.mogrify("""SELECT 1 + FROM public.sessions + WHERE session_id = %(session_id)s + AND project_id = %(project_id)s LIMIT 1;""", {"project_id": project_id, "session_id": session_id}) cur.execute(query) row = cur.fetchone() diff --git a/api/chalicelib/core/sessions/sessions_replay.py b/api/chalicelib/core/sessions/sessions_replay.py index 24e8a9478..b0ea40613 100644 --- a/api/chalicelib/core/sessions/sessions_replay.py +++ b/api/chalicelib/core/sessions/sessions_replay.py @@ -1,6 +1,6 @@ import schemas -from chalicelib.core import events, metadata, events_mobile, \ - issues, assist, canvas, user_testing +from chalicelib.core import metadata, issues, assist, canvas, user_testing +from chalicelib.core.events import events, events_mobile from . import sessions_mobs, sessions_devtool from chalicelib.core.errors.modules import errors_helper from chalicelib.utils import pg_client, helper diff --git a/api/env.default b/api/env.default index 383e74273..6c53a6b12 100644 --- a/api/env.default +++ b/api/env.default @@ -75,4 +75,5 @@ EXP_AUTOCOMPLETE=true EXP_ALERTS=true EXP_ERRORS_SEARCH=true EXP_METRICS=true -EXP_SESSIONS_SEARCH=true \ No newline at end of file +EXP_SESSIONS_SEARCH=true +EXP_EVENTS=true \ No newline at end of file diff --git a/api/env.dev b/api/env.dev index 74f9f8e1f..9183efad6 100644 --- a/api/env.dev +++ b/api/env.dev @@ -68,4 +68,5 @@ EXP_CH_DRIVER=true EXP_AUTOCOMPLETE=true EXP_ALERTS=true EXP_ERRORS_SEARCH=true -EXP_METRICS=true \ No newline at end of file +EXP_METRICS=true +EXP_EVENTS=true \ No newline at end of file diff --git a/ee/api/.gitignore b/ee/api/.gitignore index d5392c84d..7ad13e838 100644 --- a/ee/api/.gitignore +++ b/ee/api/.gitignore @@ -201,8 +201,7 @@ Pipfile.lock /chalicelib/core/metrics/heatmaps /chalicelib/core/metrics/product_analytics /chalicelib/core/metrics/product_anaytics2.py -/chalicelib/core/events.py -/chalicelib/core/events_mobile.py +/chalicelib/core/events /chalicelib/core/feature_flags.py /chalicelib/core/issue_tracking/* /chalicelib/core/issues.py diff --git a/ee/api/clean-dev.sh b/ee/api/clean-dev.sh index 2e42c9d29..cc093f1a2 100755 --- a/ee/api/clean-dev.sh +++ b/ee/api/clean-dev.sh @@ -21,8 +21,7 @@ rm -rf ./chalicelib/core/metrics/dashboards.py rm -rf ./chalicelib/core/metrics/heatmaps rm -rf ./chalicelib/core/metrics/product_analytics rm -rf ./chalicelib/core/metrics/product_anaytics2.py -rm -rf ./chalicelib/core/events.py -rm -rf ./chalicelib/core/events_mobile.py +rm -rf ./chalicelib/core/events rm -rf ./chalicelib/core/feature_flags.py rm -rf ./chalicelib/core/issue_tracking rm -rf ./chalicelib/core/integrations_manager.py From 06ff69614129238e816738f0d04f710ce6634411 Mon Sep 17 00:00:00 2001 From: Taha Yassine Kraiem Date: Fri, 16 May 2025 14:19:47 +0200 Subject: [PATCH 02/22] refactor(chalice): use CH to get replay events --- api/chalicelib/core/events.py | 226 ------------------------ api/chalicelib/core/events/events_ch.py | 66 +++++++ 2 files changed, 66 insertions(+), 226 deletions(-) delete mode 100644 api/chalicelib/core/events.py create mode 100644 api/chalicelib/core/events/events_ch.py diff --git a/api/chalicelib/core/events.py b/api/chalicelib/core/events.py deleted file mode 100644 index dcb4d88d6..000000000 --- a/api/chalicelib/core/events.py +++ /dev/null @@ -1,226 +0,0 @@ -from functools import cache -from typing import Optional - -import schemas -from chalicelib.core import issues -from chalicelib.core.autocomplete import autocomplete -from chalicelib.core.sessions import sessions_metas -from chalicelib.utils import pg_client, helper -from chalicelib.utils.TimeUTC import TimeUTC -from chalicelib.utils.event_filter_definition import SupportedFilter, Event - - -def get_customs_by_session_id(session_id, project_id): - with pg_client.PostgresClient() as cur: - cur.execute(cur.mogrify("""\ - SELECT - c.*, - 'CUSTOM' AS type - FROM events_common.customs AS c - WHERE - c.session_id = %(session_id)s - ORDER BY c.timestamp;""", - {"project_id": project_id, "session_id": session_id}) - ) - rows = cur.fetchall() - return helper.dict_to_camel_case(rows) - - -def __merge_cells(rows, start, count, replacement): - rows[start] = replacement - rows = rows[:start + 1] + rows[start + count:] - return rows - - -def __get_grouped_clickrage(rows, session_id, project_id): - click_rage_issues = issues.get_by_session_id(session_id=session_id, issue_type="click_rage", project_id=project_id) - if len(click_rage_issues) == 0: - return rows - - for c in click_rage_issues: - merge_count = c.get("payload") - if merge_count is not None: - merge_count = merge_count.get("Count", 3) - else: - merge_count = 3 - for i in range(len(rows)): - if rows[i]["timestamp"] == c["timestamp"]: - rows = __merge_cells(rows=rows, - start=i, - count=merge_count, - replacement={**rows[i], "type": "CLICKRAGE", "count": merge_count}) - break - return rows - - -def get_by_session_id(session_id, project_id, group_clickrage=False, event_type: Optional[schemas.EventType] = None): - with pg_client.PostgresClient() as cur: - rows = [] - if event_type is None or event_type == schemas.EventType.CLICK: - cur.execute(cur.mogrify("""\ - SELECT - c.*, - 'CLICK' AS type - FROM events.clicks AS c - WHERE - c.session_id = %(session_id)s - ORDER BY c.timestamp;""", - {"project_id": project_id, "session_id": session_id}) - ) - rows += cur.fetchall() - if group_clickrage: - rows = __get_grouped_clickrage(rows=rows, session_id=session_id, project_id=project_id) - if event_type is None or event_type == schemas.EventType.INPUT: - cur.execute(cur.mogrify(""" - SELECT - i.*, - 'INPUT' AS type - FROM events.inputs AS i - WHERE - i.session_id = %(session_id)s - ORDER BY i.timestamp;""", - {"project_id": project_id, "session_id": session_id}) - ) - rows += cur.fetchall() - if event_type is None or event_type == schemas.EventType.LOCATION: - cur.execute(cur.mogrify("""\ - SELECT - l.*, - l.path AS value, - l.path AS url, - 'LOCATION' AS type - FROM events.pages AS l - WHERE - l.session_id = %(session_id)s - ORDER BY l.timestamp;""", {"project_id": project_id, "session_id": session_id})) - rows += cur.fetchall() - rows = helper.list_to_camel_case(rows) - rows = sorted(rows, key=lambda k: (k["timestamp"], k["messageId"])) - return rows - - -def _search_tags(project_id, value, key=None, source=None): - with pg_client.PostgresClient() as cur: - query = f""" - SELECT public.tags.name - 'TAG' AS type - FROM public.tags - WHERE public.tags.project_id = %(project_id)s - ORDER BY SIMILARITY(public.tags.name, %(value)s) DESC - LIMIT 10 - """ - query = cur.mogrify(query, {'project_id': project_id, 'value': value}) - cur.execute(query) - results = helper.list_to_camel_case(cur.fetchall()) - return results - - -class EventType: - CLICK = Event(ui_type=schemas.EventType.CLICK, table="events.clicks", column="label") - INPUT = Event(ui_type=schemas.EventType.INPUT, table="events.inputs", column="label") - LOCATION = Event(ui_type=schemas.EventType.LOCATION, table="events.pages", column="path") - CUSTOM = Event(ui_type=schemas.EventType.CUSTOM, table="events_common.customs", column="name") - REQUEST = Event(ui_type=schemas.EventType.REQUEST, table="events_common.requests", column="path") - GRAPHQL = Event(ui_type=schemas.EventType.GRAPHQL, table="events.graphql", column="name") - STATEACTION = Event(ui_type=schemas.EventType.STATE_ACTION, table="events.state_actions", column="name") - TAG = Event(ui_type=schemas.EventType.TAG, table="events.tags", column="tag_id") - ERROR = Event(ui_type=schemas.EventType.ERROR, table="events.errors", - column=None) # column=None because errors are searched by name or message - METADATA = Event(ui_type=schemas.FilterType.METADATA, table="public.sessions", column=None) - # MOBILE - CLICK_MOBILE = Event(ui_type=schemas.EventType.CLICK_MOBILE, table="events_ios.taps", column="label") - INPUT_MOBILE = Event(ui_type=schemas.EventType.INPUT_MOBILE, table="events_ios.inputs", column="label") - VIEW_MOBILE = Event(ui_type=schemas.EventType.VIEW_MOBILE, table="events_ios.views", column="name") - SWIPE_MOBILE = Event(ui_type=schemas.EventType.SWIPE_MOBILE, table="events_ios.swipes", column="label") - CUSTOM_MOBILE = Event(ui_type=schemas.EventType.CUSTOM_MOBILE, table="events_common.customs", column="name") - REQUEST_MOBILE = Event(ui_type=schemas.EventType.REQUEST_MOBILE, table="events_common.requests", column="path") - CRASH_MOBILE = Event(ui_type=schemas.EventType.ERROR_MOBILE, table="events_common.crashes", - column=None) # column=None because errors are searched by name or message - - -@cache -def supported_types(): - return { - EventType.CLICK.ui_type: SupportedFilter(get=autocomplete.__generic_autocomplete(EventType.CLICK), - query=autocomplete.__generic_query(typename=EventType.CLICK.ui_type)), - EventType.INPUT.ui_type: SupportedFilter(get=autocomplete.__generic_autocomplete(EventType.INPUT), - query=autocomplete.__generic_query(typename=EventType.INPUT.ui_type)), - EventType.LOCATION.ui_type: SupportedFilter(get=autocomplete.__generic_autocomplete(EventType.LOCATION), - query=autocomplete.__generic_query( - typename=EventType.LOCATION.ui_type)), - EventType.CUSTOM.ui_type: SupportedFilter(get=autocomplete.__generic_autocomplete(EventType.CUSTOM), - query=autocomplete.__generic_query( - typename=EventType.CUSTOM.ui_type)), - EventType.REQUEST.ui_type: SupportedFilter(get=autocomplete.__generic_autocomplete(EventType.REQUEST), - query=autocomplete.__generic_query( - typename=EventType.REQUEST.ui_type)), - EventType.GRAPHQL.ui_type: SupportedFilter(get=autocomplete.__generic_autocomplete(EventType.GRAPHQL), - query=autocomplete.__generic_query( - typename=EventType.GRAPHQL.ui_type)), - EventType.STATEACTION.ui_type: SupportedFilter(get=autocomplete.__generic_autocomplete(EventType.STATEACTION), - query=autocomplete.__generic_query( - typename=EventType.STATEACTION.ui_type)), - EventType.TAG.ui_type: SupportedFilter(get=_search_tags, query=None), - EventType.ERROR.ui_type: SupportedFilter(get=autocomplete.__search_errors, - query=None), - EventType.METADATA.ui_type: SupportedFilter(get=autocomplete.__search_metadata, - query=None), - # MOBILE - EventType.CLICK_MOBILE.ui_type: SupportedFilter(get=autocomplete.__generic_autocomplete(EventType.CLICK_MOBILE), - query=autocomplete.__generic_query( - typename=EventType.CLICK_MOBILE.ui_type)), - EventType.SWIPE_MOBILE.ui_type: SupportedFilter(get=autocomplete.__generic_autocomplete(EventType.SWIPE_MOBILE), - query=autocomplete.__generic_query( - typename=EventType.SWIPE_MOBILE.ui_type)), - EventType.INPUT_MOBILE.ui_type: SupportedFilter(get=autocomplete.__generic_autocomplete(EventType.INPUT_MOBILE), - query=autocomplete.__generic_query( - typename=EventType.INPUT_MOBILE.ui_type)), - EventType.VIEW_MOBILE.ui_type: SupportedFilter(get=autocomplete.__generic_autocomplete(EventType.VIEW_MOBILE), - query=autocomplete.__generic_query( - typename=EventType.VIEW_MOBILE.ui_type)), - EventType.CUSTOM_MOBILE.ui_type: SupportedFilter( - get=autocomplete.__generic_autocomplete(EventType.CUSTOM_MOBILE), - query=autocomplete.__generic_query( - typename=EventType.CUSTOM_MOBILE.ui_type)), - EventType.REQUEST_MOBILE.ui_type: SupportedFilter( - get=autocomplete.__generic_autocomplete(EventType.REQUEST_MOBILE), - query=autocomplete.__generic_query( - typename=EventType.REQUEST_MOBILE.ui_type)), - EventType.CRASH_MOBILE.ui_type: SupportedFilter(get=autocomplete.__search_errors_mobile, - query=None), - } - - -def get_errors_by_session_id(session_id, project_id): - with pg_client.PostgresClient() as cur: - cur.execute(cur.mogrify(f"""\ - SELECT er.*,ur.*, er.timestamp - s.start_ts AS time - FROM {EventType.ERROR.table} AS er INNER JOIN public.errors AS ur USING (error_id) INNER JOIN public.sessions AS s USING (session_id) - WHERE er.session_id = %(session_id)s AND s.project_id=%(project_id)s - ORDER BY timestamp;""", {"session_id": session_id, "project_id": project_id})) - errors = cur.fetchall() - for e in errors: - e["stacktrace_parsed_at"] = TimeUTC.datetime_to_timestamp(e["stacktrace_parsed_at"]) - return helper.list_to_camel_case(errors) - - -def search(text, event_type, project_id, source, key): - if not event_type: - return {"data": autocomplete.__get_autocomplete_table(text, project_id)} - - if event_type in supported_types().keys(): - rows = supported_types()[event_type].get(project_id=project_id, value=text, key=key, source=source) - elif event_type + "_MOBILE" in supported_types().keys(): - rows = supported_types()[event_type + "_MOBILE"].get(project_id=project_id, value=text, key=key, source=source) - elif event_type in sessions_metas.supported_types().keys(): - return sessions_metas.search(text, event_type, project_id) - elif event_type.endswith("_IOS") \ - and event_type[:-len("_IOS")] in sessions_metas.supported_types().keys(): - return sessions_metas.search(text, event_type, project_id) - elif event_type.endswith("_MOBILE") \ - and event_type[:-len("_MOBILE")] in sessions_metas.supported_types().keys(): - return sessions_metas.search(text, event_type, project_id) - else: - return {"errors": ["unsupported event"]} - - return {"data": rows} diff --git a/api/chalicelib/core/events/events_ch.py b/api/chalicelib/core/events/events_ch.py new file mode 100644 index 000000000..4bca47a27 --- /dev/null +++ b/api/chalicelib/core/events/events_ch.py @@ -0,0 +1,66 @@ +from chalicelib.utils import ch_client +from .events_pg import * + + +def get_customs_by_session_id(session_id, project_id): + with ch_client.ClickHouseClient() as cur: + rows = cur.execute(""" \ + SELECT c.*, + 'CUSTOM' AS type + FROM product_analytics.events AS c + WHERE c.session_id = %(session_id)s + AND NOT `$auto_captured` + ORDER BY c.timestamp;""", + {"project_id": project_id, "session_id": session_id} + ) + return helper.dict_to_camel_case(rows) + + +def __merge_cells(rows, start, count, replacement): + rows[start] = replacement + rows = rows[:start + 1] + rows[start + count:] + return rows + + +def __get_grouped_clickrage(rows, session_id, project_id): + click_rage_issues = issues.get_by_session_id(session_id=session_id, issue_type="click_rage", project_id=project_id) + if len(click_rage_issues) == 0: + return rows + + for c in click_rage_issues: + merge_count = c.get("payload") + if merge_count is not None: + merge_count = merge_count.get("Count", 3) + else: + merge_count = 3 + for i in range(len(rows)): + if rows[i]["timestamp"] == c["timestamp"]: + rows = __merge_cells(rows=rows, + start=i, + count=merge_count, + replacement={**rows[i], "type": "CLICKRAGE", "count": merge_count}) + break + return rows + + +def get_by_session_id(session_id, project_id, group_clickrage=False, event_type: Optional[schemas.EventType] = None): + with ch_client.ClickHouseClient() as cur: + select_events = ('CLICK', 'INPUT', 'LOCATION') + if event_type is not None: + select_events = (event_type,) + rows = cur.execute(""" \ + SELECT c.*, + `$event_name` AS type + FROM product_analytics.events + WHERE session_id = %(session_id)s + AND `$event_name` IN %(select_events)s + AND `$auto_captured` + ORDER BY c.timestamp;""", + {"project_id": project_id, "session_id": session_id, "select_events": select_events}) + + if group_clickrage and 'CLICK' in select_events: + rows = __get_grouped_clickrage(rows=rows, session_id=session_id, project_id=project_id) + + rows = helper.list_to_camel_case(rows) + rows = sorted(rows, key=lambda k: (k["timestamp"], k["messageId"])) + return rows From 1bb8f3a7b32f1964384405fab8b8050f7fc52bc7 Mon Sep 17 00:00:00 2001 From: Taha Yassine Kraiem Date: Fri, 16 May 2025 16:32:36 +0200 Subject: [PATCH 03/22] refactor(chalice): refactored issues refactor(chalice): support issues in CH --- api/chalicelib/core/events/events_pg.py | 2 +- api/chalicelib/core/issues/__init__.py | 11 +++++++ api/chalicelib/core/issues/issues_ch.py | 31 +++++++++++++++++++ .../core/{issues.py => issues/issues_pg.py} | 0 api/chalicelib/core/metrics/custom_metrics.py | 2 +- .../core/sessions/sessions_replay.py | 3 +- api/routers/core.py | 3 +- 7 files changed, 48 insertions(+), 4 deletions(-) create mode 100644 api/chalicelib/core/issues/__init__.py create mode 100644 api/chalicelib/core/issues/issues_ch.py rename api/chalicelib/core/{issues.py => issues/issues_pg.py} (100%) diff --git a/api/chalicelib/core/events/events_pg.py b/api/chalicelib/core/events/events_pg.py index 62c599b6e..d481e6259 100644 --- a/api/chalicelib/core/events/events_pg.py +++ b/api/chalicelib/core/events/events_pg.py @@ -2,7 +2,7 @@ from functools import cache from typing import Optional import schemas -from chalicelib.core import issues +from chalicelib.core.issues import issues from chalicelib.core.autocomplete import autocomplete from chalicelib.core.sessions import sessions_metas from chalicelib.utils import pg_client, helper diff --git a/api/chalicelib/core/issues/__init__.py b/api/chalicelib/core/issues/__init__.py new file mode 100644 index 000000000..bdb52cb86 --- /dev/null +++ b/api/chalicelib/core/issues/__init__.py @@ -0,0 +1,11 @@ +import logging + +from decouple import config + +logger = logging.getLogger(__name__) + +if config("EXP_EVENTS", cast=bool, default=False): + logger.info(">>> Using experimental issues") + from . import issues_ch as issues +else: + from . import issues_pg as issues diff --git a/api/chalicelib/core/issues/issues_ch.py b/api/chalicelib/core/issues/issues_ch.py new file mode 100644 index 000000000..d705e3288 --- /dev/null +++ b/api/chalicelib/core/issues/issues_ch.py @@ -0,0 +1,31 @@ +from chalicelib.utils import ch_client, helper +from .issues_pg import get_all_types + + +def get(project_id, issue_id): + with ch_client.ClickHouseClient() as cur: + query = cur.format(query=""" \ + SELECT * + FROM product_analytics.events + WHERE project_id = %(project_id)s + AND issue_id = %(issue_id)s;""", + parameters={"project_id": project_id, "issue_id": issue_id}) + data = cur.execute(query=query) + if data is not None and len(data) > 0: + data = data[0] + data["title"] = helper.get_issue_title(data["type"]) + return helper.dict_to_camel_case(data) + + +def get_by_session_id(session_id, project_id, issue_type=None): + with ch_client.ClickHouseClient as cur: + query = cur.format(query=f"""\ + SELECT * + FROM product_analytics.events + WHERE session_id = %(session_id)s + AND project_id= %(project_id)s + {"AND issue_type = %(type)s" if issue_type is not None else ""} + ORDER BY created_at;""", + parameters={"session_id": session_id, "project_id": project_id, "type": issue_type}) + data = cur.execute(query) + return helper.list_to_camel_case(data) diff --git a/api/chalicelib/core/issues.py b/api/chalicelib/core/issues/issues_pg.py similarity index 100% rename from api/chalicelib/core/issues.py rename to api/chalicelib/core/issues/issues_pg.py diff --git a/api/chalicelib/core/metrics/custom_metrics.py b/api/chalicelib/core/metrics/custom_metrics.py index c73bd282f..2a2dd3b30 100644 --- a/api/chalicelib/core/metrics/custom_metrics.py +++ b/api/chalicelib/core/metrics/custom_metrics.py @@ -4,7 +4,7 @@ import logging from fastapi import HTTPException, status import schemas -from chalicelib.core import issues +from chalicelib.core.issues import issues from chalicelib.core.errors import errors from chalicelib.core.metrics import heatmaps, product_analytics, funnels from chalicelib.core.sessions import sessions, sessions_search diff --git a/api/chalicelib/core/sessions/sessions_replay.py b/api/chalicelib/core/sessions/sessions_replay.py index b0ea40613..6602cc434 100644 --- a/api/chalicelib/core/sessions/sessions_replay.py +++ b/api/chalicelib/core/sessions/sessions_replay.py @@ -1,5 +1,6 @@ import schemas -from chalicelib.core import metadata, issues, assist, canvas, user_testing +from chalicelib.core import metadata, assist, canvas, user_testing +from chalicelib.core.issues import issues from chalicelib.core.events import events, events_mobile from . import sessions_mobs, sessions_devtool from chalicelib.core.errors.modules import errors_helper diff --git a/api/routers/core.py b/api/routers/core.py index 42ba9b281..260df30f2 100644 --- a/api/routers/core.py +++ b/api/routers/core.py @@ -4,8 +4,9 @@ from decouple import config from fastapi import Depends, Body, BackgroundTasks import schemas -from chalicelib.core import events, projects, issues, metadata, reset_password, log_tools, \ +from chalicelib.core import events, projects, metadata, reset_password, log_tools, \ announcements, weekly_report, assist, mobile, tenants, boarding, notifications, webhook, users, saved_search, tags +from chalicelib.core.issues import issues from chalicelib.core.sourcemaps import sourcemaps from chalicelib.core.metrics import custom_metrics from chalicelib.core.alerts import alerts From d42c4a46f9050a01b5ca13c76f52804ded5b36cb Mon Sep 17 00:00:00 2001 From: Taha Yassine Kraiem Date: Fri, 16 May 2025 17:19:34 +0200 Subject: [PATCH 04/22] refactor(chalice): support incidents for replay --- api/chalicelib/core/events/__init__.py | 2 +- api/chalicelib/core/events/events_ch.py | 69 ++++++++++++++----- api/chalicelib/core/events/events_pg.py | 12 +++- api/chalicelib/core/issues/issues_ch.py | 27 +++++++- api/chalicelib/core/issues/issues_pg.py | 30 ++++++-- .../core/sessions/sessions_replay.py | 26 +------ api/schemas/schemas.py | 1 + 7 files changed, 116 insertions(+), 51 deletions(-) diff --git a/api/chalicelib/core/events/__init__.py b/api/chalicelib/core/events/__init__.py index d4df51f72..897cb7fa5 100644 --- a/api/chalicelib/core/events/__init__.py +++ b/api/chalicelib/core/events/__init__.py @@ -4,7 +4,7 @@ from decouple import config logger = logging.getLogger(__name__) -if config("EXP_EVENTS_REPLAY", cast=bool, default=False): +if config("EXP_EVENTS", cast=bool, default=False): logger.info(">>> Using experimental events replay") from . import events_ch as events else: diff --git a/api/chalicelib/core/events/events_ch.py b/api/chalicelib/core/events/events_ch.py index 4bca47a27..be90e230d 100644 --- a/api/chalicelib/core/events/events_ch.py +++ b/api/chalicelib/core/events/events_ch.py @@ -2,18 +2,27 @@ from chalicelib.utils import ch_client from .events_pg import * +def __explode_properties(rows): + for i in range(len(rows)): + rows[i] = {**rows[i], **rows[i]["$properties"]} + rows[i].pop("$properties") + return rows + + def get_customs_by_session_id(session_id, project_id): with ch_client.ClickHouseClient() as cur: rows = cur.execute(""" \ - SELECT c.*, + SELECT `$properties`, + created_at, 'CUSTOM' AS type - FROM product_analytics.events AS c - WHERE c.session_id = %(session_id)s + FROM product_analytics.events + WHERE session_id = %(session_id)s AND NOT `$auto_captured` - ORDER BY c.timestamp;""", - {"project_id": project_id, "session_id": session_id} - ) - return helper.dict_to_camel_case(rows) + AND `$event_name`!='INCIDENT' + ORDER BY created_at;""", + {"project_id": project_id, "session_id": session_id}) + rows = __explode_properties(rows) + return helper.list_to_camel_case(rows) def __merge_cells(rows, start, count, replacement): @@ -34,7 +43,7 @@ def __get_grouped_clickrage(rows, session_id, project_id): else: merge_count = 3 for i in range(len(rows)): - if rows[i]["timestamp"] == c["timestamp"]: + if rows[i]["created_at"] == c["createdAt"]: rows = __merge_cells(rows=rows, start=i, count=merge_count, @@ -48,19 +57,41 @@ def get_by_session_id(session_id, project_id, group_clickrage=False, event_type: select_events = ('CLICK', 'INPUT', 'LOCATION') if event_type is not None: select_events = (event_type,) - rows = cur.execute(""" \ - SELECT c.*, - `$event_name` AS type - FROM product_analytics.events - WHERE session_id = %(session_id)s - AND `$event_name` IN %(select_events)s - AND `$auto_captured` - ORDER BY c.timestamp;""", - {"project_id": project_id, "session_id": session_id, "select_events": select_events}) - + query = cur.format(query=""" \ + SELECT created_at, + `$properties`, + `$event_name` AS type + FROM product_analytics.events + WHERE session_id = %(session_id)s + AND `$event_name` IN %(select_events)s + AND `$auto_captured` + ORDER BY created_at;""", + parameters={"project_id": project_id, "session_id": session_id, + "select_events": select_events}) + rows = cur.execute(query) + rows = __explode_properties(rows) if group_clickrage and 'CLICK' in select_events: rows = __get_grouped_clickrage(rows=rows, session_id=session_id, project_id=project_id) rows = helper.list_to_camel_case(rows) - rows = sorted(rows, key=lambda k: (k["timestamp"], k["messageId"])) + rows = sorted(rows, key=lambda k: k["createdAt"]) + return rows + + +def get_incidents_by_session_id(session_id, project_id): + with ch_client.ClickHouseClient() as cur: + query = cur.format(query=""" \ + SELECT created_at, + `$properties`, + `properties`, + `$event_name` AS type + FROM product_analytics.events + WHERE session_id = %(session_id)s + AND `$event_name` = 'INCIDENT' + AND `$auto_captured` + ORDER BY created_at;""", + parameters={"project_id": project_id, "session_id": session_id}) + rows = cur.execute(query) + rows = helper.list_to_camel_case(rows) + rows = sorted(rows, key=lambda k: k["createdAt"]) return rows diff --git a/api/chalicelib/core/events/events_pg.py b/api/chalicelib/core/events/events_pg.py index d481e6259..06b524f51 100644 --- a/api/chalicelib/core/events/events_pg.py +++ b/api/chalicelib/core/events/events_pg.py @@ -1,14 +1,17 @@ +import logging from functools import cache from typing import Optional import schemas -from chalicelib.core.issues import issues from chalicelib.core.autocomplete import autocomplete +from chalicelib.core.issues import issues from chalicelib.core.sessions import sessions_metas from chalicelib.utils import pg_client, helper from chalicelib.utils.TimeUTC import TimeUTC from chalicelib.utils.event_filter_definition import SupportedFilter +logger = logging.getLogger(__name__) + def get_customs_by_session_id(session_id, project_id): with pg_client.PostgresClient() as cur: @@ -21,7 +24,7 @@ def get_customs_by_session_id(session_id, project_id): {"project_id": project_id, "session_id": session_id}) ) rows = cur.fetchall() - return helper.dict_to_camel_case(rows) + return helper.list_to_camel_case(rows) def __merge_cells(rows, start, count, replacement): @@ -179,6 +182,11 @@ def get_errors_by_session_id(session_id, project_id): return helper.list_to_camel_case(errors) +def get_incidents_by_session_id(session_id, project_id): + logger.warning("INCIDENTS not supported in PG") + return [] + + def search(text, event_type, project_id, source, key): if not event_type: return {"data": autocomplete.__get_autocomplete_table(text, project_id)} diff --git a/api/chalicelib/core/issues/issues_ch.py b/api/chalicelib/core/issues/issues_ch.py index d705e3288..b6297f598 100644 --- a/api/chalicelib/core/issues/issues_ch.py +++ b/api/chalicelib/core/issues/issues_ch.py @@ -1,4 +1,5 @@ from chalicelib.utils import ch_client, helper +import datetime from .issues_pg import get_all_types @@ -18,14 +19,38 @@ def get(project_id, issue_id): def get_by_session_id(session_id, project_id, issue_type=None): - with ch_client.ClickHouseClient as cur: + with ch_client.ClickHouseClient() as cur: query = cur.format(query=f"""\ SELECT * FROM product_analytics.events WHERE session_id = %(session_id)s AND project_id= %(project_id)s + AND `$event_name`='ISSUE' {"AND issue_type = %(type)s" if issue_type is not None else ""} ORDER BY created_at;""", parameters={"session_id": session_id, "project_id": project_id, "type": issue_type}) data = cur.execute(query) return helper.list_to_camel_case(data) + + +# To reduce the number of issues in the replay; +# will be removed once we agree on how to show issues +def reduce_issues(issues_list): + if issues_list is None: + return None + i = 0 + # remove same-type issues if the time between them is <2s + while i < len(issues_list) - 1: + for j in range(i + 1, len(issues_list)): + if issues_list[i]["issueType"] == issues_list[j]["issueType"]: + break + else: + i += 1 + break + + if issues_list[i]["createdAt"] - issues_list[j]["createdAt"] < datetime.timedelta(seconds=2): + issues_list.pop(j) + else: + i += 1 + + return issues_list diff --git a/api/chalicelib/core/issues/issues_pg.py b/api/chalicelib/core/issues/issues_pg.py index 66edcdf45..b2ca0b142 100644 --- a/api/chalicelib/core/issues/issues_pg.py +++ b/api/chalicelib/core/issues/issues_pg.py @@ -4,12 +4,11 @@ from chalicelib.utils import pg_client, helper def get(project_id, issue_id): with pg_client.PostgresClient() as cur: query = cur.mogrify( - """\ - SELECT - * + """ \ + SELECT * FROM public.issues WHERE project_id = %(project_id)s - AND issue_id = %(issue_id)s;""", + AND issue_id = %(issue_id)s;""", {"project_id": project_id, "issue_id": issue_id} ) cur.execute(query=query) @@ -35,6 +34,29 @@ def get_by_session_id(session_id, project_id, issue_type=None): return helper.list_to_camel_case(cur.fetchall()) +# To reduce the number of issues in the replay; +# will be removed once we agree on how to show issues +def reduce_issues(issues_list): + if issues_list is None: + return None + i = 0 + # remove same-type issues if the time between them is <2s + while i < len(issues_list) - 1: + for j in range(i + 1, len(issues_list)): + if issues_list[i]["type"] == issues_list[j]["type"]: + break + else: + i += 1 + break + + if issues_list[i]["timestamp"] - issues_list[j]["timestamp"] < 2000: + issues_list.pop(j) + else: + i += 1 + + return issues_list + + def get_all_types(): return [ { diff --git a/api/chalicelib/core/sessions/sessions_replay.py b/api/chalicelib/core/sessions/sessions_replay.py index 6602cc434..b53d60a29 100644 --- a/api/chalicelib/core/sessions/sessions_replay.py +++ b/api/chalicelib/core/sessions/sessions_replay.py @@ -129,30 +129,8 @@ def get_events(project_id, session_id): data['userTesting'] = user_testing.get_test_signals(session_id=session_id, project_id=project_id) data['issues'] = issues.get_by_session_id(session_id=session_id, project_id=project_id) - data['issues'] = reduce_issues(data['issues']) + data['issues'] = issues.reduce_issues(data['issues']) + data['incidents'] = events.get_incidents_by_session_id(session_id=session_id, project_id=project_id) return data else: return None - - -# To reduce the number of issues in the replay; -# will be removed once we agree on how to show issues -def reduce_issues(issues_list): - if issues_list is None: - return None - i = 0 - # remove same-type issues if the time between them is <2s - while i < len(issues_list) - 1: - for j in range(i + 1, len(issues_list)): - if issues_list[i]["type"] == issues_list[j]["type"]: - break - else: - i += 1 - break - - if issues_list[i]["timestamp"] - issues_list[j]["timestamp"] < 2000: - issues_list.pop(j) - else: - i += 1 - - return issues_list diff --git a/api/schemas/schemas.py b/api/schemas/schemas.py index 973dce8f1..7ab8f5ad4 100644 --- a/api/schemas/schemas.py +++ b/api/schemas/schemas.py @@ -406,6 +406,7 @@ class EventType(str, Enum): ERROR_MOBILE = "errorMobile" SWIPE_MOBILE = "swipeMobile" EVENT = "event" + INCIDENT = "incident" class PerformanceEventType(str, Enum): From b7028ff1316ce09a9431e9a546f1c890b1bd08f4 Mon Sep 17 00:00:00 2001 From: nick-delirium Date: Mon, 19 May 2025 11:01:35 +0200 Subject: [PATCH 05/22] ui: remove scope setup, fix spot capitalization --- frontend/app/PrivateRoutes.tsx | 9 ----- .../Spots/SpotsList/SpotListItem.tsx | 2 +- frontend/app/mstore/userStore.ts | 40 +++++++------------ 3 files changed, 16 insertions(+), 35 deletions(-) diff --git a/frontend/app/PrivateRoutes.tsx b/frontend/app/PrivateRoutes.tsx index f16e41523..45b5d9dd4 100644 --- a/frontend/app/PrivateRoutes.tsx +++ b/frontend/app/PrivateRoutes.tsx @@ -32,7 +32,6 @@ const components: any = { ), SpotsListPure: lazy(() => import('Components/Spots/SpotsList')), SpotPure: lazy(() => import('Components/Spots/SpotPlayer')), - ScopeSetup: lazy(() => import('Components/ScopeForm')), HighlightsPure: lazy(() => import('Components/Highlights/HighlightsList')), KaiPure: lazy(() => import('Components/Kai/KaiChat')), }; @@ -111,7 +110,6 @@ function PrivateRoutes() { const sites = projectsStore.list; const { siteId } = projectsStore; const hasRecordings = sites.some((s) => s.recorded); - const redirectToSetup = scope === 0; const redirectToOnboarding = !onboarding && (localStorage.getItem(GLOBAL_HAS_NO_RECORDINGS) === 'true' || @@ -138,13 +136,6 @@ function PrivateRoutes() { return ( }> - - {redirectToSetup ? : null} - +
diff --git a/frontend/app/mstore/userStore.ts b/frontend/app/mstore/userStore.ts index 7412b0968..ccbfb9e65 100644 --- a/frontend/app/mstore/userStore.ts +++ b/frontend/app/mstore/userStore.ts @@ -13,45 +13,25 @@ import i18next, { TFunction } from 'i18next'; class UserStore { t: TFunction; - list: User[] = []; - instance: User | null = null; - page: number = 1; - pageSize: number = 10; - searchQuery: string = ''; - modifiedCount: number = 0; - loading: boolean = false; - saving: boolean = false; - limits: any = {}; - initialDataFetched: boolean = false; - account = new Account(); - siteId: string | null = null; - passwordRequestError: boolean = false; - passwordErrors: string[] = []; - tenants: any[] = []; - onboarding: boolean = false; - sites: any[] = []; - jwt: string | null = null; - spotJwt: string | null = null; - errors: any[] = []; loginRequest = { @@ -119,7 +99,11 @@ class UserStore { } get isSSOSupported() { - return this.isEnterprise || this.account?.edition === 'msaas' || this.authStore.authDetails?.edition === 'msaas'; + return ( + this.isEnterprise || + this.account?.edition === 'msaas' || + this.authStore.authDetails?.edition === 'msaas' + ); } get isLoggedIn() { @@ -242,7 +226,7 @@ class UserStore { resolve(response); }) .catch(async (e) => { - toast.error(e.message || this.t("Failed to save user's data.")); + toast.error(e.message || this.t("Failed to save user's data.")); reject(e); }) .finally(() => { @@ -383,8 +367,12 @@ class UserStore { }); } catch (error) { const inUse = error.message.includes('already in use'); - const inUseMsg = this.t('An account with this email already exists. Please log in or use a different email address.') - const genericMsg = this.t('Error signing up; please check your data and try again') + const inUseMsg = this.t( + 'An account with this email already exists. Please log in or use a different email address.', + ); + const genericMsg = this.t( + 'Error signing up; please check your data and try again', + ); runInAction(() => { this.signUpRequest = { loading: false, @@ -411,7 +399,9 @@ class UserStore { this.spotJwt = data.spotJwt; }); } catch (e) { - toast.error(e.message || this.t('Error resetting your password; please try again')); + toast.error( + e.message || this.t('Error resetting your password; please try again'), + ); throw e; } finally { runInAction(() => { From b8f97ad15b65c6216462b2080fdbfdedc5dafeb0 Mon Sep 17 00:00:00 2001 From: nick-delirium Date: Fri, 16 May 2025 17:51:14 +0200 Subject: [PATCH 06/22] test fix for charts --- .../CustomMetricsWidgets/SessionsBy.tsx | 3 +- .../components/WidgetChart/WidgetChart.tsx | 52 +++++++++++-------- frontend/app/mstore/dashboardStore.ts | 4 +- frontend/app/mstore/types/widget.ts | 3 +- 4 files changed, 36 insertions(+), 26 deletions(-) diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/SessionsBy.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/SessionsBy.tsx index 1b9f91d30..08db63eea 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/SessionsBy.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/SessionsBy.tsx @@ -7,6 +7,7 @@ import { useModal } from 'Components/ModalContext'; import Widget from '@/mstore/types/widget'; import { useTranslation } from 'react-i18next'; import { FilterKey } from 'Types/filter/filterType'; +import { observer } from 'mobx-react-lite'; interface Props { metric?: any; @@ -128,4 +129,4 @@ function SessionsBy(props: Props) { ); } -export default SessionsBy; +export default observer(SessionsBy); diff --git a/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx b/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx index abdbc9066..49235d131 100644 --- a/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx +++ b/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx @@ -55,7 +55,7 @@ function WidgetChart(props: Props) { const { isSaved = false, metric, isTemplate } = props; const { dashboardStore, metricStore } = useStore(); const _metric: any = props.isPreview ? metricStore.instance : props.metric; - const { data } = _metric; + const data = _metric.data; const { period } = dashboardStore; const { drillDownPeriod } = dashboardStore; const { drillDownFilter } = dashboardStore; @@ -66,6 +66,7 @@ function WidgetChart(props: Props) { const metricParams = _metric.params; const prevMetricRef = useRef(); const isMounted = useIsMounted(); + const [metricData, setMetricData] = useState(data); const [compData, setCompData] = useState(null); const [enabledRows, setEnabledRows] = useState( _metric.series.map((s) => s.name), @@ -157,9 +158,16 @@ function WidgetChart(props: Props) { setStale(true); }, 4000); dashboardStore - .fetchMetricChartData(metric, payload, isSaved, period, isComparison) - .then((res: any) => { + .fetchMetricChartData( + metric, + payload, + isSaved, + period, + isComparison + ) + .then((res) => { if (isComparison) setCompData(res); + else setMetricData(res); clearTimeout(tm); setStale(false); }) @@ -252,7 +260,7 @@ function WidgetChart(props: Props) { const renderChart = React.useCallback(() => { const { metricType, metricOf } = _metric; const { viewType } = _metric; - const metricWithData = { ..._metric, data }; + const metricWithData = { ..._metric, data: metricData }; if (metricType === FUNNEL) { if (viewType === 'table') { @@ -266,7 +274,7 @@ function WidgetChart(props: Props) { valueLabel?: string; }[] = [ { - value: data.funnel.totalConversionsPercentage, + value: metricData.funnel.totalConversionsPercentage, compData: compData ? compData.funnel.totalConversionsPercentage : undefined, @@ -290,7 +298,7 @@ function WidgetChart(props: Props) { return ( @@ -306,14 +314,14 @@ function WidgetChart(props: Props) { ); } if (metricType === TIMESERIES) { - const chartData = { ...data }; + const chartData = { ...metricData }; chartData.namesMap = Array.isArray(chartData.namesMap) ? chartData.namesMap.map((n) => (enabledRows.includes(n) ? n : null)) : chartData.namesMap; @@ -411,7 +419,7 @@ function WidgetChart(props: Props) { return ( acc + curr[data.namesMap[i]], + value: metricData.chart.reduce( + (acc, curr) => acc + curr[metricData.namesMap[i]], 0, ), compData: compData @@ -444,7 +452,7 @@ function WidgetChart(props: Props) { 0, ) : undefined, - series: data.namesMap[i], + series: metricData.namesMap[i], }); } @@ -469,7 +477,7 @@ function WidgetChart(props: Props) { return ( @@ -479,7 +487,7 @@ function WidgetChart(props: Props) { return ( @@ -489,7 +497,7 @@ function WidgetChart(props: Props) { return ( @@ -522,10 +530,10 @@ function WidgetChart(props: Props) { } if (metricType === INSIGHTS) { - return ; + return ; } - if (metricType === USER_PATH && data && data.links) { + if (metricType === USER_PATH && metricData && metricData.links) { const isUngrouped = props.isPreview ? !(_metric.hideExcess ?? true) : false; @@ -533,7 +541,7 @@ function WidgetChart(props: Props) { return ( { dashboardStore.drillDownFilter.merge({ filters, page: 1 }); @@ -548,7 +556,7 @@ function WidgetChart(props: Props) { if (viewType === 'trend') { return ( {t('Unknown metric type')}; - }, [data, compData, enabledRows, _metric]); + }, [data, compData, enabledRows, _metric, metricData]); const showTable = _metric.metricType === TIMESERIES && diff --git a/frontend/app/mstore/dashboardStore.ts b/frontend/app/mstore/dashboardStore.ts index ebf8b118b..f5e3bbce7 100644 --- a/frontend/app/mstore/dashboardStore.ts +++ b/frontend/app/mstore/dashboardStore.ts @@ -522,7 +522,6 @@ export default class DashboardStore { isComparison?: boolean, ): Promise { period = period.toTimestamps(); - const { density } = data; const params = { ...period, ...data, key: metric.predefinedKey }; if (!isComparison && metric.page && metric.limit) { @@ -547,7 +546,8 @@ export default class DashboardStore { params, isSaved ); - resolve(metric.setData(data, period, isComparison, density)); + const res = metric.setData(data, period, isComparison, data.density) + resolve(res); } catch (error) { reject(error); } finally { diff --git a/frontend/app/mstore/types/widget.ts b/frontend/app/mstore/types/widget.ts index 2f0ae0842..04975e425 100644 --- a/frontend/app/mstore/types/widget.ts +++ b/frontend/app/mstore/types/widget.ts @@ -396,9 +396,10 @@ export default class Widget { _data.funnel = new Funnel().fromJSON(data); } else if (this.metricType === TABLE) { const count = data[0]['count']; - _data['values'] = data[0]['values'].map((s: any) => + const vals = data[0]['values'].map((s: any) => new SessionsByRow().fromJson(s, count, this.metricOf), ); + _data['values'] = vals _data['total'] = data[0]['total']; } else { if (data.hasOwnProperty('chart')) { From 8e0b30ece40e7dd7d9aa86d37a13cfbbc6387dfc Mon Sep 17 00:00:00 2001 From: nick-delirium Date: Mon, 19 May 2025 13:43:28 +0200 Subject: [PATCH 07/22] ui: delete deprecated components, fix widgetchart props, fix dashboard page reload check --- .../DashboardMetricSelection.tsx | 143 +----------------- .../DashboardModal/DashboardModal.tsx | 109 ------------- .../components/DashboardModal/index.ts | 1 - .../DashboardView/DashboardView.tsx | 25 +-- .../DashboardWidgetGrid/AddMetric.tsx | 129 ---------------- .../AddMetricContainer.tsx | 138 ----------------- .../DashboardWidgetGrid.tsx | 1 - .../components/MetricsList/GridView.tsx | 37 ----- .../components/WidgetChart/WidgetChart.tsx | 56 +++---- .../WidgetDateRange/WidgetDateRange.tsx | 1 - .../WidgetWrapper/WidgetWrapper.tsx | 11 +- .../WidgetWrapper/WidgetWrapperNew.tsx | 4 +- frontend/app/mstore/dashboardStore.ts | 57 +++---- 13 files changed, 61 insertions(+), 651 deletions(-) delete mode 100644 frontend/app/components/Dashboard/components/DashboardModal/DashboardModal.tsx delete mode 100644 frontend/app/components/Dashboard/components/DashboardModal/index.ts delete mode 100644 frontend/app/components/Dashboard/components/DashboardWidgetGrid/AddMetric.tsx delete mode 100644 frontend/app/components/Dashboard/components/DashboardWidgetGrid/AddMetricContainer.tsx delete mode 100644 frontend/app/components/Dashboard/components/MetricsList/GridView.tsx diff --git a/frontend/app/components/Dashboard/components/DashboardMetricSelection/DashboardMetricSelection.tsx b/frontend/app/components/Dashboard/components/DashboardMetricSelection/DashboardMetricSelection.tsx index 491219d7d..3872ead56 100644 --- a/frontend/app/components/Dashboard/components/DashboardMetricSelection/DashboardMetricSelection.tsx +++ b/frontend/app/components/Dashboard/components/DashboardMetricSelection/DashboardMetricSelection.tsx @@ -1,10 +1,7 @@ -import React, { useEffect } from 'react'; +import React from 'react'; import { useObserver } from 'mobx-react-lite'; -import { Icon, Loader } from 'UI'; +import { Icon } from 'UI'; import cn from 'classnames'; -import { useStore } from 'App/mstore'; -import WidgetWrapper from '../WidgetWrapper'; -import { useTranslation } from 'react-i18next'; interface IWiProps { category: Record; @@ -57,139 +54,3 @@ export function WidgetCategoryItem({ ); } - -interface IProps { - handleCreateNew?: () => void; - isDashboardExists?: boolean; -} - -function DashboardMetricSelection(props: IProps) { - const { t } = useTranslation(); - const { dashboardStore } = useStore(); - const widgetCategories: any[] = useObserver( - () => dashboardStore.widgetCategories, - ); - const loadingTemplates = useObserver(() => dashboardStore.loadingTemplates); - const [activeCategory, setActiveCategory] = React.useState(); - const [selectAllCheck, setSelectAllCheck] = React.useState(false); - const selectedWidgetIds = useObserver(() => - dashboardStore.selectedWidgets.map((widget: any) => widget.metricId), - ); - const scrollContainer = React.useRef(null); - - useEffect(() => { - dashboardStore?.fetchTemplates(true).then((categories) => { - setActiveCategory(categories[0]); - }); - }, []); - - useEffect(() => { - if (scrollContainer.current) { - scrollContainer.current.scrollTop = 0; - } - }, [activeCategory, scrollContainer.current]); - - const handleWidgetCategoryClick = (category: any) => { - setActiveCategory(category); - setSelectAllCheck(false); - }; - - const toggleAllWidgets = ({ target: { checked } }) => { - setSelectAllCheck(checked); - if (checked) { - dashboardStore.selectWidgetsByCategory(activeCategory.name); - } else { - dashboardStore.removeSelectedWidgetByCategory(activeCategory); - } - }; - - return useObserver(() => ( - -
-
-
{t('Type')}
-
- -
- {activeCategory && ( - <> -
-

{activeCategory.name}

- - {activeCategory.widgets.length} - -
- -
- -
- - )} -
-
-
-
-
- {activeCategory && - widgetCategories.map((category, index) => ( - - ))} -
-
-
-
- {activeCategory && - activeCategory.widgets.map((widget: any) => ( - dashboardStore.toggleWidgetSelection(widget)} - /> - ))} - {props.isDashboardExists && activeCategory?.name === 'custom' && ( -
- - {t('Create Metric')} -
- )} -
-
-
-
- )); -} - -export default DashboardMetricSelection; diff --git a/frontend/app/components/Dashboard/components/DashboardModal/DashboardModal.tsx b/frontend/app/components/Dashboard/components/DashboardModal/DashboardModal.tsx deleted file mode 100644 index e175c8ccb..000000000 --- a/frontend/app/components/Dashboard/components/DashboardModal/DashboardModal.tsx +++ /dev/null @@ -1,109 +0,0 @@ -import React from 'react'; -import { useObserver } from 'mobx-react-lite'; -import { Button } from 'antd'; -import { withRouter, RouteComponentProps } from 'react-router-dom'; -import { useStore } from 'App/mstore'; -import { useModal } from 'App/components/Modal'; -import { dashboardMetricCreate, withSiteId } from 'App/routes'; -import DashboardForm from '../DashboardForm'; -import DashboardMetricSelection from '../DashboardMetricSelection'; -import { useTranslation } from 'react-i18next'; -import { PANEL_SIZES } from 'App/constants/panelSizes' - -interface Props extends RouteComponentProps { - history: any; - siteId?: string; - dashboardId?: string; - onMetricAdd?: () => void; -} -function DashboardModal(props: Props) { - const { t } = useTranslation(); - const { history, siteId, dashboardId } = props; - const { dashboardStore } = useStore(); - const selectedWidgetsCount = useObserver( - () => dashboardStore.selectedWidgets.length, - ); - const { hideModal } = useModal(); - const dashboard = useObserver(() => dashboardStore.dashboardInstance); - const loading = useObserver(() => dashboardStore.isSaving); - - const onSave = () => { - dashboardStore - .save(dashboard) - .then(async (syncedDashboard) => { - if (dashboard.exists()) { - await dashboardStore.fetch(dashboard.dashboardId); - } - dashboardStore.selectDashboardById(syncedDashboard.dashboardId); - history.push( - withSiteId(`/dashboard/${syncedDashboard.dashboardId}`, siteId), - ); - }) - .then(hideModal); - }; - - const handleCreateNew = () => { - const path = withSiteId(dashboardMetricCreate(dashboardId), siteId); - props.onMetricAdd(); - history.push(path); - hideModal(); - }; - const isDashboardExists = dashboard.exists(); - - return useObserver(() => ( -
-
-
-
-

- {isDashboardExists - ? t('Add metrics to dashboard') - : t('Create Dashboard')} -

-
-
- {t('Past 7 days data')} -
-
- {!isDashboardExists && ( - <> - -

- {t( - 'Create new dashboard by choosing from the range of predefined metrics that you care about. You can always add your custom metrics later.', - )} -

- - )} - - -
- - - {selectedWidgetsCount} {t('Metrics')} - -
-
-
- )); -} - -export default withRouter(DashboardModal); diff --git a/frontend/app/components/Dashboard/components/DashboardModal/index.ts b/frontend/app/components/Dashboard/components/DashboardModal/index.ts deleted file mode 100644 index ff9b51745..000000000 --- a/frontend/app/components/Dashboard/components/DashboardModal/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './DashboardModal'; diff --git a/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx b/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx index 85b15b17e..02c7d73fe 100644 --- a/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx +++ b/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx @@ -11,7 +11,6 @@ import withPageTitle from 'HOCs/withPageTitle'; import withReport from 'App/components/hocs/withReport'; import { useHistory } from 'react-router'; import DashboardHeader from '../DashboardHeader'; -import DashboardModal from '../DashboardModal'; import DashboardWidgetGrid from '../DashboardWidgetGrid'; import AiQuery from './AiQuery'; import { PANEL_SIZES } from 'App/constants/panelSizes' @@ -69,15 +68,18 @@ function DashboardView(props: Props) { onAddWidgets(); trimQuery(); } + dashboardStore.resetDensity(); return () => dashboardStore.resetSelectedDashboard(); }, []); useEffect(() => { - const isExists = dashboardStore.getDashboardById(dashboardId); - if (!isExists) { - history.push(withSiteId('/dashboard', siteId)); - } + const isExists = async () => dashboardStore.getDashboardById(dashboardId); + isExists().then((res) => { + if (!res) { + history.push(withSiteId('/dashboard', siteId)); + } + }) }, [dashboardId]); useEffect(() => { @@ -85,18 +87,6 @@ function DashboardView(props: Props) { dashboardStore.fetch(dashboard.dashboardId); }, [dashboard]); - const onAddWidgets = () => { - dashboardStore.initDashboard(dashboard); - showModal( - , - { right: true }, - ); - }; - if (!dashboard) return null; const originStr = window.env.ORIGIN || window.location.origin; @@ -117,7 +107,6 @@ function DashboardView(props: Props) { diff --git a/frontend/app/components/Dashboard/components/DashboardWidgetGrid/AddMetric.tsx b/frontend/app/components/Dashboard/components/DashboardWidgetGrid/AddMetric.tsx deleted file mode 100644 index 4220045a4..000000000 --- a/frontend/app/components/Dashboard/components/DashboardWidgetGrid/AddMetric.tsx +++ /dev/null @@ -1,129 +0,0 @@ -import React from 'react'; -import { observer } from 'mobx-react-lite'; -import { Loader } from 'UI'; -import { Button } from 'antd'; -import WidgetWrapper from 'App/components/Dashboard/components/WidgetWrapper'; -import { useStore } from 'App/mstore'; -import { useModal } from 'App/components/Modal'; -import { dashboardMetricCreate, withSiteId } from 'App/routes'; -import { withRouter, RouteComponentProps } from 'react-router-dom'; -import { useTranslation } from 'react-i18next'; - -interface IProps extends RouteComponentProps { - siteId: string; - title: string; - description: string; -} - -function AddMetric({ history, siteId, title, description }: IProps) { - const { t } = useTranslation(); - const [metrics, setMetrics] = React.useState[]>([]); - - const { dashboardStore } = useStore(); - const { hideModal } = useModal(); - - React.useEffect(() => { - dashboardStore?.fetchTemplates(true).then((cats: any[]) => { - const customMetrics = - cats.find((category) => category.name === 'custom')?.widgets || []; - - setMetrics(customMetrics); - }); - }, []); - - const dashboard = dashboardStore.selectedDashboard; - const selectedWidgetIds = dashboardStore.selectedWidgets.map( - (widget: any) => widget.metricId, - ); - const queryParams = new URLSearchParams(location.search); - - const onSave = () => { - if (selectedWidgetIds.length === 0) return; - dashboardStore - .save(dashboard) - .then(async (syncedDashboard: Record) => { - if (dashboard.exists()) { - await dashboardStore.fetch(dashboard.dashboardId); - } - dashboardStore.selectDashboardById(syncedDashboard.dashboardId); - }) - .then(hideModal); - }; - - const onCreateNew = () => { - const path = withSiteId( - dashboardMetricCreate(dashboard.dashboardId), - siteId, - ); - if (!queryParams.has('modal')) history.push('?modal=addMetric'); - history.push(path); - hideModal(); - }; - - return ( -
-
-
-
-

{title}

-
{description}
-
- - -
- -
- {metrics ? ( - metrics.map((metric: any) => ( - dashboardStore.toggleWidgetSelection(metric)} - /> - )) - ) : ( -
{t('No custom metrics created.')}
- )} -
-
- -
-
- {t('Selected')} - {selectedWidgetIds.length} -  {t('out of')}  - {metrics ? metrics.length : 0} -
- -
-
-
- ); -} - -export default withRouter(observer(AddMetric)); diff --git a/frontend/app/components/Dashboard/components/DashboardWidgetGrid/AddMetricContainer.tsx b/frontend/app/components/Dashboard/components/DashboardWidgetGrid/AddMetricContainer.tsx deleted file mode 100644 index 7f768002e..000000000 --- a/frontend/app/components/Dashboard/components/DashboardWidgetGrid/AddMetricContainer.tsx +++ /dev/null @@ -1,138 +0,0 @@ -import React from 'react'; -import { observer } from 'mobx-react-lite'; -import { Icon } from 'UI'; -import { useModal } from 'App/components/Modal'; -import { useStore } from 'App/mstore'; -import cn from 'classnames'; -import AddMetric from './AddMetric'; -import AddPredefinedMetric from './AddPredefinedMetric'; - -interface AddMetricButtonProps { - iconName: 'bar-pencil' | 'grid-check'; - title: string; - description: string; - isPremade?: boolean; - isPopup?: boolean; - onClick: () => void; -} - -function AddMetricButton({ - iconName, - title, - description, - onClick, - isPremade, - isPopup, -}: AddMetricButtonProps) { - return ( -
-
- -
-
-
- {title} -
-
- {description} -
-
-
- ); -} - -interface Props { - siteId: string; - isPopup?: boolean; - onAction?: () => void; -} - -function AddMetricContainer({ siteId, isPopup, onAction }: Props) { - const { showModal } = useModal(); - const { dashboardStore } = useStore(); - - const onAddCustomMetrics = () => { - onAction?.(); - dashboardStore.initDashboard(dashboardStore.selectedDashboard); - showModal( - , - { right: true }, - ); - }; - - const onAddPredefinedMetrics = () => { - onAction?.(); - dashboardStore.initDashboard(dashboardStore.selectedDashboard); - showModal( - , - { right: true }, - ); - }; - - const classes = isPopup - ? 'bg-white border rounded p-4 grid grid-rows-2 gap-4' - : 'bg-white border border-dashed hover:!border-gray-medium rounded p-8 grid grid-cols-2 gap-8'; - return ( -
- - -
- ); -} - -export default observer(AddMetricContainer); diff --git a/frontend/app/components/Dashboard/components/DashboardWidgetGrid/DashboardWidgetGrid.tsx b/frontend/app/components/Dashboard/components/DashboardWidgetGrid/DashboardWidgetGrid.tsx index 15241457d..117719eaa 100644 --- a/frontend/app/components/Dashboard/components/DashboardWidgetGrid/DashboardWidgetGrid.tsx +++ b/frontend/app/components/Dashboard/components/DashboardWidgetGrid/DashboardWidgetGrid.tsx @@ -12,7 +12,6 @@ import { useTranslation } from 'react-i18next'; interface Props { siteId: string; dashboardId: string; - onEditHandler: () => void; id?: string; } diff --git a/frontend/app/components/Dashboard/components/MetricsList/GridView.tsx b/frontend/app/components/Dashboard/components/MetricsList/GridView.tsx deleted file mode 100644 index a7b989871..000000000 --- a/frontend/app/components/Dashboard/components/MetricsList/GridView.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import React from 'react'; -import WidgetWrapper from 'App/components/Dashboard/components/WidgetWrapper'; -import { withRouter, RouteComponentProps } from 'react-router-dom'; -import { withSiteId } from 'App/routes'; - -interface Props extends RouteComponentProps { - list: any; - siteId: any; - selectedList: any; -} -function GridView(props: Props) { - const { siteId, list, selectedList, history } = props; - - const onItemClick = (metricId: number) => { - const path = withSiteId(`/metrics/${metricId}`, siteId); - history.push(path); - }; - - return ( -
- {list.map((metric: any) => ( - - onItemClick(parseInt(metric.metricId))} - /> - - ))} -
- ); -} - -export default withRouter(GridView); diff --git a/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx b/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx index 49235d131..3106177d0 100644 --- a/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx +++ b/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx @@ -54,7 +54,7 @@ function WidgetChart(props: Props) { }); const { isSaved = false, metric, isTemplate } = props; const { dashboardStore, metricStore } = useStore(); - const _metric: any = props.isPreview ? metricStore.instance : props.metric; + const _metric: any = props.metric; const data = _metric.data; const { period } = dashboardStore; const { drillDownPeriod } = dashboardStore; @@ -66,7 +66,6 @@ function WidgetChart(props: Props) { const metricParams = _metric.params; const prevMetricRef = useRef(); const isMounted = useIsMounted(); - const [metricData, setMetricData] = useState(data); const [compData, setCompData] = useState(null); const [enabledRows, setEnabledRows] = useState( _metric.series.map((s) => s.name), @@ -158,16 +157,9 @@ function WidgetChart(props: Props) { setStale(true); }, 4000); dashboardStore - .fetchMetricChartData( - metric, - payload, - isSaved, - period, - isComparison - ) + .fetchMetricChartData(metric, payload, isSaved, period, isComparison) .then((res) => { if (isComparison) setCompData(res); - else setMetricData(res); clearTimeout(tm); setStale(false); }) @@ -189,10 +181,10 @@ function WidgetChart(props: Props) { } prevMetricRef.current = _metric; const timestmaps = drillDownPeriod.toTimestamps(); - const density = props.isPreview ? metric.density : dashboardStore.selectedDensity + const density = dashboardStore.selectedDensity; const payload = isSaved - ? { ...metricParams, density } - : { ...params, ...timestmaps, ..._metric.toJson(), density }; + ? { ...metricParams, density } + : { ...params, ...timestmaps, ..._metric.toJson(), density }; debounceRequest( _metric, payload, @@ -260,7 +252,7 @@ function WidgetChart(props: Props) { const renderChart = React.useCallback(() => { const { metricType, metricOf } = _metric; const { viewType } = _metric; - const metricWithData = { ..._metric, data: metricData }; + const metricWithData = { ..._metric, data }; if (metricType === FUNNEL) { if (viewType === 'table') { @@ -274,7 +266,7 @@ function WidgetChart(props: Props) { valueLabel?: string; }[] = [ { - value: metricData.funnel.totalConversionsPercentage, + value: data.funnel.totalConversionsPercentage, compData: compData ? compData.funnel.totalConversionsPercentage : undefined, @@ -298,7 +290,7 @@ function WidgetChart(props: Props) { return ( @@ -314,14 +306,14 @@ function WidgetChart(props: Props) { ); } if (metricType === TIMESERIES) { - const chartData = { ...metricData }; + const chartData = { ...data }; chartData.namesMap = Array.isArray(chartData.namesMap) ? chartData.namesMap.map((n) => (enabledRows.includes(n) ? n : null)) : chartData.namesMap; @@ -419,7 +411,7 @@ function WidgetChart(props: Props) { return ( acc + curr[metricData.namesMap[i]], + value: data.chart.reduce( + (acc, curr) => acc + curr[data.namesMap[i]], 0, ), compData: compData @@ -452,7 +444,7 @@ function WidgetChart(props: Props) { 0, ) : undefined, - series: metricData.namesMap[i], + series: data.namesMap[i], }); } @@ -477,7 +469,7 @@ function WidgetChart(props: Props) { return ( @@ -487,7 +479,7 @@ function WidgetChart(props: Props) { return ( @@ -497,7 +489,7 @@ function WidgetChart(props: Props) { return ( @@ -530,10 +522,10 @@ function WidgetChart(props: Props) { } if (metricType === INSIGHTS) { - return ; + return ; } - if (metricType === USER_PATH && metricData && metricData.links) { + if (metricType === USER_PATH && data && data.links) { const isUngrouped = props.isPreview ? !(_metric.hideExcess ?? true) : false; @@ -541,7 +533,7 @@ function WidgetChart(props: Props) { return ( { dashboardStore.drillDownFilter.merge({ filters, page: 1 }); @@ -556,7 +548,7 @@ function WidgetChart(props: Props) { if (viewType === 'trend') { return ( {t('Unknown metric type')}; - }, [data, compData, enabledRows, _metric, metricData]); + }, [data, compData, enabledRows, _metric, data]); const showTable = _metric.metricType === TIMESERIES && diff --git a/frontend/app/components/Dashboard/components/WidgetDateRange/WidgetDateRange.tsx b/frontend/app/components/Dashboard/components/WidgetDateRange/WidgetDateRange.tsx index ad11b6703..7a3a2e915 100644 --- a/frontend/app/components/Dashboard/components/WidgetDateRange/WidgetDateRange.tsx +++ b/frontend/app/components/Dashboard/components/WidgetDateRange/WidgetDateRange.tsx @@ -6,7 +6,6 @@ import { Space } from 'antd'; import { CUSTOM_RANGE, DATE_RANGE_COMPARISON_OPTIONS } from 'App/dateRange'; import Period from 'Types/app/period'; import RangeGranularity from './RangeGranularity'; -import { useTranslation } from 'react-i18next'; function WidgetDateRange({ label = 'Time Range', diff --git a/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapper.tsx b/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapper.tsx index d8472bc76..b3b24118f 100644 --- a/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapper.tsx +++ b/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapper.tsx @@ -1,13 +1,12 @@ import React, { useRef, lazy } from 'react'; import cn from 'classnames'; -import { ItemMenu, TextEllipsis } from 'UI'; +import { TextEllipsis } from 'UI'; import { useDrag, useDrop } from 'react-dnd'; import { observer } from 'mobx-react-lite'; import { useStore } from 'App/mstore'; import { withRouter, RouteComponentProps } from 'react-router-dom'; import { withSiteId, dashboardMetricDetails } from 'App/routes'; import { FilterKey } from 'App/types/filter/filterType'; -import { TIMESERIES } from 'App/constants/card'; import TemplateOverlay from './TemplateOverlay'; const WidgetChart = lazy( @@ -45,7 +44,6 @@ function WidgetWrapper(props: Props & RouteComponentProps) { isGridView = false, } = props; const { widget } = props; - const isTimeSeries = widget.metricType === TIMESERIES; const isPredefined = widget.metricType === 'predefined'; const dashboard = dashboardStore.selectedDashboard; @@ -73,13 +71,6 @@ function WidgetWrapper(props: Props & RouteComponentProps) { }), }); - const onDelete = async () => { - dashboardStore.deleteDashboardWidget( - dashboard?.dashboardId!, - widget.widgetId, - ); - }; - const onChartClick = () => { if (!isSaved || isPredefined) return; diff --git a/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapperNew.tsx b/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapperNew.tsx index 1b8bf5cb9..a6569da29 100644 --- a/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapperNew.tsx +++ b/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapperNew.tsx @@ -38,7 +38,7 @@ interface Props { isSaved?: boolean; } -function WidgetWrapperNew(props: Props & RouteComponentProps) { +function WidgetWrapperDashboard(props: Props & RouteComponentProps) { const { dashboardStore, metricStore } = useStore(); const { isWidget = false, @@ -178,4 +178,4 @@ function WidgetWrapperNew(props: Props & RouteComponentProps) { ); } -export default withRouter(observer(WidgetWrapperNew)); +export default withRouter(observer(WidgetWrapperDashboard)); diff --git a/frontend/app/mstore/dashboardStore.ts b/frontend/app/mstore/dashboardStore.ts index f5e3bbce7..bac09a4e2 100644 --- a/frontend/app/mstore/dashboardStore.ts +++ b/frontend/app/mstore/dashboardStore.ts @@ -14,70 +14,39 @@ interface DashboardFilter { } export default class DashboardStore { siteId: any = null; - dashboards: Dashboard[] = []; - selectedDashboard: Dashboard | null = null; - dashboardInstance: Dashboard = new Dashboard(); - selectedWidgets: Widget[] = []; - currentWidget: Widget = new Widget(); - widgetCategories: any[] = []; - widgets: Widget[] = []; - period: Record = Period({ rangeName: LAST_24_HOURS }); - drillDownFilter: Filter = new Filter(); - comparisonFilter: Filter = new Filter(); - drillDownPeriod: Record = Period({ rangeName: LAST_24_HOURS }); - selectedDensity: number = 7; - comparisonPeriods: Record = {}; - startTimestamp: number = 0; - endTimestamp: number = 0; - pendingRequests: number = 0; - filter: DashboardFilter = { showMine: false, query: '' }; - // Metrics metricsPage: number = 1; - metricsPageSize: number = 10; - metricsSearch: string = ''; - // Loading states isLoading: boolean = false; - isSaving: boolean = false; - isDeleting: boolean = false; - loadingTemplates: boolean = false; - fetchingDashboard: boolean = false; - sessionsLoading: boolean = false; - showAlertModal: boolean = false; - // Pagination page: number = 1; - pageSize: number = 10; - dashboardsSearch: string = ''; - sort: any = { by: 'desc' }; constructor() { @@ -94,6 +63,10 @@ export default class DashboardStore { ) } + resetDensity = () => { + this.createDensity(this.period.getDuration()); + } + createDensity = (duration: number) => { const densityOpts = calculateGranularities(duration); const defaultOption = densityOpts[densityOpts.length - 2]; @@ -212,6 +185,7 @@ export default class DashboardStore { this.currentWidget.update(widget); } + listFetched = false; fetchList(): Promise { this.isLoading = true; @@ -226,6 +200,7 @@ export default class DashboardStore { }) .finally(() => { runInAction(() => { + this.listFetched = true; this.isLoading = false; }); }); @@ -388,7 +363,24 @@ export default class DashboardStore { new Dashboard(); }; - getDashboardById = (dashboardId: string) => { + getDashboardById = async (dashboardId: string) => { + if (!this.listFetched) { + const maxWait = (5*1000)/250; + let count = 0; + await new Promise((resolve) => { + const interval = setInterval(() => { + if (this.listFetched) { + clearInterval(interval); + resolve(true); + } + if (count >= maxWait) { + clearInterval(interval); + resolve(false); + } + count++; + }, 250); + }) + } const dashboard = this.dashboards.find((d) => d.dashboardId == dashboardId); if (dashboard) { @@ -546,6 +538,7 @@ export default class DashboardStore { params, isSaved ); + console.log('db store', params) const res = metric.setData(data, period, isComparison, data.density) resolve(res); } catch (error) { From 1098f877e67e52497db46da8ec24c5e2fcab0c6a Mon Sep 17 00:00:00 2001 From: nick-delirium Date: Mon, 19 May 2025 13:48:57 +0200 Subject: [PATCH 08/22] ui: rm console log --- frontend/app/mstore/dashboardStore.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/app/mstore/dashboardStore.ts b/frontend/app/mstore/dashboardStore.ts index bac09a4e2..a36393f29 100644 --- a/frontend/app/mstore/dashboardStore.ts +++ b/frontend/app/mstore/dashboardStore.ts @@ -538,7 +538,6 @@ export default class DashboardStore { params, isSaved ); - console.log('db store', params) const res = metric.setData(data, period, isComparison, data.density) resolve(res); } catch (error) { From 3d02e7bbe3fb93f577a340a91ddc62fa11fdc69a Mon Sep 17 00:00:00 2001 From: nick-delirium Date: Mon, 19 May 2025 13:56:46 +0200 Subject: [PATCH 09/22] ui: rename file --- .../Dashboard/components/DashboardMetricSelection/index.ts | 1 - .../components/DashboardWidgetGrid/AddPredefinedMetric.tsx | 2 +- .../WidgetCategoryItem.tsx} | 0 .../components/Dashboard/components/WidgetCategoryItem/index.ts | 1 + 4 files changed, 2 insertions(+), 2 deletions(-) delete mode 100644 frontend/app/components/Dashboard/components/DashboardMetricSelection/index.ts rename frontend/app/components/Dashboard/components/{DashboardMetricSelection/DashboardMetricSelection.tsx => WidgetCategoryItem/WidgetCategoryItem.tsx} (100%) create mode 100644 frontend/app/components/Dashboard/components/WidgetCategoryItem/index.ts diff --git a/frontend/app/components/Dashboard/components/DashboardMetricSelection/index.ts b/frontend/app/components/Dashboard/components/DashboardMetricSelection/index.ts deleted file mode 100644 index 443a7a919..000000000 --- a/frontend/app/components/Dashboard/components/DashboardMetricSelection/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './DashboardMetricSelection'; diff --git a/frontend/app/components/Dashboard/components/DashboardWidgetGrid/AddPredefinedMetric.tsx b/frontend/app/components/Dashboard/components/DashboardWidgetGrid/AddPredefinedMetric.tsx index ffeb45183..4f34d4384 100644 --- a/frontend/app/components/Dashboard/components/DashboardWidgetGrid/AddPredefinedMetric.tsx +++ b/frontend/app/components/Dashboard/components/DashboardWidgetGrid/AddPredefinedMetric.tsx @@ -7,7 +7,7 @@ import { useStore } from 'App/mstore'; import { useModal } from 'App/components/Modal'; import { dashboardMetricCreate, withSiteId } from 'App/routes'; import { withRouter, RouteComponentProps } from 'react-router-dom'; -import { WidgetCategoryItem } from 'App/components/Dashboard/components/DashboardMetricSelection/DashboardMetricSelection'; +import { WidgetCategoryItem } from 'App/components/Dashboard/components/WidgetCategoryItem'; import { useTranslation } from 'react-i18next'; interface IProps extends RouteComponentProps { diff --git a/frontend/app/components/Dashboard/components/DashboardMetricSelection/DashboardMetricSelection.tsx b/frontend/app/components/Dashboard/components/WidgetCategoryItem/WidgetCategoryItem.tsx similarity index 100% rename from frontend/app/components/Dashboard/components/DashboardMetricSelection/DashboardMetricSelection.tsx rename to frontend/app/components/Dashboard/components/WidgetCategoryItem/WidgetCategoryItem.tsx diff --git a/frontend/app/components/Dashboard/components/WidgetCategoryItem/index.ts b/frontend/app/components/Dashboard/components/WidgetCategoryItem/index.ts new file mode 100644 index 000000000..3bad83500 --- /dev/null +++ b/frontend/app/components/Dashboard/components/WidgetCategoryItem/index.ts @@ -0,0 +1 @@ +export { WidgetCategoryItem } from './WidgetCategoryItem'; From 5de6d5de98ba6a5bc4d5ac3c9a9878f6f73e38a0 Mon Sep 17 00:00:00 2001 From: nick-delirium Date: Mon, 19 May 2025 15:33:57 +0200 Subject: [PATCH 10/22] ui: visualization prep for kai --- frontend/app/components/Kai/KaiService.ts | 4 ++- frontend/app/components/Kai/KaiStore.ts | 30 +++++++++++++++++--- frontend/app/components/Kai/SocketManager.ts | 21 ++++++-------- 3 files changed, 38 insertions(+), 17 deletions(-) diff --git a/frontend/app/components/Kai/KaiService.ts b/frontend/app/components/Kai/KaiService.ts index 045553b4e..1ca481e2d 100644 --- a/frontend/app/components/Kai/KaiService.ts +++ b/frontend/app/components/Kai/KaiService.ts @@ -31,8 +31,10 @@ export default class KaiService extends AiService { role: string; content: string; message_id: any; - duration?: number; + duration: number; feedback: boolean | null; + supports_visualization: boolean; + chart: string; }[] > => { const r = await this.client.get(`/kai/${projectId}/chats/${threadId}`); diff --git a/frontend/app/components/Kai/KaiStore.ts b/frontend/app/components/Kai/KaiStore.ts index 6c84d5a62..38e622ff1 100644 --- a/frontend/app/components/Kai/KaiStore.ts +++ b/frontend/app/components/Kai/KaiStore.ts @@ -1,12 +1,25 @@ import { makeAutoObservable, runInAction } from 'mobx'; -import { BotChunk, ChatManager, Message } from './SocketManager'; +import { BotChunk, ChatManager } from './SocketManager'; import { kaiService as aiService, kaiService } from 'App/services'; import { toast } from 'react-toastify'; +export interface Message { + text: string; + isUser: boolean; + messageId: string; + chart: string; + supports_visualization: boolean; + feedback: boolean | null; + duration: number; +} +export interface SentMessage extends Omit { + replace: boolean; +} + class KaiStore { chatManager: ChatManager | null = null; processingStage: BotChunk | null = null; - messages: Message[] = []; + messages: Array = []; queryText = ''; loadingChat = false; replacing = false; @@ -100,6 +113,8 @@ class KaiStore { messageId: m.message_id, duration: m.duration, feedback: m.feedback, + chart: m.chart, + supports_visualization: m.supports_visualization, }; }), ); @@ -125,18 +140,21 @@ class KaiStore { this.chatManager = new ChatManager({ ...settings, token }); this.chatManager.setOnMsgHook({ msgCallback: (msg) => { - if ('state' in msg) { + if (msg.type === 'state') { if (msg.state === 'running') { this.setProcessingStage({ content: 'Processing your request...', stage: 'chart', messageId: Date.now().toPrecision(), duration: msg.start_time ? Date.now() - msg.start_time : 0, + type: 'chunk', + supports_visualization: false, }); } else { this.setProcessingStage(null); } - } else { + } + if (msg.type === 'chunk') { if (msg.stage === 'start') { this.setProcessingStage({ ...msg, @@ -153,6 +171,8 @@ class KaiStore { messageId: msg.messageId, duration: msg.duration, feedback: null, + chart: '', + supports_visualization: msg.supports_visualization, }; this.addMessage(msgObj); this.setProcessingStage(null); @@ -197,6 +217,8 @@ class KaiStore { messageId: Date.now().toString(), feedback: null, duration: 0, + supports_visualization: false, + chart: '', }); }; diff --git a/frontend/app/components/Kai/SocketManager.ts b/frontend/app/components/Kai/SocketManager.ts index e34ccc155..7ba184a33 100644 --- a/frontend/app/components/Kai/SocketManager.ts +++ b/frontend/app/components/Kai/SocketManager.ts @@ -74,12 +74,12 @@ export class ChatManager { titleCallback, }: { msgCallback: ( - msg: BotChunk | { state: string; type: 'state'; start_time?: number }, + msg: StateEvent | BotChunk, ) => void; titleCallback: (title: string) => void; }) => { this.socket.on('chunk', (msg: BotChunk) => { - msgCallback(msg); + msgCallback({ ...msg, type: 'chunk' }); }); this.socket.on('title', (msg: { content: string }) => { titleCallback(msg.content); @@ -105,16 +105,13 @@ export interface BotChunk { stage: 'start' | 'chart' | 'final' | 'title'; content: string; messageId: string; - duration?: number; -} -export interface Message { - text: string; - isUser: boolean; - messageId: string; - duration?: number; - feedback: boolean | null; + duration: number; + supports_visualization: boolean; + type: 'chunk' } -export interface SentMessage extends Message { - replace: boolean; +interface StateEvent { + state: string; + start_time?: number; + type: 'state'; } From 3597523a04e490b577da4db205f9ed3a884caf27 Mon Sep 17 00:00:00 2001 From: nick-delirium Date: Tue, 20 May 2025 11:09:56 +0200 Subject: [PATCH 11/22] ui: fixes for kai msg editing --- frontend/app/components/Kai/components/ChatLog.tsx | 2 +- frontend/app/components/Kai/components/ChatMsg.tsx | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/app/components/Kai/components/ChatLog.tsx b/frontend/app/components/Kai/components/ChatLog.tsx index 5609642b5..9a8bd4f90 100644 --- a/frontend/app/components/Kai/components/ChatLog.tsx +++ b/frontend/app/components/Kai/components/ChatLog.tsx @@ -67,10 +67,10 @@ function ChatLog({ isUser={msg.isUser} userName={userLetter} messageId={msg.messageId} - isLast={index === lastHumanMsgInd} duration={msg.duration} feedback={msg.feedback} siteId={projectId} + canEdit={processingStage === null && msg.isUser && index === lastHumanMsgInd} /> ))} {processingStage ? ( diff --git a/frontend/app/components/Kai/components/ChatMsg.tsx b/frontend/app/components/Kai/components/ChatMsg.tsx index 17ab1a110..8b3224f3f 100644 --- a/frontend/app/components/Kai/components/ChatMsg.tsx +++ b/frontend/app/components/Kai/components/ChatMsg.tsx @@ -21,19 +21,19 @@ export function ChatMsg({ isUser, userName, messageId, - isLast, duration, feedback, siteId, + canEdit, }: { text: string; isUser: boolean; messageId: string; userName?: string; - isLast?: boolean; duration?: number; feedback: boolean | null; siteId: string; + canEdit?: boolean; }) { const [isProcessing, setIsProcessing] = React.useState(false); const bodyRef = React.useRef(null); @@ -103,7 +103,7 @@ export function ChatMsg({ {text} {isUser ? ( - isLast ? ( + canEdit ? (
Date: Mon, 19 May 2025 15:17:54 +0200 Subject: [PATCH 12/22] refactor(chalice): changed default scope state --- api/chalicelib/core/signup.py | 2 +- ee/api/chalicelib/core/signup.py | 2 +- ee/scripts/schema/db/init_dbs/postgresql/1.23.0/1.23.0.sql | 2 ++ ee/scripts/schema/db/init_dbs/postgresql/init_schema.sql | 2 +- scripts/schema/db/init_dbs/postgresql/1.23.0/1.23.0.sql | 2 ++ scripts/schema/db/init_dbs/postgresql/init_schema.sql | 2 +- 6 files changed, 8 insertions(+), 4 deletions(-) diff --git a/api/chalicelib/core/signup.py b/api/chalicelib/core/signup.py index 2c8c850ef..3819784ad 100644 --- a/api/chalicelib/core/signup.py +++ b/api/chalicelib/core/signup.py @@ -87,7 +87,7 @@ async def create_tenant(data: schemas.UserSignupSchema): "spotRefreshToken": r.pop("spotRefreshToken"), "spotRefreshTokenMaxAge": r.pop("spotRefreshTokenMaxAge"), 'data': { - "scopeState": 0, + "scopeState": 2, "user": r } } diff --git a/ee/api/chalicelib/core/signup.py b/ee/api/chalicelib/core/signup.py index a2ee66d04..d7a031957 100644 --- a/ee/api/chalicelib/core/signup.py +++ b/ee/api/chalicelib/core/signup.py @@ -98,7 +98,7 @@ async def create_tenant(data: schemas.UserSignupSchema): "spotRefreshTokenMaxAge": r.pop("spotRefreshTokenMaxAge"), "tenantId": t["tenant_id"], 'data': { - "scopeState": 0, + "scopeState": 2, "user": r } } diff --git a/ee/scripts/schema/db/init_dbs/postgresql/1.23.0/1.23.0.sql b/ee/scripts/schema/db/init_dbs/postgresql/1.23.0/1.23.0.sql index f3c024fb5..718a6e693 100644 --- a/ee/scripts/schema/db/init_dbs/postgresql/1.23.0/1.23.0.sql +++ b/ee/scripts/schema/db/init_dbs/postgresql/1.23.0/1.23.0.sql @@ -20,6 +20,8 @@ $fn_def$, :'next_version') -- DROP SCHEMA IF EXISTS or_cache CASCADE; +ALTER TABLE public.tenants + ALTER COLUMN scope_state SET DEFAULT 2; COMMIT; diff --git a/ee/scripts/schema/db/init_dbs/postgresql/init_schema.sql b/ee/scripts/schema/db/init_dbs/postgresql/init_schema.sql index bc95e97d4..4701bc53f 100644 --- a/ee/scripts/schema/db/init_dbs/postgresql/init_schema.sql +++ b/ee/scripts/schema/db/init_dbs/postgresql/init_schema.sql @@ -104,7 +104,7 @@ CREATE TABLE public.tenants t_users integer NOT NULL DEFAULT 1, t_integrations integer NOT NULL DEFAULT 0, last_telemetry bigint NOT NULL DEFAULT CAST(EXTRACT(epoch FROM date_trunc('day', now())) * 1000 AS BIGINT), - scope_state smallint NOT NULL DEFAULT 0 + scope_state smallint NOT NULL DEFAULT 2 ); diff --git a/scripts/schema/db/init_dbs/postgresql/1.23.0/1.23.0.sql b/scripts/schema/db/init_dbs/postgresql/1.23.0/1.23.0.sql index 79076b23e..8003f6753 100644 --- a/scripts/schema/db/init_dbs/postgresql/1.23.0/1.23.0.sql +++ b/scripts/schema/db/init_dbs/postgresql/1.23.0/1.23.0.sql @@ -20,6 +20,8 @@ $fn_def$, :'next_version') -- DROP SCHEMA IF EXISTS or_cache CASCADE; +ALTER TABLE public.tenants + ALTER COLUMN scope_state SET DEFAULT 2; COMMIT; diff --git a/scripts/schema/db/init_dbs/postgresql/init_schema.sql b/scripts/schema/db/init_dbs/postgresql/init_schema.sql index 9db52343b..7c6eb396e 100644 --- a/scripts/schema/db/init_dbs/postgresql/init_schema.sql +++ b/scripts/schema/db/init_dbs/postgresql/init_schema.sql @@ -103,7 +103,7 @@ CREATE TABLE public.tenants t_users integer NOT NULL DEFAULT 1, t_integrations integer NOT NULL DEFAULT 0, last_telemetry bigint NOT NULL DEFAULT CAST(EXTRACT(epoch FROM date_trunc('day', now())) * 1000 AS BIGINT), - scope_state smallint NOT NULL DEFAULT 0, + scope_state smallint NOT NULL DEFAULT 2, CONSTRAINT onerow_uni CHECK (tenant_id = 1) ); From c84aa417e1852df47200f500011fd8868ce0e43e Mon Sep 17 00:00:00 2001 From: Taha Yassine Kraiem Date: Mon, 19 May 2025 15:37:02 +0200 Subject: [PATCH 13/22] refactor(chalice): changed incident-events response --- api/chalicelib/core/events/events_ch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/chalicelib/core/events/events_ch.py b/api/chalicelib/core/events/events_ch.py index be90e230d..f1f28a9e7 100644 --- a/api/chalicelib/core/events/events_ch.py +++ b/api/chalicelib/core/events/events_ch.py @@ -83,7 +83,6 @@ def get_incidents_by_session_id(session_id, project_id): query = cur.format(query=""" \ SELECT created_at, `$properties`, - `properties`, `$event_name` AS type FROM product_analytics.events WHERE session_id = %(session_id)s @@ -92,6 +91,7 @@ def get_incidents_by_session_id(session_id, project_id): ORDER BY created_at;""", parameters={"project_id": project_id, "session_id": session_id}) rows = cur.execute(query) + rows = __explode_properties(rows) rows = helper.list_to_camel_case(rows) rows = sorted(rows, key=lambda k: k["createdAt"]) return rows From 2d58cf2da4ee9f8e3e85b112c696303b4cb6f993 Mon Sep 17 00:00:00 2001 From: Taha Yassine Kraiem Date: Tue, 20 May 2025 11:58:16 +0200 Subject: [PATCH 14/22] feat(chalice): filter sessions by incidents --- api/chalicelib/core/sessions/sessions_ch.py | 63 ++++++++++++++++++- .../core/sessions/sessions_legacy_mobil.py | 2 +- api/chalicelib/core/sessions/sessions_pg.py | 2 +- api/chalicelib/utils/exp_ch_helper.py | 6 +- api/schemas/schemas.py | 4 +- 5 files changed, 70 insertions(+), 7 deletions(-) diff --git a/api/chalicelib/core/sessions/sessions_ch.py b/api/chalicelib/core/sessions/sessions_ch.py index 3801609dc..7851ed1c8 100644 --- a/api/chalicelib/core/sessions/sessions_ch.py +++ b/api/chalicelib/core/sessions/sessions_ch.py @@ -379,6 +379,34 @@ def search_query_parts_ch(data: schemas.SessionsSearchPayloadSchema, error_statu events_conditions_where = ["main.project_id = %(projectId)s", "main.created_at >= toDateTime(%(startDate)s/1000)", "main.created_at <= toDateTime(%(endDate)s/1000)"] + any_incident = False + for i, e in enumerate(data.events): + if e.type == schemas.EventType.INCIDENT and e.operator == schemas.SearchEventOperator.IS_ANY: + any_incident = True + data.events.pop(i) + # don't stop here because we could have multiple filters looking for any incident + + if any_incident: + any_incident = False + for f in data.filters: + if f.type == schemas.FilterType.ISSUE: + any_incident = True + if f.value.index(schemas.IssueType.INCIDENT) < 0: + f.value.append(schemas.IssueType.INCIDENT) + if f.operator == schemas.SearchEventOperator.IS_ANY: + f.operator = schemas.SearchEventOperator.IS + break + + if not any_incident: + data.filters.append(schemas.SessionSearchFilterSchema(**{ + "type": "issue", + "isEvent": False, + "value": [ + "incident" + ], + "operator": "is" + })) + if len(data.filters) > 0: meta_keys = None # to reduce include a sub-query of sessions inside events query, in order to reduce the selected data @@ -522,7 +550,7 @@ def search_query_parts_ch(data: schemas.SessionsSearchPayloadSchema, error_statu ss_constraints.append( sh.multi_conditions(f"ms.base_referrer {op} toString(%({f_k})s)", f.value, is_not=is_not, value_key=f_k)) - elif filter_type == schemas.EventType.METADATA: + elif filter_type == schemas.FilterType.METADATA: # get metadata list only if you need it if meta_keys is None: meta_keys = metadata.get(project_id=project_id) @@ -1257,6 +1285,39 @@ def search_query_parts_ch(data: schemas.SessionsSearchPayloadSchema, error_statu _column = "label" event_where.append(f"main.`$event_name`=%({e_k})s AND main.session_id>0") events_conditions.append({"type": event_where[-1], "condition": ""}) + elif event_type == schemas.EventType.INCIDENT: + event_from = event_from % f"{MAIN_EVENTS_TABLE} AS main " + _column = "label" + event_where.append( + f"main.`$event_name`='{exp_ch_helper.get_event_type(event_type, platform=platform)}'") + events_conditions.append({"type": event_where[-1]}) + + if is_not: + # event_where.append(json_condition( + # "sub", "$properties", _column, op, event.value, e_k + # )) + event_where.append( + sh.multi_conditions( + get_sub_condition(col_name=f"sub.`$properties`.{_column}", + val_name=e_k, operator=event.operator), + event.value, value_key=e_k) + ) + events_conditions_not.append( + { + "type": f"sub.`$event_name`='{exp_ch_helper.get_event_type(event_type, platform=platform)}'" + } + ) + events_conditions_not[-1]["condition"] = event_where[-1] + else: + + event_where.append( + sh.multi_conditions( + get_sub_condition(col_name=f"main.`$properties`.{_column}", + val_name=e_k, operator=event.operator), + event.value, value_key=e_k) + ) + events_conditions[-1]["condition"] = event_where[-1] + else: continue diff --git a/api/chalicelib/core/sessions/sessions_legacy_mobil.py b/api/chalicelib/core/sessions/sessions_legacy_mobil.py index e9b0cffe1..885d08b5f 100644 --- a/api/chalicelib/core/sessions/sessions_legacy_mobil.py +++ b/api/chalicelib/core/sessions/sessions_legacy_mobil.py @@ -411,7 +411,7 @@ def search_query_parts_ch(data: schemas.SessionsSearchPayloadSchema, error_statu ss_constraints.append( _multiple_conditions(f"ms.base_referrer {op} toString(%({f_k})s)", f.value, is_not=is_not, value_key=f_k)) - elif filter_type == schemas.EventType.METADATA: + elif filter_type == schemas.FilterType.METADATA: # get metadata list only if you need it if meta_keys is None: meta_keys = metadata.get(project_id=project_id) diff --git a/api/chalicelib/core/sessions/sessions_pg.py b/api/chalicelib/core/sessions/sessions_pg.py index 2c404d82e..fe42f4ebd 100644 --- a/api/chalicelib/core/sessions/sessions_pg.py +++ b/api/chalicelib/core/sessions/sessions_pg.py @@ -440,7 +440,7 @@ def search_query_parts(data: schemas.SessionsSearchPayloadSchema, error_status, extra_constraints.append( sh.multi_conditions(f"s.base_referrer {op} %({f_k})s", f.value, is_not=is_not, value_key=f_k)) - elif filter_type == schemas.EventType.METADATA: + elif filter_type == schemas.FilterType.METADATA: # get metadata list only if you need it if meta_keys is None: meta_keys = metadata.get(project_id=project_id) diff --git a/api/chalicelib/utils/exp_ch_helper.py b/api/chalicelib/utils/exp_ch_helper.py index aaf41afb2..a0d02a524 100644 --- a/api/chalicelib/utils/exp_ch_helper.py +++ b/api/chalicelib/utils/exp_ch_helper.py @@ -56,7 +56,8 @@ def get_event_type(event_type: Union[schemas.EventType, schemas.PerformanceEvent schemas.EventType.ERROR: "ERROR", schemas.PerformanceEventType.LOCATION_AVG_CPU_LOAD: 'PERFORMANCE', schemas.PerformanceEventType.LOCATION_AVG_MEMORY_USAGE: 'PERFORMANCE', - schemas.FetchFilterType.FETCH_URL: 'REQUEST' + schemas.FetchFilterType.FETCH_URL: 'REQUEST', + schemas.EventType.INCIDENT: "INCIDENT", } defs_mobile = { schemas.EventType.CLICK_MOBILE: "TAP", @@ -65,7 +66,8 @@ def get_event_type(event_type: Union[schemas.EventType, schemas.PerformanceEvent schemas.EventType.REQUEST_MOBILE: "REQUEST", schemas.EventType.ERROR_MOBILE: "CRASH", schemas.EventType.VIEW_MOBILE: "VIEW", - schemas.EventType.SWIPE_MOBILE: "SWIPE" + schemas.EventType.SWIPE_MOBILE: "SWIPE", + schemas.EventType.INCIDENT: "INCIDENT" } if platform != "web" and event_type in defs_mobile: return defs_mobile.get(event_type) diff --git a/api/schemas/schemas.py b/api/schemas/schemas.py index 7ab8f5ad4..6c38868a4 100644 --- a/api/schemas/schemas.py +++ b/api/schemas/schemas.py @@ -507,8 +507,8 @@ class IssueType(str, Enum): CUSTOM = 'custom' JS_EXCEPTION = 'js_exception' MOUSE_THRASHING = 'mouse_thrashing' - # IOS - TAP_RAGE = 'tap_rage' + TAP_RAGE = 'tap_rage' # IOS + INCIDENT = 'incident' class MetricFormatType(str, Enum): From 3431c97712f7c76c21bdfff98fcf0d5c163d8d06 Mon Sep 17 00:00:00 2001 From: nick-delirium Date: Tue, 20 May 2025 12:11:55 +0200 Subject: [PATCH 15/22] ui: dark mode fixes --- .../CallWithErrors/MethodType.js | 2 +- .../components/AddCardSelectionModal.tsx | 4 +- .../components/Alerts/AlertListItem.tsx | 2 +- .../components/CardIssues/CardIssueItem.tsx | 2 +- .../NewDashModal/CardsLibrary.tsx | 2 +- .../FunnelIssuesSelectedFilters.tsx | 2 +- .../Player/ReplayPlayer/PlayerInst.tsx | 2 + .../BackendLogs/StatusMessages.tsx | 1 + .../Session_/Highlight/HighlightPanel.tsx | 2 +- .../app/components/Session_/Subheader.tsx | 2 +- .../SpotPlayer/components/AccessModal.tsx | 4 +- .../components/SpotVideoContainer.tsx | 2 +- .../UsabilityTesting/TestOverview.tsx | 2 +- .../FetchBasicDetails/FetchBasicDetails.tsx | 14 +- .../components/FetchTabs/FetchTimings.tsx | 6 +- frontend/app/layout/SupportModal.tsx | 6 +- frontend/app/player-ui/PlayButton.tsx | 2 +- frontend/app/styles/colors-autogen.css | 24 + frontend/app/styles/general.css | 12 +- frontend/app/theme/colors.js | 11 +- frontend/tests/mocks/sessionResponse.js | 6 +- frontend/yarn.lock | 537 ++++-------------- spot/entrypoints/audio/index.html | 10 +- spot/entrypoints/content/SavingControls.tsx | 2 +- spot/entrypoints/popup/Settings.tsx | 10 +- .../popup/components/AudioPicker.tsx | 2 +- spot/entrypoints/popup/components/Header.tsx | 6 +- 27 files changed, 212 insertions(+), 465 deletions(-) diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallWithErrors/MethodType.js b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallWithErrors/MethodType.js index e9da55110..f661ce4b8 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallWithErrors/MethodType.js +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallWithErrors/MethodType.js @@ -3,7 +3,7 @@ import { Tag } from 'antd'; function MethodType({ data }) { return ( - + {data.method} ); diff --git a/frontend/app/components/Dashboard/components/AddCardSelectionModal.tsx b/frontend/app/components/Dashboard/components/AddCardSelectionModal.tsx index 63b2521b2..b7829ee37 100644 --- a/frontend/app/components/Dashboard/components/AddCardSelectionModal.tsx +++ b/frontend/app/components/Dashboard/components/AddCardSelectionModal.tsx @@ -47,7 +47,7 @@ function AddCardSelectionModal(props: Props) {
onClick(true)} > @@ -57,7 +57,7 @@ function AddCardSelectionModal(props: Props) {
onClick(false)} > diff --git a/frontend/app/components/Dashboard/components/Alerts/AlertListItem.tsx b/frontend/app/components/Dashboard/components/Alerts/AlertListItem.tsx index f86870c31..a815c095f 100644 --- a/frontend/app/components/Dashboard/components/Alerts/AlertListItem.tsx +++ b/frontend/app/components/Dashboard/components/Alerts/AlertListItem.tsx @@ -123,7 +123,7 @@ function AlertListItem(props: Props) {
{alert.detectionMethod} diff --git a/frontend/app/components/Dashboard/components/CardIssues/CardIssueItem.tsx b/frontend/app/components/Dashboard/components/CardIssues/CardIssueItem.tsx index 074ebf35c..1c1129daa 100644 --- a/frontend/app/components/Dashboard/components/CardIssues/CardIssueItem.tsx +++ b/frontend/app/components/Dashboard/components/CardIssues/CardIssueItem.tsx @@ -15,7 +15,7 @@ function CardIssueItem(props: Props) { title={issue.name} description={
{issue.source}
} avatar={} - className="cursor-pointer hover:bg-indigo-50" + className="cursor-pointer hover:bg-indigo-lightest" />
{issue.sessionCount}
diff --git a/frontend/app/components/Dashboard/components/DashboardList/NewDashModal/CardsLibrary.tsx b/frontend/app/components/Dashboard/components/DashboardList/NewDashModal/CardsLibrary.tsx index f4766acc2..e3854de28 100644 --- a/frontend/app/components/Dashboard/components/DashboardList/NewDashModal/CardsLibrary.tsx +++ b/frontend/app/components/Dashboard/components/DashboardList/NewDashModal/CardsLibrary.tsx @@ -60,7 +60,7 @@ function CardsLibrary(props: Props) { onClick={(e) => onItemClick(e, metric.metricId)} /> removeSelectedValue(option.value)} - className="select-none rounded-lg text-base gap-1 bg-indigo-50 flex items-center" + className="select-none rounded-lg text-base gap-1 bg-indigo-lightest flex items-center" > {option.label}
diff --git a/frontend/app/components/Session/Player/ReplayPlayer/PlayerInst.tsx b/frontend/app/components/Session/Player/ReplayPlayer/PlayerInst.tsx index 5112752a7..3d9a8576e 100644 --- a/frontend/app/components/Session/Player/ReplayPlayer/PlayerInst.tsx +++ b/frontend/app/components/Session/Player/ReplayPlayer/PlayerInst.tsx @@ -199,6 +199,8 @@ function BottomBlock({ panelHeight, block }: { panelHeight: number; block: numbe return ; case LONG_TASK: return ; + case OVERVIEW: + return ; default: return null; } diff --git a/frontend/app/components/Session/Player/SharedComponents/BackendLogs/StatusMessages.tsx b/frontend/app/components/Session/Player/SharedComponents/BackendLogs/StatusMessages.tsx index 73ec560b0..6d28638c5 100644 --- a/frontend/app/components/Session/Player/SharedComponents/BackendLogs/StatusMessages.tsx +++ b/frontend/app/components/Session/Player/SharedComponents/BackendLogs/StatusMessages.tsx @@ -13,6 +13,7 @@ export function LoadingFetch({ provider }: { provider: string }) {
{t('Fetching logs from')} +   {provider} ...
diff --git a/frontend/app/components/Session_/Highlight/HighlightPanel.tsx b/frontend/app/components/Session_/Highlight/HighlightPanel.tsx index 8e524a689..b2277ffc5 100644 --- a/frontend/app/components/Session_/Highlight/HighlightPanel.tsx +++ b/frontend/app/components/Session_/Highlight/HighlightPanel.tsx @@ -206,7 +206,7 @@ function HighlightPanel({ onClose }: { onClose: () => void }) { addTag(tag)} key={tag} - className="cursor-pointer rounded-lg hover:bg-indigo-50 mr-0" + className="cursor-pointer rounded-lg hover:bg-indigo-lightest mr-0" color={tagProps[tag]} bordered={false} > diff --git a/frontend/app/components/Session_/Subheader.tsx b/frontend/app/components/Session_/Subheader.tsx index 3cf6cfe28..313b346ca 100644 --- a/frontend/app/components/Session_/Subheader.tsx +++ b/frontend/app/components/Session_/Subheader.tsx @@ -142,7 +142,7 @@ function SubHeader(props) { currentLocation={currentLocation} version={currentSession?.trackerVersion ?? ''} containerStyle={{ position: 'relative', left: 0, top: 0, transform: 'none', zIndex: 10 }} - trackerWarnStyle={{ backgroundColor: 'var(--color-yellow)' }} + trackerWarnStyle={{ backgroundColor: 'var(--color-yellow)', color: 'black' }} virtualElsFailed={showVModeBadge} onVMode={onVMode} /> diff --git a/frontend/app/components/Spots/SpotPlayer/components/AccessModal.tsx b/frontend/app/components/Spots/SpotPlayer/components/AccessModal.tsx index a5c83ee34..695719529 100644 --- a/frontend/app/components/Spots/SpotPlayer/components/AccessModal.tsx +++ b/frontend/app/components/Spots/SpotPlayer/components/AccessModal.tsx @@ -121,7 +121,7 @@ function AccessModal() {
{t('Link for internal team members')}
-
+
{spotLink}
@@ -155,7 +155,7 @@ function AccessModal() {
{t('Anyone with the following link can access this Spot')}
-
+
{spotLink}
diff --git a/frontend/app/components/Spots/SpotPlayer/components/SpotVideoContainer.tsx b/frontend/app/components/Spots/SpotPlayer/components/SpotVideoContainer.tsx index ab1a3c3be..d534d9a34 100644 --- a/frontend/app/components/Spots/SpotPlayer/components/SpotVideoContainer.tsx +++ b/frontend/app/components/Spots/SpotPlayer/components/SpotVideoContainer.tsx @@ -201,7 +201,7 @@ function SpotVideoContainer({ > {processingState === ProcessingState.Processing ? ( { {t('Task Summary')} {uxtestingStore.taskStats.length ? ( -
+
{t('Average completion time of all tasks:')} diff --git a/frontend/app/components/shared/FetchDetailsModal/components/FetchBasicDetails/FetchBasicDetails.tsx b/frontend/app/components/shared/FetchDetailsModal/components/FetchBasicDetails/FetchBasicDetails.tsx index 2b138537d..8fa99e940 100644 --- a/frontend/app/components/shared/FetchDetailsModal/components/FetchBasicDetails/FetchBasicDetails.tsx +++ b/frontend/app/components/shared/FetchDetailsModal/components/FetchBasicDetails/FetchBasicDetails.tsx @@ -23,7 +23,7 @@ function FetchBasicDetails({ resource, timestamp }: Props) {
{t('Name')}
@@ -35,7 +35,7 @@ function FetchBasicDetails({ resource, timestamp }: Props) {
{t('Request Method')}
{resource.method} @@ -49,7 +49,7 @@ function FetchBasicDetails({ resource, timestamp }: Props) { @@ -61,7 +61,7 @@ function FetchBasicDetails({ resource, timestamp }: Props) {
{t('Type')}
{resource.type} @@ -72,7 +72,7 @@ function FetchBasicDetails({ resource, timestamp }: Props) {
{t('Size')}
{formatBytes(resource.decodedBodySize)} @@ -84,7 +84,7 @@ function FetchBasicDetails({ resource, timestamp }: Props) {
{t('Duration')}
{_duration} {t('ms')} @@ -96,7 +96,7 @@ function FetchBasicDetails({ resource, timestamp }: Props) {
{t('Time')}
{timestamp} diff --git a/frontend/app/components/shared/FetchDetailsModal/components/FetchTabs/FetchTimings.tsx b/frontend/app/components/shared/FetchDetailsModal/components/FetchTabs/FetchTimings.tsx index 55739c215..82cb4164c 100644 --- a/frontend/app/components/shared/FetchDetailsModal/components/FetchTabs/FetchTimings.tsx +++ b/frontend/app/components/shared/FetchDetailsModal/components/FetchTabs/FetchTimings.tsx @@ -128,7 +128,7 @@ function FetchTimings({ timings }: { timings: Record }) { key={index} className="grid grid-cols-12 items-center gap-2 space-y-2" > -
+
@@ -160,11 +160,11 @@ function FetchTimings({ timings }: { timings: Record }) { ))}
-
+
Total:
-
+
{formatTime(total)}{' '} {isAdjusted ? ( diff --git a/frontend/app/layout/SupportModal.tsx b/frontend/app/layout/SupportModal.tsx index fffca5ea5..c6013e3dd 100644 --- a/frontend/app/layout/SupportModal.tsx +++ b/frontend/app/layout/SupportModal.tsx @@ -29,7 +29,7 @@ function SupportModal(props: Props) { className="!bg-stone-50" >
-
+
@@ -61,7 +61,7 @@ function SupportModal(props: Props) {
-
+
@@ -92,7 +92,7 @@ function SupportModal(props: Props) {
-
+
diff --git a/frontend/app/player-ui/PlayButton.tsx b/frontend/app/player-ui/PlayButton.tsx index 123793f47..3eec984a8 100644 --- a/frontend/app/player-ui/PlayButton.tsx +++ b/frontend/app/player-ui/PlayButton.tsx @@ -44,7 +44,7 @@ export function PlayButton({ togglePlay, iconSize, state }: IProps) { >
diff --git a/frontend/app/styles/colors-autogen.css b/frontend/app/styles/colors-autogen.css index 148df49f3..c8523449f 100644 --- a/frontend/app/styles/colors-autogen.css +++ b/frontend/app/styles/colors-autogen.css @@ -28,6 +28,7 @@ .fill-green-dark { fill: var(--color-green-dark) } .fill-red { fill: var(--color-red) } .fill-red2 { fill: var(--color-red2) } +.fill-red-light { fill: var(--color-red-light) } .fill-red-lightest { fill: var(--color-red-lightest) } .fill-blue { fill: var(--color-blue) } .fill-blue2 { fill: var(--color-blue2) } @@ -47,12 +48,15 @@ .fill-transparent { fill: var(--color-transparent) } .fill-cyan { fill: var(--color-cyan) } .fill-amber { fill: var(--color-amber) } +.fill-amber-medium { fill: var(--color-amber-medium) } .fill-glassWhite { fill: var(--color-glassWhite) } .fill-glassMint { fill: var(--color-glassMint) } .fill-glassLavander { fill: var(--color-glassLavander) } .fill-blueLight { fill: var(--color-blueLight) } .fill-offWhite { fill: var(--color-offWhite) } .fill-disabled-text { fill: var(--color-disabled-text) } +.fill-indigo-lightest { fill: var(--color-indigo-lightest) } +.fill-indigo { fill: var(--color-indigo) } .fill-figmaColors-accent-secondary { fill: var(--color-figmaColors-accent-secondary) } .fill-figmaColors-main { fill: var(--color-figmaColors-main) } .fill-figmaColors-primary-outlined-hover-background { fill: var(--color-figmaColors-primary-outlined-hover-background) } @@ -89,6 +93,7 @@ .hover-fill-green-dark:hover svg { fill: var(--color-green-dark) } .hover-fill-red:hover svg { fill: var(--color-red) } .hover-fill-red2:hover svg { fill: var(--color-red2) } +.hover-fill-red-light:hover svg { fill: var(--color-red-light) } .hover-fill-red-lightest:hover svg { fill: var(--color-red-lightest) } .hover-fill-blue:hover svg { fill: var(--color-blue) } .hover-fill-blue2:hover svg { fill: var(--color-blue2) } @@ -108,12 +113,15 @@ .hover-fill-transparent:hover svg { fill: var(--color-transparent) } .hover-fill-cyan:hover svg { fill: var(--color-cyan) } .hover-fill-amber:hover svg { fill: var(--color-amber) } +.hover-fill-amber-medium:hover svg { fill: var(--color-amber-medium) } .hover-fill-glassWhite:hover svg { fill: var(--color-glassWhite) } .hover-fill-glassMint:hover svg { fill: var(--color-glassMint) } .hover-fill-glassLavander:hover svg { fill: var(--color-glassLavander) } .hover-fill-blueLight:hover svg { fill: var(--color-blueLight) } .hover-fill-offWhite:hover svg { fill: var(--color-offWhite) } .hover-fill-disabled-text:hover svg { fill: var(--color-disabled-text) } +.hover-fill-indigo-lightest:hover svg { fill: var(--color-indigo-lightest) } +.hover-fill-indigo:hover svg { fill: var(--color-indigo) } .hover-fill-figmaColors-accent-secondary:hover svg { fill: var(--color-figmaColors-accent-secondary) } .hover-fill-figmaColors-main:hover svg { fill: var(--color-figmaColors-main) } .hover-fill-figmaColors-primary-outlined-hover-background:hover svg { fill: var(--color-figmaColors-primary-outlined-hover-background) } @@ -152,6 +160,7 @@ .color-green-dark { color: var(--color-green-dark) } .color-red { color: var(--color-red) } .color-red2 { color: var(--color-red2) } +.color-red-light { color: var(--color-red-light) } .color-red-lightest { color: var(--color-red-lightest) } .color-blue { color: var(--color-blue) } .color-blue2 { color: var(--color-blue2) } @@ -171,12 +180,15 @@ .color-transparent { color: var(--color-transparent) } .color-cyan { color: var(--color-cyan) } .color-amber { color: var(--color-amber) } +.color-amber-medium { color: var(--color-amber-medium) } .color-glassWhite { color: var(--color-glassWhite) } .color-glassMint { color: var(--color-glassMint) } .color-glassLavander { color: var(--color-glassLavander) } .color-blueLight { color: var(--color-blueLight) } .color-offWhite { color: var(--color-offWhite) } .color-disabled-text { color: var(--color-disabled-text) } +.color-indigo-lightest { color: var(--color-indigo-lightest) } +.color-indigo { color: var(--color-indigo) } .color-figmaColors-accent-secondary { color: var(--color-figmaColors-accent-secondary) } .color-figmaColors-main { color: var(--color-figmaColors-main) } .color-figmaColors-primary-outlined-hover-background { color: var(--color-figmaColors-primary-outlined-hover-background) } @@ -215,6 +227,7 @@ .hover-green-dark:hover { color: var(--color-green-dark) } .hover-red:hover { color: var(--color-red) } .hover-red2:hover { color: var(--color-red2) } +.hover-red-light:hover { color: var(--color-red-light) } .hover-red-lightest:hover { color: var(--color-red-lightest) } .hover-blue:hover { color: var(--color-blue) } .hover-blue2:hover { color: var(--color-blue2) } @@ -234,12 +247,15 @@ .hover-transparent:hover { color: var(--color-transparent) } .hover-cyan:hover { color: var(--color-cyan) } .hover-amber:hover { color: var(--color-amber) } +.hover-amber-medium:hover { color: var(--color-amber-medium) } .hover-glassWhite:hover { color: var(--color-glassWhite) } .hover-glassMint:hover { color: var(--color-glassMint) } .hover-glassLavander:hover { color: var(--color-glassLavander) } .hover-blueLight:hover { color: var(--color-blueLight) } .hover-offWhite:hover { color: var(--color-offWhite) } .hover-disabled-text:hover { color: var(--color-disabled-text) } +.hover-indigo-lightest:hover { color: var(--color-indigo-lightest) } +.hover-indigo:hover { color: var(--color-indigo) } .hover-figmaColors-accent-secondary:hover { color: var(--color-figmaColors-accent-secondary) } .hover-figmaColors-main:hover { color: var(--color-figmaColors-main) } .hover-figmaColors-primary-outlined-hover-background:hover { color: var(--color-figmaColors-primary-outlined-hover-background) } @@ -278,6 +294,7 @@ .border-green-dark { border-color: var(--color-green-dark) } .border-red { border-color: var(--color-red) } .border-red2 { border-color: var(--color-red2) } +.border-red-light { border-color: var(--color-red-light) } .border-red-lightest { border-color: var(--color-red-lightest) } .border-blue { border-color: var(--color-blue) } .border-blue2 { border-color: var(--color-blue2) } @@ -297,12 +314,15 @@ .border-transparent { border-color: var(--color-transparent) } .border-cyan { border-color: var(--color-cyan) } .border-amber { border-color: var(--color-amber) } +.border-amber-medium { border-color: var(--color-amber-medium) } .border-glassWhite { border-color: var(--color-glassWhite) } .border-glassMint { border-color: var(--color-glassMint) } .border-glassLavander { border-color: var(--color-glassLavander) } .border-blueLight { border-color: var(--color-blueLight) } .border-offWhite { border-color: var(--color-offWhite) } .border-disabled-text { border-color: var(--color-disabled-text) } +.border-indigo-lightest { border-color: var(--color-indigo-lightest) } +.border-indigo { border-color: var(--color-indigo) } .border-figmaColors-accent-secondary { border-color: var(--color-figmaColors-accent-secondary) } .border-figmaColors-main { border-color: var(--color-figmaColors-main) } .border-figmaColors-primary-outlined-hover-background { border-color: var(--color-figmaColors-primary-outlined-hover-background) } @@ -341,6 +361,7 @@ .bg-green-dark { background-color: var(--color-green-dark) } .bg-red { background-color: var(--color-red) } .bg-red2 { background-color: var(--color-red2) } +.bg-red-light { background-color: var(--color-red-light) } .bg-red-lightest { background-color: var(--color-red-lightest) } .bg-blue { background-color: var(--color-blue) } .bg-blue2 { background-color: var(--color-blue2) } @@ -360,12 +381,15 @@ .bg-transparent { background-color: var(--color-transparent) } .bg-cyan { background-color: var(--color-cyan) } .bg-amber { background-color: var(--color-amber) } +.bg-amber-medium { background-color: var(--color-amber-medium) } .bg-glassWhite { background-color: var(--color-glassWhite) } .bg-glassMint { background-color: var(--color-glassMint) } .bg-glassLavander { background-color: var(--color-glassLavander) } .bg-blueLight { background-color: var(--color-blueLight) } .bg-offWhite { background-color: var(--color-offWhite) } .bg-disabled-text { background-color: var(--color-disabled-text) } +.bg-indigo-lightest { background-color: var(--color-indigo-lightest) } +.bg-indigo { background-color: var(--color-indigo) } .bg-figmaColors-accent-secondary { background-color: var(--color-figmaColors-accent-secondary) } .bg-figmaColors-main { background-color: var(--color-figmaColors-main) } .bg-figmaColors-primary-outlined-hover-background { background-color: var(--color-figmaColors-primary-outlined-hover-background) } diff --git a/frontend/app/styles/general.css b/frontend/app/styles/general.css index 0d1c41aa9..13c55cc59 100644 --- a/frontend/app/styles/general.css +++ b/frontend/app/styles/general.css @@ -29,23 +29,23 @@ .info.info.info.info.info { /* BAD HACK >:) */ - background-color: rgba(242, 248, 255, 0.6); + background-color: var(--color-glassMint); &:hover { - background-color: rgba(242, 248, 255, 1); + background-color: var(--color-indigo-lightest); } } .warn.warn.warn.warn { - background-color: rgba(253, 248, 240, 0.6); + background-color: var(--color-amber); &:hover { - background-color: rgba(253, 248, 240, 1); + background-color: var(--color-amber-medium); } } .error.error.error.error { - background-color: rgba(252, 242, 242, 0.6); + background-color: var(--color-red-light); &:hover { - background-color: rgba(252, 242, 242, 1); + background-color: var(--color-red-lightest); } } diff --git a/frontend/app/theme/colors.js b/frontend/app/theme/colors.js index 17d472398..30408d5d3 100644 --- a/frontend/app/theme/colors.js +++ b/frontend/app/theme/colors.js @@ -25,7 +25,8 @@ module.exports = { 'green-dark': '#2C9848', red: '#cc0000', red2: '#F5A623', - 'red-lightest': 'rgba(204, 0, 0, 0.1)', + 'red-light': 'oklch(93.6% 0.032 17.717)', + 'red-lightest': 'oklch(97.1% 0.013 17.38)', blue: '#366CD9', blue2: '#0076FF', 'active-blue': '#F6F7FF', @@ -46,12 +47,15 @@ module.exports = { transparent: 'transparent', cyan: '#EBF4F5', amber: 'oklch(98.7% 0.022 95.277)', + 'amber-medium': 'oklch(96.2% 0.059 95.617)', glassWhite: 'rgba(255, 255, 255, 0.5)', glassMint: 'rgba(248, 255, 254, 0.5)', glassLavander: 'rgba(243, 241, 255, 0.5)', blueLight: 'rgba(235, 235, 255, 1)', offWhite: 'rgba(250, 250, 255, 1)', 'disabled-text': 'rgba(0,0,0, 0.38)', + 'indigo-lightest': 'oklch(96.2% 0.018 272.314)', + 'indigo': 'oklch(58.5% 0.233 277.117)', figmaColors: { 'accent-secondary': 'rgba(62, 170, 175, 1)', @@ -79,6 +83,11 @@ module.exports = { 'background': 'oklch(20.5% 0 0)', 'surface': '#1E1E1E', amber: 'oklch(41.4% 0.112 45.904)', + 'amber-medium': 'oklch(55.5% 0.163 48.998)', + 'red-lightest': 'oklch(25.8% 0.092 26.042)', + 'indigo-lightest': 'oklch(35.9% 0.144 278.697)', + 'indigo': 'oklch(58.5% 0.233 277.117)', + 'red-light': 'oklch(39.6% 0.141 25.723)', 'gray-light-shade': 'oklch(37.1% 0 0)', 'gray-lightest': 'oklch(26.9% 0 0)', diff --git a/frontend/tests/mocks/sessionResponse.js b/frontend/tests/mocks/sessionResponse.js index d7f269362..5e8d9141b 100644 --- a/frontend/tests/mocks/sessionResponse.js +++ b/frontend/tests/mocks/sessionResponse.js @@ -1292,7 +1292,7 @@ export const session = { sessionId: 8119081922378909, messageId: 37522, timestamp: 1673887715900, - label: 'hover-main color-main cursor-pointer rounded-full hover:bg-indigo-50', + label: 'hover-main color-main cursor-pointer rounded-full hover:bg-indigo-lightest', url: 'app.openreplay.com/5095/session/8118843021704432', selector: '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.controls-module__buttons--vje3y > div.flex.items-center > div.flex.items-center > div.relative > div > div.hover-main.color-main.cursor-pointer.rounded.hover:bg-gray-light-shade', @@ -2780,7 +2780,7 @@ export const session = { sessionId: 8119081922378909, messageId: 67718, timestamp: 1673888012602, - label: 'hover-main color-main cursor-pointer rounded-full hover:bg-indigo-50', + label: 'hover-main color-main cursor-pointer rounded-full hover:bg-indigo-lightest', url: 'app.openreplay.com/5095/session/8118633985734291', selector: '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.controls-module__buttons--vje3y > div.flex.items-center > div.flex.items-center > div.relative > div > div.hover-main.color-main.cursor-pointer.rounded.hover:bg-gray-light-shade', @@ -4046,7 +4046,7 @@ export const session = { sessionId: 8119081922378909, messageId: 90402, timestamp: 1673888133249, - label: 'hover-main color-main cursor-pointer rounded-full hover:bg-indigo-50', + label: 'hover-main color-main cursor-pointer rounded-full hover:bg-indigo-lightest', url: 'app.openreplay.com/5095/session/8118556979885624', selector: '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.controls-module__buttons--vje3y > div.flex.items-center > div.flex.items-center > div.relative > div > div.hover-main.color-main.cursor-pointer.rounded.hover:bg-gray-light-shade', diff --git a/frontend/yarn.lock b/frontend/yarn.lock index f8145198c..1f555d367 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -1745,7 +1745,7 @@ __metadata: languageName: node linkType: hard -"@emnapi/core@npm:^1.4.0": +"@emnapi/core@npm:^1.4.3": version: 1.4.3 resolution: "@emnapi/core@npm:1.4.3" dependencies: @@ -1755,7 +1755,7 @@ __metadata: languageName: node linkType: hard -"@emnapi/runtime@npm:^1.4.0": +"@emnapi/runtime@npm:^1.4.3": version: 1.4.3 resolution: "@emnapi/runtime@npm:1.4.3" dependencies: @@ -2115,12 +2115,12 @@ __metadata: languageName: node linkType: hard -"@eslint/core@npm:^0.13.0": - version: 0.13.0 - resolution: "@eslint/core@npm:0.13.0" +"@eslint/core@npm:^0.14.0": + version: 0.14.0 + resolution: "@eslint/core@npm:0.14.0" dependencies: "@types/json-schema": "npm:^7.0.15" - checksum: 10c1/2b9a0aefab71f000dead614dc8c8d34f76778b19649824d252f08a6b9dc76763407cdece736d4ce2e2434521b8f82dbf30bded9b15d01cb377ddc0686fbdf5c4 + checksum: 10c1/3ed950bd65e73d3599f5333afe0e7ea38938580d78205d58a07c201159e19cf23cdcd9ae961c8d1f15334e92d0c734e4e27a414e6883d424d226f86357ff8579 languageName: node linkType: hard @@ -2141,10 +2141,10 @@ __metadata: languageName: node linkType: hard -"@eslint/js@npm:9.26.0, @eslint/js@npm:^9.26.0": - version: 9.26.0 - resolution: "@eslint/js@npm:9.26.0" - checksum: 10c1/c022348fe10ed6f008b2f0eb7f2ff84caca0715a20f37f4705294bad73493d3e8f81d6405ff1856873a42a0cd8d1519334a7f1a8e60d467fea06baa858ffdc9e +"@eslint/js@npm:9.27.0, @eslint/js@npm:^9.26.0": + version: 9.27.0 + resolution: "@eslint/js@npm:9.27.0" + checksum: 10c1/c1fad371a9925516fcbb992542c37c4829179dc9c75e0a5614ef72853c2098c858354adb16101ccfef5fdb4e88e1d5e1a614494bfd5a44089b7248ca6474ef5c languageName: node linkType: hard @@ -2155,13 +2155,13 @@ __metadata: languageName: node linkType: hard -"@eslint/plugin-kit@npm:^0.2.8": - version: 0.2.8 - resolution: "@eslint/plugin-kit@npm:0.2.8" +"@eslint/plugin-kit@npm:^0.3.1": + version: 0.3.1 + resolution: "@eslint/plugin-kit@npm:0.3.1" dependencies: - "@eslint/core": "npm:^0.13.0" + "@eslint/core": "npm:^0.14.0" levn: "npm:^0.4.1" - checksum: 10c1/59131b1e2be7a8af0abac04c72411c804b2aba45bab3c74d9334da606dd6aea43a7731c63f247f52313533fe639728ca9fed26ca376c10b55af9f212094b6cc7 + checksum: 10c1/7c6d916aa609b63a3e7fdfd8ddd8c00c4bba9cc11e7f6ce493f74b32975fe7ed11d0f5e8f2e7b496733d3cf3dccfe199ce4a8a6ee3b072caa2f3c62a1c1c5013 languageName: node linkType: hard @@ -2617,32 +2617,14 @@ __metadata: languageName: node linkType: hard -"@modelcontextprotocol/sdk@npm:^1.8.0": - version: 1.11.3 - resolution: "@modelcontextprotocol/sdk@npm:1.11.3" - dependencies: - content-type: "npm:^1.0.5" - cors: "npm:^2.8.5" - cross-spawn: "npm:^7.0.5" - eventsource: "npm:^3.0.2" - express: "npm:^5.0.1" - express-rate-limit: "npm:^7.5.0" - pkce-challenge: "npm:^5.0.0" - raw-body: "npm:^3.0.0" - zod: "npm:^3.23.8" - zod-to-json-schema: "npm:^3.24.1" - checksum: 10c1/054f51274162a462eb4d25d014eef44c18d83471a2cd562ab7ffa94746531098d800ea92bfd27c5610457b53bccf60a7c534bb0c903f73cd86bca9b925e953ee - languageName: node - linkType: hard - "@napi-rs/wasm-runtime@npm:^0.2.9": - version: 0.2.9 - resolution: "@napi-rs/wasm-runtime@npm:0.2.9" + version: 0.2.10 + resolution: "@napi-rs/wasm-runtime@npm:0.2.10" dependencies: - "@emnapi/core": "npm:^1.4.0" - "@emnapi/runtime": "npm:^1.4.0" + "@emnapi/core": "npm:^1.4.3" + "@emnapi/runtime": "npm:^1.4.3" "@tybys/wasm-util": "npm:^0.9.0" - checksum: 10c1/9f5686a099d59146619a1e9e7ca37a4856a2e9809f6e9e5babe7c3f75ecad51cc3f8aa356f5e11fce84337b2f14a749982cf9543aaffe3242636908d325e6160 + checksum: 10c1/908dfbd14a853aa0fb541de9394f6b8a014cf04bab4297963d5ced10ce9b060ec012f18ac01ed21f3919c86d91278babaeb7c2c8cffa7092c0c7185472c80853 languageName: node linkType: hard @@ -3056,61 +3038,61 @@ __metadata: languageName: node linkType: hard -"@sentry-internal/browser-utils@npm:9.19.0": - version: 9.19.0 - resolution: "@sentry-internal/browser-utils@npm:9.19.0" +"@sentry-internal/browser-utils@npm:9.20.0": + version: 9.20.0 + resolution: "@sentry-internal/browser-utils@npm:9.20.0" dependencies: - "@sentry/core": "npm:9.19.0" - checksum: 10c1/0d299747fef8b8f3b5f42d428248a4d33c7f43176ad2d71f6c87d9a6ec279bb2104597a8b9ff30e41919491deff5b9050410e9c84201f648557585e6ec06c1f3 + "@sentry/core": "npm:9.20.0" + checksum: 10c1/f0ee708dab12063b839aee9377cd18efda7c6c7e84accf07a7e80b182dc59b07dfeacf4d5e8f48d318498981d9b343edaaeca933dc84f06200794d36fe76275d languageName: node linkType: hard -"@sentry-internal/feedback@npm:9.19.0": - version: 9.19.0 - resolution: "@sentry-internal/feedback@npm:9.19.0" +"@sentry-internal/feedback@npm:9.20.0": + version: 9.20.0 + resolution: "@sentry-internal/feedback@npm:9.20.0" dependencies: - "@sentry/core": "npm:9.19.0" - checksum: 10c1/7bb55584edd3e54db946e9eb0f49cdc78716bf740287641dffba488af28f8472676d3c21e5e71e51fd97e36a77450f8f71505ebd3a3837471e1d216a88cfbde8 + "@sentry/core": "npm:9.20.0" + checksum: 10c1/052726a5dece00c8359b56ac37dcf13a2b665440185d1dee2f443563f0c551b83e64cee22524feefd40fb5e50f5c7b7ca4a29955eb99a09a2da78836ece34fd2 languageName: node linkType: hard -"@sentry-internal/replay-canvas@npm:9.19.0": - version: 9.19.0 - resolution: "@sentry-internal/replay-canvas@npm:9.19.0" +"@sentry-internal/replay-canvas@npm:9.20.0": + version: 9.20.0 + resolution: "@sentry-internal/replay-canvas@npm:9.20.0" dependencies: - "@sentry-internal/replay": "npm:9.19.0" - "@sentry/core": "npm:9.19.0" - checksum: 10c1/44e7f1caadd20f4b4fd75c6f771b56ffb1f41516d44b33ea9cefdf239119c223442f4c7c7f85d2db3ec226a35fbe740f2a6d382d7b24b08d9c46df3f0e9a360c + "@sentry-internal/replay": "npm:9.20.0" + "@sentry/core": "npm:9.20.0" + checksum: 10c1/5c47cfde47e391a88983509892c7e46abc9f56c2aaf81f8d31cf38454a6beadfa4ee5199cf535d8a86428c2560a43407fa11cb6b2d5e8bf4de9ea6a8ca7e536c languageName: node linkType: hard -"@sentry-internal/replay@npm:9.19.0": - version: 9.19.0 - resolution: "@sentry-internal/replay@npm:9.19.0" +"@sentry-internal/replay@npm:9.20.0": + version: 9.20.0 + resolution: "@sentry-internal/replay@npm:9.20.0" dependencies: - "@sentry-internal/browser-utils": "npm:9.19.0" - "@sentry/core": "npm:9.19.0" - checksum: 10c1/6607eeee91267cb9f2fa0726a53b61bde709761d69e979fa909fd60890a415fcf3482ec7c1a55ed62180792de17c6630b91c7e9a4514f9e456e588ded790c3e8 + "@sentry-internal/browser-utils": "npm:9.20.0" + "@sentry/core": "npm:9.20.0" + checksum: 10c1/a8024de775a16c0667dfc0562a7365e2bf8685832c76b5067736da0662d1f6584ee7dd0ce6032b4a63d196001ba2771a7296ce709fa53e465a1453fe89b8b931 languageName: node linkType: hard "@sentry/browser@npm:^9.18.0": - version: 9.19.0 - resolution: "@sentry/browser@npm:9.19.0" + version: 9.20.0 + resolution: "@sentry/browser@npm:9.20.0" dependencies: - "@sentry-internal/browser-utils": "npm:9.19.0" - "@sentry-internal/feedback": "npm:9.19.0" - "@sentry-internal/replay": "npm:9.19.0" - "@sentry-internal/replay-canvas": "npm:9.19.0" - "@sentry/core": "npm:9.19.0" - checksum: 10c1/f7a1dedc2ca3dd72ea3f7b2901c913abe5a45f34f3dba5d12456a1792847cc6ed97cdccfb51c3e1ed1f3f95f3100d90a3c881865be67d0059d1e616aac5a7523 + "@sentry-internal/browser-utils": "npm:9.20.0" + "@sentry-internal/feedback": "npm:9.20.0" + "@sentry-internal/replay": "npm:9.20.0" + "@sentry-internal/replay-canvas": "npm:9.20.0" + "@sentry/core": "npm:9.20.0" + checksum: 10c1/590510de27ca795210942e3499e07ebb22e6d0b0d2271608d8866ae66e29b7e46353ea5db8ac2533698cdf83ac4c11730aac0f08caddb9e8da2cb1dc5784d651 languageName: node linkType: hard -"@sentry/core@npm:9.19.0": - version: 9.19.0 - resolution: "@sentry/core@npm:9.19.0" - checksum: 10c1/c7e1e897be8543a08cfb49b9accc054fc4b0d258421ccc9a58edb29b3150370e0d8edc5350c701ff1ef34153737fffc975aed493e31872670dae16202ccae752 +"@sentry/core@npm:9.20.0": + version: 9.20.0 + resolution: "@sentry/core@npm:9.20.0" + checksum: 10c1/ba72ef9d1d08253e6508f1008a2ae164b8d315faab88d1cb4c7bde42a7bdf6e55d4088b3dd3d8d8a912e02620109d8e5a0d79dd6ccf2cab56c151d640a0aa9e5 languageName: node linkType: hard @@ -3476,25 +3458,25 @@ __metadata: linkType: hard "@types/express@npm:*": - version: 5.0.1 - resolution: "@types/express@npm:5.0.1" + version: 5.0.2 + resolution: "@types/express@npm:5.0.2" dependencies: "@types/body-parser": "npm:*" "@types/express-serve-static-core": "npm:^5.0.0" "@types/serve-static": "npm:*" - checksum: 10c1/13845103cdaca4a61f610a51c0d3aecada5a8cde28a7a47c01f3cc01bcc38ff77a539cec2badff3dcfc0a81f6a0ddfba2c3009cc257a885de4a3c7d3d6599590 + checksum: 10c1/c90906eacd7a50aaf9b3706f228da66d3655e6d18673b44e0653cb128a41e24a42627652426ff12b08ddb2c514eca3767e57093e01d8b2c279f914a47c46588a languageName: node linkType: hard "@types/express@npm:^4.17.21": - version: 4.17.21 - resolution: "@types/express@npm:4.17.21" + version: 4.17.22 + resolution: "@types/express@npm:4.17.22" dependencies: "@types/body-parser": "npm:*" "@types/express-serve-static-core": "npm:^4.17.33" "@types/qs": "npm:*" "@types/serve-static": "npm:*" - checksum: 10c1/c7e08a480043ea66c18832e84b1930dcb9803bd037d58406efafbe37d26c0bd682db61c65f576798278a1ca12fa79ec07bbf11b80f282ed58b7689cf77c1ffe6 + checksum: 10c1/89c6569c6139a8030860435ff78e5dbbd8b2ccbe496f0125ab1427afc8271b603dd6be5c04ceb0fd9b3ed549495e08750103cc8eaba023ee5466cd0a8bf1bfe5 languageName: node linkType: hard @@ -3636,11 +3618,11 @@ __metadata: linkType: hard "@types/node@npm:*, @types/node@npm:^22.7.8": - version: 22.15.18 - resolution: "@types/node@npm:22.15.18" + version: 22.15.19 + resolution: "@types/node@npm:22.15.19" dependencies: undici-types: "npm:~6.21.0" - checksum: 10c1/00433092cf1ce4cf961a6d29b693a67957a20eece2649c4e199973c013c363b3647608fe8969bc6c905af39983aefbc4c89af1ac50c6e5319be66940f6067f56 + checksum: 10c1/b57519cdfa277e62bfe38397f764239c24767740a7bf9999efb3972e626e1c5d9c305c89d8455ca20e34ce719746d6e1316de883af1b7a913fccb7ee60741e1a languageName: node linkType: hard @@ -3659,9 +3641,9 @@ __metadata: linkType: hard "@types/qs@npm:*": - version: 6.9.18 - resolution: "@types/qs@npm:6.9.18" - checksum: 10c1/f3ceb2d647f2fbba7b28dff0606dea73557346a09345a1530aa05cc09444e43ac8c43e9a18716508081ff5ac5e3c9d802ee3cd56ab93cd4a0461405b1eb8d346 + version: 6.14.0 + resolution: "@types/qs@npm:6.14.0" + checksum: 10c1/36548b14899854c22cb8202d9382f47d9e1da88ba06bc0f50db591952de2111737e197910fe78d8b424bce445c463c66036aaa61cde4371157b98c4990fc2085 languageName: node linkType: hard @@ -4365,16 +4347,6 @@ __metadata: languageName: node linkType: hard -"accepts@npm:^2.0.0": - version: 2.0.0 - resolution: "accepts@npm:2.0.0" - dependencies: - mime-types: "npm:^3.0.0" - negotiator: "npm:^1.0.0" - checksum: 10c1/22bbe1a016b383ff4a89048f9492c2d8cc38c95c20d596cdd61a2d6e772a1d64fdafef8aad5a005b283c8a55fd4c3a18484b37a90315aa0d4430563b67757621 - languageName: node - linkType: hard - "accepts@npm:~1.3.4, accepts@npm:~1.3.8": version: 1.3.8 resolution: "accepts@npm:1.3.8" @@ -4610,8 +4582,8 @@ __metadata: linkType: hard "antd@npm:^5.25.1": - version: 5.25.1 - resolution: "antd@npm:5.25.1" + version: 5.25.2 + resolution: "antd@npm:5.25.2" dependencies: "@ant-design/colors": "npm:^7.2.0" "@ant-design/cssinjs": "npm:^1.23.0" @@ -4648,11 +4620,11 @@ __metadata: rc-rate: "npm:~2.13.1" rc-resize-observer: "npm:^1.4.3" rc-segmented: "npm:~2.7.0" - rc-select: "npm:~14.16.7" + rc-select: "npm:~14.16.8" rc-slider: "npm:~11.1.8" rc-steps: "npm:~6.0.1" rc-switch: "npm:~4.1.0" - rc-table: "npm:~7.50.4" + rc-table: "npm:~7.50.5" rc-tabs: "npm:~15.6.1" rc-textarea: "npm:~1.10.0" rc-tooltip: "npm:~6.4.0" @@ -4665,7 +4637,7 @@ __metadata: peerDependencies: react: ">=16.9.0" react-dom: ">=16.9.0" - checksum: 10c1/819bf63d49bc5e2fd8c4abd2d61f5a3665725d59d49bc5513387b9784be11dbd31d7a2d2d97f47a6220a9091bac02237c763cd56d811892efe5f0e2223048be6 + checksum: 10c1/9f9a2363eba424cdf634dab2629366808ce2239a28dfb0316a33ea306e3e6a5a6bcb5504c6b1835dc15700c10817e27a16e5829f828eeced4e1857b664e0db7f languageName: node linkType: hard @@ -5290,23 +5262,6 @@ __metadata: languageName: node linkType: hard -"body-parser@npm:^2.2.0": - version: 2.2.0 - resolution: "body-parser@npm:2.2.0" - dependencies: - bytes: "npm:^3.1.2" - content-type: "npm:^1.0.5" - debug: "npm:^4.4.0" - http-errors: "npm:^2.0.0" - iconv-lite: "npm:^0.6.3" - on-finished: "npm:^2.4.1" - qs: "npm:^6.14.0" - raw-body: "npm:^3.0.0" - type-is: "npm:^2.0.0" - checksum: 10c1/275502d25be9064b63a5bfd5abfcc98270870e807914a26748a9da982ccfbac49ee057708be8cba09e0fa426438c85877cc57b8b7c9c3a3bcb225aef9e917fa2 - languageName: node - linkType: hard - "bonjour-service@npm:^1.2.1": version: 1.3.0 resolution: "bonjour-service@npm:1.3.0" @@ -5433,7 +5388,7 @@ __metadata: languageName: node linkType: hard -"bytes@npm:3.1.2, bytes@npm:^3.1.2": +"bytes@npm:3.1.2": version: 3.1.2 resolution: "bytes@npm:3.1.2" checksum: 10c1/102066f8fe70d48c60d33e79f25b39689b858bd47b5c6332e16a07738aa72e11e8dab3b035d137a914d4bfb6edd95afd896bc20b3be3b0b6300d85aa55bf4ec7 @@ -6093,16 +6048,7 @@ __metadata: languageName: node linkType: hard -"content-disposition@npm:^1.0.0": - version: 1.0.0 - resolution: "content-disposition@npm:1.0.0" - dependencies: - safe-buffer: "npm:5.2.1" - checksum: 10c1/aaa3feebb92998e2b8c405b85e309f3442d1b721001f0a92424854656ead13d9731cc77bc157e856a7e30ce58ca0992789f3a1b8a44176dc07e1ebec2320fb4b - languageName: node - linkType: hard - -"content-type@npm:^1.0.5, content-type@npm:~1.0.4, content-type@npm:~1.0.5": +"content-type@npm:~1.0.4, content-type@npm:~1.0.5": version: 1.0.5 resolution: "content-type@npm:1.0.5" checksum: 10c1/984f6dc8716c32916530ea74ad05f395a2696ba7a63d01b9cbc5b9bef41dcfcff6b368a55fd98ec7b3a0617329e094884bc3953dd3956e128161163d23dd5633 @@ -6130,13 +6076,6 @@ __metadata: languageName: node linkType: hard -"cookie-signature@npm:^1.2.1": - version: 1.2.2 - resolution: "cookie-signature@npm:1.2.2" - checksum: 10c1/52acd2b690fa942f6a096e62a5e5b4e93724c7fd485f38de7b7a59e80ce7b7803738f57ad25cdb367ac99311903d04015717297bad708f541f774e00f9bca911 - languageName: node - linkType: hard - "cookie@npm:0.7.1": version: 0.7.1 resolution: "cookie@npm:0.7.1" @@ -6144,13 +6083,6 @@ __metadata: languageName: node linkType: hard -"cookie@npm:^0.7.1": - version: 0.7.2 - resolution: "cookie@npm:0.7.2" - checksum: 10c1/6335a587d568ca025f51f42f5811886c3d4bd751c96396f33fca4154b37a732baf223c42b5921bf62d357b7c77f4a0179ed24c59689e92bf090461196bb7ad94 - languageName: node - linkType: hard - "copy-to-clipboard@npm:^3.3.3": version: 3.3.3 resolution: "copy-to-clipboard@npm:3.3.3" @@ -6213,16 +6145,6 @@ __metadata: languageName: node linkType: hard -"cors@npm:^2.8.5": - version: 2.8.5 - resolution: "cors@npm:2.8.5" - dependencies: - object-assign: "npm:^4" - vary: "npm:^1" - checksum: 10c1/6cd6176012752a2b5a6dc423ec9d798811b45631b0bfb6b87481bf29103a1470dc70535a0913a751612527790cd5e652f2eaebc195b58f7b8610c1fa884208a3 - languageName: node - linkType: hard - "cosmiconfig@npm:^7.0.0": version: 7.1.0 resolution: "cosmiconfig@npm:7.1.0" @@ -6307,7 +6229,7 @@ __metadata: languageName: node linkType: hard -"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.3, cross-spawn@npm:^7.0.5, cross-spawn@npm:^7.0.6": +"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.3, cross-spawn@npm:^7.0.6": version: 7.0.6 resolution: "cross-spawn@npm:7.0.6" dependencies: @@ -6805,7 +6727,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:^4.4.0": +"debug@npm:4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4, debug@npm:^4.4.0": version: 4.4.1 resolution: "debug@npm:4.4.1" dependencies: @@ -6954,7 +6876,7 @@ __metadata: languageName: node linkType: hard -"depd@npm:2.0.0, depd@npm:^2.0.0": +"depd@npm:2.0.0": version: 2.0.0 resolution: "depd@npm:2.0.0" checksum: 10c1/2e8be449f7aa2dfcc5b58babf090f0ca5bfefb1dee51392296795a9aa6347f842516c7b18e01fd50067e6b4b22f0aee1b2005b6a489e6fbab05dcacb0ae2e0c9 @@ -7161,14 +7083,14 @@ __metadata: linkType: hard "dompurify@npm:^3.2.4": - version: 3.2.5 - resolution: "dompurify@npm:3.2.5" + version: 3.2.6 + resolution: "dompurify@npm:3.2.6" dependencies: "@types/trusted-types": "npm:^2.0.7" dependenciesMeta: "@types/trusted-types": optional: true - checksum: 10c1/bf25db12edc7a97b9600c06594c89cc85099f33a34464b70810348b196b51571fe1c2999d4bb4e9e8927d0b935b8865296e585fb37639f1ddbc6f10bbac91f41 + checksum: 10c1/19f97c5a9024216e5d5ccd010d156d3d5b2651e207c7e43fd2b36f7e7551c6526ad00c93ea356c5e643dd5f75a049be63c1728b3879fcc7da7b75bd3ae78f886 languageName: node linkType: hard @@ -7302,13 +7224,6 @@ __metadata: languageName: node linkType: hard -"encodeurl@npm:^2.0.0, encodeurl@npm:~2.0.0": - version: 2.0.0 - resolution: "encodeurl@npm:2.0.0" - checksum: 10c1/26dbea2452b7001172f3db9af5aa33d3b7983a9dff16c5afabc127c3025f09a40cb99da9ece81caf788984dca025a282ee1d4b834eb149fd8fb63863fe9c8977 - languageName: node - linkType: hard - "encodeurl@npm:~1.0.2": version: 1.0.2 resolution: "encodeurl@npm:1.0.2" @@ -7316,6 +7231,13 @@ __metadata: languageName: node linkType: hard +"encodeurl@npm:~2.0.0": + version: 2.0.0 + resolution: "encodeurl@npm:2.0.0" + checksum: 10c1/26dbea2452b7001172f3db9af5aa33d3b7983a9dff16c5afabc127c3025f09a40cb99da9ece81caf788984dca025a282ee1d4b834eb149fd8fb63863fe9c8977 + languageName: node + linkType: hard + "encoding@npm:^0.1.13": version: 0.1.13 resolution: "encoding@npm:0.1.13" @@ -7686,7 +7608,7 @@ __metadata: languageName: node linkType: hard -"escape-html@npm:^1.0.3, escape-html@npm:~1.0.3": +"escape-html@npm:~1.0.3": version: 1.0.3 resolution: "escape-html@npm:1.0.3" checksum: 10c1/8c1e1b5b46985dfb4520bbfaeb636b57df376dff35f40030ccbc7155a0a2253d6f2074a547ea9680dac2e8f8c1bd18fcecac76c3f41c98221a23de5d0d27237a @@ -8016,21 +7938,20 @@ __metadata: linkType: hard "eslint@npm:^9.21.0": - version: 9.26.0 - resolution: "eslint@npm:9.26.0" + version: 9.27.0 + resolution: "eslint@npm:9.27.0" dependencies: "@eslint-community/eslint-utils": "npm:^4.2.0" "@eslint-community/regexpp": "npm:^4.12.1" "@eslint/config-array": "npm:^0.20.0" "@eslint/config-helpers": "npm:^0.2.1" - "@eslint/core": "npm:^0.13.0" + "@eslint/core": "npm:^0.14.0" "@eslint/eslintrc": "npm:^3.3.1" - "@eslint/js": "npm:9.26.0" - "@eslint/plugin-kit": "npm:^0.2.8" + "@eslint/js": "npm:9.27.0" + "@eslint/plugin-kit": "npm:^0.3.1" "@humanfs/node": "npm:^0.16.6" "@humanwhocodes/module-importer": "npm:^1.0.1" "@humanwhocodes/retry": "npm:^0.4.2" - "@modelcontextprotocol/sdk": "npm:^1.8.0" "@types/estree": "npm:^1.0.6" "@types/json-schema": "npm:^7.0.15" ajv: "npm:^6.12.4" @@ -8055,7 +7976,6 @@ __metadata: minimatch: "npm:^3.1.2" natural-compare: "npm:^1.4.0" optionator: "npm:^0.9.3" - zod: "npm:^3.24.2" peerDependencies: jiti: "*" peerDependenciesMeta: @@ -8063,7 +7983,7 @@ __metadata: optional: true bin: eslint: bin/eslint.js - checksum: 10c1/4774817af0581d4d387bcf55c0a3ce2eb47d9473178e6c33808853bfb7d6633ab42d5e4346a2c68f3cad0e9a99b67a3ae9fe132990663f293fc6042c3d1b1289 + checksum: 10c1/91e469d8daa411529180d7238a2a83c223e542e6ee677a3a43fed7aeb6864aa6c7517ed3c74a14e647a0b5cd6985ff915387a1b97a4f2dd650b36254f642cd04 languageName: node linkType: hard @@ -8134,7 +8054,7 @@ __metadata: languageName: node linkType: hard -"etag@npm:^1.8.1, etag@npm:~1.8.1": +"etag@npm:~1.8.1": version: 1.8.1 resolution: "etag@npm:1.8.1" checksum: 10c1/65bd37eebafe53c524d5a3e62cc29deb4b2fed397aabf9324edbe1702f754992bc7872f3379743d4056fd3ad69b3867784bef2187ff53267005042018bfb703b @@ -8162,22 +8082,6 @@ __metadata: languageName: node linkType: hard -"eventsource-parser@npm:^3.0.1": - version: 3.0.2 - resolution: "eventsource-parser@npm:3.0.2" - checksum: 10c1/dc96c5049b694a0f504b387e597a0a4a2b1875f198c91eeaf57218f5c5efa170112a6b6faffce02948216159ef739cbadfda12d0d3bb40a35d416cbc807733a9 - languageName: node - linkType: hard - -"eventsource@npm:^3.0.2": - version: 3.0.7 - resolution: "eventsource@npm:3.0.7" - dependencies: - eventsource-parser: "npm:^3.0.1" - checksum: 10c1/de566b3809fe88dbb1cf921d016ca9da493b9943cdad299cd5d1a93f599f805ebd3a45c3c77ea80384b31bdb1897cfa9b5587901f3de9a0d7b979b3766276621 - languageName: node - linkType: hard - "execa@npm:4.1.0": version: 4.1.0 resolution: "execa@npm:4.1.0" @@ -8263,15 +8167,6 @@ __metadata: languageName: node linkType: hard -"express-rate-limit@npm:^7.5.0": - version: 7.5.0 - resolution: "express-rate-limit@npm:7.5.0" - peerDependencies: - express: ^4.11 || 5 || ^5.0.0-beta.1 - checksum: 10c1/05e32bb2627f2f341a5a6bf5340ce819490ad700ce1d740800b204a4fd7e6e2f86d017e39cdebf4b80cff7415c03875bb05e3ee4087068685f02831ddde4bfcc - languageName: node - linkType: hard - "express@npm:^4.21.2": version: 4.21.2 resolution: "express@npm:4.21.2" @@ -8311,41 +8206,6 @@ __metadata: languageName: node linkType: hard -"express@npm:^5.0.1": - version: 5.1.0 - resolution: "express@npm:5.1.0" - dependencies: - accepts: "npm:^2.0.0" - body-parser: "npm:^2.2.0" - content-disposition: "npm:^1.0.0" - content-type: "npm:^1.0.5" - cookie: "npm:^0.7.1" - cookie-signature: "npm:^1.2.1" - debug: "npm:^4.4.0" - encodeurl: "npm:^2.0.0" - escape-html: "npm:^1.0.3" - etag: "npm:^1.8.1" - finalhandler: "npm:^2.1.0" - fresh: "npm:^2.0.0" - http-errors: "npm:^2.0.0" - merge-descriptors: "npm:^2.0.0" - mime-types: "npm:^3.0.0" - on-finished: "npm:^2.4.1" - once: "npm:^1.4.0" - parseurl: "npm:^1.3.3" - proxy-addr: "npm:^2.0.7" - qs: "npm:^6.14.0" - range-parser: "npm:^1.2.1" - router: "npm:^2.2.0" - send: "npm:^1.1.0" - serve-static: "npm:^2.2.0" - statuses: "npm:^2.0.1" - type-is: "npm:^2.0.1" - vary: "npm:^1.1.2" - checksum: 10c1/e7a1ce5b5322b5abb48b2de434e8e734df68fdb2502aa32b4b69b301041fd3f4556f0bc7d760cb4cc57b30db5448e2777fa2380a9c845209076c4433efd2db05 - languageName: node - linkType: hard - "extend@npm:^3.0.0, extend@npm:~3.0.2": version: 3.0.2 resolution: "extend@npm:3.0.2" @@ -8582,20 +8442,6 @@ __metadata: languageName: node linkType: hard -"finalhandler@npm:^2.1.0": - version: 2.1.0 - resolution: "finalhandler@npm:2.1.0" - dependencies: - debug: "npm:^4.4.0" - encodeurl: "npm:^2.0.0" - escape-html: "npm:^1.0.3" - on-finished: "npm:^2.4.1" - parseurl: "npm:^1.3.3" - statuses: "npm:^2.0.1" - checksum: 10c1/4a7c653c37d454a034ea36c0c2408bfa6d18597f1752c49690545abc3537acea840a24cffb831e7aa6e6d9f1bfe95a44c92dcf792d5e89e8816c760c5f3aa385 - languageName: node - linkType: hard - "find-cache-dir@npm:^2.0.0": version: 2.1.0 resolution: "find-cache-dir@npm:2.1.0" @@ -8758,13 +8604,6 @@ __metadata: languageName: node linkType: hard -"fresh@npm:^2.0.0": - version: 2.0.0 - resolution: "fresh@npm:2.0.0" - checksum: 10c1/13be1f0577fc652a412a1ccb2343dd5bcf70dbb8912c4dd2beb75ff5fead552d763f33d5add437f020552864dec3cebbf1a6245a1f1be2562f89b90f28d22321 - languageName: node - linkType: hard - "fs-extra@npm:^7.0.1": version: 7.0.1 resolution: "fs-extra@npm:7.0.1" @@ -9469,7 +9308,7 @@ __metadata: languageName: node linkType: hard -"http-errors@npm:2.0.0, http-errors@npm:^2.0.0": +"http-errors@npm:2.0.0": version: 2.0.0 resolution: "http-errors@npm:2.0.0" dependencies: @@ -9644,7 +9483,7 @@ __metadata: languageName: node linkType: hard -"iconv-lite@npm:0.6.3, iconv-lite@npm:^0.6.2, iconv-lite@npm:^0.6.3": +"iconv-lite@npm:0.6.3, iconv-lite@npm:^0.6.2": version: 0.6.3 resolution: "iconv-lite@npm:0.6.3" dependencies: @@ -10129,13 +9968,6 @@ __metadata: languageName: node linkType: hard -"is-promise@npm:^4.0.0": - version: 4.0.0 - resolution: "is-promise@npm:4.0.0" - checksum: 10c1/5dedc059652258cffc4509b6e7e060c94599a65fa7db9d50464b13eb6e0b7212bfe60629c907945de04ff3c53c0211ab05efcb74cf0a712a2c3c1be071e07c50 - languageName: node - linkType: hard - "is-regex@npm:^1.2.1": version: 1.2.1 resolution: "is-regex@npm:1.2.1" @@ -11880,13 +11712,6 @@ __metadata: languageName: node linkType: hard -"media-typer@npm:^1.1.0": - version: 1.1.0 - resolution: "media-typer@npm:1.1.0" - checksum: 10c1/668b46f687eec72516db5ddc736013c6196137249da67e02c15136a066294ada219141f15ed0366478c5942eb8e5d2e36d6ebc1b290aaf2ebc16d49a419c4251 - languageName: node - linkType: hard - "mem@npm:^8.0.0": version: 8.1.1 resolution: "mem@npm:8.1.1" @@ -11923,13 +11748,6 @@ __metadata: languageName: node linkType: hard -"merge-descriptors@npm:^2.0.0": - version: 2.0.0 - resolution: "merge-descriptors@npm:2.0.0" - checksum: 10c1/90b0ab10f3016e2aa0d6e2d5e095aafe45cbf503bf97e7a0b41ca837333647ec2f7244e370aa0ea0b0fd6a4f0eaf54c64e85169fb58f4d1ffb8b34c617cfd0b7 - languageName: node - linkType: hard - "merge-stream@npm:^2.0.0": version: 2.0.0 resolution: "merge-stream@npm:2.0.0" @@ -12304,7 +12122,7 @@ __metadata: languageName: node linkType: hard -"mime-db@npm:>= 1.43.0 < 2, mime-db@npm:^1.54.0": +"mime-db@npm:>= 1.43.0 < 2": version: 1.54.0 resolution: "mime-db@npm:1.54.0" checksum: 10c1/c8da99e264bc5db086f56f633c0640730da754f99a3a3858c8c5365779273e985e8c8f7e8da9e68deeec2ae99c923809cfc37fe3ef1b195333c2468a635d13b7 @@ -12320,15 +12138,6 @@ __metadata: languageName: node linkType: hard -"mime-types@npm:^3.0.0, mime-types@npm:^3.0.1": - version: 3.0.1 - resolution: "mime-types@npm:3.0.1" - dependencies: - mime-db: "npm:^1.54.0" - checksum: 10c1/f4267a18893fedf4afdb4d4f945ef489614d217ab572367eb257df72bbf4a2f3802d16b8bcef96abfc62b056bcef46678264a9ba4ed9192717f502705ef23051 - languageName: node - linkType: hard - "mime@npm:1.6.0": version: 1.6.0 resolution: "mime@npm:1.6.0" @@ -12883,7 +12692,7 @@ __metadata: languageName: node linkType: hard -"object-assign@npm:4.x, object-assign@npm:^4, object-assign@npm:^4.0.1, object-assign@npm:^4.1.1": +"object-assign@npm:4.x, object-assign@npm:^4.0.1, object-assign@npm:^4.1.1": version: 4.1.1 resolution: "object-assign@npm:4.1.1" checksum: 10c1/e42b3d041acc8d82fca7bdd57b9d11c277c430fb7dccc33c479c1c10dbe4f34e33025c541f5bac77589ee2413a4d7ece62b1c0035b52119bdb193abf15c0a755 @@ -13396,7 +13205,7 @@ __metadata: languageName: node linkType: hard -"parseurl@npm:^1.3.3, parseurl@npm:~1.3.2, parseurl@npm:~1.3.3": +"parseurl@npm:~1.3.2, parseurl@npm:~1.3.3": version: 1.3.3 resolution: "parseurl@npm:1.3.3" checksum: 10c1/7256a6e3ff8a851297a0ae1373834443cbfeb082e655eb123274af8d2b8806618b3ded58a14cf3eb061f610a8f73245d5fdd611b608ece758e34575d0d2d214a @@ -13488,13 +13297,6 @@ __metadata: languageName: node linkType: hard -"path-to-regexp@npm:^8.0.0": - version: 8.2.0 - resolution: "path-to-regexp@npm:8.2.0" - checksum: 10c1/3ea2a84a685c549c66d68cb9f4b81524359700df7af610da949294deeff0497b2c8f4309637621c1d9ed55074cf9ea681bb8ec49544f1089c8efc22fa8c080f0 - languageName: node - linkType: hard - "path-type@npm:^4.0.0": version: 4.0.0 resolution: "path-type@npm:4.0.0" @@ -13576,13 +13378,6 @@ __metadata: languageName: node linkType: hard -"pkce-challenge@npm:^5.0.0": - version: 5.0.0 - resolution: "pkce-challenge@npm:5.0.0" - checksum: 10c1/824413edf08e71edc03402f7ac1191115c5e53bec0a3a1e8c9d643dd4fcac79e6aaaa7bc242afcd8a5695e7a22d84f6634ebe5efed406be1af4ced2ddb43ea2c - languageName: node - linkType: hard - "pkg-dir@npm:^3.0.0": version: 3.0.0 resolution: "pkg-dir@npm:3.0.0" @@ -14288,7 +14083,7 @@ __metadata: languageName: node linkType: hard -"proxy-addr@npm:^2.0.7, proxy-addr@npm:~2.0.7": +"proxy-addr@npm:~2.0.7": version: 2.0.7 resolution: "proxy-addr@npm:2.0.7" dependencies: @@ -14347,7 +14142,7 @@ __metadata: languageName: node linkType: hard -"qs@npm:6.14.0, qs@npm:^6.14.0": +"qs@npm:6.14.0": version: 6.14.0 resolution: "qs@npm:6.14.0" dependencies: @@ -14419,18 +14214,6 @@ __metadata: languageName: node linkType: hard -"raw-body@npm:^3.0.0": - version: 3.0.0 - resolution: "raw-body@npm:3.0.0" - dependencies: - bytes: "npm:3.1.2" - http-errors: "npm:2.0.0" - iconv-lite: "npm:0.6.3" - unpipe: "npm:1.0.0" - checksum: 10c1/fb87972020036f51fe82b313124425697739057311ebe0d712c28ba80701b0c86b890989b088b8bdb0de796ed9e3e04e3fec0fc9d456887aa97f5d944d151413 - languageName: node - linkType: hard - "rc-align@npm:^2.4.0": version: 2.4.5 resolution: "rc-align@npm:2.4.5" @@ -14792,7 +14575,7 @@ __metadata: languageName: node linkType: hard -"rc-select@npm:~14.16.2, rc-select@npm:~14.16.7": +"rc-select@npm:~14.16.2, rc-select@npm:~14.16.8": version: 14.16.8 resolution: "rc-select@npm:14.16.8" dependencies: @@ -14852,7 +14635,7 @@ __metadata: languageName: node linkType: hard -"rc-table@npm:~7.50.4": +"rc-table@npm:~7.50.5": version: 7.50.5 resolution: "rc-table@npm:7.50.5" dependencies: @@ -15850,19 +15633,6 @@ __metadata: languageName: node linkType: hard -"router@npm:^2.2.0": - version: 2.2.0 - resolution: "router@npm:2.2.0" - dependencies: - debug: "npm:^4.4.0" - depd: "npm:^2.0.0" - is-promise: "npm:^4.0.0" - parseurl: "npm:^1.3.3" - path-to-regexp: "npm:^8.0.0" - checksum: 10c1/3ac1c459224f12f80c06163075e0970130ecce554cab6f75c8995a863635a3c67b968ab93ad7877a44ee53f491ae38f4594a33ddc8cf83631b41327d754e46b5 - languageName: node - linkType: hard - "run-applescript@npm:^7.0.0": version: 7.0.0 resolution: "run-applescript@npm:7.0.0" @@ -16104,25 +15874,6 @@ __metadata: languageName: node linkType: hard -"send@npm:^1.1.0, send@npm:^1.2.0": - version: 1.2.0 - resolution: "send@npm:1.2.0" - dependencies: - debug: "npm:^4.3.5" - encodeurl: "npm:^2.0.0" - escape-html: "npm:^1.0.3" - etag: "npm:^1.8.1" - fresh: "npm:^2.0.0" - http-errors: "npm:^2.0.0" - mime-types: "npm:^3.0.1" - ms: "npm:^2.1.3" - on-finished: "npm:^2.4.1" - range-parser: "npm:^1.2.1" - statuses: "npm:^2.0.1" - checksum: 10c1/5bf65110948e8834068b069ba7508ef55ac63b74f7e2233e42d39f52452f513c99795a8aaf834161686189193cb5938a2225bbc7cc0a491dd8f068638e31feaf - languageName: node - linkType: hard - "serialize-javascript@npm:^6.0.0, serialize-javascript@npm:^6.0.2": version: 6.0.2 resolution: "serialize-javascript@npm:6.0.2" @@ -16159,18 +15910,6 @@ __metadata: languageName: node linkType: hard -"serve-static@npm:^2.2.0": - version: 2.2.0 - resolution: "serve-static@npm:2.2.0" - dependencies: - encodeurl: "npm:^2.0.0" - escape-html: "npm:^1.0.3" - parseurl: "npm:^1.3.3" - send: "npm:^1.2.0" - checksum: 10c1/343e6baca7d8bf6a6c6176a18c0a57351fd54964a6ba825cd5577c09e6618414eb61067c02fc3e5206604e402c8b0ceaf5e8747e6bc8c89cb1194b10f5f97a93 - languageName: node - linkType: hard - "set-blocking@npm:^2.0.0": version: 2.0.0 resolution: "set-blocking@npm:2.0.0" @@ -16654,7 +16393,7 @@ __metadata: languageName: node linkType: hard -"statuses@npm:2.0.1, statuses@npm:^2.0.1": +"statuses@npm:2.0.1": version: 2.0.1 resolution: "statuses@npm:2.0.1" checksum: 10c1/6230edd96de95e58310c9bdc24d9487ea633f17994daad44c1d86055e6cf667de5834c8acaa376dc7ebd44198792797ff2caf04b4bb549bc83e31865bc57fdfb @@ -17072,12 +16811,11 @@ __metadata: linkType: hard "synckit@npm:^0.11.0": - version: 0.11.5 - resolution: "synckit@npm:0.11.5" + version: 0.11.6 + resolution: "synckit@npm:0.11.6" dependencies: "@pkgr/core": "npm:^0.2.4" - tslib: "npm:^2.8.1" - checksum: 10c1/a4fcf437959f0c4465c11b30e3910077375f875ced6f4b8e237c18f04a25e7a55a6b65b46e9b4909bece3367974d42556110701272d129f64afc6b25fd56fb9a + checksum: 10c1/f5d6fd3561e4b78fb2cc05c68428a3cff410104d4ce07b6d186a8f138d49586182201e2fdbbba18f6d08889c9824d70d6015b61df43681c407a66f8597ad76c9 languageName: node linkType: hard @@ -17122,9 +16860,9 @@ __metadata: linkType: hard "tapable@npm:^2.0.0, tapable@npm:^2.1.1, tapable@npm:^2.2.0, tapable@npm:^2.2.1": - version: 2.2.1 - resolution: "tapable@npm:2.2.1" - checksum: 10c1/3835d697b2c9269546fd4971dbce9026a53d19207f90457085437f85c0abe809e83389e8a0e098bbba14af932d752905184bd87fad68818f275fcafc73ac4f2a + version: 2.2.2 + resolution: "tapable@npm:2.2.2" + checksum: 10c1/87fee52177ff587369d76ff8a8ee94f90b8a7bf2b16ed9128130bd7ff7a6cb17a53b28c13833e3bc344533415ebbc40f368df8f65f04d4bc40e8832472ee1025 languageName: node linkType: hard @@ -17417,11 +17155,11 @@ __metadata: linkType: hard "tree-dump@npm:^1.0.1": - version: 1.0.2 - resolution: "tree-dump@npm:1.0.2" + version: 1.0.3 + resolution: "tree-dump@npm:1.0.3" peerDependencies: tslib: 2 - checksum: 10c1/fbb190f064098b16541f8168b3ea31cc7bcab986c7dee03ae8631a7ec80ea6bb9ad4edd64ca8a0a057bceee5f4048dec8ae8bad8a116efda8060777363484990 + checksum: 10c1/c49e7af32b097a316c029f3662ce967d361502259148f8b37417f9f4d98baac110c0639f0d3a501ad69c5223ddc614326f0f72987ace2b7337c7bccc2e6c215c languageName: node linkType: hard @@ -17465,8 +17203,8 @@ __metadata: linkType: hard "ts-jest@npm:^29.0.5": - version: 29.3.3 - resolution: "ts-jest@npm:29.3.3" + version: 29.3.4 + resolution: "ts-jest@npm:29.3.4" dependencies: bs-logger: "npm:^0.2.6" ejs: "npm:^3.1.10" @@ -17498,7 +17236,7 @@ __metadata: optional: true bin: ts-jest: cli.js - checksum: 10c1/ba465e96c89b28019f67a17e6843c6e9a9eefb3897b2fc096fba8b4706f92114e812a860032c8b180cdbb9d07573f33f96e4242c3faecb4edd135211358a20fa + checksum: 10c1/298e3d4b6dba4a6a1a07d59b03d72f076a307e4af28d2054e6b32e400b0869b77bb0560414cbb94f35a389c99040048d84b8207d3fd3353a5792c58763f4efb0 languageName: node linkType: hard @@ -17559,7 +17297,7 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^2.0.0, tslib@npm:^2.0.3, tslib@npm:^2.1.0, tslib@npm:^2.4.0, tslib@npm:^2.8.1": +"tslib@npm:^2.0.0, tslib@npm:^2.0.3, tslib@npm:^2.1.0, tslib@npm:^2.4.0": version: 2.8.1 resolution: "tslib@npm:2.8.1" checksum: 10c1/22a6e86110cc6556a51eef13055c67961df40b8376ba34d5b3d608c671c9284e10d533cb077d6cd270bc3fffb30bd406644e33f83df6fae5de9c43d84fee54a6 @@ -17612,17 +17350,6 @@ __metadata: languageName: node linkType: hard -"type-is@npm:^2.0.0, type-is@npm:^2.0.1": - version: 2.0.1 - resolution: "type-is@npm:2.0.1" - dependencies: - content-type: "npm:^1.0.5" - media-typer: "npm:^1.1.0" - mime-types: "npm:^3.0.0" - checksum: 10c1/2982ddcca2231d5e6b5e7b8a087744d82fb8a425487516102eca665e52d7a87053fa59ede6003a13c90d89d28d88cade80b36fedbbf7382302142fd659887ae0 - languageName: node - linkType: hard - "type-is@npm:~1.6.18": version: 1.6.18 resolution: "type-is@npm:1.6.18" @@ -18020,14 +17747,14 @@ __metadata: linkType: hard "use-isomorphic-layout-effect@npm:^1.2.0": - version: 1.2.0 - resolution: "use-isomorphic-layout-effect@npm:1.2.0" + version: 1.2.1 + resolution: "use-isomorphic-layout-effect@npm:1.2.1" peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 peerDependenciesMeta: "@types/react": optional: true - checksum: 10c1/decd26ea500f6fa93319167b9b674532107c621ad7bf0e3ca79b5265289dc70e1979e49c4cb497595de1478706a0166bd43406378a0b1a6ce6143c02152ec311 + checksum: 10c1/3bed041911b8aa1143e13e28331f6c8dcd1f9a8b8647a4a3a9d0b04dc882827e33b0bdb1e009f71b4b7dc0f7d1506b612f4cbbb5a66600d119b9a5e11070c77a languageName: node linkType: hard @@ -18126,7 +17853,7 @@ __metadata: languageName: node linkType: hard -"vary@npm:^1, vary@npm:^1.1.2, vary@npm:~1.1.2": +"vary@npm:~1.1.2": version: 1.1.2 resolution: "vary@npm:1.1.2" checksum: 10c1/9664fb39d625fdd82df935d5883ca6e1593203f47e49860e7d36e147908dec73753f5a0f59739f71df563c635e75a5d31a1330683373f0967df8a7dba1efd01a @@ -18245,12 +17972,12 @@ __metadata: linkType: hard "watchpack@npm:^2.4.1": - version: 2.4.2 - resolution: "watchpack@npm:2.4.2" + version: 2.4.3 + resolution: "watchpack@npm:2.4.3" dependencies: glob-to-regexp: "npm:^0.4.1" graceful-fs: "npm:^4.1.2" - checksum: 10c1/badd7fb089b8f486055a8f17d783e54b96dcba6f3ad39bf481eab17d2d174b4a4cb68a659fe425d8c2c463589be41bf0cbd4686fc6fba6aafd6d03df17d9c8a1 + checksum: 10c1/dadcdad5b7ef9aa7f94bde18560dd5fd1c81261ca07b444ae475c643931d2a838050a71e03705aa88cfb67097aa9ca61ac11b82c13085af66aeb5b1fa4340215 languageName: node linkType: hard @@ -18834,22 +18561,6 @@ __metadata: languageName: node linkType: hard -"zod-to-json-schema@npm:^3.24.1": - version: 3.24.5 - resolution: "zod-to-json-schema@npm:3.24.5" - peerDependencies: - zod: ^3.24.1 - checksum: 10c1/f78198944e43dbf34cf9b588278a5f7c56617e4731a1ad7849558a5c22b8c9ce2e089b7434b0e6b5c410bb3eb7142e214df9ca47db357009442555a939c8d4ee - languageName: node - linkType: hard - -"zod@npm:^3.23.8, zod@npm:^3.24.2": - version: 3.24.4 - resolution: "zod@npm:3.24.4" - checksum: 10c1/9573d5ca32ee8c8aaf5d44c4c40a118f5abe67abaa0786bcb0f71a89e9845536efeb0ad9dd5d81ef4c78727abd7d50af252ef527f12da9f71e57c768cee6b073 - languageName: node - linkType: hard - "zrender@npm:5.6.1": version: 5.6.1 resolution: "zrender@npm:5.6.1" diff --git a/spot/entrypoints/audio/index.html b/spot/entrypoints/audio/index.html index 45000e227..5cdac81b9 100644 --- a/spot/entrypoints/audio/index.html +++ b/spot/entrypoints/audio/index.html @@ -8,10 +8,10 @@ -
- - - +
+ + +

@@ -23,7 +23,7 @@ - + Allow microphone access diff --git a/spot/entrypoints/content/SavingControls.tsx b/spot/entrypoints/content/SavingControls.tsx index d79c84ed5..bbf1c2568 100644 --- a/spot/entrypoints/content/SavingControls.tsx +++ b/spot/entrypoints/content/SavingControls.tsx @@ -380,7 +380,7 @@ function SavingControls({ ) : null}
{playing() ? (
void }) {
-
+

View Recording

@@ -142,7 +142,7 @@ function Settings({ goBack }: { goBack: () => void }) {

-
+

Include DevTools @@ -164,7 +164,7 @@ function Settings({ goBack }: { goBack: () => void }) {

-
+

Use Debugger @@ -185,7 +185,7 @@ function Settings({ goBack }: { goBack: () => void }) {

-
+

Ingest Point

diff --git a/spot/entrypoints/popup/components/AudioPicker.tsx b/spot/entrypoints/popup/components/AudioPicker.tsx index 627b5c840..9d24198eb 100644 --- a/spot/entrypoints/popup/components/AudioPicker.tsx +++ b/spot/entrypoints/popup/components/AudioPicker.tsx @@ -19,7 +19,7 @@ const AudioPicker: Component = (props) => { return (
diff --git a/spot/entrypoints/popup/components/Header.tsx b/spot/entrypoints/popup/components/Header.tsx index ec6f9d5b8..4f194105b 100644 --- a/spot/entrypoints/popup/components/Header.tsx +++ b/spot/entrypoints/popup/components/Header.tsx @@ -35,7 +35,7 @@ const Header: Component = (props) => {
-
+
@@ -50,7 +50,7 @@ const Header: Component = (props) => { target="_blank" rel="noopener noreferrer" > -
+
@@ -61,7 +61,7 @@ const Header: Component = (props) => { data-tip="Settings" onClick={props.openSettings} > -
+
From bd97e15e9a7c5d745eae8f691a55c9f7f31f3f74 Mon Sep 17 00:00:00 2001 From: Rajesh Rajendran Date: Tue, 20 May 2025 15:19:04 +0200 Subject: [PATCH 16/22] feat(ci): Support building from branch for old patch (#3419) Signed-off-by: rjshrjndrn --- .github/workflows/patch-build-old.yaml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/patch-build-old.yaml b/.github/workflows/patch-build-old.yaml index 719f2bfdb..27e025da7 100644 --- a/.github/workflows/patch-build-old.yaml +++ b/.github/workflows/patch-build-old.yaml @@ -8,7 +8,11 @@ on: required: true default: 'chalice,frontend' tag: - description: 'Tag to build patches from.' + description: 'Tag to update.' + required: true + type: string + branch: + description: 'Branch to build patches from. Make sure the branch is uptodate with tag. Else itll cause missing commits.' required: true type: string @@ -73,7 +77,7 @@ jobs: - name: Get HEAD Commit ID run: echo "HEAD_COMMIT_ID=$(git rev-parse HEAD)" >> $GITHUB_ENV - name: Define Branch Name - run: echo "BRANCH_NAME=patch/main/${HEAD_COMMIT_ID}" >> $GITHUB_ENV + run: echo "BRANCH_NAME=${{inputs.branch}}" >> $GITHUB_ENV - name: Build id: build-image From c5555d7343f76e0a3e5b06e13053b9fccd14cf5d Mon Sep 17 00:00:00 2001 From: nick-delirium Date: Tue, 20 May 2025 17:41:48 +0200 Subject: [PATCH 17/22] ui: fix for close icon in warnbadge darkmode --- .../app/components/Session_/WarnBadge.tsx | 2 +- frontend/yarn.lock | 74 +++++++++---------- 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/frontend/app/components/Session_/WarnBadge.tsx b/frontend/app/components/Session_/WarnBadge.tsx index c00879178..dda00d758 100644 --- a/frontend/app/components/Session_/WarnBadge.tsx +++ b/frontend/app/components/Session_/WarnBadge.tsx @@ -188,7 +188,7 @@ const WarnBadge = React.memo( className="py-1 ml-3 cursor-pointer" onClick={() => closeWarning(1)} > - +
) : null} diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 1f555d367..fa8f7edad 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -3038,61 +3038,61 @@ __metadata: languageName: node linkType: hard -"@sentry-internal/browser-utils@npm:9.20.0": - version: 9.20.0 - resolution: "@sentry-internal/browser-utils@npm:9.20.0" +"@sentry-internal/browser-utils@npm:9.21.0": + version: 9.21.0 + resolution: "@sentry-internal/browser-utils@npm:9.21.0" dependencies: - "@sentry/core": "npm:9.20.0" - checksum: 10c1/f0ee708dab12063b839aee9377cd18efda7c6c7e84accf07a7e80b182dc59b07dfeacf4d5e8f48d318498981d9b343edaaeca933dc84f06200794d36fe76275d + "@sentry/core": "npm:9.21.0" + checksum: 10c1/a19819666dbd22148321c9e177f42dfc49c3779ed134076d91f005d2e8c4f90b5bef0214c305ffbdcfeccdcc0caca4f548707a8505af81a2b6ec2ee570da796c languageName: node linkType: hard -"@sentry-internal/feedback@npm:9.20.0": - version: 9.20.0 - resolution: "@sentry-internal/feedback@npm:9.20.0" +"@sentry-internal/feedback@npm:9.21.0": + version: 9.21.0 + resolution: "@sentry-internal/feedback@npm:9.21.0" dependencies: - "@sentry/core": "npm:9.20.0" - checksum: 10c1/052726a5dece00c8359b56ac37dcf13a2b665440185d1dee2f443563f0c551b83e64cee22524feefd40fb5e50f5c7b7ca4a29955eb99a09a2da78836ece34fd2 + "@sentry/core": "npm:9.21.0" + checksum: 10c1/d12bfbb61abbcfddb67d42c7aaa1545e34f4df1210eb8dec3347eba7133e108b6b9cda680d5c131e7534882bb03c8c36972e9ab1396ca90d2113dd991d67138d languageName: node linkType: hard -"@sentry-internal/replay-canvas@npm:9.20.0": - version: 9.20.0 - resolution: "@sentry-internal/replay-canvas@npm:9.20.0" +"@sentry-internal/replay-canvas@npm:9.21.0": + version: 9.21.0 + resolution: "@sentry-internal/replay-canvas@npm:9.21.0" dependencies: - "@sentry-internal/replay": "npm:9.20.0" - "@sentry/core": "npm:9.20.0" - checksum: 10c1/5c47cfde47e391a88983509892c7e46abc9f56c2aaf81f8d31cf38454a6beadfa4ee5199cf535d8a86428c2560a43407fa11cb6b2d5e8bf4de9ea6a8ca7e536c + "@sentry-internal/replay": "npm:9.21.0" + "@sentry/core": "npm:9.21.0" + checksum: 10c1/24ab33e4edf8aa7c9871eddd2492b080ad0ce40e0a04b8a11f99d4a034d8e34a632e155f380c24ea49104e666a12c26d544c623551a9ee21ef602023bac5c919 languageName: node linkType: hard -"@sentry-internal/replay@npm:9.20.0": - version: 9.20.0 - resolution: "@sentry-internal/replay@npm:9.20.0" +"@sentry-internal/replay@npm:9.21.0": + version: 9.21.0 + resolution: "@sentry-internal/replay@npm:9.21.0" dependencies: - "@sentry-internal/browser-utils": "npm:9.20.0" - "@sentry/core": "npm:9.20.0" - checksum: 10c1/a8024de775a16c0667dfc0562a7365e2bf8685832c76b5067736da0662d1f6584ee7dd0ce6032b4a63d196001ba2771a7296ce709fa53e465a1453fe89b8b931 + "@sentry-internal/browser-utils": "npm:9.21.0" + "@sentry/core": "npm:9.21.0" + checksum: 10c1/9f5e9296b7026902e77714563933e40483c4fdcf9a02586af726b503a922d3df89bbe9ab7224edf532894e76bd3fdc63b280e4d417d06c8ebd19ca102863d6b5 languageName: node linkType: hard "@sentry/browser@npm:^9.18.0": - version: 9.20.0 - resolution: "@sentry/browser@npm:9.20.0" + version: 9.21.0 + resolution: "@sentry/browser@npm:9.21.0" dependencies: - "@sentry-internal/browser-utils": "npm:9.20.0" - "@sentry-internal/feedback": "npm:9.20.0" - "@sentry-internal/replay": "npm:9.20.0" - "@sentry-internal/replay-canvas": "npm:9.20.0" - "@sentry/core": "npm:9.20.0" - checksum: 10c1/590510de27ca795210942e3499e07ebb22e6d0b0d2271608d8866ae66e29b7e46353ea5db8ac2533698cdf83ac4c11730aac0f08caddb9e8da2cb1dc5784d651 + "@sentry-internal/browser-utils": "npm:9.21.0" + "@sentry-internal/feedback": "npm:9.21.0" + "@sentry-internal/replay": "npm:9.21.0" + "@sentry-internal/replay-canvas": "npm:9.21.0" + "@sentry/core": "npm:9.21.0" + checksum: 10c1/81a50d67a0e7bda344bc1fbaa4dad67061d38e1a0901c87df4938890361a4614e4830a4b886b3fcc2d0f869b686e7c0e5bb33d1fb2d7543fedecc7e6107a0fce languageName: node linkType: hard -"@sentry/core@npm:9.20.0": - version: 9.20.0 - resolution: "@sentry/core@npm:9.20.0" - checksum: 10c1/ba72ef9d1d08253e6508f1008a2ae164b8d315faab88d1cb4c7bde42a7bdf6e55d4088b3dd3d8d8a912e02620109d8e5a0d79dd6ccf2cab56c151d640a0aa9e5 +"@sentry/core@npm:9.21.0": + version: 9.21.0 + resolution: "@sentry/core@npm:9.21.0" + checksum: 10c1/5bca1342cb4f113f524b0f31a3a0c8c1db448949ed79a517e0dc4a6c66516138034372bde78b6ab27a4694336773df3cc87ad96fdd4f6787dd9eb06b32dc6143 languageName: node linkType: hard @@ -18135,8 +18135,8 @@ __metadata: linkType: hard "webpack@npm:^5.99.8": - version: 5.99.8 - resolution: "webpack@npm:5.99.8" + version: 5.99.9 + resolution: "webpack@npm:5.99.9" dependencies: "@types/eslint-scope": "npm:^3.7.7" "@types/estree": "npm:^1.0.6" @@ -18167,7 +18167,7 @@ __metadata: optional: true bin: webpack: bin/webpack.js - checksum: 10c1/5732aa765582af0650c12f274339864eea36250e3edc028605fedf6364adc68eaa0ad41e422630254ee4423a6ac87a5b5ead115e4d474e8ce98f416103c678ca + checksum: 10c1/446154d34349333600efd47505f3146325aaeb2b1b1c1bc83870c4c4a7769bcae8304cfba9fd95dfed40faf7aeb3524ecb362f922ad4d5c8fd5dd6e7574c3db0 languageName: node linkType: hard From 83ebd01526d277f6995da61124e604741b082d5c Mon Sep 17 00:00:00 2001 From: nick-delirium Date: Tue, 20 May 2025 17:55:49 +0200 Subject: [PATCH 18/22] ui: darkmode fixes for spot and highlights --- .../components/FilterSeries/ExcludeFilters.tsx | 2 +- .../Player/ClipPlayer/ClipPlayerContent.tsx | 15 +++++++++++++-- .../components/Spots/SpotsList/SpotListItem.tsx | 6 +++--- .../Spots/SpotsList/SpotsListHeader.tsx | 3 ++- 4 files changed, 19 insertions(+), 7 deletions(-) diff --git a/frontend/app/components/Dashboard/components/FilterSeries/ExcludeFilters.tsx b/frontend/app/components/Dashboard/components/FilterSeries/ExcludeFilters.tsx index 532176702..10bcc278a 100644 --- a/frontend/app/components/Dashboard/components/FilterSeries/ExcludeFilters.tsx +++ b/frontend/app/components/Dashboard/components/FilterSeries/ExcludeFilters.tsx @@ -56,7 +56,7 @@ function ExcludeFilters(props: Props) { ))}
) : ( - )} diff --git a/frontend/app/components/Session/Player/ClipPlayer/ClipPlayerContent.tsx b/frontend/app/components/Session/Player/ClipPlayer/ClipPlayerContent.tsx index 6bc61da82..ddf267669 100644 --- a/frontend/app/components/Session/Player/ClipPlayer/ClipPlayerContent.tsx +++ b/frontend/app/components/Session/Player/ClipPlayer/ClipPlayerContent.tsx @@ -54,11 +54,22 @@ function ClipPlayerContent(props: Props) { if (!playerContext.player) return null; + const outerHeight = props.isHighlight ? 556 + 39 : 556; + const innerHeight = props.isHighlight ? 504 + 39 : 504; return (
-
+
diff --git a/frontend/app/components/Spots/SpotsList/SpotListItem.tsx b/frontend/app/components/Spots/SpotsList/SpotListItem.tsx index 04dc1dce6..a06a71404 100644 --- a/frontend/app/components/Spots/SpotsList/SpotListItem.tsx +++ b/frontend/app/components/Spots/SpotsList/SpotListItem.tsx @@ -148,14 +148,14 @@ function SpotListItem({
-
+
{spot.duration}
@@ -213,7 +213,7 @@ export function GridItem({ return (
{t('Clear')} - From 24a220bc51c43214a140a9a7206ff3c9c2dd3edb Mon Sep 17 00:00:00 2001 From: nick-delirium Date: Tue, 20 May 2025 17:58:15 +0200 Subject: [PATCH 19/22] ui: hide kai chat msgs from self tracking --- frontend/app/components/Kai/components/ChatMsg.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/app/components/Kai/components/ChatMsg.tsx b/frontend/app/components/Kai/components/ChatMsg.tsx index 8b3224f3f..89174f368 100644 --- a/frontend/app/components/Kai/components/ChatMsg.tsx +++ b/frontend/app/components/Kai/components/ChatMsg.tsx @@ -99,7 +99,7 @@ export function ChatMsg({
)}
-
+
{text}
{isUser ? ( From f3f7992c0a6d57c99a2492ea72289e9b67218157 Mon Sep 17 00:00:00 2001 From: Delirium Date: Wed, 21 May 2025 16:19:39 +0200 Subject: [PATCH 20/22] Kai charting (#3420) * ui: chart btn * ui: chats list modal fixes, signal editing visually, map charting response * ui: support readable errors for kai * ui: add support for response limiting --- frontend/app/components/Kai/KaiChat.tsx | 72 +--------- frontend/app/components/Kai/KaiService.ts | 45 +++++- frontend/app/components/Kai/KaiStore.ts | 94 +++++++++++- frontend/app/components/Kai/SocketManager.ts | 4 + .../components/Kai/components/ChatHeader.tsx | 28 ++-- .../components/Kai/components/ChatInput.tsx | 128 ++++++++++++----- .../app/components/Kai/components/ChatLog.tsx | 21 ++- .../app/components/Kai/components/ChatMsg.tsx | 120 ++++++++++++---- .../components/Kai/components/ChatsModal.tsx | 136 ++++++++++++++++++ .../app/components/Kai/components/Usage.tsx | 31 ++++ frontend/app/components/Kai/utils.ts | 36 +++++ 11 files changed, 555 insertions(+), 160 deletions(-) create mode 100644 frontend/app/components/Kai/components/ChatsModal.tsx create mode 100644 frontend/app/components/Kai/components/Usage.tsx create mode 100644 frontend/app/components/Kai/utils.ts diff --git a/frontend/app/components/Kai/KaiChat.tsx b/frontend/app/components/Kai/KaiChat.tsx index 904181460..17bb4c820 100644 --- a/frontend/app/components/Kai/KaiChat.tsx +++ b/frontend/app/components/Kai/KaiChat.tsx @@ -1,16 +1,15 @@ import React from 'react'; import { useModal } from 'App/components/Modal'; -import { MessagesSquare, Trash } from 'lucide-react'; import ChatHeader from './components/ChatHeader'; import { PANEL_SIZES } from 'App/constants/panelSizes'; import ChatLog from './components/ChatLog'; import IntroSection from './components/IntroSection'; -import { useQuery } from '@tanstack/react-query'; import { kaiService } from 'App/services'; import { toast } from 'react-toastify'; import { useStore } from 'App/mstore'; import { observer } from 'mobx-react-lite'; import { useHistory, useLocation } from 'react-router-dom'; +import ChatsModal from './components/ChatsModal'; function KaiChat() { const { userStore, projectsStore } = useStore(); @@ -99,7 +98,9 @@ function KaiChat() { }; return (
-
+
void; - projectId: string; -}) { - const { - data = [], - isPending, - refetch, - } = useQuery({ - queryKey: ['kai', 'chats', projectId], - queryFn: () => kaiService.getKaiChats(projectId), - staleTime: 1000 * 60, - }); - - const onDelete = async (id: string) => { - try { - await kaiService.deleteKaiChat(projectId, id); - } catch (e) { - toast.error("Something wen't wrong. Please try again later."); - } - refetch(); - }; - return ( -
-
- - Chats -
- {isPending ? ( -
Loading chats...
- ) : ( -
- {data.map((chat) => ( -
-
-
onSelect(chat.thread_id, chat.title)} - className="cursor-pointer hover:underline truncate" - > - {chat.title} -
-
-
onDelete(chat.thread_id)} - className="cursor-pointer opacity-0 group-hover:opacity-100 rounded-r h-full px-2 flex items-center group-hover:bg-active-blue" - > - -
-
- ))} -
- )} -
- ); -} - export default observer(KaiChat); diff --git a/frontend/app/components/Kai/KaiService.ts b/frontend/app/components/Kai/KaiService.ts index 1ca481e2d..984f7e3f4 100644 --- a/frontend/app/components/Kai/KaiService.ts +++ b/frontend/app/components/Kai/KaiService.ts @@ -3,7 +3,7 @@ import AiService from '@/services/AiService'; export default class KaiService extends AiService { getKaiChats = async ( projectId: string, - ): Promise<{ title: string; thread_id: string }[]> => { + ): Promise<{ title: string; thread_id: string; datetime: string }[]> => { const r = await this.client.get(`/kai/${projectId}/chats`); if (!r.ok) { throw new Error('Failed to fetch chats'); @@ -35,6 +35,7 @@ export default class KaiService extends AiService { feedback: boolean | null; supports_visualization: boolean; chart: string; + chart_data: string; }[] > => { const r = await this.client.get(`/kai/${projectId}/chats/${threadId}`); @@ -79,4 +80,46 @@ export default class KaiService extends AiService { const data = await r.json(); return data; }; + + getMsgChart = async ( + messageId: string, + projectId: string, + ): Promise<{ filters: any[]; chart: string; eventsOrder: string }> => { + const r = await this.client.get( + `/kai/${projectId}/chats/data/${messageId}`, + ); + if (!r.ok) { + throw new Error('Failed to fetch chart data'); + } + const data = await r.json(); + return data; + }; + + saveChartData = async ( + messageId: string, + projectId: string, + chartData: any, + ) => { + const r = await this.client.post( + `/kai/${projectId}/chats/data/${messageId}`, + { + chart_data: JSON.stringify(chartData), + }, + ); + if (!r.ok) { + throw new Error('Failed to save chart data'); + } + + const data = await r.json(); + return data; + }; + + checkUsage = async (): Promise<{ total: number; used: number }> => { + const r = await this.client.get(`/kai/usage`); + if (!r.ok) { + throw new Error('Failed to fetch usage'); + } + const data = await r.json(); + return data; + }; } diff --git a/frontend/app/components/Kai/KaiStore.ts b/frontend/app/components/Kai/KaiStore.ts index 38e622ff1..d4d85526a 100644 --- a/frontend/app/components/Kai/KaiStore.ts +++ b/frontend/app/components/Kai/KaiStore.ts @@ -2,17 +2,25 @@ import { makeAutoObservable, runInAction } from 'mobx'; import { BotChunk, ChatManager } from './SocketManager'; import { kaiService as aiService, kaiService } from 'App/services'; import { toast } from 'react-toastify'; +import Widget from 'App/mstore/types/widget'; export interface Message { text: string; isUser: boolean; messageId: string; + /** filters to get chart */ chart: string; + /** chart data */ + chart_data: string; supports_visualization: boolean; feedback: boolean | null; duration: number; } -export interface SentMessage extends Omit { +export interface SentMessage + extends Omit< + Message, + 'duration' | 'feedback' | 'chart' | 'supports_visualization' + > { replace: boolean; } @@ -22,10 +30,16 @@ class KaiStore { messages: Array = []; queryText = ''; loadingChat = false; - replacing = false; + replacing: string | null = null; + usage = { + total: 0, + used: 0, + percent: 0, + }; constructor() { makeAutoObservable(this); + this.checkUsage(); } get lastHumanMessage() { @@ -80,9 +94,9 @@ class KaiStore { this.messages.push(message); }; - editMessage = (text: string) => { + editMessage = (text: string, messageId: string) => { this.setQueryText(text); - this.setReplacing(true); + this.setReplacing(messageId); }; replaceAtIndex = (message: Message, index: number) => { @@ -115,6 +129,7 @@ class KaiStore { feedback: m.feedback, chart: m.chart, supports_visualization: m.supports_visualization, + chart_data: m.chart_data, }; }), ); @@ -137,6 +152,7 @@ class KaiStore { console.error('No token found'); return; } + this.checkUsage(); this.chatManager = new ChatManager({ ...settings, token }); this.chatManager.setOnMsgHook({ msgCallback: (msg) => { @@ -173,7 +189,9 @@ class KaiStore { feedback: null, chart: '', supports_visualization: msg.supports_visualization, + chart_data: '', }; + this.bumpUsage(); this.addMessage(msgObj); this.setProcessingStage(null); } @@ -187,13 +205,18 @@ class KaiStore { } }; - setReplacing = (replacing: boolean) => { + setReplacing = (replacing: string | null) => { this.replacing = replacing; }; + bumpUsage = () => { + this.usage.used += 1; + this.usage.percent = (this.usage.used / this.usage.total) * 100; + }; + sendMessage = (message: string) => { if (this.chatManager) { - this.chatManager.sendMessage(message, this.replacing); + this.chatManager.sendMessage(message, !!this.replacing); } if (this.replacing) { console.log( @@ -219,6 +242,7 @@ class KaiStore { duration: 0, supports_visualization: false, chart: '', + chart_data: '', }); }; @@ -273,6 +297,64 @@ class KaiStore { this.chatManager = null; } }; + + getMessageChart = async (msgId: string, projectId: string) => { + this.setProcessingStage({ + content: 'Generating visualization...', + stage: 'chart', + messageId: msgId, + duration: 0, + type: 'chunk', + supports_visualization: false, + }); + try { + const filters = await kaiService.getMsgChart(msgId, projectId); + const data = { + metricId: undefined, + dashboardId: undefined, + widgetId: undefined, + metricOf: undefined, + metricType: undefined, + metricFormat: undefined, + viewType: undefined, + name: 'Kai Visualization', + series: [ + { + name: 'Kai Visualization', + filter: { + eventsOrder: filters.eventsOrder, + filters: filters.filters, + }, + }, + ], + }; + const metric = new Widget().fromJson(data); + return metric; + } catch (e) { + console.error(e); + throw new Error('Failed to generate visualization'); + } finally { + this.setProcessingStage(null); + } + }; + + getParsedChart = (data: string) => { + const parsedData = JSON.parse(data); + return new Widget().fromJson(parsedData); + }; + + checkUsage = async () => { + try { + const { total, used } = await kaiService.checkUsage(); + this.usage = { + total, + used, + percent: (used / total) * 100, + }; + } catch (e) { + console.error(e); + } + }; } export const kaiStore = new KaiStore(); diff --git a/frontend/app/components/Kai/SocketManager.ts b/frontend/app/components/Kai/SocketManager.ts index 7ba184a33..7fcda3171 100644 --- a/frontend/app/components/Kai/SocketManager.ts +++ b/frontend/app/components/Kai/SocketManager.ts @@ -1,4 +1,5 @@ import io from 'socket.io-client'; +import { toast } from 'react-toastify'; export class ChatManager { socket: ReturnType; @@ -41,6 +42,9 @@ export class ChatManager { console.log('Disconnected from server'); }); socket.on('error', (err) => { + if (err.message) { + toast.error(err.message); + } console.error('Socket error:', err); }); diff --git a/frontend/app/components/Kai/components/ChatHeader.tsx b/frontend/app/components/Kai/components/ChatHeader.tsx index 756aa9767..ae3cb4068 100644 --- a/frontend/app/components/Kai/components/ChatHeader.tsx +++ b/frontend/app/components/Kai/components/ChatHeader.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { Icon } from 'UI'; import { MessagesSquare, ArrowLeft } from 'lucide-react'; +import { useTranslation } from 'react-i18next'; function ChatHeader({ openChats = () => {}, @@ -11,6 +12,7 @@ function ChatHeader({ openChats?: () => void; chatTitle: string | null; }) { + const { t } = useTranslation(); return (
{goBack ? (
-
Back
+
{t('Back')}
) : null}
{chatTitle ? ( -
{chatTitle}
+
+ {chatTitle} +
) : ( <> @@ -38,14 +44,14 @@ function ChatHeader({ )}
-
- -
Chats
+
+
+ +
{t('Chats')}
+
); diff --git a/frontend/app/components/Kai/components/ChatInput.tsx b/frontend/app/components/Kai/components/ChatInput.tsx index 94bfeb89f..3ab630c87 100644 --- a/frontend/app/components/Kai/components/ChatInput.tsx +++ b/frontend/app/components/Kai/components/ChatInput.tsx @@ -1,55 +1,109 @@ -import React from 'react' -import { Button, Input } from "antd"; -import { SendHorizonal, OctagonX } from "lucide-react"; -import { kaiStore } from "../KaiStore"; -import { observer } from "mobx-react-lite"; +import React from 'react'; +import { Button, Input, Tooltip } from 'antd'; +import { SendHorizonal, OctagonX } from 'lucide-react'; +import { kaiStore } from '../KaiStore'; +import { observer } from 'mobx-react-lite'; +import Usage from './Usage'; -function ChatInput({ isLoading, onSubmit, threadId }: { isLoading?: boolean, onSubmit: (str: string) => void, threadId: string }) { - const inputRef = React.useRef(null); +function ChatInput({ + isLoading, + onSubmit, + threadId, +}: { + isLoading?: boolean; + onSubmit: (str: string) => void; + threadId: string; +}) { + const inputRef = React.useRef(null); + const usage = kaiStore.usage; + const limited = usage.percent >= 100; const inputValue = kaiStore.queryText; - const isProcessing = kaiStore.processingStage !== null + const isProcessing = kaiStore.processingStage !== null; const setInputValue = (text: string) => { - kaiStore.setQueryText(text) - } + kaiStore.setQueryText(text); + }; const submit = () => { + if (limited) { + return; + } if (isProcessing) { - const settings = { projectId: '2325', userId: '0', threadId, }; - void kaiStore.cancelGeneration(settings) + const settings = { projectId: '2325', userId: '0', threadId }; + void kaiStore.cancelGeneration(settings); } else { if (inputValue.length > 0) { - onSubmit(inputValue) - setInputValue('') + onSubmit(inputValue); + setInputValue(''); } } - } + }; + + const cancelReplace = () => { + setInputValue(''); + kaiStore.setReplacing(null); + }; React.useEffect(() => { if (inputRef.current) { - inputRef.current.focus() + inputRef.current.focus(); } - }, [inputValue]) + }, [inputValue]); + + const isReplacing = kaiStore.replacing !== null; return ( - setInputValue(e.target.value)} - suffix={ -
+ ); } -export default observer(ChatInput) +export default observer(ChatInput); diff --git a/frontend/app/components/Kai/components/ChatLog.tsx b/frontend/app/components/Kai/components/ChatLog.tsx index 9a8bd4f90..9b015ad22 100644 --- a/frontend/app/components/Kai/components/ChatLog.tsx +++ b/frontend/app/components/Kai/components/ChatLog.tsx @@ -1,6 +1,6 @@ import React from 'react'; import ChatInput from './ChatInput'; -import { ChatMsg, ChatNotice } from './ChatMsg'; +import ChatMsg, { ChatNotice } from './ChatMsg'; import { Loader } from 'UI'; import { kaiStore } from '../KaiStore'; import { observer } from 'mobx-react-lite'; @@ -61,17 +61,14 @@ function ChatLog({ >
{messages.map((msg, index) => ( - + + + ))} {processingStage ? ( (null); + const [loadingChart, setLoadingChart] = React.useState(false); + const { + text, + isUser, + messageId, + duration, + feedback, + supports_visualization, + chart_data, + } = message; + const isEditing = kaiStore.replacing && messageId === kaiStore.replacing; const [isProcessing, setIsProcessing] = React.useState(false); const bodyRef = React.useRef(null); - const onRetry = () => { - kaiStore.editMessage(text); + const onEdit = () => { + kaiStore.editMessage(text, messageId); }; + const onCancelEdit = () => { + kaiStore.setQueryText(''); + kaiStore.setReplacing(null); + } const onFeedback = (feedback: 'like' | 'dislike', messageId: string) => { kaiStore.sendMsgFeedback(feedback, messageId, siteId); }; @@ -74,6 +88,25 @@ export function ChatMsg({ setIsProcessing(false); }); }; + + React.useEffect(() => { + if (chart_data) { + const metric = kaiStore.getParsedChart(chart_data); + setMetric(metric); + } + }, [chart_data]); + + const getChart = async () => { + try { + setLoadingChart(true); + const metric = await kaiStore.getMessageChart(messageId, siteId); + setMetric(metric); + } catch (e) { + toast.error(e.message); + } finally { + setLoadingChart(false); + } + }; return (
{userName} @@ -92,28 +125,54 @@ export function ChatMsg({ ) : (
)}
-
+
{text}
+ {metric ? ( +
+ +
+ ) : null} {isUser ? ( - canEdit ? ( + <>
-
Edit
+
{t('Edit')}
- ) : null +
+
{t('Cancel')}
+
+ ) : (
{duration ? : null} @@ -132,6 +191,15 @@ export function ChatMsg({ > + {supports_visualization ? ( + + + + ) : null} bodyRef.current?.innerHTML} content={text} @@ -215,3 +283,5 @@ function MsgDuration({ duration }: { duration: number }) {
); } + +export default observer(ChatMsg); diff --git a/frontend/app/components/Kai/components/ChatsModal.tsx b/frontend/app/components/Kai/components/ChatsModal.tsx new file mode 100644 index 000000000..a39d358f0 --- /dev/null +++ b/frontend/app/components/Kai/components/ChatsModal.tsx @@ -0,0 +1,136 @@ +import React from 'react'; +import { splitByDate } from '../utils'; +import { useQuery } from '@tanstack/react-query'; +import { MessagesSquare, Trash } from 'lucide-react'; +import { kaiService } from 'App/services'; +import { toast } from 'react-toastify'; +import { useTranslation } from 'react-i18next'; +import { kaiStore } from '../KaiStore'; +import { observer } from 'mobx-react-lite'; + +function ChatsModal({ + onSelect, + projectId, +}: { + onSelect: (threadId: string, title: string) => void; + projectId: string; +}) { + const { t } = useTranslation(); + const { usage } = kaiStore; + const { + data = [], + isPending, + refetch, + } = useQuery({ + queryKey: ['kai', 'chats', projectId], + queryFn: () => kaiService.getKaiChats(projectId), + staleTime: 1000 * 60, + }); + + React.useEffect(() => { + kaiStore.checkUsage(); + }, []); + + const datedCollections = React.useMemo(() => { + return data.length ? splitByDate(data) : []; + }, [data.length]); + + const onDelete = async (id: string) => { + try { + await kaiService.deleteKaiChat(projectId, id); + } catch (e) { + toast.error("Something wen't wrong. Please try again later."); + } + refetch(); + }; + return ( +
+
+ + {t('Chats')} +
+ {usage.percent > 80 ? ( +
+ {t('You have used {{used}} out of {{total}} daily requests', { + used: usage.used, + total: usage.total, + })} +
+ ) : null} + {isPending ? ( +
{t('Loading chats')}...
+ ) : ( +
+ {datedCollections.map((col) => ( + + ))} +
+ )} +
+ ); +} + +function ChatCollection({ + data, + onSelect, + onDelete, + date, +}: { + data: { title: string; thread_id: string }[]; + onSelect: (threadId: string, title: string) => void; + onDelete: (threadId: string) => void; + date: string; +}) { + return ( +
+
{date}
+ +
+ ); +} + +function ChatsList({ + data, + onSelect, + onDelete, +}: { + data: { title: string; thread_id: string }[]; + onSelect: (threadId: string, title: string) => void; + onDelete: (threadId: string) => void; +}) { + return ( +
+ {data.map((chat) => ( +
+
+
onSelect(chat.thread_id, chat.title)} + className="cursor-pointer hover:underline truncate" + > + {chat.title} +
+
+
onDelete(chat.thread_id)} + className="cursor-pointer opacity-0 group-hover:opacity-100 rounded-r min-h-7 h-full px-2 flex items-center group-hover:bg-active-blue" + > + +
+
+ ))} +
+ ); +} + +export default observer(ChatsModal); diff --git a/frontend/app/components/Kai/components/Usage.tsx b/frontend/app/components/Kai/components/Usage.tsx new file mode 100644 index 000000000..9870c2b87 --- /dev/null +++ b/frontend/app/components/Kai/components/Usage.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import { kaiStore } from '../KaiStore'; +import { observer } from 'mobx-react-lite'; +import { Progress, Tooltip } from 'antd'; +const getUsageColor = (percent: number) => { + return 'disabled-text'; +}; + +function Usage() { + const { usage } = kaiStore; + const color = getUsageColor(usage.percent); + + if (usage.total === 0) { + return null; + } + return ( +
+ + + +
+ ); +} + +export default observer(Usage); diff --git a/frontend/app/components/Kai/utils.ts b/frontend/app/components/Kai/utils.ts new file mode 100644 index 000000000..691ad785f --- /dev/null +++ b/frontend/app/components/Kai/utils.ts @@ -0,0 +1,36 @@ +import { DateTime } from 'luxon'; + +type DatedEntry = { + date: string; + entries: { datetime: string }[]; +} + +export function splitByDate(entries: { datetime: string }[]) { + const today = DateTime.now().startOf('day'); + const yesterday = today.minus({ days: 1 }); + + const result: DatedEntry[] = [ + { date: 'Today', entries: [] }, + { date: 'Yesterday', entries: [] }, + ]; + + entries.forEach((ent) => { + const entryDate = DateTime.fromISO(ent.datetime).startOf('day'); + + if (entryDate.toMillis() === today.toMillis()) { + result[0].entries.push(ent); + } else if (entryDate.toMillis() === yesterday.toMillis()) { + result[1].entries.push(ent); + } else { + const date = entryDate.toFormat('dd LLL, yyyy') + const existingEntry = result.find((r) => r.date === date); + if (existingEntry) { + existingEntry.entries.push(ent); + } else { + result.push({ entries: [ent], date }); + } + } + }); + + return result; +} From eab0f60734192dfc030610d32d8cd1cc12abb757 Mon Sep 17 00:00:00 2001 From: nick-delirium Date: Wed, 21 May 2025 16:29:21 +0200 Subject: [PATCH 21/22] ui: hide empty dates from kai chats list --- frontend/app/components/Kai/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/app/components/Kai/utils.ts b/frontend/app/components/Kai/utils.ts index 691ad785f..ea581217e 100644 --- a/frontend/app/components/Kai/utils.ts +++ b/frontend/app/components/Kai/utils.ts @@ -32,5 +32,5 @@ export function splitByDate(entries: { datetime: string }[]) { } }); - return result; + return result.filter((r) => r.entries.length > 0); } From 07cc0f09392a29e29ec69f48a971129e23f87211 Mon Sep 17 00:00:00 2001 From: nick-delirium Date: Wed, 21 May 2025 16:41:03 +0200 Subject: [PATCH 22/22] ui: disable input on limit --- frontend/app/components/Kai/components/ChatInput.tsx | 7 ++++++- frontend/app/theme/colors.js | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/frontend/app/components/Kai/components/ChatInput.tsx b/frontend/app/components/Kai/components/ChatInput.tsx index 3ab630c87..237b79d6c 100644 --- a/frontend/app/components/Kai/components/ChatInput.tsx +++ b/frontend/app/components/Kai/components/ChatInput.tsx @@ -61,8 +61,13 @@ function ChatInput({ } }} ref={inputRef} - placeholder={'Ask anything about your product and users...'} + placeholder={ + limited + ? `You've reached the daily limit for queries, come again tomorrow!` + : 'Ask anything about your product and users...' + } size={'large'} + disabled={limited} value={inputValue} onChange={(e) => setInputValue(e.target.value)} suffix={ diff --git a/frontend/app/theme/colors.js b/frontend/app/theme/colors.js index 30408d5d3..ae82642dc 100644 --- a/frontend/app/theme/colors.js +++ b/frontend/app/theme/colors.js @@ -57,6 +57,7 @@ module.exports = { 'indigo-lightest': 'oklch(96.2% 0.018 272.314)', 'indigo': 'oklch(58.5% 0.233 277.117)', + /** DEPRECATED */ figmaColors: { 'accent-secondary': 'rgba(62, 170, 175, 1)', main: 'rgba(57, 78, 255, 1)',