From 8418a8fd63275e42f96b58a9f287b7e2072e1060 Mon Sep 17 00:00:00 2001 From: Taha Yassine Kraiem Date: Fri, 16 Dec 2022 10:32:17 +0100 Subject: [PATCH] feat(chalice): cards --- ee/api/chalicelib/core/dashboards.py | 128 +++------------------------ ee/api/routers/subs/metrics.py | 113 +++++++++++++---------- 2 files changed, 78 insertions(+), 163 deletions(-) diff --git a/ee/api/chalicelib/core/dashboards.py b/ee/api/chalicelib/core/dashboards.py index 25b1551d3..6f32093d2 100644 --- a/ee/api/chalicelib/core/dashboards.py +++ b/ee/api/chalicelib/core/dashboards.py @@ -13,44 +13,6 @@ if config("EXP_METRICS", cast=bool, default=False): else: from . import metrics as metrics -# category name should be lower cased -CATEGORY_DESCRIPTION = { - 'web vitals': 'A set of metrics that assess app performance on criteria such as load time, load performance, and stability.', - 'custom': 'Previously created custom metrics by me and my team.', - 'errors': 'Keep a closer eye on errors and track their type, origin and domain.', - 'performance': 'Optimize your app’s performance by tracking slow domains, page response times, memory consumption, CPU usage and more.', - 'resources': 'Find out which resources are missing and those that may be slowing your web app.' -} - - -def get_templates(project_id, user_id): - with pg_client.PostgresClient() as cur: - pg_query = cur.mogrify(f"""SELECT category, jsonb_agg(metrics ORDER BY name) AS widgets - FROM (SELECT * , default_config AS config - FROM metrics LEFT JOIN LATERAL (SELECT COALESCE(jsonb_agg(metric_series.* ORDER BY index), '[]'::jsonb) AS series - FROM metric_series - WHERE metric_series.metric_id = metrics.metric_id - AND metric_series.deleted_at ISNULL - ) AS metric_series ON (TRUE) - WHERE deleted_at IS NULL - AND (project_id ISNULL OR (project_id = %(project_id)s AND (is_public OR user_id= %(userId)s))) - ) AS metrics - GROUP BY category - ORDER BY ARRAY_POSITION(ARRAY ['custom','overview','errors','performance','resources'], category);""", - {"project_id": project_id, "userId": user_id}) - cur.execute(pg_query) - rows = cur.fetchall() - for r in rows: - r["description"] = CATEGORY_DESCRIPTION.get(r["category"].lower(), "") - for w in r["widgets"]: - w["created_at"] = TimeUTC.datetime_to_timestamp(w["created_at"]) - w["edited_at"] = TimeUTC.datetime_to_timestamp(w["edited_at"]) - for s in w["series"]: - s["filter"] = helper.old_search_payload_to_flat(s["filter"]) - - return helper.list_to_camel_case(rows) - - def create_dashboard(project_id, user_id, data: schemas.CreateDashboardSchema): with pg_client.PostgresClient() as cur: pg_query = f"""INSERT INTO dashboards(project_id, user_id, name, is_public, is_pinned, description) @@ -250,86 +212,18 @@ def pin_dashboard(project_id, user_id, dashboard_id): return helper.dict_to_camel_case(row) -def create_metric_add_widget(project_id, user_id, dashboard_id, data: schemas.CreateCustomMetricsSchema): +def create_metric_add_widget(project_id, user_id, dashboard_id, data: schemas.CreateCardSchema): metric_id = custom_metrics.create(project_id=project_id, user_id=user_id, data=data, dashboard=True) return add_widget(project_id=project_id, user_id=user_id, dashboard_id=dashboard_id, data=schemas.AddWidgetToDashboardPayloadSchema(metricId=metric_id)) - -PREDEFINED = {schemas.TemplatePredefinedKeys.count_sessions: metrics.get_processed_sessions, - schemas.TemplatePredefinedKeys.avg_image_load_time: metrics.get_application_activity_avg_image_load_time, - schemas.TemplatePredefinedKeys.avg_page_load_time: metrics.get_application_activity_avg_page_load_time, - schemas.TemplatePredefinedKeys.avg_request_load_time: metrics.get_application_activity_avg_request_load_time, - schemas.TemplatePredefinedKeys.avg_dom_content_load_start: metrics.get_page_metrics_avg_dom_content_load_start, - schemas.TemplatePredefinedKeys.avg_first_contentful_pixel: metrics.get_page_metrics_avg_first_contentful_pixel, - schemas.TemplatePredefinedKeys.avg_visited_pages: metrics.get_user_activity_avg_visited_pages, - schemas.TemplatePredefinedKeys.avg_session_duration: metrics.get_user_activity_avg_session_duration, - schemas.TemplatePredefinedKeys.avg_pages_dom_buildtime: metrics.get_pages_dom_build_time, - schemas.TemplatePredefinedKeys.avg_pages_response_time: metrics.get_pages_response_time, - schemas.TemplatePredefinedKeys.avg_response_time: metrics.get_top_metrics_avg_response_time, - schemas.TemplatePredefinedKeys.avg_first_paint: metrics.get_top_metrics_avg_first_paint, - schemas.TemplatePredefinedKeys.avg_dom_content_loaded: metrics.get_top_metrics_avg_dom_content_loaded, - schemas.TemplatePredefinedKeys.avg_till_first_bit: metrics.get_top_metrics_avg_till_first_bit, - schemas.TemplatePredefinedKeys.avg_time_to_interactive: metrics.get_top_metrics_avg_time_to_interactive, - schemas.TemplatePredefinedKeys.count_requests: metrics.get_top_metrics_count_requests, - schemas.TemplatePredefinedKeys.avg_time_to_render: metrics.get_time_to_render, - schemas.TemplatePredefinedKeys.avg_used_js_heap_size: metrics.get_memory_consumption, - schemas.TemplatePredefinedKeys.avg_cpu: metrics.get_avg_cpu, - schemas.TemplatePredefinedKeys.avg_fps: metrics.get_avg_fps, - schemas.TemplatePredefinedKeys.impacted_sessions_by_js_errors: metrics.get_impacted_sessions_by_js_errors, - schemas.TemplatePredefinedKeys.domains_errors_4xx: metrics.get_domains_errors_4xx, - schemas.TemplatePredefinedKeys.domains_errors_5xx: metrics.get_domains_errors_5xx, - schemas.TemplatePredefinedKeys.errors_per_domains: metrics.get_errors_per_domains, - schemas.TemplatePredefinedKeys.calls_errors: metrics.get_calls_errors, - schemas.TemplatePredefinedKeys.errors_by_type: metrics.get_errors_per_type, - schemas.TemplatePredefinedKeys.errors_by_origin: metrics.get_resources_by_party, - schemas.TemplatePredefinedKeys.speed_index_by_location: metrics.get_speed_index_location, - schemas.TemplatePredefinedKeys.slowest_domains: metrics.get_slowest_domains, - schemas.TemplatePredefinedKeys.sessions_per_browser: metrics.get_sessions_per_browser, - schemas.TemplatePredefinedKeys.time_to_render: metrics.get_time_to_render, - schemas.TemplatePredefinedKeys.impacted_sessions_by_slow_pages: metrics.get_impacted_sessions_by_slow_pages, - schemas.TemplatePredefinedKeys.memory_consumption: metrics.get_memory_consumption, - schemas.TemplatePredefinedKeys.cpu_load: metrics.get_avg_cpu, - schemas.TemplatePredefinedKeys.frame_rate: metrics.get_avg_fps, - schemas.TemplatePredefinedKeys.crashes: metrics.get_crashes, - schemas.TemplatePredefinedKeys.resources_vs_visually_complete: metrics.get_resources_vs_visually_complete, - schemas.TemplatePredefinedKeys.pages_dom_buildtime: metrics.get_pages_dom_build_time, - schemas.TemplatePredefinedKeys.pages_response_time: metrics.get_pages_response_time, - schemas.TemplatePredefinedKeys.pages_response_time_distribution: metrics.get_pages_response_time_distribution, - schemas.TemplatePredefinedKeys.missing_resources: metrics.get_missing_resources_trend, - schemas.TemplatePredefinedKeys.slowest_resources: metrics.get_slowest_resources, - schemas.TemplatePredefinedKeys.resources_fetch_time: metrics.get_resources_loading_time, - schemas.TemplatePredefinedKeys.resource_type_vs_response_end: metrics.resource_type_vs_response_end, - schemas.TemplatePredefinedKeys.resources_count_by_type: metrics.get_resources_count_by_type, - } - - -def get_predefined_metric(key: schemas.TemplatePredefinedKeys, project_id: int, data: dict): - return PREDEFINED.get(key, lambda *args: None)(project_id=project_id, **data) - - -def make_chart_metrics(project_id, user_id, metric_id, data: schemas.CustomMetricChartPayloadSchema): - raw_metric = custom_metrics.get_with_template(metric_id=metric_id, project_id=project_id, user_id=user_id, - include_dashboard=False) - if raw_metric is None: - return None - metric: schemas.CustomMetricAndTemplate = schemas.CustomMetricAndTemplate.parse_obj(raw_metric) - if metric.is_template and metric.predefined_key is None: - return None - if metric.is_template: - return get_predefined_metric(key=metric.predefined_key, project_id=project_id, data=data.dict()) - else: - return custom_metrics.make_chart(project_id=project_id, user_id=user_id, metric_id=metric_id, data=data, - metric=raw_metric) - - -def make_chart_widget(dashboard_id, project_id, user_id, widget_id, data: schemas.CustomMetricChartPayloadSchema): - raw_metric = get_widget(widget_id=widget_id, project_id=project_id, user_id=user_id, dashboard_id=dashboard_id) - if raw_metric is None: - return None - metric = schemas.CustomMetricAndTemplate = schemas.CustomMetricAndTemplate.parse_obj(raw_metric) - if metric.is_template: - return get_predefined_metric(key=metric.predefined_key, project_id=project_id, data=data.dict()) - else: - return custom_metrics.make_chart(project_id=project_id, user_id=user_id, metric_id=raw_metric["metricId"], - data=data, metric=raw_metric) +# def make_chart_widget(dashboard_id, project_id, user_id, widget_id, data: schemas.CardChartSchema): +# raw_metric = get_widget(widget_id=widget_id, project_id=project_id, user_id=user_id, dashboard_id=dashboard_id) +# if raw_metric is None: +# return None +# metric = schemas.CustomMetricAndTemplate = schemas.CustomMetricAndTemplate(**raw_metric) +# if metric.is_template: +# return get_predefined_metric(key=metric.predefined_key, project_id=project_id, data=data.dict()) +# else: +# return custom_metrics.make_chart(project_id=project_id, user_id=user_id, metric_id=raw_metric["metricId"], +# data=data, metric=raw_metric) diff --git a/ee/api/routers/subs/metrics.py b/ee/api/routers/subs/metrics.py index 2d296251b..eef9b4ac8 100644 --- a/ee/api/routers/subs/metrics.py +++ b/ee/api/routers/subs/metrics.py @@ -47,11 +47,12 @@ def pin_dashboard(projectId: int, dashboardId: int, context: schemas.CurrentCont return {"data": dashboards.pin_dashboard(project_id=projectId, user_id=context.user_id, dashboard_id=dashboardId)} +@app.post('/{projectId}/dashboards/{dashboardId}/cards', tags=["cards"]) @app.post('/{projectId}/dashboards/{dashboardId}/widgets', tags=["dashboard"]) @app.put('/{projectId}/dashboards/{dashboardId}/widgets', tags=["dashboard"]) -def add_widget_to_dashboard(projectId: int, dashboardId: int, - data: schemas.AddWidgetToDashboardPayloadSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +def add_card_to_dashboard(projectId: int, dashboardId: int, + data: schemas.AddWidgetToDashboardPayloadSchema = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): return {"data": dashboards.add_widget(project_id=projectId, user_id=context.user_id, dashboard_id=dashboardId, data=data)} @@ -59,7 +60,7 @@ def add_widget_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.CreateCustomMetricsSchema = Body(...), + data: schemas.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)} @@ -81,43 +82,41 @@ def remove_widget_from_dashboard(projectId: int, dashboardId: int, widgetId: int widget_id=widgetId) -@app.post('/{projectId}/dashboards/{dashboardId}/widgets/{widgetId}/chart', tags=["dashboard"]) -def get_widget_chart(projectId: int, dashboardId: int, widgetId: int, - data: schemas.CustomMetricChartPayloadSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): - data = dashboards.make_chart_widget(project_id=projectId, user_id=context.user_id, dashboard_id=dashboardId, - widget_id=widgetId, data=data) - if data is None: - return {"errors": ["widget not found"]} - return {"data": data} - - -@app.get('/{projectId}/metrics/templates', tags=["dashboard"]) -def get_templates(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): - return {"data": dashboards.get_templates(project_id=projectId, user_id=context.user_id)} +# @app.post('/{projectId}/dashboards/{dashboardId}/widgets/{widgetId}/chart', tags=["dashboard"]) +# def get_widget_chart(projectId: int, dashboardId: int, widgetId: int, +# data: schemas.CardChartSchema = Body(...), +# context: schemas.CurrentContext = Depends(OR_context)): +# data = dashboards.make_chart_widget(project_id=projectId, user_id=context.user_id, dashboard_id=dashboardId, +# widget_id=widgetId, data=data) +# if data is None: +# return {"errors": ["widget not found"]} +# return {"data": data} +@app.post('/{projectId}/cards/try', tags=["cards"]) @app.post('/{projectId}/metrics/try', tags=["dashboard"]) @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_custom_metric(projectId: int, data: schemas.TryCustomMetricsPayloadSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +def try_card(projectId: int, data: schemas.CreateCardSchema = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): return {"data": custom_metrics.merged_live(project_id=projectId, data=data, user_id=context.user_id)} +@app.post('/{projectId}/cards/try/sessions', tags=["cards"]) @app.post('/{projectId}/metrics/try/sessions', tags=["dashboard"]) @app.post('/{projectId}/custom_metrics/try/sessions', tags=["customMetrics"]) -def try_custom_metric_sessions(projectId: int, data: schemas.CustomMetricSessionsPayloadSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +def try_card_sessions(projectId: int, data: schemas.CardSessionsSchema = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): data = custom_metrics.try_sessions(project_id=projectId, user_id=context.user_id, data=data) return {"data": data} +@app.post('/{projectId}/card/try/issues', tags=["cards"]) @app.post('/{projectId}/metrics/try/issues', tags=["dashboard"]) @app.post('/{projectId}/custom_metrics/try/issues', tags=["customMetrics"]) -def try_custom_metric_funnel_issues(projectId: int, data: schemas.CustomMetricSessionsPayloadSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +def try_card_funnel_issues(projectId: int, data: schemas.CardSessionsSchema = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): if len(data.series) == 0: return {"data": []} data.series[0].filter.startDate = data.startTimestamp @@ -126,46 +125,67 @@ def try_custom_metric_funnel_issues(projectId: int, data: schemas.CustomMetricSe return {"data": data} +@app.get('/{projectId}/cards', tags=["cards"]) +@app.get('/{projectId}/metrics', tags=["dashboard"]) +@app.get('/{projectId}/custom_metrics', tags=["customMetrics"]) +def get_cards(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): + return {"data": custom_metrics.get_all(project_id=projectId, user_id=context.user_id)} + + +@app.post('/{projectId}/cards', tags=["cards"]) @app.post('/{projectId}/metrics', tags=["dashboard"]) @app.put('/{projectId}/metrics', tags=["dashboard"]) @app.post('/{projectId}/custom_metrics', tags=["customMetrics"]) @app.put('/{projectId}/custom_metrics', tags=["customMetrics"]) -def add_custom_metric(projectId: int, data: schemas.CreateCustomMetricsSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +def create_card(projectId: int, data: schemas.CreateCardSchema = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): return custom_metrics.create(project_id=projectId, user_id=context.user_id, data=data) -@app.get('/{projectId}/metrics', tags=["dashboard"]) -@app.get('/{projectId}/custom_metrics', tags=["customMetrics"]) -def get_custom_metrics(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): - return {"data": custom_metrics.get_all(project_id=projectId, user_id=context.user_id)} +@app.post('/{projectId}/cards/search', tags=["cards"]) +@app.post('/{projectId}/metrics/search', tags=["dashboard"]) +@app.post('/{projectId}/custom_metrics/search', tags=["customMetrics"]) +def search_cards(projectId: int, data: schemas.SearchCardsSchema = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): + return {"data": custom_metrics.search_all(project_id=projectId, user_id=context.user_id, data=data)} +@app.get('/{projectId}/cards/{metric_id}', tags=["cards"]) @app.get('/{projectId}/metrics/{metric_id}', tags=["dashboard"]) @app.get('/{projectId}/custom_metrics/{metric_id}', tags=["customMetrics"]) -def get_custom_metric(projectId: int, metric_id: str, context: schemas.CurrentContext = Depends(OR_context)): - data = custom_metrics.get(project_id=projectId, user_id=context.user_id, metric_id=metric_id) +def get_card(projectId: int, metric_id: str, context: schemas.CurrentContext = Depends(OR_context)): + if not isinstance(metric_id, int): + return {"errors": ["invalid card_id"]} + data = custom_metrics.get_card(project_id=projectId, user_id=context.user_id, metric_id=metric_id) if data is None: - return {"errors": ["custom metric not found"]} + return {"errors": ["card not found"]} return {"data": data} +@app.get('/{projectId}/cards/{metric_id}/thumbnail', tags=["cards"]) +def sign_thumbnail_for_upload(projectId: int, metric_id: int, + context: schemas.CurrentContext = Depends(OR_context)): + return custom_metrics.add_thumbnail(metric_id=metric_id, user_id=context.user_id, project_id=projectId) + + +@app.post('/{projectId}/cards/{metric_id}/sessions', tags=["cards"]) @app.post('/{projectId}/metrics/{metric_id}/sessions', tags=["dashboard"]) @app.post('/{projectId}/custom_metrics/{metric_id}/sessions', tags=["customMetrics"]) -def get_custom_metric_sessions(projectId: int, metric_id: int, - data: schemas.CustomMetricSessionsPayloadSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +def get_card_sessions(projectId: int, metric_id: int, + data: schemas.CardSessionsSchema = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): data = custom_metrics.get_sessions(project_id=projectId, user_id=context.user_id, metric_id=metric_id, data=data) if data is None: return {"errors": ["custom metric not found"]} return {"data": data} +@app.post('/{projectId}/cards/{metric_id}/issues', tags=["cards"]) @app.post('/{projectId}/metrics/{metric_id}/issues', tags=["dashboard"]) @app.post('/{projectId}/custom_metrics/{metric_id}/issues', tags=["customMetrics"]) -def get_custom_metric_funnel_issues(projectId: int, metric_id: int, - data: schemas.CustomMetricSessionsPayloadSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +def get_card_funnel_issues(projectId: int, metric_id: int, + data: schemas.CardSessionsSchema = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): data = custom_metrics.get_funnel_issues(project_id=projectId, user_id=context.user_id, metric_id=metric_id, data=data) if data is None: @@ -176,7 +196,7 @@ def get_custom_metric_funnel_issues(projectId: int, metric_id: int, @app.post('/{projectId}/metrics/{metric_id}/issues/{issueId}/sessions', tags=["dashboard"]) @app.post('/{projectId}/custom_metrics/{metric_id}/issues/{issueId}/sessions', tags=["customMetrics"]) def get_metric_funnel_issue_sessions(projectId: int, metric_id: int, issueId: str, - data: schemas.CustomMetricSessionsPayloadSchema = Body(...), + data: schemas.CardSessionsSchema = Body(...), context: schemas.CurrentContext = Depends(OR_context)): data = custom_metrics.get_funnel_sessions_by_issue(project_id=projectId, user_id=context.user_id, metric_id=metric_id, issue_id=issueId, data=data) @@ -188,7 +208,7 @@ def get_metric_funnel_issue_sessions(projectId: int, metric_id: int, issueId: st @app.post('/{projectId}/metrics/{metric_id}/errors', tags=["dashboard"]) @app.post('/{projectId}/custom_metrics/{metric_id}/errors', tags=["customMetrics"]) def get_custom_metric_errors_list(projectId: int, metric_id: int, - data: schemas.CustomMetricSessionsPayloadSchema = Body(...), + data: schemas.CardSessionsSchema = Body(...), context: schemas.CurrentContext = Depends(OR_context)): data = custom_metrics.get_errors_list(project_id=projectId, user_id=context.user_id, metric_id=metric_id, data=data) @@ -197,12 +217,13 @@ def get_custom_metric_errors_list(projectId: int, metric_id: int, return {"data": data} +@app.post('/{projectId}/cards/{metric_id}/chart', tags=["card"]) @app.post('/{projectId}/metrics/{metric_id}/chart', tags=["dashboard"]) @app.post('/{projectId}/custom_metrics/{metric_id}/chart', tags=["customMetrics"]) -def get_custom_metric_chart(projectId: int, metric_id: int, data: schemas.CustomMetricChartPayloadSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): - data = dashboards.make_chart_metrics(project_id=projectId, user_id=context.user_id, metric_id=metric_id, - data=data) +def get_card_chart(projectId: int, metric_id: int, data: schemas.CardChartSchema = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): + data = custom_metrics.make_chart_from_card(project_id=projectId, user_id=context.user_id, metric_id=metric_id, + data=data) if data is None: return {"errors": ["custom metric not found"]} return {"data": data} @@ -212,7 +233,7 @@ def get_custom_metric_chart(projectId: int, metric_id: int, data: schemas.Custom @app.put('/{projectId}/metrics/{metric_id}', tags=["dashboard"]) @app.post('/{projectId}/custom_metrics/{metric_id}', tags=["customMetrics"]) @app.put('/{projectId}/custom_metrics/{metric_id}', tags=["customMetrics"]) -def update_custom_metric(projectId: int, metric_id: int, data: schemas.UpdateCustomMetricsSchema = Body(...), +def update_custom_metric(projectId: int, metric_id: int, data: schemas.UpdateCardSchema = Body(...), context: schemas.CurrentContext = Depends(OR_context)): data = custom_metrics.update(project_id=projectId, user_id=context.user_id, metric_id=metric_id, data=data) if data is None: