feat(chalice): cards 2/5

feat(DB): transform predefined metrics to new format
This commit is contained in:
Taha Yassine Kraiem 2022-12-02 16:34:16 +01:00
parent e634bacc3a
commit 5e80a016bf
6 changed files with 345 additions and 170 deletions

View file

@ -9,7 +9,7 @@ from chalicelib.utils.TimeUTC import TimeUTC
PIE_CHART_GROUP = 5
def __try_live(project_id, data: schemas.TryCardSchema):
def __try_live(project_id, data: schemas.CreateCardSchema):
results = []
for i, s in enumerate(data.series):
s.filter.startDate = data.startTimestamp
@ -42,11 +42,11 @@ def __try_live(project_id, data: schemas.TryCardSchema):
return results
def __is_funnel_chart(data: schemas.TryCardSchema):
def __is_funnel_chart(data: schemas.CreateCardSchema):
return data.metric_type == schemas.MetricType.funnel
def __get_funnel_chart(project_id, data: schemas.TryCardSchema):
def __get_funnel_chart(project_id, data: schemas.CreateCardSchema):
if len(data.series) == 0:
return {
"stages": [],
@ -94,7 +94,7 @@ def __get_sessions_list(project_id, user_id, data):
return sessions.search_sessions(data=data.series[0].filter, project_id=project_id, user_id=user_id)
def merged_live(project_id, data: schemas.TryCardSchema, user_id=None):
def merged_live(project_id, data: schemas.CreateCardSchema, user_id=None):
if __is_funnel_chart(data):
return __get_funnel_chart(project_id=project_id, data=data)
elif __is_errors_list(data):
@ -130,7 +130,7 @@ def __merge_metric_with_data(metric, data: Union[schemas.CustomMetricChartPayloa
def make_chart(project_id, user_id, metric_id, data: schemas.CustomMetricChartPayloadSchema, metric=None):
if metric is None:
metric = get(metric_id=metric_id, project_id=project_id, user_id=user_id, flatten=False)
metric = get_card(metric_id=metric_id, project_id=project_id, user_id=user_id, flatten=False)
if metric is None:
return None
metric: schemas.CreateCardSchema = __merge_metric_with_data(metric=metric, data=data)
@ -153,7 +153,7 @@ def make_chart(project_id, user_id, metric_id, data: schemas.CustomMetricChartPa
def get_sessions(project_id, user_id, metric_id, data: schemas.CustomMetricSessionsPayloadSchema):
metric = get(metric_id=metric_id, project_id=project_id, user_id=user_id, flatten=False)
metric = get_card(metric_id=metric_id, project_id=project_id, user_id=user_id, flatten=False)
if metric is None:
return None
metric: schemas.CreateCardSchema = __merge_metric_with_data(metric=metric, data=data)
@ -172,7 +172,7 @@ def get_sessions(project_id, user_id, metric_id, data: schemas.CustomMetricSessi
def get_funnel_issues(project_id, user_id, metric_id, data: schemas.CustomMetricSessionsPayloadSchema):
metric = get(metric_id=metric_id, project_id=project_id, user_id=user_id, flatten=False)
metric = get_card(metric_id=metric_id, project_id=project_id, user_id=user_id, flatten=False)
if metric is None:
return None
metric: schemas.CreateCardSchema = __merge_metric_with_data(metric=metric, data=data)
@ -188,7 +188,7 @@ def get_funnel_issues(project_id, user_id, metric_id, data: schemas.CustomMetric
def get_errors_list(project_id, user_id, metric_id, data: schemas.CustomMetricSessionsPayloadSchema):
metric = get(metric_id=metric_id, project_id=project_id, user_id=user_id, flatten=False)
metric = get_card(metric_id=metric_id, project_id=project_id, user_id=user_id, flatten=False)
if metric is None:
return None
metric: schemas.CreateCardSchema = __merge_metric_with_data(metric=metric, data=data)
@ -227,35 +227,35 @@ def create(project_id, user_id, data: schemas.CreateCardSchema, dashboard=False)
_data[f"index_{i}"] = i
_data[f"filter_{i}"] = s.filter.json()
series_len = len(data.series)
data.series = None
params = {"user_id": user_id, "project_id": project_id,
"default_config": json.dumps(data.config.dict()),
**data.dict(), **_data}
query = cur.mogrify(f"""\
WITH m AS (INSERT INTO metrics (project_id, user_id, name, is_public,
view_type, metric_type, metric_of, metric_value,
metric_format, default_config)
VALUES (%(project_id)s, %(user_id)s, %(name)s, %(is_public)s,
%(view_type)s, %(metric_type)s, %(metric_of)s, %(metric_value)s,
%(metric_format)s, %(default_config)s)
RETURNING *)
INSERT
INTO metric_series(metric_id, index, name, filter)
VALUES {",".join([f"((SELECT metric_id FROM m), %(index_{i})s, %(name_{i})s, %(filter_{i})s::jsonb)"
for i in range(series_len)])}
RETURNING metric_id;""", params)
params = {"user_id": user_id, "project_id": project_id, **data.dict(), **_data}
params["default_config"] = json.dumps(data.default_config.dict())
query = """INSERT INTO metrics_clone (project_id, user_id, name, is_public,
view_type, metric_type, metric_of, metric_value,
metric_format, default_config)
VALUES (%(project_id)s, %(user_id)s, %(name)s, %(is_public)s,
%(view_type)s, %(metric_type)s, %(metric_of)s, %(metric_value)s,
%(metric_format)s, %(default_config)s)
RETURNING metric_id"""
if len(data.series) > 0:
query = f"""WITH m AS ({query})
INSERT INTO metric_series(metric_id, index, name, filter)
VALUES {",".join([f"((SELECT metric_id FROM m), %(index_{i})s, %(name_{i})s, %(filter_{i})s::jsonb)"
for i in range(series_len)])}
RETURNING metric_id;"""
cur.execute(
query
)
query = cur.mogrify(query, params)
print("-------")
print(query)
print("-------")
cur.execute(query)
r = cur.fetchone()
if dashboard:
return r["metric_id"]
return {"data": get(metric_id=r["metric_id"], project_id=project_id, user_id=user_id)}
return {"data": get_card(metric_id=r["metric_id"], project_id=project_id, user_id=user_id)}
def update(metric_id, user_id, project_id, data: schemas.UpdateCardSchema):
metric = get(metric_id=metric_id, project_id=project_id, user_id=user_id, flatten=False)
metric = get_card(metric_id=metric_id, project_id=project_id, user_id=user_id, flatten=False)
if metric is None:
return None
series_ids = [r["seriesId"] for r in metric["series"]]
@ -312,7 +312,7 @@ def update(metric_id, user_id, project_id, data: schemas.UpdateCardSchema):
RETURNING 1)""")
query = cur.mogrify(f"""\
{"WITH " if len(sub_queries) > 0 else ""}{",".join(sub_queries)}
UPDATE metrics
UPDATE metrics_clone
SET name = %(name)s, is_public= %(is_public)s,
view_type= %(view_type)s, metric_type= %(metric_type)s,
metric_of= %(metric_of)s, metric_value= %(metric_value)s,
@ -324,7 +324,7 @@ def update(metric_id, user_id, project_id, data: schemas.UpdateCardSchema):
AND (user_id = %(user_id)s OR is_public)
RETURNING metric_id;""", params)
cur.execute(query)
return get(metric_id=metric_id, project_id=project_id, user_id=user_id)
return get_card(metric_id=metric_id, project_id=project_id, user_id=user_id)
def get_all(project_id, user_id, include_series=False):
@ -333,31 +333,31 @@ def get_all(project_id, user_id, include_series=False):
if include_series:
sub_join = """LEFT JOIN LATERAL (SELECT COALESCE(jsonb_agg(metric_series.* ORDER BY index),'[]'::jsonb) AS series
FROM metric_series
WHERE metric_series.metric_id = metrics.metric_id
WHERE metric_series.metric_id = metrics_clone.metric_id
AND metric_series.deleted_at ISNULL
) AS metric_series ON (TRUE)"""
cur.execute(
cur.mogrify(
f"""SELECT *
FROM metrics
FROM metrics_clone
{sub_join}
LEFT JOIN LATERAL (SELECT COALESCE(jsonb_agg(connected_dashboards.* ORDER BY is_public,name),'[]'::jsonb) AS dashboards
FROM (SELECT DISTINCT dashboard_id, name, is_public
FROM dashboards INNER JOIN dashboard_widgets USING (dashboard_id)
WHERE deleted_at ISNULL
AND dashboard_widgets.metric_id = metrics.metric_id
AND dashboard_widgets.metric_id = metrics_clone.metric_id
AND project_id = %(project_id)s
AND ((dashboards.user_id = %(user_id)s OR is_public))) AS connected_dashboards
) AS connected_dashboards ON (TRUE)
LEFT JOIN LATERAL (SELECT email AS owner_email
FROM users
WHERE deleted_at ISNULL
AND users.user_id = metrics.user_id
AND users.user_id = metrics_clone.user_id
) AS owner ON (TRUE)
WHERE metrics.project_id = %(project_id)s
AND metrics.deleted_at ISNULL
AND (user_id = %(user_id)s OR metrics.is_public)
ORDER BY metrics.edited_at DESC, metrics.created_at DESC;""",
WHERE metrics_clone.project_id = %(project_id)s
AND metrics_clone.deleted_at ISNULL
AND (user_id = %(user_id)s OR metrics_clone.is_public)
ORDER BY metrics_clone.edited_at DESC, metrics_clone.created_at DESC;""",
{"project_id": project_id, "user_id": user_id}
)
)
@ -379,7 +379,7 @@ def delete(project_id, metric_id, user_id):
with pg_client.PostgresClient() as cur:
cur.execute(
cur.mogrify("""\
UPDATE public.metrics
UPDATE public.metrics_clone
SET deleted_at = timezone('utc'::text, now()), edited_at = timezone('utc'::text, now())
WHERE project_id = %(project_id)s
AND metric_id = %(metric_id)s
@ -390,37 +390,37 @@ def delete(project_id, metric_id, user_id):
return {"state": "success"}
def get(metric_id, project_id, user_id, flatten=True):
def get_card(metric_id, project_id, user_id, flatten=True):
with pg_client.PostgresClient() as cur:
cur.execute(
cur.mogrify(
"""SELECT *, default_config AS config
FROM metrics
LEFT JOIN LATERAL (SELECT COALESCE(jsonb_agg(metric_series.* ORDER BY index),'[]'::jsonb) AS series
FROM metric_series
WHERE metric_series.metric_id = metrics.metric_id
AND metric_series.deleted_at ISNULL
) AS metric_series ON (TRUE)
LEFT JOIN LATERAL (SELECT COALESCE(jsonb_agg(connected_dashboards.* ORDER BY is_public,name),'[]'::jsonb) AS dashboards
FROM (SELECT dashboard_id, name, is_public
FROM dashboards
WHERE deleted_at ISNULL
AND project_id = %(project_id)s
AND ((user_id = %(user_id)s OR is_public))) AS connected_dashboards
) AS connected_dashboards ON (TRUE)
LEFT JOIN LATERAL (SELECT email AS owner_email
FROM users
WHERE deleted_at ISNULL
AND users.user_id = metrics.user_id
) AS owner ON (TRUE)
WHERE metrics.project_id = %(project_id)s
AND metrics.deleted_at ISNULL
AND (metrics.user_id = %(user_id)s OR metrics.is_public)
AND metrics.metric_id = %(metric_id)s
ORDER BY created_at;""",
{"metric_id": metric_id, "project_id": project_id, "user_id": user_id}
)
query = cur.mogrify(
"""SELECT *, default_config AS config
FROM metrics_clone
LEFT JOIN LATERAL (SELECT COALESCE(jsonb_agg(metric_series.* ORDER BY index),'[]'::jsonb) AS series
FROM metric_series
WHERE metric_series.metric_id = metrics_clone.metric_id
AND metric_series.deleted_at ISNULL
) AS metric_series ON (TRUE)
LEFT JOIN LATERAL (SELECT COALESCE(jsonb_agg(connected_dashboards.* ORDER BY is_public,name),'[]'::jsonb) AS dashboards
FROM (SELECT dashboard_id, name, is_public
FROM dashboards INNER JOIN dashboard_widgets USING (dashboard_id)
WHERE deleted_at ISNULL
AND project_id = %(project_id)s
AND ((dashboards.user_id = %(user_id)s OR is_public))
AND metric_id = %(metric_id)s) AS connected_dashboards
) AS connected_dashboards ON (TRUE)
LEFT JOIN LATERAL (SELECT email AS owner_email
FROM users
WHERE deleted_at ISNULL
AND users.user_id = metrics_clone.user_id
) AS owner ON (TRUE)
WHERE metrics_clone.project_id = %(project_id)s
AND metrics_clone.deleted_at ISNULL
AND (metrics_clone.user_id = %(user_id)s OR metrics_clone.is_public)
AND metrics_clone.metric_id = %(metric_id)s
ORDER BY created_at;""",
{"metric_id": metric_id, "project_id": project_id, "user_id": user_id}
)
cur.execute(query)
row = cur.fetchone()
if row is None:
return None
@ -446,17 +446,17 @@ def get_with_template(metric_id, project_id, user_id, include_dashboard=True):
cur.execute(
cur.mogrify(
f"""SELECT *, default_config AS config
FROM metrics
FROM metrics_clone
LEFT JOIN LATERAL (SELECT COALESCE(jsonb_agg(metric_series.* ORDER BY index),'[]'::jsonb) AS series
FROM metric_series
WHERE metric_series.metric_id = metrics.metric_id
WHERE metric_series.metric_id = metrics_clone.metric_id
AND metric_series.deleted_at ISNULL
) AS metric_series ON (TRUE)
{sub_query}
WHERE (metrics.project_id = %(project_id)s OR metrics.project_id ISNULL)
AND metrics.deleted_at ISNULL
AND (metrics.user_id = %(user_id)s OR metrics.is_public)
AND metrics.metric_id = %(metric_id)s
WHERE (metrics_clone.project_id = %(project_id)s OR metrics_clone.project_id ISNULL)
AND metrics_clone.deleted_at ISNULL
AND (metrics_clone.user_id = %(user_id)s OR metrics_clone.is_public)
AND metrics_clone.metric_id = %(metric_id)s
ORDER BY created_at;""",
{"metric_id": metric_id, "project_id": project_id, "user_id": user_id}
)
@ -470,16 +470,16 @@ def get_series_for_alert(project_id, user_id):
cur.execute(
cur.mogrify(
"""SELECT series_id AS value,
metrics.name || '.' || (COALESCE(metric_series.name, 'series ' || index)) || '.count' AS name,
metrics_clone.name || '.' || (COALESCE(metric_series.name, 'series ' || index)) || '.count' AS name,
'count' AS unit,
FALSE AS predefined,
metric_id,
series_id
FROM metric_series
INNER JOIN metrics USING (metric_id)
WHERE metrics.deleted_at ISNULL
AND metrics.project_id = %(project_id)s
AND metrics.metric_type = 'timeseries'
INNER JOIN metrics_clone USING (metric_id)
WHERE metrics_clone.deleted_at ISNULL
AND metrics_clone.project_id = %(project_id)s
AND metrics_clone.metric_type = 'timeseries'
AND (user_id = %(user_id)s OR is_public)
ORDER BY name;""",
{"project_id": project_id, "user_id": user_id}
@ -493,20 +493,20 @@ def change_state(project_id, metric_id, user_id, status):
with pg_client.PostgresClient() as cur:
cur.execute(
cur.mogrify("""\
UPDATE public.metrics
UPDATE public.metrics_clone
SET active = %(status)s
WHERE metric_id = %(metric_id)s
AND (user_id = %(user_id)s OR is_public);""",
{"metric_id": metric_id, "status": status, "user_id": user_id})
)
return get(metric_id=metric_id, project_id=project_id, user_id=user_id)
return get_card(metric_id=metric_id, project_id=project_id, user_id=user_id)
def get_funnel_sessions_by_issue(user_id, project_id, metric_id, issue_id,
data: schemas.CustomMetricSessionsPayloadSchema
# , range_value=None, start_date=None, end_date=None
):
metric = get(metric_id=metric_id, project_id=project_id, user_id=user_id, flatten=False)
metric = get_card(metric_id=metric_id, project_id=project_id, user_id=user_id, flatten=False)
if metric is None:
return None
metric: schemas.CreateCardSchema = __merge_metric_with_data(metric=metric, data=data)

View file

@ -304,13 +304,13 @@ def search_sessions(data: schemas.SessionsSearchPayloadSchema, project_id, user_
def search2_series(data: schemas.SessionsSearchPayloadSchema, project_id: int, density: int,
view_type: schemas.MetricTimeseriesViewType, metric_type: schemas.MetricType,
metric_of: schemas.TableMetricOfType, metric_value: List):
metric_of: schemas.MetricOfTable, metric_value: List):
step_size = int(metrics_helper.__get_step_size(endTimestamp=data.endDate, startTimestamp=data.startDate,
density=density, factor=1, decimal=True))
extra_event = None
if metric_of == schemas.TableMetricOfType.visited_url:
if metric_of == schemas.MetricOfTable.visited_url:
extra_event = "events.pages"
elif metric_of == schemas.TableMetricOfType.issues and len(metric_value) > 0:
elif metric_of == schemas.MetricOfTable.issues and len(metric_value) > 0:
data.filters.append(schemas.SessionSearchFilterSchema(value=metric_value, type=schemas.FilterType.issue,
operator=schemas.SearchEventOperator._is))
full_args, query_part = search_query_parts(data=data, error_status=None, errors_only=False,

View file

@ -46,11 +46,12 @@ def pin_dashboard(projectId: int, dashboardId: int, context: schemas.CurrentCont
return {"data": dashboards.pin_dashboard(project_id=projectId, user_id=context.user_id, dashboard_id=dashboardId)}
@app.post('/{projectId}/dashboards/{dashboardId}/cards', tags=["cards"])
@app.post('/{projectId}/dashboards/{dashboardId}/widgets', tags=["dashboard"])
@app.put('/{projectId}/dashboards/{dashboardId}/widgets', tags=["dashboard"])
def add_widget_to_dashboard(projectId: int, dashboardId: int,
data: schemas.AddWidgetToDashboardPayloadSchema = Body(...),
context: schemas.CurrentContext = Depends(OR_context)):
def add_card_to_dashboard(projectId: int, dashboardId: int,
data: schemas.AddWidgetToDashboardPayloadSchema = Body(...),
context: schemas.CurrentContext = Depends(OR_context)):
return {"data": dashboards.add_widget(project_id=projectId, user_id=context.user_id, dashboard_id=dashboardId,
data=data)}
@ -100,7 +101,7 @@ def get_templates(projectId: int, context: schemas.CurrentContext = Depends(OR_c
@app.put('/{projectId}/metrics/try', tags=["dashboard"])
@app.post('/{projectId}/custom_metrics/try', tags=["customMetrics"])
@app.put('/{projectId}/custom_metrics/try', tags=["customMetrics"])
def try_custom_metric(projectId: int, data: schemas.TryCardSchema = Body(...),
def try_custom_metric(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)}
@ -132,7 +133,6 @@ def try_custom_metric_funnel_issues(projectId: int, data: schemas.CustomMetricSe
@app.put('/{projectId}/custom_metrics', tags=["customMetrics"])
def create_card(projectId: int, data: schemas.CreateCardSchema = Body(...),
context: schemas.CurrentContext = Depends(OR_context)):
return {"data": data.dict()}
return custom_metrics.create(project_id=projectId, user_id=context.user_id, data=data)
@ -140,35 +140,6 @@ def create_card(projectId: int, data: schemas.CreateCardSchema = Body(...),
@app.get('/{projectId}/metrics', tags=["dashboard"])
@app.get('/{projectId}/custom_metrics', tags=["customMetrics"])
def get_cards(projectId: int, context: schemas.CurrentContext = Depends(OR_context)):
return {
"data": [
{
"metricId": 1180,
"projectId": 5053,
"userId": 283,
"name": "ts1",
"isPublic": true,
"createdAt": 1669818461027,
"deletedAt": null,
"active": true,
"metricType": "timeseries",
"viewType": "lineChart",
"metricOf": "sessionCount",
"metricValue": [],
"metricFormat": "sessionCount",
"editedAt": 1669818461027,
"category": "custom",
"predefinedKey": null,
"defaultConfig": {
"col": 2,
"row": 2,
"position": 0
},
"dashboards": [],
"ownerEmail": "tahay@asayer.io"
}
]
}
return {"data": custom_metrics.get_all(project_id=projectId, user_id=context.user_id)}
@ -176,7 +147,7 @@ def get_cards(projectId: int, context: schemas.CurrentContext = Depends(OR_conte
@app.get('/{projectId}/metrics/{metric_id}', tags=["dashboard"])
@app.get('/{projectId}/custom_metrics/{metric_id}', tags=["customMetrics"])
def get_card(projectId: int, metric_id: str, context: schemas.CurrentContext = Depends(OR_context)):
data = custom_metrics.get(project_id=projectId, user_id=context.user_id, metric_id=metric_id)
data = custom_metrics.get_card(project_id=projectId, user_id=context.user_id, metric_id=metric_id)
if data is None:
return {"errors": ["card not found"]}
return {"data": data}

View file

@ -580,7 +580,7 @@ class _SessionSearchEventSchema(_SessionSearchEventRaw):
class SessionSearchFilterSchema(__MixedSearchFilter):
is_event: bool = Field(False, const=False)
value: Union[Optional[Union[IssueType, PlatformType, int, str]],
Optional[List[Union[IssueType, PlatformType, int, str]]]] = Field(...)
Optional[List[Union[IssueType, PlatformType, int, str]]]] = Field(...)
type: FilterType = Field(...)
operator: Union[SearchEventOperator, MathOperator] = Field(...)
source: Optional[Union[ErrorSource, str]] = Field(default=None)
@ -792,26 +792,94 @@ class MetricTableViewType(str, Enum):
pie_chart = "pieChart"
class MetricOtherViewType(str, Enum):
other_chart = "chart"
class MetricType(str, Enum):
timeseries = "timeseries"
table = "table"
predefined = "predefined"
funnel = "funnel"
errors = "errors"
performance = "performance"
resources = "resources"
web_vital = "webVital"
pathAnalysis = "pathAnalysis"
retention = "retention"
stickiness = "stickiness"
click_map = "clickMap"
class TableMetricOfType(str, Enum):
user_os = FilterType.user_os.value
user_browser = FilterType.user_browser.value
user_device = FilterType.user_device.value
user_country = FilterType.user_country.value
user_id = FilterType.user_id.value
issues = FilterType.issue.value
visited_url = EventType.location.value
sessions = "SESSIONS"
errors = IssueType.js_exception.value
class MetricOfErrors(str, Enum):
calls_errors = "callsErrors" # calls_errors
domains_errors_4xx = "domainsErrors4Xx" # domains_errors_4xx
domains_errors_5xx = "domainsErrors5Xx" # domains_errors_5xx
errors_per_domains = "errorsPerDomains" # errors_per_domains
errors_per_type = "errorsPerType" # errors_per_type
impacted_sessions_by_js_errors = "impactedSessionsByJsErrors" # impacted_sessions_by_js_errors
resources_by_party = "resourcesByParty" # resources_by_party
class TimeseriesMetricOfType(str, Enum):
class MetricOfPerformance(str, Enum):
cpu = "cpu" # cpu
crashes = "crashes" # crashes
fps = "fps" # fps
impacted_sessions_by_slow_pages = "impactedSessionsBySlowPages" # impacted_sessions_by_slow_pages
memory_consumption = "memoryConsumption" # memory_consumption
pages_dom_buildtime = "pagesDomBuildtime" # pages_dom_buildtime
pages_response_time = "pagesResponseTime" # pages_response_time
pages_response_time_distribution = "pagesResponseTimeDistribution" # pages_response_time_distribution
resources_vs_visually_complete = "resourcesVsVisuallyComplete" # resources_vs_visually_complete
sessions_per_browser = "sessionsPerBrowser" # sessions_per_browser
slowest_domains = "slowestDomains" # slowest_domains
speed_location = "speedLocation" # speed_location
time_to_render = "timeToRender" # time_to_render
class MetricOfResources(str, Enum):
missing_resources = "missingResources" # missing_resources
resources_count_by_type = "resourcesCountByType" # resources_count_by_type
resources_loading_time = "resourcesLoadingTime" # resources_loading_time
resource_type_vs_response_end = "resourceTypeVsResponseEnd" # resource_type_vs_response_end
slowest_resources = "slowestResources" # slowest_resources
class MetricOfWebVitals(str, Enum):
avg_cpu = "avgCpu" # avg_cpu
avg_dom_content_loaded = "avgDomContentLoaded" # avg_dom_content_loaded
avg_dom_content_load_start = "avgDomContentLoadStart" # avg_dom_content_load_start
avg_first_contentful_pixel = "avgFirstContentfulPixel" # avg_first_contentful_pixel
avg_first_paint = "avgFirstPaint" # avg_first_paint
avg_fps = "avgFps" # avg_fps
avg_image_load_time = "avgImageLoadTime" # avg_image_load_time
avg_page_load_time = "avgPageLoadTime" # avg_page_load_time
avg_pages_dom_buildtime = "avgPagesDomBuildtime" # avg_pages_dom_buildtime
avg_pages_response_time = "avgPagesResponseTime" # avg_pages_response_time
avg_request_load_time = "avgRequestLoadTime" # avg_request_load_time
avg_response_time = "avgResponseTime" # avg_response_time
avg_session_duration = "avgSessionDuration" # avg_session_duration
avg_till_first_byte = "avgTillFirstByte" # avg_till_first_byte
avg_time_to_interactive = "avgTimeToInteractive" # avg_time_to_interactive
avg_time_to_render = "avgTimeToRender" # avg_time_to_render
avg_used_js_heap_size = "avgUsedJsHeapSize" # avg_used_js_heap_size
avg_visited_pages = "avgVisitedPages" # avg_visited_pages
count_requests = "countRequests" # count_requests
count_sessions = "countSessions" # count_sessions
class MetricOfTable(str, Enum):
user_os = "userOS" # USEROS
user_browser = "userBrowser" # USERBROWSER
user_device = "userDevice" # USERDEVICE
user_country = "userCountry" # USERCOUNTRY
user_id = "userId" # USERID
issues = "issue" # ISSUE
visited_url = "location" # LOCATION
sessions = "sessions" # SESSIONS
errors = "jsException" # js_exception
class MetricOfTimeseries(str, Enum):
session_count = "sessionCount"
@ -831,22 +899,31 @@ class CustomMetricChartPayloadSchema(CustomMetricSessionsPayloadSchema, _Paginat
alias_generator = attribute_to_camel_case
class TryCardSchema(CustomMetricChartPayloadSchema):
class CustomMetricsConfigSchema(BaseModel):
col: Optional[int] = Field(...)
row: Optional[int] = Field(default=2)
position: Optional[int] = Field(default=0)
class CreateCardSchema(CustomMetricChartPayloadSchema):
name: Optional[str] = Field(...)
series: List[CustomMetricCreateSeriesSchema] = Field(...)
series: List[CustomMetricCreateSeriesSchema] = Field(default=[])
is_public: bool = Field(default=True)
view_type: Union[MetricTimeseriesViewType, MetricTableViewType, str] = Field(MetricTimeseriesViewType.line_chart)
metric_type: Union[MetricType, str] = Field(MetricType.timeseries)
metric_of: Union[TableMetricOfType, TimeseriesMetricOfType, str] = Field(TableMetricOfType.user_id)
view_type: Union[MetricTimeseriesViewType, MetricTableViewType, MetricOtherViewType] \
= Field(MetricTimeseriesViewType.line_chart)
metric_type: Union[MetricType] = Field(default=MetricType.timeseries)
metric_of: Union[MetricOfTimeseries, MetricOfTable, MetricOfErrors, MetricOfPerformance,
MetricOfResources, MetricOfWebVitals] = Field(MetricOfTable.user_id)
metric_value: List[IssueType] = Field([])
metric_format: Optional[MetricFormatType] = Field(None)
default_config: CustomMetricsConfigSchema = Field(...)
# This is used to handle wrong values sent by the UI
@root_validator(pre=True)
def transform(cls, values):
if values.get("metricType") == MetricType.timeseries \
or values.get("metricType") == MetricType.table \
and values.get("metricOf") != TableMetricOfType.issues:
and values.get("metricOf") != MetricOfTable.issues:
values["metricValue"] = []
if values.get("metric_type") == MetricType.funnel.value and \
@ -858,33 +935,40 @@ class TryCardSchema(CustomMetricChartPayloadSchema):
def validator(cls, values):
if values.get("metric_type") == MetricType.table:
assert isinstance(values.get("view_type"), MetricTableViewType), \
f"viewType must be of type {MetricTableViewType} for metricType:{MetricType.table.value}"
assert isinstance(values.get("metric_of"), TableMetricOfType), \
f"metricOf must be of type {TableMetricOfType} for metricType:{MetricType.table.value}"
if values.get("metric_of") != TableMetricOfType.issues:
f"viewType must be of type {MetricTableViewType} for metricType:{MetricType.table}"
assert isinstance(values.get("metric_of"), MetricOfTable), \
f"metricOf must be of type {MetricOfTable} for metricType:{MetricType.table}"
if values.get("metric_of") != MetricOfTable.issues:
assert values.get("metric_value") is None or len(values.get("metric_value")) == 0, \
f"metricValue is only available for metricOf:{TableMetricOfType.issues.value}"
f"metricValue is only available for metricOf:{MetricOfTable.issues}"
elif values.get("metric_type") == MetricType.timeseries:
assert isinstance(values.get("view_type"), MetricTimeseriesViewType), \
f"viewType must be of type {MetricTimeseriesViewType} for metricType:{MetricType.timeseries.value}"
assert isinstance(values.get("metric_of"), TimeseriesMetricOfType), \
f"metricOf must be of type {TimeseriesMetricOfType} for metricType:{MetricType.timeseries.value}"
f"viewType must be of type {MetricTimeseriesViewType} for metricType:{MetricType.timeseries}"
assert isinstance(values.get("metric_of"), MetricOfTimeseries), \
f"metricOf must be of type {MetricOfTimeseries} for metricType:{MetricType.timeseries}"
else:
if values.get("metric_type") == MetricType.errors:
assert isinstance(values.get("metric_of"), MetricOfErrors), \
f"metricOf must be of type {MetricOfErrors} for metricType:{MetricType.errors}"
elif values.get("metric_type") == MetricType.performance:
assert isinstance(values.get("metric_of"), MetricOfPerformance), \
f"metricOf must be of type {MetricOfPerformance} for metricType:{MetricType.performance}"
elif values.get("metric_type") == MetricType.resources:
assert isinstance(values.get("metric_of"), MetricOfResources), \
f"metricOf must be of type {MetricOfResources} for metricType:{MetricType.resources}"
elif values.get("metric_type") == MetricType.web_vital:
assert isinstance(values.get("metric_of"), MetricOfWebVitals), \
f"metricOf must be of type {MetricOfWebVitals} for metricType:{MetricType.web_vital}"
assert isinstance(values.get("view_type"), MetricOtherViewType), \
f"viewType must be 'chart' for metricOf:{values.get('metric_of')}"
return values
class Config:
alias_generator = attribute_to_camel_case
class CustomMetricsConfigSchema(BaseModel):
col: Optional[int] = Field(...)
row: Optional[int] = Field(default=2)
position: Optional[int] = Field(default=0)
class CreateCardSchema(TryCardSchema):
name: str = Field(...)
class CustomMetricUpdateSeriesSchema(CustomMetricCreateSeriesSchema):
series_id: Optional[int] = Field(None)
@ -1025,7 +1109,7 @@ class LiveSessionSearchFilterSchema(BaseModel):
type: LiveFilterType = Field(...)
source: Optional[str] = Field(None)
operator: Literal[SearchEventOperator._is.value,
SearchEventOperator._contains.value] = Field(SearchEventOperator._contains.value)
SearchEventOperator._contains.value] = Field(SearchEventOperator._contains.value)
@root_validator
def validator(cls, values):

View file

@ -20,16 +20,70 @@ CREATE TABLE IF NOT EXISTS assist_records
ALTER TYPE webhook_type ADD VALUE IF NOT EXISTS 'msteams';
DELETE
FROM metrics
WHERE is_predefined
AND is_template
AND metric_type = 'predefined';
UPDATE metrics_clone
SET metric_of=CASE
WHEN metric_of = 'USEROS' THEN 'userOS'
WHEN metric_of = 'USERBROWSER' THEN 'userBrowser'
WHEN metric_of = 'USERDEVICE' THEN 'userDevice'
WHEN metric_of = 'USERCOUNTRY' THEN 'userCountry'
WHEN metric_of = 'USERID' THEN 'userId'
WHEN metric_of = 'ISSUE' THEN 'issue'
WHEN metric_of = 'LOCATION' THEN 'location'
WHEN metric_of = 'SESSIONS' THEN 'sessions'
WHEN metric_of = 'js_exception' THEN 'jsException'
WHEN metric_of = 'sessionCount' THEN 'sessionCount'
END
WHERE NOT is_predefined;
-- TODO: transform metric_type to text
-- TODO: drop metric_type enum
-- TODO: drop is_pinned
-- TODO: drop is_predefined
-- TODO: drop is_template
-- 1. pre transform structure
ALTER TABLE IF EXISTS metrics_clone
ALTER COLUMN metric_type TYPE text,
ALTER COLUMN view_type TYPE text,
ADD COLUMN IF NOT EXISTS o_metric_id INTEGER,
ADD COLUMN IF NOT EXISTS o_widget_id INTEGER;
-- 2. insert predefined metrics related to dashboards as custom metrics
INSERT INTO metrics_clone(project_id, user_id, name, metric_type, view_type, metric_of, metric_value, metric_format,
default_config, o_metric_id, o_widget_id)
SELECT dashboards.project_id,
dashboard_widgets.user_id,
metrics_clone.name,
left(category, 1) || right(replace(initcap(category), ' ', ''), -1) AS metric_type,
'chart' AS view_type,
left(predefined_key, 1) || right(replace(initcap(predefined_key), '_', ''), -1) AS metric_of,
metric_value,
metric_format,
default_config,
metrics_clone.metric_id,
dashboard_widgets.widget_id
FROM metrics_clone
INNER JOIN dashboard_widgets USING (metric_id)
INNER JOIN dashboards USING (dashboard_id)
WHERE is_predefined;
-- 3. update widgets
UPDATE dashboard_widgets
SET metric_id=metrics_clone.metric_id
FROM metrics_clone
WHERE metrics_clone.o_widget_id IS NOT NULL
AND dashboard_widgets.widget_id = metrics_clone.o_widget_id;
-- 4. delete predefined metrics
DELETE
FROM metrics_clone
WHERE is_predefined;
ALTER TABLE IF EXISTS metrics_clone
DROP COLUMN IF EXISTS active,
DROP COLUMN IF EXISTS is_predefined,
DROP COLUMN IF EXISTS is_template,
DROP COLUMN IF EXISTS category,
DROP COLUMN IF EXISTS o_metric_id,
DROP COLUMN IF EXISTS o_widget_id,
DROP CONSTRAINT IF EXISTS null_project_id_for_template_only,
DROP CONSTRAINT IF EXISTS metrics_clone_unique_key;
DROP TYPE IF EXISTS metric_type;
DROP TYPE IF EXISTS metric_view_type;
COMMIT;

View file

@ -10,4 +10,70 @@ FROM metrics
WHERE is_predefined
AND is_template;
UPDATE metrics_clone
SET metric_of=CASE
WHEN metric_of = 'USEROS' THEN 'userOS'
WHEN metric_of = 'USERBROWSER' THEN 'userBrowser'
WHEN metric_of = 'USERDEVICE' THEN 'userDevice'
WHEN metric_of = 'USERCOUNTRY' THEN 'userCountry'
WHEN metric_of = 'USERID' THEN 'userId'
WHEN metric_of = 'ISSUE' THEN 'issue'
WHEN metric_of = 'LOCATION' THEN 'location'
WHEN metric_of = 'SESSIONS' THEN 'sessions'
WHEN metric_of = 'js_exception' THEN 'jsException'
WHEN metric_of = 'sessionCount' THEN 'sessionCount'
END
WHERE NOT is_predefined;
-- 1. pre transform structure
ALTER TABLE IF EXISTS metrics_clone
ALTER COLUMN metric_type TYPE text,
ALTER COLUMN view_type TYPE text,
ADD COLUMN IF NOT EXISTS o_metric_id INTEGER,
ADD COLUMN IF NOT EXISTS o_widget_id INTEGER;
-- 2. insert predefined metrics related to dashboards as custom metrics
INSERT INTO metrics_clone(project_id, user_id, name, metric_type, view_type, metric_of, metric_value, metric_format,
default_config, o_metric_id, o_widget_id)
SELECT dashboards.project_id,
dashboard_widgets.user_id,
metrics_clone.name,
left(category, 1) || right(replace(initcap(category), ' ', ''), -1) AS metric_type,
'chart' AS view_type,
left(predefined_key, 1) || right(replace(initcap(predefined_key), '_', ''), -1) AS metric_of,
metric_value,
metric_format,
default_config,
metrics_clone.metric_id,
dashboard_widgets.widget_id
FROM metrics_clone
INNER JOIN dashboard_widgets USING (metric_id)
INNER JOIN dashboards USING (dashboard_id)
WHERE is_predefined;
-- 3. update widgets
UPDATE dashboard_widgets
SET metric_id=metrics_clone.metric_id
FROM metrics_clone
WHERE metrics_clone.o_widget_id IS NOT NULL
AND dashboard_widgets.widget_id = metrics_clone.o_widget_id;
-- 4. delete predefined metrics
DELETE
FROM metrics_clone
WHERE is_predefined;
ALTER TABLE IF EXISTS metrics_clone
DROP COLUMN IF EXISTS active,
DROP COLUMN IF EXISTS is_predefined,
DROP COLUMN IF EXISTS is_template,
DROP COLUMN IF EXISTS category,
DROP COLUMN IF EXISTS o_metric_id,
DROP COLUMN IF EXISTS o_widget_id,
DROP CONSTRAINT IF EXISTS null_project_id_for_template_only,
DROP CONSTRAINT IF EXISTS metrics_clone_unique_key;
DROP TYPE IF EXISTS metric_type;
DROP TYPE IF EXISTS metric_view_type;
COMMIT;