Api v1.15.0 (#1464)
* feat(DB): rearranged queries feat(DB): ready for v1.15.0 * refactor(chalice): upgraded dependencies refactor(crons): upgraded dependencies refactor(alerts): upgraded dependencies * fix(chalice): return error when updating inexistant webhook * feat(chalice): fixed delete webhook response * feat(chalice): limit webhooks name length * feat(chalice): upgraded dependencies feat(alerts): upgraded dependencies feat(crons): upgraded dependencies * fix(chalice): remove urllib3 dependency * feat(chalice): remove FOSS to pydantic v2 * fix(chalice): freeze urllib3 to not have conflicts between boto3 and requests * feat(chalice): refactoring schema in progress * feat(chalice): refactoring schema in progress * feat(chalice): refactoring schema in progress * feat(chalice): refactoring schema in progress feat(chalice): upgraded dependencies * feat(chalice): refactored schema * fix(chalice): pull rebase dev * feat(DB): transfer size support * feat(chalice): support service account * feat(chalice): support service account * fix(chalice): fixed refactored PayloadSchema-name * feat(chalice): path analysis * feat(chalice): support service account 1/2 * feat(DB): timezone support * feat(chalice): upgraded dependencies feat(alerts): upgraded dependencies feat(crons): upgraded dependencies feat(assist): upgraded dependencies feat(sourcemaps): upgraded dependencies * feat(chalice): path analysis schema changes * feat(chalice): path analysis query change * feat(chalice): path analysis query change * feat(chalice): ios replay support * feat(chalice): ios replay support * feat(chalice): path analysis changes * feat(chalice): upgraded dependencies * feat(chalice): simple hide minor paths * feat(chalice): path analysis density * feat(chalice): session's replay ios events * feat(chalice): fixed typo * feat(chalice): support project's platform * feat(DB): support project's platform * feat(chalice): path analysis EE in progress * feat(chalice): project's platform API * feat(chalice): fixed create project * feat(chalice): EE path analysis in progress * feat(chalice): EE path analysis refactor(chalice): support specific database name for clickhouse-client * feat(chalice): upgraded dependencies feat(chalice): path analysis specific event type for startPoint feat(chalice): path analysis specific event type for endPoint feat(chalice): path analysis specific event type for exclude * refactoring(chalice): changed IOS click event type
This commit is contained in:
parent
0ece13064a
commit
a34179365e
99 changed files with 4999 additions and 4049 deletions
22
api/Pipfile
22
api/Pipfile
|
|
@ -5,18 +5,18 @@ name = "pypi"
|
|||
|
||||
[packages]
|
||||
requests = "==2.31.0"
|
||||
urllib3 = "==1.26.16"
|
||||
boto3 = "==1.26.148"
|
||||
pyjwt = "==2.7.0"
|
||||
psycopg2-binary = "==2.9.6"
|
||||
elasticsearch = "==8.8.0"
|
||||
jira = "==3.5.1"
|
||||
fastapi = "==0.96.0"
|
||||
uvicorn = {version = "==0.22.0", extras = ["standard"]}
|
||||
boto3 = "==1.28.40"
|
||||
pyjwt = "==2.8.0"
|
||||
psycopg2-binary = "==2.9.7"
|
||||
elasticsearch = "==8.9.0"
|
||||
jira = "==3.5.2"
|
||||
fastapi = "==0.103.1"
|
||||
python-decouple = "==3.8"
|
||||
pydantic = {version = "==1.10.8", extras = ["email"]}
|
||||
apscheduler = "==3.10.1"
|
||||
redis = "==4.5.5"
|
||||
apscheduler = "==3.10.4"
|
||||
redis = "==5.0.0"
|
||||
urllib3 = "==1.26.16"
|
||||
uvicorn = {version = "==0.23.2", extras = ["standard"]}
|
||||
pydantic = {version = "==2.3.0", extras = ["email"]}
|
||||
|
||||
[dev-packages]
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ class JWTAuth(HTTPBearer):
|
|||
if credentials:
|
||||
if not credentials.scheme == "Bearer":
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid authentication scheme.")
|
||||
jwt_payload = authorizers.jwt_authorizer(credentials.scheme + " " + credentials.credentials)
|
||||
jwt_payload = authorizers.jwt_authorizer(scheme=credentials.scheme, token=credentials.credentials)
|
||||
auth_exists = jwt_payload is not None \
|
||||
and users.auth_exists(user_id=jwt_payload.get("userId", -1),
|
||||
tenant_id=jwt_payload.get("tenantId", -1),
|
||||
|
|
@ -27,18 +27,13 @@ class JWTAuth(HTTPBearer):
|
|||
if jwt_payload is None \
|
||||
or jwt_payload.get("iat") is None or jwt_payload.get("aud") is None \
|
||||
or not auth_exists:
|
||||
print("JWTAuth: Token issue")
|
||||
if jwt_payload is not None:
|
||||
print(jwt_payload)
|
||||
print(f"JWTAuth: user_id={jwt_payload.get('userId')} tenant_id={jwt_payload.get('tenantId')}")
|
||||
if jwt_payload is None:
|
||||
print("JWTAuth: jwt_payload is None")
|
||||
print(credentials.scheme + " " + credentials.credentials)
|
||||
if jwt_payload is not None and jwt_payload.get("iat") is None:
|
||||
print("JWTAuth: iat is None")
|
||||
if jwt_payload is not None and jwt_payload.get("aud") is None:
|
||||
print("JWTAuth: aud is None")
|
||||
if jwt_payload is not None and not auth_exists:
|
||||
if jwt_payload.get("iat") is None:
|
||||
print("JWTAuth: iat is None")
|
||||
if jwt_payload.get("aud") is None:
|
||||
print("JWTAuth: aud is None")
|
||||
if not auth_exists:
|
||||
print("JWTAuth: not users.auth_exists")
|
||||
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Invalid token or expired token.")
|
||||
|
|
@ -47,10 +42,9 @@ class JWTAuth(HTTPBearer):
|
|||
print("JWTAuth: User not found.")
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="User not found.")
|
||||
jwt_payload["authorizer_identity"] = "jwt"
|
||||
print(jwt_payload)
|
||||
request.state.authorizer_identity = "jwt"
|
||||
request.state.currentContext = schemas.CurrentContext(tenant_id=jwt_payload.get("tenantId", -1),
|
||||
user_id=jwt_payload.get("userId", -1),
|
||||
request.state.currentContext = schemas.CurrentContext(tenantId=jwt_payload.get("tenantId", -1),
|
||||
userId=jwt_payload.get("userId", -1),
|
||||
email=user["email"])
|
||||
return request.state.currentContext
|
||||
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ def __process_circular(alert):
|
|||
|
||||
|
||||
def create(project_id, data: schemas.AlertSchema):
|
||||
data = data.dict()
|
||||
data = data.model_dump()
|
||||
data["query"] = json.dumps(data["query"])
|
||||
data["options"] = json.dumps(data["options"])
|
||||
|
||||
|
|
@ -72,7 +72,7 @@ def create(project_id, data: schemas.AlertSchema):
|
|||
|
||||
|
||||
def update(id, data: schemas.AlertSchema):
|
||||
data = data.dict()
|
||||
data = data.model_dump()
|
||||
data["query"] = json.dumps(data["query"])
|
||||
data["options"] = json.dumps(data["options"])
|
||||
|
||||
|
|
|
|||
|
|
@ -6,13 +6,12 @@ from chalicelib.core import tenants
|
|||
from chalicelib.core import users
|
||||
|
||||
|
||||
def jwt_authorizer(token):
|
||||
token = token.split(" ")
|
||||
if len(token) != 2 or token[0].lower() != "bearer":
|
||||
def jwt_authorizer(scheme: str, token: str):
|
||||
if scheme.lower() != "bearer":
|
||||
return None
|
||||
try:
|
||||
payload = jwt.decode(
|
||||
token[1],
|
||||
token,
|
||||
config("jwt_secret"),
|
||||
algorithms=config("jwt_algorithm"),
|
||||
audience=[f"front:{helper.get_stage_name()}"]
|
||||
|
|
@ -22,6 +21,7 @@ def jwt_authorizer(token):
|
|||
return None
|
||||
except BaseException as e:
|
||||
print("! JWT Base Exception")
|
||||
print(e)
|
||||
return None
|
||||
return payload
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ COALESCE((SELECT TRUE
|
|||
AND fs.user_id = %(userId)s LIMIT 1), FALSE) AS viewed """
|
||||
|
||||
|
||||
def search_short_session(data: schemas.FlatClickMapSessionsSearch, project_id, user_id, include_mobs: bool = True):
|
||||
def search_short_session(data: schemas.ClickMapSessionsSearch, project_id, user_id, include_mobs: bool = True):
|
||||
no_platform = True
|
||||
for f in data.filters:
|
||||
if f.type == schemas.FilterType.platform:
|
||||
|
|
@ -62,7 +62,7 @@ def search_short_session(data: schemas.FlatClickMapSessionsSearch, project_id, u
|
|||
print("--------- CLICK MAP SHORT SESSION SEARCH QUERY EXCEPTION -----------")
|
||||
print(main_query.decode('UTF-8'))
|
||||
print("--------- PAYLOAD -----------")
|
||||
print(data.json())
|
||||
print(data.model_dump_json())
|
||||
print("--------------------")
|
||||
raise err
|
||||
|
||||
|
|
|
|||
|
|
@ -13,25 +13,24 @@ from chalicelib.utils.storage import StorageClient
|
|||
PIE_CHART_GROUP = 5
|
||||
|
||||
|
||||
# TODO: refactor this to split
|
||||
# timeseries /
|
||||
# table of errors / table of issues / table of browsers / table of devices / table of countries / table of URLs
|
||||
def __try_live(project_id, data: schemas.CardSchema):
|
||||
results = []
|
||||
for i, s in enumerate(data.series):
|
||||
s.filter.startDate = data.startTimestamp
|
||||
s.filter.endDate = data.endTimestamp
|
||||
results.append(sessions.search2_series(data=s.filter, project_id=project_id, density=data.density,
|
||||
view_type=data.view_type, metric_type=data.metric_type,
|
||||
metric_of=data.metric_of, metric_value=data.metric_value))
|
||||
if data.view_type == schemas.MetricTimeseriesViewType.progress:
|
||||
r = {"count": results[-1]}
|
||||
diff = s.filter.endDate - s.filter.startDate
|
||||
s.filter.endDate = s.filter.startDate
|
||||
s.filter.startDate = s.filter.endDate - diff
|
||||
diff = s.filter.endTimestamp - s.filter.startTimestamp
|
||||
s.filter.endTimestamp = s.filter.startTimestamp
|
||||
s.filter.startTimestamp = s.filter.endTimestamp - diff
|
||||
r["previousCount"] = sessions.search2_series(data=s.filter, project_id=project_id, density=data.density,
|
||||
view_type=data.view_type, metric_type=data.metric_type,
|
||||
metric_of=data.metric_of, metric_value=data.metric_value)
|
||||
r["countProgress"] = helper.__progress(old_val=r["previousCount"], new_val=r["count"])
|
||||
# r["countProgress"] = ((r["count"] - r["previousCount"]) / r["previousCount"]) * 100 \
|
||||
# if r["previousCount"] > 0 else 0
|
||||
r["seriesName"] = s.name if s.name else i + 1
|
||||
r["seriesId"] = s.series_id if s.series_id else None
|
||||
results[-1] = r
|
||||
|
|
@ -50,14 +49,12 @@ def __is_funnel_chart(data: schemas.CardSchema):
|
|||
return data.metric_type == schemas.MetricType.funnel
|
||||
|
||||
|
||||
def __get_funnel_chart(project_id, data: schemas.CardSchema):
|
||||
def __get_funnel_chart(project_id: int, data: schemas.CardFunnel, user_id: int = None):
|
||||
if len(data.series) == 0:
|
||||
return {
|
||||
"stages": [],
|
||||
"totalDropDueToIssues": 0
|
||||
}
|
||||
data.series[0].filter.startDate = data.startTimestamp
|
||||
data.series[0].filter.endDate = data.endTimestamp
|
||||
return funnels.get_top_insights_on_the_fly_widget(project_id=project_id, data=data.series[0].filter)
|
||||
|
||||
|
||||
|
|
@ -72,10 +69,6 @@ def __get_errors_list(project_id, user_id, data: schemas.CardSchema):
|
|||
"total": 0,
|
||||
"errors": []
|
||||
}
|
||||
data.series[0].filter.startDate = data.startTimestamp
|
||||
data.series[0].filter.endDate = data.endTimestamp
|
||||
data.series[0].filter.page = data.page
|
||||
data.series[0].filter.limit = data.limit
|
||||
return errors.search(data.series[0].filter, project_id=project_id, user_id=user_id)
|
||||
|
||||
|
||||
|
|
@ -91,10 +84,6 @@ def __get_sessions_list(project_id, user_id, data: schemas.CardSchema):
|
|||
"total": 0,
|
||||
"sessions": []
|
||||
}
|
||||
data.series[0].filter.startDate = data.startTimestamp
|
||||
data.series[0].filter.endDate = data.endTimestamp
|
||||
data.series[0].filter.page = data.page
|
||||
data.series[0].filter.limit = data.limit
|
||||
return sessions.search_sessions(data=data.series[0].filter, project_id=project_id, user_id=user_id)
|
||||
|
||||
|
||||
|
|
@ -106,48 +95,33 @@ def __is_click_map(data: schemas.CardSchema):
|
|||
return data.metric_type == schemas.MetricType.click_map
|
||||
|
||||
|
||||
def __get_click_map_chart(project_id, user_id, data: schemas.CardSchema, include_mobs: bool = True):
|
||||
def __get_click_map_chart(project_id, user_id, data: schemas.CardClickMap, include_mobs: bool = True):
|
||||
if len(data.series) == 0:
|
||||
return None
|
||||
data.series[0].filter.startDate = data.startTimestamp
|
||||
data.series[0].filter.endDate = data.endTimestamp
|
||||
return click_maps.search_short_session(project_id=project_id, user_id=user_id,
|
||||
data=schemas.FlatClickMapSessionsSearch(**data.series[0].filter.dict()),
|
||||
data=schemas.ClickMapSessionsSearch(
|
||||
**data.series[0].filter.model_dump()),
|
||||
include_mobs=include_mobs)
|
||||
|
||||
|
||||
def __get_path_analysis_chart(project_id, data: schemas.CardSchema):
|
||||
def __get_path_analysis_chart(project_id: int, user_id: int, data: schemas.CardPathAnalysis):
|
||||
if len(data.series) == 0:
|
||||
data.series.append(schemas.CardSeriesSchema())
|
||||
data.series.append(
|
||||
schemas.CardPathAnalysisSchema(startTimestamp=data.startTimestamp, endTimestamp=data.endTimestamp))
|
||||
elif not isinstance(data.series[0].filter, schemas.PathAnalysisSchema):
|
||||
data.series[0].filter = schemas.PathAnalysisSchema()
|
||||
data.series[0].filter.startTimestamp = data.startTimestamp
|
||||
data.series[0].filter.endTimestamp = data.endTimestamp
|
||||
return product_analytics.path_analysis(project_id=project_id,
|
||||
data=schemas.PathAnalysisSchema(**data.series[0].filter.dict()))
|
||||
|
||||
return product_analytics.path_analysis(project_id=project_id, data=data.series[0].filter, density=data.density,
|
||||
selected_event_type=data.metric_value, hide_minor_paths=data.hide_excess)
|
||||
|
||||
|
||||
def __is_path_analysis(data: schemas.CardSchema):
|
||||
return data.metric_type == schemas.MetricType.pathAnalysis
|
||||
|
||||
|
||||
def merged_live(project_id, data: schemas.CardSchema, user_id=None):
|
||||
if data.is_template:
|
||||
return get_predefined_metric(key=data.metric_of, project_id=project_id, data=data.dict())
|
||||
elif __is_funnel_chart(data):
|
||||
return __get_funnel_chart(project_id=project_id, data=data)
|
||||
elif __is_errors_list(data):
|
||||
return __get_errors_list(project_id=project_id, user_id=user_id, data=data)
|
||||
elif __is_sessions_list(data):
|
||||
return __get_sessions_list(project_id=project_id, user_id=user_id, data=data)
|
||||
elif __is_click_map(data):
|
||||
return __get_click_map_chart(project_id=project_id, user_id=user_id, data=data)
|
||||
elif __is_path_analysis(data):
|
||||
return __get_path_analysis_chart(project_id=project_id, data=data)
|
||||
elif len(data.series) == 0:
|
||||
return []
|
||||
def __get_timeseries_chart(project_id: int, data: schemas.CardTimeSeries, user_id: int = None):
|
||||
series_charts = __try_live(project_id=project_id, data=data)
|
||||
if data.view_type == schemas.MetricTimeseriesViewType.progress or data.metric_type == schemas.MetricType.table:
|
||||
if data.view_type == schemas.MetricTimeseriesViewType.progress:
|
||||
return series_charts
|
||||
results = [{}] * len(series_charts[0])
|
||||
for i in range(len(results)):
|
||||
|
|
@ -157,26 +131,131 @@ def merged_live(project_id, data: schemas.CardSchema, user_id=None):
|
|||
return results
|
||||
|
||||
|
||||
def empty(**args):
|
||||
raise Exception("not supported")
|
||||
|
||||
|
||||
def __get_table_of_user_ids(project_id: int, data: schemas.CardTable, user_id: int = None):
|
||||
series_charts = __try_live(project_id=project_id, data=data)
|
||||
return series_charts
|
||||
|
||||
|
||||
def __get_table_of_sessions(project_id: int, data: schemas.CardTable, user_id):
|
||||
return __get_sessions_list(project_id=project_id, user_id=user_id, data=data)
|
||||
|
||||
|
||||
def __get_table_of_errors(project_id: int, data: schemas.CardTable, user_id: int):
|
||||
return __get_errors_list(project_id=project_id, user_id=user_id, data=data)
|
||||
|
||||
|
||||
def __get_table_of_issues(project_id: int, data: schemas.CardTable, user_id: int = None):
|
||||
return __try_live(project_id=project_id, data=data)
|
||||
|
||||
|
||||
def __get_table_of_browsers(project_id: int, data: schemas.CardTable, user_id: int = None):
|
||||
return __try_live(project_id=project_id, data=data)
|
||||
|
||||
|
||||
def __get_table_of_devises(project_id: int, data: schemas.CardTable, user_id: int = None):
|
||||
return __try_live(project_id=project_id, data=data)
|
||||
|
||||
|
||||
def __get_table_of_countries(project_id: int, data: schemas.CardTable, user_id: int = None):
|
||||
return __try_live(project_id=project_id, data=data)
|
||||
|
||||
|
||||
def __get_table_of_urls(project_id: int, data: schemas.CardTable, user_id: int = None):
|
||||
return __try_live(project_id=project_id, data=data)
|
||||
|
||||
|
||||
def __get_table_chart(project_id: int, data: schemas.CardTable, user_id: int):
|
||||
supported = {
|
||||
schemas.MetricOfTable.sessions: __get_table_of_sessions,
|
||||
schemas.MetricOfTable.errors: __get_table_of_errors,
|
||||
schemas.MetricOfTable.user_id: __get_table_of_user_ids,
|
||||
schemas.MetricOfTable.issues: __get_table_of_issues,
|
||||
schemas.MetricOfTable.user_browser: __get_table_of_browsers,
|
||||
schemas.MetricOfTable.user_device: __get_table_of_devises,
|
||||
schemas.MetricOfTable.user_country: __get_table_of_countries,
|
||||
schemas.MetricOfTable.visited_url: __get_table_of_urls,
|
||||
}
|
||||
return supported.get(data.metric_of, empty)(project_id=project_id, data=data, user_id=user_id)
|
||||
|
||||
|
||||
def get_chart(project_id: int, data: schemas.CardSchema, user_id: int):
|
||||
if data.is_template:
|
||||
return get_predefined_metric(key=data.metric_of, project_id=project_id, data=data.model_dump())
|
||||
|
||||
supported = {
|
||||
schemas.MetricType.timeseries: __get_timeseries_chart,
|
||||
schemas.MetricType.table: __get_table_chart,
|
||||
schemas.MetricType.click_map: __get_click_map_chart,
|
||||
schemas.MetricType.funnel: __get_funnel_chart,
|
||||
schemas.MetricType.insights: empty,
|
||||
schemas.MetricType.pathAnalysis: __get_path_analysis_chart
|
||||
}
|
||||
return supported.get(data.metric_type, empty)(project_id=project_id, data=data, user_id=user_id)
|
||||
|
||||
|
||||
def merged_live(project_id, data: schemas.CardSchema, user_id=None):
|
||||
return get_chart(project_id=project_id, data=data, user_id=user_id)
|
||||
print("---1")
|
||||
if data.is_template:
|
||||
print("---2")
|
||||
return get_predefined_metric(key=data.metric_of, project_id=project_id, data=data.model_dump())
|
||||
elif __is_funnel_chart(data):
|
||||
print("---3")
|
||||
return __get_funnel_chart(project_id=project_id, data=data)
|
||||
elif __is_errors_list(data):
|
||||
print("---4")
|
||||
return __get_errors_list(project_id=project_id, user_id=user_id, data=data)
|
||||
elif __is_sessions_list(data):
|
||||
print("---5")
|
||||
return __get_sessions_list(project_id=project_id, user_id=user_id, data=data)
|
||||
elif __is_click_map(data):
|
||||
print("---6")
|
||||
return __get_click_map_chart(project_id=project_id, user_id=user_id, data=data)
|
||||
elif __is_path_analysis(data):
|
||||
print("---7")
|
||||
return __get_path_analysis_chart(project_id=project_id, data=data)
|
||||
elif len(data.series) == 0:
|
||||
print("---8")
|
||||
return []
|
||||
series_charts = __try_live(project_id=project_id, data=data)
|
||||
print("---9")
|
||||
if data.view_type == schemas.MetricTimeseriesViewType.progress or data.metric_type == schemas.MetricType.table:
|
||||
print("---10")
|
||||
return series_charts
|
||||
results = [{}] * len(series_charts[0])
|
||||
print("---11")
|
||||
for i in range(len(results)):
|
||||
for j, series_chart in enumerate(series_charts):
|
||||
results[i] = {**results[i], "timestamp": series_chart[i]["timestamp"],
|
||||
data.series[j].name if data.series[j].name else j + 1: series_chart[i]["count"]}
|
||||
return results
|
||||
|
||||
|
||||
def __merge_metric_with_data(metric: schemas.CardSchema,
|
||||
data: schemas.CardChartSchema) -> schemas.CardSchema:
|
||||
data: schemas.CardSessionsSchema) -> schemas.CardSchema:
|
||||
if data.series is not None and len(data.series) > 0:
|
||||
metric.series = data.series
|
||||
metric: schemas.CardSchema = schemas.CardSchema(
|
||||
**{**data.dict(by_alias=True), **metric.dict(by_alias=True)})
|
||||
# TODO: try to refactor this
|
||||
metric: schemas.CardSchema = schemas.CardSchema(**{**data.model_dump(by_alias=True),
|
||||
**metric.model_dump(by_alias=True)})
|
||||
if len(data.filters) > 0 or len(data.events) > 0:
|
||||
for s in metric.series:
|
||||
if len(data.filters) > 0:
|
||||
s.filter.filters += data.filters
|
||||
if len(data.events) > 0:
|
||||
s.filter.events += data.events
|
||||
metric.limit = data.limit
|
||||
metric.page = data.page
|
||||
metric.startTimestamp = data.startTimestamp
|
||||
metric.endTimestamp = data.endTimestamp
|
||||
# metric.limit = data.limit
|
||||
# metric.page = data.page
|
||||
# metric.startTimestamp = data.startTimestamp
|
||||
# metric.endTimestamp = data.endTimestamp
|
||||
return metric
|
||||
|
||||
|
||||
def make_chart(project_id, user_id, data: schemas.CardChartSchema, metric: schemas.CardSchema):
|
||||
def make_chart(project_id, user_id, data: schemas.CardSessionsSchema, metric: schemas.CardSchema):
|
||||
if metric is None:
|
||||
return None
|
||||
metric: schemas.CardSchema = __merge_metric_with_data(metric=metric, data=data)
|
||||
|
|
@ -198,10 +277,10 @@ def get_sessions(project_id, user_id, metric_id, data: schemas.CardSessionsSchem
|
|||
# if __is_click_map(metric) and raw_metric.get("data") is not None:
|
||||
# is_click_map = True
|
||||
for s in metric.series:
|
||||
s.filter.startDate = data.startTimestamp
|
||||
s.filter.endDate = data.endTimestamp
|
||||
s.filter.limit = data.limit
|
||||
s.filter.page = data.page
|
||||
# s.filter.startTimestamp = data.startTimestamp
|
||||
# s.filter.endTimestamp = data.endTimestamp
|
||||
# s.filter.limit = data.limit
|
||||
# s.filter.page = data.page
|
||||
# if is_click_map:
|
||||
# results.append(
|
||||
# {"seriesId": s.series_id, "seriesName": s.name, "total": 1, "sessions": [raw_metric["data"]]})
|
||||
|
|
@ -221,10 +300,6 @@ def get_funnel_issues(project_id, user_id, metric_id, data: schemas.CardSessions
|
|||
if metric is None:
|
||||
return None
|
||||
for s in metric.series:
|
||||
s.filter.startDate = data.startTimestamp
|
||||
s.filter.endDate = data.endTimestamp
|
||||
s.filter.limit = data.limit
|
||||
s.filter.page = data.page
|
||||
return {"seriesId": s.series_id, "seriesName": s.name,
|
||||
**funnels.get_issues_on_the_fly_widget(project_id=project_id, data=s.filter)}
|
||||
|
||||
|
|
@ -238,23 +313,15 @@ def get_errors_list(project_id, user_id, metric_id, data: schemas.CardSessionsSc
|
|||
if metric is None:
|
||||
return None
|
||||
for s in metric.series:
|
||||
s.filter.startDate = data.startTimestamp
|
||||
s.filter.endDate = data.endTimestamp
|
||||
s.filter.limit = data.limit
|
||||
s.filter.page = data.page
|
||||
return {"seriesId": s.series_id, "seriesName": s.name,
|
||||
**errors.search(data=s.filter, project_id=project_id, user_id=user_id)}
|
||||
|
||||
|
||||
def try_sessions(project_id, user_id, data: schemas.CardSessionsSchema):
|
||||
results = []
|
||||
if data.series is None:
|
||||
if len(data.series) == 0:
|
||||
return results
|
||||
for s in data.series:
|
||||
s.filter.startDate = data.startTimestamp
|
||||
s.filter.endDate = data.endTimestamp
|
||||
s.filter.limit = data.limit
|
||||
s.filter.page = data.page
|
||||
if len(data.filters) > 0:
|
||||
s.filter.filters += data.filters
|
||||
if len(data.events) > 0:
|
||||
|
|
@ -265,7 +332,7 @@ def try_sessions(project_id, user_id, data: schemas.CardSessionsSchema):
|
|||
return results
|
||||
|
||||
|
||||
def create(project_id, user_id, data: schemas.CardSchema, dashboard=False):
|
||||
def create_card(project_id, user_id, data: schemas.CardSchema, dashboard=False):
|
||||
with pg_client.PostgresClient() as cur:
|
||||
session_data = None
|
||||
if __is_click_map(data):
|
||||
|
|
@ -275,13 +342,13 @@ def create(project_id, user_id, data: schemas.CardSchema, dashboard=False):
|
|||
session_data = json.dumps(session_data)
|
||||
_data = {"session_data": session_data}
|
||||
for i, s in enumerate(data.series):
|
||||
for k in s.dict().keys():
|
||||
for k in s.model_dump().keys():
|
||||
_data[f"{k}_{i}"] = s.__getattribute__(k)
|
||||
_data[f"index_{i}"] = i
|
||||
_data[f"filter_{i}"] = s.filter.json()
|
||||
series_len = len(data.series)
|
||||
params = {"user_id": user_id, "project_id": project_id, **data.dict(), **_data}
|
||||
params["default_config"] = json.dumps(data.default_config.dict())
|
||||
params = {"user_id": user_id, "project_id": project_id, **data.model_dump(), **_data}
|
||||
params["default_config"] = json.dumps(data.default_config.model_dump())
|
||||
query = """INSERT INTO metrics (project_id, user_id, name, is_public,
|
||||
view_type, metric_type, metric_of, metric_value,
|
||||
metric_format, default_config, thumbnail, data)
|
||||
|
|
@ -307,7 +374,7 @@ def create(project_id, user_id, data: schemas.CardSchema, dashboard=False):
|
|||
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):
|
||||
def update_card(metric_id, user_id, project_id, data: schemas.CardSchema):
|
||||
metric: dict = get_card(metric_id=metric_id, project_id=project_id, user_id=user_id, flatten=False)
|
||||
if metric is None:
|
||||
return None
|
||||
|
|
@ -320,7 +387,7 @@ def update(metric_id, user_id, project_id, data: schemas.UpdateCardSchema):
|
|||
"user_id": user_id, "project_id": project_id, "view_type": data.view_type,
|
||||
"metric_type": data.metric_type, "metric_of": data.metric_of,
|
||||
"metric_value": data.metric_value, "metric_format": data.metric_format,
|
||||
"config": json.dumps(data.default_config.dict()), "thumbnail": data.thumbnail}
|
||||
"config": json.dumps(data.default_config.model_dump()), "thumbnail": data.thumbnail}
|
||||
for i, s in enumerate(data.series):
|
||||
prefix = "u_"
|
||||
if s.index is None:
|
||||
|
|
@ -331,7 +398,7 @@ def update(metric_id, user_id, project_id, data: schemas.UpdateCardSchema):
|
|||
else:
|
||||
u_series.append({"i": i, "s": s})
|
||||
u_series_ids.append(s.series_id)
|
||||
ns = s.dict()
|
||||
ns = s.model_dump()
|
||||
for k in ns.keys():
|
||||
if k == "filter":
|
||||
ns[k] = json.dumps(ns[k])
|
||||
|
|
@ -453,7 +520,7 @@ def get_all(project_id, user_id):
|
|||
return result
|
||||
|
||||
|
||||
def delete(project_id, metric_id, user_id):
|
||||
def delete_card(project_id, metric_id, user_id):
|
||||
with pg_client.PostgresClient() as cur:
|
||||
cur.execute(
|
||||
cur.mogrify("""\
|
||||
|
|
@ -562,8 +629,8 @@ def get_funnel_sessions_by_issue(user_id, project_id, metric_id, issue_id,
|
|||
if metric is None:
|
||||
return None
|
||||
for s in metric.series:
|
||||
s.filter.startDate = data.startTimestamp
|
||||
s.filter.endDate = data.endTimestamp
|
||||
s.filter.startTimestamp = data.startTimestamp
|
||||
s.filter.endTimestamp = data.endTimestamp
|
||||
s.filter.limit = data.limit
|
||||
s.filter.page = data.page
|
||||
issues_list = funnels.get_issues_on_the_fly_widget(project_id=project_id, data=s.filter).get("issues", {})
|
||||
|
|
@ -589,13 +656,15 @@ def get_funnel_sessions_by_issue(user_id, project_id, metric_id, issue_id,
|
|||
"issue": issue}
|
||||
|
||||
|
||||
def make_chart_from_card(project_id, user_id, metric_id, data: schemas.CardChartSchema):
|
||||
def make_chart_from_card(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, include_data=True)
|
||||
if raw_metric is None:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="card not found")
|
||||
raw_metric["startTimestamp"] = data.startTimestamp
|
||||
raw_metric["endTimestamp"] = data.endTimestamp
|
||||
metric: schemas.CardSchema = schemas.CardSchema(**raw_metric)
|
||||
if metric.is_template:
|
||||
return get_predefined_metric(key=metric.metric_of, project_id=project_id, data=data.dict())
|
||||
return get_predefined_metric(key=metric.metric_of, project_id=project_id, data=data.model_dump())
|
||||
elif __is_click_map(metric):
|
||||
if raw_metric["data"]:
|
||||
keys = sessions_mobs. \
|
||||
|
|
@ -615,53 +684,52 @@ def make_chart_from_card(project_id, user_id, metric_id, data: schemas.CardChart
|
|||
return make_chart(project_id=project_id, user_id=user_id, data=data, metric=metric)
|
||||
|
||||
|
||||
PREDEFINED = {schemas.MetricOfWebVitals.count_sessions: metrics.get_processed_sessions,
|
||||
schemas.MetricOfWebVitals.avg_image_load_time: metrics.get_application_activity_avg_image_load_time,
|
||||
schemas.MetricOfWebVitals.avg_page_load_time: metrics.get_application_activity_avg_page_load_time,
|
||||
schemas.MetricOfWebVitals.avg_request_load_time: metrics.get_application_activity_avg_request_load_time,
|
||||
schemas.MetricOfWebVitals.avg_dom_content_load_start: metrics.get_page_metrics_avg_dom_content_load_start,
|
||||
schemas.MetricOfWebVitals.avg_first_contentful_pixel: metrics.get_page_metrics_avg_first_contentful_pixel,
|
||||
schemas.MetricOfWebVitals.avg_visited_pages: metrics.get_user_activity_avg_visited_pages,
|
||||
schemas.MetricOfWebVitals.avg_session_duration: metrics.get_user_activity_avg_session_duration,
|
||||
schemas.MetricOfWebVitals.avg_pages_dom_buildtime: metrics.get_pages_dom_build_time,
|
||||
schemas.MetricOfWebVitals.avg_pages_response_time: metrics.get_pages_response_time,
|
||||
schemas.MetricOfWebVitals.avg_response_time: metrics.get_top_metrics_avg_response_time,
|
||||
schemas.MetricOfWebVitals.avg_first_paint: metrics.get_top_metrics_avg_first_paint,
|
||||
schemas.MetricOfWebVitals.avg_dom_content_loaded: metrics.get_top_metrics_avg_dom_content_loaded,
|
||||
schemas.MetricOfWebVitals.avg_till_first_byte: metrics.get_top_metrics_avg_till_first_bit,
|
||||
schemas.MetricOfWebVitals.avg_time_to_interactive: metrics.get_top_metrics_avg_time_to_interactive,
|
||||
schemas.MetricOfWebVitals.count_requests: metrics.get_top_metrics_count_requests,
|
||||
schemas.MetricOfWebVitals.avg_time_to_render: metrics.get_time_to_render,
|
||||
schemas.MetricOfWebVitals.avg_used_js_heap_size: metrics.get_memory_consumption,
|
||||
schemas.MetricOfWebVitals.avg_cpu: metrics.get_avg_cpu,
|
||||
schemas.MetricOfWebVitals.avg_fps: metrics.get_avg_fps,
|
||||
schemas.MetricOfErrors.impacted_sessions_by_js_errors: metrics.get_impacted_sessions_by_js_errors,
|
||||
schemas.MetricOfErrors.domains_errors_4xx: metrics.get_domains_errors_4xx,
|
||||
schemas.MetricOfErrors.domains_errors_5xx: metrics.get_domains_errors_5xx,
|
||||
schemas.MetricOfErrors.errors_per_domains: metrics.get_errors_per_domains,
|
||||
schemas.MetricOfErrors.calls_errors: metrics.get_calls_errors,
|
||||
schemas.MetricOfErrors.errors_per_type: metrics.get_errors_per_type,
|
||||
schemas.MetricOfErrors.resources_by_party: metrics.get_resources_by_party,
|
||||
schemas.MetricOfPerformance.speed_location: metrics.get_speed_index_location,
|
||||
schemas.MetricOfPerformance.slowest_domains: metrics.get_slowest_domains,
|
||||
schemas.MetricOfPerformance.sessions_per_browser: metrics.get_sessions_per_browser,
|
||||
schemas.MetricOfPerformance.time_to_render: metrics.get_time_to_render,
|
||||
schemas.MetricOfPerformance.impacted_sessions_by_slow_pages: metrics.get_impacted_sessions_by_slow_pages,
|
||||
schemas.MetricOfPerformance.memory_consumption: metrics.get_memory_consumption,
|
||||
schemas.MetricOfPerformance.cpu: metrics.get_avg_cpu,
|
||||
schemas.MetricOfPerformance.fps: metrics.get_avg_fps,
|
||||
schemas.MetricOfPerformance.crashes: metrics.get_crashes,
|
||||
schemas.MetricOfPerformance.resources_vs_visually_complete: metrics.get_resources_vs_visually_complete,
|
||||
schemas.MetricOfPerformance.pages_dom_buildtime: metrics.get_pages_dom_build_time,
|
||||
schemas.MetricOfPerformance.pages_response_time: metrics.get_pages_response_time,
|
||||
schemas.MetricOfPerformance.pages_response_time_distribution: metrics.get_pages_response_time_distribution,
|
||||
schemas.MetricOfResources.missing_resources: metrics.get_missing_resources_trend,
|
||||
schemas.MetricOfResources.slowest_resources: metrics.get_slowest_resources,
|
||||
schemas.MetricOfResources.resources_loading_time: metrics.get_resources_loading_time,
|
||||
schemas.MetricOfResources.resource_type_vs_response_end: metrics.resource_type_vs_response_end,
|
||||
schemas.MetricOfResources.resources_count_by_type: metrics.get_resources_count_by_type, }
|
||||
|
||||
|
||||
def get_predefined_metric(key: Union[schemas.MetricOfWebVitals, schemas.MetricOfErrors, \
|
||||
schemas.MetricOfPerformance, schemas.MetricOfResources], project_id: int, data: dict):
|
||||
return PREDEFINED.get(key, lambda *args: None)(project_id=project_id, **data)
|
||||
supported = {schemas.MetricOfWebVitals.count_sessions: metrics.get_processed_sessions,
|
||||
schemas.MetricOfWebVitals.avg_image_load_time: metrics.get_application_activity_avg_image_load_time,
|
||||
schemas.MetricOfWebVitals.avg_page_load_time: metrics.get_application_activity_avg_page_load_time,
|
||||
schemas.MetricOfWebVitals.avg_request_load_time: metrics.get_application_activity_avg_request_load_time,
|
||||
schemas.MetricOfWebVitals.avg_dom_content_load_start: metrics.get_page_metrics_avg_dom_content_load_start,
|
||||
schemas.MetricOfWebVitals.avg_first_contentful_pixel: metrics.get_page_metrics_avg_first_contentful_pixel,
|
||||
schemas.MetricOfWebVitals.avg_visited_pages: metrics.get_user_activity_avg_visited_pages,
|
||||
schemas.MetricOfWebVitals.avg_session_duration: metrics.get_user_activity_avg_session_duration,
|
||||
schemas.MetricOfWebVitals.avg_pages_dom_buildtime: metrics.get_pages_dom_build_time,
|
||||
schemas.MetricOfWebVitals.avg_pages_response_time: metrics.get_pages_response_time,
|
||||
schemas.MetricOfWebVitals.avg_response_time: metrics.get_top_metrics_avg_response_time,
|
||||
schemas.MetricOfWebVitals.avg_first_paint: metrics.get_top_metrics_avg_first_paint,
|
||||
schemas.MetricOfWebVitals.avg_dom_content_loaded: metrics.get_top_metrics_avg_dom_content_loaded,
|
||||
schemas.MetricOfWebVitals.avg_till_first_byte: metrics.get_top_metrics_avg_till_first_bit,
|
||||
schemas.MetricOfWebVitals.avg_time_to_interactive: metrics.get_top_metrics_avg_time_to_interactive,
|
||||
schemas.MetricOfWebVitals.count_requests: metrics.get_top_metrics_count_requests,
|
||||
schemas.MetricOfWebVitals.avg_time_to_render: metrics.get_time_to_render,
|
||||
schemas.MetricOfWebVitals.avg_used_js_heap_size: metrics.get_memory_consumption,
|
||||
schemas.MetricOfWebVitals.avg_cpu: metrics.get_avg_cpu,
|
||||
schemas.MetricOfWebVitals.avg_fps: metrics.get_avg_fps,
|
||||
schemas.MetricOfErrors.impacted_sessions_by_js_errors: metrics.get_impacted_sessions_by_js_errors,
|
||||
schemas.MetricOfErrors.domains_errors_4xx: metrics.get_domains_errors_4xx,
|
||||
schemas.MetricOfErrors.domains_errors_5xx: metrics.get_domains_errors_5xx,
|
||||
schemas.MetricOfErrors.errors_per_domains: metrics.get_errors_per_domains,
|
||||
schemas.MetricOfErrors.calls_errors: metrics.get_calls_errors,
|
||||
schemas.MetricOfErrors.errors_per_type: metrics.get_errors_per_type,
|
||||
schemas.MetricOfErrors.resources_by_party: metrics.get_resources_by_party,
|
||||
schemas.MetricOfPerformance.speed_location: metrics.get_speed_index_location,
|
||||
schemas.MetricOfPerformance.slowest_domains: metrics.get_slowest_domains,
|
||||
schemas.MetricOfPerformance.sessions_per_browser: metrics.get_sessions_per_browser,
|
||||
schemas.MetricOfPerformance.time_to_render: metrics.get_time_to_render,
|
||||
schemas.MetricOfPerformance.impacted_sessions_by_slow_pages: metrics.get_impacted_sessions_by_slow_pages,
|
||||
schemas.MetricOfPerformance.memory_consumption: metrics.get_memory_consumption,
|
||||
schemas.MetricOfPerformance.cpu: metrics.get_avg_cpu,
|
||||
schemas.MetricOfPerformance.fps: metrics.get_avg_fps,
|
||||
schemas.MetricOfPerformance.crashes: metrics.get_crashes,
|
||||
schemas.MetricOfPerformance.resources_vs_visually_complete: metrics.get_resources_vs_visually_complete,
|
||||
schemas.MetricOfPerformance.pages_dom_buildtime: metrics.get_pages_dom_build_time,
|
||||
schemas.MetricOfPerformance.pages_response_time: metrics.get_pages_response_time,
|
||||
schemas.MetricOfPerformance.pages_response_time_distribution: metrics.get_pages_response_time_distribution,
|
||||
schemas.MetricOfResources.missing_resources: metrics.get_missing_resources_trend,
|
||||
schemas.MetricOfResources.slowest_resources: metrics.get_slowest_resources,
|
||||
schemas.MetricOfResources.resources_loading_time: metrics.get_resources_loading_time,
|
||||
schemas.MetricOfResources.resource_type_vs_response_end: metrics.resource_type_vs_response_end,
|
||||
schemas.MetricOfResources.resources_count_by_type: metrics.get_resources_count_by_type, }
|
||||
|
||||
return supported.get(key, lambda *args: None)(project_id=project_id, **data)
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ def create_dashboard(project_id, user_id, data: schemas.CreateDashboardSchema):
|
|||
pg_query = f"""INSERT INTO dashboards(project_id, user_id, name, is_public, is_pinned, description)
|
||||
VALUES(%(projectId)s, %(userId)s, %(name)s, %(is_public)s, %(is_pinned)s, %(description)s)
|
||||
RETURNING *"""
|
||||
params = {"userId": user_id, "projectId": project_id, **data.dict()}
|
||||
params = {"userId": user_id, "projectId": project_id, **data.model_dump()}
|
||||
if data.metrics is not None and len(data.metrics) > 0:
|
||||
pg_query = f"""WITH dash AS ({pg_query})
|
||||
INSERT INTO dashboard_widgets(dashboard_id, metric_id, user_id, config)
|
||||
|
|
@ -109,7 +109,7 @@ def update_dashboard(project_id, user_id, dashboard_id, data: schemas.EditDashbo
|
|||
pg_query = """SELECT COALESCE(COUNT(*),0) AS count
|
||||
FROM dashboard_widgets
|
||||
WHERE dashboard_id = %(dashboard_id)s;"""
|
||||
params = {"userId": user_id, "projectId": project_id, "dashboard_id": dashboard_id, **data.dict()}
|
||||
params = {"userId": user_id, "projectId": project_id, "dashboard_id": dashboard_id, **data.model_dump()}
|
||||
cur.execute(cur.mogrify(pg_query, params))
|
||||
row = cur.fetchone()
|
||||
offset = row["count"]
|
||||
|
|
@ -178,7 +178,7 @@ def add_widget(project_id, user_id, dashboard_id, data: schemas.AddWidgetToDashb
|
|||
AND dashboard_id = %(dashboard_id)s
|
||||
AND (dashboards.user_id = %(userId)s OR is_public))
|
||||
RETURNING *;"""
|
||||
params = {"userId": user_id, "projectId": project_id, "dashboard_id": dashboard_id, **data.dict()}
|
||||
params = {"userId": user_id, "projectId": project_id, "dashboard_id": dashboard_id, **data.model_dump()}
|
||||
params["config"] = json.dumps(data.config)
|
||||
cur.execute(cur.mogrify(pg_query, params))
|
||||
row = cur.fetchone()
|
||||
|
|
@ -192,7 +192,7 @@ def update_widget(project_id, user_id, dashboard_id, widget_id, data: schemas.Up
|
|||
WHERE dashboard_id=%(dashboard_id)s AND widget_id=%(widget_id)s
|
||||
RETURNING *;"""
|
||||
params = {"userId": user_id, "projectId": project_id, "dashboard_id": dashboard_id,
|
||||
"widget_id": widget_id, **data.dict()}
|
||||
"widget_id": widget_id, **data.model_dump()}
|
||||
params["config"] = json.dumps(data.config)
|
||||
cur.execute(cur.mogrify(pg_query, params))
|
||||
row = cur.fetchone()
|
||||
|
|
@ -224,7 +224,7 @@ def pin_dashboard(project_id, user_id, dashboard_id):
|
|||
|
||||
|
||||
def create_metric_add_widget(project_id, user_id, dashboard_id, data: schemas.CardSchema):
|
||||
metric_id = custom_metrics.create(project_id=project_id, user_id=user_id, data=data, dashboard=True)
|
||||
metric_id = custom_metrics.create_card(project_id=project_id, user_id=user_id, data=data, dashboard=True)
|
||||
return add_widget(project_id=project_id, user_id=user_id, dashboard_id=dashboard_id,
|
||||
data=schemas.AddWidgetToDashboardPayloadSchema(metricId=metric_id))
|
||||
|
||||
|
|
@ -234,7 +234,7 @@ def create_metric_add_widget(project_id, user_id, dashboard_id, data: schemas.Ca
|
|||
# return None
|
||||
# metric = schemas.CustomMetricAndTemplate = schemas.CustomMetricAndTemplate(**raw_metric)
|
||||
# if metric.is_template:
|
||||
# return get_predefined_metric(key=metric.predefined_key, project_id=project_id, data=data.dict())
|
||||
# return get_predefined_metric(key=metric.predefined_key, project_id=project_id, data=data.model_dump())
|
||||
# else:
|
||||
# return custom_metrics.make_chart(project_id=project_id, user_id=user_id, metric_id=raw_metric["metricId"],
|
||||
# data=data, metric=raw_metric)
|
||||
|
|
|
|||
|
|
@ -454,10 +454,10 @@ def search(data: schemas.SearchErrorsSchema, project_id, user_id):
|
|||
pg_sub_query_chart.append("errors.error_id =details.error_id")
|
||||
statuses = []
|
||||
error_ids = None
|
||||
if data.startDate is None:
|
||||
data.startDate = TimeUTC.now(-30)
|
||||
if data.endDate is None:
|
||||
data.endDate = TimeUTC.now(1)
|
||||
if data.startTimestamp is None:
|
||||
data.startTimestamp = TimeUTC.now(-30)
|
||||
if data.endTimestamp is None:
|
||||
data.endTimestamp = TimeUTC.now(1)
|
||||
if len(data.events) > 0 or len(data.filters) > 0:
|
||||
print("-- searching for sessions before errors")
|
||||
statuses = sessions.search_sessions(data=data, project_id=project_id, user_id=user_id, errors_only=True,
|
||||
|
|
@ -466,18 +466,18 @@ def search(data: schemas.SearchErrorsSchema, project_id, user_id):
|
|||
return empty_response
|
||||
error_ids = [e["errorId"] for e in statuses]
|
||||
with pg_client.PostgresClient() as cur:
|
||||
step_size = __get_step_size(data.startDate, data.endDate, data.density, factor=1)
|
||||
step_size = __get_step_size(data.startTimestamp, data.endTimestamp, data.density, factor=1)
|
||||
sort = __get_sort_key('datetime')
|
||||
if data.sort is not None:
|
||||
sort = __get_sort_key(data.sort)
|
||||
order = schemas.SortOrderType.desc.value
|
||||
order = schemas.SortOrderType.desc
|
||||
if data.order is not None:
|
||||
order = data.order.value
|
||||
order = data.order
|
||||
extra_join = ""
|
||||
|
||||
params = {
|
||||
"startDate": data.startDate,
|
||||
"endDate": data.endDate,
|
||||
"startDate": data.startTimestamp,
|
||||
"endDate": data.endTimestamp,
|
||||
"project_id": project_id,
|
||||
"userId": user_id,
|
||||
"step_size": step_size}
|
||||
|
|
@ -709,41 +709,3 @@ def change_state(project_id, user_id, error_id, action):
|
|||
for e in errors:
|
||||
e["status"] = row["status"]
|
||||
return {"data": errors}
|
||||
|
||||
|
||||
MAX_RANK = 2
|
||||
|
||||
|
||||
def __status_rank(status):
|
||||
return {
|
||||
'unresolved': MAX_RANK - 2,
|
||||
'ignored': MAX_RANK - 1,
|
||||
'resolved': MAX_RANK
|
||||
}.get(status)
|
||||
|
||||
|
||||
def stats(project_id, user_id, startTimestamp=TimeUTC.now(delta_days=-7), endTimestamp=TimeUTC.now()):
|
||||
with pg_client.PostgresClient() as cur:
|
||||
query = cur.mogrify(
|
||||
"""WITH user_viewed AS (SELECT error_id FROM public.user_viewed_errors WHERE user_id = %(user_id)s)
|
||||
SELECT COUNT(timed_errors.*) AS unresolved_and_unviewed
|
||||
FROM (SELECT root_error.error_id
|
||||
FROM events.errors
|
||||
INNER JOIN public.errors AS root_error USING (error_id)
|
||||
LEFT JOIN user_viewed USING (error_id)
|
||||
WHERE project_id = %(project_id)s
|
||||
AND timestamp >= %(startTimestamp)s
|
||||
AND timestamp <= %(endTimestamp)s
|
||||
AND source = 'js_exception'
|
||||
AND root_error.status = 'unresolved'
|
||||
AND user_viewed.error_id ISNULL
|
||||
LIMIT 1
|
||||
) AS timed_errors;""",
|
||||
{"project_id": project_id, "user_id": user_id, "startTimestamp": startTimestamp,
|
||||
"endTimestamp": endTimestamp})
|
||||
cur.execute(query=query)
|
||||
row = cur.fetchone()
|
||||
|
||||
return {
|
||||
"data": helper.dict_to_camel_case(row)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -110,11 +110,12 @@ class EventType:
|
|||
column=None) # column=None because errors are searched by name or message
|
||||
METADATA = Event(ui_type=schemas.FilterType.metadata, table="public.sessions", column=None)
|
||||
# IOS
|
||||
CLICK_IOS = Event(ui_type=schemas.EventType.click_ios, table="events_ios.clicks", column="label")
|
||||
CLICK_IOS = Event(ui_type=schemas.EventType.click_ios, table="events_ios.taps", column="label")
|
||||
INPUT_IOS = Event(ui_type=schemas.EventType.input_ios, table="events_ios.inputs", column="label")
|
||||
VIEW_IOS = Event(ui_type=schemas.EventType.view_ios, table="events_ios.views", column="name")
|
||||
SWIPE_IOS = Event(ui_type=schemas.EventType.swipe_ios, table="events_ios.swipes", column="label")
|
||||
CUSTOM_IOS = Event(ui_type=schemas.EventType.custom_ios, table="events_common.customs", column="name")
|
||||
REQUEST_IOS = Event(ui_type=schemas.EventType.request_ios, table="events_common.requests", column="url")
|
||||
REQUEST_IOS = Event(ui_type=schemas.EventType.request_ios, table="events_common.requests", column="path")
|
||||
ERROR_IOS = Event(ui_type=schemas.EventType.error_ios, table="events_ios.crashes",
|
||||
column=None) # column=None because errors are searched by name or message
|
||||
|
||||
|
|
|
|||
|
|
@ -2,20 +2,8 @@ from chalicelib.utils import pg_client, helper
|
|||
from chalicelib.core import events
|
||||
|
||||
|
||||
def get_customs_by_sessionId(session_id, project_id):
|
||||
with pg_client.PostgresClient() as cur:
|
||||
cur.execute(cur.mogrify(f"""\
|
||||
SELECT
|
||||
c.*,
|
||||
'{events.EventType.CUSTOM_IOS.ui_type}' AS type
|
||||
FROM {events.EventType.CUSTOM_IOS.table} AS c
|
||||
WHERE
|
||||
c.session_id = %(session_id)s
|
||||
ORDER BY c.timestamp;""",
|
||||
{"project_id": project_id, "session_id": session_id})
|
||||
)
|
||||
rows = cur.fetchall()
|
||||
return helper.dict_to_camel_case(rows)
|
||||
def get_customs_by_session_id(session_id, project_id):
|
||||
return events.get_customs_by_session_id(session_id=session_id, project_id=project_id)
|
||||
|
||||
|
||||
def get_by_sessionId(session_id, project_id):
|
||||
|
|
@ -23,8 +11,8 @@ def get_by_sessionId(session_id, project_id):
|
|||
cur.execute(cur.mogrify(f"""
|
||||
SELECT
|
||||
c.*,
|
||||
'{events.EventType.CLICK_IOS.ui_type}' AS type
|
||||
FROM {events.EventType.CLICK_IOS.table} AS c
|
||||
'TAP' AS type
|
||||
FROM events_ios.taps AS c
|
||||
WHERE
|
||||
c.session_id = %(session_id)s
|
||||
ORDER BY c.timestamp;""",
|
||||
|
|
@ -35,8 +23,8 @@ def get_by_sessionId(session_id, project_id):
|
|||
cur.execute(cur.mogrify(f"""
|
||||
SELECT
|
||||
i.*,
|
||||
'{events.EventType.INPUT_IOS.ui_type}' AS type
|
||||
FROM {events.EventType.INPUT_IOS.table} AS i
|
||||
'INPUT' AS type
|
||||
FROM events_ios.inputs AS i
|
||||
WHERE
|
||||
i.session_id = %(session_id)s
|
||||
ORDER BY i.timestamp;""",
|
||||
|
|
@ -46,12 +34,21 @@ def get_by_sessionId(session_id, project_id):
|
|||
cur.execute(cur.mogrify(f"""
|
||||
SELECT
|
||||
v.*,
|
||||
'{events.EventType.VIEW_IOS.ui_type}' AS type
|
||||
FROM {events.EventType.VIEW_IOS.table} AS v
|
||||
'VIEW' AS type
|
||||
FROM events_ios.views AS v
|
||||
WHERE
|
||||
v.session_id = %(session_id)s
|
||||
ORDER BY v.timestamp;""", {"project_id": project_id, "session_id": session_id}))
|
||||
rows += cur.fetchall()
|
||||
cur.execute(cur.mogrify(f"""
|
||||
SELECT
|
||||
s.*,
|
||||
'SWIPE' AS type
|
||||
FROM events_ios.swipes AS s
|
||||
WHERE
|
||||
s.session_id = %(session_id)s
|
||||
ORDER BY s.timestamp;""", {"project_id": project_id, "session_id": session_id}))
|
||||
rows += cur.fetchall()
|
||||
rows = helper.list_to_camel_case(rows)
|
||||
rows = sorted(rows, key=lambda k: k["timestamp"])
|
||||
return rows
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ def search_feature_flags(project_id: int, user_id: int, data: schemas.SearchFlag
|
|||
SELECT COUNT(1) OVER () AS count, {", ".join(feature_flag_columns)}
|
||||
FROM feature_flags
|
||||
WHERE {" AND ".join(constraints)}
|
||||
ORDER BY updated_at {data.order.value}
|
||||
ORDER BY updated_at {data.order}
|
||||
LIMIT %(limit)s OFFSET %(offset)s;
|
||||
"""
|
||||
|
||||
|
|
@ -206,7 +206,7 @@ def prepare_params_to_create_flag(feature_flag_data, project_id, user_id):
|
|||
params = {
|
||||
"project_id": project_id,
|
||||
"created_by": user_id,
|
||||
**feature_flag_data.dict(),
|
||||
**feature_flag_data.model_dump(),
|
||||
**conditions_data,
|
||||
**variants_data,
|
||||
"payload": json.dumps(feature_flag_data.payload)
|
||||
|
|
@ -218,7 +218,7 @@ def prepare_params_to_create_flag(feature_flag_data, project_id, user_id):
|
|||
def prepare_variants_values(feature_flag_data):
|
||||
variants_data = {}
|
||||
for i, v in enumerate(feature_flag_data.variants):
|
||||
for k in v.dict().keys():
|
||||
for k in v.model_dump().keys():
|
||||
variants_data[f"v_{k}_{i}"] = v.__getattribute__(k)
|
||||
variants_data[f"v_value_{i}"] = v.value
|
||||
variants_data[f"v_description_{i}"] = v.description
|
||||
|
|
@ -230,11 +230,11 @@ def prepare_variants_values(feature_flag_data):
|
|||
def prepare_conditions_values(feature_flag_data):
|
||||
conditions_data = {}
|
||||
for i, s in enumerate(feature_flag_data.conditions):
|
||||
for k in s.dict().keys():
|
||||
for k in s.model_dump().keys():
|
||||
conditions_data[f"{k}_{i}"] = s.__getattribute__(k)
|
||||
conditions_data[f"name_{i}"] = s.name
|
||||
conditions_data[f"rollout_percentage_{i}"] = s.rollout_percentage
|
||||
conditions_data[f"filters_{i}"] = json.dumps([filter_.dict() for filter_ in s.filters])
|
||||
conditions_data[f"filters_{i}"] = json.dumps([filter_.model_dump() for filter_ in s.filters])
|
||||
return conditions_data
|
||||
|
||||
|
||||
|
|
@ -299,7 +299,7 @@ def create_conditions(feature_flag_id: int, conditions: List[schemas.FeatureFlag
|
|||
|
||||
with pg_client.PostgresClient() as cur:
|
||||
params = [
|
||||
(feature_flag_id, c.name, c.rollout_percentage, json.dumps([filter_.dict() for filter_ in c.filters]))
|
||||
(feature_flag_id, c.name, c.rollout_percentage, json.dumps([filter_.model_dump() for filter_ in c.filters]))
|
||||
for c in conditions]
|
||||
query = cur.mogrify(sql, params)
|
||||
cur.execute(query)
|
||||
|
|
@ -327,10 +327,10 @@ def update_feature_flag(project_id: int, feature_flag_id: int,
|
|||
)
|
||||
|
||||
params = {
|
||||
**feature_flag.dict(),
|
||||
"updated_by": user_id,
|
||||
"feature_flag_id": feature_flag_id,
|
||||
"project_id": project_id,
|
||||
**feature_flag.model_dump(),
|
||||
"payload": json.dumps(feature_flag.payload),
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from chalicelib.utils import helper
|
|||
from chalicelib.utils import sql_helper as sh
|
||||
|
||||
|
||||
def filter_stages(stages: List[schemas._SessionSearchEventSchema]):
|
||||
def filter_stages(stages: List[schemas.SessionSearchEventSchema2]):
|
||||
ALLOW_TYPES = [schemas.EventType.click, schemas.EventType.input,
|
||||
schemas.EventType.location, schemas.EventType.custom,
|
||||
schemas.EventType.click_ios, schemas.EventType.input_ios,
|
||||
|
|
@ -15,10 +15,10 @@ def filter_stages(stages: List[schemas._SessionSearchEventSchema]):
|
|||
|
||||
|
||||
def __parse_events(f_events: List[dict]):
|
||||
return [schemas._SessionSearchEventSchema.parse_obj(e) for e in f_events]
|
||||
return [schemas.SessionSearchEventSchema2.parse_obj(e) for e in f_events]
|
||||
|
||||
|
||||
def __fix_stages(f_events: List[schemas._SessionSearchEventSchema]):
|
||||
def __fix_stages(f_events: List[schemas.SessionSearchEventSchema2]):
|
||||
if f_events is None:
|
||||
return
|
||||
events = []
|
||||
|
|
@ -41,7 +41,7 @@ def get_top_insights_on_the_fly_widget(project_id, data: schemas.CardSeriesFilte
|
|||
data.events = __fix_stages(data.events)
|
||||
if len(data.events) == 0:
|
||||
return {"stages": [], "totalDropDueToIssues": 0}
|
||||
insights, total_drop_due_to_issues = significance.get_top_insights(filter_d=data.dict(), project_id=project_id)
|
||||
insights, total_drop_due_to_issues = significance.get_top_insights(filter_d=data, project_id=project_id)
|
||||
insights = helper.list_to_camel_case(insights)
|
||||
if len(insights) > 0:
|
||||
# TODO: check if this correct
|
||||
|
|
@ -64,5 +64,5 @@ def get_issues_on_the_fly_widget(project_id, data: schemas.CardSeriesFilterSchem
|
|||
|
||||
return {
|
||||
"issues": helper.dict_to_camel_case(
|
||||
significance.get_issues_list(filter_d=data.dict(), project_id=project_id, first_stage=1,
|
||||
significance.get_issues_list(filter_d=data, project_id=project_id, first_stage=1,
|
||||
last_stage=len(data.events)))}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from chalicelib.utils import helper, pg_client
|
|||
|
||||
|
||||
def get_by_url(project_id, data: schemas.GetHeatmapPayloadSchema):
|
||||
args = {"startDate": data.startDate, "endDate": data.endDate,
|
||||
args = {"startDate": data.startTimestamp, "endDate": data.endTimestamp,
|
||||
"project_id": project_id, "url": data.url}
|
||||
constraints = ["sessions.project_id = %(project_id)s",
|
||||
"(url = %(url)s OR path= %(url)s)",
|
||||
|
|
@ -43,13 +43,6 @@ def get_by_url(project_id, data: schemas.GetHeatmapPayloadSchema):
|
|||
f.value, value_key=f_k))
|
||||
constraints.append(sh.multi_conditions(f"mis.type = %({f_k})s",
|
||||
f.value, value_key=f_k))
|
||||
if len(f.filters) > 0:
|
||||
for j, sf in enumerate(f.filters):
|
||||
f_k = f"issue_svalue{i}{j}"
|
||||
args = {**args, **sh.multi_values(sf.value, value_key=f_k)}
|
||||
if sf.type == schemas.IssueFilterType._selector and len(sf.value) > 0:
|
||||
constraints.append(sh.multi_conditions(f"clicks.selector = %({f_k})s",
|
||||
sf.value, value_key=f_k))
|
||||
|
||||
if data.click_rage and not has_click_rage_filter:
|
||||
constraints.append("""(issues.session_id IS NULL
|
||||
|
|
|
|||
|
|
@ -72,16 +72,15 @@ class GitHubIntegration(integration_base.BaseIntegration):
|
|||
)
|
||||
return {"state": "success"}
|
||||
|
||||
def add_edit(self, data):
|
||||
def add_edit(self, data: schemas.IssueTrackingGithubSchema):
|
||||
s = self.get()
|
||||
if s is not None:
|
||||
return self.update(
|
||||
changes={
|
||||
"token": data["token"] \
|
||||
if data.get("token") and len(data["token"]) > 0 and data["token"].find("***") == -1 \
|
||||
else s["token"]
|
||||
"token": data.token if len(data.token) > 0 and data.token.find("***") == -1 \
|
||||
else s.token
|
||||
},
|
||||
obfuscate=True
|
||||
)
|
||||
else:
|
||||
return self.add(token=data["token"], obfuscate=True)
|
||||
return self.add(token=data.token, obfuscate=True)
|
||||
|
|
|
|||
|
|
@ -113,21 +113,20 @@ class JIRAIntegration(integration_base.BaseIntegration):
|
|||
)
|
||||
return {"state": "success"}
|
||||
|
||||
def add_edit(self, data):
|
||||
def add_edit(self, data: schemas.IssueTrackingJiraSchema):
|
||||
if self.integration is not None:
|
||||
return self.update(
|
||||
changes={
|
||||
"username": data["username"],
|
||||
"token": data["token"] \
|
||||
if data.get("token") and len(data["token"]) > 0 and data["token"].find("***") == -1 \
|
||||
else self.integration["token"],
|
||||
"url": data["url"]
|
||||
"username": data.username,
|
||||
"token": data.token if len(data.token) > 0 and data.token.find("***") == -1 \
|
||||
else self.integration.token,
|
||||
"url": data.url
|
||||
},
|
||||
obfuscate=True
|
||||
)
|
||||
else:
|
||||
return self.add(
|
||||
username=data["username"],
|
||||
token=data["token"],
|
||||
url=data["url"]
|
||||
username=data.username,
|
||||
token=data.token,
|
||||
url=data.url
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
from chalicelib.core import log_tools
|
||||
import requests
|
||||
|
||||
from schemas import schemas
|
||||
|
||||
IN_TY = "bugsnag"
|
||||
|
||||
|
||||
|
|
@ -60,14 +62,14 @@ def delete(tenant_id, project_id):
|
|||
return log_tools.delete(project_id=project_id, integration=IN_TY)
|
||||
|
||||
|
||||
def add_edit(tenant_id, project_id, data):
|
||||
def add_edit(tenant_id, project_id, data:schemas.IntegrationBugsnagSchema ):
|
||||
s = get(project_id)
|
||||
if s is not None:
|
||||
return update(tenant_id=tenant_id, project_id=project_id,
|
||||
changes={"authorizationToken": data["authorizationToken"],
|
||||
"bugsnagProjectId": data["bugsnagProjectId"]})
|
||||
changes={"authorizationToken": data.authorization_token,
|
||||
"bugsnagProjectId": data.bugsnag_project_id})
|
||||
else:
|
||||
return add(tenant_id=tenant_id,
|
||||
project_id=project_id,
|
||||
authorization_token=data["authorizationToken"],
|
||||
bugsnag_project_id=data["bugsnagProjectId"])
|
||||
authorization_token=data.authorization_token,
|
||||
bugsnag_project_id=data.bugsnag_project_id)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import boto3
|
||||
from chalicelib.core import log_tools
|
||||
from schemas import schemas
|
||||
|
||||
IN_TY = "cloudwatch"
|
||||
|
||||
|
|
@ -102,18 +103,18 @@ def delete(tenant_id, project_id):
|
|||
return log_tools.delete(project_id=project_id, integration=IN_TY)
|
||||
|
||||
|
||||
def add_edit(tenant_id, project_id, data):
|
||||
def add_edit(tenant_id, project_id, data: schemas.IntegrationCloudwatchSchema):
|
||||
s = get(project_id)
|
||||
if s is not None:
|
||||
return update(tenant_id=tenant_id, project_id=project_id,
|
||||
changes={"awsAccessKeyId": data["awsAccessKeyId"],
|
||||
"awsSecretAccessKey": data["awsSecretAccessKey"],
|
||||
"logGroupName": data["logGroupName"],
|
||||
"region": data["region"]})
|
||||
changes={"awsAccessKeyId": data.aws_access_key_id,
|
||||
"awsSecretAccessKey": data.aws_secret_access_key,
|
||||
"logGroupName": data.log_group_name,
|
||||
"region": data.region})
|
||||
else:
|
||||
return add(tenant_id=tenant_id,
|
||||
project_id=project_id,
|
||||
aws_access_key_id=data["awsAccessKeyId"],
|
||||
aws_secret_access_key=data["awsSecretAccessKey"],
|
||||
log_group_name=data["logGroupName"],
|
||||
region=data["region"])
|
||||
aws_access_key_id=data.aws_access_key_id,
|
||||
aws_secret_access_key=data.aws_secret_access_key,
|
||||
log_group_name=data.log_group_name,
|
||||
region=data.region)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
from chalicelib.core import log_tools
|
||||
from schemas import schemas
|
||||
|
||||
IN_TY = "datadog"
|
||||
|
||||
|
|
@ -30,14 +31,14 @@ def delete(tenant_id, project_id):
|
|||
return log_tools.delete(project_id=project_id, integration=IN_TY)
|
||||
|
||||
|
||||
def add_edit(tenant_id, project_id, data):
|
||||
def add_edit(tenant_id, project_id, data: schemas.IntegrationDatadogSchema):
|
||||
s = get(project_id)
|
||||
if s is not None:
|
||||
return update(tenant_id=tenant_id, project_id=project_id,
|
||||
changes={"apiKey": data["apiKey"],
|
||||
"applicationKey": data["applicationKey"]})
|
||||
changes={"apiKey": data.api_key,
|
||||
"applicationKey": data.application_key})
|
||||
else:
|
||||
return add(tenant_id=tenant_id,
|
||||
project_id=project_id,
|
||||
api_key=data["apiKey"],
|
||||
application_key=data["applicationKey"])
|
||||
api_key=data.api_key,
|
||||
application_key=data.application_key)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
# from elasticsearch import Elasticsearch, RequestsHttpConnection
|
||||
from elasticsearch import Elasticsearch
|
||||
from chalicelib.core import log_tools
|
||||
import logging
|
||||
|
||||
from schemas import schemas
|
||||
|
||||
logging.getLogger('elasticsearch').level = logging.ERROR
|
||||
|
||||
IN_TY = "elasticsearch"
|
||||
|
|
@ -44,17 +45,16 @@ def delete(tenant_id, project_id):
|
|||
return log_tools.delete(project_id=project_id, integration=IN_TY)
|
||||
|
||||
|
||||
def add_edit(tenant_id, project_id, data):
|
||||
def add_edit(tenant_id, project_id, data: schemas.IntegrationElasticsearchSchema):
|
||||
s = get(project_id)
|
||||
if s is not None:
|
||||
return update(tenant_id=tenant_id, project_id=project_id,
|
||||
changes={"host": data["host"], "apiKeyId": data["apiKeyId"], "apiKey": data["apiKey"],
|
||||
"indexes": data["indexes"], "port": data["port"]})
|
||||
changes={"host": data.host, "apiKeyId": data.api_key_id, "apiKey": data.api_key,
|
||||
"indexes": data.indexes, "port": data.port})
|
||||
else:
|
||||
return add(tenant_id=tenant_id,
|
||||
project_id=project_id,
|
||||
host=data["host"], api_key=data["apiKey"], api_key_id=data["apiKeyId"], indexes=data["indexes"],
|
||||
port=data["port"])
|
||||
return add(tenant_id=tenant_id, project_id=project_id,
|
||||
host=data.host, api_key=data.api_key, api_key_id=data.api_key_id,
|
||||
indexes=data.indexes, port=data.port)
|
||||
|
||||
|
||||
def __get_es_client(host, port, api_key_id, api_key, use_ssl=False, timeout=15):
|
||||
|
|
@ -64,15 +64,9 @@ def __get_es_client(host, port, api_key_id, api_key, use_ssl=False, timeout=15):
|
|||
args = {
|
||||
"hosts": [{"host": host, "port": port, "scheme": scheme}],
|
||||
"verify_certs": False,
|
||||
# "ca_certs": False,
|
||||
# "connection_class": RequestsHttpConnection,
|
||||
"request_timeout": timeout,
|
||||
"api_key": (api_key_id, api_key)
|
||||
}
|
||||
# if api_key_id is not None and len(api_key_id) > 0:
|
||||
# # args["http_auth"] = (username, password)
|
||||
# token = "ApiKey " + base64.b64encode(f"{api_key_id}:{api_key}".encode("utf-8")).decode("utf-8")
|
||||
# args["headers"] = {"Authorization": token}
|
||||
es = Elasticsearch(
|
||||
**args
|
||||
)
|
||||
|
|
@ -88,8 +82,8 @@ def __get_es_client(host, port, api_key_id, api_key, use_ssl=False, timeout=15):
|
|||
return es
|
||||
|
||||
|
||||
def ping(tenant_id, host, port, apiKeyId, apiKey):
|
||||
es = __get_es_client(host, port, apiKeyId, apiKey, timeout=3)
|
||||
def ping(tenant_id, data: schemas.IntegrationElasticsearchTestSchema):
|
||||
es = __get_es_client(data.host, data.port, data.api_key_id, data.api_key, timeout=3)
|
||||
if es is None:
|
||||
return {"state": False}
|
||||
return {"state": es.ping()}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
from chalicelib.core import log_tools
|
||||
from schemas import schemas
|
||||
|
||||
IN_TY = "newrelic"
|
||||
|
||||
|
|
@ -34,17 +35,16 @@ def delete(tenant_id, project_id):
|
|||
return log_tools.delete(project_id=project_id, integration=IN_TY)
|
||||
|
||||
|
||||
def add_edit(tenant_id, project_id, data):
|
||||
def add_edit(tenant_id, project_id, data: schemas.IntegrationNewrelicSchema):
|
||||
s = get(project_id)
|
||||
if s is not None:
|
||||
return update(tenant_id=tenant_id, project_id=project_id,
|
||||
changes={"applicationId": data["applicationId"],
|
||||
"xQueryKey": data["xQueryKey"],
|
||||
"region": data["region"]})
|
||||
changes={"applicationId": data.application_id,
|
||||
"xQueryKey": data.x_query_key,
|
||||
"region": data.region})
|
||||
else:
|
||||
return add(tenant_id=tenant_id,
|
||||
project_id=project_id,
|
||||
application_id=data["applicationId"],
|
||||
x_query_key=data["xQueryKey"],
|
||||
region=data["region"]
|
||||
)
|
||||
application_id=data.application_id,
|
||||
x_query_key=data.x_query_key,
|
||||
region=data.region)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
from chalicelib.core import log_tools
|
||||
from schemas import schemas
|
||||
|
||||
IN_TY = "rollbar"
|
||||
|
||||
|
|
@ -27,12 +28,12 @@ def delete(tenant_id, project_id):
|
|||
return log_tools.delete(project_id=project_id, integration=IN_TY)
|
||||
|
||||
|
||||
def add_edit(tenant_id, project_id, data):
|
||||
def add_edit(tenant_id, project_id, data: schemas.IntegrationRollbarSchema):
|
||||
s = get(project_id)
|
||||
if s is not None:
|
||||
return update(tenant_id=tenant_id, project_id=project_id,
|
||||
changes={"accessToken": data["accessToken"]})
|
||||
changes={"accessToken": data.access_token})
|
||||
else:
|
||||
return add(tenant_id=tenant_id,
|
||||
project_id=project_id,
|
||||
access_token=data["accessToken"])
|
||||
access_token=data.access_token)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import requests
|
||||
from chalicelib.core import log_tools
|
||||
from schemas import schemas
|
||||
|
||||
IN_TY = "sentry"
|
||||
|
||||
|
|
@ -35,18 +36,19 @@ def delete(tenant_id, project_id):
|
|||
return log_tools.delete(project_id=project_id, integration=IN_TY)
|
||||
|
||||
|
||||
def add_edit(tenant_id, project_id, data):
|
||||
def add_edit(tenant_id, project_id, data: schemas.IntegrationSentrySchema):
|
||||
s = get(project_id)
|
||||
if s is not None:
|
||||
return update(tenant_id=tenant_id, project_id=project_id,
|
||||
changes={"projectSlug": data["projectSlug"],
|
||||
"organizationSlug": data["organizationSlug"],
|
||||
"token": data["token"]})
|
||||
changes={"projectSlug": data.project_slug,
|
||||
"organizationSlug": data.organization_slug,
|
||||
"token": data.token})
|
||||
else:
|
||||
return add(tenant_id=tenant_id,
|
||||
project_id=project_id,
|
||||
project_slug=data["projectSlug"],
|
||||
organization_slug=data["organizationSlug"], token=data["token"])
|
||||
project_slug=data.project_slug,
|
||||
organization_slug=data.organization_slug,
|
||||
token=data.token)
|
||||
|
||||
|
||||
def proxy_get(tenant_id, project_id, event_id):
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
from chalicelib.core import log_tools
|
||||
from schemas import schemas
|
||||
|
||||
IN_TY = "stackdriver"
|
||||
|
||||
|
|
@ -29,14 +30,13 @@ def delete(tenant_id, project_id):
|
|||
return log_tools.delete(project_id=project_id, integration=IN_TY)
|
||||
|
||||
|
||||
def add_edit(tenant_id, project_id, data):
|
||||
def add_edit(tenant_id, project_id, data: schemas.IntegartionStackdriverSchema):
|
||||
s = get(project_id)
|
||||
if s is not None:
|
||||
return update(tenant_id=tenant_id, project_id=project_id,
|
||||
changes={"serviceAccountCredentials": data["serviceAccountCredentials"],
|
||||
"logName": data["logName"]})
|
||||
changes={"serviceAccountCredentials": data.service_account_credentials,
|
||||
"logName": data.log_name})
|
||||
else:
|
||||
return add(tenant_id=tenant_id,
|
||||
project_id=project_id,
|
||||
service_account_credentials=data["serviceAccountCredentials"],
|
||||
log_name=data["logName"])
|
||||
return add(tenant_id=tenant_id, project_id=project_id,
|
||||
service_account_credentials=data.service_account_credentials,
|
||||
log_name=data.log_name)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
from chalicelib.core import log_tools
|
||||
from schemas import schemas
|
||||
|
||||
IN_TY = "sumologic"
|
||||
|
||||
|
|
@ -38,16 +39,16 @@ def delete(tenant_id, project_id):
|
|||
return log_tools.delete(project_id=project_id, integration=IN_TY)
|
||||
|
||||
|
||||
def add_edit(tenant_id, project_id, data):
|
||||
def add_edit(tenant_id, project_id, data: schemas.IntegrationSumologicSchema):
|
||||
s = get(project_id)
|
||||
if s is not None:
|
||||
return update(tenant_id=tenant_id, project_id=project_id,
|
||||
changes={"accessId": data["accessId"],
|
||||
"accessKey": data["accessKey"],
|
||||
"region": data["region"]})
|
||||
changes={"accessId": data.access_id,
|
||||
"accessKey": data.access_key,
|
||||
"region": data.region})
|
||||
else:
|
||||
return add(tenant_id=tenant_id,
|
||||
project_id=project_id,
|
||||
access_id=data["accessId"],
|
||||
access_key=data["accessKey"],
|
||||
region=data["region"])
|
||||
access_id=data.access_id,
|
||||
access_key=data.access_key,
|
||||
region=data.region)
|
||||
|
|
|
|||
|
|
@ -243,45 +243,45 @@ def get_keys_by_projects(project_ids):
|
|||
return results
|
||||
|
||||
|
||||
def add_edit_delete(tenant_id, project_id, new_metas):
|
||||
old_metas = get(project_id)
|
||||
old_indexes = [k["index"] for k in old_metas]
|
||||
new_indexes = [k["index"] for k in new_metas if "index" in k]
|
||||
new_keys = [k["key"] for k in new_metas]
|
||||
|
||||
add_metas = [k["key"] for k in new_metas
|
||||
if "index" not in k]
|
||||
new_metas = {k["index"]: {"key": k["key"]} for
|
||||
k in new_metas if
|
||||
"index" in k}
|
||||
old_metas = {k["index"]: {"key": k["key"]} for k in old_metas}
|
||||
|
||||
if len(new_keys) > 20:
|
||||
return {"errors": ["you cannot add more than 20 key"]}
|
||||
for k in new_metas.keys():
|
||||
if re.match(regex, new_metas[k]["key"]) is None:
|
||||
return {"errors": [f"invalid key {k}"]}
|
||||
for k in add_metas:
|
||||
if re.match(regex, k) is None:
|
||||
return {"errors": [f"invalid key {k}"]}
|
||||
if len(new_indexes) > len(set(new_indexes)):
|
||||
return {"errors": ["duplicate indexes"]}
|
||||
if len(new_keys) > len(set(new_keys)):
|
||||
return {"errors": ["duplicate keys"]}
|
||||
to_delete = list(set(old_indexes) - set(new_indexes))
|
||||
|
||||
with pg_client.PostgresClient() as cur:
|
||||
for d in to_delete:
|
||||
delete(tenant_id=tenant_id, project_id=project_id, index=d)
|
||||
|
||||
for k in add_metas:
|
||||
add(tenant_id=tenant_id, project_id=project_id, new_name=k)
|
||||
|
||||
for k in new_metas.keys():
|
||||
if new_metas[k]["key"].lower() != old_metas[k]["key"]:
|
||||
edit(tenant_id=tenant_id, project_id=project_id, index=k, new_name=new_metas[k]["key"])
|
||||
|
||||
return {"data": get(project_id)}
|
||||
# def add_edit_delete(tenant_id, project_id, new_metas):
|
||||
# old_metas = get(project_id)
|
||||
# old_indexes = [k["index"] for k in old_metas]
|
||||
# new_indexes = [k["index"] for k in new_metas if "index" in k]
|
||||
# new_keys = [k["key"] for k in new_metas]
|
||||
#
|
||||
# add_metas = [k["key"] for k in new_metas
|
||||
# if "index" not in k]
|
||||
# new_metas = {k["index"]: {"key": k["key"]} for
|
||||
# k in new_metas if
|
||||
# "index" in k}
|
||||
# old_metas = {k["index"]: {"key": k["key"]} for k in old_metas}
|
||||
#
|
||||
# if len(new_keys) > 20:
|
||||
# return {"errors": ["you cannot add more than 20 key"]}
|
||||
# for k in new_metas.keys():
|
||||
# if re.match(regex, new_metas[k]["key"]) is None:
|
||||
# return {"errors": [f"invalid key {k}"]}
|
||||
# for k in add_metas:
|
||||
# if re.match(regex, k) is None:
|
||||
# return {"errors": [f"invalid key {k}"]}
|
||||
# if len(new_indexes) > len(set(new_indexes)):
|
||||
# return {"errors": ["duplicate indexes"]}
|
||||
# if len(new_keys) > len(set(new_keys)):
|
||||
# return {"errors": ["duplicate keys"]}
|
||||
# to_delete = list(set(old_indexes) - set(new_indexes))
|
||||
#
|
||||
# with pg_client.PostgresClient() as cur:
|
||||
# for d in to_delete:
|
||||
# delete(tenant_id=tenant_id, project_id=project_id, index=d)
|
||||
#
|
||||
# for k in add_metas:
|
||||
# add(tenant_id=tenant_id, project_id=project_id, new_name=k)
|
||||
#
|
||||
# for k in new_metas.keys():
|
||||
# if new_metas[k]["key"].lower() != old_metas[k]["key"]:
|
||||
# edit(tenant_id=tenant_id, project_id=project_id, index=k, new_name=new_metas[k]["key"])
|
||||
#
|
||||
# return {"data": get(project_id)}
|
||||
|
||||
|
||||
def get_remaining_metadata_with_count(tenant_id):
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ def get_all_count(tenant_id, user_id):
|
|||
|
||||
|
||||
def view_notification(user_id, notification_ids=[], tenant_id=None, startTimestamp=None, endTimestamp=None):
|
||||
if (notification_ids is None or len(notification_ids) == 0) and endTimestamp is None:
|
||||
if len(notification_ids) == 0 and endTimestamp is None:
|
||||
return False
|
||||
if startTimestamp is None:
|
||||
startTimestamp = 0
|
||||
|
|
|
|||
|
|
@ -1,8 +1,13 @@
|
|||
from typing import List
|
||||
|
||||
import schemas
|
||||
from chalicelib.core import metadata
|
||||
from chalicelib.core.metrics import __get_constraints, __get_constraint_values
|
||||
from chalicelib.utils import helper, dev
|
||||
from chalicelib.utils import pg_client
|
||||
from chalicelib.utils.TimeUTC import TimeUTC
|
||||
from chalicelib.utils import sql_helper as sh
|
||||
from time import time
|
||||
|
||||
|
||||
def __transform_journey(rows):
|
||||
|
|
@ -19,90 +24,346 @@ def __transform_journey(rows):
|
|||
return {"nodes": nodes, "links": sorted(links, key=lambda x: x["value"], reverse=True)}
|
||||
|
||||
|
||||
JOURNEY_DEPTH = 5
|
||||
def __transform_journey2(rows, reverse_path=False):
|
||||
# nodes should contain duplicates for different steps otherwise the UI crashes
|
||||
nodes = []
|
||||
nodes_values = []
|
||||
links = []
|
||||
for r in rows:
|
||||
source = f"{r['event_number_in_session']}_{r['event_type']}_{r['e_value']}"
|
||||
if source not in nodes:
|
||||
nodes.append(source)
|
||||
# TODO: remove this after UI supports long values
|
||||
nodes_values.append({"name": r['e_value'][:10], "eventType": r['event_type']})
|
||||
if r['next_value']:
|
||||
target = f"{r['event_number_in_session'] + 1}_{r['next_type']}_{r['next_value']}"
|
||||
if target not in nodes:
|
||||
nodes.append(target)
|
||||
# TODO: remove this after UI supports long values
|
||||
nodes_values.append({"name": r['next_value'][:10], "eventType": r['next_type']})
|
||||
link = {"eventType": r['event_type'], "value": r["sessions_count"],
|
||||
"avgTimeToTarget": r["avg_time_to_target"]}
|
||||
if not reverse_path:
|
||||
link["source"] = nodes.index(source)
|
||||
link["target"] = nodes.index(target)
|
||||
else:
|
||||
link["source"] = nodes.index(target)
|
||||
link["target"] = nodes.index(source)
|
||||
links.append(link)
|
||||
|
||||
return {"nodes": nodes_values,
|
||||
"links": sorted(links, key=lambda x: x["value"], reverse=True)}
|
||||
|
||||
|
||||
JOURNEY_TYPES = {
|
||||
schemas.ProductAnalyticsEventType.location: {"table": "events.pages", "column": "path", "table_id": "message_id"},
|
||||
schemas.ProductAnalyticsEventType.click: {"table": "events.clicks", "column": "label", "table_id": "message_id"},
|
||||
schemas.ProductAnalyticsEventType.input: {"table": "events.inputs", "column": "label", "table_id": "message_id"},
|
||||
schemas.ProductAnalyticsEventType.custom_event: {"table": "events_common.customs", "column": "name",
|
||||
"table_id": "seq_index"}
|
||||
schemas.ProductAnalyticsSelectedEventType.location: {"table": "events.pages", "column": "path"},
|
||||
schemas.ProductAnalyticsSelectedEventType.click: {"table": "events.clicks", "column": "label"},
|
||||
schemas.ProductAnalyticsSelectedEventType.input: {"table": "events.inputs", "column": "label"},
|
||||
schemas.ProductAnalyticsSelectedEventType.custom_event: {"table": "events_common.customs", "column": "name"}
|
||||
}
|
||||
|
||||
|
||||
def path_analysis(project_id, data: schemas.PathAnalysisSchema):
|
||||
def path_analysis(project_id: int, data: schemas.PathAnalysisSchema,
|
||||
selected_event_type: List[schemas.ProductAnalyticsSelectedEventType],
|
||||
density: int = 4, hide_minor_paths: bool = False):
|
||||
# pg_sub_query_subset = __get_constraints(project_id=project_id, data=args, duration=True, main_table="sessions",
|
||||
# time_constraint=True)
|
||||
# TODO: check if data=args is required
|
||||
pg_sub_query_subset = __get_constraints(project_id=project_id, duration=True, main_table="sessions",
|
||||
time_constraint=True)
|
||||
event_start = None
|
||||
event_table = JOURNEY_TYPES[schemas.ProductAnalyticsEventType.location]["table"]
|
||||
event_column = JOURNEY_TYPES[schemas.ProductAnalyticsEventType.location]["column"]
|
||||
event_table_id = JOURNEY_TYPES[schemas.ProductAnalyticsEventType.location]["table_id"]
|
||||
pg_sub_query_subset = __get_constraints(project_id=project_id, duration=True, main_table="s", time_constraint=True)
|
||||
sub_events = []
|
||||
start_points_join = ""
|
||||
start_points_conditions = []
|
||||
sessions_conditions = ["start_ts>=%(startTimestamp)s", "start_ts<%(endTimestamp)s",
|
||||
"project_id=%(project_id)s", "events_count > 1", "duration>0"]
|
||||
if len(selected_event_type) == 0:
|
||||
selected_event_type.append(schemas.ProductAnalyticsSelectedEventType.location)
|
||||
sub_events.append({"table": JOURNEY_TYPES[schemas.ProductAnalyticsSelectedEventType.location]["table"],
|
||||
"column": JOURNEY_TYPES[schemas.ProductAnalyticsSelectedEventType.location]["column"],
|
||||
"eventType": schemas.ProductAnalyticsSelectedEventType.location.value})
|
||||
else:
|
||||
for v in selected_event_type:
|
||||
if JOURNEY_TYPES.get(v):
|
||||
sub_events.append({"table": JOURNEY_TYPES[v]["table"],
|
||||
"column": JOURNEY_TYPES[v]["column"],
|
||||
"eventType": v})
|
||||
|
||||
extra_values = {}
|
||||
for f in data.filters:
|
||||
if f.type == schemas.ProductAnalyticsFilterType.start_point:
|
||||
event_start = f.value[0]
|
||||
elif f.type == schemas.ProductAnalyticsFilterType.event_type and JOURNEY_TYPES.get(f.value[0]):
|
||||
event_table = JOURNEY_TYPES[f.value[0]]["table"]
|
||||
event_column = JOURNEY_TYPES[f.value[0]]["column"]
|
||||
elif f.type == schemas.ProductAnalyticsFilterType.user_id:
|
||||
pg_sub_query_subset.append(f"sessions.user_id = %(user_id)s")
|
||||
extra_values["user_id"] = f.value
|
||||
reverse = False
|
||||
meta_keys = None
|
||||
exclusions = {}
|
||||
for i, f in enumerate(data.filters):
|
||||
op = sh.get_sql_operator(f.operator)
|
||||
is_any = sh.isAny_opreator(f.operator)
|
||||
is_not = sh.is_negation_operator(f.operator)
|
||||
is_undefined = sh.isUndefined_operator(f.operator)
|
||||
f_k = f"f_value_{i}"
|
||||
extra_values = {**extra_values, **sh.multi_values(f.value, value_key=f_k)}
|
||||
|
||||
if f.type in [schemas.ProductAnalyticsFilterType.start_point, schemas.ProductAnalyticsFilterType.end_point]:
|
||||
for sf in f.filters:
|
||||
extra_values = {**extra_values, **sh.multi_values(sf.value, value_key=f_k)}
|
||||
start_points_conditions.append(f"(event_type='{sf.type}' AND " +
|
||||
sh.multi_conditions(f'e_value {op} %({f_k})s', sf.value, is_not=is_not,
|
||||
value_key=f_k)
|
||||
+ ")")
|
||||
|
||||
reverse = f.type == schemas.ProductAnalyticsFilterType.end_point
|
||||
elif f.type == schemas.ProductAnalyticsFilterType.exclude:
|
||||
for sf in f.filters:
|
||||
if sf.type in selected_event_type:
|
||||
extra_values = {**extra_values, **sh.multi_values(sf.value, value_key=f_k)}
|
||||
exclusions[sf.type] = [
|
||||
sh.multi_conditions(f'{JOURNEY_TYPES[sf.type]["column"]} != %({f_k})s', sf.value, is_not=True,
|
||||
value_key=f_k)]
|
||||
|
||||
# ---- meta-filters
|
||||
if f.type == schemas.FilterType.user_browser:
|
||||
if is_any:
|
||||
sessions_conditions.append('user_browser IS NOT NULL')
|
||||
else:
|
||||
sessions_conditions.append(
|
||||
sh.multi_conditions(f'user_browser {op} %({f_k})s', f.value, is_not=is_not, value_key=f_k))
|
||||
|
||||
elif f.type in [schemas.FilterType.user_os]:
|
||||
if is_any:
|
||||
sessions_conditions.append('user_os IS NOT NULL')
|
||||
else:
|
||||
sessions_conditions.append(
|
||||
sh.multi_conditions(f'user_os {op} %({f_k})s', f.value, is_not=is_not, value_key=f_k))
|
||||
|
||||
elif f.type in [schemas.FilterType.user_device]:
|
||||
if is_any:
|
||||
sessions_conditions.append('user_device IS NOT NULL')
|
||||
else:
|
||||
sessions_conditions.append(
|
||||
sh.multi_conditions(f'user_device {op} %({f_k})s', f.value, is_not=is_not, value_key=f_k))
|
||||
|
||||
elif f.type in [schemas.FilterType.user_country]:
|
||||
if is_any:
|
||||
sessions_conditions.append('user_country IS NOT NULL')
|
||||
else:
|
||||
sessions_conditions.append(
|
||||
sh.multi_conditions(f'user_country {op} %({f_k})s', f.value, is_not=is_not, value_key=f_k))
|
||||
|
||||
elif f.type == schemas.FilterType.user_city:
|
||||
if is_any:
|
||||
sessions_conditions.append('user_city IS NOT NULL')
|
||||
else:
|
||||
sessions_conditions.append(
|
||||
sh.multi_conditions(f'user_city {op} %({f_k})s', f.value, is_not=is_not, value_key=f_k))
|
||||
|
||||
elif f.type == schemas.FilterType.user_state:
|
||||
if is_any:
|
||||
sessions_conditions.append('user_state IS NOT NULL')
|
||||
else:
|
||||
sessions_conditions.append(
|
||||
sh.multi_conditions(f'user_state {op} %({f_k})s', f.value, is_not=is_not, value_key=f_k))
|
||||
|
||||
elif f.type in [schemas.FilterType.utm_source]:
|
||||
if is_any:
|
||||
sessions_conditions.append('utm_source IS NOT NULL')
|
||||
elif is_undefined:
|
||||
sessions_conditions.append('utm_source IS NULL')
|
||||
else:
|
||||
sessions_conditions.append(
|
||||
sh.multi_conditions(f'utm_source {op} %({f_k})s::text', f.value, is_not=is_not,
|
||||
value_key=f_k))
|
||||
|
||||
elif f.type in [schemas.FilterType.utm_medium]:
|
||||
if is_any:
|
||||
sessions_conditions.append('utm_medium IS NOT NULL')
|
||||
elif is_undefined:
|
||||
sessions_conditions.append('utm_medium IS NULL')
|
||||
else:
|
||||
sessions_conditions.append(
|
||||
sh.multi_conditions(f'utm_medium {op} %({f_k})s::text', f.value, is_not=is_not,
|
||||
value_key=f_k))
|
||||
|
||||
elif f.type in [schemas.FilterType.utm_campaign]:
|
||||
if is_any:
|
||||
sessions_conditions.append('utm_campaign IS NOT NULL')
|
||||
elif is_undefined:
|
||||
sessions_conditions.append('utm_campaign IS NULL')
|
||||
else:
|
||||
sessions_conditions.append(
|
||||
sh.multi_conditions(f'utm_campaign {op} %({f_k})s::text', f.value, is_not=is_not,
|
||||
value_key=f_k))
|
||||
|
||||
elif f.type == schemas.FilterType.duration:
|
||||
if len(f.value) > 0 and f.value[0] is not None:
|
||||
sessions_conditions.append("duration >= %(minDuration)s")
|
||||
extra_values["minDuration"] = f.value[0]
|
||||
if len(f.value) > 1 and f.value[1] is not None and int(f.value[1]) > 0:
|
||||
sessions_conditions.append("duration <= %(maxDuration)s")
|
||||
extra_values["maxDuration"] = f.value[1]
|
||||
elif f.type == schemas.FilterType.referrer:
|
||||
# extra_from += f"INNER JOIN {events.event_type.LOCATION.table} AS p USING(session_id)"
|
||||
if is_any:
|
||||
sessions_conditions.append('base_referrer IS NOT NULL')
|
||||
else:
|
||||
sessions_conditions.append(
|
||||
sh.multi_conditions(f"base_referrer {op} %({f_k})s", f.value, is_not=is_not,
|
||||
value_key=f_k))
|
||||
elif f.type == schemas.FilterType.metadata:
|
||||
# get metadata list only if you need it
|
||||
if meta_keys is None:
|
||||
meta_keys = metadata.get(project_id=project_id)
|
||||
meta_keys = {m["key"]: m["index"] for m in meta_keys}
|
||||
if f.source in meta_keys.keys():
|
||||
if is_any:
|
||||
sessions_conditions.append(f"{metadata.index_to_colname(meta_keys[f.source])} IS NOT NULL")
|
||||
elif is_undefined:
|
||||
sessions_conditions.append(f"{metadata.index_to_colname(meta_keys[f.source])} IS NULL")
|
||||
else:
|
||||
sessions_conditions.append(
|
||||
sh.multi_conditions(
|
||||
f"{metadata.index_to_colname(meta_keys[f.source])} {op} %({f_k})s::text",
|
||||
f.value, is_not=is_not, value_key=f_k))
|
||||
|
||||
elif f.type in [schemas.FilterType.user_id, schemas.FilterType.user_id_ios]:
|
||||
if is_any:
|
||||
sessions_conditions.append('user_id IS NOT NULL')
|
||||
elif is_undefined:
|
||||
sessions_conditions.append('user_id IS NULL')
|
||||
else:
|
||||
sessions_conditions.append(
|
||||
sh.multi_conditions(f"s.user_id {op} %({f_k})s::text", f.value, is_not=is_not,
|
||||
value_key=f_k))
|
||||
|
||||
elif f.type in [schemas.FilterType.user_anonymous_id,
|
||||
schemas.FilterType.user_anonymous_id_ios]:
|
||||
if is_any:
|
||||
sessions_conditions.append('user_anonymous_id IS NOT NULL')
|
||||
elif is_undefined:
|
||||
sessions_conditions.append('user_anonymous_id IS NULL')
|
||||
else:
|
||||
sessions_conditions.append(
|
||||
sh.multi_conditions(f"user_anonymous_id {op} %({f_k})s::text", f.value, is_not=is_not,
|
||||
value_key=f_k))
|
||||
|
||||
elif f.type in [schemas.FilterType.rev_id, schemas.FilterType.rev_id_ios]:
|
||||
if is_any:
|
||||
sessions_conditions.append('rev_id IS NOT NULL')
|
||||
elif is_undefined:
|
||||
sessions_conditions.append('rev_id IS NULL')
|
||||
else:
|
||||
sessions_conditions.append(
|
||||
sh.multi_conditions(f"rev_id {op} %({f_k})s::text", f.value, is_not=is_not, value_key=f_k))
|
||||
|
||||
elif f.type == schemas.FilterType.platform:
|
||||
# op = __ sh.get_sql_operator(f.operator)
|
||||
sessions_conditions.append(
|
||||
sh.multi_conditions(f"user_device_type {op} %({f_k})s", f.value, is_not=is_not,
|
||||
value_key=f_k))
|
||||
|
||||
elif f.type == schemas.FilterType.issue:
|
||||
if is_any:
|
||||
sessions_conditions.append("array_length(issue_types, 1) > 0")
|
||||
else:
|
||||
sessions_conditions.append(
|
||||
sh.multi_conditions(f"%({f_k})s {op} ANY (issue_types)", f.value, is_not=is_not,
|
||||
value_key=f_k))
|
||||
|
||||
elif f.type == schemas.FilterType.events_count:
|
||||
sessions_conditions.append(
|
||||
sh.multi_conditions(f"events_count {op} %({f_k})s", f.value, is_not=is_not,
|
||||
value_key=f_k))
|
||||
events_subquery = []
|
||||
for t in sub_events:
|
||||
sub_events_conditions = ["e.timestamp >= %(startTimestamp)s",
|
||||
"e.timestamp < %(endTimestamp)s"] + exclusions.get(t["eventType"], [])
|
||||
events_subquery.append(f"""\
|
||||
SELECT session_id, {t["column"]} AS e_value, timestamp, '{t["eventType"]}' AS event_type
|
||||
FROM {t["table"]} AS e
|
||||
INNER JOIN sub_sessions USING (session_id)
|
||||
WHERE {" AND ".join(sub_events_conditions)}""")
|
||||
events_subquery = "\n UNION ALL \n".join(events_subquery)
|
||||
|
||||
if reverse:
|
||||
path_direction = "DESC"
|
||||
else:
|
||||
path_direction = ""
|
||||
|
||||
if len(start_points_conditions) == 0:
|
||||
start_points_join = """INNER JOIN
|
||||
(SELECT event_type, e_value
|
||||
FROM ranked_events
|
||||
WHERE event_number_in_session = 1
|
||||
GROUP BY event_type, e_value
|
||||
ORDER BY count(1) DESC
|
||||
LIMIT 2
|
||||
) AS top_start_events USING (event_type, e_value)"""
|
||||
else:
|
||||
start_points_conditions = ["(" + " OR ".join(start_points_conditions) + ")"]
|
||||
start_points_conditions.append("event_number_in_session = 1")
|
||||
start_points_conditions.append("next_value IS NOT NULL")
|
||||
|
||||
with pg_client.PostgresClient() as cur:
|
||||
pg_query = f"""SELECT source_event,
|
||||
target_event,
|
||||
count(*) AS value
|
||||
FROM (SELECT event_number || '_' || value as target_event,
|
||||
LAG(event_number || '_' || value, 1) OVER ( PARTITION BY session_rank ) AS source_event
|
||||
FROM (SELECT value,
|
||||
session_rank,
|
||||
message_id,
|
||||
ROW_NUMBER() OVER ( PARTITION BY session_rank ORDER BY timestamp ) AS event_number
|
||||
|
||||
{f"FROM (SELECT * FROM (SELECT *, MIN(mark) OVER ( PARTITION BY session_id , session_rank ORDER BY timestamp ) AS max FROM (SELECT *, CASE WHEN value = %(event_start)s THEN timestamp ELSE NULL END as mark"
|
||||
if event_start else ""}
|
||||
|
||||
FROM (SELECT session_id,
|
||||
message_id,
|
||||
timestamp,
|
||||
value,
|
||||
SUM(new_session) OVER (ORDER BY session_id, timestamp) AS session_rank
|
||||
FROM (SELECT *,
|
||||
CASE
|
||||
WHEN source_timestamp IS NULL THEN 1
|
||||
ELSE 0 END AS new_session
|
||||
FROM (SELECT session_id,
|
||||
{event_table_id} AS message_id,
|
||||
timestamp,
|
||||
{event_column} AS value,
|
||||
LAG(timestamp)
|
||||
OVER (PARTITION BY session_id ORDER BY timestamp) AS source_timestamp
|
||||
FROM {event_table} INNER JOIN public.sessions USING (session_id)
|
||||
WHERE {" AND ".join(pg_sub_query_subset)}
|
||||
) AS related_events) AS ranked_events) AS processed
|
||||
{") AS marked) AS maxed WHERE timestamp >= max) AS filtered" if event_start else ""}
|
||||
) AS sorted_events
|
||||
WHERE event_number <= %(JOURNEY_DEPTH)s) AS final
|
||||
WHERE source_event IS NOT NULL
|
||||
and target_event IS NOT NULL
|
||||
GROUP BY source_event, target_event
|
||||
ORDER BY value DESC
|
||||
LIMIT 20;"""
|
||||
pg_query = f"""\
|
||||
WITH sub_sessions AS ( SELECT session_id
|
||||
FROM public.sessions
|
||||
WHERE {" AND ".join(sessions_conditions)}),
|
||||
sub_events AS ({events_subquery}),
|
||||
ranked_events AS (SELECT *
|
||||
FROM (SELECT session_id,
|
||||
event_type,
|
||||
e_value,
|
||||
row_number() OVER (PARTITION BY session_id ORDER BY timestamp {path_direction}) AS event_number_in_session,
|
||||
LEAD(e_value, 1) OVER (PARTITION BY session_id ORDER BY timestamp {path_direction}) AS next_value,
|
||||
LEAD(event_type, 1) OVER (PARTITION BY session_id ORDER BY timestamp {path_direction}) AS next_type,
|
||||
abs(LEAD(timestamp, 1) OVER (PARTITION BY session_id ORDER BY timestamp {path_direction}) -
|
||||
timestamp) AS time_to_next
|
||||
FROM sub_events
|
||||
ORDER BY session_id) AS full_ranked_events
|
||||
WHERE event_number_in_session < %(density)s
|
||||
),
|
||||
start_points AS (SELECT session_id
|
||||
FROM ranked_events {start_points_join}
|
||||
WHERE {" AND ".join(start_points_conditions)}),
|
||||
limited_events AS (SELECT *
|
||||
FROM (SELECT *,
|
||||
row_number()
|
||||
OVER (PARTITION BY event_number_in_session, event_type, e_value ORDER BY sessions_count DESC ) AS _event_number_in_group
|
||||
FROM (SELECT event_number_in_session,
|
||||
event_type,
|
||||
e_value,
|
||||
next_type,
|
||||
next_value,
|
||||
time_to_next,
|
||||
count(1) AS sessions_count
|
||||
FROM ranked_events
|
||||
INNER JOIN start_points USING (session_id)
|
||||
GROUP BY event_number_in_session, event_type, e_value, next_type, next_value,
|
||||
time_to_next) AS groupped_events) AS ranked_groupped_events
|
||||
WHERE _event_number_in_group < %(eventThresholdNumberInGroup)s)
|
||||
SELECT event_number_in_session,
|
||||
event_type,
|
||||
e_value,
|
||||
next_type,
|
||||
next_value,
|
||||
sessions_count,
|
||||
avg(time_to_next) AS avg_time_to_target
|
||||
FROM limited_events
|
||||
GROUP BY event_number_in_session, event_type, e_value, next_type, next_value, sessions_count
|
||||
ORDER BY event_number_in_session, e_value, next_value;"""
|
||||
params = {"project_id": project_id, "startTimestamp": data.startTimestamp,
|
||||
"endTimestamp": data.endTimestamp, "event_start": event_start, "JOURNEY_DEPTH": JOURNEY_DEPTH,
|
||||
"endTimestamp": data.endTimestamp, "density": density,
|
||||
"eventThresholdNumberInGroup": 8 if hide_minor_paths else 6,
|
||||
# TODO: add if data=args is required
|
||||
# **__get_constraint_values(args),
|
||||
**extra_values}
|
||||
query = cur.mogrify(pg_query, params)
|
||||
print("----------------------")
|
||||
print(query)
|
||||
print("----------------------")
|
||||
_now = time()
|
||||
|
||||
cur.execute(query)
|
||||
if time() - _now > 3:
|
||||
print(f">>>>>>>>>PathAnalysis long query ({int(time() - _now)}s)<<<<<<<<<")
|
||||
print("----------------------")
|
||||
print(query)
|
||||
print("----------------------")
|
||||
rows = cur.fetchall()
|
||||
|
||||
return __transform_journey(rows)
|
||||
return __transform_journey2(rows=rows, reverse_path=reverse)
|
||||
|
||||
#
|
||||
# def __compute_weekly_percentage(rows):
|
||||
|
|
|
|||
|
|
@ -41,12 +41,12 @@ def __update(tenant_id, project_id, changes):
|
|||
return helper.dict_to_camel_case(cur.fetchone())
|
||||
|
||||
|
||||
def __create(tenant_id, name):
|
||||
def __create(tenant_id, data):
|
||||
with pg_client.PostgresClient() as cur:
|
||||
query = cur.mogrify(f"""INSERT INTO public.projects (name, active)
|
||||
VALUES (%(name)s,TRUE)
|
||||
query = cur.mogrify(f"""INSERT INTO public.projects (name, platform, active)
|
||||
VALUES (%(name)s,%(platform)s,TRUE)
|
||||
RETURNING project_id;""",
|
||||
{"name": name})
|
||||
data)
|
||||
cur.execute(query=query)
|
||||
project_id = cur.fetchone()["project_id"]
|
||||
return get_project(tenant_id=tenant_id, project_id=project_id, include_gdpr=True)
|
||||
|
|
@ -69,7 +69,8 @@ def get_projects(tenant_id: int, gdpr: bool = False, recorded: bool = False):
|
|||
|
||||
query = cur.mogrify(f"""{"SELECT *, first_recorded IS NOT NULL AS recorded FROM (" if recorded else ""}
|
||||
SELECT s.project_id, s.name, s.project_key, s.save_request_payloads, s.first_recorded_session_at,
|
||||
created_at, sessions_last_check_at, sample_rate {extra_projection}
|
||||
s.created_at, s.sessions_last_check_at, s.sample_rate, s.platform
|
||||
{extra_projection}
|
||||
FROM public.projects AS s
|
||||
WHERE s.deleted_at IS NULL
|
||||
ORDER BY s.name {") AS raw" if recorded else ""};""",
|
||||
|
|
@ -159,7 +160,7 @@ def create(tenant_id, user_id, data: schemas.CreateProjectSchema, skip_authoriza
|
|||
admin = users.get(user_id=user_id, tenant_id=tenant_id)
|
||||
if not admin["admin"] and not admin["superAdmin"]:
|
||||
return {"errors": ["unauthorized"]}
|
||||
return {"data": __create(tenant_id=tenant_id, name=data.name)}
|
||||
return {"data": __create(tenant_id=tenant_id, data=data.model_dump())}
|
||||
|
||||
|
||||
def edit(tenant_id, user_id, project_id, data: schemas.CreateProjectSchema):
|
||||
|
|
@ -169,7 +170,7 @@ def edit(tenant_id, user_id, project_id, data: schemas.CreateProjectSchema):
|
|||
if not admin["admin"] and not admin["superAdmin"]:
|
||||
return {"errors": ["unauthorized"]}
|
||||
return {"data": __update(tenant_id=tenant_id, project_id=project_id,
|
||||
changes={"name": data.name})}
|
||||
changes=data.model_dump())}
|
||||
|
||||
|
||||
def delete(tenant_id, user_id, project_id):
|
||||
|
|
@ -200,14 +201,14 @@ def get_gdpr(project_id):
|
|||
return row
|
||||
|
||||
|
||||
def edit_gdpr(project_id, gdpr):
|
||||
def edit_gdpr(project_id, gdpr: schemas.GdprSchema):
|
||||
with pg_client.PostgresClient() as cur:
|
||||
query = cur.mogrify("""UPDATE public.projects
|
||||
SET gdpr = gdpr|| %(gdpr)s
|
||||
WHERE project_id = %(project_id)s
|
||||
AND deleted_at ISNULL
|
||||
RETURNING gdpr;""",
|
||||
{"project_id": project_id, "gdpr": json.dumps(gdpr)})
|
||||
{"project_id": project_id, "gdpr": json.dumps(gdpr.model_dump_json())})
|
||||
cur.execute(query=query)
|
||||
row = cur.fetchone()
|
||||
if not row:
|
||||
|
|
@ -252,15 +253,9 @@ def get_capture_status(project_id):
|
|||
return helper.dict_to_camel_case(cur.fetchone())
|
||||
|
||||
|
||||
def update_capture_status(project_id, changes):
|
||||
if "rate" not in changes and "captureAll" not in changes:
|
||||
return {"errors": ["please provide 'rate' and/or 'captureAll' attributes to update."]}
|
||||
if int(changes["rate"]) < 0 or int(changes["rate"]) > 100:
|
||||
return {"errors": ["'rate' must be between 0..100."]}
|
||||
sample_rate = 0
|
||||
if "rate" in changes:
|
||||
sample_rate = int(changes["rate"])
|
||||
if changes.get("captureAll"):
|
||||
def update_capture_status(project_id, changes: schemas.SampleRateSchema):
|
||||
sample_rate = changes.rate
|
||||
if changes.capture_all:
|
||||
sample_rate = 100
|
||||
with pg_client.PostgresClient() as cur:
|
||||
query = cur.mogrify("""UPDATE public.projects
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ from chalicelib.utils.TimeUTC import TimeUTC
|
|||
|
||||
def create(project_id, user_id, data: schemas.SavedSearchSchema):
|
||||
with pg_client.PostgresClient() as cur:
|
||||
data = data.dict()
|
||||
data = data.model_dump()
|
||||
data["filter"] = json.dumps(data["filter"])
|
||||
query = cur.mogrify("""\
|
||||
INSERT INTO public.searches (project_id, user_id, name, filter,is_public)
|
||||
|
|
@ -25,7 +25,7 @@ def create(project_id, user_id, data: schemas.SavedSearchSchema):
|
|||
|
||||
def update(search_id, project_id, user_id, data: schemas.SavedSearchSchema):
|
||||
with pg_client.PostgresClient() as cur:
|
||||
data = data.dict()
|
||||
data = data.model_dump()
|
||||
data["filter"] = json.dumps(data["filter"])
|
||||
query = cur.mogrify(f"""\
|
||||
UPDATE public.searches
|
||||
|
|
@ -43,7 +43,6 @@ def update(search_id, project_id, user_id, data: schemas.SavedSearchSchema):
|
|||
r["created_at"] = TimeUTC.datetime_to_timestamp(r["created_at"])
|
||||
r["filter"] = helper.old_search_payload_to_flat(r["filter"])
|
||||
r = helper.dict_to_camel_case(r)
|
||||
# r["filter"]["startDate"], r["filter"]["endDate"] = TimeUTC.get_start_end_from_range(r["filter"]["rangeValue"])
|
||||
return r
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ COALESCE((SELECT TRUE
|
|||
def search_sessions(data: schemas.SessionsSearchPayloadSchema, project_id, user_id, errors_only=False,
|
||||
error_status=schemas.ErrorStatus.all, count_only=False, issue=None, ids_only=False):
|
||||
if data.bookmarked:
|
||||
data.startDate, data.endDate = sessions_favorite.get_start_end_timestamp(project_id, user_id)
|
||||
data.startTimestamp, data.endTimestamp = sessions_favorite.get_start_end_timestamp(project_id, user_id)
|
||||
|
||||
full_args, query_part = search_query_parts(data=data, error_status=error_status, errors_only=errors_only,
|
||||
favorite_only=data.bookmarked, issue=issue, project_id=project_id,
|
||||
|
|
@ -69,7 +69,7 @@ def search_sessions(data: schemas.SessionsSearchPayloadSchema, project_id, user_
|
|||
if data.order is None:
|
||||
data.order = schemas.SortOrderType.desc.value
|
||||
else:
|
||||
data.order = data.order.value
|
||||
data.order = data.order
|
||||
if data.sort is not None and data.sort != 'sessionsCount':
|
||||
sort = helper.key_to_snake_case(data.sort)
|
||||
g_sort = f"{'MIN' if data.order == schemas.SortOrderType.desc else 'MAX'}({sort})"
|
||||
|
|
@ -104,7 +104,7 @@ def search_sessions(data: schemas.SessionsSearchPayloadSchema, project_id, user_
|
|||
if data.order is None:
|
||||
data.order = schemas.SortOrderType.desc.value
|
||||
else:
|
||||
data.order = data.order.value
|
||||
data.order = data.order
|
||||
sort = 'session_id'
|
||||
if data.sort is not None and data.sort != "session_id":
|
||||
# sort += " " + data.order + "," + helper.key_to_snake_case(data.sort)
|
||||
|
|
@ -129,7 +129,7 @@ def search_sessions(data: schemas.SessionsSearchPayloadSchema, project_id, user_
|
|||
print("--------- SESSIONS SEARCH QUERY EXCEPTION -----------")
|
||||
print(main_query.decode('UTF-8'))
|
||||
print("--------- PAYLOAD -----------")
|
||||
print(data.json())
|
||||
print(data.model_dump_json())
|
||||
print("--------------------")
|
||||
raise err
|
||||
if errors_only or ids_only:
|
||||
|
|
@ -164,7 +164,7 @@ 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.MetricOfTable, metric_value: List):
|
||||
step_size = int(metrics_helper.__get_step_size(endTimestamp=data.endDate, startTimestamp=data.startDate,
|
||||
step_size = int(metrics_helper.__get_step_size(endTimestamp=data.endTimestamp, startTimestamp=data.startTimestamp,
|
||||
density=density, factor=1, decimal=True))
|
||||
extra_event = None
|
||||
if metric_of == schemas.MetricOfTable.visited_url:
|
||||
|
|
@ -204,7 +204,7 @@ def search2_series(data: schemas.SessionsSearchPayloadSchema, project_id: int, d
|
|||
print("--------- SESSIONS-SERIES QUERY EXCEPTION -----------")
|
||||
print(main_query.decode('UTF-8'))
|
||||
print("--------- PAYLOAD -----------")
|
||||
print(data.json())
|
||||
print(data.model_dump_json())
|
||||
print("--------------------")
|
||||
raise err
|
||||
if view_type == schemas.MetricTimeseriesViewType.line_chart:
|
||||
|
|
@ -267,7 +267,7 @@ def search2_series(data: schemas.SessionsSearchPayloadSchema, project_id: int, d
|
|||
return sessions
|
||||
|
||||
|
||||
def __is_valid_event(is_any: bool, event: schemas._SessionSearchEventSchema):
|
||||
def __is_valid_event(is_any: bool, event: schemas.SessionSearchEventSchema2):
|
||||
return not (not is_any and len(event.value) == 0 and event.type not in [schemas.EventType.request_details,
|
||||
schemas.EventType.graphql] \
|
||||
or event.type in [schemas.PerformanceEventType.location_dom_complete,
|
||||
|
|
@ -284,7 +284,7 @@ def __is_valid_event(is_any: bool, event: schemas._SessionSearchEventSchema):
|
|||
def search_query_parts(data: schemas.SessionsSearchPayloadSchema, error_status, errors_only, favorite_only, issue,
|
||||
project_id, user_id, extra_event=None):
|
||||
ss_constraints = []
|
||||
full_args = {"project_id": project_id, "startDate": data.startDate, "endDate": data.endDate,
|
||||
full_args = {"project_id": project_id, "startDate": data.startTimestamp, "endDate": data.endTimestamp,
|
||||
"projectId": project_id, "userId": user_id}
|
||||
extra_constraints = [
|
||||
"s.project_id = %(project_id)s",
|
||||
|
|
@ -516,22 +516,6 @@ def search_query_parts(data: schemas.SessionsSearchPayloadSchema, error_status,
|
|||
ss_constraints.append(
|
||||
sh.multi_conditions(f"%({f_k})s {op} ANY (ms.issue_types)", f.value, is_not=is_not,
|
||||
value_key=f_k))
|
||||
# search sessions with click_rage on a specific selector
|
||||
if len(f.filters) > 0 and schemas.IssueType.click_rage in f.value:
|
||||
for j, sf in enumerate(f.filters):
|
||||
if sf.operator == schemas.IssueFilterOperator._on_selector:
|
||||
f_k = f"f_value{i}_{j}"
|
||||
full_args = {**full_args, **sh.multi_values(sf.value, value_key=f_k)}
|
||||
extra_constraints += ["mc.timestamp>=%(startDate)s",
|
||||
"mc.timestamp<=%(endDate)s",
|
||||
"mis.type='click_rage'",
|
||||
sh.multi_conditions(f"mc.selector=%({f_k})s",
|
||||
sf.value, is_not=is_not,
|
||||
value_key=f_k)]
|
||||
|
||||
extra_from += """INNER JOIN events.clicks AS mc USING(session_id)
|
||||
INNER JOIN events_common.issues USING (session_id,timestamp)
|
||||
INNER JOIN public.issues AS mis USING (issue_id)\n"""
|
||||
|
||||
elif filter_type == schemas.FilterType.events_count:
|
||||
extra_constraints.append(
|
||||
|
|
@ -582,11 +566,11 @@ def search_query_parts(data: schemas.SessionsSearchPayloadSchema, error_status,
|
|||
event_where.append(f"event_{event_index - 1}.timestamp <= main.timestamp")
|
||||
e_k = f"e_value{i}"
|
||||
s_k = e_k + "_source"
|
||||
if event.type != schemas.PerformanceEventType.time_between_events:
|
||||
event.value = helper.values_for_operator(value=event.value, op=event.operator)
|
||||
full_args = {**full_args,
|
||||
**sh.multi_values(event.value, value_key=e_k),
|
||||
**sh.multi_values(event.source, value_key=s_k)}
|
||||
|
||||
event.value = helper.values_for_operator(value=event.value, op=event.operator)
|
||||
full_args = {**full_args,
|
||||
**sh.multi_values(event.value, value_key=e_k),
|
||||
**sh.multi_values(event.source, value_key=s_k)}
|
||||
|
||||
if event_type == events.EventType.CLICK.ui_type:
|
||||
event_from = event_from % f"{events.EventType.CLICK.table} AS main "
|
||||
|
|
@ -741,44 +725,6 @@ def search_query_parts(data: schemas.SessionsSearchPayloadSchema, error_status,
|
|||
event_where.append(f"{tname}.{colname} IS NOT NULL AND {tname}.{colname}>0 AND " +
|
||||
sh.multi_conditions(f"{tname}.{colname} {event.sourceOperator.value} %({e_k})s",
|
||||
event.source, value_key=e_k))
|
||||
elif event_type == schemas.PerformanceEventType.time_between_events:
|
||||
event_from = event_from % f"{getattr(events.EventType, event.value[0].type).table} AS main INNER JOIN {getattr(events.EventType, event.value[1].type).table} AS main2 USING(session_id) "
|
||||
if not isinstance(event.value[0].value, list):
|
||||
event.value[0].value = [event.value[0].value]
|
||||
if not isinstance(event.value[1].value, list):
|
||||
event.value[1].value = [event.value[1].value]
|
||||
event.value[0].value = helper.values_for_operator(value=event.value[0].value,
|
||||
op=event.value[0].operator)
|
||||
event.value[1].value = helper.values_for_operator(value=event.value[1].value,
|
||||
op=event.value[0].operator)
|
||||
e_k1 = e_k + "_e1"
|
||||
e_k2 = e_k + "_e2"
|
||||
full_args = {**full_args,
|
||||
**sh.multi_values(event.value[0].value, value_key=e_k1),
|
||||
**sh.multi_values(event.value[1].value, value_key=e_k2)}
|
||||
s_op = sh.get_sql_operator(event.value[0].operator)
|
||||
event_where += ["main2.timestamp >= %(startDate)s", "main2.timestamp <= %(endDate)s"]
|
||||
if event_index > 0 and not or_events:
|
||||
event_where.append("main2.session_id=event_0.session_id")
|
||||
is_any = sh.isAny_opreator(event.value[0].operator)
|
||||
if not is_any:
|
||||
event_where.append(
|
||||
sh.multi_conditions(
|
||||
f"main.{getattr(events.EventType, event.value[0].type).column} {s_op} %({e_k1})s",
|
||||
event.value[0].value, value_key=e_k1))
|
||||
s_op = sh.get_sql_operator(event.value[1].operator)
|
||||
is_any = sh.isAny_opreator(event.value[1].operator)
|
||||
if not is_any:
|
||||
event_where.append(
|
||||
sh.multi_conditions(
|
||||
f"main2.{getattr(events.EventType, event.value[1].type).column} {s_op} %({e_k2})s",
|
||||
event.value[1].value, value_key=e_k2))
|
||||
|
||||
e_k += "_custom"
|
||||
full_args = {**full_args, **sh.multi_values(event.source, value_key=e_k)}
|
||||
event_where.append(
|
||||
sh.multi_conditions(f"main2.timestamp - main.timestamp {event.sourceOperator.value} %({e_k})s",
|
||||
event.source, value_key=e_k))
|
||||
|
||||
elif event_type == schemas.EventType.request_details:
|
||||
event_from = event_from % f"{events.EventType.REQUEST.table} AS main "
|
||||
|
|
@ -905,9 +851,9 @@ def search_query_parts(data: schemas.SessionsSearchPayloadSchema, error_status,
|
|||
else:
|
||||
data.events = []
|
||||
# ---------------------------------------------------------------------------
|
||||
if data.startDate is not None:
|
||||
if data.startTimestamp is not None:
|
||||
extra_constraints.append("s.start_ts >= %(startDate)s")
|
||||
if data.endDate is not None:
|
||||
if data.endTimestamp is not None:
|
||||
extra_constraints.append("s.start_ts <= %(endDate)s")
|
||||
# if data.platform is not None:
|
||||
# if data.platform == schemas.PlatformType.mobile:
|
||||
|
|
|
|||
|
|
@ -14,6 +14,16 @@ def __get_mob_keys(project_id, session_id):
|
|||
]
|
||||
|
||||
|
||||
def __get_ios_video_keys(project_id, session_id):
|
||||
params = {
|
||||
"sessionId": session_id,
|
||||
"projectId": project_id
|
||||
}
|
||||
return [
|
||||
config("SESSION_IOS_VIDEO_PATTERN", default="replay.mp4") % params,
|
||||
]
|
||||
|
||||
|
||||
def __get_mob_keys_deprecated(session_id):
|
||||
return [str(session_id), str(session_id) + "e"]
|
||||
|
||||
|
|
@ -44,12 +54,30 @@ def get_urls_depercated(session_id, check_existence: bool = True):
|
|||
return results
|
||||
|
||||
|
||||
def get_ios(session_id):
|
||||
return StorageClient.get_presigned_url_for_sharing(
|
||||
bucket=config("ios_bucket"),
|
||||
expires_in=config("PRESIGNED_URL_EXPIRATION", cast=int, default=900),
|
||||
key=str(session_id)
|
||||
)
|
||||
def get_ios(session_id, project_id, check_existence=False):
|
||||
results = []
|
||||
for k in __get_mob_keys(project_id=project_id, session_id=session_id):
|
||||
if check_existence and not StorageClient.exists(bucket=config("IOS_BUCKET"), key=k):
|
||||
continue
|
||||
results.append(StorageClient.get_presigned_url_for_sharing(
|
||||
bucket=config("IOS_BUCKET"),
|
||||
expires_in=config("PRESIGNED_URL_EXPIRATION", cast=int, default=900),
|
||||
key=k
|
||||
))
|
||||
return results
|
||||
|
||||
|
||||
def get_ios_videos(session_id, project_id, check_existence=False):
|
||||
results = []
|
||||
for k in __get_ios_video_keys(project_id=project_id, session_id=session_id):
|
||||
if check_existence and not StorageClient.exists(bucket=config("IOS_VIDEO_BUCKET"), key=k):
|
||||
continue
|
||||
results.append(StorageClient.get_presigned_url_for_sharing(
|
||||
bucket=config("IOS_VIDEO_BUCKET"),
|
||||
expires_in=config("PRESIGNED_URL_EXPIRATION", cast=int, default=900),
|
||||
key=k
|
||||
))
|
||||
return results
|
||||
|
||||
|
||||
def delete_mobs(project_id, session_ids):
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ def get_all_notes_by_project_id(tenant_id, project_id, user_id, data: schemas.Se
|
|||
query = cur.mogrify(f"""SELECT sessions_notes.*, users.name AS user_name
|
||||
FROM sessions_notes INNER JOIN users USING (user_id)
|
||||
WHERE {" AND ".join(conditions)}
|
||||
ORDER BY created_at {data.order.value}
|
||||
ORDER BY created_at {data.order}
|
||||
LIMIT {data.limit} OFFSET {data.limit * (data.page - 1)};""",
|
||||
{"project_id": project_id, "user_id": user_id, "tenant_id": tenant_id, **extra_params})
|
||||
|
||||
|
|
@ -86,7 +86,7 @@ def create(tenant_id, user_id, project_id, session_id, data: schemas.SessionNote
|
|||
query = cur.mogrify(f"""INSERT INTO public.sessions_notes (message, user_id, tag, session_id, project_id, timestamp, is_public)
|
||||
VALUES (%(message)s, %(user_id)s, %(tag)s, %(session_id)s, %(project_id)s, %(timestamp)s, %(is_public)s)
|
||||
RETURNING *,(SELECT name FROM users WHERE users.user_id=%(user_id)s) AS user_name;""",
|
||||
{"user_id": user_id, "project_id": project_id, "session_id": session_id, **data.dict()})
|
||||
{"user_id": user_id, "project_id": project_id, "session_id": session_id, **data.model_dump()})
|
||||
cur.execute(query)
|
||||
result = helper.dict_to_camel_case(cur.fetchone())
|
||||
if result:
|
||||
|
|
@ -115,7 +115,7 @@ def edit(tenant_id, user_id, project_id, note_id, data: schemas.SessionUpdateNot
|
|||
AND note_id = %(note_id)s
|
||||
AND deleted_at ISNULL
|
||||
RETURNING *,(SELECT name FROM users WHERE users.user_id=%(user_id)s) AS user_name;""",
|
||||
{"project_id": project_id, "user_id": user_id, "note_id": note_id, **data.dict()})
|
||||
{"project_id": project_id, "user_id": user_id, "note_id": note_id, **data.model_dump()})
|
||||
)
|
||||
row = helper.dict_to_camel_case(cur.fetchone())
|
||||
if row:
|
||||
|
|
|
|||
|
|
@ -41,8 +41,6 @@ def get_by_id2_pg(project_id, session_id, context: schemas.CurrentContext, full_
|
|||
AND s.session_id = %(session_id)s;""",
|
||||
{"project_id": project_id, "session_id": session_id, "userId": context.user_id}
|
||||
)
|
||||
# print("===============")
|
||||
# print(query)
|
||||
cur.execute(query=query)
|
||||
|
||||
data = cur.fetchone()
|
||||
|
|
@ -55,9 +53,9 @@ def get_by_id2_pg(project_id, session_id, context: schemas.CurrentContext, full_
|
|||
if e["type"].endswith("_IOS"):
|
||||
e["type"] = e["type"][:-len("_IOS")]
|
||||
data['crashes'] = events_ios.get_crashes_by_session_id(session_id=session_id)
|
||||
data['userEvents'] = events_ios.get_customs_by_sessionId(project_id=project_id,
|
||||
session_id=session_id)
|
||||
data['mobsUrl'] = sessions_mobs.get_ios(session_id=session_id)
|
||||
data['userEvents'] = events_ios.get_customs_by_session_id(project_id=project_id,
|
||||
session_id=session_id)
|
||||
data['mobsUrl'] = sessions_mobs.get_ios(session_id=session_id, project_id=project_id)
|
||||
else:
|
||||
data['events'] = events.get_by_session_id(project_id=project_id, session_id=session_id,
|
||||
group_clickrage=True)
|
||||
|
|
@ -117,8 +115,6 @@ def get_replay(project_id, session_id, context: schemas.CurrentContext, full_dat
|
|||
AND s.session_id = %(session_id)s;""",
|
||||
{"project_id": project_id, "session_id": session_id, "userId": context.user_id}
|
||||
)
|
||||
# print("===============")
|
||||
# print(query)
|
||||
cur.execute(query=query)
|
||||
|
||||
data = cur.fetchone()
|
||||
|
|
@ -126,7 +122,10 @@ def get_replay(project_id, session_id, context: schemas.CurrentContext, full_dat
|
|||
data = helper.dict_to_camel_case(data)
|
||||
if full_data:
|
||||
if data["platform"] == 'ios':
|
||||
data['mobsUrl'] = sessions_mobs.get_ios(session_id=session_id)
|
||||
data['domURL'] = sessions_mobs.get_ios(session_id=session_id, project_id=project_id,
|
||||
check_existence=False)
|
||||
data['videoURL'] = sessions_mobs.get_ios_videos(session_id=session_id, project_id=project_id,
|
||||
check_existence=False)
|
||||
else:
|
||||
data['domURL'] = sessions_mobs.get_urls(session_id=session_id, project_id=project_id,
|
||||
check_existence=False)
|
||||
|
|
@ -154,8 +153,6 @@ def get_events(project_id, session_id):
|
|||
AND s.session_id = %(session_id)s;""",
|
||||
{"project_id": project_id, "session_id": session_id}
|
||||
)
|
||||
# print("===============")
|
||||
# print(query)
|
||||
cur.execute(query=query)
|
||||
|
||||
s_data = cur.fetchone()
|
||||
|
|
@ -168,8 +165,8 @@ def get_events(project_id, session_id):
|
|||
if e["type"].endswith("_IOS"):
|
||||
e["type"] = e["type"][:-len("_IOS")]
|
||||
data['crashes'] = events_ios.get_crashes_by_session_id(session_id=session_id)
|
||||
data['userEvents'] = events_ios.get_customs_by_sessionId(project_id=project_id,
|
||||
session_id=session_id)
|
||||
data['userEvents'] = events_ios.get_customs_by_session_id(project_id=project_id,
|
||||
session_id=session_id)
|
||||
else:
|
||||
data['events'] = events.get_by_session_id(project_id=project_id, session_id=session_id,
|
||||
group_clickrage=True)
|
||||
|
|
@ -199,11 +196,11 @@ def reduce_issues(issues_list):
|
|||
i = 0
|
||||
# remove same-type issues if the time between them is <2s
|
||||
while i < len(issues_list) - 1:
|
||||
for j in range(i+1,len(issues_list)):
|
||||
for j in range(i + 1, len(issues_list)):
|
||||
if issues_list[i]["type"] == issues_list[j]["type"]:
|
||||
break
|
||||
else:
|
||||
i+=1
|
||||
i += 1
|
||||
break
|
||||
|
||||
if issues_list[i]["timestamp"] - issues_list[j]["timestamp"] < 2000:
|
||||
|
|
|
|||
|
|
@ -24,17 +24,19 @@ T_VALUES = {1: 12.706, 2: 4.303, 3: 3.182, 4: 2.776, 5: 2.571, 6: 2.447, 7: 2.36
|
|||
21: 2.080, 22: 2.074, 23: 2.069, 25: 2.064, 26: 2.060, 27: 2.056, 28: 2.052, 29: 2.045, 30: 2.042}
|
||||
|
||||
|
||||
def get_stages_and_events(filter_d, project_id) -> List[RealDictRow]:
|
||||
def get_stages_and_events(filter_d: schemas.CardSeriesFilterSchema, project_id) -> List[RealDictRow]:
|
||||
"""
|
||||
Add minimal timestamp
|
||||
:param filter_d: dict contains events&filters&...
|
||||
:return:
|
||||
"""
|
||||
stages: [dict] = filter_d.get("events", [])
|
||||
filters: [dict] = filter_d.get("filters", [])
|
||||
filter_issues = filter_d.get("issueTypes")
|
||||
if filter_issues is None or len(filter_issues) == 0:
|
||||
filter_issues = []
|
||||
stages: [dict] = filter_d.events
|
||||
filters: [dict] = filter_d.filters
|
||||
filter_issues = []
|
||||
# TODO: enable this if needed by an endpoint
|
||||
# filter_issues = filter_d.get("issueTypes")
|
||||
# if filter_issues is None or len(filter_issues) == 0:
|
||||
# filter_issues = []
|
||||
stage_constraints = ["main.timestamp <= %(endTimestamp)s"]
|
||||
first_stage_extra_constraints = ["s.project_id=%(project_id)s", "s.start_ts >= %(startTimestamp)s",
|
||||
"s.start_ts <= %(endTimestamp)s"]
|
||||
|
|
@ -120,22 +122,22 @@ def get_stages_and_events(filter_d, project_id) -> List[RealDictRow]:
|
|||
i = -1
|
||||
for s in stages:
|
||||
|
||||
if s.get("operator") is None:
|
||||
s["operator"] = "is"
|
||||
if s.operator is None:
|
||||
s.operator = schemas.SearchEventOperator._is
|
||||
|
||||
if not isinstance(s["value"], list):
|
||||
s["value"] = [s["value"]]
|
||||
is_any = sh.isAny_opreator(s["operator"])
|
||||
if not is_any and isinstance(s["value"], list) and len(s["value"]) == 0:
|
||||
if not isinstance(s.value, list):
|
||||
s.value = [s.value]
|
||||
is_any = sh.isAny_opreator(s.operator)
|
||||
if not is_any and isinstance(s.value, list) and len(s.value) == 0:
|
||||
continue
|
||||
i += 1
|
||||
if i == 0:
|
||||
extra_from = filter_extra_from + ["INNER JOIN public.sessions AS s USING (session_id)"]
|
||||
else:
|
||||
extra_from = []
|
||||
op = sh.get_sql_operator(s["operator"])
|
||||
op = sh.get_sql_operator(s.operator)
|
||||
# event_type = s["type"].upper()
|
||||
event_type = s["type"]
|
||||
event_type = s.type
|
||||
if event_type == events.EventType.CLICK.ui_type:
|
||||
next_table = events.EventType.CLICK.table
|
||||
next_col_name = events.EventType.CLICK.column
|
||||
|
|
@ -165,16 +167,16 @@ def get_stages_and_events(filter_d, project_id) -> List[RealDictRow]:
|
|||
print(f"=================UNDEFINED:{event_type}")
|
||||
continue
|
||||
|
||||
values = {**values, **sh.multi_values(helper.values_for_operator(value=s["value"], op=s["operator"]),
|
||||
values = {**values, **sh.multi_values(helper.values_for_operator(value=s.value, op=s.operator),
|
||||
value_key=f"value{i + 1}")}
|
||||
if sh.is_negation_operator(s["operator"]) and i > 0:
|
||||
if sh.is_negation_operator(s.operator) and i > 0:
|
||||
op = sh.reverse_sql_operator(op)
|
||||
main_condition = "left_not.session_id ISNULL"
|
||||
extra_from.append(f"""LEFT JOIN LATERAL (SELECT session_id
|
||||
FROM {next_table} AS s_main
|
||||
WHERE
|
||||
{sh.multi_conditions(f"s_main.{next_col_name} {op} %(value{i + 1})s",
|
||||
values=s["value"], value_key=f"value{i + 1}")}
|
||||
values=s.value, value_key=f"value{i + 1}")}
|
||||
AND s_main.timestamp >= T{i}.stage{i}_timestamp
|
||||
AND s_main.session_id = T1.session_id) AS left_not ON (TRUE)""")
|
||||
else:
|
||||
|
|
@ -182,7 +184,7 @@ def get_stages_and_events(filter_d, project_id) -> List[RealDictRow]:
|
|||
main_condition = "TRUE"
|
||||
else:
|
||||
main_condition = sh.multi_conditions(f"main.{next_col_name} {op} %(value{i + 1})s",
|
||||
values=s["value"], value_key=f"value{i + 1}")
|
||||
values=s.value, value_key=f"value{i + 1}")
|
||||
n_stages_query.append(f"""
|
||||
(SELECT main.session_id,
|
||||
{"MIN(main.timestamp)" if i + 1 < len(stages) else "MAX(main.timestamp)"} AS stage{i + 1}_timestamp
|
||||
|
|
@ -225,7 +227,8 @@ def get_stages_and_events(filter_d, project_id) -> List[RealDictRow]:
|
|||
"""
|
||||
|
||||
# LIMIT 10000
|
||||
params = {"project_id": project_id, "startTimestamp": filter_d["startDate"], "endTimestamp": filter_d["endDate"],
|
||||
params = {"project_id": project_id, "startTimestamp": filter_d.startTimestamp,
|
||||
"endTimestamp": filter_d.endTimestamp,
|
||||
"issueTypes": tuple(filter_issues), **values}
|
||||
with pg_client.PostgresClient() as cur:
|
||||
query = cur.mogrify(n_stages_query, params)
|
||||
|
|
@ -239,7 +242,7 @@ def get_stages_and_events(filter_d, project_id) -> List[RealDictRow]:
|
|||
print("--------- FUNNEL SEARCH QUERY EXCEPTION -----------")
|
||||
print(query.decode('UTF-8'))
|
||||
print("--------- PAYLOAD -----------")
|
||||
print(filter_d)
|
||||
print(filter_d.model_dump_json())
|
||||
print("--------------------")
|
||||
raise err
|
||||
return rows
|
||||
|
|
@ -544,9 +547,9 @@ def get_issues(stages, rows, first_stage=None, last_stage=None, drop_only=False)
|
|||
return n_critical_issues, issues_dict, total_drop_due_to_issues
|
||||
|
||||
|
||||
def get_top_insights(filter_d, project_id):
|
||||
def get_top_insights(filter_d: schemas.CardSeriesFilterSchema, project_id):
|
||||
output = []
|
||||
stages = filter_d.get("events", [])
|
||||
stages = filter_d.events
|
||||
# TODO: handle 1 stage alone
|
||||
if len(stages) == 0:
|
||||
print("no stages found")
|
||||
|
|
@ -554,17 +557,24 @@ def get_top_insights(filter_d, project_id):
|
|||
elif len(stages) == 1:
|
||||
# TODO: count sessions, and users for single stage
|
||||
output = [{
|
||||
"type": stages[0]["type"],
|
||||
"value": stages[0]["value"],
|
||||
"type": stages[0].type,
|
||||
"value": stages[0].value,
|
||||
"dropPercentage": None,
|
||||
"operator": stages[0]["operator"],
|
||||
"operator": stages[0].operator,
|
||||
"sessionsCount": 0,
|
||||
"dropPct": 0,
|
||||
"usersCount": 0,
|
||||
"dropDueToIssues": 0
|
||||
|
||||
}]
|
||||
counts = sessions.search_sessions(data=schemas.SessionsSearchCountSchema.parse_obj(filter_d),
|
||||
# original
|
||||
# counts = sessions.search_sessions(data=schemas.SessionsSearchCountSchema.parse_obj(filter_d),
|
||||
# project_id=project_id, user_id=None, count_only=True)
|
||||
# first change
|
||||
# counts = sessions.search_sessions(data=schemas.FlatSessionsSearchPayloadSchema.parse_obj(filter_d),
|
||||
# project_id=project_id, user_id=None, count_only=True)
|
||||
# last change
|
||||
counts = sessions.search_sessions(data=schemas.SessionsSearchPayloadSchema.model_validate(filter_d),
|
||||
project_id=project_id, user_id=None, count_only=True)
|
||||
output[0]["sessionsCount"] = counts["countSessions"]
|
||||
output[0]["usersCount"] = counts["countUsers"]
|
||||
|
|
@ -583,9 +593,9 @@ def get_top_insights(filter_d, project_id):
|
|||
return stages_list, total_drop_due_to_issues
|
||||
|
||||
|
||||
def get_issues_list(filter_d, project_id, first_stage=None, last_stage=None):
|
||||
def get_issues_list(filter_d: schemas.CardSeriesFilterSchema, project_id, first_stage=None, last_stage=None):
|
||||
output = dict({"total_drop_due_to_issues": 0, "critical_issues_count": 0, "significant": [], "insignificant": []})
|
||||
stages = filter_d.get("events", [])
|
||||
stages = filter_d.events
|
||||
# The result of the multi-stage query
|
||||
rows = get_stages_and_events(filter_d=filter_d, project_id=project_id)
|
||||
# print(json.dumps(rows[0],indent=4))
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ def create_tenant(data: schemas.UserSignupSchema):
|
|||
|
||||
email = data.email
|
||||
print(f"=====================> {email}")
|
||||
password = data.password
|
||||
password = data.password.get_secret_value()
|
||||
|
||||
if email is None or len(email) < 5:
|
||||
errors.append("Invalid email address.")
|
||||
|
|
|
|||
|
|
@ -223,29 +223,29 @@ def update(tenant_id, user_id, changes, output=True):
|
|||
return get(user_id=user_id, tenant_id=tenant_id)
|
||||
|
||||
|
||||
def create_member(tenant_id, user_id, data, background_tasks: BackgroundTasks):
|
||||
def create_member(tenant_id, user_id, data: schemas.CreateMemberSchema, background_tasks: BackgroundTasks):
|
||||
admin = get(tenant_id=tenant_id, user_id=user_id)
|
||||
if not admin["admin"] and not admin["superAdmin"]:
|
||||
return {"errors": ["unauthorized"]}
|
||||
if data.get("userId") is not None:
|
||||
if data.user_id is not None:
|
||||
return {"errors": ["please use POST/PUT /client/members/{memberId} for update"]}
|
||||
user = get_by_email_only(email=data["email"])
|
||||
user = get_by_email_only(email=data.email)
|
||||
if user:
|
||||
return {"errors": ["user already exists"]}
|
||||
name = data.get("name", None)
|
||||
if name is None or len(name) == 0:
|
||||
name = data["email"]
|
||||
|
||||
if data.name is None or len(data.name) == 0:
|
||||
data.name = data.email
|
||||
invitation_token = __generate_invitation_token()
|
||||
user = get_deleted_user_by_email(email=data["email"])
|
||||
user = get_deleted_user_by_email(email=data.email)
|
||||
if user is not None:
|
||||
new_member = restore_member(email=data["email"], invitation_token=invitation_token,
|
||||
admin=data.get("admin", False), name=name, user_id=user["userId"])
|
||||
new_member = restore_member(email=data.email, invitation_token=invitation_token,
|
||||
admin=data.admin, name=data.name, user_id=user["userId"])
|
||||
else:
|
||||
new_member = create_new_member(email=data["email"], invitation_token=invitation_token,
|
||||
admin=data.get("admin", False), name=name)
|
||||
new_member = create_new_member(email=data.email, invitation_token=invitation_token,
|
||||
admin=data.admin, name=data.name)
|
||||
new_member["invitationLink"] = __get_invitation_link(new_member.pop("invitationToken"))
|
||||
background_tasks.add_task(email_helper.send_team_invitation, **{
|
||||
"recipient": data["email"],
|
||||
"recipient": data.email,
|
||||
"invitation_link": new_member["invitationLink"],
|
||||
"client_id": tenants.get_by_tenant_id(tenant_id)["name"],
|
||||
"sender_name": admin["name"]
|
||||
|
|
@ -607,11 +607,7 @@ def auth_exists(user_id, tenant_id, jwt_iat, jwt_aud):
|
|||
r = cur.fetchone()
|
||||
return r is not None \
|
||||
and r.get("jwt_iat") is not None \
|
||||
and (abs(jwt_iat - TimeUTC.datetime_to_timestamp(r["jwt_iat"]) // 1000) <= 1 \
|
||||
or (jwt_aud.startswith("plugin") \
|
||||
and (r["changed_at"] is None \
|
||||
or jwt_iat >= (TimeUTC.datetime_to_timestamp(r["changed_at"]) // 1000)))
|
||||
)
|
||||
and abs(jwt_iat - TimeUTC.datetime_to_timestamp(r["jwt_iat"]) // 1000) <= 1
|
||||
|
||||
|
||||
def change_jwt_iat(user_id):
|
||||
|
|
|
|||
|
|
@ -77,6 +77,8 @@ def update(tenant_id, webhook_id, changes, replace_none=False):
|
|||
{"id": webhook_id, **changes})
|
||||
)
|
||||
w = helper.dict_to_camel_case(cur.fetchone())
|
||||
if w is None:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"webhook not found.")
|
||||
w["createdAt"] = TimeUTC.datetime_to_timestamp(w["createdAt"])
|
||||
if replace_none:
|
||||
for k in w.keys():
|
||||
|
|
@ -120,20 +122,22 @@ def exists_by_name(name: str, exclude_id: Optional[int], webhook_type: str = sch
|
|||
return row["exists"]
|
||||
|
||||
|
||||
def add_edit(tenant_id, data, replace_none=None):
|
||||
if "name" in data and len(data["name"]) > 0 \
|
||||
and exists_by_name(name=data["name"], exclude_id=data.get("webhookId")):
|
||||
def add_edit(tenant_id, data: schemas.WebhookSchema, replace_none=None):
|
||||
if len(data.name) > 0 \
|
||||
and exists_by_name(name=data.name, exclude_id=data.webhook_id):
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=f"name already exists.")
|
||||
if data.get("webhookId") is not None:
|
||||
return update(tenant_id=tenant_id, webhook_id=data["webhookId"],
|
||||
changes={"endpoint": data["endpoint"],
|
||||
"authHeader": None if "authHeader" not in data else data["authHeader"],
|
||||
"name": data["name"] if "name" in data else ""}, replace_none=replace_none)
|
||||
if data.webhook_id is not None:
|
||||
return update(tenant_id=tenant_id, webhook_id=data.webhook_id,
|
||||
changes={"endpoint": data.endpoint,
|
||||
"authHeader": data.auth_header,
|
||||
"name": data.name},
|
||||
replace_none=replace_none)
|
||||
else:
|
||||
return add(tenant_id=tenant_id,
|
||||
endpoint=data["endpoint"],
|
||||
auth_header=None if "authHeader" not in data else data["authHeader"],
|
||||
name=data["name"] if "name" in data else "", replace_none=replace_none)
|
||||
endpoint=data.endpoint,
|
||||
auth_header=data.auth_header,
|
||||
name=data.name,
|
||||
replace_none=replace_none)
|
||||
|
||||
|
||||
def delete(tenant_id, webhook_id):
|
||||
|
|
|
|||
|
|
@ -49,9 +49,12 @@ EFS_DEVTOOLS_MOB_PATTERN=%(sessionId)sdevtools
|
|||
SESSION_MOB_PATTERN_S=%(sessionId)s/dom.mobs
|
||||
SESSION_MOB_PATTERN_E=%(sessionId)s/dom.mobe
|
||||
DEVTOOLS_MOB_PATTERN=%(sessionId)s/devtools.mob
|
||||
SESSION_IOS_VIDEO_PATTERN=%(sessionId)s/replay.mp4
|
||||
PRESIGNED_URL_EXPIRATION=3600
|
||||
ASSIST_JWT_EXPIRATION=144000
|
||||
ASSIST_JWT_SECRET=
|
||||
PYTHONUNBUFFERED=1
|
||||
REDIS_STRING=redis://redis-master.db.svc.cluster.local:6379
|
||||
SCH_DELETE_DAYS=30
|
||||
SCH_DELETE_DAYS=30
|
||||
IOS_BUCKET=mobs
|
||||
IOS_VIDEO_BUCKET=mobs
|
||||
|
|
@ -1,15 +1,16 @@
|
|||
requests==2.31.0
|
||||
# Keep this version to not have conflicts between requests and boto3
|
||||
urllib3==1.26.16
|
||||
boto3==1.26.148
|
||||
pyjwt==2.7.0
|
||||
psycopg2-binary==2.9.6
|
||||
elasticsearch==8.8.0
|
||||
jira==3.5.1
|
||||
requests==2.31.0
|
||||
boto3==1.28.30
|
||||
pyjwt==2.8.0
|
||||
psycopg2-binary==2.9.7
|
||||
elasticsearch==8.9.0
|
||||
jira==3.5.2
|
||||
|
||||
|
||||
|
||||
fastapi==0.96.0
|
||||
uvicorn[standard]==0.22.0
|
||||
fastapi==0.101.1
|
||||
uvicorn[standard]==0.23.2
|
||||
python-decouple==3.8
|
||||
pydantic[email]==1.10.8
|
||||
apscheduler==3.10.1
|
||||
pydantic[email]==2.2.1
|
||||
apscheduler==3.10.4
|
||||
|
|
|
|||
|
|
@ -1,17 +1,18 @@
|
|||
requests==2.31.0
|
||||
# Keep this version to not have conflicts between requests and boto3
|
||||
urllib3==1.26.16
|
||||
boto3==1.26.148
|
||||
pyjwt==2.7.0
|
||||
psycopg2-binary==2.9.6
|
||||
elasticsearch==8.8.0
|
||||
jira==3.5.1
|
||||
requests==2.31.0
|
||||
boto3==1.28.40
|
||||
pyjwt==2.8.0
|
||||
psycopg2-binary==2.9.7
|
||||
elasticsearch==8.9.0
|
||||
jira==3.5.2
|
||||
|
||||
|
||||
|
||||
fastapi==0.97.0
|
||||
uvicorn[standard]==0.22.0
|
||||
fastapi==0.103.1
|
||||
uvicorn[standard]==0.23.2
|
||||
python-decouple==3.8
|
||||
pydantic[email]==1.10.8
|
||||
apscheduler==3.10.1
|
||||
pydantic[email]==2.3.0
|
||||
apscheduler==3.10.4
|
||||
|
||||
redis==4.5.5
|
||||
redis==5.0.0
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
from typing import Union
|
||||
|
||||
from decouple import config
|
||||
from fastapi import Depends, Body
|
||||
|
||||
import schemas
|
||||
|
|
@ -18,20 +19,6 @@ from routers.base import get_routers
|
|||
public_app, app, app_apikey = get_routers()
|
||||
|
||||
|
||||
@app.post('/{projectId}/sessions/search', tags=["sessions"])
|
||||
def sessions_search(projectId: int, data: schemas.FlatSessionsSearchPayloadSchema = Body(...),
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
data = sessions.search_sessions(data=data, project_id=projectId, user_id=context.user_id)
|
||||
return {'data': data}
|
||||
|
||||
|
||||
@app.post('/{projectId}/sessions/search/ids', tags=["sessions"])
|
||||
def session_ids_search(projectId: int, data: schemas.FlatSessionsSearchPayloadSchema = Body(...),
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
data = sessions.search_sessions(data=data, project_id=projectId, user_id=context.user_id, ids_only=True)
|
||||
return {'data': data}
|
||||
|
||||
|
||||
@app.get('/{projectId}/events/search', tags=["events"])
|
||||
def events_search(projectId: int, q: str,
|
||||
type: Union[schemas.FilterType, schemas.EventType,
|
||||
|
|
@ -107,9 +94,9 @@ def get_sentry(projectId: int, context: schemas.CurrentContext = Depends(OR_cont
|
|||
|
||||
|
||||
@app.post('/{projectId}/integrations/sentry', tags=["integrations"])
|
||||
def add_edit_sentry(projectId: int, data: schemas.SentrySchema = Body(...),
|
||||
def add_edit_sentry(projectId: int, data: schemas.IntegrationSentrySchema = Body(...),
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
return {"data": log_tool_sentry.add_edit(tenant_id=context.tenant_id, project_id=projectId, data=data.dict())}
|
||||
return {"data": log_tool_sentry.add_edit(tenant_id=context.tenant_id, project_id=projectId, data=data)}
|
||||
|
||||
|
||||
@app.delete('/{projectId}/integrations/sentry', tags=["integrations"])
|
||||
|
|
@ -133,9 +120,9 @@ def get_datadog(projectId: int, context: schemas.CurrentContext = Depends(OR_con
|
|||
|
||||
|
||||
@app.post('/{projectId}/integrations/datadog', tags=["integrations"])
|
||||
def add_edit_datadog(projectId: int, data: schemas.DatadogSchema = Body(...),
|
||||
def add_edit_datadog(projectId: int, data: schemas.IntegrationDatadogSchema = Body(...),
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
return {"data": log_tool_datadog.add_edit(tenant_id=context.tenant_id, project_id=projectId, data=data.dict())}
|
||||
return {"data": log_tool_datadog.add_edit(tenant_id=context.tenant_id, project_id=projectId, data=data)}
|
||||
|
||||
|
||||
@app.delete('/{projectId}/integrations/datadog', tags=["integrations"])
|
||||
|
|
@ -154,9 +141,9 @@ def get_stackdriver(projectId: int, context: schemas.CurrentContext = Depends(OR
|
|||
|
||||
|
||||
@app.post('/{projectId}/integrations/stackdriver', tags=["integrations"])
|
||||
def add_edit_stackdriver(projectId: int, data: schemas.StackdriverSchema = Body(...),
|
||||
def add_edit_stackdriver(projectId: int, data: schemas.IntegartionStackdriverSchema = Body(...),
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
return {"data": log_tool_stackdriver.add_edit(tenant_id=context.tenant_id, project_id=projectId, data=data.dict())}
|
||||
return {"data": log_tool_stackdriver.add_edit(tenant_id=context.tenant_id, project_id=projectId, data=data)}
|
||||
|
||||
|
||||
@app.delete('/{projectId}/integrations/stackdriver', tags=["integrations"])
|
||||
|
|
@ -175,9 +162,9 @@ def get_newrelic(projectId: int, context: schemas.CurrentContext = Depends(OR_co
|
|||
|
||||
|
||||
@app.post('/{projectId}/integrations/newrelic', tags=["integrations"])
|
||||
def add_edit_newrelic(projectId: int, data: schemas.NewrelicSchema = Body(...),
|
||||
def add_edit_newrelic(projectId: int, data: schemas.IntegrationNewrelicSchema = Body(...),
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
return {"data": log_tool_newrelic.add_edit(tenant_id=context.tenant_id, project_id=projectId, data=data.dict())}
|
||||
return {"data": log_tool_newrelic.add_edit(tenant_id=context.tenant_id, project_id=projectId, data=data)}
|
||||
|
||||
|
||||
@app.delete('/{projectId}/integrations/newrelic', tags=["integrations"])
|
||||
|
|
@ -196,9 +183,9 @@ def get_rollbar(projectId: int, context: schemas.CurrentContext = Depends(OR_con
|
|||
|
||||
|
||||
@app.post('/{projectId}/integrations/rollbar', tags=["integrations"])
|
||||
def add_edit_rollbar(projectId: int, data: schemas.RollbarSchema = Body(...),
|
||||
def add_edit_rollbar(projectId: int, data: schemas.IntegrationRollbarSchema = Body(...),
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
return {"data": log_tool_rollbar.add_edit(tenant_id=context.tenant_id, project_id=projectId, data=data.dict())}
|
||||
return {"data": log_tool_rollbar.add_edit(tenant_id=context.tenant_id, project_id=projectId, data=data)}
|
||||
|
||||
|
||||
@app.delete('/{projectId}/integrations/rollbar', tags=["integrations"])
|
||||
|
|
@ -207,9 +194,9 @@ def delete_datadog(projectId: int, _=Body(None), context: schemas.CurrentContext
|
|||
|
||||
|
||||
@app.post('/integrations/bugsnag/list_projects', tags=["integrations"])
|
||||
def list_projects_bugsnag(data: schemas.BugsnagBasicSchema = Body(...),
|
||||
def list_projects_bugsnag(data: schemas.IntegrationBugsnagBasicSchema = Body(...),
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
return {"data": log_tool_bugsnag.list_projects(auth_token=data.authorizationToken)}
|
||||
return {"data": log_tool_bugsnag.list_projects(auth_token=data.authorization_token)}
|
||||
|
||||
|
||||
@app.get('/integrations/bugsnag', tags=["integrations"])
|
||||
|
|
@ -223,9 +210,9 @@ def get_bugsnag(projectId: int, context: schemas.CurrentContext = Depends(OR_con
|
|||
|
||||
|
||||
@app.post('/{projectId}/integrations/bugsnag', tags=["integrations"])
|
||||
def add_edit_bugsnag(projectId: int, data: schemas.BugsnagSchema = Body(...),
|
||||
def add_edit_bugsnag(projectId: int, data: schemas.IntegrationBugsnagSchema = Body(...),
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
return {"data": log_tool_bugsnag.add_edit(tenant_id=context.tenant_id, project_id=projectId, data=data.dict())}
|
||||
return {"data": log_tool_bugsnag.add_edit(tenant_id=context.tenant_id, project_id=projectId, data=data)}
|
||||
|
||||
|
||||
@app.delete('/{projectId}/integrations/bugsnag', tags=["integrations"])
|
||||
|
|
@ -234,7 +221,7 @@ def delete_bugsnag(projectId: int, _=Body(None), context: schemas.CurrentContext
|
|||
|
||||
|
||||
@app.post('/integrations/cloudwatch/list_groups', tags=["integrations"])
|
||||
def list_groups_cloudwatch(data: schemas.CloudwatchBasicSchema = Body(...),
|
||||
def list_groups_cloudwatch(data: schemas.IntegrationCloudwatchBasicSchema = Body(...),
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
return {"data": log_tool_cloudwatch.list_log_groups(aws_access_key_id=data.awsAccessKeyId,
|
||||
aws_secret_access_key=data.awsSecretAccessKey,
|
||||
|
|
@ -252,9 +239,9 @@ def get_cloudwatch(projectId: int, context: schemas.CurrentContext = Depends(OR_
|
|||
|
||||
|
||||
@app.post('/{projectId}/integrations/cloudwatch', tags=["integrations"])
|
||||
def add_edit_cloudwatch(projectId: int, data: schemas.CloudwatchSchema = Body(...),
|
||||
def add_edit_cloudwatch(projectId: int, data: schemas.IntegrationCloudwatchSchema = Body(...),
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
return {"data": log_tool_cloudwatch.add_edit(tenant_id=context.tenant_id, project_id=projectId, data=data.dict())}
|
||||
return {"data": log_tool_cloudwatch.add_edit(tenant_id=context.tenant_id, project_id=projectId, data=data)}
|
||||
|
||||
|
||||
@app.delete('/{projectId}/integrations/cloudwatch', tags=["integrations"])
|
||||
|
|
@ -273,16 +260,16 @@ def get_elasticsearch(projectId: int, context: schemas.CurrentContext = Depends(
|
|||
|
||||
|
||||
@app.post('/integrations/elasticsearch/test', tags=["integrations"])
|
||||
def test_elasticsearch_connection(data: schemas.ElasticsearchBasicSchema = Body(...),
|
||||
def test_elasticsearch_connection(data: schemas.IntegrationElasticsearchTestSchema = Body(...),
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
return {"data": log_tool_elasticsearch.ping(tenant_id=context.tenant_id, **data.dict())}
|
||||
return {"data": log_tool_elasticsearch.ping(tenant_id=context.tenant_id, data=data)}
|
||||
|
||||
|
||||
@app.post('/{projectId}/integrations/elasticsearch', tags=["integrations"])
|
||||
def add_edit_elasticsearch(projectId: int, data: schemas.ElasticsearchSchema = Body(...),
|
||||
def add_edit_elasticsearch(projectId: int, data: schemas.IntegrationElasticsearchSchema = Body(...),
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
return {
|
||||
"data": log_tool_elasticsearch.add_edit(tenant_id=context.tenant_id, project_id=projectId, data=data.dict())}
|
||||
"data": log_tool_elasticsearch.add_edit(tenant_id=context.tenant_id, project_id=projectId, data=data)}
|
||||
|
||||
|
||||
@app.delete('/{projectId}/integrations/elasticsearch', tags=["integrations"])
|
||||
|
|
@ -301,9 +288,9 @@ def get_sumologic(projectId: int, context: schemas.CurrentContext = Depends(OR_c
|
|||
|
||||
|
||||
@app.post('/{projectId}/integrations/sumologic', tags=["integrations"])
|
||||
def add_edit_sumologic(projectId: int, data: schemas.SumologicSchema = Body(...),
|
||||
def add_edit_sumologic(projectId: int, data: schemas.IntegrationSumologicSchema = Body(...),
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
return {"data": log_tool_sumologic.add_edit(tenant_id=context.tenant_id, project_id=projectId, data=data.dict())}
|
||||
return {"data": log_tool_sumologic.add_edit(tenant_id=context.tenant_id, project_id=projectId, data=data)}
|
||||
|
||||
|
||||
@app.delete('/{projectId}/integrations/sumologic', tags=["integrations"])
|
||||
|
|
@ -341,7 +328,7 @@ def get_integration_status_github(context: schemas.CurrentContext = Depends(OR_c
|
|||
|
||||
|
||||
@app.post('/integrations/jira', tags=["integrations"])
|
||||
def add_edit_jira_cloud(data: schemas.JiraSchema = Body(...),
|
||||
def add_edit_jira_cloud(data: schemas.IssueTrackingJiraSchema = Body(...),
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
if not data.url.endswith('atlassian.net'):
|
||||
return {"errors": ["url must be a valid JIRA URL (example.atlassian.net)"]}
|
||||
|
|
@ -350,18 +337,18 @@ def add_edit_jira_cloud(data: schemas.JiraSchema = Body(...),
|
|||
user_id=context.user_id)
|
||||
if error is not None and integration is None:
|
||||
return error
|
||||
return {"data": integration.add_edit(data=data.dict())}
|
||||
return {"data": integration.add_edit(data=data)}
|
||||
|
||||
|
||||
@app.post('/integrations/github', tags=["integrations"])
|
||||
def add_edit_github(data: schemas.GithubSchema = Body(...),
|
||||
def add_edit_github(data: schemas.IssueTrackingGithubSchema = Body(...),
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
error, integration = integrations_manager.get_integration(tool=integration_github.PROVIDER,
|
||||
tenant_id=context.tenant_id,
|
||||
user_id=context.user_id)
|
||||
if error is not None:
|
||||
return error
|
||||
return {"data": integration.add_edit(data=data.dict())}
|
||||
return {"data": integration.add_edit(data=data)}
|
||||
|
||||
|
||||
@app.delete('/integrations/issues', tags=["integrations"])
|
||||
|
|
@ -452,7 +439,7 @@ def get_gdpr(projectId: int, context: schemas.CurrentContext = Depends(OR_contex
|
|||
@app.post('/{projectId}/gdpr', tags=["projects", "gdpr"])
|
||||
def edit_gdpr(projectId: int, data: schemas.GdprSchema = Body(...),
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
result = projects.edit_gdpr(project_id=projectId, gdpr=data.dict())
|
||||
result = projects.edit_gdpr(project_id=projectId, gdpr=data)
|
||||
if "errors" in result:
|
||||
return result
|
||||
return {"data": result}
|
||||
|
|
@ -470,20 +457,20 @@ def get_metadata(projectId: int, context: schemas.CurrentContext = Depends(OR_co
|
|||
return {"data": metadata.get(project_id=projectId)}
|
||||
|
||||
|
||||
@app.post('/{projectId}/metadata/list', tags=["metadata"])
|
||||
def add_edit_delete_metadata(projectId: int, data: schemas.MetadataListSchema = Body(...),
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
return metadata.add_edit_delete(tenant_id=context.tenant_id, project_id=projectId, new_metas=data.list)
|
||||
# @app.post('/{projectId}/metadata/list', tags=["metadata"])
|
||||
# def add_edit_delete_metadata(projectId: int, data: schemas.MetadataListSchema = Body(...),
|
||||
# context: schemas.CurrentContext = Depends(OR_context)):
|
||||
# return metadata.add_edit_delete(tenant_id=context.tenant_id, project_id=projectId, new_metas=data.list)
|
||||
|
||||
|
||||
@app.post('/{projectId}/metadata', tags=["metadata"])
|
||||
def add_metadata(projectId: int, data: schemas.MetadataBasicSchema = Body(...),
|
||||
def add_metadata(projectId: int, data: schemas.MetadataSchema = Body(...),
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
return metadata.add(tenant_id=context.tenant_id, project_id=projectId, new_name=data.key)
|
||||
|
||||
|
||||
@app.post('/{projectId}/metadata/{index}', tags=["metadata"])
|
||||
def edit_metadata(projectId: int, index: int, data: schemas.MetadataBasicSchema = Body(...),
|
||||
def edit_metadata(projectId: int, index: int, data: schemas.MetadataSchema = Body(...),
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
return metadata.edit(tenant_id=context.tenant_id, project_id=projectId, index=index,
|
||||
new_name=data.key)
|
||||
|
|
@ -519,7 +506,7 @@ def get_capture_status(projectId: int, context: schemas.CurrentContext = Depends
|
|||
@app.post('/{projectId}/sample_rate', tags=["projects"])
|
||||
def update_capture_status(projectId: int, data: schemas.SampleRateSchema = Body(...),
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
return {"data": projects.update_capture_status(project_id=projectId, changes=data.dict())}
|
||||
return {"data": projects.update_capture_status(project_id=projectId, changes=data)}
|
||||
|
||||
|
||||
@app.get('/announcements', tags=["announcements"])
|
||||
|
|
@ -688,6 +675,8 @@ def batch_view_notifications(data: schemas.NotificationsViewSchema,
|
|||
|
||||
@app.get('/boarding', tags=['boarding'])
|
||||
def get_boarding_state(context: schemas.CurrentContext = Depends(OR_context)):
|
||||
if config("LOCAL_DEV", cast=bool, default=False):
|
||||
return {"data": ""}
|
||||
return {"data": boarding.get_state(tenant_id=context.tenant_id)}
|
||||
|
||||
|
||||
|
|
@ -727,9 +716,9 @@ def delete_slack_integration(webhookId: int, _=Body(None), context: schemas.Curr
|
|||
|
||||
|
||||
@app.put('/webhooks', tags=["webhooks"])
|
||||
def add_edit_webhook(data: schemas.CreateEditWebhookSchema = Body(...),
|
||||
def add_edit_webhook(data: schemas.WebhookSchema = Body(...),
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
return {"data": webhook.add_edit(tenant_id=context.tenant_id, data=data.dict(), replace_none=True)}
|
||||
return {"data": webhook.add_edit(tenant_id=context.tenant_id, data=data, replace_none=True)}
|
||||
|
||||
|
||||
@app.get('/webhooks', tags=["webhooks"])
|
||||
|
|
@ -739,7 +728,7 @@ def get_webhooks(context: schemas.CurrentContext = Depends(OR_context)):
|
|||
|
||||
@app.delete('/webhooks/{webhookId}', tags=["webhooks"])
|
||||
def delete_webhook(webhookId: int, _=Body(None), context: schemas.CurrentContext = Depends(OR_context)):
|
||||
return {"data": webhook.delete(tenant_id=context.tenant_id, webhook_id=webhookId)}
|
||||
return webhook.delete(tenant_id=context.tenant_id, webhook_id=webhookId)
|
||||
|
||||
|
||||
@app.get('/client/members', tags=["client"])
|
||||
|
|
@ -765,8 +754,8 @@ def generate_new_user_token(context: schemas.CurrentContext = Depends(OR_context
|
|||
@app.post('/account/password', tags=["account"])
|
||||
def change_client_password(data: schemas.EditUserPasswordSchema = Body(...),
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
return users.change_password(email=context.email, old_password=data.old_password,
|
||||
new_password=data.new_password, tenant_id=context.tenant_id,
|
||||
return users.change_password(email=context.email, old_password=data.old_password.get_secret_value(),
|
||||
new_password=data.new_password.get_secret_value(), tenant_id=context.tenant_id,
|
||||
user_id=context.user_id)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ def login_user(data: schemas.UserLoginSchema = Body(...)):
|
|||
detail="Invalid captcha."
|
||||
)
|
||||
|
||||
r = users.authenticate(data.email, data.password)
|
||||
r = users.authenticate(data.email, data.password.get_secret_value())
|
||||
if r is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
|
|
@ -129,7 +129,7 @@ def edit_slack_integration(integrationId: int, data: schemas.EditCollaborationSc
|
|||
@app.post('/client/members', tags=["client"])
|
||||
def add_member(background_tasks: BackgroundTasks, data: schemas.CreateMemberSchema = Body(...),
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
return users.create_member(tenant_id=context.tenant_id, user_id=context.user_id, data=data.dict(),
|
||||
return users.create_member(tenant_id=context.tenant_id, user_id=context.user_id, data=data,
|
||||
background_tasks=background_tasks)
|
||||
|
||||
|
||||
|
|
@ -160,7 +160,7 @@ def change_password_by_invitation(data: schemas.EditPasswordByInvitationSchema =
|
|||
if user["expiredChange"]:
|
||||
return {"errors": ["expired change, please re-use the invitation link"]}
|
||||
|
||||
return users.set_password_invitation(new_password=data.password, user_id=user["userId"])
|
||||
return users.set_password_invitation(new_password=data.password.get_secret_value(), user_id=user["userId"])
|
||||
|
||||
|
||||
@app.put('/client/members/{memberId}', tags=["client"])
|
||||
|
|
@ -193,8 +193,10 @@ def get_projects(context: schemas.CurrentContext = Depends(OR_context)):
|
|||
@app.get('/{projectId}/sessions/{sessionId}', tags=["sessions", "replay"])
|
||||
def get_session(projectId: int, sessionId: Union[int, str], background_tasks: BackgroundTasks,
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
if isinstance(sessionId, str):
|
||||
if not sessionId.isnumeric():
|
||||
return {"errors": ["session not found"]}
|
||||
else:
|
||||
sessionId = int(sessionId)
|
||||
data = sessions_replay.get_by_id2_pg(project_id=projectId, session_id=sessionId, full_data=True,
|
||||
include_fav_viewed=True, group_metadata=True, context=context)
|
||||
if data is None:
|
||||
|
|
@ -207,11 +209,27 @@ def get_session(projectId: int, sessionId: Union[int, str], background_tasks: Ba
|
|||
}
|
||||
|
||||
|
||||
@app.post('/{projectId}/sessions/search', tags=["sessions"])
|
||||
def sessions_search(projectId: int, data: schemas.SessionsSearchPayloadSchema = Body(...),
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
data = sessions.search_sessions(data=data, project_id=projectId, user_id=context.user_id)
|
||||
return {'data': data}
|
||||
|
||||
|
||||
@app.post('/{projectId}/sessions/search/ids', tags=["sessions"])
|
||||
def session_ids_search(projectId: int, data: schemas.SessionsSearchPayloadSchema = Body(...),
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
data = sessions.search_sessions(data=data, project_id=projectId, user_id=context.user_id, ids_only=True)
|
||||
return {'data': data}
|
||||
|
||||
|
||||
@app.get('/{projectId}/sessions/{sessionId}/replay', tags=["sessions", "replay"])
|
||||
def get_session_events(projectId: int, sessionId: Union[int, str], background_tasks: BackgroundTasks,
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
if isinstance(sessionId, str):
|
||||
if not sessionId.isnumeric():
|
||||
return {"errors": ["session not found"]}
|
||||
else:
|
||||
sessionId = int(sessionId)
|
||||
data = sessions_replay.get_replay(project_id=projectId, session_id=sessionId, full_data=True,
|
||||
include_fav_viewed=True, group_metadata=True, context=context)
|
||||
if data is None:
|
||||
|
|
@ -227,8 +245,10 @@ def get_session_events(projectId: int, sessionId: Union[int, str], background_ta
|
|||
@app.get('/{projectId}/sessions/{sessionId}/events', tags=["sessions", "replay"])
|
||||
def get_session_events(projectId: int, sessionId: Union[int, str],
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
if isinstance(sessionId, str):
|
||||
if not sessionId.isnumeric():
|
||||
return {"errors": ["session not found"]}
|
||||
else:
|
||||
sessionId = int(sessionId)
|
||||
data = sessions_replay.get_events(project_id=projectId, session_id=sessionId)
|
||||
if data is None:
|
||||
return {"errors": ["session not found"]}
|
||||
|
|
@ -249,18 +269,6 @@ def get_error_trace(projectId: int, sessionId: int, errorId: str,
|
|||
}
|
||||
|
||||
|
||||
@app.post('/{projectId}/errors/search', tags=['errors'])
|
||||
def errors_search(projectId: int, data: schemas.SearchErrorsSchema = Body(...),
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
return {"data": errors.search(data, projectId, user_id=context.user_id)}
|
||||
|
||||
|
||||
@app.get('/{projectId}/errors/stats', tags=['errors'])
|
||||
def errors_stats(projectId: int, startTimestamp: int, endTimestamp: int,
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
return errors.stats(projectId, user_id=context.user_id, startTimestamp=startTimestamp, endTimestamp=endTimestamp)
|
||||
|
||||
|
||||
@app.get('/{projectId}/errors/{errorId}', tags=['errors'])
|
||||
def errors_get_details(projectId: int, errorId: str, background_tasks: BackgroundTasks, density24: int = 24,
|
||||
density30: int = 30, context: schemas.CurrentContext = Depends(OR_context)):
|
||||
|
|
@ -272,15 +280,6 @@ def errors_get_details(projectId: int, errorId: str, background_tasks: Backgroun
|
|||
return data
|
||||
|
||||
|
||||
@app.get('/{projectId}/errors/{errorId}/stats', tags=['errors'])
|
||||
def errors_get_details_right_column(projectId: int, errorId: str, startDate: int = TimeUTC.now(-7),
|
||||
endDate: int = TimeUTC.now(), density: int = 7,
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
data = errors.get_details_chart(project_id=projectId, user_id=context.user_id, error_id=errorId,
|
||||
**{"startDate": startDate, "endDate": endDate, "density": density})
|
||||
return data
|
||||
|
||||
|
||||
@app.get('/{projectId}/errors/{errorId}/sourcemaps', tags=['errors'])
|
||||
def errors_get_details_sourcemaps(projectId: int, errorId: str,
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
|
|
@ -329,9 +328,10 @@ def get_live_session(projectId: int, sessionId: str, background_tasks: Backgroun
|
|||
def get_live_session_replay_file(projectId: int, sessionId: Union[int, str],
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
not_found = {"errors": ["Replay file not found"]}
|
||||
if isinstance(sessionId, str):
|
||||
print(f"{sessionId} not a valid number.")
|
||||
if not sessionId.isnumeric():
|
||||
return not_found
|
||||
else:
|
||||
sessionId = int(sessionId)
|
||||
if not sessions.session_exists(project_id=projectId, session_id=sessionId):
|
||||
print(f"{projectId}/{sessionId} not found in DB.")
|
||||
if not assist.session_exists(project_id=projectId, session_id=sessionId):
|
||||
|
|
@ -349,9 +349,10 @@ def get_live_session_replay_file(projectId: int, sessionId: Union[int, str],
|
|||
def get_live_session_devtools_file(projectId: int, sessionId: Union[int, str],
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
not_found = {"errors": ["Devtools file not found"]}
|
||||
if isinstance(sessionId, str):
|
||||
print(f"{sessionId} not a valid number.")
|
||||
if not sessionId.isnumeric():
|
||||
return not_found
|
||||
else:
|
||||
sessionId = int(sessionId)
|
||||
if not sessions.session_exists(project_id=projectId, session_id=sessionId):
|
||||
print(f"{projectId}/{sessionId} not found in DB.")
|
||||
if not assist.session_exists(project_id=projectId, session_id=sessionId):
|
||||
|
|
@ -484,7 +485,7 @@ def get_all_notes(projectId: int, data: schemas.SearchNoteSchema = Body(...),
|
|||
|
||||
|
||||
@app.post('/{projectId}/click_maps/search', tags=["click maps"])
|
||||
def click_map_search(projectId: int, data: schemas.FlatClickMapSessionsSearch = Body(...),
|
||||
def click_map_search(projectId: int, data: schemas.ClickMapSessionsSearch = Body(...),
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
return {"data": click_maps.search_short_session(user_id=context.user_id, data=data, project_id=projectId)}
|
||||
|
||||
|
|
@ -515,7 +516,7 @@ def update_feature_flag(project_id: int, feature_flag_id: int, data: schemas.Fea
|
|||
|
||||
|
||||
@app.delete('/{project_id}/feature-flags/{feature_flag_id}', tags=["feature flags"])
|
||||
async def delete_feature_flag(project_id: int, feature_flag_id: int, _=Body(None)):
|
||||
def delete_feature_flag(project_id: int, feature_flag_id: int, _=Body(None)):
|
||||
return {"data": feature_flags.delete_feature_flag(project_id=project_id, feature_flag_id=feature_flag_id)}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
from decouple import config
|
||||
from fastapi import HTTPException, status
|
||||
|
||||
from chalicelib.core import health, tenants
|
||||
|
|
@ -8,6 +9,8 @@ public_app, app, app_apikey = get_routers()
|
|||
|
||||
@app.get('/healthz', tags=["health-check"])
|
||||
def get_global_health_status():
|
||||
if config("LOCAL_DEV", cast=bool, default=False):
|
||||
return {"data": ""}
|
||||
return {"data": health.get_health()}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -11,10 +11,10 @@ public_app, app, app_apikey = get_routers()
|
|||
async def get_insights_journey(projectId: int):
|
||||
return {"data": product_analytics.path_analysis(project_id=projectId, data=schemas.PathAnalysisSchema())}
|
||||
|
||||
#
|
||||
# @app.post('/{projectId}/insights/journey', tags=["insights"])
|
||||
# async def get_insights_journey(projectId: int, data: schemas.MetricPayloadSchema = Body(...)):
|
||||
# return {"data": product_analytics.journey(project_id=projectId, data=data)}
|
||||
|
||||
@app.post('/{projectId}/insights/journey', tags=["insights"])
|
||||
async def get_insights_journey(projectId: int, data: schemas.PathAnalysisSchema = Body(...)):
|
||||
return {"data": product_analytics.path_analysis(project_id=projectId, data=data)}
|
||||
#
|
||||
#
|
||||
# @app.post('/{projectId}/insights/users_acquisition', tags=["insights"])
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ public_app, app, app_apikey = get_routers()
|
|||
|
||||
|
||||
@app.post('/{projectId}/dashboards', tags=["dashboard"])
|
||||
@app.put('/{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)):
|
||||
return dashboards.create_dashboard(project_id=projectId, user_id=context.user_id, data=data)
|
||||
|
|
@ -30,7 +30,7 @@ def get_dashboard(projectId: int, dashboardId: int, context: schemas.CurrentCont
|
|||
return {"data": data}
|
||||
|
||||
|
||||
@app.post('/{projectId}/dashboards/{dashboardId}', tags=["dashboard"])
|
||||
# @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)):
|
||||
|
|
@ -50,8 +50,8 @@ def pin_dashboard(projectId: int, dashboardId: int, context: schemas.CurrentCont
|
|||
|
||||
|
||||
@app.post('/{projectId}/dashboards/{dashboardId}/cards', tags=["cards"])
|
||||
@app.post('/{projectId}/dashboards/{dashboardId}/widgets', tags=["dashboard"])
|
||||
@app.put('/{projectId}/dashboards/{dashboardId}/widgets', tags=["dashboard"])
|
||||
# @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)):
|
||||
|
|
@ -60,7 +60,7 @@ def add_card_to_dashboard(projectId: int, dashboardId: int,
|
|||
|
||||
|
||||
@app.post('/{projectId}/dashboards/{dashboardId}/metrics', tags=["dashboard"])
|
||||
@app.put('/{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.CardSchema = Body(...),
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
|
|
@ -69,7 +69,7 @@ def create_metric_and_add_to_dashboard(projectId: int, dashboardId: int,
|
|||
|
||||
|
||||
@app.post('/{projectId}/dashboards/{dashboardId}/widgets/{widgetId}', tags=["dashboard"])
|
||||
@app.put('/{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)):
|
||||
|
|
@ -96,18 +96,18 @@ def remove_widget_from_dashboard(projectId: int, dashboardId: int, widgetId: int
|
|||
|
||||
|
||||
@app.post('/{projectId}/cards/try', tags=["cards"])
|
||||
@app.post('/{projectId}/metrics/try', tags=["dashboard"])
|
||||
@app.put('/{projectId}/metrics/try', tags=["dashboard"])
|
||||
@app.post('/{projectId}/custom_metrics/try', tags=["customMetrics"])
|
||||
@app.put('/{projectId}/custom_metrics/try', tags=["customMetrics"])
|
||||
# @app.post('/{projectId}/metrics/try', tags=["dashboard"])
|
||||
# @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.CardSchema = 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"])
|
||||
# @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)):
|
||||
data = custom_metrics.try_sessions(project_id=projectId, user_id=context.user_id, data=data)
|
||||
|
|
@ -115,48 +115,50 @@ 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"])
|
||||
# @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)):
|
||||
if len(data.series) == 0:
|
||||
return {"data": []}
|
||||
data.series[0].filter.startDate = data.startTimestamp
|
||||
data.series[0].filter.endDate = data.endTimestamp
|
||||
data.series[0].filter.startTimestamp = data.startTimestamp
|
||||
data.series[0].filter.endTimestamp = data.endTimestamp
|
||||
data = funnels.get_issues_on_the_fly_widget(project_id=projectId, data=data.series[0].filter)
|
||||
return {"data": data}
|
||||
|
||||
|
||||
@app.get('/{projectId}/cards', tags=["cards"])
|
||||
@app.get('/{projectId}/metrics', tags=["dashboard"])
|
||||
@app.get('/{projectId}/custom_metrics', tags=["customMetrics"])
|
||||
# @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": custom_metrics.get_all(project_id=projectId, user_id=context.user_id)}
|
||||
|
||||
|
||||
@app.post('/{projectId}/cards', tags=["cards"])
|
||||
@app.post('/{projectId}/metrics', tags=["dashboard"])
|
||||
@app.put('/{projectId}/metrics', tags=["dashboard"])
|
||||
@app.post('/{projectId}/custom_metrics', tags=["customMetrics"])
|
||||
@app.put('/{projectId}/custom_metrics', tags=["customMetrics"])
|
||||
# @app.post('/{projectId}/metrics', tags=["dashboard"])
|
||||
# @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.CardSchema = Body(...),
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
return custom_metrics.create(project_id=projectId, user_id=context.user_id, data=data)
|
||||
return custom_metrics.create_card(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"])
|
||||
# @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)):
|
||||
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"])
|
||||
# @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)):
|
||||
if not isinstance(metric_id, int):
|
||||
if metric_id.isnumeric():
|
||||
metric_id = int(metric_id)
|
||||
else:
|
||||
return {"errors": ["invalid card_id"]}
|
||||
data = custom_metrics.get_card(project_id=projectId, user_id=context.user_id, metric_id=metric_id)
|
||||
if data is None:
|
||||
|
|
@ -173,8 +175,8 @@ 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"])
|
||||
# @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)):
|
||||
|
|
@ -185,13 +187,15 @@ 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"])
|
||||
# @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)):
|
||||
if not isinstance(metric_id, int):
|
||||
return {"errors": [f"invalid card_id: {metric_id}"]}
|
||||
if metric_id.isnumeric():
|
||||
metric_id = int(metric_id)
|
||||
else:
|
||||
return {"errors": ["invalid card_id"]}
|
||||
|
||||
data = custom_metrics.get_funnel_issues(project_id=projectId, user_id=context.user_id, metric_id=metric_id,
|
||||
data=data)
|
||||
|
|
@ -201,8 +205,8 @@ 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"])
|
||||
# @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)):
|
||||
|
|
@ -214,22 +218,22 @@ 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"])
|
||||
# @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)):
|
||||
data = custom_metrics.get_errors_list(project_id=projectId, user_id=context.user_id, metric_id=metric_id,
|
||||
data=data)
|
||||
data = custom_metrics.get_errors_list(project_id=projectId, user_id=context.user_id,
|
||||
metric_id=metric_id, data=data)
|
||||
if data is None:
|
||||
return {"errors": ["custom metric not found"]}
|
||||
return {"data": data}
|
||||
|
||||
|
||||
@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(...),
|
||||
# @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.CardSessionsSchema = 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)
|
||||
|
|
@ -237,25 +241,25 @@ def get_card_chart(projectId: int, metric_id: int, request: Request, data: schem
|
|||
|
||||
|
||||
@app.post('/{projectId}/cards/{metric_id}', tags=["dashboard"])
|
||||
@app.post('/{projectId}/metrics/{metric_id}', tags=["dashboard"])
|
||||
@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(...),
|
||||
# @app.post('/{projectId}/metrics/{metric_id}', tags=["dashboard"])
|
||||
# @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.CardSchema = 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)
|
||||
data = custom_metrics.update_card(project_id=projectId, user_id=context.user_id, metric_id=metric_id, data=data)
|
||||
if data is None:
|
||||
return {"errors": ["custom metric not found"]}
|
||||
return {"data": data}
|
||||
|
||||
|
||||
@app.post('/{projectId}/cards/{metric_id}/status', tags=["dashboard"])
|
||||
@app.post('/{projectId}/metrics/{metric_id}/status', tags=["dashboard"])
|
||||
@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"])
|
||||
# @app.post('/{projectId}/metrics/{metric_id}/status', tags=["dashboard"])
|
||||
# @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(...),
|
||||
data: schemas.UpdateCardStatusSchema = 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,
|
||||
|
|
@ -263,8 +267,8 @@ 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"])
|
||||
# @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, _=Body(None),
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
return {"data": custom_metrics.delete(project_id=projectId, user_id=context.user_id, metric_id=metric_id)}
|
||||
return {"data": custom_metrics.delete_card(project_id=projectId, user_id=context.user_id, metric_id=metric_id)}
|
||||
|
|
|
|||
1457
api/schemas.py
1457
api/schemas.py
File diff suppressed because it is too large
Load diff
2
api/schemas/__init__.py
Normal file
2
api/schemas/__init__.py
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
from .schemas import *
|
||||
from . import overrides as _overrides
|
||||
62
api/schemas/overrides.py
Normal file
62
api/schemas/overrides.py
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
from typing import TypeVar, Annotated, Union, Any
|
||||
from enum import Enum as _Enum
|
||||
from pydantic import BaseModel as _BaseModel
|
||||
from pydantic import ConfigDict, TypeAdapter, Field
|
||||
from pydantic.types import AnyType
|
||||
|
||||
|
||||
def attribute_to_camel_case(snake_str: str) -> str:
|
||||
components = snake_str.split("_")
|
||||
return components[0] + ''.join(x.title() for x in components[1:])
|
||||
|
||||
|
||||
def transform_email(email: str) -> str:
|
||||
return email.lower().strip() if isinstance(email, str) else email
|
||||
|
||||
|
||||
def remove_whitespace(value: str) -> str:
|
||||
return " ".join(value.split()) if isinstance(value, str) else value
|
||||
|
||||
|
||||
def remove_duplicate_values(value: list) -> list:
|
||||
if value is not None and isinstance(value, list):
|
||||
if len(value) > 0 \
|
||||
and (isinstance(value[0], int) or isinstance(value[0], dict)):
|
||||
return value
|
||||
value = list(set(value))
|
||||
return value
|
||||
|
||||
|
||||
def single_to_list(value: Union[list, Any]) -> list:
|
||||
if value is not None and not isinstance(value, list):
|
||||
value = [value]
|
||||
return value
|
||||
|
||||
|
||||
def schema_extra(schema: dict, _):
|
||||
props = {}
|
||||
for k, v in schema.get('properties', {}).items():
|
||||
if not v.get("doc_hidden", False):
|
||||
props[k] = v
|
||||
schema["properties"] = props
|
||||
|
||||
|
||||
class BaseModel(_BaseModel):
|
||||
model_config = ConfigDict(alias_generator=attribute_to_camel_case,
|
||||
use_enum_values=True,
|
||||
json_schema_extra=schema_extra)
|
||||
|
||||
|
||||
class Enum(_Enum):
|
||||
@classmethod
|
||||
def has_value(cls, value) -> bool:
|
||||
return value in cls._value2member_map_
|
||||
|
||||
|
||||
T = TypeVar('T')
|
||||
|
||||
|
||||
class ORUnion:
|
||||
def __new__(self, union_types: Union[AnyType], discriminator: str) -> T:
|
||||
return lambda **args: TypeAdapter(Annotated[union_types, Field(discriminator=discriminator)]) \
|
||||
.validate_python(args)
|
||||
1644
api/schemas/schemas.py
Normal file
1644
api/schemas/schemas.py
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -18,10 +18,10 @@
|
|||
},
|
||||
"homepage": "https://github.com/openreplay/openreplay#readme",
|
||||
"dependencies": {
|
||||
"@maxmind/geoip2-node": "^3.5.0",
|
||||
"@maxmind/geoip2-node": "^4.2.0",
|
||||
"express": "^4.18.2",
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
"socket.io": "^4.6.1",
|
||||
"jsonwebtoken": "^9.0.1",
|
||||
"socket.io": "^4.7.2",
|
||||
"ua-parser-js": "^1.0.35"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
5
ee/api/.gitignore
vendored
5
ee/api/.gitignore
vendored
|
|
@ -260,7 +260,6 @@ Pipfile.lock
|
|||
/Dockerfile_bundle
|
||||
/entrypoint.bundle.sh
|
||||
/chalicelib/core/heatmaps.py
|
||||
/schemas.py
|
||||
#exp /chalicelib/core/custom_metrics.py
|
||||
/chalicelib/core/performance_event.py
|
||||
/chalicelib/core/saved_search.py
|
||||
|
|
@ -270,4 +269,6 @@ Pipfile.lock
|
|||
/run-dev.sh
|
||||
/run-alerts-dev.sh
|
||||
/routers/subs/v1_api.py
|
||||
#exp /chalicelib/core/dashboards.py
|
||||
#exp /chalicelib/core/dashboards.py
|
||||
/schemas/overrides.py
|
||||
/schemas/schemas.py
|
||||
|
|
|
|||
|
|
@ -4,22 +4,24 @@ verify_ssl = true
|
|||
name = "pypi"
|
||||
|
||||
[packages]
|
||||
requests = "==2.31.0"
|
||||
urllib3 = "==1.26.16"
|
||||
boto3 = "==1.26.148"
|
||||
pyjwt = "==2.7.0"
|
||||
psycopg2-binary = "==2.9.6"
|
||||
elasticsearch = "==8.8.0"
|
||||
jira = "==3.5.1"
|
||||
fastapi = "==0.96.0"
|
||||
uvicorn = {version = "==0.22.0", extras = ["standard"]}
|
||||
requests = "==2.31.0"
|
||||
boto3 = "==1.28.40"
|
||||
pyjwt = "==2.8.0"
|
||||
psycopg2-binary = "==2.9.7"
|
||||
elasticsearch = "==8.9.0"
|
||||
jira = "==3.5.2"
|
||||
fastapi = "==0.103.1"
|
||||
gunicorn = "==21.2.0"
|
||||
python-decouple = "==3.8"
|
||||
pydantic = {version = "==1.10.8", extras = ["email"]}
|
||||
apscheduler = "==3.10.1"
|
||||
clickhouse-driver = {version = "==0.2.5", extras = ["lz4"]}
|
||||
apscheduler = "==3.10.4"
|
||||
python3-saml = "==1.15.0"
|
||||
python-multipart = "==0.0.6"
|
||||
redis = "==4.5.5"
|
||||
azure-storage-blob = "==12.16.0"
|
||||
redis = "==5.0.0"
|
||||
azure-storage-blob = "==12.17.0"
|
||||
uvicorn = {version = "==0.23.2", extras = ["standard"]}
|
||||
pydantic = {version = "==2.3.0", extras = ["email"]}
|
||||
clickhouse-driver = {version = "==0.2.6", extras = ["lz4"]}
|
||||
|
||||
[dev-packages]
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,11 @@ from chalicelib.core import traces
|
|||
from chalicelib.utils import events_queue
|
||||
from chalicelib.utils import helper
|
||||
from chalicelib.utils import pg_client
|
||||
from routers import core, core_dynamic, ee, saml
|
||||
from routers import core, core_dynamic
|
||||
from routers import ee
|
||||
|
||||
if config("ENABLE_SSO", cast=bool, default=True):
|
||||
from routers import saml
|
||||
from crons import core_crons, ee_crons, core_dynamic_crons
|
||||
from routers.subs import insights, metrics, v1_api_ee
|
||||
from routers.subs import v1_api, health
|
||||
|
|
@ -97,9 +101,6 @@ app.include_router(core_dynamic.app_apikey)
|
|||
app.include_router(ee.public_app)
|
||||
app.include_router(ee.app)
|
||||
app.include_router(ee.app_apikey)
|
||||
app.include_router(saml.public_app)
|
||||
app.include_router(saml.app)
|
||||
app.include_router(saml.app_apikey)
|
||||
app.include_router(metrics.app)
|
||||
app.include_router(insights.app)
|
||||
app.include_router(v1_api.app_apikey)
|
||||
|
|
@ -107,3 +108,8 @@ app.include_router(v1_api_ee.app_apikey)
|
|||
app.include_router(health.public_app)
|
||||
app.include_router(health.app)
|
||||
app.include_router(health.app_apikey)
|
||||
|
||||
if config("ENABLE_SSO", cast=bool, default=True):
|
||||
app.include_router(saml.public_app)
|
||||
app.include_router(saml.app)
|
||||
app.include_router(saml.app_apikey)
|
||||
|
|
|
|||
|
|
@ -6,19 +6,19 @@ from starlette import status
|
|||
from starlette.exceptions import HTTPException
|
||||
|
||||
from chalicelib.core import authorizers, users
|
||||
import schemas_ee
|
||||
import schemas
|
||||
|
||||
|
||||
class JWTAuth(HTTPBearer):
|
||||
def __init__(self, auto_error: bool = True):
|
||||
super(JWTAuth, self).__init__(auto_error=auto_error)
|
||||
|
||||
async def __call__(self, request: Request) -> Optional[schemas_ee.CurrentContext]:
|
||||
async def __call__(self, request: Request) -> Optional[schemas.CurrentContext]:
|
||||
credentials: HTTPAuthorizationCredentials = await super(JWTAuth, self).__call__(request)
|
||||
if credentials:
|
||||
if not credentials.scheme == "Bearer":
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid authentication scheme.")
|
||||
jwt_payload = authorizers.jwt_authorizer(credentials.scheme + " " + credentials.credentials)
|
||||
jwt_payload = authorizers.jwt_authorizer(scheme=credentials.scheme, token=credentials.credentials)
|
||||
auth_exists = jwt_payload is not None \
|
||||
and users.auth_exists(user_id=jwt_payload.get("userId", -1),
|
||||
tenant_id=jwt_payload.get("tenantId", -1),
|
||||
|
|
@ -27,18 +27,13 @@ class JWTAuth(HTTPBearer):
|
|||
if jwt_payload is None \
|
||||
or jwt_payload.get("iat") is None or jwt_payload.get("aud") is None \
|
||||
or not auth_exists:
|
||||
print("JWTAuth: Token issue")
|
||||
if jwt_payload is not None:
|
||||
print(jwt_payload)
|
||||
print(f"JWTAuth: user_id={jwt_payload.get('userId')} tenant_id={jwt_payload.get('tenantId')}")
|
||||
if jwt_payload is None:
|
||||
print("JWTAuth: jwt_payload is None")
|
||||
print(credentials.scheme + " " + credentials.credentials)
|
||||
if jwt_payload is not None and jwt_payload.get("iat") is None:
|
||||
print("JWTAuth: iat is None")
|
||||
if jwt_payload is not None and jwt_payload.get("aud") is None:
|
||||
print("JWTAuth: aud is None")
|
||||
if jwt_payload is not None and not auth_exists:
|
||||
if jwt_payload.get("iat") is None:
|
||||
print("JWTAuth: iat is None")
|
||||
if jwt_payload.get("aud") is None:
|
||||
print("JWTAuth: aud is None")
|
||||
if not auth_exists:
|
||||
print("JWTAuth: not users.auth_exists")
|
||||
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Invalid token or expired token.")
|
||||
|
|
@ -47,12 +42,14 @@ class JWTAuth(HTTPBearer):
|
|||
print("JWTAuth: User not found.")
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="User not found.")
|
||||
jwt_payload["authorizer_identity"] = "jwt"
|
||||
print(jwt_payload)
|
||||
request.state.authorizer_identity = "jwt"
|
||||
request.state.currentContext = schemas_ee.CurrentContext(tenant_id=jwt_payload.get("tenantId", -1),
|
||||
user_id=jwt_payload.get("userId", -1),
|
||||
email=user["email"],
|
||||
permissions=user["permissions"])
|
||||
if user["serviceAccount"]:
|
||||
user["permissions"] = [p.value for p in schemas_ee.ServicePermissions]
|
||||
request.state.currentContext = schemas.CurrentContext(tenantId=jwt_payload.get("tenantId", -1),
|
||||
userId=jwt_payload.get("userId", -1),
|
||||
email=user["email"],
|
||||
permissions=user["permissions"],
|
||||
serviceAccount=user["serviceAccount"])
|
||||
return request.state.currentContext
|
||||
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import hashlib
|
|||
from decouple import config
|
||||
|
||||
import schemas
|
||||
import schemas_ee
|
||||
import schemas
|
||||
from chalicelib.utils import pg_client, helper
|
||||
from chalicelib.utils.TimeUTC import TimeUTC
|
||||
from chalicelib.utils.storage import StorageClient
|
||||
|
|
@ -14,16 +14,16 @@ def generate_file_key(project_id, key):
|
|||
return f"{project_id}/{hashlib.md5(key.encode()).hexdigest()}"
|
||||
|
||||
|
||||
def presign_record(project_id, data: schemas_ee.AssistRecordPayloadSchema, context: schemas_ee.CurrentContext):
|
||||
def presign_record(project_id, data: schemas.AssistRecordPayloadSchema, context: schemas.CurrentContext):
|
||||
key = generate_file_key(project_id=project_id, key=f"{TimeUTC.now()}-{data.name}")
|
||||
presigned_url = StorageClient.get_presigned_url_for_upload(bucket=config('ASSIST_RECORDS_BUCKET'), expires_in=1800,
|
||||
key=key)
|
||||
return {"URL": presigned_url, "key": key}
|
||||
|
||||
|
||||
def save_record(project_id, data: schemas_ee.AssistRecordSavePayloadSchema, context: schemas_ee.CurrentContext):
|
||||
def save_record(project_id, data: schemas.AssistRecordSavePayloadSchema, context: schemas.CurrentContext):
|
||||
extra.tag_record(file_key=data.key, tag_value=config('RETENTION_L_VALUE', default='vault'))
|
||||
params = {"user_id": context.user_id, "project_id": project_id, **data.dict()}
|
||||
params = {"user_id": context.user_id, "project_id": project_id, **data.model_dump()}
|
||||
with pg_client.PostgresClient() as cur:
|
||||
query = cur.mogrify(
|
||||
f"""INSERT INTO assist_records(project_id, user_id, name, file_key, duration, session_id)
|
||||
|
|
@ -40,8 +40,8 @@ def save_record(project_id, data: schemas_ee.AssistRecordSavePayloadSchema, cont
|
|||
return result
|
||||
|
||||
|
||||
def search_records(project_id: int, data: schemas_ee.AssistRecordSearchPayloadSchema,
|
||||
context: schemas_ee.CurrentContext):
|
||||
def search_records(project_id: int, data: schemas.AssistRecordSearchPayloadSchema,
|
||||
context: schemas.CurrentContext):
|
||||
conditions = ["projects.tenant_id=%(tenant_id)s",
|
||||
"projects.deleted_at ISNULL",
|
||||
"projects.project_id=%(project_id)s",
|
||||
|
|
@ -54,7 +54,7 @@ def search_records(project_id: int, data: schemas_ee.AssistRecordSearchPayloadSc
|
|||
params = {"tenant_id": context.tenant_id, "project_id": project_id,
|
||||
"startDate": data.startTimestamp, "endDate": data.endTimestamp,
|
||||
"p_start": (data.page - 1) * data.limit, "p_limit": data.limit,
|
||||
**data.dict()}
|
||||
**data.model_dump()}
|
||||
if data.user_id is not None:
|
||||
conditions.append("assist_records.user_id=%(user_id)s")
|
||||
if data.query is not None and len(data.query) > 0:
|
||||
|
|
@ -85,7 +85,7 @@ def search_records(project_id: int, data: schemas_ee.AssistRecordSearchPayloadSc
|
|||
return results
|
||||
|
||||
|
||||
def get_record(project_id, record_id, context: schemas_ee.CurrentContext):
|
||||
def get_record(project_id, record_id, context: schemas.CurrentContext):
|
||||
conditions = ["projects.tenant_id=%(tenant_id)s",
|
||||
"projects.deleted_at ISNULL",
|
||||
"assist_records.record_id=%(record_id)s",
|
||||
|
|
@ -110,8 +110,8 @@ def get_record(project_id, record_id, context: schemas_ee.CurrentContext):
|
|||
return result
|
||||
|
||||
|
||||
def update_record(project_id, record_id, data: schemas_ee.AssistRecordUpdatePayloadSchema,
|
||||
context: schemas_ee.CurrentContext):
|
||||
def update_record(project_id, record_id, data: schemas.AssistRecordUpdatePayloadSchema,
|
||||
context: schemas.CurrentContext):
|
||||
conditions = ["assist_records.record_id=%(record_id)s", "assist_records.deleted_at ISNULL"]
|
||||
params = {"tenant_id": context.tenant_id, "project_id": project_id, "record_id": record_id, "name": data.name}
|
||||
with pg_client.PostgresClient() as cur:
|
||||
|
|
@ -136,7 +136,7 @@ def update_record(project_id, record_id, data: schemas_ee.AssistRecordUpdatePayl
|
|||
return result
|
||||
|
||||
|
||||
def delete_record(project_id, record_id, context: schemas_ee.CurrentContext):
|
||||
def delete_record(project_id, record_id, context: schemas.CurrentContext):
|
||||
conditions = ["assist_records.record_id=%(record_id)s"]
|
||||
params = {"tenant_id": context.tenant_id, "project_id": project_id, "record_id": record_id}
|
||||
with pg_client.PostgresClient() as cur:
|
||||
|
|
|
|||
|
|
@ -7,13 +7,12 @@ from chalicelib.utils import helper
|
|||
from chalicelib.utils.TimeUTC import TimeUTC
|
||||
|
||||
|
||||
def jwt_authorizer(token):
|
||||
token = token.split(" ")
|
||||
if len(token) != 2 or token[0].lower() != "bearer":
|
||||
def jwt_authorizer(scheme: str, token: str):
|
||||
if scheme.lower() != "bearer":
|
||||
return None
|
||||
try:
|
||||
payload = jwt.decode(
|
||||
token[1],
|
||||
token,
|
||||
config("jwt_secret"),
|
||||
algorithms=config("jwt_algorithm"),
|
||||
audience=[f"front:{helper.get_stage_name()}"]
|
||||
|
|
@ -23,6 +22,7 @@ def jwt_authorizer(token):
|
|||
return None
|
||||
except BaseException as e:
|
||||
print("! JWT Base Exception")
|
||||
print(e)
|
||||
return None
|
||||
return payload
|
||||
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ from decouple import config
|
|||
from fastapi import HTTPException, status
|
||||
|
||||
import schemas
|
||||
import schemas_ee
|
||||
from chalicelib.core import funnels, issues, metrics, click_maps, sessions_insights, sessions_mobs, sessions_favorite
|
||||
from chalicelib.core import funnels, issues, metrics, click_maps, sessions_insights, sessions_mobs, sessions_favorite, \
|
||||
product_analytics
|
||||
from chalicelib.utils import helper, pg_client
|
||||
from chalicelib.utils.TimeUTC import TimeUTC
|
||||
from chalicelib.utils.storage import StorageClient, extra
|
||||
|
|
@ -25,25 +25,24 @@ else:
|
|||
PIE_CHART_GROUP = 5
|
||||
|
||||
|
||||
def __try_live(project_id, data: schemas_ee.CardSchema):
|
||||
# TODO: refactor this to split
|
||||
# timeseries /
|
||||
# table of errors / table of issues / table of browsers / table of devices / table of countries / table of URLs
|
||||
def __try_live(project_id, data: schemas.CardSchema):
|
||||
results = []
|
||||
for i, s in enumerate(data.series):
|
||||
s.filter.startDate = data.startTimestamp
|
||||
s.filter.endDate = data.endTimestamp
|
||||
results.append(sessions.search2_series(data=s.filter, project_id=project_id, density=data.density,
|
||||
view_type=data.view_type, metric_type=data.metric_type,
|
||||
metric_of=data.metric_of, metric_value=data.metric_value))
|
||||
if data.view_type == schemas.MetricTimeseriesViewType.progress:
|
||||
r = {"count": results[-1]}
|
||||
diff = s.filter.endDate - s.filter.startDate
|
||||
s.filter.endDate = s.filter.startDate
|
||||
s.filter.startDate = s.filter.endDate - diff
|
||||
diff = s.filter.endTimestamp - s.filter.startTimestamp
|
||||
s.filter.endTimestamp = s.filter.startTimestamp
|
||||
s.filter.startTimestamp = s.filter.endTimestamp - diff
|
||||
r["previousCount"] = sessions.search2_series(data=s.filter, project_id=project_id, density=data.density,
|
||||
view_type=data.view_type, metric_type=data.metric_type,
|
||||
metric_of=data.metric_of, metric_value=data.metric_value)
|
||||
r["countProgress"] = helper.__progress(old_val=r["previousCount"], new_val=r["count"])
|
||||
# r["countProgress"] = ((r["count"] - r["previousCount"]) / r["previousCount"]) * 100 \
|
||||
# if r["previousCount"] > 0 else 0
|
||||
r["seriesName"] = s.name if s.name else i + 1
|
||||
r["seriesId"] = s.series_id if s.series_id else None
|
||||
results[-1] = r
|
||||
|
|
@ -58,108 +57,97 @@ def __try_live(project_id, data: schemas_ee.CardSchema):
|
|||
return results
|
||||
|
||||
|
||||
def __is_funnel_chart(data: schemas_ee.CardSchema):
|
||||
def __is_funnel_chart(data: schemas.CardSchema):
|
||||
return data.metric_type == schemas.MetricType.funnel
|
||||
|
||||
|
||||
def __get_funnel_chart(project_id, data: schemas_ee.CardSchema):
|
||||
def __get_funnel_chart(project_id: int, data: schemas.CardFunnel, user_id: int = None):
|
||||
if len(data.series) == 0:
|
||||
return {
|
||||
"stages": [],
|
||||
"totalDropDueToIssues": 0
|
||||
}
|
||||
data.series[0].filter.startDate = data.startTimestamp
|
||||
data.series[0].filter.endDate = data.endTimestamp
|
||||
return funnels.get_top_insights_on_the_fly_widget(project_id=project_id, data=data.series[0].filter)
|
||||
|
||||
|
||||
def __is_errors_list(data: schemas_ee.CardSchema):
|
||||
def __is_errors_list(data: schemas.CardSchema):
|
||||
return data.metric_type == schemas.MetricType.table \
|
||||
and data.metric_of == schemas.MetricOfTable.errors
|
||||
|
||||
|
||||
def __get_errors_list(project_id, user_id, data: schemas_ee.CardSchema):
|
||||
def __get_errors_list(project_id, user_id, data: schemas.CardSchema):
|
||||
if len(data.series) == 0:
|
||||
return {
|
||||
"total": 0,
|
||||
"errors": []
|
||||
}
|
||||
data.series[0].filter.startDate = data.startTimestamp
|
||||
data.series[0].filter.endDate = data.endTimestamp
|
||||
data.series[0].filter.page = data.page
|
||||
data.series[0].filter.limit = data.limit
|
||||
return errors.search(data.series[0].filter, project_id=project_id, user_id=user_id)
|
||||
|
||||
|
||||
def __is_sessions_list(data: schemas_ee.CardSchema):
|
||||
def __is_sessions_list(data: schemas.CardSchema):
|
||||
return data.metric_type == schemas.MetricType.table \
|
||||
and data.metric_of == schemas.MetricOfTable.sessions
|
||||
|
||||
|
||||
def __get_sessions_list(project_id, user_id, data: schemas_ee.CardSchema):
|
||||
def __get_sessions_list(project_id, user_id, data: schemas.CardSchema):
|
||||
if len(data.series) == 0:
|
||||
print("empty series")
|
||||
return {
|
||||
"total": 0,
|
||||
"sessions": []
|
||||
}
|
||||
data.series[0].filter.startDate = data.startTimestamp
|
||||
data.series[0].filter.endDate = data.endTimestamp
|
||||
data.series[0].filter.page = data.page
|
||||
data.series[0].filter.limit = data.limit
|
||||
return sessions.search_sessions(data=data.series[0].filter, project_id=project_id, user_id=user_id)
|
||||
|
||||
|
||||
def __is_predefined(data: schemas_ee.CardSchema):
|
||||
def __is_predefined(data: schemas.CardSchema):
|
||||
return data.is_template
|
||||
|
||||
|
||||
def __is_click_map(data: schemas_ee.CardSchema):
|
||||
def __is_click_map(data: schemas.CardSchema):
|
||||
return data.metric_type == schemas.MetricType.click_map
|
||||
|
||||
|
||||
def __get_click_map_chart(project_id, user_id, data: schemas_ee.CardSchema, include_mobs: bool = True):
|
||||
def __get_click_map_chart(project_id, user_id, data: schemas.CardClickMap, include_mobs: bool = True):
|
||||
if len(data.series) == 0:
|
||||
return None
|
||||
data.series[0].filter.startDate = data.startTimestamp
|
||||
data.series[0].filter.endDate = data.endTimestamp
|
||||
return click_maps.search_short_session(project_id=project_id, user_id=user_id,
|
||||
data=schemas.FlatClickMapSessionsSearch(**data.series[0].filter.dict()),
|
||||
data=schemas.ClickMapSessionsSearch(
|
||||
**data.series[0].filter.model_dump()),
|
||||
include_mobs=include_mobs)
|
||||
|
||||
|
||||
# EE only
|
||||
def __is_insights(data: schemas_ee.CardSchema):
|
||||
def __is_insights(data: schemas.CardSchema):
|
||||
return data.metric_type == schemas.MetricType.insights
|
||||
|
||||
|
||||
# EE only
|
||||
def __get_insights_chart(project_id, user_id, data: schemas_ee.CardSchema):
|
||||
def __get_insights_chart(project_id: int, data: schemas.CardInsights, user_id: int = None):
|
||||
return sessions_insights.fetch_selected(project_id=project_id,
|
||||
data=schemas_ee.GetInsightsSchema(startTimestamp=data.startTimestamp,
|
||||
endTimestamp=data.endTimestamp,
|
||||
metricValue=data.metric_value,
|
||||
series=data.series))
|
||||
data=schemas.GetInsightsSchema(startTimestamp=data.startTimestamp,
|
||||
endTimestamp=data.endTimestamp,
|
||||
metricValue=data.metric_value,
|
||||
series=data.series))
|
||||
|
||||
|
||||
def merged_live(project_id, data: schemas_ee.CardSchema, user_id=None):
|
||||
if data.is_template:
|
||||
return get_predefined_metric(key=data.metric_of, project_id=project_id, data=data.dict())
|
||||
elif __is_funnel_chart(data):
|
||||
return __get_funnel_chart(project_id=project_id, data=data)
|
||||
elif __is_errors_list(data):
|
||||
return __get_errors_list(project_id=project_id, user_id=user_id, data=data)
|
||||
elif __is_sessions_list(data):
|
||||
return __get_sessions_list(project_id=project_id, user_id=user_id, data=data)
|
||||
elif __is_click_map(data):
|
||||
return __get_click_map_chart(project_id=project_id, user_id=user_id, data=data)
|
||||
# EE only
|
||||
elif __is_insights(data):
|
||||
return __get_insights_chart(project_id=project_id, user_id=user_id, data=data)
|
||||
elif len(data.series) == 0:
|
||||
return []
|
||||
def __get_path_analysis_chart(project_id: int, user_id: int, data: schemas.CardPathAnalysis):
|
||||
if len(data.series) == 0:
|
||||
data.series.append(
|
||||
schemas.CardPathAnalysisSchema(startTimestamp=data.startTimestamp, endTimestamp=data.endTimestamp))
|
||||
elif not isinstance(data.series[0].filter, schemas.PathAnalysisSchema):
|
||||
data.series[0].filter = schemas.PathAnalysisSchema()
|
||||
|
||||
return product_analytics.path_analysis(project_id=project_id, data=data.series[0].filter, density=data.density,
|
||||
selected_event_type=data.metric_value, hide_minor_paths=data.hide_excess)
|
||||
|
||||
|
||||
def __is_path_analysis(data: schemas.CardSchema):
|
||||
return data.metric_type == schemas.MetricType.pathAnalysis
|
||||
|
||||
|
||||
def __get_timeseries_chart(project_id: int, data: schemas.CardTimeSeries, user_id: int = None):
|
||||
series_charts = __try_live(project_id=project_id, data=data)
|
||||
if data.view_type == schemas.MetricTimeseriesViewType.progress or data.metric_type == schemas.MetricType.table:
|
||||
if data.view_type == schemas.MetricTimeseriesViewType.progress:
|
||||
return series_charts
|
||||
results = [{}] * len(series_charts[0])
|
||||
for i in range(len(results)):
|
||||
|
|
@ -169,29 +157,137 @@ def merged_live(project_id, data: schemas_ee.CardSchema, user_id=None):
|
|||
return results
|
||||
|
||||
|
||||
def __merge_metric_with_data(metric: schemas_ee.CardSchema,
|
||||
data: schemas.CardChartSchema) -> schemas_ee.CardSchema:
|
||||
def empty(**args):
|
||||
raise Exception("not supported")
|
||||
|
||||
|
||||
def __get_table_of_user_ids(project_id: int, data: schemas.CardTable, user_id: int = None):
|
||||
series_charts = __try_live(project_id=project_id, data=data)
|
||||
return series_charts
|
||||
|
||||
|
||||
def __get_table_of_sessions(project_id: int, data: schemas.CardTable, user_id):
|
||||
return __get_sessions_list(project_id=project_id, user_id=user_id, data=data)
|
||||
|
||||
|
||||
def __get_table_of_errors(project_id: int, data: schemas.CardTable, user_id: int):
|
||||
return __get_errors_list(project_id=project_id, user_id=user_id, data=data)
|
||||
|
||||
|
||||
def __get_table_of_issues(project_id: int, data: schemas.CardTable, user_id: int = None):
|
||||
return __try_live(project_id=project_id, data=data)
|
||||
|
||||
|
||||
def __get_table_of_browsers(project_id: int, data: schemas.CardTable, user_id: int = None):
|
||||
return __try_live(project_id=project_id, data=data)
|
||||
|
||||
|
||||
def __get_table_of_devises(project_id: int, data: schemas.CardTable, user_id: int = None):
|
||||
return __try_live(project_id=project_id, data=data)
|
||||
|
||||
|
||||
def __get_table_of_countries(project_id: int, data: schemas.CardTable, user_id: int = None):
|
||||
return __try_live(project_id=project_id, data=data)
|
||||
|
||||
|
||||
def __get_table_of_urls(project_id: int, data: schemas.CardTable, user_id: int = None):
|
||||
return __try_live(project_id=project_id, data=data)
|
||||
|
||||
|
||||
def __get_table_chart(project_id: int, data: schemas.CardTable, user_id: int):
|
||||
supported = {
|
||||
schemas.MetricOfTable.sessions: __get_table_of_sessions,
|
||||
schemas.MetricOfTable.errors: __get_table_of_errors,
|
||||
schemas.MetricOfTable.user_id: __get_table_of_user_ids,
|
||||
schemas.MetricOfTable.issues: __get_table_of_issues,
|
||||
schemas.MetricOfTable.user_browser: __get_table_of_browsers,
|
||||
schemas.MetricOfTable.user_device: __get_table_of_devises,
|
||||
schemas.MetricOfTable.user_country: __get_table_of_countries,
|
||||
schemas.MetricOfTable.visited_url: __get_table_of_urls,
|
||||
}
|
||||
return supported.get(data.metric_of, empty)(project_id=project_id, data=data, user_id=user_id)
|
||||
|
||||
|
||||
def get_chart(project_id: int, data: schemas.CardSchema, user_id: int):
|
||||
if data.is_template:
|
||||
return get_predefined_metric(key=data.metric_of, project_id=project_id, data=data.model_dump())
|
||||
|
||||
supported = {
|
||||
schemas.MetricType.timeseries: __get_timeseries_chart,
|
||||
schemas.MetricType.table: __get_table_chart,
|
||||
schemas.MetricType.click_map: __get_click_map_chart,
|
||||
schemas.MetricType.funnel: __get_funnel_chart,
|
||||
schemas.MetricType.insights: __get_insights_chart,
|
||||
schemas.MetricType.pathAnalysis: __get_path_analysis_chart
|
||||
}
|
||||
return supported.get(data.metric_type, empty)(project_id=project_id, data=data, user_id=user_id)
|
||||
|
||||
|
||||
def merged_live(project_id, data: schemas.CardSchema, user_id=None):
|
||||
return get_chart(project_id=project_id, data=data, user_id=user_id)
|
||||
print("---1")
|
||||
if data.is_template:
|
||||
print("---2")
|
||||
return get_predefined_metric(key=data.metric_of, project_id=project_id, data=data.model_dump())
|
||||
elif __is_funnel_chart(data):
|
||||
print("---3")
|
||||
return __get_funnel_chart(project_id=project_id, data=data)
|
||||
elif __is_errors_list(data):
|
||||
print("---4")
|
||||
return __get_errors_list(project_id=project_id, user_id=user_id, data=data)
|
||||
elif __is_sessions_list(data):
|
||||
print("---5")
|
||||
return __get_sessions_list(project_id=project_id, user_id=user_id, data=data)
|
||||
elif __is_click_map(data):
|
||||
print("---6")
|
||||
return __get_click_map_chart(project_id=project_id, user_id=user_id, data=data)
|
||||
# EE only
|
||||
elif __is_insights(data):
|
||||
return __get_insights_chart(project_id=project_id, user_id=user_id, data=data)
|
||||
elif __is_path_analysis(data):
|
||||
print("---7")
|
||||
return __get_path_analysis_chart(project_id=project_id, data=data)
|
||||
elif len(data.series) == 0:
|
||||
print("---8")
|
||||
return []
|
||||
series_charts = __try_live(project_id=project_id, data=data)
|
||||
print("---9")
|
||||
if data.view_type == schemas.MetricTimeseriesViewType.progress or data.metric_type == schemas.MetricType.table:
|
||||
print("---10")
|
||||
return series_charts
|
||||
results = [{}] * len(series_charts[0])
|
||||
print("---11")
|
||||
for i in range(len(results)):
|
||||
for j, series_chart in enumerate(series_charts):
|
||||
results[i] = {**results[i], "timestamp": series_chart[i]["timestamp"],
|
||||
data.series[j].name if data.series[j].name else j + 1: series_chart[i]["count"]}
|
||||
return results
|
||||
|
||||
|
||||
def __merge_metric_with_data(metric: schemas.CardSchema,
|
||||
data: schemas.CardSessionsSchema) -> schemas.CardSchema:
|
||||
if data.series is not None and len(data.series) > 0:
|
||||
metric.series = data.series
|
||||
metric: schemas_ee.CardSchema = schemas_ee.CardSchema(
|
||||
**{**data.dict(by_alias=True), **metric.dict(by_alias=True)})
|
||||
# TODO: try to refactor this
|
||||
metric: schemas.CardSchema = schemas.CardSchema(**{**data.model_dump(by_alias=True),
|
||||
**metric.model_dump(by_alias=True)})
|
||||
if len(data.filters) > 0 or len(data.events) > 0:
|
||||
for s in metric.series:
|
||||
if len(data.filters) > 0:
|
||||
s.filter.filters += data.filters
|
||||
if len(data.events) > 0:
|
||||
s.filter.events += data.events
|
||||
metric.limit = data.limit
|
||||
metric.page = data.page
|
||||
metric.startTimestamp = data.startTimestamp
|
||||
metric.endTimestamp = data.endTimestamp
|
||||
# metric.limit = data.limit
|
||||
# metric.page = data.page
|
||||
# metric.startTimestamp = data.startTimestamp
|
||||
# metric.endTimestamp = data.endTimestamp
|
||||
return metric
|
||||
|
||||
|
||||
def make_chart(project_id, user_id, data: schemas.CardChartSchema, metric: schemas_ee.CardSchema):
|
||||
def make_chart(project_id, user_id, data: schemas.CardSessionsSchema, metric: schemas.CardSchema):
|
||||
if metric is None:
|
||||
return None
|
||||
metric: schemas_ee.CardSchema = __merge_metric_with_data(metric=metric, data=data)
|
||||
metric: schemas.CardSchema = __merge_metric_with_data(metric=metric, data=data)
|
||||
|
||||
return merged_live(project_id=project_id, data=metric, user_id=user_id)
|
||||
|
||||
|
|
@ -201,8 +297,8 @@ def get_sessions(project_id, user_id, metric_id, data: schemas.CardSessionsSchem
|
|||
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_ee.CardSchema = schemas_ee.CardSchema(**raw_metric)
|
||||
metric: schemas_ee.CardSchema = __merge_metric_with_data(metric=metric, data=data)
|
||||
metric: schemas.CardSchema = schemas.CardSchema(**raw_metric)
|
||||
metric: schemas.CardSchema = __merge_metric_with_data(metric=metric, data=data)
|
||||
if metric is None:
|
||||
return None
|
||||
results = []
|
||||
|
|
@ -210,10 +306,10 @@ def get_sessions(project_id, user_id, metric_id, data: schemas.CardSessionsSchem
|
|||
# if __is_click_map(metric) and raw_metric.get("data") is not None:
|
||||
# is_click_map = True
|
||||
for s in metric.series:
|
||||
s.filter.startDate = data.startTimestamp
|
||||
s.filter.endDate = data.endTimestamp
|
||||
s.filter.limit = data.limit
|
||||
s.filter.page = data.page
|
||||
# s.filter.startTimestamp = data.startTimestamp
|
||||
# s.filter.endTimestamp = data.endTimestamp
|
||||
# s.filter.limit = data.limit
|
||||
# s.filter.page = data.page
|
||||
# if is_click_map:
|
||||
# results.append(
|
||||
# {"seriesId": s.series_id, "seriesName": s.name, "total": 1, "sessions": [raw_metric["data"]]})
|
||||
|
|
@ -228,15 +324,11 @@ def get_funnel_issues(project_id, user_id, metric_id, data: schemas.CardSessions
|
|||
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_ee.CardSchema = schemas_ee.CardSchema(**raw_metric)
|
||||
metric: schemas_ee.CardSchema = __merge_metric_with_data(metric=metric, data=data)
|
||||
metric: schemas.CardSchema = schemas.CardSchema(**raw_metric)
|
||||
metric: schemas.CardSchema = __merge_metric_with_data(metric=metric, data=data)
|
||||
if metric is None:
|
||||
return None
|
||||
for s in metric.series:
|
||||
s.filter.startDate = data.startTimestamp
|
||||
s.filter.endDate = data.endTimestamp
|
||||
s.filter.limit = data.limit
|
||||
s.filter.page = data.page
|
||||
return {"seriesId": s.series_id, "seriesName": s.name,
|
||||
**funnels.get_issues_on_the_fly_widget(project_id=project_id, data=s.filter)}
|
||||
|
||||
|
|
@ -245,28 +337,20 @@ def get_errors_list(project_id, user_id, metric_id, data: schemas.CardSessionsSc
|
|||
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_ee.CardSchema = schemas_ee.CardSchema(**raw_metric)
|
||||
metric: schemas_ee.CardSchema = __merge_metric_with_data(metric=metric, data=data)
|
||||
metric: schemas.CardSchema = schemas.CardSchema(**raw_metric)
|
||||
metric: schemas.CardSchema = __merge_metric_with_data(metric=metric, data=data)
|
||||
if metric is None:
|
||||
return None
|
||||
for s in metric.series:
|
||||
s.filter.startDate = data.startTimestamp
|
||||
s.filter.endDate = data.endTimestamp
|
||||
s.filter.limit = data.limit
|
||||
s.filter.page = data.page
|
||||
return {"seriesId": s.series_id, "seriesName": s.name,
|
||||
**errors.search(data=s.filter, project_id=project_id, user_id=user_id)}
|
||||
|
||||
|
||||
def try_sessions(project_id, user_id, data: schemas.CardSessionsSchema):
|
||||
results = []
|
||||
if data.series is None:
|
||||
if len(data.series) == 0:
|
||||
return results
|
||||
for s in data.series:
|
||||
s.filter.startDate = data.startTimestamp
|
||||
s.filter.endDate = data.endTimestamp
|
||||
s.filter.limit = data.limit
|
||||
s.filter.page = data.page
|
||||
if len(data.filters) > 0:
|
||||
s.filter.filters += data.filters
|
||||
if len(data.events) > 0:
|
||||
|
|
@ -277,7 +361,7 @@ def try_sessions(project_id, user_id, data: schemas.CardSessionsSchema):
|
|||
return results
|
||||
|
||||
|
||||
def create(project_id, user_id, data: schemas_ee.CardSchema, dashboard=False):
|
||||
def create_card(project_id, user_id, data: schemas.CardSchema, dashboard=False):
|
||||
with pg_client.PostgresClient() as cur:
|
||||
session_data = None
|
||||
if __is_click_map(data):
|
||||
|
|
@ -299,13 +383,13 @@ def create(project_id, user_id, data: schemas_ee.CardSchema, dashboard=False):
|
|||
session_data = json.dumps(session_data)
|
||||
_data = {"session_data": session_data}
|
||||
for i, s in enumerate(data.series):
|
||||
for k in s.dict().keys():
|
||||
for k in s.model_dump().keys():
|
||||
_data[f"{k}_{i}"] = s.__getattribute__(k)
|
||||
_data[f"index_{i}"] = i
|
||||
_data[f"filter_{i}"] = s.filter.json()
|
||||
series_len = len(data.series)
|
||||
params = {"user_id": user_id, "project_id": project_id, **data.dict(), **_data}
|
||||
params["default_config"] = json.dumps(data.default_config.dict())
|
||||
params = {"user_id": user_id, "project_id": project_id, **data.model_dump(), **_data}
|
||||
params["default_config"] = json.dumps(data.default_config.model_dump())
|
||||
query = """INSERT INTO metrics (project_id, user_id, name, is_public,
|
||||
view_type, metric_type, metric_of, metric_value,
|
||||
metric_format, default_config, thumbnail, data)
|
||||
|
|
@ -331,7 +415,7 @@ def create(project_id, user_id, data: schemas_ee.CardSchema, dashboard=False):
|
|||
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_ee.UpdateCardSchema):
|
||||
def update_card(metric_id, user_id, project_id, data: schemas.CardSchema):
|
||||
metric: dict = get_card(metric_id=metric_id, project_id=project_id, user_id=user_id, flatten=False)
|
||||
if metric is None:
|
||||
return None
|
||||
|
|
@ -344,7 +428,7 @@ def update(metric_id, user_id, project_id, data: schemas_ee.UpdateCardSchema):
|
|||
"user_id": user_id, "project_id": project_id, "view_type": data.view_type,
|
||||
"metric_type": data.metric_type, "metric_of": data.metric_of,
|
||||
"metric_value": data.metric_value, "metric_format": data.metric_format,
|
||||
"config": json.dumps(data.default_config.dict()), "thumbnail": data.thumbnail}
|
||||
"config": json.dumps(data.default_config.model_dump()), "thumbnail": data.thumbnail}
|
||||
for i, s in enumerate(data.series):
|
||||
prefix = "u_"
|
||||
if s.index is None:
|
||||
|
|
@ -355,7 +439,7 @@ def update(metric_id, user_id, project_id, data: schemas_ee.UpdateCardSchema):
|
|||
else:
|
||||
u_series.append({"i": i, "s": s})
|
||||
u_series_ids.append(s.series_id)
|
||||
ns = s.dict()
|
||||
ns = s.model_dump()
|
||||
for k in ns.keys():
|
||||
if k == "filter":
|
||||
ns[k] = json.dumps(ns[k])
|
||||
|
|
@ -477,7 +561,7 @@ def get_all(project_id, user_id):
|
|||
return result
|
||||
|
||||
|
||||
def delete(project_id, metric_id, user_id):
|
||||
def delete_card(project_id, metric_id, user_id):
|
||||
with pg_client.PostgresClient() as cur:
|
||||
cur.execute(
|
||||
cur.mogrify("""\
|
||||
|
|
@ -485,8 +569,7 @@ def delete(project_id, metric_id, user_id):
|
|||
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
|
||||
AND (user_id = %(user_id)s OR is_public)
|
||||
RETURNING data;""",
|
||||
AND (user_id = %(user_id)s OR is_public);""",
|
||||
{"metric_id": metric_id, "project_id": project_id, "user_id": user_id})
|
||||
)
|
||||
# for EE only
|
||||
|
|
@ -596,13 +679,13 @@ def get_funnel_sessions_by_issue(user_id, project_id, metric_id, issue_id,
|
|||
metric: dict = get_card(metric_id=metric_id, project_id=project_id, user_id=user_id, flatten=False)
|
||||
if metric is None:
|
||||
return None
|
||||
metric: schemas_ee.CardSchema = schemas.CardSchema(**metric)
|
||||
metric: schemas_ee.CardSchema = __merge_metric_with_data(metric=metric, data=data)
|
||||
metric: schemas.CardSchema = schemas.CardSchema(**metric)
|
||||
metric: schemas.CardSchema = __merge_metric_with_data(metric=metric, data=data)
|
||||
if metric is None:
|
||||
return None
|
||||
for s in metric.series:
|
||||
s.filter.startDate = data.startTimestamp
|
||||
s.filter.endDate = data.endTimestamp
|
||||
s.filter.startTimestamp = data.startTimestamp
|
||||
s.filter.endTimestamp = data.endTimestamp
|
||||
s.filter.limit = data.limit
|
||||
s.filter.page = data.page
|
||||
issues_list = funnels.get_issues_on_the_fly_widget(project_id=project_id, data=s.filter).get("issues", {})
|
||||
|
|
@ -628,13 +711,15 @@ def get_funnel_sessions_by_issue(user_id, project_id, metric_id, issue_id,
|
|||
"issue": issue}
|
||||
|
||||
|
||||
def make_chart_from_card(project_id, user_id, metric_id, data: schemas.CardChartSchema):
|
||||
def make_chart_from_card(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, include_data=True)
|
||||
if raw_metric is None:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="card not found")
|
||||
metric: schemas_ee.CardSchema = schemas_ee.CardSchema(**raw_metric)
|
||||
raw_metric["startTimestamp"] = data.startTimestamp
|
||||
raw_metric["endTimestamp"] = data.endTimestamp
|
||||
metric: schemas.CardSchema = schemas.CardSchema(**raw_metric)
|
||||
if metric.is_template:
|
||||
return get_predefined_metric(key=metric.metric_of, project_id=project_id, data=data.dict())
|
||||
return get_predefined_metric(key=metric.metric_of, project_id=project_id, data=data.model_dump())
|
||||
elif __is_click_map(metric):
|
||||
if raw_metric["data"]:
|
||||
keys = sessions_mobs. \
|
||||
|
|
@ -654,53 +739,52 @@ def make_chart_from_card(project_id, user_id, metric_id, data: schemas.CardChart
|
|||
return make_chart(project_id=project_id, user_id=user_id, data=data, metric=metric)
|
||||
|
||||
|
||||
PREDEFINED = {schemas.MetricOfWebVitals.count_sessions: metrics.get_processed_sessions,
|
||||
schemas.MetricOfWebVitals.avg_image_load_time: metrics.get_application_activity_avg_image_load_time,
|
||||
schemas.MetricOfWebVitals.avg_page_load_time: metrics.get_application_activity_avg_page_load_time,
|
||||
schemas.MetricOfWebVitals.avg_request_load_time: metrics.get_application_activity_avg_request_load_time,
|
||||
schemas.MetricOfWebVitals.avg_dom_content_load_start: metrics.get_page_metrics_avg_dom_content_load_start,
|
||||
schemas.MetricOfWebVitals.avg_first_contentful_pixel: metrics.get_page_metrics_avg_first_contentful_pixel,
|
||||
schemas.MetricOfWebVitals.avg_visited_pages: metrics.get_user_activity_avg_visited_pages,
|
||||
schemas.MetricOfWebVitals.avg_session_duration: metrics.get_user_activity_avg_session_duration,
|
||||
schemas.MetricOfWebVitals.avg_pages_dom_buildtime: metrics.get_pages_dom_build_time,
|
||||
schemas.MetricOfWebVitals.avg_pages_response_time: metrics.get_pages_response_time,
|
||||
schemas.MetricOfWebVitals.avg_response_time: metrics.get_top_metrics_avg_response_time,
|
||||
schemas.MetricOfWebVitals.avg_first_paint: metrics.get_top_metrics_avg_first_paint,
|
||||
schemas.MetricOfWebVitals.avg_dom_content_loaded: metrics.get_top_metrics_avg_dom_content_loaded,
|
||||
schemas.MetricOfWebVitals.avg_till_first_byte: metrics.get_top_metrics_avg_till_first_bit,
|
||||
schemas.MetricOfWebVitals.avg_time_to_interactive: metrics.get_top_metrics_avg_time_to_interactive,
|
||||
schemas.MetricOfWebVitals.count_requests: metrics.get_top_metrics_count_requests,
|
||||
schemas.MetricOfWebVitals.avg_time_to_render: metrics.get_time_to_render,
|
||||
schemas.MetricOfWebVitals.avg_used_js_heap_size: metrics.get_memory_consumption,
|
||||
schemas.MetricOfWebVitals.avg_cpu: metrics.get_avg_cpu,
|
||||
schemas.MetricOfWebVitals.avg_fps: metrics.get_avg_fps,
|
||||
schemas.MetricOfErrors.impacted_sessions_by_js_errors: metrics.get_impacted_sessions_by_js_errors,
|
||||
schemas.MetricOfErrors.domains_errors_4xx: metrics.get_domains_errors_4xx,
|
||||
schemas.MetricOfErrors.domains_errors_5xx: metrics.get_domains_errors_5xx,
|
||||
schemas.MetricOfErrors.errors_per_domains: metrics.get_errors_per_domains,
|
||||
schemas.MetricOfErrors.calls_errors: metrics.get_calls_errors,
|
||||
schemas.MetricOfErrors.errors_per_type: metrics.get_errors_per_type,
|
||||
schemas.MetricOfErrors.resources_by_party: metrics.get_resources_by_party,
|
||||
schemas.MetricOfPerformance.speed_location: metrics.get_speed_index_location,
|
||||
schemas.MetricOfPerformance.slowest_domains: metrics.get_slowest_domains,
|
||||
schemas.MetricOfPerformance.sessions_per_browser: metrics.get_sessions_per_browser,
|
||||
schemas.MetricOfPerformance.time_to_render: metrics.get_time_to_render,
|
||||
schemas.MetricOfPerformance.impacted_sessions_by_slow_pages: metrics.get_impacted_sessions_by_slow_pages,
|
||||
schemas.MetricOfPerformance.memory_consumption: metrics.get_memory_consumption,
|
||||
schemas.MetricOfPerformance.cpu: metrics.get_avg_cpu,
|
||||
schemas.MetricOfPerformance.fps: metrics.get_avg_fps,
|
||||
schemas.MetricOfPerformance.crashes: metrics.get_crashes,
|
||||
schemas.MetricOfPerformance.resources_vs_visually_complete: metrics.get_resources_vs_visually_complete,
|
||||
schemas.MetricOfPerformance.pages_dom_buildtime: metrics.get_pages_dom_build_time,
|
||||
schemas.MetricOfPerformance.pages_response_time: metrics.get_pages_response_time,
|
||||
schemas.MetricOfPerformance.pages_response_time_distribution: metrics.get_pages_response_time_distribution,
|
||||
schemas.MetricOfResources.missing_resources: metrics.get_missing_resources_trend,
|
||||
schemas.MetricOfResources.slowest_resources: metrics.get_slowest_resources,
|
||||
schemas.MetricOfResources.resources_loading_time: metrics.get_resources_loading_time,
|
||||
schemas.MetricOfResources.resource_type_vs_response_end: metrics.resource_type_vs_response_end,
|
||||
schemas.MetricOfResources.resources_count_by_type: metrics.get_resources_count_by_type, }
|
||||
|
||||
|
||||
def get_predefined_metric(key: Union[schemas.MetricOfWebVitals, schemas.MetricOfErrors, \
|
||||
schemas.MetricOfPerformance, schemas.MetricOfResources], project_id: int, data: dict):
|
||||
return PREDEFINED.get(key, lambda *args: None)(project_id=project_id, **data)
|
||||
supported = {schemas.MetricOfWebVitals.count_sessions: metrics.get_processed_sessions,
|
||||
schemas.MetricOfWebVitals.avg_image_load_time: metrics.get_application_activity_avg_image_load_time,
|
||||
schemas.MetricOfWebVitals.avg_page_load_time: metrics.get_application_activity_avg_page_load_time,
|
||||
schemas.MetricOfWebVitals.avg_request_load_time: metrics.get_application_activity_avg_request_load_time,
|
||||
schemas.MetricOfWebVitals.avg_dom_content_load_start: metrics.get_page_metrics_avg_dom_content_load_start,
|
||||
schemas.MetricOfWebVitals.avg_first_contentful_pixel: metrics.get_page_metrics_avg_first_contentful_pixel,
|
||||
schemas.MetricOfWebVitals.avg_visited_pages: metrics.get_user_activity_avg_visited_pages,
|
||||
schemas.MetricOfWebVitals.avg_session_duration: metrics.get_user_activity_avg_session_duration,
|
||||
schemas.MetricOfWebVitals.avg_pages_dom_buildtime: metrics.get_pages_dom_build_time,
|
||||
schemas.MetricOfWebVitals.avg_pages_response_time: metrics.get_pages_response_time,
|
||||
schemas.MetricOfWebVitals.avg_response_time: metrics.get_top_metrics_avg_response_time,
|
||||
schemas.MetricOfWebVitals.avg_first_paint: metrics.get_top_metrics_avg_first_paint,
|
||||
schemas.MetricOfWebVitals.avg_dom_content_loaded: metrics.get_top_metrics_avg_dom_content_loaded,
|
||||
schemas.MetricOfWebVitals.avg_till_first_byte: metrics.get_top_metrics_avg_till_first_bit,
|
||||
schemas.MetricOfWebVitals.avg_time_to_interactive: metrics.get_top_metrics_avg_time_to_interactive,
|
||||
schemas.MetricOfWebVitals.count_requests: metrics.get_top_metrics_count_requests,
|
||||
schemas.MetricOfWebVitals.avg_time_to_render: metrics.get_time_to_render,
|
||||
schemas.MetricOfWebVitals.avg_used_js_heap_size: metrics.get_memory_consumption,
|
||||
schemas.MetricOfWebVitals.avg_cpu: metrics.get_avg_cpu,
|
||||
schemas.MetricOfWebVitals.avg_fps: metrics.get_avg_fps,
|
||||
schemas.MetricOfErrors.impacted_sessions_by_js_errors: metrics.get_impacted_sessions_by_js_errors,
|
||||
schemas.MetricOfErrors.domains_errors_4xx: metrics.get_domains_errors_4xx,
|
||||
schemas.MetricOfErrors.domains_errors_5xx: metrics.get_domains_errors_5xx,
|
||||
schemas.MetricOfErrors.errors_per_domains: metrics.get_errors_per_domains,
|
||||
schemas.MetricOfErrors.calls_errors: metrics.get_calls_errors,
|
||||
schemas.MetricOfErrors.errors_per_type: metrics.get_errors_per_type,
|
||||
schemas.MetricOfErrors.resources_by_party: metrics.get_resources_by_party,
|
||||
schemas.MetricOfPerformance.speed_location: metrics.get_speed_index_location,
|
||||
schemas.MetricOfPerformance.slowest_domains: metrics.get_slowest_domains,
|
||||
schemas.MetricOfPerformance.sessions_per_browser: metrics.get_sessions_per_browser,
|
||||
schemas.MetricOfPerformance.time_to_render: metrics.get_time_to_render,
|
||||
schemas.MetricOfPerformance.impacted_sessions_by_slow_pages: metrics.get_impacted_sessions_by_slow_pages,
|
||||
schemas.MetricOfPerformance.memory_consumption: metrics.get_memory_consumption,
|
||||
schemas.MetricOfPerformance.cpu: metrics.get_avg_cpu,
|
||||
schemas.MetricOfPerformance.fps: metrics.get_avg_fps,
|
||||
schemas.MetricOfPerformance.crashes: metrics.get_crashes,
|
||||
schemas.MetricOfPerformance.resources_vs_visually_complete: metrics.get_resources_vs_visually_complete,
|
||||
schemas.MetricOfPerformance.pages_dom_buildtime: metrics.get_pages_dom_build_time,
|
||||
schemas.MetricOfPerformance.pages_response_time: metrics.get_pages_response_time,
|
||||
schemas.MetricOfPerformance.pages_response_time_distribution: metrics.get_pages_response_time_distribution,
|
||||
schemas.MetricOfResources.missing_resources: metrics.get_missing_resources_trend,
|
||||
schemas.MetricOfResources.slowest_resources: metrics.get_slowest_resources,
|
||||
schemas.MetricOfResources.resources_loading_time: metrics.get_resources_loading_time,
|
||||
schemas.MetricOfResources.resource_type_vs_response_end: metrics.resource_type_vs_response_end,
|
||||
schemas.MetricOfResources.resources_count_by_type: metrics.get_resources_count_by_type, }
|
||||
|
||||
return supported.get(key, lambda *args: None)(project_id=project_id, **data)
|
||||
|
|
|
|||
|
|
@ -461,10 +461,10 @@ def search(data: schemas.SearchErrorsSchema, project_id, user_id):
|
|||
pg_sub_query_chart.append("errors.error_id =details.error_id")
|
||||
statuses = []
|
||||
error_ids = None
|
||||
if data.startDate is None:
|
||||
data.startDate = TimeUTC.now(-30)
|
||||
if data.endDate is None:
|
||||
data.endDate = TimeUTC.now(1)
|
||||
if data.startTimestamp is None:
|
||||
data.startTimestamp = TimeUTC.now(-30)
|
||||
if data.endTimestamp is None:
|
||||
data.endTimestamp = TimeUTC.now(1)
|
||||
if len(data.events) > 0 or len(data.filters) > 0:
|
||||
print("-- searching for sessions before errors")
|
||||
statuses = sessions.search_sessions(data=data, project_id=project_id, user_id=user_id, errors_only=True,
|
||||
|
|
@ -473,18 +473,18 @@ def search(data: schemas.SearchErrorsSchema, project_id, user_id):
|
|||
return empty_response
|
||||
error_ids = [e["errorId"] for e in statuses]
|
||||
with pg_client.PostgresClient() as cur:
|
||||
step_size = __get_step_size(data.startDate, data.endDate, data.density, factor=1)
|
||||
step_size = __get_step_size(data.startTimestamp, data.endTimestamp, data.density, factor=1)
|
||||
sort = __get_sort_key('datetime')
|
||||
if data.sort is not None:
|
||||
sort = __get_sort_key(data.sort)
|
||||
order = schemas.SortOrderType.desc.value
|
||||
order = schemas.SortOrderType.desc
|
||||
if data.order is not None:
|
||||
order = data.order.value
|
||||
order = data.order
|
||||
extra_join = ""
|
||||
|
||||
params = {
|
||||
"startDate": data.startDate,
|
||||
"endDate": data.endDate,
|
||||
"startDate": data.startTimestamp,
|
||||
"endDate": data.endTimestamp,
|
||||
"project_id": project_id,
|
||||
"userId": user_id,
|
||||
"step_size": step_size}
|
||||
|
|
@ -716,41 +716,3 @@ def change_state(project_id, user_id, error_id, action):
|
|||
for e in errors:
|
||||
e["status"] = row["status"]
|
||||
return {"data": errors}
|
||||
|
||||
|
||||
MAX_RANK = 2
|
||||
|
||||
|
||||
def __status_rank(status):
|
||||
return {
|
||||
'unresolved': MAX_RANK - 2,
|
||||
'ignored': MAX_RANK - 1,
|
||||
'resolved': MAX_RANK
|
||||
}.get(status)
|
||||
|
||||
|
||||
def stats(project_id, user_id, startTimestamp=TimeUTC.now(delta_days=-7), endTimestamp=TimeUTC.now()):
|
||||
with pg_client.PostgresClient() as cur:
|
||||
query = cur.mogrify(
|
||||
"""WITH user_viewed AS (SELECT error_id FROM public.user_viewed_errors WHERE user_id = %(user_id)s)
|
||||
SELECT COUNT(timed_errors.*) AS unresolved_and_unviewed
|
||||
FROM (SELECT root_error.error_id
|
||||
FROM events.errors
|
||||
INNER JOIN public.errors AS root_error USING (error_id)
|
||||
LEFT JOIN user_viewed USING (error_id)
|
||||
WHERE project_id = %(project_id)s
|
||||
AND timestamp >= %(startTimestamp)s
|
||||
AND timestamp <= %(endTimestamp)s
|
||||
AND source = 'js_exception'
|
||||
AND root_error.status = 'unresolved'
|
||||
AND user_viewed.error_id ISNULL
|
||||
LIMIT 1
|
||||
) AS timed_errors;""",
|
||||
{"project_id": project_id, "user_id": user_id, "startTimestamp": startTimestamp,
|
||||
"endTimestamp": endTimestamp})
|
||||
cur.execute(query=query)
|
||||
row = cur.fetchone()
|
||||
|
||||
return {
|
||||
"data": helper.dict_to_camel_case(row)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ def get_all_count(tenant_id, user_id):
|
|||
|
||||
|
||||
def view_notification(user_id, notification_ids=[], tenant_id=None, startTimestamp=None, endTimestamp=None):
|
||||
if (notification_ids is None or len(notification_ids) == 0) and endTimestamp is None:
|
||||
if len(notification_ids) == 0 and endTimestamp is None:
|
||||
return False
|
||||
if startTimestamp is None:
|
||||
startTimestamp = 0
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
from fastapi.security import SecurityScopes
|
||||
|
||||
import schemas_ee
|
||||
import schemas
|
||||
|
||||
|
||||
def check(security_scopes: SecurityScopes, context: schemas_ee.CurrentContext):
|
||||
def check(security_scopes: SecurityScopes, context: schemas.CurrentContext):
|
||||
for scope in security_scopes.scopes:
|
||||
if scope not in context.permissions:
|
||||
return False
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -42,12 +42,12 @@ def __update(tenant_id, project_id, changes):
|
|||
return helper.dict_to_camel_case(cur.fetchone())
|
||||
|
||||
|
||||
def __create(tenant_id, name):
|
||||
def __create(tenant_id, data):
|
||||
with pg_client.PostgresClient() as cur:
|
||||
query = cur.mogrify(f"""INSERT INTO public.projects (tenant_id, name, active)
|
||||
VALUES (%(tenant_id)s,%(name)s,TRUE)
|
||||
query = cur.mogrify(f"""INSERT INTO public.projects (tenant_id, name, platform, active)
|
||||
VALUES (%(tenant_id)s,%(name)s,%(platform)s,TRUE)
|
||||
RETURNING project_id;""",
|
||||
{"tenant_id": tenant_id, "name": name})
|
||||
{"tenant_id": tenant_id, **data})
|
||||
cur.execute(query=query)
|
||||
project_id = cur.fetchone()["project_id"]
|
||||
return get_project(tenant_id=tenant_id, project_id=project_id, include_gdpr=True)
|
||||
|
|
@ -79,14 +79,15 @@ def get_projects(tenant_id: int, gdpr: bool = False, recorded: bool = False, use
|
|||
|
||||
query = cur.mogrify(f"""{"SELECT *, first_recorded IS NOT NULL AS recorded FROM (" if recorded else ""}
|
||||
SELECT s.project_id, s.name, s.project_key, s.save_request_payloads, s.first_recorded_session_at,
|
||||
created_at, sessions_last_check_at, sample_rate {extra_projection}
|
||||
s.created_at, s.sessions_last_check_at, s.sample_rate, s.platform
|
||||
{extra_projection}
|
||||
FROM public.projects AS s
|
||||
{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.name {") AS raw" if recorded else ""};""",
|
||||
{"tenant_id": tenant_id, "user_id": user_id, "now": TimeUTC.now(),
|
||||
"check_delta": TimeUTC.MS_HOUR * 4})
|
||||
{"now": TimeUTC.now(), "check_delta": TimeUTC.MS_HOUR * 4,
|
||||
"tenant_id": tenant_id, "user_id": user_id})
|
||||
cur.execute(query)
|
||||
rows = cur.fetchall()
|
||||
# if recorded is requested, check if it was saved or computed
|
||||
|
|
@ -145,6 +146,29 @@ def get_project(tenant_id, project_id, include_last_session=False, include_gdpr=
|
|||
return helper.dict_to_camel_case(row)
|
||||
|
||||
|
||||
def get_project_by_key(tenant_id, project_key, include_last_session=False, include_gdpr=None):
|
||||
with pg_client.PostgresClient() as cur:
|
||||
extra_select = ""
|
||||
if include_last_session:
|
||||
extra_select += """,(SELECT max(ss.start_ts)
|
||||
FROM public.sessions AS ss
|
||||
WHERE ss.project_key = %(project_key)s) AS last_recorded_session_at"""
|
||||
if include_gdpr:
|
||||
extra_select += ",s.gdpr"
|
||||
query = cur.mogrify(f"""SELECT s.project_key,
|
||||
s.name
|
||||
{extra_select}
|
||||
FROM public.projects AS s
|
||||
WHERE s.project_key =%(project_key)s
|
||||
AND s.tenant_id =%(tenant_id)s
|
||||
AND s.deleted_at IS NULL
|
||||
LIMIT 1;""",
|
||||
{"project_key": project_key, "tenant_id": tenant_id})
|
||||
cur.execute(query=query)
|
||||
row = cur.fetchone()
|
||||
return helper.dict_to_camel_case(row)
|
||||
|
||||
|
||||
def create(tenant_id, user_id, data: schemas.CreateProjectSchema, skip_authorization=False):
|
||||
if __exists_by_name(name=data.name, exclude_id=None, tenant_id=tenant_id):
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=f"name already exists.")
|
||||
|
|
@ -154,7 +178,7 @@ def create(tenant_id, user_id, data: schemas.CreateProjectSchema, skip_authoriza
|
|||
return {"errors": ["unauthorized"]}
|
||||
if admin["roleId"] is not None and not admin["allProjects"]:
|
||||
return {"errors": ["unauthorized: you need allProjects permission to create a new project"]}
|
||||
return {"data": __create(tenant_id=tenant_id, name=data.name)}
|
||||
return {"data": __create(tenant_id=tenant_id, data=data.model_dump())}
|
||||
|
||||
|
||||
def edit(tenant_id, user_id, project_id, data: schemas.CreateProjectSchema):
|
||||
|
|
@ -164,7 +188,7 @@ def edit(tenant_id, user_id, project_id, data: schemas.CreateProjectSchema):
|
|||
if not admin["admin"] and not admin["superAdmin"]:
|
||||
return {"errors": ["unauthorized"]}
|
||||
return {"data": __update(tenant_id=tenant_id, project_id=project_id,
|
||||
changes={"name": data.name})}
|
||||
changes=data.model_dump())}
|
||||
|
||||
|
||||
def delete(tenant_id, user_id, project_id):
|
||||
|
|
@ -195,14 +219,14 @@ def get_gdpr(project_id):
|
|||
return row
|
||||
|
||||
|
||||
def edit_gdpr(project_id, gdpr):
|
||||
def edit_gdpr(project_id, gdpr: schemas.GdprSchema):
|
||||
with pg_client.PostgresClient() as cur:
|
||||
query = cur.mogrify("""UPDATE public.projects
|
||||
SET gdpr = gdpr|| %(gdpr)s
|
||||
WHERE project_id = %(project_id)s
|
||||
AND deleted_at ISNULL
|
||||
RETURNING gdpr;""",
|
||||
{"project_id": project_id, "gdpr": json.dumps(gdpr)})
|
||||
{"project_id": project_id, "gdpr": json.dumps(gdpr.model_dump_json())})
|
||||
cur.execute(query=query)
|
||||
row = cur.fetchone()
|
||||
if not row:
|
||||
|
|
@ -216,7 +240,7 @@ def get_internal_project_id(project_key):
|
|||
with pg_client.PostgresClient() as cur:
|
||||
query = cur.mogrify("""SELECT project_id
|
||||
FROM public.projects
|
||||
WHERE project_key =%(project_key)s
|
||||
WHERE project_key =%(project_key)s
|
||||
AND deleted_at ISNULL;""",
|
||||
{"project_key": project_key})
|
||||
cur.execute(query=query)
|
||||
|
|
@ -247,20 +271,14 @@ def get_capture_status(project_id):
|
|||
return helper.dict_to_camel_case(cur.fetchone())
|
||||
|
||||
|
||||
def update_capture_status(project_id, changes):
|
||||
if "rate" not in changes and "captureAll" not in changes:
|
||||
return {"errors": ["please provide 'rate' and/or 'captureAll' attributes to update."]}
|
||||
if int(changes["rate"]) < 0 or int(changes["rate"]) > 100:
|
||||
return {"errors": ["'rate' must be between 0..100."]}
|
||||
sample_rate = 0
|
||||
if "rate" in changes:
|
||||
sample_rate = int(changes["rate"])
|
||||
if changes.get("captureAll"):
|
||||
def update_capture_status(project_id, changes: schemas.SampleRateSchema):
|
||||
sample_rate = changes.rate
|
||||
if changes.capture_all:
|
||||
sample_rate = 100
|
||||
with pg_client.PostgresClient() as cur:
|
||||
query = cur.mogrify("""UPDATE public.projects
|
||||
SET sample_rate= %(sample_rate)s
|
||||
WHERE project_id =%(project_id)s
|
||||
WHERE project_id =%(project_id)s
|
||||
AND deleted_at ISNULL;""",
|
||||
{"project_id": project_id, "sample_rate": sample_rate})
|
||||
cur.execute(query=query)
|
||||
|
|
@ -280,30 +298,6 @@ def get_projects_ids(tenant_id):
|
|||
return [r["project_id"] for r in rows]
|
||||
|
||||
|
||||
def get_project_by_key(tenant_id, project_key, include_last_session=False, include_gdpr=None):
|
||||
with pg_client.PostgresClient() as cur:
|
||||
extra_select = ""
|
||||
if include_last_session:
|
||||
extra_select += """,(SELECT max(ss.start_ts)
|
||||
FROM public.sessions AS ss
|
||||
WHERE ss.project_key = %(project_key)s) AS last_recorded_session_at"""
|
||||
if include_gdpr:
|
||||
extra_select += ",s.gdpr"
|
||||
query = cur.mogrify(f"""SELECT s.project_key,
|
||||
s.name
|
||||
{extra_select}
|
||||
FROM public.projects AS s
|
||||
WHERE s.project_key =%(project_key)s
|
||||
AND s.tenant_id =%(tenant_id)s
|
||||
AND s.deleted_at IS NULL
|
||||
LIMIT 1;""",
|
||||
{"project_key": project_key, "tenant_id": tenant_id})
|
||||
|
||||
cur.execute(query=query)
|
||||
row = cur.fetchone()
|
||||
return helper.dict_to_camel_case(row)
|
||||
|
||||
|
||||
def is_authorized(project_id, tenant_id, user_id=None):
|
||||
if project_id is None or not str(project_id).isdigit():
|
||||
return False
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
from typing import Optional
|
||||
|
||||
from fastapi import HTTPException, status
|
||||
|
||||
import schemas_ee
|
||||
import schemas
|
||||
from chalicelib.core import users, projects
|
||||
from chalicelib.utils import pg_client, helper
|
||||
from chalicelib.utils.TimeUTC import TimeUTC
|
||||
|
|
@ -22,7 +21,7 @@ def __exists_by_name(tenant_id: int, name: str, exclude_id: Optional[int]) -> bo
|
|||
return row["exists"]
|
||||
|
||||
|
||||
def update(tenant_id, user_id, role_id, data: schemas_ee.RolePayloadSchema):
|
||||
def update(tenant_id, user_id, role_id, data: schemas.RolePayloadSchema):
|
||||
admin = users.get(user_id=user_id, tenant_id=tenant_id)
|
||||
|
||||
if not admin["admin"] and not admin["superAdmin"]:
|
||||
|
|
@ -57,7 +56,7 @@ def update(tenant_id, user_id, role_id, data: schemas_ee.RolePayloadSchema):
|
|||
RETURNING *, COALESCE((SELECT ARRAY_AGG(project_id)
|
||||
FROM roles_projects
|
||||
WHERE roles_projects.role_id=%(role_id)s),'{}') AS projects;""",
|
||||
{"tenant_id": tenant_id, "role_id": role_id, **data.dict()})
|
||||
{"tenant_id": tenant_id, "role_id": role_id, **data.model_dump()})
|
||||
cur.execute(query=query)
|
||||
row = cur.fetchone()
|
||||
row["created_at"] = TimeUTC.datetime_to_timestamp(row["created_at"])
|
||||
|
|
@ -80,7 +79,7 @@ def update(tenant_id, user_id, role_id, data: schemas_ee.RolePayloadSchema):
|
|||
return helper.dict_to_camel_case(row)
|
||||
|
||||
|
||||
def create(tenant_id, user_id, data: schemas_ee.RolePayloadSchema):
|
||||
def create(tenant_id, user_id, data: schemas.RolePayloadSchema):
|
||||
admin = users.get(user_id=user_id, tenant_id=tenant_id)
|
||||
|
||||
if not admin["admin"] and not admin["superAdmin"]:
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
from decouple import config
|
||||
from fastapi.security import SecurityScopes
|
||||
|
||||
import schemas_ee
|
||||
import schemas
|
||||
from chalicelib.core import permissions
|
||||
from chalicelib.utils.storage import StorageClient
|
||||
|
||||
SCOPES = SecurityScopes([schemas_ee.Permissions.dev_tools])
|
||||
SCOPES = SecurityScopes([schemas.Permissions.dev_tools])
|
||||
|
||||
|
||||
def __get_devtools_keys(project_id, session_id):
|
||||
|
|
@ -18,7 +18,7 @@ def __get_devtools_keys(project_id, session_id):
|
|||
]
|
||||
|
||||
|
||||
def get_urls(session_id, project_id, context: schemas_ee.CurrentContext, check_existence: bool = True):
|
||||
def get_urls(session_id, project_id, context: schemas.CurrentContext, check_existence: bool = True):
|
||||
if not permissions.check(security_scopes=SCOPES, context=context):
|
||||
return []
|
||||
results = []
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import ast
|
|||
from typing import List, Union
|
||||
|
||||
import schemas
|
||||
import schemas_ee
|
||||
import schemas
|
||||
from chalicelib.core import events, metadata, projects, performance_event, metrics
|
||||
from chalicelib.utils import pg_client, helper, metrics_helper, ch_client, exp_ch_helper
|
||||
|
||||
|
|
@ -246,7 +246,7 @@ def search_sessions(data: schemas.SessionsSearchPayloadSchema, project_id, user_
|
|||
else:
|
||||
for i in range(len(sessions)):
|
||||
sessions[i]["metadata"] = ast.literal_eval(sessions[i]["metadata"])
|
||||
sessions[i] = schemas_ee.SessionModel.parse_obj(helper.dict_to_camel_case(sessions[i]))
|
||||
sessions[i] = schemas.SessionModel.parse_obj(helper.dict_to_camel_case(sessions[i]))
|
||||
|
||||
# if not data.group_by_user and data.sort is not None and data.sort != "session_id":
|
||||
# sessions = sorted(sessions, key=lambda s: s[helper.key_to_snake_case(data.sort)],
|
||||
|
|
@ -260,12 +260,12 @@ 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.MetricOfTable, metric_value: List):
|
||||
step_size = int(metrics_helper.__get_step_size(endTimestamp=data.endDate, startTimestamp=data.startDate,
|
||||
step_size = int(metrics_helper.__get_step_size(endTimestamp=data.endTimestamp, startTimestamp=data.startTimestamp,
|
||||
density=density))
|
||||
extra_event = None
|
||||
if metric_of == schemas.MetricOfTable.visited_url:
|
||||
extra_event = f"""SELECT DISTINCT ev.session_id, ev.url_path
|
||||
FROM {exp_ch_helper.get_main_events_table(data.startDate)} AS ev
|
||||
FROM {exp_ch_helper.get_main_events_table(data.startTimestamp)} AS ev
|
||||
WHERE ev.datetime >= toDateTime(%(startDate)s / 1000)
|
||||
AND ev.datetime <= toDateTime(%(endDate)s / 1000)
|
||||
AND ev.project_id = %(project_id)s
|
||||
|
|
@ -300,7 +300,7 @@ def search2_series(data: schemas.SessionsSearchPayloadSchema, project_id: int, d
|
|||
# print("--------------------")
|
||||
sessions = cur.execute(main_query)
|
||||
if view_type == schemas.MetricTimeseriesViewType.line_chart:
|
||||
sessions = metrics.__complete_missing_steps(start_time=data.startDate, end_time=data.endDate,
|
||||
sessions = metrics.__complete_missing_steps(start_time=data.startTimestamp, end_time=data.endTimestamp,
|
||||
density=density, neutral={"count": 0}, rows=sessions)
|
||||
else:
|
||||
sessions = sessions[0]["count"] if len(sessions) > 0 else 0
|
||||
|
|
@ -362,7 +362,7 @@ def search2_series(data: schemas.SessionsSearchPayloadSchema, project_id: int, d
|
|||
return sessions
|
||||
|
||||
|
||||
def __is_valid_event(is_any: bool, event: schemas._SessionSearchEventSchema):
|
||||
def __is_valid_event(is_any: bool, event: schemas.SessionSearchEventSchema2):
|
||||
return not (not is_any and len(event.value) == 0 and event.type not in [schemas.EventType.request_details,
|
||||
schemas.EventType.graphql] \
|
||||
or event.type in [schemas.PerformanceEventType.location_dom_complete,
|
||||
|
|
@ -402,11 +402,11 @@ def __get_event_type(event_type: Union[schemas.EventType, schemas.PerformanceEve
|
|||
def search_query_parts_ch(data: schemas.SessionsSearchPayloadSchema, error_status, errors_only, favorite_only, issue,
|
||||
project_id, user_id, extra_event=None):
|
||||
ss_constraints = []
|
||||
full_args = {"project_id": project_id, "startDate": data.startDate, "endDate": data.endDate,
|
||||
full_args = {"project_id": project_id, "startDate": data.startTimestamp, "endDate": data.endTimestamp,
|
||||
"projectId": project_id, "userId": user_id}
|
||||
|
||||
MAIN_EVENTS_TABLE = exp_ch_helper.get_main_events_table(data.startDate)
|
||||
MAIN_SESSIONS_TABLE = exp_ch_helper.get_main_sessions_table(data.startDate)
|
||||
MAIN_EVENTS_TABLE = exp_ch_helper.get_main_events_table(data.startTimestamp)
|
||||
MAIN_SESSIONS_TABLE = exp_ch_helper.get_main_sessions_table(data.startTimestamp)
|
||||
|
||||
full_args["MAIN_EVENTS_TABLE"] = MAIN_EVENTS_TABLE
|
||||
full_args["MAIN_SESSIONS_TABLE"] = MAIN_SESSIONS_TABLE
|
||||
|
|
@ -1224,9 +1224,9 @@ def search_query_parts_ch(data: schemas.SessionsSearchPayloadSchema, error_statu
|
|||
else:
|
||||
data.events = []
|
||||
# ---------------------------------------------------------------------------
|
||||
if data.startDate is not None:
|
||||
if data.startTimestamp is not None:
|
||||
extra_constraints.append("s.datetime >= toDateTime(%(startDate)s/1000)")
|
||||
if data.endDate is not None:
|
||||
if data.endTimestamp is not None:
|
||||
extra_constraints.append("s.datetime <= toDateTime(%(endDate)s/1000)")
|
||||
# if data.platform is not None:
|
||||
# if data.platform == schemas.PlatformType.mobile:
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
from decouple import config
|
||||
|
||||
import schemas_ee
|
||||
import schemas
|
||||
from chalicelib.core import sessions, sessions_favorite_exp, sessions_mobs, sessions_devtool
|
||||
from chalicelib.utils import pg_client
|
||||
from chalicelib.utils.storage import extra
|
||||
|
||||
|
||||
def add_favorite_session(context: schemas_ee.CurrentContext, project_id, session_id):
|
||||
def add_favorite_session(context: schemas.CurrentContext, project_id, session_id):
|
||||
with pg_client.PostgresClient() as cur:
|
||||
cur.execute(
|
||||
cur.mogrify(f"""\
|
||||
|
|
@ -22,7 +22,7 @@ def add_favorite_session(context: schemas_ee.CurrentContext, project_id, session
|
|||
return {"errors": ["something went wrong"]}
|
||||
|
||||
|
||||
def remove_favorite_session(context: schemas_ee.CurrentContext, project_id, session_id):
|
||||
def remove_favorite_session(context: schemas.CurrentContext, project_id, session_id):
|
||||
with pg_client.PostgresClient() as cur:
|
||||
cur.execute(
|
||||
cur.mogrify(f"""\
|
||||
|
|
@ -39,7 +39,7 @@ def remove_favorite_session(context: schemas_ee.CurrentContext, project_id, sess
|
|||
return {"errors": ["something went wrong"]}
|
||||
|
||||
|
||||
def favorite_session(context: schemas_ee.CurrentContext, project_id, session_id):
|
||||
def favorite_session(context: schemas.CurrentContext, project_id, session_id):
|
||||
keys = sessions_mobs.__get_mob_keys(project_id=project_id, session_id=session_id)
|
||||
keys += sessions_mobs.__get_mob_keys_deprecated(session_id=session_id) # To support old sessions
|
||||
keys += sessions_devtool.__get_devtools_keys(project_id=project_id, session_id=session_id)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
from typing import Optional
|
||||
|
||||
import schemas
|
||||
import schemas_ee
|
||||
import schemas
|
||||
from chalicelib.core import metrics
|
||||
from chalicelib.utils import ch_client
|
||||
|
||||
|
|
@ -161,7 +161,7 @@ def query_requests_by_period(project_id, start_time, end_time, filters: Optional
|
|||
for n in names_:
|
||||
if n is None:
|
||||
continue
|
||||
data_ = {'category': schemas_ee.InsightCategories.network, 'name': n,
|
||||
data_ = {'category': schemas.InsightCategories.network, 'name': n,
|
||||
'value': None, 'oldValue': None, 'ratio': None, 'change': None, 'isNew': True}
|
||||
for n_, v in ratio:
|
||||
if n == n_:
|
||||
|
|
@ -266,7 +266,7 @@ def query_most_errors_by_period(project_id, start_time, end_time,
|
|||
for n in names_:
|
||||
if n is None:
|
||||
continue
|
||||
data_ = {'category': schemas_ee.InsightCategories.errors, 'name': n,
|
||||
data_ = {'category': schemas.InsightCategories.errors, 'name': n,
|
||||
'value': None, 'oldValue': None, 'ratio': None, 'change': None, 'isNew': True}
|
||||
for n_, v in ratio:
|
||||
if n == n_:
|
||||
|
|
@ -346,7 +346,7 @@ def query_cpu_memory_by_period(project_id, start_time, end_time,
|
|||
|
||||
output = list()
|
||||
if cpu_oldvalue is not None or cpu_newvalue is not None:
|
||||
output.append({'category': schemas_ee.InsightCategories.resources,
|
||||
output.append({'category': schemas.InsightCategories.resources,
|
||||
'name': 'cpu',
|
||||
'value': cpu_newvalue,
|
||||
'oldValue': cpu_oldvalue,
|
||||
|
|
@ -354,7 +354,7 @@ def query_cpu_memory_by_period(project_id, start_time, end_time,
|
|||
cpu_newvalue - cpu_oldvalue) / cpu_oldvalue if cpu_ratio is not None else cpu_ratio,
|
||||
'isNew': True if cpu_newvalue is not None and cpu_oldvalue is None else False})
|
||||
if mem_oldvalue is not None or mem_newvalue is not None:
|
||||
output.append({'category': schemas_ee.InsightCategories.resources,
|
||||
output.append({'category': schemas.InsightCategories.resources,
|
||||
'name': 'memory',
|
||||
'value': mem_newvalue,
|
||||
'oldValue': mem_oldvalue,
|
||||
|
|
@ -434,7 +434,7 @@ def query_click_rage_by_period(project_id, start_time, end_time,
|
|||
for n in names_:
|
||||
if n is None:
|
||||
continue
|
||||
data_ = {'category': schemas_ee.InsightCategories.rage, 'name': n,
|
||||
data_ = {'category': schemas.InsightCategories.rage, 'name': n,
|
||||
'value': None, 'oldValue': None, 'ratio': None, 'change': None, 'isNew': True}
|
||||
for n_, v in ratio:
|
||||
if n == n_:
|
||||
|
|
@ -453,26 +453,26 @@ def query_click_rage_by_period(project_id, start_time, end_time,
|
|||
return results
|
||||
|
||||
|
||||
def fetch_selected(project_id, data: schemas_ee.GetInsightsSchema):
|
||||
def fetch_selected(project_id, data: schemas.GetInsightsSchema):
|
||||
output = list()
|
||||
if data.metricValue is None or len(data.metricValue) == 0:
|
||||
data.metricValue = []
|
||||
for v in schemas_ee.InsightCategories:
|
||||
for v in schemas.InsightCategories:
|
||||
data.metricValue.append(v)
|
||||
filters = None
|
||||
if len(data.series) > 0:
|
||||
filters = data.series[0].filter
|
||||
|
||||
if schemas_ee.InsightCategories.errors in data.metricValue:
|
||||
if schemas.InsightCategories.errors in data.metricValue:
|
||||
output += query_most_errors_by_period(project_id=project_id, start_time=data.startTimestamp,
|
||||
end_time=data.endTimestamp, filters=filters)
|
||||
if schemas_ee.InsightCategories.network in data.metricValue:
|
||||
if schemas.InsightCategories.network in data.metricValue:
|
||||
output += query_requests_by_period(project_id=project_id, start_time=data.startTimestamp,
|
||||
end_time=data.endTimestamp, filters=filters)
|
||||
if schemas_ee.InsightCategories.rage in data.metricValue:
|
||||
if schemas.InsightCategories.rage in data.metricValue:
|
||||
output += query_click_rage_by_period(project_id=project_id, start_time=data.startTimestamp,
|
||||
end_time=data.endTimestamp, filters=filters)
|
||||
if schemas_ee.InsightCategories.resources in data.metricValue:
|
||||
if schemas.InsightCategories.resources in data.metricValue:
|
||||
output += query_cpu_memory_by_period(project_id=project_id, start_time=data.startTimestamp,
|
||||
end_time=data.endTimestamp, filters=filters)
|
||||
return output
|
||||
|
|
|
|||
|
|
@ -88,7 +88,8 @@ def create(tenant_id, user_id, project_id, session_id, data: schemas.SessionNote
|
|||
query = cur.mogrify(f"""INSERT INTO public.sessions_notes (message, user_id, tag, session_id, project_id, timestamp, is_public)
|
||||
VALUES (%(message)s, %(user_id)s, %(tag)s, %(session_id)s, %(project_id)s, %(timestamp)s, %(is_public)s)
|
||||
RETURNING *,(SELECT name FROM users WHERE users.user_id=%(user_id)s AND users.tenant_id=%(tenant_id)s) AS user_name;""",
|
||||
{"user_id": user_id, "project_id": project_id, "session_id": session_id, **data.dict(),
|
||||
{"user_id": user_id, "project_id": project_id, "session_id": session_id,
|
||||
**data.model_dump(),
|
||||
"tenant_id": tenant_id})
|
||||
cur.execute(query)
|
||||
result = helper.dict_to_camel_case(cur.fetchone())
|
||||
|
|
@ -118,7 +119,7 @@ def edit(tenant_id, user_id, project_id, note_id, data: schemas.SessionUpdateNot
|
|||
AND note_id = %(note_id)s
|
||||
AND deleted_at ISNULL
|
||||
RETURNING *,(SELECT name FROM users WHERE users.user_id=%(user_id)s AND users.tenant_id=%(tenant_id)s) AS user_name;""",
|
||||
{"project_id": project_id, "user_id": user_id, "note_id": note_id, **data.dict(),
|
||||
{"project_id": project_id, "user_id": user_id, "note_id": note_id, **data.model_dump(),
|
||||
"tenant_id": tenant_id})
|
||||
)
|
||||
row = helper.dict_to_camel_case(cur.fetchone())
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import schemas
|
||||
import schemas_ee
|
||||
import schemas
|
||||
from chalicelib.core import events, metadata, events_ios, \
|
||||
sessions_mobs, issues, resources, assist, sessions_devtool, sessions_notes
|
||||
from chalicelib.utils import errors_helper
|
||||
|
|
@ -17,7 +17,7 @@ def __group_metadata(session, project_metadata):
|
|||
|
||||
# for backward compatibility
|
||||
# This function should not use Clickhouse because it doesn't have `file_key`
|
||||
def get_by_id2_pg(project_id, session_id, context: schemas_ee.CurrentContext, full_data=False,
|
||||
def get_by_id2_pg(project_id, session_id, context: schemas.CurrentContext, full_data=False,
|
||||
include_fav_viewed=False, group_metadata=False, live=True):
|
||||
with pg_client.PostgresClient() as cur:
|
||||
extra_query = []
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import schemas_ee
|
||||
import schemas
|
||||
import logging
|
||||
from chalicelib.utils import events_queue
|
||||
|
||||
|
||||
def handle_frontend_signals_queued(project_id: int, user_id: int, data: schemas_ee.SignalsSchema):
|
||||
def handle_frontend_signals_queued(project_id: int, user_id: int, data: schemas.SignalsSchema):
|
||||
try:
|
||||
events_queue.global_queue.put((project_id, user_id, data))
|
||||
return {'data': 'insertion succeded'}
|
||||
|
|
|
|||
|
|
@ -30,17 +30,19 @@ T_VALUES = {1: 12.706, 2: 4.303, 3: 3.182, 4: 2.776, 5: 2.571, 6: 2.447, 7: 2.36
|
|||
21: 2.080, 22: 2.074, 23: 2.069, 25: 2.064, 26: 2.060, 27: 2.056, 28: 2.052, 29: 2.045, 30: 2.042}
|
||||
|
||||
|
||||
def get_stages_and_events(filter_d, project_id) -> List[RealDictRow]:
|
||||
def get_stages_and_events(filter_d: schemas.CardSeriesFilterSchema, project_id) -> List[RealDictRow]:
|
||||
"""
|
||||
Add minimal timestamp
|
||||
:param filter_d: dict contains events&filters&...
|
||||
:return:
|
||||
"""
|
||||
stages: [dict] = filter_d.get("events", [])
|
||||
filters: [dict] = filter_d.get("filters", [])
|
||||
filter_issues = filter_d.get("issueTypes")
|
||||
if filter_issues is None or len(filter_issues) == 0:
|
||||
filter_issues = []
|
||||
stages: [dict] = filter_d.events
|
||||
filters: [dict] = filter_d.filters
|
||||
filter_issues = []
|
||||
# TODO: enable this if needed by an endpoint
|
||||
# filter_issues = filter_d.get("issueTypes")
|
||||
# if filter_issues is None or len(filter_issues) == 0:
|
||||
# filter_issues = []
|
||||
stage_constraints = ["main.timestamp <= %(endTimestamp)s"]
|
||||
first_stage_extra_constraints = ["s.project_id=%(project_id)s", "s.start_ts >= %(startTimestamp)s",
|
||||
"s.start_ts <= %(endTimestamp)s"]
|
||||
|
|
@ -126,22 +128,22 @@ def get_stages_and_events(filter_d, project_id) -> List[RealDictRow]:
|
|||
i = -1
|
||||
for s in stages:
|
||||
|
||||
if s.get("operator") is None:
|
||||
s["operator"] = "is"
|
||||
if s.operator is None:
|
||||
s.operator = schemas.SearchEventOperator._is
|
||||
|
||||
if not isinstance(s["value"], list):
|
||||
s["value"] = [s["value"]]
|
||||
is_any = sh.isAny_opreator(s["operator"])
|
||||
if not is_any and isinstance(s["value"], list) and len(s["value"]) == 0:
|
||||
if not isinstance(s.value, list):
|
||||
s.value = [s.value]
|
||||
is_any = sh.isAny_opreator(s.operator)
|
||||
if not is_any and isinstance(s.value, list) and len(s.value) == 0:
|
||||
continue
|
||||
i += 1
|
||||
if i == 0:
|
||||
extra_from = filter_extra_from + ["INNER JOIN public.sessions AS s USING (session_id)"]
|
||||
else:
|
||||
extra_from = []
|
||||
op = sh.get_sql_operator(s["operator"])
|
||||
op = sh.get_sql_operator(s.operator)
|
||||
# event_type = s["type"].upper()
|
||||
event_type = s["type"]
|
||||
event_type = s.type
|
||||
if event_type == events.EventType.CLICK.ui_type:
|
||||
next_table = events.EventType.CLICK.table
|
||||
next_col_name = events.EventType.CLICK.column
|
||||
|
|
@ -171,16 +173,16 @@ def get_stages_and_events(filter_d, project_id) -> List[RealDictRow]:
|
|||
print(f"=================UNDEFINED:{event_type}")
|
||||
continue
|
||||
|
||||
values = {**values, **sh.multi_values(helper.values_for_operator(value=s["value"], op=s["operator"]),
|
||||
values = {**values, **sh.multi_values(helper.values_for_operator(value=s.value, op=s.operator),
|
||||
value_key=f"value{i + 1}")}
|
||||
if sh.is_negation_operator(s["operator"]) and i > 0:
|
||||
if sh.is_negation_operator(s.operator) and i > 0:
|
||||
op = sh.reverse_sql_operator(op)
|
||||
main_condition = "left_not.session_id ISNULL"
|
||||
extra_from.append(f"""LEFT JOIN LATERAL (SELECT session_id
|
||||
FROM {next_table} AS s_main
|
||||
WHERE
|
||||
{sh.multi_conditions(f"s_main.{next_col_name} {op} %(value{i + 1})s",
|
||||
values=s["value"], value_key=f"value{i + 1}")}
|
||||
values=s.value, value_key=f"value{i + 1}")}
|
||||
AND s_main.timestamp >= T{i}.stage{i}_timestamp
|
||||
AND s_main.session_id = T1.session_id) AS left_not ON (TRUE)""")
|
||||
else:
|
||||
|
|
@ -188,7 +190,7 @@ def get_stages_and_events(filter_d, project_id) -> List[RealDictRow]:
|
|||
main_condition = "TRUE"
|
||||
else:
|
||||
main_condition = sh.multi_conditions(f"main.{next_col_name} {op} %(value{i + 1})s",
|
||||
values=s["value"], value_key=f"value{i + 1}")
|
||||
values=s.value, value_key=f"value{i + 1}")
|
||||
n_stages_query.append(f"""
|
||||
(SELECT main.session_id,
|
||||
{"MIN(main.timestamp)" if i + 1 < len(stages) else "MAX(main.timestamp)"} AS stage{i + 1}_timestamp
|
||||
|
|
@ -231,7 +233,8 @@ def get_stages_and_events(filter_d, project_id) -> List[RealDictRow]:
|
|||
"""
|
||||
|
||||
# LIMIT 10000
|
||||
params = {"project_id": project_id, "startTimestamp": filter_d["startDate"], "endTimestamp": filter_d["endDate"],
|
||||
params = {"project_id": project_id, "startTimestamp": filter_d.startTimestamp,
|
||||
"endTimestamp": filter_d.endTimestamp,
|
||||
"issueTypes": tuple(filter_issues), **values}
|
||||
with pg_client.PostgresClient() as cur:
|
||||
query = cur.mogrify(n_stages_query, params)
|
||||
|
|
@ -245,7 +248,7 @@ def get_stages_and_events(filter_d, project_id) -> List[RealDictRow]:
|
|||
print("--------- FUNNEL SEARCH QUERY EXCEPTION -----------")
|
||||
print(query.decode('UTF-8'))
|
||||
print("--------- PAYLOAD -----------")
|
||||
print(filter_d)
|
||||
print(filter_d.model_dump_json())
|
||||
print("--------------------")
|
||||
raise err
|
||||
return rows
|
||||
|
|
@ -550,9 +553,9 @@ def get_issues(stages, rows, first_stage=None, last_stage=None, drop_only=False)
|
|||
return n_critical_issues, issues_dict, total_drop_due_to_issues
|
||||
|
||||
|
||||
def get_top_insights(filter_d, project_id):
|
||||
def get_top_insights(filter_d: schemas.CardSeriesFilterSchema, project_id):
|
||||
output = []
|
||||
stages = filter_d.get("events", [])
|
||||
stages = filter_d.events
|
||||
# TODO: handle 1 stage alone
|
||||
if len(stages) == 0:
|
||||
print("no stages found")
|
||||
|
|
@ -560,17 +563,24 @@ def get_top_insights(filter_d, project_id):
|
|||
elif len(stages) == 1:
|
||||
# TODO: count sessions, and users for single stage
|
||||
output = [{
|
||||
"type": stages[0]["type"],
|
||||
"value": stages[0]["value"],
|
||||
"type": stages[0].type,
|
||||
"value": stages[0].value,
|
||||
"dropPercentage": None,
|
||||
"operator": stages[0]["operator"],
|
||||
"operator": stages[0].operator,
|
||||
"sessionsCount": 0,
|
||||
"dropPct": 0,
|
||||
"usersCount": 0,
|
||||
"dropDueToIssues": 0
|
||||
|
||||
}]
|
||||
counts = sessions.search_sessions(data=schemas.SessionsSearchCountSchema.parse_obj(filter_d),
|
||||
# original
|
||||
# counts = sessions.search_sessions(data=schemas.SessionsSearchCountSchema.parse_obj(filter_d),
|
||||
# project_id=project_id, user_id=None, count_only=True)
|
||||
# first change
|
||||
# counts = sessions.search_sessions(data=schemas.FlatSessionsSearchPayloadSchema.parse_obj(filter_d),
|
||||
# project_id=project_id, user_id=None, count_only=True)
|
||||
# last change
|
||||
counts = sessions.search_sessions(data=schemas.SessionsSearchPayloadSchema.model_validate(filter_d),
|
||||
project_id=project_id, user_id=None, count_only=True)
|
||||
output[0]["sessionsCount"] = counts["countSessions"]
|
||||
output[0]["usersCount"] = counts["countUsers"]
|
||||
|
|
@ -589,9 +599,9 @@ def get_top_insights(filter_d, project_id):
|
|||
return stages_list, total_drop_due_to_issues
|
||||
|
||||
|
||||
def get_issues_list(filter_d, project_id, first_stage=None, last_stage=None):
|
||||
def get_issues_list(filter_d: schemas.CardSeriesFilterSchema, project_id, first_stage=None, last_stage=None):
|
||||
output = dict({"total_drop_due_to_issues": 0, "critical_issues_count": 0, "significant": [], "insignificant": []})
|
||||
stages = filter_d.get("events", [])
|
||||
stages = filter_d.events
|
||||
# The result of the multi-stage query
|
||||
rows = get_stages_and_events(filter_d=filter_d, project_id=project_id)
|
||||
# print(json.dumps(rows[0],indent=4))
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
__author__ = "AZNAUROV David"
|
||||
__maintainer__ = "KRAIEM Taha Yassine"
|
||||
|
||||
from decouple import config
|
||||
|
|
@ -30,17 +29,19 @@ T_VALUES = {1: 12.706, 2: 4.303, 3: 3.182, 4: 2.776, 5: 2.571, 6: 2.447, 7: 2.36
|
|||
21: 2.080, 22: 2.074, 23: 2.069, 25: 2.064, 26: 2.060, 27: 2.056, 28: 2.052, 29: 2.045, 30: 2.042}
|
||||
|
||||
|
||||
def get_stages_and_events(filter_d, project_id) -> List[RealDictRow]:
|
||||
def get_stages_and_events(filter_d: schemas.CardSeriesFilterSchema, project_id) -> List[RealDictRow]:
|
||||
"""
|
||||
Add minimal timestamp
|
||||
:param filter_d: dict contains events&filters&...
|
||||
:return:
|
||||
"""
|
||||
stages: [dict] = filter_d.get("events", [])
|
||||
filters: [dict] = filter_d.get("filters", [])
|
||||
filter_issues = filter_d.get("issueTypes")
|
||||
if filter_issues is None or len(filter_issues) == 0:
|
||||
filter_issues = []
|
||||
stages: [dict] = filter_d.events
|
||||
filters: [dict] = filter_d.filters
|
||||
filter_issues = []
|
||||
# TODO: enable this if needed by an endpoint
|
||||
# filter_issues = filter_d.get("issueTypes")
|
||||
# if filter_issues is None or len(filter_issues) == 0:
|
||||
# filter_issues = []
|
||||
stage_constraints = ["main.timestamp <= %(endTimestamp)s"]
|
||||
first_stage_extra_constraints = ["s.project_id=%(project_id)s", "s.start_ts >= %(startTimestamp)s",
|
||||
"s.start_ts <= %(endTimestamp)s"]
|
||||
|
|
@ -126,22 +127,22 @@ def get_stages_and_events(filter_d, project_id) -> List[RealDictRow]:
|
|||
i = -1
|
||||
for s in stages:
|
||||
|
||||
if s.get("operator") is None:
|
||||
s["operator"] = "is"
|
||||
if s.operator is None:
|
||||
s.operator = schemas.SearchEventOperator._is
|
||||
|
||||
if not isinstance(s["value"], list):
|
||||
s["value"] = [s["value"]]
|
||||
is_any = sh.isAny_opreator(s["operator"])
|
||||
if not is_any and isinstance(s["value"], list) and len(s["value"]) == 0:
|
||||
if not isinstance(s.value, list):
|
||||
s.value = [s.value]
|
||||
is_any = sh.isAny_opreator(s.operator)
|
||||
if not is_any and isinstance(s.value, list) and len(s.value) == 0:
|
||||
continue
|
||||
i += 1
|
||||
if i == 0:
|
||||
extra_from = filter_extra_from + ["INNER JOIN public.sessions AS s USING (session_id)"]
|
||||
else:
|
||||
extra_from = []
|
||||
op = sh.get_sql_operator(s["operator"])
|
||||
op = sh.get_sql_operator(s.operator)
|
||||
# event_type = s["type"].upper()
|
||||
event_type = s["type"]
|
||||
event_type = s.type
|
||||
if event_type == events.EventType.CLICK.ui_type:
|
||||
next_table = events.EventType.CLICK.table
|
||||
next_col_name = events.EventType.CLICK.column
|
||||
|
|
@ -171,16 +172,16 @@ def get_stages_and_events(filter_d, project_id) -> List[RealDictRow]:
|
|||
print(f"=================UNDEFINED:{event_type}")
|
||||
continue
|
||||
|
||||
values = {**values, **sh.multi_values(helper.values_for_operator(value=s["value"], op=s["operator"]),
|
||||
values = {**values, **sh.multi_values(helper.values_for_operator(value=s.value, op=s.operator),
|
||||
value_key=f"value{i + 1}")}
|
||||
if sh.is_negation_operator(s["operator"]) and i > 0:
|
||||
if sh.is_negation_operator(s.operator) and i > 0:
|
||||
op = sh.reverse_sql_operator(op)
|
||||
main_condition = "left_not.session_id ISNULL"
|
||||
extra_from.append(f"""LEFT JOIN LATERAL (SELECT session_id
|
||||
FROM {next_table} AS s_main
|
||||
WHERE
|
||||
{sh.multi_conditions(f"s_main.{next_col_name} {op} %(value{i + 1})s",
|
||||
values=s["value"], value_key=f"value{i + 1}")}
|
||||
values=s.value, value_key=f"value{i + 1}")}
|
||||
AND s_main.timestamp >= T{i}.stage{i}_timestamp
|
||||
AND s_main.session_id = T1.session_id) AS left_not ON (TRUE)""")
|
||||
else:
|
||||
|
|
@ -188,7 +189,7 @@ def get_stages_and_events(filter_d, project_id) -> List[RealDictRow]:
|
|||
main_condition = "TRUE"
|
||||
else:
|
||||
main_condition = sh.multi_conditions(f"main.{next_col_name} {op} %(value{i + 1})s",
|
||||
values=s["value"], value_key=f"value{i + 1}")
|
||||
values=s.value, value_key=f"value{i + 1}")
|
||||
n_stages_query.append(f"""
|
||||
(SELECT main.session_id,
|
||||
{"MIN(main.timestamp)" if i + 1 < len(stages) else "MAX(main.timestamp)"} AS stage{i + 1}_timestamp
|
||||
|
|
@ -231,7 +232,8 @@ def get_stages_and_events(filter_d, project_id) -> List[RealDictRow]:
|
|||
"""
|
||||
|
||||
# LIMIT 10000
|
||||
params = {"project_id": project_id, "startTimestamp": filter_d["startDate"], "endTimestamp": filter_d["endDate"],
|
||||
params = {"project_id": project_id, "startTimestamp": filter_d.startTimestamp,
|
||||
"endTimestamp": filter_d.endTimestamp,
|
||||
"issueTypes": tuple(filter_issues), **values}
|
||||
with pg_client.PostgresClient() as cur:
|
||||
query = cur.mogrify(n_stages_query, params)
|
||||
|
|
@ -245,7 +247,7 @@ def get_stages_and_events(filter_d, project_id) -> List[RealDictRow]:
|
|||
print("--------- FUNNEL SEARCH QUERY EXCEPTION -----------")
|
||||
print(query.decode('UTF-8'))
|
||||
print("--------- PAYLOAD -----------")
|
||||
print(filter_d)
|
||||
print(filter_d.model_dump_json())
|
||||
print("--------------------")
|
||||
raise err
|
||||
return rows
|
||||
|
|
@ -550,9 +552,9 @@ def get_issues(stages, rows, first_stage=None, last_stage=None, drop_only=False)
|
|||
return n_critical_issues, issues_dict, total_drop_due_to_issues
|
||||
|
||||
|
||||
def get_top_insights(filter_d, project_id):
|
||||
def get_top_insights(filter_d: schemas.CardSeriesFilterSchema, project_id):
|
||||
output = []
|
||||
stages = filter_d.get("events", [])
|
||||
stages = filter_d.events
|
||||
# TODO: handle 1 stage alone
|
||||
if len(stages) == 0:
|
||||
print("no stages found")
|
||||
|
|
@ -560,17 +562,24 @@ def get_top_insights(filter_d, project_id):
|
|||
elif len(stages) == 1:
|
||||
# TODO: count sessions, and users for single stage
|
||||
output = [{
|
||||
"type": stages[0]["type"],
|
||||
"value": stages[0]["value"],
|
||||
"type": stages[0].type,
|
||||
"value": stages[0].value,
|
||||
"dropPercentage": None,
|
||||
"operator": stages[0]["operator"],
|
||||
"operator": stages[0].operator,
|
||||
"sessionsCount": 0,
|
||||
"dropPct": 0,
|
||||
"usersCount": 0,
|
||||
"dropDueToIssues": 0
|
||||
|
||||
}]
|
||||
counts = sessions.search_sessions(data=schemas.SessionsSearchCountSchema.parse_obj(filter_d),
|
||||
# original
|
||||
# counts = sessions.search_sessions(data=schemas.SessionsSearchCountSchema.parse_obj(filter_d),
|
||||
# project_id=project_id, user_id=None, count_only=True)
|
||||
# first change
|
||||
# counts = sessions.search_sessions(data=schemas.FlatSessionsSearchPayloadSchema.parse_obj(filter_d),
|
||||
# project_id=project_id, user_id=None, count_only=True)
|
||||
# last change
|
||||
counts = sessions.search_sessions(data=schemas.SessionsSearchPayloadSchema.model_validate(filter_d),
|
||||
project_id=project_id, user_id=None, count_only=True)
|
||||
output[0]["sessionsCount"] = counts["countSessions"]
|
||||
output[0]["usersCount"] = counts["countUsers"]
|
||||
|
|
@ -589,9 +598,9 @@ def get_top_insights(filter_d, project_id):
|
|||
return stages_list, total_drop_due_to_issues
|
||||
|
||||
|
||||
def get_issues_list(filter_d, project_id, first_stage=None, last_stage=None):
|
||||
def get_issues_list(filter_d: schemas.CardSeriesFilterSchema, project_id, first_stage=None, last_stage=None):
|
||||
output = dict({"total_drop_due_to_issues": 0, "critical_issues_count": 0, "significant": [], "insignificant": []})
|
||||
stages = filter_d.get("events", [])
|
||||
stages = filter_d.events
|
||||
# The result of the multi-stage query
|
||||
rows = get_stages_and_events(filter_d=filter_d, project_id=project_id)
|
||||
# print(json.dumps(rows[0],indent=4))
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import json
|
|||
from decouple import config
|
||||
|
||||
import schemas
|
||||
import schemas_ee
|
||||
from chalicelib.core import users, telemetry, tenants
|
||||
from chalicelib.utils import captcha
|
||||
from chalicelib.utils import helper
|
||||
|
|
@ -19,7 +18,7 @@ def create_tenant(data: schemas.UserSignupSchema):
|
|||
|
||||
email = data.email
|
||||
print(f"=====================> {email}")
|
||||
password = data.password
|
||||
password = data.password.get_secret_value()
|
||||
|
||||
if email is None or len(email) < 5:
|
||||
errors.append("Invalid email address.")
|
||||
|
|
@ -52,7 +51,7 @@ def create_tenant(data: schemas.UserSignupSchema):
|
|||
params = {
|
||||
"email": email, "password": password, "fullname": fullname, "projectName": project_name,
|
||||
"data": json.dumps({"lastAnnouncementView": TimeUTC.now()}), "organizationName": organization_name,
|
||||
"permissions": [p.value for p in schemas_ee.Permissions]
|
||||
"permissions": [p.value for p in schemas.Permissions]
|
||||
}
|
||||
query = """WITH t AS (
|
||||
INSERT INTO public.tenants (name)
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ from starlette.background import BackgroundTask
|
|||
|
||||
import app as main_app
|
||||
import schemas
|
||||
import schemas_ee
|
||||
import schemas
|
||||
from chalicelib.utils import pg_client, helper
|
||||
from chalicelib.utils.TimeUTC import TimeUTC
|
||||
from schemas import CurrentContext
|
||||
|
|
@ -64,7 +64,7 @@ class TraceSchema(BaseModel):
|
|||
|
||||
|
||||
def __process_trace(trace: TraceSchema):
|
||||
data = trace.dict()
|
||||
data = trace.model_dump()
|
||||
data["parameters"] = json.dumps(trace.parameters) if trace.parameters is not None and len(
|
||||
trace.parameters.keys()) > 0 else None
|
||||
data["payload"] = json.dumps(trace.payload) if trace.payload is not None and len(trace.payload.keys()) > 0 else None
|
||||
|
|
@ -158,7 +158,7 @@ async def process_traces_queue():
|
|||
await write_traces_batch(traces)
|
||||
|
||||
|
||||
def get_all(tenant_id, data: schemas_ee.TrailSearchPayloadSchema):
|
||||
def get_all(tenant_id, data: schemas.TrailSearchPayloadSchema):
|
||||
with pg_client.PostgresClient() as cur:
|
||||
conditions = ["traces.tenant_id=%(tenant_id)s",
|
||||
"traces.created_at>=%(startDate)s",
|
||||
|
|
@ -168,7 +168,7 @@ def get_all(tenant_id, data: schemas_ee.TrailSearchPayloadSchema):
|
|||
"endDate": data.endDate,
|
||||
"p_start": (data.page - 1) * data.limit,
|
||||
"p_end": data.page * data.limit,
|
||||
**data.dict()}
|
||||
**data.model_dump()}
|
||||
if data.user_id is not None:
|
||||
conditions.append("user_id=%(user_id)s")
|
||||
if data.action is not None:
|
||||
|
|
@ -184,10 +184,10 @@ def get_all(tenant_id, data: schemas_ee.TrailSearchPayloadSchema):
|
|||
COALESCE(JSONB_AGG(full_traces ORDER BY rn)
|
||||
FILTER (WHERE rn > %(p_start)s AND rn <= %(p_end)s), '[]'::JSONB) AS sessions
|
||||
FROM (SELECT traces.*,users.email,users.name AS username,
|
||||
ROW_NUMBER() OVER (ORDER BY traces.created_at {data.order.value}) AS rn
|
||||
ROW_NUMBER() OVER (ORDER BY traces.created_at {data.order}) AS rn
|
||||
FROM traces LEFT JOIN users USING (user_id)
|
||||
WHERE {" AND ".join(conditions)}
|
||||
ORDER BY traces.created_at {data.order.value}) AS full_traces;""", params)
|
||||
ORDER BY traces.created_at {data.order}) AS full_traces;""", params)
|
||||
)
|
||||
rows = cur.fetchone()
|
||||
return helper.dict_to_camel_case(rows)
|
||||
|
|
|
|||
|
|
@ -2,15 +2,17 @@ import json
|
|||
import secrets
|
||||
|
||||
from decouple import config
|
||||
from fastapi import BackgroundTasks
|
||||
from fastapi import BackgroundTasks, HTTPException
|
||||
from starlette import status
|
||||
|
||||
import schemas
|
||||
import schemas_ee
|
||||
from chalicelib.core import authorizers, metadata, projects, roles
|
||||
from chalicelib.core import authorizers, metadata, projects
|
||||
from chalicelib.core import tenants, assist
|
||||
from chalicelib.utils import helper, email_helper, smtp
|
||||
from chalicelib.utils import email_helper, smtp
|
||||
from chalicelib.utils import helper
|
||||
from chalicelib.utils import pg_client
|
||||
from chalicelib.utils.TimeUTC import TimeUTC
|
||||
from chalicelib.core import roles
|
||||
|
||||
|
||||
def __generate_invitation_token():
|
||||
|
|
@ -210,33 +212,33 @@ def update(tenant_id, user_id, changes, output=True):
|
|||
return get(user_id=user_id, tenant_id=tenant_id)
|
||||
|
||||
|
||||
def create_member(tenant_id, user_id, data, background_tasks: BackgroundTasks):
|
||||
def create_member(tenant_id, user_id, data: schemas.CreateMemberSchema, background_tasks: BackgroundTasks):
|
||||
admin = get(tenant_id=tenant_id, user_id=user_id)
|
||||
if not admin["admin"] and not admin["superAdmin"]:
|
||||
return {"errors": ["unauthorized"]}
|
||||
if data.get("userId") is not None:
|
||||
if data.user_id is not None:
|
||||
return {"errors": ["please use POST/PUT /client/members/{memberId} for update"]}
|
||||
user = get_by_email_only(email=data["email"])
|
||||
user = get_by_email_only(email=data.email)
|
||||
if user:
|
||||
return {"errors": ["user already exists"]}
|
||||
name = data.get("name", None)
|
||||
if name is None or len(name) == 0:
|
||||
name = data["email"]
|
||||
|
||||
if data.name is None or len(data.name) == 0:
|
||||
data.name = data.email
|
||||
role_id = data.get("roleId")
|
||||
if role_id is None:
|
||||
role_id = roles.get_role_by_name(tenant_id=tenant_id, name="member").get("roleId")
|
||||
invitation_token = __generate_invitation_token()
|
||||
user = get_deleted_user_by_email(email=data["email"])
|
||||
user = get_deleted_user_by_email(email=data.email)
|
||||
if user is not None and user["tenantId"] == tenant_id:
|
||||
new_member = restore_member(tenant_id=tenant_id, email=data["email"], invitation_token=invitation_token,
|
||||
admin=data.get("admin", False), name=name, user_id=user["userId"], role_id=role_id)
|
||||
new_member = restore_member(tenant_id=tenant_id, email=data.email, invitation_token=invitation_token,
|
||||
admin=data.admin, name=data.name, user_id=user["userId"], role_id=role_id)
|
||||
elif user is not None:
|
||||
__hard_delete_user(user_id=user["userId"])
|
||||
new_member = create_new_member(tenant_id=tenant_id, email=data["email"], invitation_token=invitation_token,
|
||||
admin=data.get("admin", False), name=name, role_id=role_id)
|
||||
admin=data.get("admin", False), name=data.name, role_id=role_id)
|
||||
else:
|
||||
new_member = create_new_member(tenant_id=tenant_id, email=data["email"], invitation_token=invitation_token,
|
||||
admin=data.get("admin", False), name=name, role_id=role_id)
|
||||
admin=data.get("admin", False), name=data.name, role_id=role_id)
|
||||
new_member["invitationLink"] = __get_invitation_link(new_member.pop("invitationToken"))
|
||||
background_tasks.add_task(email_helper.send_team_invitation, **{
|
||||
"recipient": data["email"],
|
||||
|
|
@ -282,7 +284,8 @@ def get(user_id, tenant_id):
|
|||
roles.name AS role_name,
|
||||
roles.permissions,
|
||||
roles.all_projects,
|
||||
basic_authentication.password IS NOT NULL AS has_password
|
||||
basic_authentication.password IS NOT NULL AS has_password,
|
||||
users.service_account
|
||||
FROM public.users LEFT JOIN public.basic_authentication ON users.user_id=basic_authentication.user_id
|
||||
LEFT JOIN public.roles USING (role_id)
|
||||
WHERE
|
||||
|
|
@ -351,7 +354,7 @@ def edit_account(user_id, tenant_id, changes: schemas.EditAccountSchema):
|
|||
return {"data": __get_account_info(tenant_id=tenant_id, user_id=user_id)}
|
||||
|
||||
|
||||
def edit_member(user_id_to_update, tenant_id, changes: schemas_ee.EditMemberSchema, editor_id):
|
||||
def edit_member(user_id_to_update, tenant_id, changes: schemas.EditMemberSchema, editor_id):
|
||||
user = get_member(user_id=user_id_to_update, tenant_id=tenant_id)
|
||||
_changes = {}
|
||||
if editor_id != user_id_to_update:
|
||||
|
|
@ -472,7 +475,9 @@ def get_members(tenant_id):
|
|||
FROM public.users
|
||||
LEFT JOIN public.basic_authentication ON users.user_id=basic_authentication.user_id
|
||||
LEFT JOIN public.roles USING (role_id)
|
||||
WHERE users.tenant_id = %(tenant_id)s AND users.deleted_at IS NULL
|
||||
WHERE users.tenant_id = %(tenant_id)s
|
||||
AND users.deleted_at IS NULL
|
||||
AND NOT users.service_account
|
||||
ORDER BY name, user_id""",
|
||||
{"tenant_id": tenant_id})
|
||||
)
|
||||
|
|
@ -626,17 +631,24 @@ def auth_exists(user_id, tenant_id, jwt_iat, jwt_aud):
|
|||
with pg_client.PostgresClient() as cur:
|
||||
cur.execute(
|
||||
cur.mogrify(
|
||||
f"SELECT user_id AS id,jwt_iat, changed_at FROM public.users INNER JOIN public.basic_authentication USING(user_id) WHERE user_id = %(userId)s AND tenant_id = %(tenant_id)s AND deleted_at IS NULL LIMIT 1;",
|
||||
f"""SELECT user_id,
|
||||
jwt_iat,
|
||||
changed_at,
|
||||
service_account,
|
||||
basic_authentication.user_id IS NOT NULL AS has_basic_auth
|
||||
FROM public.users
|
||||
LEFT JOIN public.basic_authentication USING(user_id)
|
||||
WHERE user_id = %(userId)s
|
||||
AND tenant_id = %(tenant_id)s
|
||||
AND deleted_at IS NULL
|
||||
LIMIT 1;""",
|
||||
{"userId": user_id, "tenant_id": tenant_id})
|
||||
)
|
||||
r = cur.fetchone()
|
||||
return r is not None \
|
||||
and r.get("jwt_iat") is not None \
|
||||
and (abs(jwt_iat - TimeUTC.datetime_to_timestamp(r["jwt_iat"]) // 1000) <= 1 \
|
||||
or (jwt_aud.startswith("plugin") \
|
||||
and (r["changed_at"] is None \
|
||||
or jwt_iat >= (TimeUTC.datetime_to_timestamp(r["changed_at"]) // 1000)))
|
||||
)
|
||||
and (r["service_account"] and not r["has_basic_auth"]
|
||||
or r.get("jwt_iat") is not None \
|
||||
and (abs(jwt_iat - TimeUTC.datetime_to_timestamp(r["jwt_iat"]) // 1000) <= 1))
|
||||
|
||||
|
||||
def change_jwt_iat(user_id):
|
||||
|
|
@ -665,7 +677,8 @@ def authenticate(email, password, for_change_password=False) -> dict | None:
|
|||
users.origin,
|
||||
users.role_id,
|
||||
roles.name AS role_name,
|
||||
roles.permissions
|
||||
roles.permissions,
|
||||
users.service_account
|
||||
FROM public.users AS users INNER JOIN public.basic_authentication USING(user_id)
|
||||
LEFT JOIN public.roles ON (roles.role_id = users.role_id AND roles.tenant_id = users.tenant_id)
|
||||
WHERE users.email = %(email)s
|
||||
|
|
@ -694,7 +707,10 @@ def authenticate(email, password, for_change_password=False) -> dict | None:
|
|||
if for_change_password:
|
||||
return True
|
||||
r = helper.dict_to_camel_case(r)
|
||||
if config("enforce_SSO", cast=bool, default=False) and helper.is_saml2_available():
|
||||
if r["serviceAccount"]:
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="service account is not authorized to login")
|
||||
elif config("enforce_SSO", cast=bool, default=False) and helper.is_saml2_available():
|
||||
return {"errors": ["must sign-in with SSO, enforced by admin"]}
|
||||
|
||||
jwt_iat = change_jwt_iat(r['userId'])
|
||||
|
|
@ -710,33 +726,27 @@ def authenticate(email, password, for_change_password=False) -> dict | None:
|
|||
return None
|
||||
|
||||
|
||||
def authenticate_sso(email, internal_id, exp=None):
|
||||
def get_user_role(tenant_id, user_id):
|
||||
with pg_client.PostgresClient() as cur:
|
||||
query = cur.mogrify(
|
||||
f"""SELECT
|
||||
users.user_id,
|
||||
users.tenant_id,
|
||||
users.role,
|
||||
users.name,
|
||||
(CASE WHEN users.role = 'owner' THEN TRUE ELSE FALSE END) AS super_admin,
|
||||
(CASE WHEN users.role = 'admin' THEN TRUE ELSE FALSE END) AS admin,
|
||||
(CASE WHEN users.role = 'member' THEN TRUE ELSE FALSE END) AS member,
|
||||
origin,
|
||||
role_id
|
||||
FROM public.users AS users
|
||||
WHERE users.email = %(email)s AND internal_id = %(internal_id)s;""",
|
||||
{"email": email, "internal_id": internal_id})
|
||||
|
||||
cur.execute(query)
|
||||
r = cur.fetchone()
|
||||
|
||||
if r is not None:
|
||||
r = helper.dict_to_camel_case(r)
|
||||
jwt_iat = TimeUTC.datetime_to_timestamp(change_jwt_iat(r['userId']))
|
||||
return authorizers.generate_jwt(r['userId'], r['tenantId'],
|
||||
iat=jwt_iat, aud=f"front:{helper.get_stage_name()}",
|
||||
exp=(exp + jwt_iat // 1000) if exp is not None else None)
|
||||
return None
|
||||
cur.execute(
|
||||
cur.mogrify(
|
||||
f"""SELECT
|
||||
users.user_id,
|
||||
users.email,
|
||||
users.role,
|
||||
users.name,
|
||||
users.created_at,
|
||||
(CASE WHEN users.role = 'owner' THEN TRUE ELSE FALSE END) AS super_admin,
|
||||
(CASE WHEN users.role = 'admin' THEN TRUE ELSE FALSE END) AS admin,
|
||||
(CASE WHEN users.role = 'member' THEN TRUE ELSE FALSE END) AS member
|
||||
FROM public.users
|
||||
WHERE users.deleted_at IS NULL
|
||||
AND users.user_id=%(user_id)s
|
||||
AND users.tenant_id=%(tenant_id)s
|
||||
LIMIT 1""",
|
||||
{"tenant_id": tenant_id, "user_id": user_id})
|
||||
)
|
||||
return helper.dict_to_camel_case(cur.fetchone())
|
||||
|
||||
|
||||
def create_sso_user(tenant_id, email, admin, name, origin, role_id, internal_id=None):
|
||||
|
|
@ -772,6 +782,48 @@ def create_sso_user(tenant_id, email, admin, name, origin, role_id, internal_id=
|
|||
return helper.dict_to_camel_case(cur.fetchone())
|
||||
|
||||
|
||||
def __hard_delete_user(user_id):
|
||||
with pg_client.PostgresClient() as cur:
|
||||
query = cur.mogrify(
|
||||
f"""DELETE FROM public.users
|
||||
WHERE users.user_id = %(user_id)s AND users.deleted_at IS NOT NULL ;""",
|
||||
{"user_id": user_id})
|
||||
cur.execute(query)
|
||||
|
||||
|
||||
def authenticate_sso(email, internal_id, exp=None):
|
||||
with pg_client.PostgresClient() as cur:
|
||||
query = cur.mogrify(
|
||||
f"""SELECT
|
||||
users.user_id,
|
||||
users.tenant_id,
|
||||
users.role,
|
||||
users.name,
|
||||
(CASE WHEN users.role = 'owner' THEN TRUE ELSE FALSE END) AS super_admin,
|
||||
(CASE WHEN users.role = 'admin' THEN TRUE ELSE FALSE END) AS admin,
|
||||
(CASE WHEN users.role = 'member' THEN TRUE ELSE FALSE END) AS member,
|
||||
origin,
|
||||
role_id,
|
||||
service_account
|
||||
FROM public.users AS users
|
||||
WHERE users.email = %(email)s AND internal_id = %(internal_id)s;""",
|
||||
{"email": email, "internal_id": internal_id})
|
||||
|
||||
cur.execute(query)
|
||||
r = cur.fetchone()
|
||||
|
||||
if r is not None:
|
||||
r = helper.dict_to_camel_case(r)
|
||||
if r["serviceAccount"]:
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="service account is not authorized to login")
|
||||
jwt_iat = TimeUTC.datetime_to_timestamp(change_jwt_iat(r['userId']))
|
||||
return authorizers.generate_jwt(r['userId'], r['tenantId'],
|
||||
iat=jwt_iat, aud=f"front:{helper.get_stage_name()}",
|
||||
exp=(exp + jwt_iat // 1000) if exp is not None else None)
|
||||
return None
|
||||
|
||||
|
||||
def restore_sso_user(user_id, tenant_id, email, admin, name, origin, role_id, internal_id=None):
|
||||
with pg_client.PostgresClient() as cur:
|
||||
query = cur.mogrify(f"""\
|
||||
|
|
@ -822,35 +874,3 @@ def restore_sso_user(user_id, tenant_id, email, admin, name, origin, role_id, in
|
|||
query
|
||||
)
|
||||
return helper.dict_to_camel_case(cur.fetchone())
|
||||
|
||||
|
||||
def __hard_delete_user(user_id):
|
||||
with pg_client.PostgresClient() as cur:
|
||||
query = cur.mogrify(
|
||||
f"""DELETE FROM public.users
|
||||
WHERE users.user_id = %(user_id)s AND users.deleted_at IS NOT NULL ;""",
|
||||
{"user_id": user_id})
|
||||
cur.execute(query)
|
||||
|
||||
|
||||
def get_user_role(tenant_id, user_id):
|
||||
with pg_client.PostgresClient() as cur:
|
||||
cur.execute(
|
||||
cur.mogrify(
|
||||
f"""SELECT
|
||||
users.user_id,
|
||||
users.email,
|
||||
users.role,
|
||||
users.name,
|
||||
users.created_at,
|
||||
(CASE WHEN users.role = 'owner' THEN TRUE ELSE FALSE END) AS super_admin,
|
||||
(CASE WHEN users.role = 'admin' THEN TRUE ELSE FALSE END) AS admin,
|
||||
(CASE WHEN users.role = 'member' THEN TRUE ELSE FALSE END) AS member
|
||||
FROM public.users
|
||||
WHERE users.deleted_at IS NULL
|
||||
AND users.user_id=%(user_id)s
|
||||
AND users.tenant_id=%(tenant_id)s
|
||||
LIMIT 1""",
|
||||
{"tenant_id": tenant_id, "user_id": user_id})
|
||||
)
|
||||
return helper.dict_to_camel_case(cur.fetchone())
|
||||
|
|
|
|||
|
|
@ -83,6 +83,8 @@ def update(tenant_id, webhook_id, changes, replace_none=False):
|
|||
{"tenant_id": tenant_id, "id": webhook_id, **changes})
|
||||
)
|
||||
w = helper.dict_to_camel_case(cur.fetchone())
|
||||
if w is None:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"webhook not found.")
|
||||
w["createdAt"] = TimeUTC.datetime_to_timestamp(w["createdAt"])
|
||||
if replace_none:
|
||||
for k in w.keys():
|
||||
|
|
@ -128,20 +130,22 @@ def exists_by_name(tenant_id: int, name: str, exclude_id: Optional[int],
|
|||
return row["exists"]
|
||||
|
||||
|
||||
def add_edit(tenant_id, data, replace_none=None):
|
||||
if "name" in data and len(data["name"]) > 0 \
|
||||
and exists_by_name(name=data["name"], exclude_id=data.get("webhookId"), tenant_id=tenant_id):
|
||||
def add_edit(tenant_id, data: schemas.WebhookSchema, replace_none=None):
|
||||
if len(data.name) > 0 \
|
||||
and exists_by_name(name=data.name, exclude_id=data.webhook_id):
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=f"name already exists.")
|
||||
if data.get("webhookId") is not None:
|
||||
return update(tenant_id=tenant_id, webhook_id=data["webhookId"],
|
||||
changes={"endpoint": data["endpoint"],
|
||||
"authHeader": None if "authHeader" not in data else data["authHeader"],
|
||||
"name": data["name"] if "name" in data else ""}, replace_none=replace_none)
|
||||
if data.webhook_id is not None:
|
||||
return update(tenant_id=tenant_id, webhook_id=data.webhook_id,
|
||||
changes={"endpoint": data.endpoint,
|
||||
"authHeader": data.auth_header,
|
||||
"name": data.name},
|
||||
replace_none=replace_none)
|
||||
else:
|
||||
return add(tenant_id=tenant_id,
|
||||
endpoint=data["endpoint"],
|
||||
auth_header=None if "authHeader" not in data else data["authHeader"],
|
||||
name=data["name"] if "name" in data else "", replace_none=replace_none)
|
||||
endpoint=data.endpoint,
|
||||
auth_header=data.auth_header,
|
||||
name=data.name,
|
||||
replace_none=replace_none)
|
||||
|
||||
|
||||
def delete(tenant_id, webhook_id):
|
||||
|
|
|
|||
|
|
@ -5,9 +5,11 @@ from urllib.parse import urlparse
|
|||
|
||||
from decouple import config
|
||||
from fastapi import Request
|
||||
from onelogin.saml2.auth import OneLogin_Saml2_Auth
|
||||
from starlette.datastructures import FormData
|
||||
|
||||
if config("ENABLE_SSO", cast=bool, default=True):
|
||||
from onelogin.saml2.auth import OneLogin_Saml2_Auth
|
||||
|
||||
SAML2 = {
|
||||
"strict": config("saml_strict", cast=bool, default=True),
|
||||
"debug": config("saml_debug", cast=bool, default=True),
|
||||
|
|
|
|||
|
|
@ -18,9 +18,10 @@ if config('ch_receive_timeout', cast=int, default=-1) > 0:
|
|||
class ClickHouseClient:
|
||||
__client = None
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, database=None):
|
||||
self.__client = clickhouse_driver.Client(host=config("ch_host"),
|
||||
database=config("ch_database", default="default"),
|
||||
database=database if database else config("ch_database",
|
||||
default="default"),
|
||||
user=config("ch_user", default="default"),
|
||||
password=config("ch_password", default=""),
|
||||
port=config("ch_port", cast=int),
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ class EventQueue():
|
|||
project_id, user_id, element = self.events.get()
|
||||
params[f'project_id_{i}'] = project_id
|
||||
params[f'user_id_{i}'] = user_id
|
||||
for _key, _val in element.dict().items():
|
||||
for _key, _val in element.model_dump().items():
|
||||
if _key == 'data':
|
||||
params[f'{_key}_{i}'] = json.dumps(_val)
|
||||
if 'sessionId' in _val.keys():
|
||||
|
|
@ -77,7 +77,7 @@ async def terminate():
|
|||
logging.info('> queue fulshed')
|
||||
|
||||
# def __process_schema(trace):
|
||||
# data = trace.dict()
|
||||
# data = trace.model_dump()
|
||||
# data["parameters"] = json.dumps(trace.parameters) if trace.parameters is not None and len(
|
||||
# trace.parameters.keys()) > 0 else None
|
||||
# data["payload"] = json.dumps(trace.payload) if trace.payload is not None and len(trace.payload.keys()) > 0 else None
|
||||
|
|
|
|||
|
|
@ -82,7 +82,6 @@ rm -rf ./db_changes.sql
|
|||
rm -rf ./Dockerfile_bundle
|
||||
rm -rf ./entrypoint.bundle.sh
|
||||
rm -rf ./chalicelib/core/heatmaps.py
|
||||
rm -rf ./schemas.py
|
||||
rm -rf ./routers/subs/v1_api.py
|
||||
#exp rm -rf ./chalicelib/core/custom_metrics.py
|
||||
rm -rf ./chalicelib/core/performance_event.py
|
||||
|
|
@ -91,3 +90,5 @@ rm -rf ./app_alerts.py
|
|||
rm -rf ./build_alerts.sh
|
||||
rm -rf ./run-dev.sh
|
||||
rm -rf ./run-alerts-dev.sh
|
||||
rm -rf ./schemas/overrides.py
|
||||
rm -rf ./schemas/schemas.py
|
||||
|
|
@ -10,12 +10,12 @@ from starlette.exceptions import HTTPException
|
|||
from starlette.requests import Request
|
||||
from starlette.responses import Response, JSONResponse
|
||||
|
||||
import schemas_ee
|
||||
import schemas
|
||||
from chalicelib.utils import helper
|
||||
from chalicelib.core import traces
|
||||
|
||||
|
||||
async def OR_context(request: Request) -> schemas_ee.CurrentContext:
|
||||
async def OR_context(request: Request) -> schemas.CurrentContext:
|
||||
if hasattr(request.state, "currentContext"):
|
||||
return request.state.currentContext
|
||||
else:
|
||||
|
|
@ -55,11 +55,20 @@ class ORRoute(APIRoute):
|
|||
return custom_route_handler
|
||||
|
||||
|
||||
def __check(security_scopes: SecurityScopes, context: schemas_ee.CurrentContext = Depends(OR_context)):
|
||||
def __check(security_scopes: SecurityScopes, context: schemas.CurrentContext = Depends(OR_context)):
|
||||
s_p = 0
|
||||
for scope in security_scopes.scopes:
|
||||
if isinstance(scope, schemas.ServicePermissions):
|
||||
s_p += 1
|
||||
if context.service_account and not isinstance(scope, schemas.ServicePermissions) \
|
||||
or not context.service_account and not isinstance(scope, schemas.Permissions):
|
||||
continue
|
||||
if scope not in context.permissions:
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Not enough permissions")
|
||||
if context.service_account and s_p == 0:
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Not enough permissions (service account)")
|
||||
|
||||
|
||||
def OR_scope(*scopes):
|
||||
|
|
|
|||
|
|
@ -1,20 +1,20 @@
|
|||
requests==2.31.0
|
||||
# Keep this version to not have conflicts between requests and boto3
|
||||
urllib3==1.26.16
|
||||
boto3==1.26.148
|
||||
pyjwt==2.7.0
|
||||
psycopg2-binary==2.9.6
|
||||
elasticsearch==8.8.0
|
||||
jira==3.5.1
|
||||
requests==2.31.0
|
||||
boto3==1.28.35
|
||||
pyjwt==2.8.0
|
||||
psycopg2-binary==2.9.7
|
||||
elasticsearch==8.9.0
|
||||
jira==3.5.2
|
||||
|
||||
|
||||
|
||||
fastapi==0.97.0
|
||||
uvicorn[standard]==0.22.0
|
||||
fastapi==0.103.0
|
||||
uvicorn[standard]==0.23.2
|
||||
python-decouple==3.8
|
||||
pydantic[email]==1.10.8
|
||||
apscheduler==3.10.1
|
||||
pydantic[email]==2.3.0
|
||||
apscheduler==3.10.4
|
||||
|
||||
clickhouse-driver==0.2.6
|
||||
clickhouse-driver[lz4]==0.2.6
|
||||
python-multipart==0.0.5
|
||||
azure-storage-blob==12.16.0
|
||||
python-multipart==0.0.6
|
||||
azure-storage-blob==12.17.0
|
||||
|
|
@ -1,18 +1,19 @@
|
|||
requests==2.31.0
|
||||
# Keep this version to not have conflicts between requests and boto3
|
||||
urllib3==1.26.16
|
||||
boto3==1.26.148
|
||||
pyjwt==2.7.0
|
||||
psycopg2-binary==2.9.6
|
||||
elasticsearch==8.8.0
|
||||
jira==3.5.1
|
||||
requests==2.31.0
|
||||
boto3==1.28.35
|
||||
pyjwt==2.8.0
|
||||
psycopg2-binary==2.9.7
|
||||
elasticsearch==8.9.0
|
||||
jira==3.5.2
|
||||
|
||||
|
||||
fastapi==0.97.0
|
||||
|
||||
fastapi==0.103.0
|
||||
python-decouple==3.8
|
||||
pydantic[email]==1.10.8
|
||||
apscheduler==3.10.1
|
||||
pydantic[email]==2.3.0
|
||||
apscheduler==3.10.4
|
||||
|
||||
clickhouse-driver==0.2.5
|
||||
clickhouse-driver[lz4]==0.2.5
|
||||
redis==4.5.5
|
||||
azure-storage-blob==12.16.0
|
||||
clickhouse-driver[lz4]==0.2.6
|
||||
redis==5.0.0
|
||||
azure-storage-blob==12.17.0
|
||||
|
|
|
|||
|
|
@ -1,21 +1,21 @@
|
|||
requests==2.31.0
|
||||
# Keep this version to not have conflicts between requests and boto3
|
||||
urllib3==1.26.16
|
||||
boto3==1.26.148
|
||||
pyjwt==2.7.0
|
||||
psycopg2-binary==2.9.6
|
||||
elasticsearch==8.8.0
|
||||
jira==3.5.1
|
||||
requests==2.31.0
|
||||
boto3==1.28.40
|
||||
pyjwt==2.8.0
|
||||
psycopg2-binary==2.9.7
|
||||
elasticsearch==8.9.0
|
||||
jira==3.5.2
|
||||
|
||||
|
||||
|
||||
fastapi==0.97.0
|
||||
uvicorn[standard]==0.22.0
|
||||
gunicorn==20.1.0
|
||||
fastapi==0.103.1
|
||||
uvicorn[standard]==0.23.2
|
||||
gunicorn==21.2.0
|
||||
python-decouple==3.8
|
||||
pydantic[email]==1.10.8
|
||||
apscheduler==3.10.1
|
||||
pydantic[email]==2.3.0
|
||||
apscheduler==3.10.4
|
||||
|
||||
clickhouse-driver==0.2.6
|
||||
clickhouse-driver[lz4]==0.2.6
|
||||
# TODO: enable after xmlsec fix https://github.com/xmlsec/python-xmlsec/issues/252
|
||||
#--no-binary is used to avoid libxml2 library version incompatibilities between xmlsec and lxml
|
||||
|
|
@ -23,6 +23,6 @@ clickhouse-driver[lz4]==0.2.6
|
|||
python3-saml==1.15.0
|
||||
python-multipart==0.0.6
|
||||
|
||||
redis==4.5.5
|
||||
redis==5.0.0
|
||||
#confluent-kafka==2.1.0
|
||||
azure-storage-blob==12.16.0
|
||||
azure-storage-blob==12.17.0
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ from fastapi import HTTPException, status
|
|||
from starlette.responses import RedirectResponse, FileResponse
|
||||
|
||||
import schemas
|
||||
import schemas_ee
|
||||
from chalicelib.core import sessions, assist, heatmaps, sessions_favorite, sessions_assignments, errors, errors_viewed, \
|
||||
errors_favorite, sessions_notes, click_maps, sessions_replay, signup, feature_flags
|
||||
from chalicelib.core import sessions_viewed
|
||||
|
|
@ -18,9 +17,11 @@ from chalicelib.utils import captcha
|
|||
from chalicelib.utils import helper
|
||||
from chalicelib.utils.TimeUTC import TimeUTC
|
||||
from or_dependencies import OR_context, OR_scope
|
||||
from routers import saml
|
||||
from routers.base import get_routers
|
||||
from schemas_ee import Permissions
|
||||
from schemas import Permissions, ServicePermissions
|
||||
|
||||
if config("ENABLE_SSO", cast=bool, default=True):
|
||||
from routers import saml
|
||||
|
||||
public_app, app, app_apikey = get_routers()
|
||||
|
||||
|
|
@ -49,7 +50,7 @@ def login_user(data: schemas.UserLoginSchema = Body(...)):
|
|||
detail="Invalid captcha."
|
||||
)
|
||||
|
||||
r = users.authenticate(data.email, data.password)
|
||||
r = users.authenticate(data.email, data.password.get_secret_value())
|
||||
if r is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
|
|
@ -132,9 +133,9 @@ def edit_slack_integration(integrationId: int, data: schemas.EditCollaborationSc
|
|||
|
||||
|
||||
@app.post('/client/members', tags=["client"])
|
||||
def add_member(background_tasks: BackgroundTasks, data: schemas_ee.CreateMemberSchema = Body(...),
|
||||
def add_member(background_tasks: BackgroundTasks, data: schemas.CreateMemberSchema = Body(...),
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
return users.create_member(tenant_id=context.tenant_id, user_id=context.user_id, data=data.dict(),
|
||||
return users.create_member(tenant_id=context.tenant_id, user_id=context.user_id, data=data,
|
||||
background_tasks=background_tasks)
|
||||
|
||||
|
||||
|
|
@ -167,11 +168,12 @@ def change_password_by_invitation(data: schemas.EditPasswordByInvitationSchema =
|
|||
if user["expiredChange"]:
|
||||
return {"errors": ["expired change, please re-use the invitation link"]}
|
||||
|
||||
return users.set_password_invitation(new_password=data.password, user_id=user["userId"], tenant_id=user["tenantId"])
|
||||
return users.set_password_invitation(new_password=data.password.get_secret_value(), user_id=user["userId"],
|
||||
tenant_id=user["tenantId"])
|
||||
|
||||
|
||||
@app.put('/client/members/{memberId}', tags=["client"])
|
||||
def edit_member(memberId: int, data: schemas_ee.EditMemberSchema,
|
||||
def edit_member(memberId: int, data: schemas.EditMemberSchema,
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
return users.edit_member(tenant_id=context.tenant_id, editor_id=context.user_id, changes=data,
|
||||
user_id_to_update=memberId)
|
||||
|
|
@ -203,11 +205,13 @@ def get_projects(context: schemas.CurrentContext = Depends(OR_context)):
|
|||
|
||||
# for backward compatibility
|
||||
@app.get('/{projectId}/sessions/{sessionId}', tags=["sessions", "replay"],
|
||||
dependencies=[OR_scope(Permissions.session_replay)])
|
||||
dependencies=[OR_scope(Permissions.session_replay, ServicePermissions.session_replay)])
|
||||
def get_session(projectId: int, sessionId: Union[int, str], background_tasks: BackgroundTasks,
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
if isinstance(sessionId, str):
|
||||
if not sessionId.isnumeric():
|
||||
return {"errors": ["session not found"]}
|
||||
else:
|
||||
sessionId = int(sessionId)
|
||||
data = sessions_replay.get_by_id2_pg(project_id=projectId, session_id=sessionId, full_data=True,
|
||||
include_fav_viewed=True, group_metadata=True, context=context)
|
||||
if data is None:
|
||||
|
|
@ -220,12 +224,30 @@ def get_session(projectId: int, sessionId: Union[int, str], background_tasks: Ba
|
|||
}
|
||||
|
||||
|
||||
@app.post('/{projectId}/sessions/search', tags=["sessions"],
|
||||
dependencies=[OR_scope(Permissions.session_replay)])
|
||||
def sessions_search(projectId: int, data: schemas.SessionsSearchPayloadSchema = Body(...),
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
data = sessions.search_sessions(data=data, project_id=projectId, user_id=context.user_id)
|
||||
return {'data': data}
|
||||
|
||||
|
||||
@app.post('/{projectId}/sessions/search/ids', tags=["sessions"],
|
||||
dependencies=[OR_scope(Permissions.session_replay)])
|
||||
def session_ids_search(projectId: int, data: schemas.SessionsSearchPayloadSchema = Body(...),
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
data = sessions.search_sessions(data=data, project_id=projectId, user_id=context.user_id, ids_only=True)
|
||||
return {'data': data}
|
||||
|
||||
|
||||
@app.get('/{projectId}/sessions/{sessionId}/replay', tags=["sessions", "replay"],
|
||||
dependencies=[OR_scope(Permissions.session_replay)])
|
||||
dependencies=[OR_scope(Permissions.session_replay, ServicePermissions.session_replay)])
|
||||
def get_session_events(projectId: int, sessionId: Union[int, str], background_tasks: BackgroundTasks,
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
if isinstance(sessionId, str):
|
||||
if not sessionId.isnumeric():
|
||||
return {"errors": ["session not found"]}
|
||||
else:
|
||||
sessionId = int(sessionId)
|
||||
data = sessions_replay.get_replay(project_id=projectId, session_id=sessionId, full_data=True,
|
||||
include_fav_viewed=True, group_metadata=True, context=context)
|
||||
if data is None:
|
||||
|
|
@ -239,11 +261,13 @@ def get_session_events(projectId: int, sessionId: Union[int, str], background_ta
|
|||
|
||||
|
||||
@app.get('/{projectId}/sessions/{sessionId}/events', tags=["sessions", "replay"],
|
||||
dependencies=[OR_scope(Permissions.session_replay)])
|
||||
dependencies=[OR_scope(Permissions.session_replay, ServicePermissions.session_replay)])
|
||||
def get_session_events(projectId: int, sessionId: Union[int, str],
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
if isinstance(sessionId, str):
|
||||
if not sessionId.isnumeric():
|
||||
return {"errors": ["session not found"]}
|
||||
else:
|
||||
sessionId = int(sessionId)
|
||||
data = sessions_replay.get_events(project_id=projectId, session_id=sessionId)
|
||||
if data is None:
|
||||
return {"errors": ["session not found"]}
|
||||
|
|
@ -265,18 +289,6 @@ def get_error_trace(projectId: int, sessionId: int, errorId: str,
|
|||
}
|
||||
|
||||
|
||||
@app.post('/{projectId}/errors/search', tags=['errors'], dependencies=[OR_scope(Permissions.dev_tools)])
|
||||
def errors_search(projectId: int, data: schemas.SearchErrorsSchema = Body(...),
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
return {"data": errors.search(data, projectId, user_id=context.user_id)}
|
||||
|
||||
|
||||
@app.get('/{projectId}/errors/stats', tags=['errors'], dependencies=[OR_scope(Permissions.dev_tools)])
|
||||
def errors_stats(projectId: int, startTimestamp: int, endTimestamp: int,
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
return errors.stats(projectId, user_id=context.user_id, startTimestamp=startTimestamp, endTimestamp=endTimestamp)
|
||||
|
||||
|
||||
@app.get('/{projectId}/errors/{errorId}', tags=['errors'], dependencies=[OR_scope(Permissions.dev_tools)])
|
||||
def errors_get_details(projectId: int, errorId: str, background_tasks: BackgroundTasks, density24: int = 24,
|
||||
density30: int = 30, context: schemas.CurrentContext = Depends(OR_context)):
|
||||
|
|
@ -288,15 +300,6 @@ def errors_get_details(projectId: int, errorId: str, background_tasks: Backgroun
|
|||
return data
|
||||
|
||||
|
||||
@app.get('/{projectId}/errors/{errorId}/stats', tags=['errors'], dependencies=[OR_scope(Permissions.dev_tools)])
|
||||
def errors_get_details_right_column(projectId: int, errorId: str, startDate: int = TimeUTC.now(-7),
|
||||
endDate: int = TimeUTC.now(), density: int = 7,
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
data = errors.get_details_chart(project_id=projectId, user_id=context.user_id, error_id=errorId,
|
||||
**{"startDate": startDate, "endDate": endDate, "density": density})
|
||||
return data
|
||||
|
||||
|
||||
@app.get('/{projectId}/errors/{errorId}/sourcemaps', tags=['errors'], dependencies=[OR_scope(Permissions.dev_tools)])
|
||||
def errors_get_details_sourcemaps(projectId: int, errorId: str,
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
|
|
@ -326,9 +329,10 @@ def add_remove_favorite_error(projectId: int, errorId: str, action: str, startDa
|
|||
return {"errors": ["undefined action"]}
|
||||
|
||||
|
||||
@app.get('/{projectId}/assist/sessions/{sessionId}', tags=["assist"], dependencies=[OR_scope(Permissions.assist_live)])
|
||||
@app.get('/{projectId}/assist/sessions/{sessionId}', tags=["assist"],
|
||||
dependencies=[OR_scope(Permissions.assist_live, ServicePermissions.assist_live)])
|
||||
def get_live_session(projectId: int, sessionId: str, background_tasks: BackgroundTasks,
|
||||
context: schemas_ee.CurrentContext = Depends(OR_context)):
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
data = assist.get_live_session_by_id(project_id=projectId, session_id=sessionId)
|
||||
if data is None:
|
||||
data = sessions_replay.get_replay(context=context, project_id=projectId, session_id=sessionId,
|
||||
|
|
@ -342,13 +346,15 @@ def get_live_session(projectId: int, sessionId: str, background_tasks: Backgroun
|
|||
|
||||
|
||||
@app.get('/{projectId}/unprocessed/{sessionId}/dom.mob', tags=["assist"],
|
||||
dependencies=[OR_scope(Permissions.assist_live, Permissions.session_replay)])
|
||||
dependencies=[OR_scope(Permissions.assist_live, Permissions.session_replay,
|
||||
ServicePermissions.assist_live, ServicePermissions.session_replay)])
|
||||
def get_live_session_replay_file(projectId: int, sessionId: Union[int, str],
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
not_found = {"errors": ["Replay file not found"]}
|
||||
if isinstance(sessionId, str):
|
||||
print(f"{sessionId} not a valid number.")
|
||||
if not sessionId.isnumeric():
|
||||
return not_found
|
||||
else:
|
||||
sessionId = int(sessionId)
|
||||
if not sessions.session_exists(project_id=projectId, session_id=sessionId):
|
||||
print(f"{projectId}/{sessionId} not found in DB.")
|
||||
if not assist.session_exists(project_id=projectId, session_id=sessionId):
|
||||
|
|
@ -363,13 +369,16 @@ def get_live_session_replay_file(projectId: int, sessionId: Union[int, str],
|
|||
|
||||
|
||||
@app.get('/{projectId}/unprocessed/{sessionId}/devtools.mob', tags=["assist"],
|
||||
dependencies=[OR_scope(Permissions.assist_live, Permissions.session_replay, Permissions.dev_tools)])
|
||||
dependencies=[OR_scope(Permissions.assist_live, Permissions.session_replay, Permissions.dev_tools,
|
||||
ServicePermissions.assist_live, ServicePermissions.session_replay,
|
||||
ServicePermissions.dev_tools)])
|
||||
def get_live_session_devtools_file(projectId: int, sessionId: Union[int, str],
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
not_found = {"errors": ["Devtools file not found"]}
|
||||
if isinstance(sessionId, str):
|
||||
print(f"{sessionId} not a valid number.")
|
||||
if not sessionId.isnumeric():
|
||||
return not_found
|
||||
else:
|
||||
sessionId = int(sessionId)
|
||||
if not sessions.session_exists(project_id=projectId, session_id=sessionId):
|
||||
print(f"{projectId}/{sessionId} not found in DB.")
|
||||
if not assist.session_exists(project_id=projectId, session_id=sessionId):
|
||||
|
|
@ -392,7 +401,7 @@ def get_heatmaps_by_url(projectId: int, data: schemas.GetHeatmapPayloadSchema =
|
|||
@app.get('/{projectId}/sessions/{sessionId}/favorite', tags=["sessions"],
|
||||
dependencies=[OR_scope(Permissions.session_replay)])
|
||||
def add_remove_favorite_session2(projectId: int, sessionId: int,
|
||||
context: schemas_ee.CurrentContext = Depends(OR_context)):
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
return sessions_favorite.favorite_session(context=context, project_id=projectId, session_id=sessionId)
|
||||
|
||||
|
||||
|
|
@ -511,7 +520,7 @@ def get_all_notes(projectId: int, data: schemas.SearchNoteSchema = Body(...),
|
|||
|
||||
|
||||
@app.post('/{projectId}/click_maps/search', tags=["click maps"], dependencies=[OR_scope(Permissions.session_replay)])
|
||||
def click_map_search(projectId: int, data: schemas.FlatClickMapSessionsSearch = Body(...),
|
||||
def click_map_search(projectId: int, data: schemas.ClickMapSessionsSearch = Body(...),
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
return {"data": click_maps.search_short_session(user_id=context.user_id, data=data, project_id=projectId)}
|
||||
|
||||
|
|
@ -547,7 +556,7 @@ def update_feature_flag(project_id: int, feature_flag_id: int, data: schemas.Fea
|
|||
@app.delete('/{project_id}/feature-flags/{feature_flag_id}', tags=["feature flags"],
|
||||
dependencies=[OR_scope(Permissions.feature_flags)])
|
||||
def delete_feature_flag(project_id: int, feature_flag_id: int, _=Body(None)):
|
||||
return feature_flags.delete_feature_flag(project_id=project_id, feature_flag_id=feature_flag_id)
|
||||
return {"data": feature_flags.delete_feature_flag(project_id=project_id, feature_flag_id=feature_flag_id)}
|
||||
|
||||
|
||||
@app.post('/{project_id}/feature-flags/{feature_flag_id}/status', tags=["feature flags"],
|
||||
|
|
|
|||
|
|
@ -7,14 +7,14 @@ unlock.check()
|
|||
|
||||
from or_dependencies import OR_context
|
||||
from routers.base import get_routers
|
||||
import schemas_ee
|
||||
import schemas
|
||||
from fastapi import Depends, Body
|
||||
|
||||
public_app, app, app_apikey = get_routers()
|
||||
|
||||
|
||||
@app.get('/client/roles', tags=["client", "roles"])
|
||||
def get_roles(context: schemas_ee.CurrentContext = Depends(OR_context)):
|
||||
def get_roles(context: schemas.CurrentContext = Depends(OR_context)):
|
||||
return {
|
||||
'data': roles.get_roles(tenant_id=context.tenant_id)
|
||||
}
|
||||
|
|
@ -22,8 +22,8 @@ def get_roles(context: schemas_ee.CurrentContext = Depends(OR_context)):
|
|||
|
||||
@app.post('/client/roles', tags=["client", "roles"])
|
||||
@app.put('/client/roles', tags=["client", "roles"])
|
||||
def add_role(data: schemas_ee.RolePayloadSchema = Body(...),
|
||||
context: schemas_ee.CurrentContext = Depends(OR_context)):
|
||||
def add_role(data: schemas.RolePayloadSchema = Body(...),
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
data = roles.create(tenant_id=context.tenant_id, user_id=context.user_id, data=data)
|
||||
if "errors" in data:
|
||||
return data
|
||||
|
|
@ -35,8 +35,8 @@ def add_role(data: schemas_ee.RolePayloadSchema = Body(...),
|
|||
|
||||
@app.post('/client/roles/{roleId}', tags=["client", "roles"])
|
||||
@app.put('/client/roles/{roleId}', tags=["client", "roles"])
|
||||
def edit_role(roleId: int, data: schemas_ee.RolePayloadSchema = Body(...),
|
||||
context: schemas_ee.CurrentContext = Depends(OR_context)):
|
||||
def edit_role(roleId: int, data: schemas.RolePayloadSchema = Body(...),
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
data = roles.update(tenant_id=context.tenant_id, user_id=context.user_id, role_id=roleId, data=data)
|
||||
if "errors" in data:
|
||||
return data
|
||||
|
|
@ -47,7 +47,7 @@ def edit_role(roleId: int, data: schemas_ee.RolePayloadSchema = Body(...),
|
|||
|
||||
|
||||
@app.delete('/client/roles/{roleId}', tags=["client", "roles"])
|
||||
def delete_role(roleId: int, _=Body(None), context: schemas_ee.CurrentContext = Depends(OR_context)):
|
||||
def delete_role(roleId: int, _=Body(None), context: schemas.CurrentContext = Depends(OR_context)):
|
||||
data = roles.delete(tenant_id=context.tenant_id, user_id=context.user_id, role_id=roleId)
|
||||
if "errors" in data:
|
||||
return data
|
||||
|
|
@ -63,48 +63,48 @@ def get_assist_credentials():
|
|||
|
||||
|
||||
@app.post('/trails', tags=["traces", "trails"])
|
||||
def get_trails(data: schemas_ee.TrailSearchPayloadSchema = Body(...),
|
||||
context: schemas_ee.CurrentContext = Depends(OR_context)):
|
||||
def get_trails(data: schemas.TrailSearchPayloadSchema = Body(...),
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
return {
|
||||
'data': traces.get_all(tenant_id=context.tenant_id, data=data)
|
||||
}
|
||||
|
||||
|
||||
@app.post('/trails/actions', tags=["traces", "trails"])
|
||||
def get_available_trail_actions(context: schemas_ee.CurrentContext = Depends(OR_context)):
|
||||
def get_available_trail_actions(context: schemas.CurrentContext = Depends(OR_context)):
|
||||
return {'data': traces.get_available_actions(tenant_id=context.tenant_id)}
|
||||
|
||||
|
||||
@app.put('/{projectId}/assist/save', tags=["assist"])
|
||||
def sign_record_for_upload(projectId: int, data: schemas_ee.AssistRecordPayloadSchema = Body(...),
|
||||
context: schemas_ee.CurrentContext = Depends(OR_context)):
|
||||
def sign_record_for_upload(projectId: int, data: schemas.AssistRecordPayloadSchema = Body(...),
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
if not sessions.session_exists(project_id=projectId, session_id=data.session_id):
|
||||
return {"errors": ["Session not found"]}
|
||||
return {"data": assist_records.presign_record(project_id=projectId, data=data, context=context)}
|
||||
|
||||
|
||||
@app.put('/{projectId}/assist/save/done', tags=["assist"])
|
||||
def save_record_after_upload(projectId: int, data: schemas_ee.AssistRecordSavePayloadSchema = Body(...),
|
||||
context: schemas_ee.CurrentContext = Depends(OR_context)):
|
||||
def save_record_after_upload(projectId: int, data: schemas.AssistRecordSavePayloadSchema = Body(...),
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
if not sessions.session_exists(project_id=projectId, session_id=data.session_id):
|
||||
return {"errors": ["Session not found"]}
|
||||
return {"data": {"URL": assist_records.save_record(project_id=projectId, data=data, context=context)}}
|
||||
|
||||
|
||||
@app.post('/{projectId}/assist/records', tags=["assist"])
|
||||
def search_records(projectId: int, data: schemas_ee.AssistRecordSearchPayloadSchema = Body(...),
|
||||
context: schemas_ee.CurrentContext = Depends(OR_context)):
|
||||
def search_records(projectId: int, data: schemas.AssistRecordSearchPayloadSchema = Body(...),
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
return {"data": assist_records.search_records(project_id=projectId, data=data, context=context)}
|
||||
|
||||
|
||||
@app.get('/{projectId}/assist/records/{recordId}', tags=["assist"])
|
||||
def get_record(projectId: int, recordId: int, context: schemas_ee.CurrentContext = Depends(OR_context)):
|
||||
def get_record(projectId: int, recordId: int, context: schemas.CurrentContext = Depends(OR_context)):
|
||||
return {"data": assist_records.get_record(project_id=projectId, record_id=recordId, context=context)}
|
||||
|
||||
|
||||
@app.post('/{projectId}/assist/records/{recordId}', tags=["assist"])
|
||||
def update_record(projectId: int, recordId: int, data: schemas_ee.AssistRecordUpdatePayloadSchema = Body(...),
|
||||
context: schemas_ee.CurrentContext = Depends(OR_context)):
|
||||
def update_record(projectId: int, recordId: int, data: schemas.AssistRecordUpdatePayloadSchema = Body(...),
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
result = assist_records.update_record(project_id=projectId, record_id=recordId, data=data, context=context)
|
||||
if "errors" in result:
|
||||
return result
|
||||
|
|
@ -113,7 +113,7 @@ def update_record(projectId: int, recordId: int, data: schemas_ee.AssistRecordUp
|
|||
|
||||
@app.delete('/{projectId}/assist/records/{recordId}', tags=["assist"])
|
||||
def delete_record(projectId: int, recordId: int, _=Body(None),
|
||||
context: schemas_ee.CurrentContext = Depends(OR_context)):
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
result = assist_records.delete_record(project_id=projectId, record_id=recordId, context=context)
|
||||
if "errors" in result:
|
||||
return result
|
||||
|
|
@ -121,8 +121,8 @@ def delete_record(projectId: int, recordId: int, _=Body(None),
|
|||
|
||||
|
||||
@app.post('/{projectId}/signals', tags=['signals'])
|
||||
def send_interactions(projectId: int, data: schemas_ee.SignalsSchema = Body(...),
|
||||
context: schemas_ee.CurrentContext = Depends(OR_context)):
|
||||
def send_interactions(projectId: int, data: schemas.SignalsSchema = Body(...),
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
data = signals.handle_frontend_signals_queued(project_id=projectId, user_id=context.user_id, data=data)
|
||||
|
||||
if "errors" in data:
|
||||
|
|
@ -132,6 +132,6 @@ def send_interactions(projectId: int, data: schemas_ee.SignalsSchema = Body(...)
|
|||
|
||||
@app.post('/{projectId}/dashboard/insights', tags=["insights"])
|
||||
@app.post('/{projectId}/dashboard/insights', tags=["insights"])
|
||||
def sessions_search(projectId: int, data: schemas_ee.GetInsightsSchema = Body(...),
|
||||
context: schemas_ee.CurrentContext = Depends(OR_context)):
|
||||
def sessions_search(projectId: int, data: schemas.GetInsightsSchema = Body(...),
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
return {'data': sessions_insights.fetch_selected(data=data, project_id=projectId)}
|
||||
|
|
|
|||
|
|
@ -4,9 +4,8 @@ import schemas
|
|||
from chalicelib.core import product_analytics
|
||||
from or_dependencies import OR_scope
|
||||
from routers.base import get_routers
|
||||
from schemas_ee import Permissions
|
||||
|
||||
public_app, app, app_apikey = get_routers([OR_scope(Permissions.metrics)])
|
||||
public_app, app, app_apikey = get_routers([OR_scope(schemas.Permissions.metrics)])
|
||||
|
||||
|
||||
@app.post('/{projectId}/insights/journey', tags=["insights"])
|
||||
|
|
@ -14,7 +13,6 @@ public_app, app, app_apikey = get_routers([OR_scope(Permissions.metrics)])
|
|||
async def get_insights_journey(projectId: int, data: schemas.PathAnalysisSchema = Body(...)):
|
||||
return {"data": product_analytics.path_analysis(project_id=projectId, **data.dict())}
|
||||
|
||||
|
||||
# @app.post('/{projectId}/insights/users_acquisition', tags=["insights"])
|
||||
# @app.get('/{projectId}/insights/users_acquisition', tags=["insights"])
|
||||
# async def get_users_acquisition(projectId: int, data: schemas.MetricPayloadSchema = Body(...)):
|
||||
|
|
@ -81,7 +79,6 @@ async def get_insights_journey(projectId: int, data: schemas.PathAnalysisSchema
|
|||
# return {"data": product_analytics.users_slipping(project_id=projectId, **data.dict())}
|
||||
|
||||
|
||||
|
||||
#
|
||||
#
|
||||
# @app.route('/{projectId}/dashboard/{widget}/search', methods=['GET'])
|
||||
|
|
|
|||
|
|
@ -3,17 +3,15 @@ from typing import Union
|
|||
from fastapi import Body, Depends, Request
|
||||
|
||||
import schemas
|
||||
import schemas_ee
|
||||
from chalicelib.core import dashboards, custom_metrics, funnels
|
||||
from or_dependencies import OR_context, OR_scope
|
||||
from routers.base import get_routers
|
||||
from schemas_ee import Permissions
|
||||
|
||||
public_app, app, app_apikey = get_routers([OR_scope(Permissions.metrics)])
|
||||
public_app, app, app_apikey = get_routers([OR_scope(schemas.Permissions.metrics)])
|
||||
|
||||
|
||||
@app.post('/{projectId}/dashboards', tags=["dashboard"])
|
||||
@app.put('/{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)):
|
||||
return dashboards.create_dashboard(project_id=projectId, user_id=context.user_id, data=data)
|
||||
|
|
@ -32,7 +30,7 @@ def get_dashboard(projectId: int, dashboardId: int, context: schemas.CurrentCont
|
|||
return {"data": data}
|
||||
|
||||
|
||||
@app.post('/{projectId}/dashboards/{dashboardId}', tags=["dashboard"])
|
||||
# @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)):
|
||||
|
|
@ -52,8 +50,8 @@ def pin_dashboard(projectId: int, dashboardId: int, context: schemas.CurrentCont
|
|||
|
||||
|
||||
@app.post('/{projectId}/dashboards/{dashboardId}/cards', tags=["cards"])
|
||||
@app.post('/{projectId}/dashboards/{dashboardId}/widgets', tags=["dashboard"])
|
||||
@app.put('/{projectId}/dashboards/{dashboardId}/widgets', tags=["dashboard"])
|
||||
# @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)):
|
||||
|
|
@ -62,16 +60,16 @@ def add_card_to_dashboard(projectId: int, dashboardId: int,
|
|||
|
||||
|
||||
@app.post('/{projectId}/dashboards/{dashboardId}/metrics', tags=["dashboard"])
|
||||
@app.put('/{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.CardSchema = Body(...),
|
||||
data: schemas.CardSchema = 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"])
|
||||
# @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)):
|
||||
|
|
@ -98,18 +96,18 @@ def remove_widget_from_dashboard(projectId: int, dashboardId: int, widgetId: int
|
|||
|
||||
|
||||
@app.post('/{projectId}/cards/try', tags=["cards"])
|
||||
@app.post('/{projectId}/metrics/try', tags=["dashboard"])
|
||||
@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.CardSchema = Body(...),
|
||||
# @app.post('/{projectId}/metrics/try', tags=["dashboard"])
|
||||
# @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.CardSchema = 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"])
|
||||
# @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)):
|
||||
data = custom_metrics.try_sessions(project_id=projectId, user_id=context.user_id, data=data)
|
||||
|
|
@ -117,48 +115,50 @@ 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"])
|
||||
# @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)):
|
||||
if len(data.series) == 0:
|
||||
return {"data": []}
|
||||
data.series[0].filter.startDate = data.startTimestamp
|
||||
data.series[0].filter.endDate = data.endTimestamp
|
||||
data.series[0].filter.startTimestamp = data.startTimestamp
|
||||
data.series[0].filter.endTimestamp = data.endTimestamp
|
||||
data = funnels.get_issues_on_the_fly_widget(project_id=projectId, data=data.series[0].filter)
|
||||
return {"data": data}
|
||||
|
||||
|
||||
@app.get('/{projectId}/cards', tags=["cards"])
|
||||
@app.get('/{projectId}/metrics', tags=["dashboard"])
|
||||
@app.get('/{projectId}/custom_metrics', tags=["customMetrics"])
|
||||
# @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": custom_metrics.get_all(project_id=projectId, user_id=context.user_id)}
|
||||
|
||||
|
||||
@app.post('/{projectId}/cards', tags=["cards"])
|
||||
@app.post('/{projectId}/metrics', tags=["dashboard"])
|
||||
@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.CardSchema = Body(...),
|
||||
# @app.post('/{projectId}/metrics', tags=["dashboard"])
|
||||
# @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.CardSchema = Body(...),
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
return custom_metrics.create(project_id=projectId, user_id=context.user_id, data=data)
|
||||
return custom_metrics.create_card(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"])
|
||||
# @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)):
|
||||
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"])
|
||||
# @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)):
|
||||
if not isinstance(metric_id, int):
|
||||
if metric_id.isnumeric():
|
||||
metric_id = int(metric_id)
|
||||
else:
|
||||
return {"errors": ["invalid card_id"]}
|
||||
data = custom_metrics.get_card(project_id=projectId, user_id=context.user_id, metric_id=metric_id)
|
||||
if data is None:
|
||||
|
|
@ -175,8 +175,8 @@ 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"])
|
||||
# @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)):
|
||||
|
|
@ -187,13 +187,15 @@ 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"])
|
||||
# @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)):
|
||||
if not isinstance(metric_id, int):
|
||||
return {"errors": [f"invalid card_id: {metric_id}"]}
|
||||
if metric_id.isnumeric():
|
||||
metric_id = int(metric_id)
|
||||
else:
|
||||
return {"errors": ["invalid card_id"]}
|
||||
|
||||
data = custom_metrics.get_funnel_issues(project_id=projectId, user_id=context.user_id, metric_id=metric_id,
|
||||
data=data)
|
||||
|
|
@ -203,8 +205,8 @@ 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"])
|
||||
# @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)):
|
||||
|
|
@ -216,22 +218,22 @@ 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"])
|
||||
# @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)):
|
||||
data = custom_metrics.get_errors_list(project_id=projectId, user_id=context.user_id, metric_id=metric_id,
|
||||
data=data)
|
||||
data = custom_metrics.get_errors_list(project_id=projectId, user_id=context.user_id,
|
||||
metric_id=metric_id, data=data)
|
||||
if data is None:
|
||||
return {"errors": ["custom metric not found"]}
|
||||
return {"data": data}
|
||||
|
||||
|
||||
@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(...),
|
||||
# @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.CardSessionsSchema = 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)
|
||||
|
|
@ -239,25 +241,25 @@ def get_card_chart(projectId: int, metric_id: int, request: Request, data: schem
|
|||
|
||||
|
||||
@app.post('/{projectId}/cards/{metric_id}', tags=["dashboard"])
|
||||
@app.post('/{projectId}/metrics/{metric_id}', tags=["dashboard"])
|
||||
@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(...),
|
||||
# @app.post('/{projectId}/metrics/{metric_id}', tags=["dashboard"])
|
||||
# @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.CardSchema = 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)
|
||||
data = custom_metrics.update_card(project_id=projectId, user_id=context.user_id, metric_id=metric_id, data=data)
|
||||
if data is None:
|
||||
return {"errors": ["custom metric not found"]}
|
||||
return {"data": data}
|
||||
|
||||
|
||||
@app.post('/{projectId}/cards/{metric_id}/status', tags=["dashboard"])
|
||||
@app.post('/{projectId}/metrics/{metric_id}/status', tags=["dashboard"])
|
||||
@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"])
|
||||
# @app.post('/{projectId}/metrics/{metric_id}/status', tags=["dashboard"])
|
||||
# @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(...),
|
||||
data: schemas.UpdateCardStatusSchema = 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,
|
||||
|
|
@ -265,8 +267,8 @@ 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"])
|
||||
# @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, _=Body(None),
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
return {"data": custom_metrics.delete(project_id=projectId, user_id=context.user_id, metric_id=metric_id)}
|
||||
return {"data": custom_metrics.delete_card(project_id=projectId, user_id=context.user_id, metric_id=metric_id)}
|
||||
|
|
|
|||
3
ee/api/schemas/__init__.py
Normal file
3
ee/api/schemas/__init__.py
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
from .schemas import *
|
||||
from .schemas_ee import *
|
||||
from . import overrides as _overrides
|
||||
|
|
@ -1,11 +1,12 @@
|
|||
from enum import Enum
|
||||
from typing import Optional, List, Union, Literal
|
||||
|
||||
from pydantic import BaseModel, Field, EmailStr
|
||||
from pydantic import root_validator, validator
|
||||
from pydantic import Field, EmailStr, field_validator, model_validator
|
||||
|
||||
import schemas
|
||||
from . import schemas
|
||||
from chalicelib.utils.TimeUTC import TimeUTC
|
||||
from .overrides import BaseModel, Enum
|
||||
from .overrides import transform_email, remove_whitespace, remove_duplicate_values, \
|
||||
single_to_list, ORUnion
|
||||
|
||||
|
||||
class Permissions(str, Enum):
|
||||
|
|
@ -18,8 +19,16 @@ class Permissions(str, Enum):
|
|||
feature_flags = "FEATURE_FLAGS"
|
||||
|
||||
|
||||
class ServicePermissions(str, Enum):
|
||||
session_replay = "SERVICE_SESSION_REPLAY"
|
||||
dev_tools = "SERVICE_DEV_TOOLS"
|
||||
assist_live = "SERVICE_ASSIST_LIVE"
|
||||
assist_call = "SERVICE_ASSIST_CALL"
|
||||
|
||||
|
||||
class CurrentContext(schemas.CurrentContext):
|
||||
permissions: List[Optional[Permissions]] = Field(...)
|
||||
permissions: List[Union[Permissions, ServicePermissions]] = Field(...)
|
||||
service_account: bool = Field(default=False)
|
||||
|
||||
|
||||
class RolePayloadSchema(BaseModel):
|
||||
|
|
@ -28,10 +37,7 @@ class RolePayloadSchema(BaseModel):
|
|||
permissions: List[Permissions] = Field(...)
|
||||
all_projects: bool = Field(default=True)
|
||||
projects: List[int] = Field(default=[])
|
||||
_transform_name = validator('name', pre=True, allow_reuse=True)(schemas.remove_whitespace)
|
||||
|
||||
class Config:
|
||||
alias_generator = schemas.attribute_to_camel_case
|
||||
_transform_name = field_validator('name', mode="before")(remove_whitespace)
|
||||
|
||||
|
||||
class SignalsSchema(BaseModel):
|
||||
|
|
@ -55,9 +61,6 @@ class GetInsightsSchema(schemas._TimedSchema):
|
|||
metricValue: List[InsightCategories] = Field(default=[])
|
||||
series: List[schemas.CardSeriesSchema] = Field(default=[])
|
||||
|
||||
class Config:
|
||||
alias_generator = schemas.attribute_to_camel_case
|
||||
|
||||
|
||||
class CreateMemberSchema(schemas.CreateMemberSchema):
|
||||
roleId: Optional[int] = Field(None)
|
||||
|
|
@ -79,7 +82,7 @@ class TrailSearchPayloadSchema(schemas._PaginatedSchema):
|
|||
action: Optional[str] = Field(default=None)
|
||||
order: schemas.SortOrderType = Field(default=schemas.SortOrderType.desc)
|
||||
|
||||
@root_validator(pre=True)
|
||||
@model_validator(mode="before")
|
||||
def transform_order(cls, values):
|
||||
if values.get("order") is None:
|
||||
values["order"] = schemas.SortOrderType.desc
|
||||
|
|
@ -87,9 +90,6 @@ class TrailSearchPayloadSchema(schemas._PaginatedSchema):
|
|||
values["order"] = values["order"].upper()
|
||||
return values
|
||||
|
||||
class Config:
|
||||
alias_generator = schemas.attribute_to_camel_case
|
||||
|
||||
|
||||
class SessionModel(BaseModel):
|
||||
viewed: bool = Field(default=False)
|
||||
|
|
@ -119,16 +119,13 @@ class SessionModel(BaseModel):
|
|||
|
||||
class AssistRecordUpdatePayloadSchema(BaseModel):
|
||||
name: str = Field(..., min_length=1)
|
||||
_transform_name = validator('name', pre=True, allow_reuse=True)(schemas.remove_whitespace)
|
||||
_transform_name = field_validator('name', mode="before")(remove_whitespace)
|
||||
|
||||
|
||||
class AssistRecordPayloadSchema(AssistRecordUpdatePayloadSchema):
|
||||
duration: int = Field(...)
|
||||
session_id: int = Field(...)
|
||||
|
||||
class Config:
|
||||
alias_generator = schemas.attribute_to_camel_case
|
||||
|
||||
|
||||
class AssistRecordSavePayloadSchema(AssistRecordPayloadSchema):
|
||||
key: str = Field(...)
|
||||
|
|
@ -139,41 +136,14 @@ class AssistRecordSearchPayloadSchema(schemas._PaginatedSchema, schemas._TimedSc
|
|||
query: Optional[str] = Field(default=None)
|
||||
order: Literal["asc", "desc"] = Field(default="desc")
|
||||
|
||||
class Config:
|
||||
alias_generator = schemas.attribute_to_camel_case
|
||||
|
||||
|
||||
# TODO: move these to schema when Insights is supported on PG
|
||||
class MetricOfInsights(str, Enum):
|
||||
issue_categories = "issueCategories"
|
||||
class CardInsights(schemas.CardInsights):
|
||||
metric_value: List[InsightCategories] = Field(default=[])
|
||||
|
||||
|
||||
class CardSchema(schemas.CardSchema):
|
||||
metric_of: Union[schemas.MetricOfTimeseries, schemas.MetricOfTable, \
|
||||
schemas.MetricOfErrors, schemas.MetricOfPerformance, \
|
||||
schemas.MetricOfResources, schemas.MetricOfWebVitals, \
|
||||
schemas.MetricOfClickMap, MetricOfInsights] = Field(default=schemas.MetricOfTable.user_id)
|
||||
metric_value: List[Union[schemas.IssueType, InsightCategories]] = Field(default=[])
|
||||
|
||||
@root_validator
|
||||
@model_validator(mode='after')
|
||||
def restrictions(cls, values):
|
||||
return values
|
||||
|
||||
@root_validator
|
||||
def validator(cls, values):
|
||||
values = super().validator(values)
|
||||
if values.get("metric_type") == schemas.MetricType.insights:
|
||||
assert values.get("view_type") == schemas.MetricOtherViewType.list_chart, \
|
||||
f"viewType must be 'list' for metricOf:{values.get('metric_of')}"
|
||||
assert isinstance(values.get("metric_of"), MetricOfInsights), \
|
||||
f"metricOf must be of type {MetricOfInsights} for metricType:{schemas.MetricType.insights}"
|
||||
if values.get("metric_value") is not None and len(values.get("metric_value")) > 0:
|
||||
for i in values.get("metric_value"):
|
||||
assert isinstance(i, InsightCategories), \
|
||||
f"metricValue should be of type [InsightCategories] for metricType:{schemas.MetricType.insights}"
|
||||
|
||||
return values
|
||||
|
||||
|
||||
class UpdateCardSchema(CardSchema):
|
||||
series: List[schemas.CardUpdateSeriesSchema] = Field(...)
|
||||
CardSchema = ORUnion(Union[schemas.__cards_union_base, CardInsights], discriminator='metric_type')
|
||||
|
|
@ -18,13 +18,13 @@
|
|||
},
|
||||
"homepage": "https://github.com/openreplay/openreplay#readme",
|
||||
"dependencies": {
|
||||
"@maxmind/geoip2-node": "^3.5.0",
|
||||
"@socket.io/redis-adapter": "^8.1.0",
|
||||
"@maxmind/geoip2-node": "^4.2.0",
|
||||
"@socket.io/redis-adapter": "^8.2.1",
|
||||
"express": "^4.18.2",
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
"redis": "^4.6.6",
|
||||
"socket.io": "^4.6.1",
|
||||
"jsonwebtoken": "^9.0.1",
|
||||
"redis": "^4.6.7",
|
||||
"socket.io": "^4.7.2",
|
||||
"ua-parser-js": "^1.0.35",
|
||||
"uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.23.0"
|
||||
"uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.31.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
CREATE OR REPLACE FUNCTION openreplay_version AS() -> 'v1.15.0-ee';
|
||||
|
||||
ALTER TABLE experimental.events
|
||||
ADD COLUMN IF NOT EXISTS transfer_size Nullable(UInt32);
|
||||
|
||||
ALTER TABLE experimental.sessions
|
||||
ADD COLUMN IF NOT EXISTS timezone LowCardinality(Nullable(String));
|
||||
|
|
@ -79,6 +79,7 @@ CREATE TABLE IF NOT EXISTS experimental.events
|
|||
issue_id Nullable(String),
|
||||
error_tags_keys Array(String),
|
||||
error_tags_values Array(Nullable(String)),
|
||||
transfer_size Nullable(UInt32),
|
||||
message_id UInt64 DEFAULT 0,
|
||||
_timestamp DateTime DEFAULT now()
|
||||
) ENGINE = ReplacingMergeTree(_timestamp)
|
||||
|
|
@ -131,6 +132,7 @@ CREATE TABLE IF NOT EXISTS experimental.sessions
|
|||
user_state LowCardinality(String),
|
||||
platform Enum8('web'=1,'ios'=2,'android'=3) DEFAULT 'web',
|
||||
datetime DateTime,
|
||||
timezone LowCardinality(Nullable(String)),
|
||||
duration UInt32,
|
||||
pages_count UInt16,
|
||||
events_count UInt16,
|
||||
|
|
@ -273,6 +275,7 @@ SELECT session_id,
|
|||
issue_id,
|
||||
error_tags_keys,
|
||||
error_tags_values,
|
||||
transfer_size,
|
||||
message_id,
|
||||
_timestamp
|
||||
FROM experimental.events
|
||||
|
|
|
|||
|
|
@ -71,6 +71,14 @@ UPDATE public.roles
|
|||
SET permissions = (SELECT array_agg(distinct e) FROM unnest(permissions || '{FEATURE_FLAGS}') AS e)
|
||||
where not permissions @> '{FEATURE_FLAGS}';
|
||||
|
||||
ALTER TYPE public.user_role ADD VALUE IF NOT EXISTS 'service';
|
||||
|
||||
ALTER TABLE IF EXISTS public.users
|
||||
ADD COLUMN IF NOT EXISTS service_account bool NOT NULL DEFAULT FALSE;
|
||||
|
||||
ALTER TABLE IF EXISTS public.roles
|
||||
ADD COLUMN IF NOT EXISTS service_role bool NOT NULL DEFAULT FALSE;
|
||||
|
||||
COMMIT;
|
||||
|
||||
\elif :is_next
|
||||
|
|
|
|||
36
ee/scripts/schema/db/init_dbs/postgresql/1.15.0/1.15.0.sql
Normal file
36
ee/scripts/schema/db/init_dbs/postgresql/1.15.0/1.15.0.sql
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
\set previous_version 'v1.14.0-ee'
|
||||
\set next_version 'v1.15.0-ee'
|
||||
SELECT openreplay_version() AS current_version,
|
||||
openreplay_version() = :'previous_version' AS valid_previous,
|
||||
openreplay_version() = :'next_version' AS is_next
|
||||
\gset
|
||||
|
||||
\if :valid_previous
|
||||
\echo valid previous DB version :'previous_version', starting DB upgrade to :'next_version'
|
||||
BEGIN;
|
||||
SELECT format($fn_def$
|
||||
CREATE OR REPLACE FUNCTION openreplay_version()
|
||||
RETURNS text AS
|
||||
$$
|
||||
SELECT '%1$s'
|
||||
$$ LANGUAGE sql IMMUTABLE;
|
||||
$fn_def$, :'next_version')
|
||||
\gexec
|
||||
|
||||
--
|
||||
ALTER TABLE IF EXISTS events_common.requests
|
||||
ADD COLUMN transfer_size bigint NULL;
|
||||
|
||||
ALTER TABLE IF EXISTS public.sessions
|
||||
ADD COLUMN IF NOT EXISTS timezone text NULL;
|
||||
|
||||
ALTER TABLE IF EXISTS public.projects
|
||||
ADD COLUMN IF NOT EXISTS platform public.platform NOT NULL DEFAULT 'web';
|
||||
|
||||
COMMIT;
|
||||
|
||||
\elif :is_next
|
||||
\echo new version detected :'next_version', nothing to do
|
||||
\else
|
||||
\warn skipping DB upgrade of :'next_version', expected previous version :'previous_version', found :'current_version'
|
||||
\endif
|
||||
|
|
@ -8,7 +8,7 @@ CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
|||
CREATE OR REPLACE FUNCTION openreplay_version()
|
||||
RETURNS text AS
|
||||
$$
|
||||
SELECT 'v1.14.0-ee'
|
||||
SELECT 'v1.15.0-ee'
|
||||
$$ LANGUAGE sql IMMUTABLE;
|
||||
|
||||
|
||||
|
|
@ -107,6 +107,7 @@ $$
|
|||
('dashboards'),
|
||||
('dashboard_widgets'),
|
||||
('errors'),
|
||||
('errors_tags'),
|
||||
('integrations'),
|
||||
('issues'),
|
||||
('jira_cloud'),
|
||||
|
|
@ -172,32 +173,34 @@ $$
|
|||
protected bool NOT NULL DEFAULT FALSE,
|
||||
all_projects bool NOT NULL DEFAULT TRUE,
|
||||
created_at timestamp NOT NULL DEFAULT timezone('utc'::text, now()),
|
||||
deleted_at timestamp NULL DEFAULT NULL
|
||||
deleted_at timestamp NULL DEFAULT NULL,
|
||||
service_role bool NOT NULL DEFAULT FALSE
|
||||
);
|
||||
|
||||
IF NOT EXISTS(SELECT *
|
||||
FROM pg_type typ
|
||||
WHERE typ.typname = 'user_role') THEN
|
||||
CREATE TYPE user_role AS ENUM ('owner','admin','member');
|
||||
CREATE TYPE user_role AS ENUM ('owner','admin','member','service');
|
||||
END IF;
|
||||
|
||||
|
||||
CREATE TABLE IF NOT EXISTS users
|
||||
(
|
||||
user_id integer generated BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
tenant_id integer NOT NULL REFERENCES tenants (tenant_id) ON DELETE CASCADE,
|
||||
email text NOT NULL UNIQUE,
|
||||
role user_role NOT NULL DEFAULT 'member',
|
||||
name text NOT NULL,
|
||||
created_at timestamp without time zone NOT NULL DEFAULT (now() at time zone 'utc'),
|
||||
deleted_at timestamp without time zone NULL DEFAULT NULL,
|
||||
api_key text UNIQUE DEFAULT generate_api_key(20) NOT NULL,
|
||||
jwt_iat timestamp without time zone NULL DEFAULT NULL,
|
||||
data jsonb NOT NULL DEFAULT'{}'::jsonb,
|
||||
weekly_report boolean NOT NULL DEFAULT TRUE,
|
||||
origin text NULL DEFAULT NULL,
|
||||
role_id integer REFERENCES roles (role_id) ON DELETE SET NULL,
|
||||
internal_id text NULL DEFAULT NULL
|
||||
user_id integer generated BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
tenant_id integer NOT NULL REFERENCES tenants (tenant_id) ON DELETE CASCADE,
|
||||
email text NOT NULL UNIQUE,
|
||||
role user_role NOT NULL DEFAULT 'member',
|
||||
name text NOT NULL,
|
||||
created_at timestamp without time zone NOT NULL DEFAULT (now() at time zone 'utc'),
|
||||
deleted_at timestamp without time zone NULL DEFAULT NULL,
|
||||
api_key text UNIQUE DEFAULT generate_api_key(20) NOT NULL,
|
||||
jwt_iat timestamp without time zone NULL DEFAULT NULL,
|
||||
data jsonb NOT NULL DEFAULT'{}'::jsonb,
|
||||
weekly_report boolean NOT NULL DEFAULT TRUE,
|
||||
origin text NULL DEFAULT NULL,
|
||||
role_id integer REFERENCES roles (role_id) ON DELETE SET NULL,
|
||||
internal_id text NULL DEFAULT NULL,
|
||||
service_account bool NOT NULL DEFAULT FALSE
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS users_tenant_id_deleted_at_N_idx ON users (tenant_id) WHERE deleted_at ISNULL;
|
||||
CREATE INDEX IF NOT EXISTS users_name_gin_idx ON users USING GIN (name gin_trgm_ops);
|
||||
|
|
@ -230,12 +233,19 @@ $$
|
|||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS oauth_authentication_unique_user_id_provider_idx ON oauth_authentication (user_id, provider);
|
||||
|
||||
IF NOT EXISTS(SELECT *
|
||||
FROM pg_type typ
|
||||
WHERE typ.typname = 'platform') THEN
|
||||
CREATE TYPE platform AS ENUM ('web','ios','android');
|
||||
END IF;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS projects
|
||||
(
|
||||
project_id integer generated BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
project_key varchar(20) NOT NULL UNIQUE DEFAULT generate_api_key(20),
|
||||
tenant_id integer NOT NULL REFERENCES tenants (tenant_id) ON DELETE CASCADE,
|
||||
name text NOT NULL,
|
||||
platform platform NOT NULL DEFAULT 'web',
|
||||
active boolean NOT NULL,
|
||||
sample_rate smallint NOT NULL DEFAULT 100 CHECK (sample_rate >= 0 AND sample_rate <= 100),
|
||||
created_at timestamp without time zone NOT NULL DEFAULT (now() at time zone 'utc'),
|
||||
|
|
@ -487,11 +497,20 @@ $$
|
|||
CREATE INDEX IF NOT EXISTS user_viewed_errors_user_id_idx ON public.user_viewed_errors (user_id);
|
||||
CREATE INDEX IF NOT EXISTS user_viewed_errors_error_id_idx ON public.user_viewed_errors (error_id);
|
||||
|
||||
IF NOT EXISTS(SELECT *
|
||||
FROM pg_type typ
|
||||
WHERE typ.typname = 'platform') THEN
|
||||
CREATE TYPE platform AS ENUM ('web','ios','android');
|
||||
END IF;
|
||||
CREATE TABLE IF NOT EXISTS errors_tags
|
||||
(
|
||||
key text NOT NULL,
|
||||
value text NOT NULL,
|
||||
created_at timestamp without time zone NOT NULL default (now() at time zone 'utc'),
|
||||
error_id text NOT NULL REFERENCES errors (error_id) ON DELETE CASCADE,
|
||||
session_id bigint NOT NULL,
|
||||
message_id bigint NOT NULL,
|
||||
FOREIGN KEY (session_id, message_id) REFERENCES events.errors (session_id, message_id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS errors_tags_error_id_idx ON errors_tags (error_id);
|
||||
CREATE INDEX IF NOT EXISTS errors_tags_session_id_idx ON errors_tags (session_id);
|
||||
CREATE INDEX IF NOT EXISTS errors_tags_message_id_idx ON errors_tags (message_id);
|
||||
|
||||
IF NOT EXISTS(SELECT *
|
||||
FROM pg_type typ
|
||||
|
|
@ -512,6 +531,7 @@ $$
|
|||
project_id integer NOT NULL REFERENCES projects (project_id) ON DELETE CASCADE,
|
||||
tracker_version text NOT NULL,
|
||||
start_ts bigint NOT NULL,
|
||||
timezone text NULL,
|
||||
duration integer NULL,
|
||||
rev_id text DEFAULT NULL,
|
||||
platform platform NOT NULL DEFAULT 'web',
|
||||
|
|
@ -1086,21 +1106,6 @@ $$
|
|||
CREATE INDEX IF NOT EXISTS errors_error_id_timestamp_session_id_idx ON events.errors (error_id, timestamp, session_id);
|
||||
CREATE INDEX IF NOT EXISTS errors_error_id_idx ON events.errors (error_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS errors_tags
|
||||
(
|
||||
key text NOT NULL,
|
||||
value text NOT NULL,
|
||||
created_at timestamp without time zone NOT NULL default (now() at time zone 'utc'),
|
||||
error_id text NOT NULL REFERENCES errors (error_id) ON DELETE CASCADE,
|
||||
session_id bigint NOT NULL,
|
||||
message_id bigint NOT NULL,
|
||||
FOREIGN KEY (session_id, message_id) REFERENCES events.errors (session_id, message_id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS errors_tags_error_id_idx ON errors_tags (error_id);
|
||||
CREATE INDEX IF NOT EXISTS errors_tags_session_id_idx ON errors_tags (session_id);
|
||||
CREATE INDEX IF NOT EXISTS errors_tags_message_id_idx ON errors_tags (message_id);
|
||||
|
||||
IF NOT EXISTS(SELECT *
|
||||
FROM pg_type typ
|
||||
WHERE typ.typname = 'http_method') THEN
|
||||
|
|
@ -1280,6 +1285,7 @@ $$
|
|||
host text NULL,
|
||||
path text NULL,
|
||||
query text NULL,
|
||||
transfer_size bigint NULL,
|
||||
PRIMARY KEY (session_id, timestamp, seq_index)
|
||||
);
|
||||
|
||||
|
|
|
|||
36
scripts/schema/db/init_dbs/postgresql/1.15.0/1.15.0.sql
Normal file
36
scripts/schema/db/init_dbs/postgresql/1.15.0/1.15.0.sql
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
\set previous_version 'v1.14.0'
|
||||
\set next_version 'v1.15.0'
|
||||
SELECT openreplay_version() AS current_version,
|
||||
openreplay_version() = :'previous_version' AS valid_previous,
|
||||
openreplay_version() = :'next_version' AS is_next
|
||||
\gset
|
||||
|
||||
\if :valid_previous
|
||||
\echo valid previous DB version :'previous_version', starting DB upgrade to :'next_version'
|
||||
BEGIN;
|
||||
SELECT format($fn_def$
|
||||
CREATE OR REPLACE FUNCTION openreplay_version()
|
||||
RETURNS text AS
|
||||
$$
|
||||
SELECT '%1$s'
|
||||
$$ LANGUAGE sql IMMUTABLE;
|
||||
$fn_def$, :'next_version')
|
||||
\gexec
|
||||
|
||||
--
|
||||
ALTER TABLE IF EXISTS events_common.requests
|
||||
ADD COLUMN transfer_size bigint NULL;
|
||||
|
||||
ALTER TABLE IF EXISTS public.sessions
|
||||
ADD COLUMN IF NOT EXISTS timezone text NULL;
|
||||
|
||||
ALTER TABLE IF EXISTS public.projects
|
||||
ADD COLUMN IF NOT EXISTS platform public.platform NOT NULL DEFAULT 'web';
|
||||
|
||||
COMMIT;
|
||||
|
||||
\elif :is_next
|
||||
\echo new version detected :'next_version', nothing to do
|
||||
\else
|
||||
\warn skipping DB upgrade of :'next_version', expected previous version :'previous_version', found :'current_version'
|
||||
\endif
|
||||
|
|
@ -8,7 +8,7 @@ CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
|||
CREATE OR REPLACE FUNCTION openreplay_version()
|
||||
RETURNS text AS
|
||||
$$
|
||||
SELECT 'v1.14.0'
|
||||
SELECT 'v1.15.0'
|
||||
$$ LANGUAGE sql IMMUTABLE;
|
||||
|
||||
|
||||
|
|
@ -163,6 +163,7 @@ $$
|
|||
);
|
||||
CREATE UNIQUE INDEX oauth_authentication_unique_user_id_provider_idx ON oauth_authentication (user_id, provider);
|
||||
|
||||
CREATE TYPE platform AS ENUM ('web','ios','android');
|
||||
|
||||
CREATE TABLE projects
|
||||
(
|
||||
|
|
@ -171,6 +172,7 @@ $$
|
|||
name text NOT NULL,
|
||||
active boolean NOT NULL,
|
||||
sample_rate smallint NOT NULL DEFAULT 100 CHECK (sample_rate >= 0 AND sample_rate <= 100),
|
||||
platform platform NOT NULL DEFAULT 'web',
|
||||
created_at timestamp without time zone NOT NULL DEFAULT (now() at time zone 'utc'),
|
||||
deleted_at timestamp without time zone NULL DEFAULT NULL,
|
||||
max_session_duration integer NOT NULL DEFAULT 7200000,
|
||||
|
|
@ -375,10 +377,23 @@ $$
|
|||
CREATE INDEX user_viewed_errors_user_id_idx ON public.user_viewed_errors (user_id);
|
||||
CREATE INDEX user_viewed_errors_error_id_idx ON public.user_viewed_errors (error_id);
|
||||
|
||||
CREATE TABLE errors_tags
|
||||
(
|
||||
key text NOT NULL,
|
||||
value text NOT NULL,
|
||||
created_at timestamp without time zone NOT NULL default (now() at time zone 'utc'),
|
||||
error_id text NOT NULL REFERENCES errors (error_id) ON DELETE CASCADE,
|
||||
session_id bigint NOT NULL,
|
||||
message_id bigint NOT NULL,
|
||||
FOREIGN KEY (session_id, message_id) REFERENCES events.errors (session_id, message_id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX errors_tags_error_id_idx ON errors_tags (error_id);
|
||||
CREATE INDEX errors_tags_session_id_idx ON errors_tags (session_id);
|
||||
CREATE INDEX errors_tags_message_id_idx ON errors_tags (message_id);
|
||||
|
||||
CREATE TYPE device_type AS ENUM ('desktop', 'tablet', 'mobile', 'other');
|
||||
CREATE TYPE country AS ENUM ('UN', 'RW', 'SO', 'YE', 'IQ', 'SA', 'IR', 'CY', 'TZ', 'SY', 'AM', 'KE', 'CD', 'DJ', 'UG', 'CF', 'SC', 'JO', 'LB', 'KW', 'OM', 'QA', 'BH', 'AE', 'IL', 'TR', 'ET', 'ER', 'EG', 'SD', 'GR', 'BI', 'EE', 'LV', 'AZ', 'LT', 'SJ', 'GE', 'MD', 'BY', 'FI', 'AX', 'UA', 'MK', 'HU', 'BG', 'AL', 'PL', 'RO', 'XK', 'ZW', 'ZM', 'KM', 'MW', 'LS', 'BW', 'MU', 'SZ', 'RE', 'ZA', 'YT', 'MZ', 'MG', 'AF', 'PK', 'BD', 'TM', 'TJ', 'LK', 'BT', 'IN', 'MV', 'IO', 'NP', 'MM', 'UZ', 'KZ', 'KG', 'TF', 'HM', 'CC', 'PW', 'VN', 'TH', 'ID', 'LA', 'TW', 'PH', 'MY', 'CN', 'HK', 'BN', 'MO', 'KH', 'KR', 'JP', 'KP', 'SG', 'CK', 'TL', 'RU', 'MN', 'AU', 'CX', 'MH', 'FM', 'PG', 'SB', 'TV', 'NR', 'VU', 'NC', 'NF', 'NZ', 'FJ', 'LY', 'CM', 'SN', 'CG', 'PT', 'LR', 'CI', 'GH', 'GQ', 'NG', 'BF', 'TG', 'GW', 'MR', 'BJ', 'GA', 'SL', 'ST', 'GI', 'GM', 'GN', 'TD', 'NE', 'ML', 'EH', 'TN', 'ES', 'MA', 'MT', 'DZ', 'FO', 'DK', 'IS', 'GB', 'CH', 'SE', 'NL', 'AT', 'BE', 'DE', 'LU', 'IE', 'MC', 'FR', 'AD', 'LI', 'JE', 'IM', 'GG', 'SK', 'CZ', 'NO', 'VA', 'SM', 'IT', 'SI', 'ME', 'HR', 'BA', 'AO', 'NA', 'SH', 'BV', 'BB', 'CV', 'GY', 'GF', 'SR', 'PM', 'GL', 'PY', 'UY', 'BR', 'FK', 'GS', 'JM', 'DO', 'CU', 'MQ', 'BS', 'BM', 'AI', 'TT', 'KN', 'DM', 'AG', 'LC', 'TC', 'AW', 'VG', 'VC', 'MS', 'MF', 'BL', 'GP', 'GD', 'KY', 'BZ', 'SV', 'GT', 'HN', 'NI', 'CR', 'VE', 'EC', 'CO', 'PA', 'HT', 'AR', 'CL', 'BO', 'PE', 'MX', 'PF', 'PN', 'KI', 'TK', 'TO', 'WF', 'WS', 'NU', 'MP', 'GU', 'PR', 'VI', 'UM', 'AS', 'CA', 'US', 'PS', 'RS', 'AQ', 'SX', 'CW', 'BQ', 'SS','AC','AN','BU','CP','CS','CT','DD','DG','DY','EA','FQ','FX','HV','IC','JT','MI','NH','NQ','NT','PC','PU','PZ','RH','SU','TA','TP','VD','WK','YD','YU','ZR');
|
||||
CREATE TYPE platform AS ENUM ('web','ios','android');
|
||||
|
||||
CREATE TABLE sessions
|
||||
(
|
||||
|
|
@ -386,6 +401,7 @@ $$
|
|||
project_id integer NOT NULL REFERENCES projects (project_id) ON DELETE CASCADE,
|
||||
tracker_version text NOT NULL,
|
||||
start_ts bigint NOT NULL,
|
||||
timezone text NULL,
|
||||
duration integer NULL,
|
||||
rev_id text DEFAULT NULL,
|
||||
platform platform NOT NULL DEFAULT 'web',
|
||||
|
|
@ -560,6 +576,7 @@ $$
|
|||
host text NULL,
|
||||
path text NULL,
|
||||
query text NULL,
|
||||
transfer_size bigint NULL,
|
||||
PRIMARY KEY (session_id, timestamp, seq_index)
|
||||
);
|
||||
|
||||
|
|
@ -687,20 +704,6 @@ $$
|
|||
CREATE INDEX errors_error_id_timestamp_session_id_idx ON events.errors (error_id, timestamp, session_id);
|
||||
CREATE INDEX errors_error_id_idx ON events.errors (error_id);
|
||||
|
||||
CREATE TABLE errors_tags
|
||||
(
|
||||
key text NOT NULL,
|
||||
value text NOT NULL,
|
||||
created_at timestamp without time zone NOT NULL default (now() at time zone 'utc'),
|
||||
error_id text NOT NULL REFERENCES errors (error_id) ON DELETE CASCADE,
|
||||
session_id bigint NOT NULL,
|
||||
message_id bigint NOT NULL,
|
||||
FOREIGN KEY (session_id, message_id) REFERENCES events.errors (session_id, message_id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX errors_tags_error_id_idx ON errors_tags (error_id);
|
||||
CREATE INDEX errors_tags_session_id_idx ON errors_tags (session_id);
|
||||
CREATE INDEX errors_tags_message_id_idx ON errors_tags (message_id);
|
||||
|
||||
CREATE TABLE events.graphql
|
||||
(
|
||||
|
|
|
|||
|
|
@ -18,8 +18,8 @@
|
|||
},
|
||||
"homepage": "https://github.com/openreplay/openreplay#readme",
|
||||
"dependencies": {
|
||||
"@azure/storage-blob": "^12.14.0",
|
||||
"aws-sdk": "^2.1385.0",
|
||||
"@azure/storage-blob": "^12.15.0",
|
||||
"aws-sdk": "^2.1440.0",
|
||||
"express": "^4.18.2",
|
||||
"source-map": "^0.7.4"
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue