From bb13cb191199022e6a1117de3efb433b563fe22f Mon Sep 17 00:00:00 2001 From: Taha Yassine Kraiem Date: Mon, 26 May 2025 16:56:37 +0200 Subject: [PATCH] fix(chalice): cards drilldown returns valid sessions --- api/chalicelib/core/metrics/custom_metrics.py | 6 +- api/chalicelib/core/sessions/sessions_ch.py | 4 +- .../core/sessions/sessions_search_ch.py | 67 +++++++++++++++++-- .../core/sessions/sessions_search_pg.py | 2 +- api/schemas/overrides.py | 3 +- api/schemas/schemas.py | 9 ++- 6 files changed, 78 insertions(+), 13 deletions(-) diff --git a/api/chalicelib/core/metrics/custom_metrics.py b/api/chalicelib/core/metrics/custom_metrics.py index 1df053c73..06fe705c9 100644 --- a/api/chalicelib/core/metrics/custom_metrics.py +++ b/api/chalicelib/core/metrics/custom_metrics.py @@ -172,7 +172,8 @@ def get_sessions_by_card_id(project: schemas.ProjectContext, user_id, metric_id, results = [] for s in data.series: results.append({"seriesId": s.series_id, "seriesName": s.name, - **sessions_search.search_sessions(data=s.filter, project=project, user_id=user_id)}) + **sessions_search.search_sessions(data=s.filter, project=project, user_id=user_id, + metric_of=data.metric_of)}) return results @@ -187,7 +188,8 @@ def get_sessions(project: schemas.ProjectContext, user_id, data: schemas.CardSes s.filter = schemas.SessionsSearchPayloadSchema(**s.filter.model_dump(by_alias=True)) results.append({"seriesId": None, "seriesName": s.name, - **sessions_search.search_sessions(data=s.filter, project=project, user_id=user_id)}) + **sessions_search.search_sessions(data=s.filter, project=project, user_id=user_id, + metric_of=data.metric_of)}) return results diff --git a/api/chalicelib/core/sessions/sessions_ch.py b/api/chalicelib/core/sessions/sessions_ch.py index e64555d72..69e0d5111 100644 --- a/api/chalicelib/core/sessions/sessions_ch.py +++ b/api/chalicelib/core/sessions/sessions_ch.py @@ -150,7 +150,7 @@ def search2_table(data: schemas.SessionsSearchPayloadSchema, project_id: int, de for e in data.events: if e.type == schemas.EventType.LOCATION: if e.operator not in extra_conditions: - extra_conditions[e.operator] = schemas.SessionSearchEventSchema.model_validate({ + extra_conditions[e.operator] = schemas.SessionSearchEventSchema(**{ "type": e.type, "isEvent": True, "value": [], @@ -175,7 +175,7 @@ def search2_table(data: schemas.SessionsSearchPayloadSchema, project_id: int, de for e in data.events: if e.type == schemas.EventType.REQUEST_DETAILS: if e.operator not in extra_conditions: - extra_conditions[e.operator] = schemas.SessionSearchEventSchema.model_validate({ + extra_conditions[e.operator] = schemas.SessionSearchEventSchema(**{ "type": e.type, "isEvent": True, "value": [], diff --git a/api/chalicelib/core/sessions/sessions_search_ch.py b/api/chalicelib/core/sessions/sessions_search_ch.py index c0142bae4..2b03b27b1 100644 --- a/api/chalicelib/core/sessions/sessions_search_ch.py +++ b/api/chalicelib/core/sessions/sessions_search_ch.py @@ -64,8 +64,7 @@ def __parse_metadata(metadata_map): # This function executes the query and return result def search_sessions(data: schemas.SessionsSearchPayloadSchema, project: schemas.ProjectContext, user_id, errors_only=False, error_status=schemas.ErrorStatus.ALL, - count_only=False, issue=None, ids_only=False): - platform = project.platform + count_only=False, issue=None, ids_only=False, metric_of: schemas.MetricOfTable = None): if data.bookmarked: data.startTimestamp, data.endTimestamp = sessions_favorite.get_start_end_timestamp(project.project_id, user_id) if data.startTimestamp is None: @@ -75,18 +74,78 @@ def search_sessions(data: schemas.SessionsSearchPayloadSchema, project: schemas. 'sessions': [], '_src': 2 } + # ---------------------- extra filter in order to only select sessions that has been used in the card-table + extra_event = None + # extra_deduplication = [] + extra_conditions = None + if metric_of == schemas.MetricOfTable.VISITED_URL: + extra_event = f"""SELECT DISTINCT ev.session_id, + JSONExtractString(toString(ev.`$properties`), 'url_path') AS url_path + FROM {exp_ch_helper.get_main_events_table(data.startTimestamp)} AS ev + WHERE ev.created_at >= toDateTime(%(startDate)s / 1000) + AND ev.created_at <= toDateTime(%(endDate)s / 1000) + AND ev.project_id = %(project_id)s + AND ev.`$event_name` = 'LOCATION'""" + # extra_deduplication.append("url_path") + extra_conditions = {} + for e in data.events: + if e.type == schemas.EventType.LOCATION: + if e.operator not in extra_conditions: + extra_conditions[e.operator] = schemas.SessionSearchEventSchema(**{ + "type": e.type, + "isEvent": True, + "value": [], + "operator": e.operator, + "filters": e.filters + }) + for v in e.value: + if v not in extra_conditions[e.operator].value: + extra_conditions[e.operator].value.append(v) + extra_conditions = list(extra_conditions.values()) + elif metric_of == schemas.MetricOfTable.FETCH: + extra_event = f"""SELECT DISTINCT ev.session_id + FROM {exp_ch_helper.get_main_events_table(data.startTimestamp)} AS ev + WHERE ev.created_at >= toDateTime(%(startDate)s / 1000) + AND ev.created_at <= toDateTime(%(endDate)s / 1000) + AND ev.project_id = %(project_id)s + AND ev.`$event_name` = 'REQUEST'""" + + # extra_deduplication.append("url_path") + extra_conditions = {} + for e in data.events: + if e.type == schemas.EventType.REQUEST_DETAILS: + if e.operator not in extra_conditions: + extra_conditions[e.operator] = schemas.SessionSearchEventSchema(**{ + "type": e.type, + "isEvent": True, + "value": [], + "operator": e.operator, + "filters": e.filters + }) + for v in e.value: + if v not in extra_conditions[e.operator].value: + extra_conditions[e.operator].value.append(v) + extra_conditions = list(extra_conditions.values()) + + # elif metric_of == schemas.MetricOfTable.ISSUES and len(metric_value) > 0: + # data.filters.append(schemas.SessionSearchFilterSchema(value=metric_value, type=schemas.FilterType.ISSUE, + # operator=schemas.SearchEventOperator.IS)) + # ---------------------- if project.platform == "web": full_args, query_part = sessions.search_query_parts_ch(data=data, error_status=error_status, errors_only=errors_only, favorite_only=data.bookmarked, issue=issue, project_id=project.project_id, - user_id=user_id, platform=platform) + user_id=user_id, platform=project.platform, + extra_event=extra_event, + # extra_deduplication=extra_deduplication, + extra_conditions=extra_conditions) else: full_args, query_part = sessions_legacy_mobil.search_query_parts_ch(data=data, error_status=error_status, errors_only=errors_only, favorite_only=data.bookmarked, issue=issue, project_id=project.project_id, - user_id=user_id, platform=platform) + user_id=user_id, platform=project.platform) if data.sort == "startTs": data.sort = "datetime" if data.limit is not None and data.page is not None: diff --git a/api/chalicelib/core/sessions/sessions_search_pg.py b/api/chalicelib/core/sessions/sessions_search_pg.py index 9036e2686..920ea6487 100644 --- a/api/chalicelib/core/sessions/sessions_search_pg.py +++ b/api/chalicelib/core/sessions/sessions_search_pg.py @@ -40,7 +40,7 @@ COALESCE((SELECT TRUE # This function executes the query and return result def search_sessions(data: schemas.SessionsSearchPayloadSchema, project: schemas.ProjectContext, user_id, errors_only=False, error_status=schemas.ErrorStatus.ALL, - count_only=False, issue=None, ids_only=False): + count_only=False, issue=None, ids_only=False, metric_of: schemas.MetricOfTable = None): platform = project.platform if data.bookmarked: data.startTimestamp, data.endTimestamp = sessions_favorite.get_start_end_timestamp(project.project_id, user_id) diff --git a/api/schemas/overrides.py b/api/schemas/overrides.py index cd81f1157..b543fa6b9 100644 --- a/api/schemas/overrides.py +++ b/api/schemas/overrides.py @@ -22,7 +22,8 @@ class BaseModel(_BaseModel): model_config = ConfigDict(alias_generator=attribute_to_camel_case, use_enum_values=True, json_schema_extra=schema_extra, - extra='forbid') + # extra='forbid' + ) class Enum(_Enum): diff --git a/api/schemas/schemas.py b/api/schemas/schemas.py index c138f9ca5..652b744c7 100644 --- a/api/schemas/schemas.py +++ b/api/schemas/schemas.py @@ -1043,11 +1043,16 @@ class MetricOfPathAnalysis(str, Enum): session_count = MetricOfTimeseries.SESSION_COUNT.value -# class CardSessionsSchema(SessionsSearchPayloadSchema): class CardSessionsSchema(_TimedSchema, _PaginatedSchema): startTimestamp: int = Field(default=TimeUTC.now(-7)) endTimestamp: int = Field(default=TimeUTC.now()) density: int = Field(default=7, ge=1, le=200) + # we need metric_type&metric_of in the payload of sessions search + # because the API will retrun all sessions if the card is not identified + # example: table of requests contains only sessions that have a request, + # but drill-down doesn't take that into consideration + metric_type: MetricType = Field(...) + metric_of: Any series: List[CardSeriesSchema] = Field(default_factory=list) # events: List[SessionSearchEventSchema2] = Field(default_factory=list, doc_hidden=True) @@ -1125,8 +1130,6 @@ class __CardSchema(CardSessionsSchema): thumbnail: Optional[str] = Field(default=None) metric_format: Optional[MetricFormatType] = Field(default=None) view_type: Any - metric_type: MetricType = Field(...) - metric_of: Any metric_value: List[IssueType] = Field(default_factory=list) # This is used to save the selected session for heatmaps session_id: Optional[int] = Field(default=None)