From 5b6e9ab7e0f91e9d72ea2950b6bfae3f56ef263b Mon Sep 17 00:00:00 2001 From: Taha Yassine Kraiem Date: Wed, 21 May 2025 16:45:42 +0200 Subject: [PATCH] feature(chalice): search sessions by x/y coordinates feature(chalice): search heatmaps by x/y coordinates --- api/chalicelib/core/metrics/custom_metrics.py | 3 ++ api/chalicelib/core/sessions/sessions_ch.py | 29 +++++++++++++++++-- api/chalicelib/utils/sql_helper.py | 29 ++++++++++++++++++- api/schemas/schemas.py | 7 ++++- 4 files changed, 63 insertions(+), 5 deletions(-) diff --git a/api/chalicelib/core/metrics/custom_metrics.py b/api/chalicelib/core/metrics/custom_metrics.py index 2a2dd3b30..1df053c73 100644 --- a/api/chalicelib/core/metrics/custom_metrics.py +++ b/api/chalicelib/core/metrics/custom_metrics.py @@ -61,6 +61,9 @@ def get_heat_map_chart(project: schemas.ProjectContext, user_id, data: schemas.C return None data.series[0].filter.filters += data.series[0].filter.events data.series[0].filter.events = [] + print(">>>>>>>>>>>>>>>>>>>>>>>>><") + print(data.series[0].filter.model_dump()) + print(">>>>>>>>>>>>>>>>>>>>>>>>><") return heatmaps.search_short_session(project_id=project.project_id, user_id=user_id, data=schemas.HeatMapSessionsSearch( **data.series[0].filter.model_dump()), diff --git a/api/chalicelib/core/sessions/sessions_ch.py b/api/chalicelib/core/sessions/sessions_ch.py index 7851ed1c8..fb5457932 100644 --- a/api/chalicelib/core/sessions/sessions_ch.py +++ b/api/chalicelib/core/sessions/sessions_ch.py @@ -1293,9 +1293,6 @@ def search_query_parts_ch(data: schemas.SessionsSearchPayloadSchema, error_statu 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}", @@ -1317,7 +1314,33 @@ def search_query_parts_ch(data: schemas.SessionsSearchPayloadSchema, error_statu event.value, value_key=e_k) ) events_conditions[-1]["condition"] = event_where[-1] + elif event_type == schemas.EventType.CLICK_COORDINATES: + event_from = event_from % f"{MAIN_EVENTS_TABLE} AS main " + event_where.append( + f"main.`$event_name`='{exp_ch_helper.get_event_type(schemas.EventType.CLICK, platform=platform)}'") + events_conditions.append({"type": event_where[-1]}) + if is_not: + event_where.append( + sh.coordinate_conditions( + condition_x=f"sub.`$properties`.normalized_x", + condition_y=f"sub.`$properties`.normalized_y", + values=event.value, value_key=e_k, is_not=True) + ) + events_conditions_not.append( + { + "type": f"sub.`$event_name`='{exp_ch_helper.get_event_type(schemas.EventType.CLICK, platform=platform)}'" + } + ) + events_conditions_not[-1]["condition"] = event_where[-1] + else: + event_where.append( + sh.coordinate_conditions( + condition_x=f"main.`$properties`.normalized_x", + condition_y=f"main.`$properties`.normalized_y", + values=event.value, value_key=e_k, is_not=True) + ) + events_conditions[-1]["condition"] = event_where[-1] else: continue diff --git a/api/chalicelib/utils/sql_helper.py b/api/chalicelib/utils/sql_helper.py index 7290ff638..8b7cccbfb 100644 --- a/api/chalicelib/utils/sql_helper.py +++ b/api/chalicelib/utils/sql_helper.py @@ -44,7 +44,7 @@ def reverse_sql_operator(op): return "=" if op == "!=" else "!=" if op == "=" else "ILIKE" if op == "NOT ILIKE" else "NOT ILIKE" -def multi_conditions(condition, values, value_key="value", is_not=False): +def multi_conditions(condition, values, value_key="value", is_not=False) -> str: query = [] for i in range(len(values)): k = f"{value_key}_{i}" @@ -79,3 +79,30 @@ def single_value(values): if isinstance(v, Enum): values[i] = v.value return values + + +def coordinate_conditions(condition_x, condition_y, values, value_key="value", is_not=False): + query = [] + if len(values) == 2: + # if 2 values are provided, it means x=v[0] and y=v[1] + for i in range(len(values)): + k = f"{value_key}_{i}" + if i == 0: + query.append(f"{condition_x}=%({k})s") + elif i == 1: + query.append(f"{condition_y}=%({k})s") + + elif len(values) == 4: + # if 4 values are provided, it means v[0]<=x<=v[1] and v[2]<=y<=v[3] + for i in range(len(values)): + k = f"{value_key}_{i}" + if i == 0: + query.append(f"{condition_x}>=%({k})s") + elif i == 1: + query.append(f"{condition_x}<=%({k})s") + elif i == 2: + query.append(f"{condition_y}>=%({k})s") + elif i == 3: + query.append(f"{condition_y}<=%({k})s") + + return "(" + (" AND " if is_not else " OR ").join(query) + ")" diff --git a/api/schemas/schemas.py b/api/schemas/schemas.py index 6c38868a4..c138f9ca5 100644 --- a/api/schemas/schemas.py +++ b/api/schemas/schemas.py @@ -407,6 +407,7 @@ class EventType(str, Enum): SWIPE_MOBILE = "swipeMobile" EVENT = "event" INCIDENT = "incident" + CLICK_COORDINATES = "clickCoordinates" class PerformanceEventType(str, Enum): @@ -660,6 +661,10 @@ class SessionSearchEventSchema(BaseModel): elif self.type == EventType.GRAPHQL: assert isinstance(self.filters, List) and len(self.filters) > 0, \ f"filters should be defined for {EventType.GRAPHQL}" + elif self.type == EventType.CLICK_COORDINATES: + assert isinstance(self.value, List) \ + and (len(self.value) == 0 or len(self.value) == 2 or len(self.value) == 4), \ + f"value should be [x,y] or [x1,x2,y1,y2] for {EventType.CLICK_COORDINATES}" if isinstance(self.operator, ClickEventExtraOperator): assert self.type == EventType.CLICK, \ @@ -1521,7 +1526,7 @@ class MetricSearchSchema(_PaginatedSchema): class _HeatMapSearchEventRaw(SessionSearchEventSchema): - type: Literal[EventType.LOCATION] = Field(...) + type: Literal[EventType.LOCATION, EventType.CLICK_COORDINATES] = Field(...) class HeatMapSessionsSearch(SessionsSearchPayloadSchema):