* refactor(chalice): upgraded dependencies

* refactor(chalice): upgraded dependencies
feat(chalice): support heatmaps

* feat(chalice): support table-of-browsers showing user-count

* feat(chalice): support table-of-devices showing user-count

* feat(chalice): support table-of-URLs showing user-count

* fix(chalice): fixed saved card's sessions-drilldown
This commit is contained in:
Kraiem Taha Yassine 2024-06-27 16:28:34 +02:00 committed by GitHub
parent 2b9d38f858
commit 5fc087abd9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 206 additions and 91 deletions

View file

@ -6,18 +6,18 @@ name = "pypi"
[packages] [packages]
urllib3 = "==1.26.16" urllib3 = "==1.26.16"
requests = "==2.32.3" requests = "==2.32.3"
boto3 = "==1.34.134" boto3 = "==1.34.125"
pyjwt = "==2.8.0" pyjwt = "==2.8.0"
psycopg2-binary = "==2.9.9" psycopg2-binary = "==2.9.9"
psycopg = {extras = ["binary", "pool"], version = "==3.1.19"}
elasticsearch = "==8.14.0" elasticsearch = "==8.14.0"
jira = "==3.8.0" jira = "==3.8.0"
fastapi = "==0.111.0" fastapi = "==0.111.0"
uvicorn = {extras = ["standard"], version = "==0.30.1"}
python-decouple = "==3.8" python-decouple = "==3.8"
pydantic = {extras = ["email"], version = "==2.3.0"}
apscheduler = "==3.10.4" apscheduler = "==3.10.4"
redis = "==5.1.0b6" redis = "==5.1.0b6"
psycopg = {extras = ["binary", "pool"], version = "==3.1.19"}
uvicorn = {extras = ["standard"], version = "==0.30.1"}
pydantic = {extras = ["email"], version = "==2.3.0"}
[dev-packages] [dev-packages]

View file

@ -190,31 +190,34 @@ def get_chart(project_id: int, data: schemas.CardSchema, user_id: int):
return supported.get(data.metric_type, not_supported)(project_id=project_id, data=data, user_id=user_id) return supported.get(data.metric_type, not_supported)(project_id=project_id, data=data, user_id=user_id)
def __merge_metric_with_data(metric: schemas.CardSchema, # def __merge_metric_with_data(metric: schemas.CardSchema,
data: schemas.CardSessionsSchema) -> schemas.CardSchema: # data: schemas.CardSessionsSchema) -> schemas.CardSchema:
metric.startTimestamp = data.startTimestamp # metric.startTimestamp = data.startTimestamp
metric.endTimestamp = data.endTimestamp # metric.endTimestamp = data.endTimestamp
metric.page = data.page # metric.page = data.page
metric.limit = data.limit # metric.limit = data.limit
metric.density = data.density # metric.density = data.density
if data.series is not None and len(data.series) > 0: # if data.series is not None and len(data.series) > 0:
metric.series = data.series # metric.series = data.series
#
# if len(data.filters) > 0: # # if len(data.filters) > 0:
# for s in metric.series: # # for s in metric.series:
# s.filter.filters += data.filters # # s.filter.filters += data.filters
# metric = schemas.CardSchema(**metric.model_dump(by_alias=True)) # # metric = schemas.CardSchema(**metric.model_dump(by_alias=True))
return metric # return metric
def get_sessions_by_card_id(project_id, user_id, metric_id, data: schemas.CardSessionsSchema): def get_sessions_by_card_id(project_id, user_id, metric_id, data: schemas.CardSessionsSchema):
card: dict = get_card(metric_id=metric_id, project_id=project_id, user_id=user_id, flatten=False) # No need for this because UI is sending the full payload
if card is None: # card: dict = get_card(metric_id=metric_id, project_id=project_id, user_id=user_id, flatten=False)
# if card is None:
# return None
# metric: schemas.CardSchema = schemas.CardSchema(**card)
# metric: schemas.CardSchema = __merge_metric_with_data(metric=metric, data=data)
if not card_exists(metric_id=metric_id, project_id=project_id, user_id=user_id):
return None return None
metric: schemas.CardSchema = schemas.CardSchema(**card)
metric: schemas.CardSchema = __merge_metric_with_data(metric=metric, data=data)
results = [] results = []
for s in metric.series: for s in data.series:
results.append({"seriesId": s.series_id, "seriesName": s.name, results.append({"seriesId": s.series_id, "seriesName": s.name,
**sessions.search_sessions(data=s.filter, project_id=project_id, user_id=user_id)}) **sessions.search_sessions(data=s.filter, project_id=project_id, user_id=user_id)})
@ -222,27 +225,33 @@ def get_sessions_by_card_id(project_id, user_id, metric_id, data: schemas.CardSe
def get_funnel_issues(project_id, user_id, metric_id, data: schemas.CardSessionsSchema): def get_funnel_issues(project_id, user_id, metric_id, data: schemas.CardSessionsSchema):
raw_metric: dict = get_card(metric_id=metric_id, project_id=project_id, user_id=user_id, flatten=False) # No need for this because UI is sending the full payload
if raw_metric is None: # raw_metric: dict = get_card(metric_id=metric_id, project_id=project_id, user_id=user_id, flatten=False)
# if raw_metric is None:
# return None
# metric: schemas.CardSchema = schemas.CardSchema(**raw_metric)
# metric: schemas.CardSchema = __merge_metric_with_data(metric=metric, data=data)
# if metric is None:
# return None
if not card_exists(metric_id=metric_id, project_id=project_id, user_id=user_id):
return None return None
metric: schemas.CardSchema = schemas.CardSchema(**raw_metric) for s in data.series:
metric: schemas.CardSchema = __merge_metric_with_data(metric=metric, data=data)
if metric is None:
return None
for s in metric.series:
return {"seriesId": s.series_id, "seriesName": s.name, return {"seriesId": s.series_id, "seriesName": s.name,
**funnels.get_issues_on_the_fly_widget(project_id=project_id, data=s.filter)} **funnels.get_issues_on_the_fly_widget(project_id=project_id, data=s.filter)}
def get_errors_list(project_id, user_id, metric_id, data: schemas.CardSessionsSchema): def get_errors_list(project_id, user_id, metric_id, data: schemas.CardSessionsSchema):
raw_metric: dict = get_card(metric_id=metric_id, project_id=project_id, user_id=user_id, flatten=False) # No need for this because UI is sending the full payload
if raw_metric is None: # raw_metric: dict = get_card(metric_id=metric_id, project_id=project_id, user_id=user_id, flatten=False)
# if raw_metric is None:
# return None
# metric: schemas.CardSchema = schemas.CardSchema(**raw_metric)
# metric: schemas.CardSchema = __merge_metric_with_data(metric=metric, data=data)
# if metric is None:
# return None
if not card_exists(metric_id=metric_id, project_id=project_id, user_id=user_id):
return None return None
metric: schemas.CardSchema = schemas.CardSchema(**raw_metric) for s in data.series:
metric: schemas.CardSchema = __merge_metric_with_data(metric=metric, data=data)
if metric is None:
return None
for s in metric.series:
return {"seriesId": s.series_id, "seriesName": s.name, return {"seriesId": s.series_id, "seriesName": s.name,
**errors.search(data=s.filter, project_id=project_id, user_id=user_id)} **errors.search(data=s.filter, project_id=project_id, user_id=user_id)}
@ -626,14 +635,17 @@ def get_funnel_sessions_by_issue(user_id, project_id, metric_id, issue_id,
data: schemas.CardSessionsSchema data: schemas.CardSessionsSchema
# , range_value=None, start_date=None, end_date=None # , range_value=None, start_date=None, end_date=None
): ):
card: dict = get_card(metric_id=metric_id, project_id=project_id, user_id=user_id, flatten=False) # No need for this because UI is sending the full payload
if card is None: # card: dict = get_card(metric_id=metric_id, project_id=project_id, user_id=user_id, flatten=False)
# if card is None:
# return None
# metric: schemas.CardSchema = schemas.CardSchema(**card)
# metric: schemas.CardSchema = __merge_metric_with_data(metric=metric, data=data)
# if metric is None:
# return None
if not card_exists(metric_id=metric_id, project_id=project_id, user_id=user_id):
return None return None
metric: schemas.CardSchema = schemas.CardSchema(**card) for s in data.series:
metric: schemas.CardSchema = __merge_metric_with_data(metric=metric, data=data)
if metric is None:
return None
for s in metric.series:
s.filter.startTimestamp = data.startTimestamp s.filter.startTimestamp = data.startTimestamp
s.filter.endTimestamp = data.endTimestamp s.filter.endTimestamp = data.endTimestamp
s.filter.limit = data.limit s.filter.limit = data.limit
@ -693,3 +705,33 @@ def make_chart_from_card(project_id, user_id, metric_id, data: schemas.CardSessi
return raw_metric["data"] return raw_metric["data"]
return get_chart(project_id=project_id, data=metric, user_id=user_id) return get_chart(project_id=project_id, data=metric, user_id=user_id)
def card_exists(metric_id, project_id, user_id) -> bool:
with pg_client.PostgresClient() as cur:
query = cur.mogrify(
f"""SELECT 1
FROM metrics
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.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}
)
cur.execute(query)
row = cur.fetchone()
return row is not None

View file

@ -1081,6 +1081,36 @@ class CardSessionsSchema(_TimedSchema, _PaginatedSchema):
return values return values
@model_validator(mode="after")
def __merge_out_filters_with_series(cls, values):
if len(values.filters) > 0:
for f in values.filters:
for s in values.series:
found = False
if f.is_event:
sub = s.filter.events
else:
sub = s.filter.filters
for e in sub:
if f.type == e.type and f.operator == e.operator:
found = True
if f.is_event:
# If extra event: append value
for v in f.value:
if v not in e.value:
e.value.append(v)
else:
# If extra filter: override value
e.value = f.value
if not found:
sub.append(f)
values.filters = []
return values
class CardConfigSchema(BaseModel): class CardConfigSchema(BaseModel):
col: Optional[int] = Field(default=None) col: Optional[int] = Field(default=None)

View file

@ -6,22 +6,22 @@ name = "pypi"
[packages] [packages]
urllib3 = "==1.26.16" urllib3 = "==1.26.16"
requests = "==2.32.3" requests = "==2.32.3"
boto3 = "==1.34.134" boto3 = "==1.34.125"
pyjwt = "==2.8.0" pyjwt = "==2.8.0"
psycopg2-binary = "==2.9.9" psycopg2-binary = "==2.9.9"
psycopg = {extras = ["binary", "pool"], version = "==3.1.19"}
elasticsearch = "==8.14.0" elasticsearch = "==8.14.0"
jira = "==3.8.0" jira = "==3.8.0"
fastapi = "==0.111.0" fastapi = "==0.111.0"
uvicorn = {extras = ["standard"], version = "==0.30.1"}
gunicorn = "==22.0.0" gunicorn = "==22.0.0"
python-decouple = "==3.8" python-decouple = "==3.8"
pydantic = {extras = ["email"], version = "==2.3.0"}
apscheduler = "==3.10.4" apscheduler = "==3.10.4"
clickhouse-driver = {extras = ["lz4"], version = "==0.2.8"}
python3-saml = "==1.16.0" python3-saml = "==1.16.0"
redis = "==5.1.0b6" redis = "==5.1.0b6"
azure-storage-blob = "==12.21.0b1" azure-storage-blob = "==12.21.0b1"
psycopg = {extras = ["pool", "binary"], version = "==3.1.19"}
uvicorn = {extras = ["standard"], version = "==0.30.1"}
pydantic = {extras = ["email"], version = "==2.3.0"}
clickhouse-driver = {extras = ["lz4"], version = "==0.2.8"}
[dev-packages] [dev-packages]

View file

@ -75,6 +75,7 @@ def __get_funnel_chart(project_id: int, data: schemas.CardFunnel, user_id: int =
"stages": [], "stages": [],
"totalDropDueToIssues": 0 "totalDropDueToIssues": 0
} }
return funnels.get_top_insights_on_the_fly_widget(project_id=project_id, return funnels.get_top_insights_on_the_fly_widget(project_id=project_id,
data=data.series[0].filter, data=data.series[0].filter,
metric_of=data.metric_of) metric_of=data.metric_of)
@ -209,31 +210,34 @@ def get_chart(project_id: int, data: schemas.CardSchema, user_id: int):
return supported.get(data.metric_type, not_supported)(project_id=project_id, data=data, user_id=user_id) return supported.get(data.metric_type, not_supported)(project_id=project_id, data=data, user_id=user_id)
def __merge_metric_with_data(metric: schemas.CardSchema, # def __merge_metric_with_data(metric: schemas.CardSchema,
data: schemas.CardSessionsSchema) -> schemas.CardSchema: # data: schemas.CardSessionsSchema) -> schemas.CardSchema:
metric.startTimestamp = data.startTimestamp # metric.startTimestamp = data.startTimestamp
metric.endTimestamp = data.endTimestamp # metric.endTimestamp = data.endTimestamp
metric.page = data.page # metric.page = data.page
metric.limit = data.limit # metric.limit = data.limit
metric.density = data.density # metric.density = data.density
if data.series is not None and len(data.series) > 0: # if data.series is not None and len(data.series) > 0:
metric.series = data.series # metric.series = data.series
#
# if len(data.filters) > 0: # # if len(data.filters) > 0:
# for s in metric.series: # # for s in metric.series:
# s.filter.filters += data.filters # # s.filter.filters += data.filters
# metric = schemas.CardSchema(**metric.model_dump(by_alias=True)) # # metric = schemas.CardSchema(**metric.model_dump(by_alias=True))
return metric # return metric
def get_sessions_by_card_id(project_id, user_id, metric_id, data: schemas.CardSessionsSchema): def get_sessions_by_card_id(project_id, user_id, metric_id, data: schemas.CardSessionsSchema):
card: dict = get_card(metric_id=metric_id, project_id=project_id, user_id=user_id, flatten=False) # No need for this because UI is sending the full payload
if card is None: # card: dict = get_card(metric_id=metric_id, project_id=project_id, user_id=user_id, flatten=False)
# if card is None:
# return None
# metric: schemas.CardSchema = schemas.CardSchema(**card)
# metric: schemas.CardSchema = __merge_metric_with_data(metric=metric, data=data)
if not card_exists(metric_id=metric_id, project_id=project_id, user_id=user_id):
return None return None
metric: schemas.CardSchema = schemas.CardSchema(**card)
metric: schemas.CardSchema = __merge_metric_with_data(metric=metric, data=data)
results = [] results = []
for s in metric.series: for s in data.series:
results.append({"seriesId": s.series_id, "seriesName": s.name, results.append({"seriesId": s.series_id, "seriesName": s.name,
**sessions.search_sessions(data=s.filter, project_id=project_id, user_id=user_id)}) **sessions.search_sessions(data=s.filter, project_id=project_id, user_id=user_id)})
@ -241,27 +245,33 @@ def get_sessions_by_card_id(project_id, user_id, metric_id, data: schemas.CardSe
def get_funnel_issues(project_id, user_id, metric_id, data: schemas.CardSessionsSchema): def get_funnel_issues(project_id, user_id, metric_id, data: schemas.CardSessionsSchema):
raw_metric: dict = get_card(metric_id=metric_id, project_id=project_id, user_id=user_id, flatten=False) # No need for this because UI is sending the full payload
if raw_metric is None: # raw_metric: dict = get_card(metric_id=metric_id, project_id=project_id, user_id=user_id, flatten=False)
# if raw_metric is None:
# return None
# metric: schemas.CardSchema = schemas.CardSchema(**raw_metric)
# metric: schemas.CardSchema = __merge_metric_with_data(metric=metric, data=data)
# if metric is None:
# return None
if not card_exists(metric_id=metric_id, project_id=project_id, user_id=user_id):
return None return None
metric: schemas.CardSchema = schemas.CardSchema(**raw_metric) for s in data.series:
metric: schemas.CardSchema = __merge_metric_with_data(metric=metric, data=data)
if metric is None:
return None
for s in metric.series:
return {"seriesId": s.series_id, "seriesName": s.name, return {"seriesId": s.series_id, "seriesName": s.name,
**funnels.get_issues_on_the_fly_widget(project_id=project_id, data=s.filter)} **funnels.get_issues_on_the_fly_widget(project_id=project_id, data=s.filter)}
def get_errors_list(project_id, user_id, metric_id, data: schemas.CardSessionsSchema): def get_errors_list(project_id, user_id, metric_id, data: schemas.CardSessionsSchema):
raw_metric: dict = get_card(metric_id=metric_id, project_id=project_id, user_id=user_id, flatten=False) # No need for this because UI is sending the full payload
if raw_metric is None: # raw_metric: dict = get_card(metric_id=metric_id, project_id=project_id, user_id=user_id, flatten=False)
# if raw_metric is None:
# return None
# metric: schemas.CardSchema = schemas.CardSchema(**raw_metric)
# metric: schemas.CardSchema = __merge_metric_with_data(metric=metric, data=data)
# if metric is None:
# return None
if not card_exists(metric_id=metric_id, project_id=project_id, user_id=user_id):
return None return None
metric: schemas.CardSchema = schemas.CardSchema(**raw_metric) for s in data.series:
metric: schemas.CardSchema = __merge_metric_with_data(metric=metric, data=data)
if metric is None:
return None
for s in metric.series:
return {"seriesId": s.series_id, "seriesName": s.name, return {"seriesId": s.series_id, "seriesName": s.name,
**errors.search(data=s.filter, project_id=project_id, user_id=user_id)} **errors.search(data=s.filter, project_id=project_id, user_id=user_id)}
@ -672,14 +682,17 @@ def get_funnel_sessions_by_issue(user_id, project_id, metric_id, issue_id,
data: schemas.CardSessionsSchema data: schemas.CardSessionsSchema
# , range_value=None, start_date=None, end_date=None # , range_value=None, start_date=None, end_date=None
): ):
card: dict = get_card(metric_id=metric_id, project_id=project_id, user_id=user_id, flatten=False) # No need for this because UI is sending the full payload
if card is None: # card: dict = get_card(metric_id=metric_id, project_id=project_id, user_id=user_id, flatten=False)
# if card is None:
# return None
# metric: schemas.CardSchema = schemas.CardSchema(**card)
# metric: schemas.CardSchema = __merge_metric_with_data(metric=metric, data=data)
# if metric is None:
# return None
if not card_exists(metric_id=metric_id, project_id=project_id, user_id=user_id):
return None return None
metric: schemas.CardSchema = schemas.CardSchema(**card) for s in data.series:
metric: schemas.CardSchema = __merge_metric_with_data(metric=metric, data=data)
if metric is None:
return None
for s in metric.series:
s.filter.startTimestamp = data.startTimestamp s.filter.startTimestamp = data.startTimestamp
s.filter.endTimestamp = data.endTimestamp s.filter.endTimestamp = data.endTimestamp
s.filter.limit = data.limit s.filter.limit = data.limit
@ -739,3 +752,33 @@ def make_chart_from_card(project_id, user_id, metric_id, data: schemas.CardSessi
return raw_metric["data"] return raw_metric["data"]
return get_chart(project_id=project_id, data=metric, user_id=user_id) return get_chart(project_id=project_id, data=metric, user_id=user_id)
def card_exists(metric_id, project_id, user_id) -> bool:
with pg_client.PostgresClient() as cur:
query = cur.mogrify(
f"""SELECT 1
FROM metrics
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.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}
)
cur.execute(query)
row = cur.fetchone()
return row is not None