commit
409b890d97
37 changed files with 442 additions and 402 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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": []}
|
||||
|
|
|
|||
|
|
@ -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)}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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": []}
|
||||
|
|
|
|||
|
|
@ -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)}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ function AssistSearchField(props: Props) {
|
|||
return (
|
||||
<div className="flex items-center w-full">
|
||||
<div style={{ width: '60%', marginRight: '10px' }}>
|
||||
<SessionSearchField fetchFilterSearch={props.fetchFilterSearch} addFilterByKeyAndValue={props.addFilterByKeyAndValue} />
|
||||
<SessionSearchField />
|
||||
</div>
|
||||
<Button
|
||||
variant="text-primary"
|
||||
|
|
|
|||
|
|
@ -26,8 +26,8 @@ function mapResponse(resp: Record<string, any>) {
|
|||
return { overallHealth, healthMap };
|
||||
}
|
||||
|
||||
export async function getHealthRequest() {
|
||||
const r = await healthService.fetchStatus();
|
||||
export async function getHealthRequest(isPublic?: boolean) {
|
||||
const r = await healthService.fetchStatus(isPublic);
|
||||
const healthMap = mapResponse(r);
|
||||
const asked = new Date().getTime();
|
||||
localStorage.setItem(healthResponseKey, JSON.stringify(healthMap));
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import { fetchSessions } from 'Duck/liveSearch';
|
|||
import AssistDuration from './AssistDuration';
|
||||
import Timeline from './Timeline';
|
||||
import ControlButton from 'Components/Session_/Player/Controls/ControlButton';
|
||||
|
||||
import { SKIP_INTERVALS } from 'Components/Session_/Player/Controls/Controls'
|
||||
import styles from 'Components/Session_/Player/Controls/controls.module.css';
|
||||
|
||||
function Controls(props: any) {
|
||||
|
|
|
|||
|
|
@ -74,17 +74,21 @@ function WebPlayer(props: any) {
|
|||
}
|
||||
}, [session.events, session.errors, contextValue.player])
|
||||
|
||||
const isPlayerReady = contextValue.store?.get().ready
|
||||
const { ready: isPlayerReady, firstVisualEvent: visualOffset } = contextValue.store?.get() || {}
|
||||
|
||||
React.useEffect(() => {
|
||||
if (showNoteModal) {
|
||||
contextValue.player.pause()
|
||||
}
|
||||
|
||||
if (activeTab === '' && !showNoteModal && isPlayerReady) {
|
||||
contextValue.player && contextValue.player.play()
|
||||
if (activeTab === '' && !showNoteModal && isPlayerReady && contextValue.player) {
|
||||
contextValue.player.play()
|
||||
|
||||
if (visualOffset !== 0) {
|
||||
contextValue.player.jump(visualOffset)
|
||||
}
|
||||
}
|
||||
}, [activeTab, isPlayerReady, showNoteModal])
|
||||
}, [activeTab, isPlayerReady, showNoteModal, visualOffset])
|
||||
|
||||
// LAYOUT (TODO: local layout state - useContext or something..)
|
||||
useEffect(
|
||||
|
|
@ -100,7 +104,7 @@ function WebPlayer(props: any) {
|
|||
contextValue.player.play();
|
||||
};
|
||||
|
||||
if (!session) return <Loader size={75} style={{ position: 'fixed', top: '50%', left: '50%', transform: 'translateX(-50%)' }} />;
|
||||
if (!session.sessionId) return <Loader size={75} style={{ position: 'fixed', top: '50%', left: '50%', transform: 'translateX(-50%)', height: 75 }} />;
|
||||
|
||||
return (
|
||||
<PlayerContext.Provider value={contextValue}>
|
||||
|
|
|
|||
|
|
@ -492,5 +492,5 @@ function Performance({
|
|||
}
|
||||
|
||||
export const ConnectedPerformance = connect((state: any) => ({
|
||||
userDeviceHeapSize: state.getIn(['sessions', 'current']).userDeviceHeapSize,
|
||||
userDeviceHeapSize: state.getIn(['sessions', 'current']).userDeviceHeapSize || 0,
|
||||
}))(observer(Performance));
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ import PlayerControls from './components/PlayerControls';
|
|||
import styles from './controls.module.css';
|
||||
import XRayButton from 'Shared/XRayButton';
|
||||
|
||||
const SKIP_INTERVALS = {
|
||||
export const SKIP_INTERVALS = {
|
||||
2: 2e3,
|
||||
5: 5e3,
|
||||
10: 1e4,
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ export default class Signup extends React.Component {
|
|||
|
||||
getHealth = async () => {
|
||||
this.setState({ healthStatusLoading: true });
|
||||
const { healthMap } = await getHealthRequest();
|
||||
const { healthMap } = await getHealthRequest(true);
|
||||
this.setState({ healthStatus: healthMap, healthStatusLoading: false });
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,10 +6,7 @@ import stl from './FilterModal.module.css';
|
|||
import { filtersMap } from 'Types/filter/newFilter';
|
||||
import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG';
|
||||
|
||||
function filterJson(
|
||||
jsonObj: Record<string, any>,
|
||||
excludeKeys: string[] = []
|
||||
): Record<string, any> {
|
||||
function filterJson(jsonObj: Record<string, any>, excludeKeys: string[] = []): Record<string, any> {
|
||||
let filtered: Record<string, any> = {};
|
||||
|
||||
for (const key in jsonObj) {
|
||||
|
|
@ -26,37 +23,39 @@ export const getMatchingEntries = (searchQuery: string, filters: Record<string,
|
|||
const matchingCategories: string[] = [];
|
||||
const matchingFilters: Record<string, any> = {};
|
||||
const lowerCaseQuery = searchQuery.toLowerCase();
|
||||
|
||||
if (lowerCaseQuery.length === 0) return {
|
||||
matchingCategories: Object.keys(filters),
|
||||
matchingFilters: filters,
|
||||
};
|
||||
|
||||
Object.keys(filters).forEach(name => {
|
||||
if (lowerCaseQuery.length === 0)
|
||||
return {
|
||||
matchingCategories: Object.keys(filters),
|
||||
matchingFilters: filters,
|
||||
};
|
||||
|
||||
Object.keys(filters).forEach((name) => {
|
||||
if (name.toLocaleLowerCase().includes(lowerCaseQuery)) {
|
||||
matchingCategories.push(name);
|
||||
matchingFilters[name] = filters[name];
|
||||
} else {
|
||||
const filtersQuery = filters[name]
|
||||
.filter((filterOption: any) => filterOption.label.toLocaleLowerCase().includes(lowerCaseQuery))
|
||||
const filtersQuery = filters[name].filter((filterOption: any) =>
|
||||
filterOption.label.toLocaleLowerCase().includes(lowerCaseQuery)
|
||||
);
|
||||
|
||||
if (filtersQuery.length > 0) matchingFilters[name] = filtersQuery
|
||||
filtersQuery.length > 0 && matchingCategories.push(name);
|
||||
}
|
||||
})
|
||||
if (filtersQuery.length > 0) matchingFilters[name] = filtersQuery;
|
||||
filtersQuery.length > 0 && matchingCategories.push(name);
|
||||
}
|
||||
});
|
||||
|
||||
return { matchingCategories, matchingFilters };
|
||||
}
|
||||
};
|
||||
|
||||
interface Props {
|
||||
filters: any,
|
||||
onFilterClick?: (filter: any) => void,
|
||||
filterSearchList: any,
|
||||
filters: any;
|
||||
onFilterClick?: (filter: any) => void;
|
||||
filterSearchList: any;
|
||||
// metaOptions: any,
|
||||
isMainSearch?: boolean,
|
||||
fetchingFilterSearchList: boolean,
|
||||
searchQuery?: string,
|
||||
excludeFilterKeys?: Array<string>
|
||||
isMainSearch?: boolean;
|
||||
fetchingFilterSearchList: boolean;
|
||||
searchQuery?: string;
|
||||
excludeFilterKeys?: Array<string>;
|
||||
}
|
||||
function FilterModal(props: Props) {
|
||||
const {
|
||||
|
|
@ -66,42 +65,59 @@ function FilterModal(props: Props) {
|
|||
isMainSearch = false,
|
||||
fetchingFilterSearchList,
|
||||
searchQuery = '',
|
||||
excludeFilterKeys = []
|
||||
excludeFilterKeys = [],
|
||||
} = props;
|
||||
const showSearchList = isMainSearch && searchQuery.length > 0;
|
||||
|
||||
const onFilterSearchClick = (filter: any) => {
|
||||
const _filter = filtersMap[filter.type];
|
||||
const _filter = {...filtersMap[filter.type]};
|
||||
_filter.value = [filter.value];
|
||||
onFilterClick(_filter);
|
||||
}
|
||||
};
|
||||
|
||||
const { matchingCategories, matchingFilters } = getMatchingEntries(searchQuery, filterJson(filters, excludeFilterKeys));
|
||||
const { matchingCategories, matchingFilters } = getMatchingEntries(
|
||||
searchQuery,
|
||||
filterJson(filters, excludeFilterKeys)
|
||||
);
|
||||
|
||||
const isResultEmpty = (!filterSearchList || Object.keys(filterSearchList).length === 0)
|
||||
&& matchingCategories.length === 0 && Object.keys(matchingFilters).length === 0
|
||||
const isResultEmpty =
|
||||
(!filterSearchList || Object.keys(filterSearchList).length === 0) &&
|
||||
matchingCategories.length === 0 &&
|
||||
Object.keys(matchingFilters).length === 0;
|
||||
|
||||
return (
|
||||
<div className={stl.wrapper} style={{ width: '480px', maxHeight: '380px', overflowY: 'auto'}}>
|
||||
<div className={searchQuery && !isResultEmpty ? 'mb-6' : ''} style={{ columns: matchingCategories.length > 1 ? 'auto 200px' : 1 }}>
|
||||
{matchingCategories.map((key) => {
|
||||
return (
|
||||
<div className="mb-6 flex flex-col gap-2" key={key}>
|
||||
<div className="uppercase font-medium mb-1 color-gray-medium tracking-widest text-sm">{key}</div>
|
||||
<div>
|
||||
{matchingFilters[key] && matchingFilters[key].map((filter: any) => (
|
||||
<div key={filter.label} className={cn(stl.optionItem, "flex items-center py-2 cursor-pointer -mx-2 px-2")} onClick={() => onFilterClick({ ...filter, value: [''] })}>
|
||||
<Icon name={filter.icon} size="16"/>
|
||||
<span className="ml-2">{filter.label}</span>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
<div className={stl.wrapper} style={{ width: '480px', maxHeight: '380px', overflowY: 'auto' }}>
|
||||
<div
|
||||
className={searchQuery && !isResultEmpty ? 'mb-6' : ''}
|
||||
style={{ columns: matchingCategories.length > 1 ? 'auto 200px' : 1 }}
|
||||
>
|
||||
{matchingCategories.map((key) => {
|
||||
return (
|
||||
<div className="mb-6 flex flex-col gap-2" key={key}>
|
||||
<div className="uppercase font-medium mb-1 color-gray-medium tracking-widest text-sm">
|
||||
{key}
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
</div>
|
||||
{ showSearchList && (
|
||||
<div>
|
||||
{matchingFilters[key] &&
|
||||
matchingFilters[key].map((filter: any) => (
|
||||
<div
|
||||
key={filter.label}
|
||||
className={cn(
|
||||
stl.optionItem,
|
||||
'flex items-center py-2 cursor-pointer -mx-2 px-2'
|
||||
)}
|
||||
onClick={() => onFilterClick({ ...filter, value: [''] })}
|
||||
>
|
||||
<Icon name={filter.icon} size="16" />
|
||||
<span className="ml-2">{filter.label}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{showSearchList && (
|
||||
<Loader size="small" loading={fetchingFilterSearchList}>
|
||||
<div className="-mx-6 px-6">
|
||||
{isResultEmpty && !fetchingFilterSearchList ? (
|
||||
|
|
@ -109,30 +125,38 @@ function FilterModal(props: Props) {
|
|||
<AnimatedSVG name={ICONS.NO_SEARCH_RESULTS} size={180} />
|
||||
<div className="color-gray-medium font-medium px-3"> No Suggestions Found </div>
|
||||
</div>
|
||||
) : Object.keys(filterSearchList).map((key, index) => {
|
||||
const filter = filterSearchList[key];
|
||||
const option = filtersMap[key];
|
||||
return option ? (
|
||||
<div
|
||||
key={index}
|
||||
className={cn('mb-3')}
|
||||
>
|
||||
<div className="font-medium uppercase color-gray-medium text-sm mb-2">{option.label}</div>
|
||||
<div>
|
||||
{filter.map((f, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className={cn(stl.filterSearchItem, "cursor-pointer px-3 py-1 text-sm flex items-center")}
|
||||
onClick={() => onFilterSearchClick({ type: key, value: f.value })}
|
||||
>
|
||||
<Icon className="mr-2" name={option.icon} size="16" />
|
||||
<div className="whitespace-nowrap text-ellipsis overflow-hidden">{f.value}</div>
|
||||
</div>
|
||||
))}
|
||||
) : (
|
||||
Object.keys(filterSearchList).map((key, index) => {
|
||||
const filter = filterSearchList[key];
|
||||
const option = filtersMap[key];
|
||||
return option ? (
|
||||
<div key={index} className={cn('mb-3')}>
|
||||
<div className="font-medium uppercase color-gray-medium mb-2">
|
||||
{option.label}
|
||||
</div>
|
||||
<div>
|
||||
{filter.map((f, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className={cn(
|
||||
stl.filterSearchItem,
|
||||
'cursor-pointer px-3 py-1 flex items-center'
|
||||
)}
|
||||
onClick={() => onFilterSearchClick({ type: key, value: f.value })}
|
||||
>
|
||||
<Icon className="mr-2" name={option.icon} size="16" />
|
||||
<div className="whitespace-nowrap text-ellipsis overflow-hidden">
|
||||
{f.value}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : <></>;
|
||||
})}
|
||||
) : (
|
||||
<></>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</div>
|
||||
</Loader>
|
||||
)}
|
||||
|
|
@ -141,14 +165,15 @@ function FilterModal(props: Props) {
|
|||
}
|
||||
|
||||
export default connect((state: any, props: any) => {
|
||||
return ({
|
||||
filters: props.isLive ? state.getIn([ 'search', 'filterListLive' ]) : state.getIn([ 'search', 'filterList' ]),
|
||||
filterSearchList: props.isLive ? state.getIn([ 'liveSearch', 'filterSearchList' ]) : state.getIn([ 'search', 'filterSearchList' ]),
|
||||
// filterSearchList: state.getIn([ 'search', 'filterSearchList' ]),
|
||||
// liveFilterSearchList: state.getIn([ 'liveSearch', 'filterSearchList' ]),
|
||||
// metaOptions: state.getIn([ 'customFields', 'list' ]),
|
||||
return {
|
||||
filters: props.isLive
|
||||
? state.getIn(['search', 'filterListLive'])
|
||||
: state.getIn(['search', 'filterList']),
|
||||
filterSearchList: props.isLive
|
||||
? state.getIn(['liveSearch', 'filterSearchList'])
|
||||
: state.getIn(['search', 'filterSearchList']),
|
||||
fetchingFilterSearchList: props.isLive
|
||||
? state.getIn(['liveSearch', 'fetchFilterSearch', 'loading'])
|
||||
: state.getIn(['search', 'fetchFilterSearch', 'loading']),
|
||||
})
|
||||
? state.getIn(['liveSearch', 'fetchFilterSearch', 'loading'])
|
||||
: state.getIn(['search', 'fetchFilterSearch', 'loading']),
|
||||
};
|
||||
})(FilterModal);
|
||||
|
|
|
|||
|
|
@ -77,12 +77,12 @@ function LiveFilterModal(props: Props) {
|
|||
key={index}
|
||||
className={cn('mb-3')}
|
||||
>
|
||||
<div className="font-medium uppercase color-gray-medium text-sm mb-2">{option.label}</div>
|
||||
<div className="font-medium uppercase color-gray-medium mb-2">{option.label}</div>
|
||||
<div>
|
||||
{filter.map((f, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className={cn(stl.filterSearchItem, "cursor-pointer px-3 py-1 text-sm flex items-center")}
|
||||
className={cn(stl.filterSearchItem, "cursor-pointer px-3 py-1 flex items-center")}
|
||||
onClick={() => onFilterSearchClick({ type: key, value: f.value })}
|
||||
>
|
||||
<Icon className="mr-2" name={option.icon} size="16" />
|
||||
|
|
@ -106,12 +106,12 @@ function LiveFilterModal(props: Props) {
|
|||
key={index}
|
||||
className={cn('mb-3')}
|
||||
>
|
||||
<div className="font-medium uppercase color-gray-medium text-sm mb-2">{option.label}</div>
|
||||
<div className="font-medium uppercase color-gray-medium mb-2">{option.label}</div>
|
||||
<div>
|
||||
{filter.map((f, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className={cn(stl.filterSearchItem, "cursor-pointer px-3 py-1 text-sm flex items-center")}
|
||||
className={cn(stl.filterSearchItem, "cursor-pointer px-3 py-1 flex items-center")}
|
||||
onClick={() => onFilterSearchClick({ type: key, value: f.value })}
|
||||
>
|
||||
<Icon className="mr-2" name={option.icon} size="16" />
|
||||
|
|
|
|||
|
|
@ -2,51 +2,40 @@ import React from 'react';
|
|||
import SessionSearchField from 'Shared/SessionSearchField';
|
||||
import SavedSearch from 'Shared/SavedSearch';
|
||||
import { Button } from 'UI';
|
||||
// import { clearSearch } from 'Duck/search';
|
||||
import { connect } from 'react-redux';
|
||||
import { edit as editFilter, addFilterByKeyAndValue, clearSearch, fetchFilterSearch } from 'Duck/search';
|
||||
import { clearSearch } from 'Duck/search';
|
||||
|
||||
interface Props {
|
||||
clearSearch: () => void;
|
||||
appliedFilter: any;
|
||||
optionsReady: boolean;
|
||||
editFilter: any,
|
||||
addFilterByKeyAndValue: any,
|
||||
fetchFilterSearch: any,
|
||||
clearSearch: () => void;
|
||||
appliedFilter: any;
|
||||
}
|
||||
const MainSearchBar = (props: Props) => {
|
||||
const { appliedFilter } = props;
|
||||
const { appliedFilter } = props;
|
||||
const hasFilters = appliedFilter && appliedFilter.filters && appliedFilter.filters.size > 0;
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
<div style={{ width: "60%", marginRight: "10px"}}>
|
||||
<SessionSearchField
|
||||
editFilter={props.editFilter}
|
||||
addFilterByKeyAndValue={props.addFilterByKeyAndValue}
|
||||
clearSearch={props.clearSearch}
|
||||
fetchFilterSearch={props.fetchFilterSearch}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center" style={{ width: "40%"}}>
|
||||
<div style={{ width: '60%', marginRight: '10px' }}>
|
||||
<SessionSearchField />
|
||||
</div>
|
||||
<div className="flex items-center" style={{ width: '40%' }}>
|
||||
<SavedSearch />
|
||||
<Button
|
||||
variant="text-primary"
|
||||
className="ml-auto font-medium"
|
||||
disabled={!hasFilters}
|
||||
onClick={() => props.clearSearch()}
|
||||
variant="text-primary"
|
||||
className="ml-auto font-medium"
|
||||
disabled={!hasFilters}
|
||||
onClick={() => props.clearSearch()}
|
||||
>
|
||||
Clear Search
|
||||
Clear Search
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<div className="relative">
|
||||
<Input
|
||||
icon="search"
|
||||
onFocus={() => 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 (
|
||||
<div className="relative">
|
||||
<Input
|
||||
icon="search"
|
||||
onFocus={() => 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 && (
|
||||
<div className="absolute left-0 border shadow rounded bg-white z-50">
|
||||
<FilterModal
|
||||
searchQuery={searchQuery}
|
||||
isMainSearch={true}
|
||||
onFilterClick={onAddFilter}
|
||||
isLive={isRoute(ASSIST_ROUTE, window.location.pathname) || window.location.pathname.includes('multiview')}
|
||||
// filters={isRoute(ASSIST_ROUTE, window.location.pathname) ? props.filterListLive : props.filterList }
|
||||
// filterSearchList={isRoute(ASSIST_ROUTE, window.location.pathname) ? props.filterSearchListLive : props.filterSearchList }
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{showModal && (
|
||||
<div className="absolute left-0 border shadow rounded bg-white z-50">
|
||||
<FilterModal
|
||||
searchQuery={searchQuery}
|
||||
isMainSearch={true}
|
||||
onFilterClick={onAddFilter}
|
||||
isLive={isLive}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -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 : '' })
|
||||
}}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import type { Timed } from './types';
|
||||
|
||||
export default class ListWalker<T extends Timed> {
|
||||
private p = 0
|
||||
private p = 0 /* Pointer to the "current" item */
|
||||
constructor(private _list: Array<T> = []) {}
|
||||
|
||||
append(m: T): void {
|
||||
|
|
@ -130,7 +130,7 @@ export default class ListWalker<T extends Timed> {
|
|||
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<any> | undefined): Promise<void> {
|
||||
// Applying only in increment order for now
|
||||
if (t < this.timeNow) {
|
||||
this.reset();
|
||||
|
|
@ -138,23 +138,8 @@ export default class ListWalker<T extends Timed> {
|
|||
|
||||
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<any> | undefined): Promise<void> {
|
||||
// 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 }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<any>/*<LocationEvent>*/ = 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<Message> = []
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -66,7 +66,6 @@ export default class WebPlayer extends Player {
|
|||
|
||||
// @ts-ignore
|
||||
window.playerJumpToTime = this.jump.bind(this)
|
||||
|
||||
}
|
||||
|
||||
updateLists = (session: any) => {
|
||||
|
|
|
|||
|
|
@ -39,7 +39,6 @@ export default class DOMManager extends ListWalker<Message> {
|
|||
private readonly vTexts: Map<number, VText> = new Map() // map vs object here?
|
||||
private readonly vElements: Map<number, VElement> = new Map()
|
||||
private readonly vRoots: Map<number, VShadowRoot | VDocument> = new Map()
|
||||
private activeIframeRoots: Map<number, number> = new Map()
|
||||
private styleSheets: Map<number, CSSStyleSheet> = new Map()
|
||||
private ppStyleSheets: Map<number, PostponedStyleSheet> = new Map()
|
||||
private stringDict: Record<number,string> = {}
|
||||
|
|
@ -197,7 +196,6 @@ export default class DOMManager extends ListWalker<Message> {
|
|||
// 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<Message> {
|
|||
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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import BaseService from './BaseService';
|
||||
|
||||
export default class HealthService extends BaseService {
|
||||
fetchStatus(): Promise<any> {
|
||||
return this.client.get('/health')
|
||||
fetchStatus(isPublic?: boolean): Promise<any> {
|
||||
return this.client.get(isPublic ? '/health' : '/healthz')
|
||||
.then(r => r.json())
|
||||
.then(j => j.data || {})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue