From cdc22f107e35387374af592f7b332357ee849bfe Mon Sep 17 00:00:00 2001 From: Taha Yassine Kraiem Date: Fri, 6 Jan 2023 16:11:24 +0100 Subject: [PATCH] feat(chalice): insights as cards --- api/schemas.py | 9 ++- ee/api/chalicelib/core/custom_metrics.py | 70 +++++++++++++-------- ee/api/chalicelib/core/sessions_insights.py | 2 - ee/api/routers/subs/metrics.py | 7 ++- ee/api/schemas_ee.py | 37 ++++++++++- 5 files changed, 91 insertions(+), 34 deletions(-) diff --git a/api/schemas.py b/api/schemas.py index eb24e63ff..867e284e9 100644 --- a/api/schemas.py +++ b/api/schemas.py @@ -874,6 +874,7 @@ class MetricTableViewType(str, Enum): class MetricOtherViewType(str, Enum): other_chart = "chart" + list_chart = "list" class MetricType(str, Enum): @@ -888,6 +889,7 @@ class MetricType(str, Enum): retention = "retention" stickiness = "stickiness" click_map = "clickMap" + insights = "insights" class MetricOfErrors(str, Enum): @@ -1017,6 +1019,11 @@ class CreateCardSchema(CardChartSchema): return values + @root_validator + def restrictions(cls, values): + assert values.get("metric_type") != MetricType.insights, f"metricType:{MetricType.insights} not supported yet" + return values + @root_validator def validator(cls, values): if values.get("metric_type") == MetricType.timeseries: @@ -1062,7 +1069,7 @@ class CreateCardSchema(CardChartSchema): assert f.type == EventType.location, f"only events of type:{EventType.location} are allowed for metricOf:{MetricType.click_map}" assert isinstance(values.get("view_type"), MetricOtherViewType), \ - f"viewType must be 'chart' for metricOf:{values.get('metric_of')}" + f"viewType must be 'chart|list' for metricOf:{values.get('metric_of')}" return values diff --git a/ee/api/chalicelib/core/custom_metrics.py b/ee/api/chalicelib/core/custom_metrics.py index 10cbe0dcf..61d6dea0e 100644 --- a/ee/api/chalicelib/core/custom_metrics.py +++ b/ee/api/chalicelib/core/custom_metrics.py @@ -6,7 +6,8 @@ from starlette import status from decouple import config import schemas -from chalicelib.core import funnels, issues, metrics, click_maps +import schemas_ee +from chalicelib.core import funnels, issues, metrics, click_maps, sessions_insights from chalicelib.utils import helper, pg_client from chalicelib.utils.TimeUTC import TimeUTC @@ -24,7 +25,7 @@ else: PIE_CHART_GROUP = 5 -def __try_live(project_id, data: schemas.CreateCardSchema): +def __try_live(project_id, data: schemas_ee.CreateCardSchema): results = [] for i, s in enumerate(data.series): s.filter.startDate = data.startTimestamp @@ -57,11 +58,11 @@ def __try_live(project_id, data: schemas.CreateCardSchema): return results -def __is_funnel_chart(data: schemas.CreateCardSchema): +def __is_funnel_chart(data: schemas_ee.CreateCardSchema): return data.metric_type == schemas.MetricType.funnel -def __get_funnel_chart(project_id, data: schemas.CreateCardSchema): +def __get_funnel_chart(project_id, data: schemas_ee.CreateCardSchema): if len(data.series) == 0: return { "stages": [], @@ -72,12 +73,12 @@ def __get_funnel_chart(project_id, data: schemas.CreateCardSchema): return funnels.get_top_insights_on_the_fly_widget(project_id=project_id, data=data.series[0].filter) -def __is_errors_list(data: schemas.CreateCardSchema): +def __is_errors_list(data: schemas_ee.CreateCardSchema): return data.metric_type == schemas.MetricType.table \ and data.metric_of == schemas.MetricOfTable.errors -def __get_errors_list(project_id, user_id, data: schemas.CreateCardSchema): +def __get_errors_list(project_id, user_id, data: schemas_ee.CreateCardSchema): if len(data.series) == 0: return { "total": 0, @@ -90,12 +91,12 @@ def __get_errors_list(project_id, user_id, data: schemas.CreateCardSchema): return errors.search(data.series[0].filter, project_id=project_id, user_id=user_id) -def __is_sessions_list(data: schemas.CreateCardSchema): +def __is_sessions_list(data: schemas_ee.CreateCardSchema): return data.metric_type == schemas.MetricType.table \ and data.metric_of == schemas.MetricOfTable.sessions -def __get_sessions_list(project_id, user_id, data: schemas.CreateCardSchema): +def __get_sessions_list(project_id, user_id, data: schemas_ee.CreateCardSchema): if len(data.series) == 0: print("empty series") return { @@ -109,15 +110,15 @@ def __get_sessions_list(project_id, user_id, data: schemas.CreateCardSchema): return sessions.search_sessions(data=data.series[0].filter, project_id=project_id, user_id=user_id) -def __is_predefined(data: schemas.CreateCardSchema): +def __is_predefined(data: schemas_ee.CreateCardSchema): return data.is_template -def __is_click_map(data: schemas.CreateCardSchema): +def __is_click_map(data: schemas_ee.CreateCardSchema): return data.metric_type == schemas.MetricType.click_map -def __get_click_map_chat(project_id, user_id, data: schemas.CreateCardSchema): +def __get_click_map_chat(project_id, user_id, data: schemas_ee.CreateCardSchema): if len(data.series) == 0: return None data.series[0].filter.startDate = data.startTimestamp @@ -126,7 +127,20 @@ def __get_click_map_chat(project_id, user_id, data: schemas.CreateCardSchema): data=schemas.FlatClickMapSessionsSearch(**data.series[0].filter.dict())) -def merged_live(project_id, data: schemas.CreateCardSchema, user_id=None): +# EE only +def __is_insights(data: schemas_ee.CreateCardSchema): + return data.metric_type == schemas.MetricType.insights + + +# EE only +def __get_insights_chat(project_id, user_id, data: schemas_ee.CreateCardSchema): + return sessions_insights.fetch_selected(project_id=project_id, + data=schemas_ee.GetInsightsSchema(startTimestamp=data.startTimestamp, + endTimestamp=data.endTimestamp, + categories=data.metric_value)) + + +def merged_live(project_id, data: schemas_ee.CreateCardSchema, user_id=None): if data.is_template: return get_predefined_metric(key=data.metric_of, project_id=project_id, data=data.dict()) elif __is_funnel_chart(data): @@ -137,6 +151,9 @@ def merged_live(project_id, data: schemas.CreateCardSchema, user_id=None): return __get_sessions_list(project_id=project_id, user_id=user_id, data=data) elif __is_click_map(data): return __get_click_map_chat(project_id=project_id, user_id=user_id, data=data) + # EE only + elif __is_insights(data): + return __get_insights_chat(project_id=project_id, user_id=user_id, data=data) elif len(data.series) == 0: return [] series_charts = __try_live(project_id=project_id, data=data) @@ -150,11 +167,11 @@ def merged_live(project_id, data: schemas.CreateCardSchema, user_id=None): return results -def __merge_metric_with_data(metric: schemas.CreateCardSchema, - data: schemas.CardChartSchema) -> schemas.CreateCardSchema: +def __merge_metric_with_data(metric: schemas_ee.CreateCardSchema, + data: schemas.CardChartSchema) -> schemas_ee.CreateCardSchema: if data.series is not None and len(data.series) > 0: metric.series = data.series - metric: schemas.CreateCardSchema = schemas.CreateCardSchema( + metric: schemas_ee.CreateCardSchema = schemas_ee.CreateCardSchema( **{**data.dict(by_alias=True), **metric.dict(by_alias=True)}) if len(data.filters) > 0 or len(data.events) > 0: for s in metric.series: @@ -165,12 +182,13 @@ def __merge_metric_with_data(metric: schemas.CreateCardSchema, return metric -def make_chart(project_id, user_id, metric_id, data: schemas.CardChartSchema, metric: schemas.CreateCardSchema = None): +def make_chart(project_id, user_id, metric_id, data: schemas.CardChartSchema, + metric: schemas_ee.CreateCardSchema = None): if metric is None: metric = get_card(metric_id=metric_id, project_id=project_id, user_id=user_id, flatten=False) if metric is None: return None - metric: schemas.CreateCardSchema = __merge_metric_with_data(metric=metric, data=data) + metric: schemas_ee.CreateCardSchema = __merge_metric_with_data(metric=metric, data=data) return merged_live(project_id=project_id, data=metric, user_id=user_id) @@ -179,8 +197,8 @@ def get_sessions(project_id, user_id, metric_id, data: schemas.CardSessionsSchem raw_metric = get_card(metric_id=metric_id, project_id=project_id, user_id=user_id, flatten=False) if raw_metric is None: return None - metric: schemas.CreateCardSchema = schemas.CreateCardSchema(**raw_metric) - metric: schemas.CreateCardSchema = __merge_metric_with_data(metric=metric, data=data) + metric: schemas_ee.CreateCardSchema = schemas_ee.CreateCardSchema(**raw_metric) + metric: schemas_ee.CreateCardSchema = __merge_metric_with_data(metric=metric, data=data) if metric is None: return None results = [] @@ -199,8 +217,8 @@ def get_funnel_issues(project_id, user_id, metric_id, data: schemas.CardSessions raw_metric = get_card(metric_id=metric_id, project_id=project_id, user_id=user_id, flatten=False) if raw_metric is None: return None - metric: schemas.CreateCardSchema = schemas.CreateCardSchema(**raw_metric) - metric: schemas.CreateCardSchema = __merge_metric_with_data(metric=metric, data=data) + metric: schemas_ee.CreateCardSchema = schemas_ee.CreateCardSchema(**raw_metric) + metric: schemas_ee.CreateCardSchema = __merge_metric_with_data(metric=metric, data=data) if metric is None: return None for s in metric.series: @@ -216,8 +234,8 @@ def get_errors_list(project_id, user_id, metric_id, data: schemas.CardSessionsSc raw_metric = get_card(metric_id=metric_id, project_id=project_id, user_id=user_id, flatten=False) if raw_metric is None: return None - metric: schemas.CreateCardSchema = schemas.CreateCardSchema(**raw_metric) - metric: schemas.CreateCardSchema = __merge_metric_with_data(metric=metric, data=data) + metric: schemas_ee.CreateCardSchema = schemas_ee.CreateCardSchema(**raw_metric) + metric: schemas_ee.CreateCardSchema = __merge_metric_with_data(metric=metric, data=data) if metric is None: return None for s in metric.series: @@ -244,7 +262,7 @@ def try_sessions(project_id, user_id, data: schemas.CardSessionsSchema): return results -def create(project_id, user_id, data: schemas.CreateCardSchema, dashboard=False): +def create(project_id, user_id, data: schemas_ee.CreateCardSchema, dashboard=False): with pg_client.PostgresClient() as cur: _data = {} for i, s in enumerate(data.series): @@ -570,7 +588,7 @@ def get_funnel_sessions_by_issue(user_id, project_id, metric_id, issue_id, metric = get_card(metric_id=metric_id, project_id=project_id, user_id=user_id, flatten=False) if metric is None: return None - metric: schemas.CreateCardSchema = __merge_metric_with_data(metric=metric, data=data) + metric: schemas_ee.CreateCardSchema = __merge_metric_with_data(metric=metric, data=data) if metric is None: return None for s in metric.series: @@ -606,7 +624,7 @@ def make_chart_from_card(project_id, user_id, metric_id, data: schemas.CardChart include_dashboard=False) if raw_metric is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="card not found") - metric: schemas.CreateCardSchema = schemas.CreateCardSchema(**raw_metric) + metric: schemas_ee.CreateCardSchema = schemas_ee.CreateCardSchema(**raw_metric) if metric.is_template: return get_predefined_metric(key=metric.metric_of, project_id=project_id, data=data.dict()) else: diff --git a/ee/api/chalicelib/core/sessions_insights.py b/ee/api/chalicelib/core/sessions_insights.py index 9cad92020..9cd714f3a 100644 --- a/ee/api/chalicelib/core/sessions_insights.py +++ b/ee/api/chalicelib/core/sessions_insights.py @@ -147,8 +147,6 @@ def query_requests_by_period(project_id, start_time, end_time, time_step, conn=N def query_most_errors_by_period(project_id, start_time, end_time, time_step, conn=None): function, steps = __handle_timestep(time_step) - print(function) - print(steps) query = f"""WITH {function.format(f"toDateTime64('{start_time}', 0)")} as start, {function.format(f"toDateTime64('{end_time}', 0)")} as end SELECT T1.hh, count(T2.session_id) as sessions, T2.name as names, diff --git a/ee/api/routers/subs/metrics.py b/ee/api/routers/subs/metrics.py index 24de16db3..c41b7019c 100644 --- a/ee/api/routers/subs/metrics.py +++ b/ee/api/routers/subs/metrics.py @@ -3,6 +3,7 @@ from typing import Union from fastapi import Body, Depends import schemas +import schemas_ee from chalicelib.core import dashboards, custom_metrics, funnels from or_dependencies import OR_context, OR_scope from routers.base import get_routers @@ -62,7 +63,7 @@ def add_card_to_dashboard(projectId: int, dashboardId: int, @app.post('/{projectId}/dashboards/{dashboardId}/metrics', tags=["dashboard"]) @app.put('/{projectId}/dashboards/{dashboardId}/metrics', tags=["dashboard"]) def create_metric_and_add_to_dashboard(projectId: int, dashboardId: int, - data: schemas.CreateCardSchema = Body(...), + data: schemas_ee.CreateCardSchema = Body(...), context: schemas.CurrentContext = Depends(OR_context)): return {"data": dashboards.create_metric_add_widget(project_id=projectId, user_id=context.user_id, dashboard_id=dashboardId, data=data)} @@ -100,7 +101,7 @@ def remove_widget_from_dashboard(projectId: int, dashboardId: int, widgetId: int @app.put('/{projectId}/metrics/try', tags=["dashboard"]) @app.post('/{projectId}/custom_metrics/try', tags=["customMetrics"]) @app.put('/{projectId}/custom_metrics/try', tags=["customMetrics"]) -def try_card(projectId: int, data: schemas.CreateCardSchema = Body(...), +def try_card(projectId: int, data: schemas_ee.CreateCardSchema = Body(...), context: schemas.CurrentContext = Depends(OR_context)): return {"data": custom_metrics.merged_live(project_id=projectId, data=data, user_id=context.user_id)} @@ -139,7 +140,7 @@ def get_cards(projectId: int, context: schemas.CurrentContext = Depends(OR_conte @app.put('/{projectId}/metrics', tags=["dashboard"]) @app.post('/{projectId}/custom_metrics', tags=["customMetrics"]) @app.put('/{projectId}/custom_metrics', tags=["customMetrics"]) -def create_card(projectId: int, data: schemas.CreateCardSchema = Body(...), +def create_card(projectId: int, data: schemas_ee.CreateCardSchema = Body(...), context: schemas.CurrentContext = Depends(OR_context)): return custom_metrics.create(project_id=projectId, user_id=context.user_id, data=data) diff --git a/ee/api/schemas_ee.py b/ee/api/schemas_ee.py index 0af134a7e..b08156c44 100644 --- a/ee/api/schemas_ee.py +++ b/ee/api/schemas_ee.py @@ -1,10 +1,11 @@ -from typing import Optional, List, Literal +from enum import Enum +from typing import Optional, List, Union, Literal from pydantic import BaseModel, Field, EmailStr +from pydantic import root_validator import schemas from chalicelib.utils.TimeUTC import TimeUTC -from enum import Enum class Permissions(str, Enum): @@ -134,3 +135,35 @@ class AssistRecordSearchPayloadSchema(schemas._PaginatedSchema): class Config: alias_generator = schemas.attribute_to_camel_case + + +# TODO: move these to schema when Insights is supported on PG +class MetricOfInsights(str, Enum): + issue_categories = "issueCategories" + + +class CreateCardSchema(schemas.CreateCardSchema): + metric_of: Union[schemas.MetricOfTimeseries, schemas.MetricOfTable, \ + schemas.MetricOfErrors, schemas.MetricOfPerformance, \ + schemas.MetricOfResources, schemas.MetricOfWebVitals, \ + schemas.MetricOfClickMap, MetricOfInsights] = Field(default=schemas.MetricOfTable.user_id) + metric_value: List[Union[schemas.IssueType, InsightCategories]] = Field(default=[]) + + @root_validator + def restrictions(cls, values): + return values + + @root_validator + def validator(cls, values): + values = super().validator(values) + if values.get("metric_type") == schemas.MetricType.insights: + assert values.get("view_type") == schemas.MetricOtherViewType.list_chart, \ + f"viewType must be 'list' for metricOf:{values.get('metric_of')}" + assert isinstance(values.get("metric_of"), MetricOfInsights), \ + f"metricOf must be of type {MetricOfInsights} for metricType:{schemas.MetricType.insights}" + if values.get("metric_value") is not None and len(values.get("metric_value")) > 0: + for i in values.get("metric_value"): + assert isinstance(i, InsightCategories), \ + f"metricValue should be of type [InsightCategories] for metricType:{schemas.MetricType.insights}" + + return values