Merge pull request #1084 from openreplay/dev

(chores) v1.11.0
This commit is contained in:
Mehdi Osman 2023-03-31 16:05:34 +02:00 committed by GitHub
commit 409b890d97
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
37 changed files with 442 additions and 402 deletions

View file

@ -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

View file

@ -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()

View file

@ -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": []}

View file

@ -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)}

View file

@ -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,

View file

@ -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()

View file

@ -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": []}

View file

@ -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)}

View file

@ -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)

View file

@ -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;

View file

@ -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"

View file

@ -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));

View file

@ -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) {

View file

@ -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}>

View file

@ -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));

View file

@ -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,

View file

@ -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 });
}

View file

@ -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);

View file

@ -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" />

View file

@ -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);

View file

@ -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);

View file

@ -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 : '' })
}}
/>

View file

@ -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;

View file

@ -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;

View file

@ -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 }
}
}

View file

@ -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;

View file

@ -66,7 +66,6 @@ export default class WebPlayer extends Player {
// @ts-ignore
window.playerJumpToTime = this.jump.bind(this)
}
updateLists = (session: any) => {

View file

@ -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

View file

@ -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
}

View file

@ -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;
}
}
}

View file

@ -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 || {})
}

View file

@ -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;
}

View file

@ -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;
}
}
}

View file

@ -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

View file

@ -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

View file

@ -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()))
}

View file

@ -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
}