diff --git a/api/chalicelib/core/custom_metrics.py b/api/chalicelib/core/custom_metrics.py index 0e9061a11..7c04b6c6b 100644 --- a/api/chalicelib/core/custom_metrics.py +++ b/api/chalicelib/core/custom_metrics.py @@ -2,7 +2,7 @@ import json from typing import Union import schemas -from chalicelib.core import sessions, funnels +from chalicelib.core import sessions, funnels, errors from chalicelib.utils import helper, pg_client from chalicelib.utils.TimeUTC import TimeUTC @@ -42,11 +42,16 @@ def __try_live(project_id, data: schemas.TryCustomMetricsPayloadSchema): return results -def merged_live(project_id, data: schemas.TryCustomMetricsPayloadSchema): +def merged_live(project_id, data: schemas.TryCustomMetricsPayloadSchema, user_id=None): if data.metric_type == schemas.MetricType.funnel: if len(data.series) == 0: return {} return funnels.get_top_insights_on_the_fly_widget(project_id=project_id, data=data.series[0].filter) + elif data.metric_type == schemas.MetricType.table \ + and data.metric_of == schemas.TableMetricOfType.issues \ + and len(data.metric_value) == 1 and data.metric_value[0] == schemas.IssueType.js_exception \ + and data.metric_format == schemas.MetricFormatType.errors_list: + return errors.search(data.series[0].filter, project_id=project_id, user_id=user_id) series_charts = __try_live(project_id=project_id, data=data) if data.view_type == schemas.MetricTimeseriesViewType.progress or data.metric_type == schemas.MetricType.table: @@ -129,6 +134,25 @@ def get_funnel_issues(project_id, user_id, metric_id, data: schemas.CustomMetric return results +def get_errors_list(project_id, user_id, metric_id, data: schemas.CustomMetricSessionsPayloadSchema): + metric = get(metric_id=metric_id, project_id=project_id, user_id=user_id, flatten=False) + if metric is None: + return None + metric: schemas.CreateCustomMetricsSchema = __merge_metric_with_data(metric=metric, data=data) + if metric is None: + return None + results = [] + for s in metric.series: + s.filter.startDate = data.startTimestamp + s.filter.endDate = data.endTimestamp + s.filter.limit = data.limit + s.filter.page = data.page + results.append({"seriesId": s.series_id, "seriesName": s.name, + **errors.search(data=s.filter, project_id=project_id, user_id=user_id)}) + + return results + + def try_sessions(project_id, user_id, data: schemas.CustomMetricSessionsPayloadSchema): results = [] if data.series is None: diff --git a/api/chalicelib/core/errors.py b/api/chalicelib/core/errors.py index 983d091f8..2026f9232 100644 --- a/api/chalicelib/core/errors.py +++ b/api/chalicelib/core/errors.py @@ -425,10 +425,9 @@ def __get_sort_key(key): def search(data: schemas.SearchErrorsSchema, project_id, user_id, flows=False): - empty_response = {"data": { - 'total': 0, - 'errors': [] - }} + empty_response = {'total': 0, + 'errors': [] + } platform = None for f in data.filters: @@ -544,7 +543,7 @@ def search(data: schemas.SearchErrorsSchema, project_id, user_id, flows=False): rows = cur.fetchall() total = 0 if len(rows) == 0 else rows[0]["full_count"] if flows: - return {"data": {"count": total}} + return {"count": total} if total == 0: rows = [] @@ -592,10 +591,8 @@ def search(data: schemas.SearchErrorsSchema, project_id, user_id, flows=False): and (r["message"].lower() != "script error." or len(r["stack"][0]["absPath"]) > 0))] offset -= len(rows) return { - "data": { - 'total': total - offset, - 'errors': helper.list_to_camel_case(rows) - } + 'total': total - offset, + 'errors': helper.list_to_camel_case(rows) } diff --git a/api/routers/core.py b/api/routers/core.py index 5265287e6..2a38d0a75 100644 --- a/api/routers/core.py +++ b/api/routers/core.py @@ -903,7 +903,7 @@ def edit_client(data: schemas.UpdateTenantSchema = Body(...), @app.post('/{projectId}/errors/search', tags=['errors']) def errors_search(projectId: int, data: schemas.SearchErrorsSchema = Body(...), context: schemas.CurrentContext = Depends(OR_context)): - return errors.search(data, projectId, user_id=context.user_id) + return {"data": errors.search(data, projectId, user_id=context.user_id)} @app.get('/{projectId}/errors/stats', tags=['errors']) diff --git a/api/routers/subs/metrics.py b/api/routers/subs/metrics.py index 57e3b28f7..e00d2d4f7 100644 --- a/api/routers/subs/metrics.py +++ b/api/routers/subs/metrics.py @@ -102,7 +102,7 @@ def get_templates(projectId: int, context: schemas.CurrentContext = Depends(OR_c @app.put('/{projectId}/custom_metrics/try', tags=["customMetrics"]) def try_custom_metric(projectId: int, data: schemas.TryCustomMetricsPayloadSchema = Body(...), context: schemas.CurrentContext = Depends(OR_context)): - return {"data": custom_metrics.merged_live(project_id=projectId, data=data)} + return {"data": custom_metrics.merged_live(project_id=projectId, data=data, user_id=context.user_id)} @app.post('/{projectId}/metrics/try/sessions', tags=["dashboard"]) @@ -162,10 +162,23 @@ def get_custom_metric_sessions(projectId: int, metric_id: int, @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)): - data = custom_metrics.get_funnel_issues(project_id=projectId, user_id=context.user_id, metric_id=metric_id, data=data) +def get_custom_metric_funnel_issues(projectId: int, metric_id: int, + data: schemas.CustomMetricSessionsPayloadSchema = 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: + return {"errors": ["custom metric not found"]} + return {"data": data} + + +@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(...), + 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) if data is None: return {"errors": ["custom metric not found"]} return {"data": data} diff --git a/api/schemas.py b/api/schemas.py index c1979811e..d1b84e915 100644 --- a/api/schemas.py +++ b/api/schemas.py @@ -479,6 +479,11 @@ class IssueType(str, Enum): js_exception = 'js_exception' +class MetricFormatType(str, Enum): + session_count = 'sessionCount' + errors_list = 'errors' + + class __MixedSearchFilter(BaseModel): is_event: bool = Field(...) @@ -761,8 +766,7 @@ class MobileSignPayloadSchema(BaseModel): keys: List[str] = Field(...) -class CustomMetricSeriesFilterSchema(FlatSessionsSearchPayloadSchema): - # class CustomMetricSeriesFilterSchema(SessionsSearchPayloadSchema): +class CustomMetricSeriesFilterSchema(FlatSessionsSearchPayloadSchema, SearchErrorsSchema): startDate: Optional[int] = Field(None) endDate: Optional[int] = Field(None) sort: Optional[str] = Field(None) @@ -836,7 +840,7 @@ class TryCustomMetricsPayloadSchema(CustomMetricChartPayloadSchema): metric_type: MetricType = Field(MetricType.timeseries) metric_of: Union[TableMetricOfType, TimeseriesMetricOfType] = Field(TableMetricOfType.user_id) metric_value: List[IssueType] = Field([]) - metric_format: Optional[str] = Field(None) + metric_format: Optional[MetricFormatType] = Field(None) # metricFraction: float = Field(None, gt=0, lt=1) # This is used to handle wrong values sent by the UI diff --git a/ee/api/chalicelib/core/errors.py b/ee/api/chalicelib/core/errors.py index ecf1aeda2..9477f8ec7 100644 --- a/ee/api/chalicelib/core/errors.py +++ b/ee/api/chalicelib/core/errors.py @@ -83,7 +83,7 @@ def __rearrange_chart_details(start_at, end_at, density, chart): for i in range(len(chart)): chart[i] = {"timestamp": chart[i][0], "count": chart[i][1]} chart = metrics.__complete_missing_steps(rows=chart, start_time=start_at, end_time=end_at, density=density, - neutral={"count": 0}) + neutral={"count": 0}) return chart @@ -466,10 +466,9 @@ def __get_basic_constraints_pg(platform=None, time_constraint=True, startTime_ar def search(data: schemas.SearchErrorsSchema, project_id, user_id, flows=False): - empty_response = {"data": { - 'total': 0, - 'errors': [] - }} + empty_response = {'total': 0, + 'errors': [] + } platform = None for f in data.filters: @@ -585,7 +584,7 @@ def search(data: schemas.SearchErrorsSchema, project_id, user_id, flows=False): rows = cur.fetchall() total = 0 if len(rows) == 0 else rows[0]["full_count"] if flows: - return {"data": {"count": total}} + return {"count": total} if total == 0: rows = [] @@ -633,10 +632,8 @@ def search(data: schemas.SearchErrorsSchema, project_id, user_id, flows=False): and (r["message"].lower() != "script error." or len(r["stack"][0]["absPath"]) > 0))] offset -= len(rows) return { - "data": { - 'total': total - offset, - 'errors': helper.list_to_camel_case(rows) - } + 'total': total - offset, + 'errors': helper.list_to_camel_case(rows) } @@ -790,8 +787,8 @@ def search_deprecated(data: schemas.SearchErrorsSchema, project_id, user_id, flo for i in range(len(r["chart"])): r["chart"][i] = {"timestamp": r["chart"][i][0], "count": r["chart"][i][1]} r["chart"] = metrics.__complete_missing_steps(rows=r["chart"], start_time=data.startDate, - end_time=data.endDate, - density=data.density, neutral={"count": 0}) + end_time=data.endDate, + density=data.density, neutral={"count": 0}) offset = len(rows) rows = [r for r in rows if r["stack"] is None or (len(r["stack"]) == 0 or len(r["stack"]) > 1