diff --git a/api/chalicelib/core/sessions.py b/api/chalicelib/core/sessions.py index 70858481c..509607ddc 100644 --- a/api/chalicelib/core/sessions.py +++ b/api/chalicelib/core/sessions.py @@ -192,7 +192,8 @@ def search2_pg(data: schemas.SessionsSearchPayloadSchema, project_id, user_id, f f.value = helper.values_for_operator(value=f.value, op=f.operator) f_k = f"f_value{i}" full_args = {**full_args, **_multiple_values(f.value, value_key=f_k)} - op = __get_sql_operator(f.operator) + op = __get_sql_operator(f.operator) \ + if filter_type not in [schemas.FilterType.events_count] else f.operator is_not = False if __is_negation_operator(f.operator): is_not = True @@ -288,6 +289,13 @@ def search2_pg(data: schemas.SessionsSearchPayloadSchema, project_id, user_id, f ss_constraints.append( _multiple_conditions(f"%({f_k})s {op} ANY (ms.issue_types)", f.value, is_not=is_not, value_key=f_k)) + elif filter_type == schemas.FilterType.events_count: + extra_constraints.append( + _multiple_conditions(f"s.events_count {op} %({f_k})s", f.value, is_not=is_not, + value_key=f_k)) + ss_constraints.append( + _multiple_conditions(f"ms.events_count {op} %({f_k})s", f.value, is_not=is_not, + value_key=f_k)) # --------------------------------------------------------------------------- if len(data.events) > 0: @@ -341,7 +349,7 @@ def search2_pg(data: schemas.SessionsSearchPayloadSchema, project_id, user_id, f event_where.append( _multiple_conditions(f"main.{events.event_type.INPUT.column} {op} %({e_k})s", event.value, value_key=e_k)) - if len(event.custom) > 0: + if event.custom is not None and len(event.custom) > 0: event_where.append(_multiple_conditions(f"main.value ILIKE %(custom{i})s", event.custom, value_key=f"custom{i}")) full_args = {**full_args, **_multiple_values(event.custom, value_key=f"custom{i}")} @@ -404,7 +412,7 @@ def search2_pg(data: schemas.SessionsSearchPayloadSchema, project_id, user_id, f event_where.append( _multiple_conditions(f"main.{events.event_type.INPUT_IOS.column} {op} %({e_k})s", event.value, value_key=e_k)) - if len(event.custom) > 0: + if event.custom is not None and len(event.custom) > 0: event_where.append(_multiple_conditions(f"main.value ILIKE %(custom{i})s", event.custom, value_key="custom{i}")) full_args = {**full_args, **_multiple_values(event.custom, f"custom{i}")} @@ -484,7 +492,7 @@ def search2_pg(data: schemas.SessionsSearchPayloadSchema, project_id, user_id, f if event_index == 0 or or_events: event_where += ss_constraints if is_not: - if event_index == 0: + if event_index == 0 or or_events: events_query_from.append(f"""\ (SELECT session_id, @@ -498,7 +506,7 @@ def search2_pg(data: schemas.SessionsSearchPayloadSchema, project_id, user_id, f AND start_ts >= %(startDate)s AND start_ts <= %(endDate)s AND duration IS NOT NULL - ) AS event_{event_index} {"ON(TRUE)" if event_index > 0 else ""}\ + ) {"" if or_events else ("AS event_{event_index}" + ("ON(TRUE)" if event_index > 0 else ""))}\ """) else: events_query_from.append(f"""\ diff --git a/api/schemas.py b/api/schemas.py index 47966c854..8c31b1772 100644 --- a/api/schemas.py +++ b/api/schemas.py @@ -395,6 +395,7 @@ class FilterType(str, Enum): platform = "PLATFORM" metadata = "METADATA" issue = "ISSUE" + events_count = "EVENTS_COUNT" class SearchEventOperator(str, Enum): @@ -438,7 +439,7 @@ class IssueType(str, Enum): class _SessionSearchEventRaw(BaseModel): - custom: Optional[List[Union[str, int]]] = Field(None) + custom: Optional[List[Union[int, str]]] = Field(None) customOperator: Optional[MathOperator] = Field(None) key: Optional[str] = Field(None) value: Union[str, List[str]] = Field(...) @@ -468,20 +469,29 @@ class _SessionSearchEventSchema(_SessionSearchEventRaw): class _SessionSearchFilterSchema(BaseModel): custom: Optional[List[str]] = Field(None) key: Optional[str] = Field(None) - value: Union[Optional[Union[str, int, IssueType, PlatformType]], - Optional[List[Union[str, int, IssueType, PlatformType]]]] = Field(...) + value: Union[Optional[Union[IssueType, PlatformType, int, str]], + Optional[List[Union[IssueType, PlatformType, int, str]]]] = Field(...) type: FilterType = Field(...) - operator: SearchEventOperator = Field(...) + operator: Union[SearchEventOperator, MathOperator] = Field(...) source: Optional[ErrorSource] = Field(default=ErrorSource.js_exception) @root_validator def check_card_number_omitted(cls, values): if values.get("type") == FilterType.issue: for v in values.get("value"): - assert IssueType(v) + assert isinstance(v, IssueType), f"value should be of type IssueType for {values.get('type')} filter" elif values.get("type") == FilterType.platform: for v in values.get("value"): - assert PlatformType(v) + assert isinstance(v, PlatformType), \ + f"value should be of type PlatformType for {values.get('type')} filter" + elif values.get("type") == FilterType.events_count: + assert isinstance(values.get("operator"), MathOperator), \ + f"operator should be of type MathOperator for {values.get('type')} filter" + for v in values.get("value"): + assert isinstance(v, int), f"value should be of type int for {values.get('type')} filter" + else: + assert isinstance(values.get("operator"), SearchEventOperator), \ + f"operator should be of type SearchEventOperator for {values.get('type')} filter" return values