diff --git a/api/chalicelib/core/projects.py b/api/chalicelib/core/projects.py index 865a4a73c..0ec2ab97c 100644 --- a/api/chalicelib/core/projects.py +++ b/api/chalicelib/core/projects.py @@ -83,7 +83,7 @@ def get_projects(tenant_id, recording_state=False, gdpr=None, recorded=False, st FROM public.projects AS s {extra_join} WHERE s.deleted_at IS NULL - ORDER BY s.project_id {") AS raw" if recorded else ""};""", {"now": TimeUTC.now()}) + ORDER BY s.name {") AS raw" if recorded else ""};""", {"now": TimeUTC.now()}) cur.execute(query) rows = cur.fetchall() # if recorded is requested, check if it was saved or computed diff --git a/api/routers/subs/health.py b/api/routers/subs/health.py index fdef52509..81e5294ea 100644 --- a/api/routers/subs/health.py +++ b/api/routers/subs/health.py @@ -6,15 +6,15 @@ from routers.base import get_routers public_app, app, app_apikey = get_routers() -@app.get('/health', tags=["health-check"]) -def get_global_health_status(): +@app.get('/healthz', tags=["health-check"]) +async def get_global_health_status(): return {"data": health.get_health()} if not tenants.tenants_exists(use_pool=False): @public_app.get('/health', tags=["health-check"]) - def get_public_health_status(): + async def get_public_health_status(): if tenants.tenants_exists(): raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Not Found") - return get_global_health_status() + return await get_global_health_status() diff --git a/api/routers/subs/insights.py b/api/routers/subs/insights.py index cce4917d4..c27e4d704 100644 --- a/api/routers/subs/insights.py +++ b/api/routers/subs/insights.py @@ -9,79 +9,79 @@ public_app, app, app_apikey = get_routers() @app.post('/{projectId}/insights/journey', tags=["insights"]) @app.get('/{projectId}/insights/journey', tags=["insights"]) -def get_insights_journey(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): +async def get_insights_journey(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): return {"data": insights.journey(project_id=projectId, **data.dict())} @app.post('/{projectId}/insights/users_acquisition', tags=["insights"]) @app.get('/{projectId}/insights/users_acquisition', tags=["insights"]) -def get_users_acquisition(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): +async def get_users_acquisition(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): return {"data": insights.users_acquisition(project_id=projectId, **data.dict())} @app.post('/{projectId}/insights/users_retention', tags=["insights"]) @app.get('/{projectId}/insights/users_retention', tags=["insights"]) -def get_users_retention(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): +async def get_users_retention(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): return {"data": insights.users_retention(project_id=projectId, **data.dict())} @app.post('/{projectId}/insights/feature_retention', tags=["insights"]) @app.get('/{projectId}/insights/feature_retention', tags=["insights"]) -def get_feature_rentention(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): +async def get_feature_rentention(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): return {"data": insights.feature_retention(project_id=projectId, **data.dict())} @app.post('/{projectId}/insights/feature_acquisition', tags=["insights"]) @app.get('/{projectId}/insights/feature_acquisition', tags=["insights"]) -def get_feature_acquisition(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): +async def get_feature_acquisition(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): return {"data": insights.feature_acquisition(project_id=projectId, **data.dict())} @app.post('/{projectId}/insights/feature_popularity_frequency', tags=["insights"]) @app.get('/{projectId}/insights/feature_popularity_frequency', tags=["insights"]) -def get_feature_popularity_frequency(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): +async def get_feature_popularity_frequency(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): return {"data": insights.feature_popularity_frequency(project_id=projectId, **data.dict())} @app.post('/{projectId}/insights/feature_intensity', tags=["insights"]) @app.get('/{projectId}/insights/feature_intensity', tags=["insights"]) -def get_feature_intensity(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): +async def get_feature_intensity(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): return {"data": insights.feature_intensity(project_id=projectId, **data.dict())} @app.post('/{projectId}/insights/feature_adoption', tags=["insights"]) @app.get('/{projectId}/insights/feature_adoption', tags=["insights"]) -def get_feature_adoption(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): +async def get_feature_adoption(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): return {"data": insights.feature_adoption(project_id=projectId, **data.dict())} @app.post('/{projectId}/insights/feature_adoption_top_users', tags=["insights"]) @app.get('/{projectId}/insights/feature_adoption_top_users', tags=["insights"]) -def get_feature_adoption(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): +async def get_feature_adoption(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): return {"data": insights.feature_adoption_top_users(project_id=projectId, **data.dict())} @app.post('/{projectId}/insights/users_active', tags=["insights"]) @app.get('/{projectId}/insights/users_active', tags=["insights"]) -def get_users_active(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): +async def get_users_active(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): return {"data": insights.users_active(project_id=projectId, **data.dict())} @app.post('/{projectId}/insights/users_power', tags=["insights"]) @app.get('/{projectId}/insights/users_power', tags=["insights"]) -def get_users_power(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): +async def get_users_power(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): return {"data": insights.users_power(project_id=projectId, **data.dict())} @app.post('/{projectId}/insights/users_slipping', tags=["insights"]) @app.get('/{projectId}/insights/users_slipping', tags=["insights"]) -def get_users_slipping(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): +async def get_users_slipping(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): return {"data": insights.users_slipping(project_id=projectId, **data.dict())} # # # @app.route('/{projectId}/dashboard/{widget}/search', methods=['GET']) -# def get_dashboard_autocomplete(projectId:int, widget): +# async def get_dashboard_autocomplete(projectId:int, widget): # params = app.current_request.query_params # if params is None or params.get('q') is None or len(params.get('q')) == 0: # return {"data": []} diff --git a/api/routers/subs/metrics.py b/api/routers/subs/metrics.py index ac54842da..8af41425c 100644 --- a/api/routers/subs/metrics.py +++ b/api/routers/subs/metrics.py @@ -12,18 +12,18 @@ public_app, app, app_apikey = get_routers() @app.post('/{projectId}/dashboards', tags=["dashboard"]) @app.put('/{projectId}/dashboards', tags=["dashboard"]) -def create_dashboards(projectId: int, data: schemas.CreateDashboardSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +async def create_dashboards(projectId: int, data: schemas.CreateDashboardSchema = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): return dashboards.create_dashboard(project_id=projectId, user_id=context.user_id, data=data) @app.get('/{projectId}/dashboards', tags=["dashboard"]) -def get_dashboards(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): +async def get_dashboards(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): return {"data": dashboards.get_dashboards(project_id=projectId, user_id=context.user_id)} @app.get('/{projectId}/dashboards/{dashboardId}', tags=["dashboard"]) -def get_dashboard(projectId: int, dashboardId: int, context: schemas.CurrentContext = Depends(OR_context)): +async def get_dashboard(projectId: int, dashboardId: int, context: schemas.CurrentContext = Depends(OR_context)): data = dashboards.get_dashboard(project_id=projectId, user_id=context.user_id, dashboard_id=dashboardId) if data is None: return {"errors": ["dashboard not found"]} @@ -32,59 +32,59 @@ def get_dashboard(projectId: int, dashboardId: int, context: schemas.CurrentCont @app.post('/{projectId}/dashboards/{dashboardId}', tags=["dashboard"]) @app.put('/{projectId}/dashboards/{dashboardId}', tags=["dashboard"]) -def update_dashboard(projectId: int, dashboardId: int, data: schemas.EditDashboardSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +async def update_dashboard(projectId: int, dashboardId: int, data: schemas.EditDashboardSchema = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): return {"data": dashboards.update_dashboard(project_id=projectId, user_id=context.user_id, dashboard_id=dashboardId, data=data)} @app.delete('/{projectId}/dashboards/{dashboardId}', tags=["dashboard"]) -def delete_dashboard(projectId: int, dashboardId: int, context: schemas.CurrentContext = Depends(OR_context)): +async def delete_dashboard(projectId: int, dashboardId: int, context: schemas.CurrentContext = Depends(OR_context)): return dashboards.delete_dashboard(project_id=projectId, user_id=context.user_id, dashboard_id=dashboardId) @app.get('/{projectId}/dashboards/{dashboardId}/pin', tags=["dashboard"]) -def pin_dashboard(projectId: int, dashboardId: int, context: schemas.CurrentContext = Depends(OR_context)): +async def pin_dashboard(projectId: int, dashboardId: int, context: schemas.CurrentContext = Depends(OR_context)): 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_card_to_dashboard(projectId: int, dashboardId: int, - data: schemas.AddWidgetToDashboardPayloadSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +async 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)} @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(...), - context: schemas.CurrentContext = Depends(OR_context)): +async def create_metric_and_add_to_dashboard(projectId: int, dashboardId: int, + 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)} @app.post('/{projectId}/dashboards/{dashboardId}/widgets/{widgetId}', tags=["dashboard"]) @app.put('/{projectId}/dashboards/{dashboardId}/widgets/{widgetId}', tags=["dashboard"]) -def update_widget_in_dashboard(projectId: int, dashboardId: int, widgetId: int, - data: schemas.UpdateWidgetPayloadSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +async def update_widget_in_dashboard(projectId: int, dashboardId: int, widgetId: int, + data: schemas.UpdateWidgetPayloadSchema = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): return dashboards.update_widget(project_id=projectId, user_id=context.user_id, dashboard_id=dashboardId, widget_id=widgetId, data=data) @app.delete('/{projectId}/dashboards/{dashboardId}/widgets/{widgetId}', tags=["dashboard"]) -def remove_widget_from_dashboard(projectId: int, dashboardId: int, widgetId: int, - context: schemas.CurrentContext = Depends(OR_context)): +async def remove_widget_from_dashboard(projectId: int, dashboardId: int, widgetId: int, + context: schemas.CurrentContext = Depends(OR_context)): return dashboards.remove_widget(project_id=projectId, user_id=context.user_id, dashboard_id=dashboardId, widget_id=widgetId) # @app.post('/{projectId}/dashboards/{dashboardId}/widgets/{widgetId}/chart', tags=["dashboard"]) -# def get_widget_chart(projectId: int, dashboardId: int, widgetId: int, +# async 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, @@ -99,16 +99,16 @@ 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(...), - context: schemas.CurrentContext = Depends(OR_context)): +async 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_card_sessions(projectId: int, data: schemas.CardSessionsSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +async 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} @@ -116,8 +116,8 @@ def try_card_sessions(projectId: int, data: schemas.CardSessionsSchema = Body(.. @app.post('/{projectId}/cards/try/issues', tags=["cards"]) @app.post('/{projectId}/metrics/try/issues', tags=["dashboard"]) @app.post('/{projectId}/custom_metrics/try/issues', tags=["customMetrics"]) -def try_card_funnel_issues(projectId: int, data: schemas.CardSessionsSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +async 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 @@ -129,7 +129,7 @@ def try_card_funnel_issues(projectId: int, data: schemas.CardSessionsSchema = Bo @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)): +async 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)} @@ -138,23 +138,23 @@ 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(...), - context: schemas.CurrentContext = Depends(OR_context)): +async 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.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)): +async 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_card(projectId: int, metric_id: Union[int, str], context: schemas.CurrentContext = Depends(OR_context)): +async def get_card(projectId: int, metric_id: Union[int, 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) @@ -164,7 +164,7 @@ def get_card(projectId: int, metric_id: Union[int, str], context: schemas.Curren # @app.get('/{projectId}/cards/{metric_id}/thumbnail', tags=["cards"]) -# def sign_thumbnail_for_upload(projectId: int, metric_id: Union[int, str], +# async def sign_thumbnail_for_upload(projectId: int, metric_id: Union[int, str], # context: schemas.CurrentContext = Depends(OR_context)): # if not isinstance(metric_id, int): # return {"errors": ["invalid card_id"]} @@ -174,9 +174,9 @@ def get_card(projectId: int, metric_id: Union[int, str], context: schemas.Curren @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_card_sessions(projectId: int, metric_id: int, - data: schemas.CardSessionsSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +async 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"]} @@ -186,9 +186,9 @@ def get_card_sessions(projectId: int, metric_id: int, @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_card_funnel_issues(projectId: int, metric_id: Union[int, str], - data: schemas.CardSessionsSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +async def get_card_funnel_issues(projectId: int, metric_id: Union[int, str], + data: schemas.CardSessionsSchema = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): if not isinstance(metric_id, int): return {"errors": [f"invalid card_id: {metric_id}"]} @@ -202,9 +202,9 @@ def get_card_funnel_issues(projectId: int, metric_id: Union[int, str], @app.post('/{projectId}/cards/{metric_id}/issues/{issueId}/sessions', tags=["dashboard"]) @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.CardSessionsSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +async def get_metric_funnel_issue_sessions(projectId: int, metric_id: int, issueId: str, + 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) if data is None: @@ -215,9 +215,9 @@ def get_metric_funnel_issue_sessions(projectId: int, metric_id: int, issueId: st @app.post('/{projectId}/cards/{metric_id}/errors', tags=["dashboard"]) @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.CardSessionsSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +async def get_custom_metric_errors_list(projectId: int, metric_id: int, + 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) if data is None: @@ -228,8 +228,8 @@ def get_custom_metric_errors_list(projectId: int, metric_id: int, @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_card_chart(projectId: int, metric_id: int, request: Request, data: schemas.CardChartSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +async def get_card_chart(projectId: int, metric_id: int, request: Request, 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) return {"data": data} @@ -240,8 +240,8 @@ def get_card_chart(projectId: int, metric_id: int, request: Request, data: schem @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.UpdateCardSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +async 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: return {"errors": ["custom metric not found"]} @@ -253,9 +253,9 @@ def update_custom_metric(projectId: int, metric_id: int, data: schemas.UpdateCar @app.put('/{projectId}/metrics/{metric_id}/status', tags=["dashboard"]) @app.post('/{projectId}/custom_metrics/{metric_id}/status', tags=["customMetrics"]) @app.put('/{projectId}/custom_metrics/{metric_id}/status', tags=["customMetrics"]) -def update_custom_metric_state(projectId: int, metric_id: int, - data: schemas.UpdateCustomMetricsStatusSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +async def update_custom_metric_state(projectId: int, metric_id: int, + data: schemas.UpdateCustomMetricsStatusSchema = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): return { "data": custom_metrics.change_state(project_id=projectId, user_id=context.user_id, metric_id=metric_id, status=data.active)} @@ -264,5 +264,5 @@ def update_custom_metric_state(projectId: int, metric_id: int, @app.delete('/{projectId}/cards/{metric_id}', tags=["dashboard"]) @app.delete('/{projectId}/metrics/{metric_id}', tags=["dashboard"]) @app.delete('/{projectId}/custom_metrics/{metric_id}', tags=["customMetrics"]) -def delete_custom_metric(projectId: int, metric_id: int, context: schemas.CurrentContext = Depends(OR_context)): +async def delete_custom_metric(projectId: int, metric_id: int, context: schemas.CurrentContext = Depends(OR_context)): return {"data": custom_metrics.delete(project_id=projectId, user_id=context.user_id, metric_id=metric_id)} diff --git a/api/routers/subs/v1_api.py b/api/routers/subs/v1_api.py index 2af0b398a..0759d0c31 100644 --- a/api/routers/subs/v1_api.py +++ b/api/routers/subs/v1_api.py @@ -10,7 +10,7 @@ public_app, app, app_apikey = get_routers() @app_apikey.get('/v1/{projectKey}/users/{userId}/sessions', tags=["api"]) -def get_user_sessions(projectKey: str, userId: str, start_date: int = None, end_date: int = None): +async def get_user_sessions(projectKey: str, userId: str, start_date: int = None, end_date: int = None): projectId = projects.get_internal_project_id(projectKey) if projectId is None: return {"errors": ["invalid projectKey"]} @@ -25,7 +25,7 @@ def get_user_sessions(projectKey: str, userId: str, start_date: int = None, end_ @app_apikey.get('/v1/{projectKey}/sessions/{sessionId}/events', tags=["api"]) -def get_session_events(projectKey: str, sessionId: int): +async def get_session_events(projectKey: str, sessionId: int): projectId = projects.get_internal_project_id(projectKey) if projectId is None: return {"errors": ["invalid projectKey"]} @@ -38,7 +38,7 @@ def get_session_events(projectKey: str, sessionId: int): @app_apikey.get('/v1/{projectKey}/users/{userId}', tags=["api"]) -def get_user_details(projectKey: str, userId: str): +async def get_user_details(projectKey: str, userId: str): projectId = projects.get_internal_project_id(projectKey) if projectId is None: return {"errors": ["invalid projectKey"]} @@ -51,7 +51,7 @@ def get_user_details(projectKey: str, userId: str): @app_apikey.delete('/v1/{projectKey}/users/{userId}', tags=["api"]) -def schedule_to_delete_user_data(projectKey: str, userId: str): +async def schedule_to_delete_user_data(projectKey: str, userId: str): projectId = projects.get_internal_project_id(projectKey) if projectId is None: return {"errors": ["invalid projectKey"]} @@ -66,7 +66,7 @@ def schedule_to_delete_user_data(projectKey: str, userId: str): @app_apikey.get('/v1/{projectKey}/jobs', tags=["api"]) -def get_jobs(projectKey: str): +async def get_jobs(projectKey: str): projectId = projects.get_internal_project_id(projectKey) if projectId is None: return {"errors": ["invalid projectKey"]} @@ -76,14 +76,14 @@ def get_jobs(projectKey: str): @app_apikey.get('/v1/{projectKey}/jobs/{jobId}', tags=["api"]) -def get_job(projectKey: str, jobId: int): +async def get_job(projectKey: str, jobId: int): return { 'data': jobs.get(job_id=jobId) } @app_apikey.delete('/v1/{projectKey}/jobs/{jobId}', tags=["api"]) -def cancel_job(projectKey: str, jobId: int): +async def cancel_job(projectKey: str, jobId: int): job = jobs.get(job_id=jobId) job_not_found = len(job.keys()) == 0 @@ -99,7 +99,7 @@ def cancel_job(projectKey: str, jobId: int): @app_apikey.get('/v1/projects', tags=["api"]) -def get_projects(context: schemas.CurrentContext = Depends(OR_context)): +async def get_projects(context: schemas.CurrentContext = Depends(OR_context)): records = projects.get_projects(tenant_id=context.tenant_id) for record in records: del record['projectId'] @@ -110,15 +110,15 @@ def get_projects(context: schemas.CurrentContext = Depends(OR_context)): @app_apikey.get('/v1/projects/{projectKey}', tags=["api"]) -def get_project(projectKey: str, context: schemas.CurrentContext = Depends(OR_context)): +async def get_project(projectKey: str, context: schemas.CurrentContext = Depends(OR_context)): return { 'data': projects.get_project_by_key(tenant_id=context.tenant_id, project_key=projectKey) } @app_apikey.post('/v1/projects', tags=["api"]) -def create_project(data: schemas.CreateProjectSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +async def create_project(data: schemas.CreateProjectSchema = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): record = projects.create( tenant_id=context.tenant_id, user_id=None, diff --git a/ee/api/chalicelib/core/projects.py b/ee/api/chalicelib/core/projects.py index 467f4185e..4d77f7ba5 100644 --- a/ee/api/chalicelib/core/projects.py +++ b/ee/api/chalicelib/core/projects.py @@ -95,7 +95,7 @@ def get_projects(tenant_id, recording_state=False, gdpr=None, recorded=False, st {role_query if user_id is not None else ""} WHERE s.tenant_id =%(tenant_id)s AND s.deleted_at IS NULL - ORDER BY s.project_id {") AS raw" if recorded else ""};""", + ORDER BY s.name {") AS raw" if recorded else ""};""", {"tenant_id": tenant_id, "user_id": user_id, "now": TimeUTC.now()}) cur.execute(query) rows = cur.fetchall() diff --git a/ee/api/routers/subs/insights.py b/ee/api/routers/subs/insights.py index aa3ca2674..5bd68d313 100644 --- a/ee/api/routers/subs/insights.py +++ b/ee/api/routers/subs/insights.py @@ -11,79 +11,79 @@ public_app, app, app_apikey = get_routers([OR_scope(Permissions.metrics)]) @app.post('/{projectId}/insights/journey', tags=["insights"]) @app.get('/{projectId}/insights/journey', tags=["insights"]) -def get_insights_journey(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): +async def get_insights_journey(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): return {"data": insights.journey(project_id=projectId, **data.dict())} @app.post('/{projectId}/insights/users_acquisition', tags=["insights"]) @app.get('/{projectId}/insights/users_acquisition', tags=["insights"]) -def get_users_acquisition(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): +async def get_users_acquisition(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): return {"data": insights.users_acquisition(project_id=projectId, **data.dict())} @app.post('/{projectId}/insights/users_retention', tags=["insights"]) @app.get('/{projectId}/insights/users_retention', tags=["insights"]) -def get_users_retention(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): +async def get_users_retention(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): return {"data": insights.users_retention(project_id=projectId, **data.dict())} @app.post('/{projectId}/insights/feature_retention', tags=["insights"]) @app.get('/{projectId}/insights/feature_retention', tags=["insights"]) -def get_feature_rentention(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): +async def get_feature_rentention(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): return {"data": insights.feature_retention(project_id=projectId, **data.dict())} @app.post('/{projectId}/insights/feature_acquisition', tags=["insights"]) @app.get('/{projectId}/insights/feature_acquisition', tags=["insights"]) -def get_feature_acquisition(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): +async def get_feature_acquisition(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): return {"data": insights.feature_acquisition(project_id=projectId, **data.dict())} @app.post('/{projectId}/insights/feature_popularity_frequency', tags=["insights"]) @app.get('/{projectId}/insights/feature_popularity_frequency', tags=["insights"]) -def get_feature_popularity_frequency(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): +async def get_feature_popularity_frequency(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): return {"data": insights.feature_popularity_frequency(project_id=projectId, **data.dict())} @app.post('/{projectId}/insights/feature_intensity', tags=["insights"]) @app.get('/{projectId}/insights/feature_intensity', tags=["insights"]) -def get_feature_intensity(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): +async def get_feature_intensity(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): return {"data": insights.feature_intensity(project_id=projectId, **data.dict())} @app.post('/{projectId}/insights/feature_adoption', tags=["insights"]) @app.get('/{projectId}/insights/feature_adoption', tags=["insights"]) -def get_feature_adoption(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): +async def get_feature_adoption(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): return {"data": insights.feature_adoption(project_id=projectId, **data.dict())} @app.post('/{projectId}/insights/feature_adoption_top_users', tags=["insights"]) @app.get('/{projectId}/insights/feature_adoption_top_users', tags=["insights"]) -def get_feature_adoption(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): +async def get_feature_adoption(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): return {"data": insights.feature_adoption_top_users(project_id=projectId, **data.dict())} @app.post('/{projectId}/insights/users_active', tags=["insights"]) @app.get('/{projectId}/insights/users_active', tags=["insights"]) -def get_users_active(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): +async def get_users_active(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): return {"data": insights.users_active(project_id=projectId, **data.dict())} @app.post('/{projectId}/insights/users_power', tags=["insights"]) @app.get('/{projectId}/insights/users_power', tags=["insights"]) -def get_users_power(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): +async def get_users_power(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): return {"data": insights.users_power(project_id=projectId, **data.dict())} @app.post('/{projectId}/insights/users_slipping', tags=["insights"]) @app.get('/{projectId}/insights/users_slipping', tags=["insights"]) -def get_users_slipping(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): +async def get_users_slipping(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): return {"data": insights.users_slipping(project_id=projectId, **data.dict())} # # # @app.route('/{projectId}/dashboard/{widget}/search', methods=['GET']) -# def get_dashboard_autocomplete(projectId:int, widget): +# async def get_dashboard_autocomplete(projectId:int, widget): # params = app.current_request.query_params # if params is None or params.get('q') is None or len(params.get('q')) == 0: # return {"data": []} diff --git a/ee/api/routers/subs/metrics.py b/ee/api/routers/subs/metrics.py index 274c8e256..ec489fc92 100644 --- a/ee/api/routers/subs/metrics.py +++ b/ee/api/routers/subs/metrics.py @@ -14,18 +14,18 @@ public_app, app, app_apikey = get_routers([OR_scope(Permissions.metrics)]) @app.post('/{projectId}/dashboards', tags=["dashboard"]) @app.put('/{projectId}/dashboards', tags=["dashboard"]) -def create_dashboards(projectId: int, data: schemas.CreateDashboardSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +async def create_dashboards(projectId: int, data: schemas.CreateDashboardSchema = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): return dashboards.create_dashboard(project_id=projectId, user_id=context.user_id, data=data) @app.get('/{projectId}/dashboards', tags=["dashboard"]) -def get_dashboards(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): +async def get_dashboards(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): return {"data": dashboards.get_dashboards(project_id=projectId, user_id=context.user_id)} @app.get('/{projectId}/dashboards/{dashboardId}', tags=["dashboard"]) -def get_dashboard(projectId: int, dashboardId: int, context: schemas.CurrentContext = Depends(OR_context)): +async def get_dashboard(projectId: int, dashboardId: int, context: schemas.CurrentContext = Depends(OR_context)): data = dashboards.get_dashboard(project_id=projectId, user_id=context.user_id, dashboard_id=dashboardId) if data is None: return {"errors": ["dashboard not found"]} @@ -34,59 +34,59 @@ def get_dashboard(projectId: int, dashboardId: int, context: schemas.CurrentCont @app.post('/{projectId}/dashboards/{dashboardId}', tags=["dashboard"]) @app.put('/{projectId}/dashboards/{dashboardId}', tags=["dashboard"]) -def update_dashboard(projectId: int, dashboardId: int, data: schemas.EditDashboardSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +async def update_dashboard(projectId: int, dashboardId: int, data: schemas.EditDashboardSchema = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): return {"data": dashboards.update_dashboard(project_id=projectId, user_id=context.user_id, dashboard_id=dashboardId, data=data)} @app.delete('/{projectId}/dashboards/{dashboardId}', tags=["dashboard"]) -def delete_dashboard(projectId: int, dashboardId: int, context: schemas.CurrentContext = Depends(OR_context)): +async def delete_dashboard(projectId: int, dashboardId: int, context: schemas.CurrentContext = Depends(OR_context)): return dashboards.delete_dashboard(project_id=projectId, user_id=context.user_id, dashboard_id=dashboardId) @app.get('/{projectId}/dashboards/{dashboardId}/pin', tags=["dashboard"]) -def pin_dashboard(projectId: int, dashboardId: int, context: schemas.CurrentContext = Depends(OR_context)): +async def pin_dashboard(projectId: int, dashboardId: int, context: schemas.CurrentContext = Depends(OR_context)): 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_card_to_dashboard(projectId: int, dashboardId: int, - data: schemas.AddWidgetToDashboardPayloadSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +async 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)} @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_ee.CreateCardSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +async def create_metric_and_add_to_dashboard(projectId: int, dashboardId: int, + 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)} @app.post('/{projectId}/dashboards/{dashboardId}/widgets/{widgetId}', tags=["dashboard"]) @app.put('/{projectId}/dashboards/{dashboardId}/widgets/{widgetId}', tags=["dashboard"]) -def update_widget_in_dashboard(projectId: int, dashboardId: int, widgetId: int, - data: schemas.UpdateWidgetPayloadSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +async def update_widget_in_dashboard(projectId: int, dashboardId: int, widgetId: int, + data: schemas.UpdateWidgetPayloadSchema = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): return dashboards.update_widget(project_id=projectId, user_id=context.user_id, dashboard_id=dashboardId, widget_id=widgetId, data=data) @app.delete('/{projectId}/dashboards/{dashboardId}/widgets/{widgetId}', tags=["dashboard"]) -def remove_widget_from_dashboard(projectId: int, dashboardId: int, widgetId: int, - context: schemas.CurrentContext = Depends(OR_context)): +async def remove_widget_from_dashboard(projectId: int, dashboardId: int, widgetId: int, + context: schemas.CurrentContext = Depends(OR_context)): return dashboards.remove_widget(project_id=projectId, user_id=context.user_id, dashboard_id=dashboardId, widget_id=widgetId) # @app.post('/{projectId}/dashboards/{dashboardId}/widgets/{widgetId}/chart', tags=["dashboard"]) -# def get_widget_chart(projectId: int, dashboardId: int, widgetId: int, +# async 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, @@ -101,16 +101,16 @@ 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_ee.CreateCardSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +async 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)} @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_card_sessions(projectId: int, data: schemas.CardSessionsSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +async 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} @@ -118,8 +118,8 @@ def try_card_sessions(projectId: int, data: schemas.CardSessionsSchema = Body(.. @app.post('/{projectId}/cards/try/issues', tags=["cards"]) @app.post('/{projectId}/metrics/try/issues', tags=["dashboard"]) @app.post('/{projectId}/custom_metrics/try/issues', tags=["customMetrics"]) -def try_card_funnel_issues(projectId: int, data: schemas.CardSessionsSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +async 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 @@ -131,7 +131,7 @@ def try_card_funnel_issues(projectId: int, data: schemas.CardSessionsSchema = Bo @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)): +async 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)} @@ -140,23 +140,23 @@ 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_ee.CreateCardSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +async 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) @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)): +async 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_card(projectId: int, metric_id: Union[int, str], context: schemas.CurrentContext = Depends(OR_context)): +async def get_card(projectId: int, metric_id: Union[int, 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) @@ -166,7 +166,7 @@ def get_card(projectId: int, metric_id: Union[int, str], context: schemas.Curren # @app.get('/{projectId}/cards/{metric_id}/thumbnail', tags=["cards"]) -# def sign_thumbnail_for_upload(projectId: int, metric_id: Union[int, str], +# async def sign_thumbnail_for_upload(projectId: int, metric_id: Union[int, str], # context: schemas.CurrentContext = Depends(OR_context)): # if not isinstance(metric_id, int): # return {"errors": ["invalid card_id"]} @@ -176,9 +176,9 @@ def get_card(projectId: int, metric_id: Union[int, str], context: schemas.Curren @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_card_sessions(projectId: int, metric_id: int, - data: schemas.CardSessionsSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +async 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"]} @@ -188,9 +188,9 @@ def get_card_sessions(projectId: int, metric_id: int, @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_card_funnel_issues(projectId: int, metric_id: Union[int, str], - data: schemas.CardSessionsSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +async def get_card_funnel_issues(projectId: int, metric_id: Union[int, str], + data: schemas.CardSessionsSchema = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): if not isinstance(metric_id, int): return {"errors": [f"invalid card_id: {metric_id}"]} @@ -204,9 +204,9 @@ def get_card_funnel_issues(projectId: int, metric_id: Union[int, str], @app.post('/{projectId}/cards/{metric_id}/issues/{issueId}/sessions', tags=["dashboard"]) @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.CardSessionsSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +async def get_metric_funnel_issue_sessions(projectId: int, metric_id: int, issueId: str, + 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) if data is None: @@ -217,9 +217,9 @@ def get_metric_funnel_issue_sessions(projectId: int, metric_id: int, issueId: st @app.post('/{projectId}/cards/{metric_id}/errors', tags=["dashboard"]) @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.CardSessionsSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +async def get_custom_metric_errors_list(projectId: int, metric_id: int, + 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) if data is None: @@ -230,8 +230,8 @@ def get_custom_metric_errors_list(projectId: int, metric_id: int, @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_card_chart(projectId: int, metric_id: int, request: Request, data: schemas.CardChartSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +async def get_card_chart(projectId: int, metric_id: int, request: Request, 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) return {"data": data} @@ -242,8 +242,8 @@ def get_card_chart(projectId: int, metric_id: int, request: Request, data: schem @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_ee.UpdateCardSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +async def update_custom_metric(projectId: int, metric_id: int, data: schemas_ee.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: return {"errors": ["custom metric not found"]} @@ -255,9 +255,9 @@ def update_custom_metric(projectId: int, metric_id: int, data: schemas_ee.Update @app.put('/{projectId}/metrics/{metric_id}/status', tags=["dashboard"]) @app.post('/{projectId}/custom_metrics/{metric_id}/status', tags=["customMetrics"]) @app.put('/{projectId}/custom_metrics/{metric_id}/status', tags=["customMetrics"]) -def update_custom_metric_state(projectId: int, metric_id: int, - data: schemas.UpdateCustomMetricsStatusSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +async def update_custom_metric_state(projectId: int, metric_id: int, + data: schemas.UpdateCustomMetricsStatusSchema = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): return { "data": custom_metrics.change_state(project_id=projectId, user_id=context.user_id, metric_id=metric_id, status=data.active)} @@ -266,5 +266,5 @@ def update_custom_metric_state(projectId: int, metric_id: int, @app.delete('/{projectId}/cards/{metric_id}', tags=["dashboard"]) @app.delete('/{projectId}/metrics/{metric_id}', tags=["dashboard"]) @app.delete('/{projectId}/custom_metrics/{metric_id}', tags=["customMetrics"]) -def delete_custom_metric(projectId: int, metric_id: int, context: schemas.CurrentContext = Depends(OR_context)): +async def delete_custom_metric(projectId: int, metric_id: int, context: schemas.CurrentContext = Depends(OR_context)): return {"data": custom_metrics.delete(project_id=projectId, user_id=context.user_id, metric_id=metric_id)} diff --git a/ee/api/routers/subs/v1_api_ee.py b/ee/api/routers/subs/v1_api_ee.py index a4706ff5a..7bfc406f8 100644 --- a/ee/api/routers/subs/v1_api_ee.py +++ b/ee/api/routers/subs/v1_api_ee.py @@ -11,7 +11,7 @@ public_app, app, app_apikey = get_routers() @app_apikey.get('/v1/assist/credentials', tags=["api"]) -def get_assist_credentials(): +async def get_assist_credentials(): credentials = assist_helper.get_temporary_credentials() if "errors" in credentials: return credentials @@ -19,17 +19,17 @@ def get_assist_credentials(): @app_apikey.get('/v1/{projectKey}/assist/sessions', tags=["api"]) -def get_sessions_live(projectKey: str, userId: str = None, context: schemas.CurrentContext = Depends(OR_context)): +async def get_sessions_live(projectKey: str, userId: str = None, context: schemas.CurrentContext = Depends(OR_context)): projectId = projects.get_internal_project_id(projectKey) if projectId is None: return {"errors": ["invalid projectKey"]} - return core.get_sessions_live(projectId=projectId, userId=userId, context=context) + return await core.get_sessions_live(projectId=projectId, userId=userId, context=context) @app_apikey.post('/v1/{projectKey}/assist/sessions', tags=["api"]) -def sessions_live(projectKey: str, data: schemas.LiveSessionsSearchPayloadSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +async def sessions_live(projectKey: str, data: schemas.LiveSessionsSearchPayloadSchema = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): projectId = projects.get_internal_project_id(projectKey) if projectId is None: return {"errors": ["invalid projectKey"]} - return core.sessions_live(projectId=projectId, data=data, context=context) + return await core.sessions_live(projectId=projectId, data=data, context=context) diff --git a/ee/scripts/schema/db/init_dbs/postgresql/init_schema.sql b/ee/scripts/schema/db/init_dbs/postgresql/init_schema.sql index 0418c5d7d..14b7162db 100644 --- a/ee/scripts/schema/db/init_dbs/postgresql/init_schema.sql +++ b/ee/scripts/schema/db/init_dbs/postgresql/init_schema.sql @@ -7,7 +7,7 @@ CREATE EXTENSION IF NOT EXISTS pgcrypto; CREATE OR REPLACE FUNCTION openreplay_version() RETURNS text AS $$ -SELECT 'v1.10.0-ee' +SELECT 'v1.11.0-ee' $$ LANGUAGE sql IMMUTABLE; @@ -414,7 +414,8 @@ $$ 'ml_excessive_scrolling', 'ml_slow_resources', 'custom', - 'js_exception' + 'js_exception', + 'mouse_thrashing' ); END IF; diff --git a/frontend/app/components/Assist/AssistSearchField/AssistSearchField.tsx b/frontend/app/components/Assist/AssistSearchField/AssistSearchField.tsx index df53e4404..2481d2785 100644 --- a/frontend/app/components/Assist/AssistSearchField/AssistSearchField.tsx +++ b/frontend/app/components/Assist/AssistSearchField/AssistSearchField.tsx @@ -18,7 +18,7 @@ function AssistSearchField(props: Props) { return (
- +
-
+ - ) -} -export default connect(state => ({ + ); +}; +export default connect( + (state: any) => ({ appliedFilter: state.getIn(['search', 'instance']), - optionsReady: state.getIn(['customFields', 'optionsReady']) -}), { + }), + { clearSearch, - editFilter, - addFilterByKeyAndValue, - fetchFilterSearch -})(MainSearchBar); + } +)(MainSearchBar); diff --git a/frontend/app/components/shared/SessionSearchField/SessionSearchField.tsx b/frontend/app/components/shared/SessionSearchField/SessionSearchField.tsx index 0e6529e18..84745c8ba 100644 --- a/frontend/app/components/shared/SessionSearchField/SessionSearchField.tsx +++ b/frontend/app/components/shared/SessionSearchField/SessionSearchField.tsx @@ -4,66 +4,73 @@ import { Input } from 'UI'; import FilterModal from 'Shared/Filters/FilterModal'; import { debounce } from 'App/utils'; import { assist as assistRoute, isRoute } from 'App/routes'; +import { addFilterByKeyAndValue, fetchFilterSearch } from 'Duck/search'; +import { + addFilterByKeyAndValue as liveAddFilterByKeyAndValue, + fetchFilterSearch as liveFetchFilterSearch, +} from 'Duck/liveSearch'; const ASSIST_ROUTE = assistRoute(); interface Props { - fetchFilterSearch: (query: any) => void; - addFilterByKeyAndValue: (key: string, value: string) => void; - filterList: any; - filterListLive: any; - filterSearchListLive: any; - filterSearchList: any; + fetchFilterSearch: (query: any) => void; + addFilterByKeyAndValue: (key: string, value: string) => void; + liveAddFilterByKeyAndValue: (key: string, value: string) => void; + filterSearchList: any; + liveFetchFilterSearch: any; } function SessionSearchField(props: Props) { - const debounceFetchFilterSearch = React.useCallback(debounce(props.fetchFilterSearch, 1000), []); - const [showModal, setShowModal] = useState(false); - const [searchQuery, setSearchQuery] = useState(''); + const isLive = + isRoute(ASSIST_ROUTE, window.location.pathname) || + window.location.pathname.includes('multiview'); + const debounceFetchFilterSearch = React.useCallback( + debounce(isLive ? props.liveFetchFilterSearch : props.fetchFilterSearch, 1000), + [] + ); + const [showModal, setShowModal] = useState(false); + const [searchQuery, setSearchQuery] = useState(''); - const onSearchChange = ({ target: { value } }: any) => { - setSearchQuery(value); - debounceFetchFilterSearch({ q: value }); - }; + const onSearchChange = ({ target: { value } }: any) => { + setSearchQuery(value); + debounceFetchFilterSearch({ q: value }); + }; - const onAddFilter = (filter: any) => { - props.addFilterByKeyAndValue(filter.key, filter.value); - }; + const onAddFilter = (filter: any) => { + isLive + ? props.liveAddFilterByKeyAndValue(filter.key, filter.value) + : props.addFilterByKeyAndValue(filter.key, filter.value); + }; - return ( -
- setShowModal(true)} - onBlur={() => setTimeout(setShowModal, 200, false)} - onChange={onSearchChange} - placeholder={'Search sessions using any captured event (click, input, page, error...)'} - id="search" - type="search" - autoComplete="off" - className="hover:border-gray-medium text-lg placeholder-lg" - /> + return ( +
+ setShowModal(true)} + onBlur={() => setTimeout(setShowModal, 200, false)} + onChange={onSearchChange} + placeholder={'Search sessions using any captured event (click, input, page, error...)'} + id="search" + type="search" + autoComplete="off" + className="hover:border-gray-medium text-lg placeholder-lg" + /> - {showModal && ( -
- -
- )} + {showModal && ( +
+
- ); + )} +
+ ); } -export default connect( - (state: any) => ({ - filterSearchList: state.getIn(['search', 'filterSearchList']), - filterSearchListLive: state.getIn(['liveSearch', 'filterSearchList']), - filterList: state.getIn(['search', 'filterList']), - filterListLive: state.getIn(['search', 'filterListLive']), - }), - {} -)(SessionSearchField); +export default connect(null, { + addFilterByKeyAndValue, + fetchFilterSearch, + liveFetchFilterSearch, + liveAddFilterByKeyAndValue, +})(SessionSearchField); diff --git a/frontend/app/components/shared/SessionSettings/components/ListingVisibility.tsx b/frontend/app/components/shared/SessionSettings/components/ListingVisibility.tsx index 8bea8b004..b7c1a0f61 100644 --- a/frontend/app/components/shared/SessionSettings/components/ListingVisibility.tsx +++ b/frontend/app/components/shared/SessionSettings/components/ListingVisibility.tsx @@ -52,7 +52,6 @@ function ListingVisibility() { min={0} placeholder="E.g 10" onChange={({ target: { value } }: any) => { - console.log('value', value) changeSettings({ count: value > 0 ? value : '' }) }} /> diff --git a/frontend/app/duck/liveSearch.js b/frontend/app/duck/liveSearch.js index 499f6c740..c9f2d305f 100644 --- a/frontend/app/duck/liveSearch.js +++ b/frontend/app/duck/liveSearch.js @@ -4,7 +4,7 @@ import { createRequestReducer } from './funcTools/request'; import { mergeReducers, success } from './funcTools/tools'; import Filter from 'Types/filter'; import { liveFiltersMap } from 'Types/filter/newFilter'; -import { filterMap, checkFilterValue, hasFilterApplied } from './search'; +import { filterMap, checkFilterValue, hasFilterApplied, getAppliedFilterIndex } from './search'; import Session from 'Types/session'; const name = "liveSearch"; @@ -111,8 +111,18 @@ export const clearSearch = () => (dispatch, getState) => { export const addFilter = (filter) => (dispatch, getState) => { filter.value = checkFilterValue(filter.value); const instance = getState().getIn([ 'liveSearch', 'instance']); + const filters = instance.get('filters'); - if (hasFilterApplied(instance.filters, filter)) { + const index = getAppliedFilterIndex(filters, filter); + if (index !== -1) { + const oldFilter = filters.get(index); + const updatedFilter = { + ...oldFilter, + value: oldFilter.value.concat(filter.value), + }; + + const updatedFilters = filters.set(index, updatedFilter); + return dispatch(edit(instance.set('filters', updatedFilters))); // const index = instance.filters.findIndex(f => f.key === filter.key); // const oldFilter = instance.filters.get(index); // oldFilter.value = oldFilter.value.concat(filter.value); @@ -124,7 +134,7 @@ export const addFilter = (filter) => (dispatch, getState) => { } export const addFilterByKeyAndValue = (key, value, operator = undefined) => (dispatch, getState) => { - let defaultFilter = liveFiltersMap[key]; + let defaultFilter = { ...liveFiltersMap[key] }; defaultFilter.value = value; if (operator) { defaultFilter.operator = operator; diff --git a/frontend/app/duck/search.js b/frontend/app/duck/search.js index 54b3f0132..a9ffc0f12 100644 --- a/frontend/app/duck/search.js +++ b/frontend/app/duck/search.js @@ -349,27 +349,46 @@ export const hasFilterApplied = (filters, filter) => { return !filter.isEvent && filters.some((f) => f.key === filter.key); }; -export const addFilter = (filter) => (dispatch, getState) => { - filter.value = checkFilterValue(filter.value); - filter.filters = filter.filters - ? filter.filters.map((subFilter) => ({ - ...subFilter, - value: checkFilterValue(subFilter.value), - })) - : null; - const instance = getState().getIn(['search', 'instance']); - - if (hasFilterApplied(instance.filters, filter)) { - } else { - const filters = instance.filters.push(filter); - return dispatch(edit(instance.set('filters', filters))); +export const getAppliedFilterIndex = (filters, filterToFind) => { + if (!filterToFind.isEvent) { + return filters.findIndex((filter) => filter.key === filterToFind.key); } + return -1; + }; + +export const addFilter = (filter) => (dispatch, getState) => { + const instance = getState().getIn(['search', 'instance']); + const filters = instance.get('filters'); + const index = getAppliedFilterIndex(filters, filter); + + filter.value = checkFilterValue(filter.value); + + filter.filters = filter.filters + ? filter.filters.map((subFilter) => ({ + ...subFilter, + value: checkFilterValue(subFilter.value), + })) + : null; + + if (index !== -1) { + const oldFilter = filters.get(index); + const updatedFilter = { + ...oldFilter, + value: oldFilter.value.concat(filter.value), + }; + + const updatedFilters = filters.set(index, updatedFilter); + return dispatch(edit(instance.set('filters', updatedFilters))); + } else { + const updatedFilters = filters.push(filter); + return dispatch(edit(instance.set('filters', updatedFilters))); + } }; export const addFilterByKeyAndValue = (key, value, operator = undefined, sourceOperator = undefined, source = undefined) => (dispatch, getState) => { - let defaultFilter = filtersMap[key]; + let defaultFilter = {...filtersMap[key]}; defaultFilter.value = value; if (operator) { defaultFilter.operator = operator; diff --git a/frontend/app/player/common/ListWalker.ts b/frontend/app/player/common/ListWalker.ts index fbd44007b..ca3d409ac 100644 --- a/frontend/app/player/common/ListWalker.ts +++ b/frontend/app/player/common/ListWalker.ts @@ -1,7 +1,7 @@ import type { Timed } from './types'; export default class ListWalker { - private p = 0 + private p = 0 /* Pointer to the "current" item */ constructor(private _list: Array = []) {} append(m: T): void { @@ -130,7 +130,7 @@ export default class ListWalker { return changed ? this.list[ this.p - 1 ] : null; } - moveApply(t: number, fn: (msg: T) => void, fnBack?: (msg: T) => void): void { + async moveWait(t: number, callback: (msg: T) => Promise | undefined): Promise { // Applying only in increment order for now if (t < this.timeNow) { this.reset(); @@ -138,23 +138,8 @@ export default class ListWalker { const list = this.list while (list[this.p] && list[this.p].time <= t) { - fn(this.moveNext()) - } - while (fnBack && this.p > 0 && list[ this.p - 1 ].time > t) { - fnBack(this.movePrev()); - } - } - - async moveWait(t: number, fn: (msg: T) => Promise | undefined): Promise { - // Applying only in increment order for now - if (t < this.timeNow) { - this.reset(); - } - - const list = this.list - while (list[this.p] && list[this.p].time <= t) { - const ret = fn(this.list[ this.p++ ]); - if (ret) { await ret } + const maybePromise = callback(this.list[ this.p++ ]); + if (maybePromise) { await maybePromise } } } diff --git a/frontend/app/player/web/MessageManager.ts b/frontend/app/player/web/MessageManager.ts index fbce59bb0..eee29593e 100644 --- a/frontend/app/player/web/MessageManager.ts +++ b/frontend/app/player/web/MessageManager.ts @@ -65,6 +65,7 @@ export interface State extends ScreenState, ListsState { ready: boolean, lastMessageTime: number, + firstVisualEvent: number, } @@ -91,6 +92,7 @@ export default class MessageManager { cssLoading: false, ready: false, lastMessageTime: 0, + firstVisualEvent: 0, } private locationEventManager: ListWalker/**/ = new ListWalker(); @@ -116,6 +118,7 @@ export default class MessageManager { private sessionStart: number; private navigationStartOffset: number = 0; private lastMessageTime: number = 0; + private firstVisualEventSet = false; constructor( private readonly session: any /*Session*/, @@ -221,7 +224,7 @@ export default class MessageManager { fileReader.append(b) const msgs: Array = [] for (let msg = fileReader.readNext();msg !== null;msg = fileReader.readNext()) { - msg && msgs.push(msg) + msgs.push(msg) } const sorted = msgs.sort((m1, m2) => { // @ts-ignore @@ -467,6 +470,7 @@ export default class MessageManager { default: switch (msg.tp) { case MType.CreateDocument: + if (!this.firstVisualEventSet) this.state.update({ firstVisualEvent: msg.time }); this.windowNodeCounter.reset(); this.performanceTrackManager.setCurrentNodesCount(this.windowNodeCounter.count); break; diff --git a/frontend/app/player/web/WebPlayer.ts b/frontend/app/player/web/WebPlayer.ts index c44ddc7b2..a2e22ef5c 100644 --- a/frontend/app/player/web/WebPlayer.ts +++ b/frontend/app/player/web/WebPlayer.ts @@ -66,7 +66,6 @@ export default class WebPlayer extends Player { // @ts-ignore window.playerJumpToTime = this.jump.bind(this) - } updateLists = (session: any) => { diff --git a/frontend/app/player/web/managers/DOM/DOMManager.ts b/frontend/app/player/web/managers/DOM/DOMManager.ts index d5cbdd845..5ae59c5c4 100644 --- a/frontend/app/player/web/managers/DOM/DOMManager.ts +++ b/frontend/app/player/web/managers/DOM/DOMManager.ts @@ -39,7 +39,6 @@ export default class DOMManager extends ListWalker { private readonly vTexts: Map = new Map() // map vs object here? private readonly vElements: Map = new Map() private readonly vRoots: Map = new Map() - private activeIframeRoots: Map = new Map() private styleSheets: Map = new Map() private ppStyleSheets: Map = new Map() private stringDict: Record = {} @@ -197,7 +196,6 @@ export default class DOMManager extends ListWalker { // todo: start from 0-node (sync logic with tracker) this.vTexts.clear() this.stylesManager.reset() - this.activeIframeRoots.clear() this.stringDict = {} return case MType.CreateTextNode: @@ -333,12 +331,8 @@ export default class DOMManager extends ListWalker { logger.warn("No default iframe doc", msg, host) return } - // remove old root of the same iframe if present - const oldRootId = this.activeIframeRoots.get(msg.frameID) - oldRootId != null && this.vRoots.delete(oldRootId) const vDoc = new VDocument(doc) - this.activeIframeRoots.set(msg.frameID, msg.id) this.vRoots.set(msg.id, vDoc) return; } else if (host instanceof Element) { // shadow DOM diff --git a/frontend/app/player/web/managers/DOM/VirtualDOM.ts b/frontend/app/player/web/managers/DOM/VirtualDOM.ts index d9fd3f77a..a528ae658 100644 --- a/frontend/app/player/web/managers/DOM/VirtualDOM.ts +++ b/frontend/app/player/web/managers/DOM/VirtualDOM.ts @@ -135,7 +135,7 @@ export class VStyleElement extends VElement { this.stylesheetCallbacks = [] } else { // console.warn("Style onload: sheet is null") ? - // sometimes logs shit ton of errors for some reason + // sometimes logs sheet ton of errors for some reason } this.loaded = true } diff --git a/frontend/app/player/web/messages/RawMessageReader.gen.ts b/frontend/app/player/web/messages/RawMessageReader.gen.ts index 0ae41e9a4..135960cef 100644 --- a/frontend/app/player/web/messages/RawMessageReader.gen.ts +++ b/frontend/app/player/web/messages/RawMessageReader.gen.ts @@ -800,9 +800,8 @@ export default class RawMessageReader extends PrimitiveReader { } default: - console.error(`Unrecognizable message type: ${ tp }; Pointer at the position ${this.p} of ${this.buf.length}`) - // skipping unrecognized messages - return false; + throw new Error(`Unrecognizable message type: ${ tp }; Pointer at the position ${this.p} of ${this.buf.length}`) + return null; } } } diff --git a/frontend/app/services/HealthService.ts b/frontend/app/services/HealthService.ts index 7d2b3cc7f..bc6815a2c 100644 --- a/frontend/app/services/HealthService.ts +++ b/frontend/app/services/HealthService.ts @@ -1,8 +1,8 @@ import BaseService from './BaseService'; export default class HealthService extends BaseService { - fetchStatus(): Promise { - return this.client.get('/health') + fetchStatus(isPublic?: boolean): Promise { + return this.client.get(isPublic ? '/health' : '/healthz') .then(r => r.json()) .then(j => j.data || {}) } diff --git a/frontend/app/utils/search.ts b/frontend/app/utils/search.ts index 5779df732..292dc63b4 100644 --- a/frontend/app/utils/search.ts +++ b/frontend/app/utils/search.ts @@ -118,7 +118,7 @@ function getQueryObject(search: any) { .split('&') .map((item: any) => { let [key, value] = item.split('='); - return { key: key.slice(0, -2), value }; + return { key: key.slice(0, -2), value: decodeURI(value) }; }); return jsonArray; } diff --git a/mobs/templates/frontend~app~player~web~messages~RawMessageReader.gen.ts.erb b/mobs/templates/frontend~app~player~web~messages~RawMessageReader.gen.ts.erb index 4b59b9d59..3c9268366 100644 --- a/mobs/templates/frontend~app~player~web~messages~RawMessageReader.gen.ts.erb +++ b/mobs/templates/frontend~app~player~web~messages~RawMessageReader.gen.ts.erb @@ -30,9 +30,8 @@ export default class RawMessageReader extends PrimitiveReader { } <% end %> default: - console.error(`Unrecognizable message type: ${ tp }; Pointer at the position ${this.p} of ${this.buf.length}`) - // skipping unrecognized messages - return false; + throw new Error(`Unrecognizable message type: ${ tp }; Pointer at the position ${this.p} of ${this.buf.length}`) + return null; } } } diff --git a/scripts/schema/db/init_dbs/postgresql/init_schema.sql b/scripts/schema/db/init_dbs/postgresql/init_schema.sql index 2fe532636..73b2a29ce 100644 --- a/scripts/schema/db/init_dbs/postgresql/init_schema.sql +++ b/scripts/schema/db/init_dbs/postgresql/init_schema.sql @@ -6,7 +6,7 @@ CREATE SCHEMA IF NOT EXISTS events; CREATE OR REPLACE FUNCTION openreplay_version() RETURNS text AS $$ -SELECT 'v1.10.0' +SELECT 'v1.11.0' $$ LANGUAGE sql IMMUTABLE; @@ -316,7 +316,8 @@ $$ 'ml_excessive_scrolling', 'ml_slow_resources', 'custom', - 'js_exception' + 'js_exception', + 'mouse_thrashing' ); CREATE TABLE issues diff --git a/tracker/tracker/CHANGELOG.md b/tracker/tracker/CHANGELOG.md index 2cd9d01e4..c04c48bd4 100644 --- a/tracker/tracker/CHANGELOG.md +++ b/tracker/tracker/CHANGELOG.md @@ -1,5 +1,12 @@ +# 6.0.1 + +- fix webworker writer re-init request +- remove useless logs + # 6.0.0 + **(Compatible with OpenReplay v1.11.0+ only)** + - **[breaking]:** Capture mouse thrashing, input hesitation+duration, click hesitation - Capture DOM node drop event (>30% nodes removed) - Capture iframe network requests @@ -18,7 +25,9 @@ - Use `@medv/finder` instead of our own implementation of `getSelector` for better clickmaps experience ## 5.0.0 + **(Compatible with OpenReplay v1.10.0+ only)** + - **[breaking]:** string dictionary to reduce session size - Added "tel" to supported input types - Added `{ withCurrentTime: true }` to `tracker.getSessionURL` method which will return sessionURL with current session's timestamp diff --git a/tracker/tracker/src/main/modules/mouse.ts b/tracker/tracker/src/main/modules/mouse.ts index 5bba2ddda..e09955ced 100644 --- a/tracker/tracker/src/main/modules/mouse.ts +++ b/tracker/tracker/src/main/modules/mouse.ts @@ -142,7 +142,6 @@ export default function (app: App, options?: MouseHandlerOptions): void { const acceleration = (nextVelocity - velocity) / shakeCheckInterval if (directionChangeCount > 3 && acceleration > shakeThreshold) { - console.log('Mouse shake detected!') app.send(MouseThrashing(now())) } diff --git a/tracker/tracker/src/webworker/index.ts b/tracker/tracker/src/webworker/index.ts index 5afa42cee..e821cc6bd 100644 --- a/tracker/tracker/src/webworker/index.ts +++ b/tracker/tracker/src/webworker/index.ts @@ -24,7 +24,7 @@ const AUTO_SEND_INTERVAL = 10 * 1000 let sender: QueueSender | null = null let writer: BatchWriter | null = null let workerStatus: WorkerStatus = WorkerStatus.NotActive -let afterSleepRestarts = 0 +// let afterSleepRestarts = 0 function finalize(): void { if (!writer) { return @@ -98,10 +98,7 @@ self.onmessage = ({ data }: any): any => { } if (!writer) { postMessage('not_init') - if (afterSleepRestarts === 0) { - afterSleepRestarts += 1 - initiateRestart() - } + initiateRestart() } return }