From 9675da07e1aa6bc837ccb2c943564e81858adc72 Mon Sep 17 00:00:00 2001 From: Taha Yassine Kraiem Date: Tue, 22 Mar 2022 17:16:08 +0100 Subject: [PATCH] feat(api): dashboards 1/5 --- api/chalicelib/core/dashboards2.py | 60 +++++++++++++++++++ api/routers/subs/dashboard.py | 27 ++++++++- api/schemas.py | 9 +++ .../db/init_dbs/postgresql/1.5.5/1.5.5.sql | 43 +++++++++++++ 4 files changed, 137 insertions(+), 2 deletions(-) create mode 100644 api/chalicelib/core/dashboards2.py create mode 100644 scripts/helm/db/init_dbs/postgresql/1.5.5/1.5.5.sql diff --git a/api/chalicelib/core/dashboards2.py b/api/chalicelib/core/dashboards2.py new file mode 100644 index 000000000..5f2bced2d --- /dev/null +++ b/api/chalicelib/core/dashboards2.py @@ -0,0 +1,60 @@ +import schemas +from chalicelib.utils import helper +from chalicelib.utils import pg_client + + +def create_dashboard(project_id, user_id, data: schemas.CreateDashboardSchema): + with pg_client.PostgresClient() as cur: + pg_query = f"""INSERT INTO dashboards(project_id, user_id, name, is_public, is_pinned) + VALUES(%(projectId)s, %(userId)s, %(name)s, %(is_public)s, %(is_pinned)s) + RETURNING *;""" + params = {"userId": user_id, "projectId": project_id, **data.dict()} + cur.execute(cur.mogrify(pg_query, params)) + row = cur.fetchone() + return helper.dict_to_camel_case(row) + + +def get_dashboards(project_id, user_id): + with pg_client.PostgresClient() as cur: + pg_query = f"""SELECT * + FROM dashboards + WHERE deleted_at ISNULL + AND project_id = %(projectId)s + AND (user_id = %(userId)s OR is_public);""" + params = {"userId": user_id, "projectId": project_id} + cur.execute(cur.mogrify(pg_query, params)) + rows = cur.fetchall() + return helper.list_to_camel_case(rows) + + +def get_dashboard(project_id, user_id, dashboard_id): + with pg_client.PostgresClient() as cur: + pg_query = """SELECT dashboards.*, all_widgets.* + FROM dashboards + LEFT JOIN LATERAL (SELECT COALESCE(ARRAY_AGG(widgets), '{}') AS widgets + FROM widgets + INNER JOIN dashboard_widgets USING (widget_id) + WHERE dashboard_widgets.dashboard_id = dashboards.dashboard_id + AND widgets.deleted_at ISNULL + AND (widgets.project_id ISNULL OR widgets.project_id = %(projectId)s) + ) AS all_widgets ON (TRUE) + WHERE dashboards.deleted_at ISNULL + AND dashboards.project_id = %(projectId)s + AND dashboard_id = %(dashboard_id)s + AND (dashboards.user_id = %(userId)s OR is_public);""" + params = {"userId": user_id, "projectId": project_id, "dashboard_id": dashboard_id} + cur.execute(cur.mogrify(pg_query, params)) + row = cur.fetchone() + return helper.dict_to_camel_case(row) + + +def get_widgets(project_id): + with pg_client.PostgresClient() as cur: + pg_query = f"""SELECT * + FROM widgets + WHERE deleted_at ISNULL + AND project_id = %(projectId)s;""" + params = {"projectId": project_id} + cur.execute(cur.mogrify(pg_query, params)) + rows = cur.fetchall() + return helper.list_to_camel_case(rows) diff --git a/api/routers/subs/dashboard.py b/api/routers/subs/dashboard.py index 169893693..1f5e827e4 100644 --- a/api/routers/subs/dashboard.py +++ b/api/routers/subs/dashboard.py @@ -1,9 +1,10 @@ -from fastapi import Body +from fastapi import Body, Depends import schemas -from chalicelib.core import dashboard +from chalicelib.core import dashboard, dashboards2 from chalicelib.core import metadata from chalicelib.utils import helper +from or_dependencies import OR_context from routers.base import get_routers public_app, app, app_apikey = get_routers() @@ -344,3 +345,25 @@ def get_dashboard_group(projectId: int, data: schemas.MetricPayloadSchema = Body *helper.explode_widget(dashboard.get_avg_cpu(project_id=projectId, **data.dict())), *helper.explode_widget(dashboard.get_avg_fps(project_id=projectId, **data.dict())), ]} + + +@app.post('/{projectId}/dashboards', tags=["dashboard", "metrics"]) +@app.put('/{projectId}/dashboards', tags=["dashboard", "metrics"]) +def create_dashboards(projectId: int, data: schemas.CreateDashboardSchema = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): + return {"data": dashboards2.create_dashboard(project_id=projectId, user_id=context.user_id, data=data)} + + +@app.get('/{projectId}/dashboards', tags=["dashboard", "metrics"]) +def get_dashboards(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): + return {"data": dashboards2.get_dashboards(project_id=projectId, user_id=context.user_id)} + + +@app.get('/{projectId}/dashboards/{dashboardId}', tags=["dashboard", "metrics"]) +def get_dashboards(projectId: int, dashboardId: int, context: schemas.CurrentContext = Depends(OR_context)): + return {"data": dashboards2.get_dashboard(project_id=projectId, user_id=context.user_id, dashboard_id=dashboardId)} + + +@app.get('/{projectId}/widgets', tags=["dashboard", "metrics"]) +def get_dashboards(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): + return {"data": dashboards2.get_widgets(project_id=projectId)} diff --git a/api/schemas.py b/api/schemas.py index 77cb78c05..61b1f4056 100644 --- a/api/schemas.py +++ b/api/schemas.py @@ -875,3 +875,12 @@ class UpdateCustomMetricsStatusSchema(BaseModel): class SavedSearchSchema(FunnelSchema): filter: FlatSessionsSearchPayloadSchema = Field([]) + + +class CreateDashboardSchema(BaseModel): + name: str = Field(...) + is_public: bool = Field(default=False) + is_pinned: bool = Field(default=False) + + class Config: + alias_generator = attribute_to_camel_case diff --git a/scripts/helm/db/init_dbs/postgresql/1.5.5/1.5.5.sql b/scripts/helm/db/init_dbs/postgresql/1.5.5/1.5.5.sql new file mode 100644 index 000000000..a8bb8aafd --- /dev/null +++ b/scripts/helm/db/init_dbs/postgresql/1.5.5/1.5.5.sql @@ -0,0 +1,43 @@ +BEGIN; +CREATE OR REPLACE FUNCTION openreplay_version() + RETURNS text AS +$$ +SELECT 'v1.5.5' +$$ LANGUAGE sql IMMUTABLE; + + +CREATE TABLE dashboards +( + dashboard_id integer generated BY DEFAULT AS IDENTITY PRIMARY KEY, + project_id integer NOT NULL REFERENCES projects (project_id) ON DELETE CASCADE, + user_id integer NOT NULL REFERENCES users (user_id) ON DELETE SET NULL, + name text NOT NULL, + is_public boolean NOT NULL DEFAULT TRUE, + is_pinned boolean NOT NULL DEFAULT FALSE, + created_at timestamp NOT NULL DEFAULT timezone('utc'::text, now()), + deleted_at timestamp NULL DEFAULT NULL +); + + +CREATE TABLE widgets +( + widget_id integer generated BY DEFAULT AS IDENTITY PRIMARY KEY, + project_id integer NOT NULL REFERENCES projects (project_id) ON DELETE CASCADE, + user_id integer NOT NULL REFERENCES users (user_id) ON DELETE SET NULL, + name text NOT NULL, + is_template boolean NOT NULL DEFAULT FALSE, + predefined boolean NOT NULL DEFAULT FALSE, + created_at timestamp NOT NULL DEFAULT timezone('utc'::text, now()), + deleted_at timestamp NULL DEFAULT NULL +); + +CREATE TABLE dashboard_widgets +( + dashboard_id integer NOT NULL REFERENCES dashboards (dashboard_id) ON DELETE CASCADE, + widget_id integer NOT NULL REFERENCES widgets (widget_id) ON DELETE CASCADE, + user_id integer NOT NULL REFERENCES users (user_id) ON DELETE SET NULL, + created_at timestamp NOT NULL DEFAULT timezone('utc'::text, now()), + configuration jsonb NOT NULL DEFAULT '{}'::jsonb +); + +COMMIT; \ No newline at end of file