feat(chalice): search sessions by global properties

feat(chalice): search sessions by negative global properties
This commit is contained in:
Taha Yassine Kraiem 2025-06-04 16:45:45 +02:00
parent 51b838a2b4
commit fb9005d20d
2 changed files with 44 additions and 2 deletions

View file

@ -410,7 +410,8 @@ def search_query_parts_ch(data: schemas.SessionsSearchPayloadSchema, error_statu
], ],
"operator": "is" "operator": "is"
})) }))
global_properties = []
global_properties_negative = []
if len(data.filters) > 0: if len(data.filters) > 0:
meta_keys = None meta_keys = None
# to reduce include a sub-query of sessions inside events query, in order to reduce the selected data # to reduce include a sub-query of sessions inside events query, in order to reduce the selected data
@ -429,6 +430,23 @@ def search_query_parts_ch(data: schemas.SessionsSearchPayloadSchema, error_statu
is_not = False is_not = False
if sh.is_negation_operator(f.operator): if sh.is_negation_operator(f.operator):
is_not = True is_not = True
if not f.is_predefined:
cast = get_col_cast(data_type=f.data_type, value=f.value)
if is_any:
global_properties.append(f'isNotNull(e.properties.`{f.type}`)')
else:
if is_not:
op = sh.reverse_sql_operator(op)
global_properties_negative.append(sh.multi_conditions(get_sub_condition(
col_name=f"accurateCastOrNull(e.properties.`{f.type}`,'{cast}')",
val_name=f_k, operator=op), f.value, is_not=False, value_key=f_k))
else:
global_properties.append(sh.multi_conditions(get_sub_condition(
col_name=f"accurateCastOrNull(e.properties.`{f.type}`,'{cast}')",
val_name=f_k, operator=f.operator), f.value, is_not=False, value_key=f_k))
continue
if filter_type == schemas.FilterType.USER_BROWSER: if filter_type == schemas.FilterType.USER_BROWSER:
if is_any: if is_any:
extra_constraints.append('isNotNull(s.user_browser)') extra_constraints.append('isNotNull(s.user_browser)')
@ -658,6 +676,11 @@ def search_query_parts_ch(data: schemas.SessionsSearchPayloadSchema, error_statu
events_conditions_where.append(f"""main.session_id IN (SELECT s.session_id events_conditions_where.append(f"""main.session_id IN (SELECT s.session_id
FROM {MAIN_SESSIONS_TABLE} AS s FROM {MAIN_SESSIONS_TABLE} AS s
WHERE {" AND ".join(extra_constraints)})""") WHERE {" AND ".join(extra_constraints)})""")
if len(global_properties) > 0:
global_properties += ["e.project_id=%(project_id)s",
"e.created_at >= toDateTime(%(startDate)s/1000)",
"e.created_at <= toDateTime(%(endDate)s/1000)"]
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
events_extra_join = "" events_extra_join = ""
if len(data.events) > 0: if len(data.events) > 0:
@ -1612,6 +1635,18 @@ def search_query_parts_ch(data: schemas.SessionsSearchPayloadSchema, error_statu
ORDER BY _timestamp DESC) AS s ON(s.session_id=f.session_id)""" ORDER BY _timestamp DESC) AS s ON(s.session_id=f.session_id)"""
else: else:
deduplication_keys = ["session_id"] + extra_deduplication deduplication_keys = ["session_id"] + extra_deduplication
if len(global_properties) > 0:
extra_join += f""" INNER JOIN (SELECT DISTINCT session_id
FROM {MAIN_EVENTS_TABLE} AS e
WHERE {" AND ".join(global_properties)}) AS global_filters USING(session_id)"""
if len(global_properties_negative) > 0:
extra_join += f""" LEFT JOIN (SELECT DISTINCT session_id
FROM {MAIN_EVENTS_TABLE} AS e
WHERE project_id=%(project_id)s
AND created_at >= toDateTime(%(startDate)s/1000)
AND created_at <= toDateTime(%(endDate)s/1000)
AND ({" OR ".join(global_properties_negative)})) AS negative_global_filters USING(session_id)"""
extra_constraints.append("isNull(negative_global_filters.session_id)")
extra_join = f"""(SELECT * extra_join = f"""(SELECT *
FROM {MAIN_SESSIONS_TABLE} AS s {extra_join} {extra_event} FROM {MAIN_SESSIONS_TABLE} AS s {extra_join} {extra_event}
WHERE {" AND ".join(extra_constraints)} WHERE {" AND ".join(extra_constraints)}

View file

@ -682,13 +682,20 @@ class SessionSearchEventSchema(BaseModel):
class SessionSearchFilterSchema(BaseModel): class SessionSearchFilterSchema(BaseModel):
is_event: Literal[False] = False is_event: Literal[False] = False
value: List[Union[IssueType, PlatformType, int, str]] = Field(default_factory=list) value: List[Union[IssueType, PlatformType, int, str]] = Field(default_factory=list)
type: FilterType = Field(...) type: Union[FilterType, str] = Field(...)
operator: Union[SearchEventOperator, MathOperator] = Field(...) operator: Union[SearchEventOperator, MathOperator] = Field(...)
source: Optional[Union[ErrorSource, str]] = Field(default=None) source: Optional[Union[ErrorSource, str]] = Field(default=None)
# used for global-properties
data_type: Optional[PropertyType] = Field(default=PropertyType.STRING.value)
_remove_duplicate_values = field_validator('value', mode='before')(remove_duplicate_values) _remove_duplicate_values = field_validator('value', mode='before')(remove_duplicate_values)
_single_to_list_values = field_validator('value', mode='before')(single_to_list) _single_to_list_values = field_validator('value', mode='before')(single_to_list)
@computed_field
@property
def is_predefined(self) -> bool:
return FilterType.has_value(self.type)
@model_validator(mode="before") @model_validator(mode="before")
@classmethod @classmethod
def _transform_data(cls, values): def _transform_data(cls, values):