From fe53ee07d7fbe9aec3140c4a9387f92cd6e43011 Mon Sep 17 00:00:00 2001 From: Taha Yassine Kraiem Date: Tue, 29 Mar 2022 18:33:41 +0200 Subject: [PATCH] feat(api): dashboards 3/5 --- api/chalicelib/core/dashboards2.py | 108 ++++++++++++++---- api/chalicelib/core/templates.py | 20 ---- api/routers/subs/metrics.py | 59 +++++++--- api/schemas.py | 5 +- .../db/init_dbs/postgresql/1.5.5/1.5.5.sql | 1 + .../db/init_dbs/postgresql/init_schema.sql | 1 + 6 files changed, 129 insertions(+), 65 deletions(-) delete mode 100644 api/chalicelib/core/templates.py diff --git a/api/chalicelib/core/dashboards2.py b/api/chalicelib/core/dashboards2.py index c36293059..215a4f9ea 100644 --- a/api/chalicelib/core/dashboards2.py +++ b/api/chalicelib/core/dashboards2.py @@ -4,6 +4,24 @@ import schemas from chalicelib.utils import helper from chalicelib.utils import pg_client +CATEGORY_DESCRIPTION = { + 'categ1': 'lorem', +} + + +def get_templates(project_id, user_id): + with pg_client.PostgresClient() as cur: + pg_query = cur.mogrify(f"""SELECT category, jsonb_agg(metrics ORDER BY name) AS widgets + FROM metrics + WHERE deleted_at IS NULL AND (project_id ISNULL OR (project_id = %(project_id)s AND (is_public OR user_id= %(userId)s))) + GROUP BY category + ORDER BY category;""", {"project_id": project_id, "userId": user_id}) + cur.execute(pg_query) + rows = cur.fetchall() + for r in rows: + r["description"] = CATEGORY_DESCRIPTION.get(r["category"], "") + return helper.list_to_camel_case(rows) + def create_dashboard(project_id, user_id, data: schemas.CreateDashboardSchema): with pg_client.PostgresClient() as cur: @@ -31,15 +49,10 @@ def get_dashboards(project_id, user_id): def get_dashboard(project_id, user_id, dashboard_id): with pg_client.PostgresClient() as cur: - pg_query = """SELECT dashboards.*, all_template_widgets.widgets AS template_widgets, all_metric_widgets.widgets AS metric_widgets + pg_query = """SELECT dashboards.*, all_metric_widgets.widgets AS widgets FROM dashboards - LEFT JOIN LATERAL (SELECT COALESCE(JSONB_AGG(templates), '[]'::jsonb) AS widgets - FROM templates - INNER JOIN dashboard_widgets USING (template_id) - WHERE dashboard_widgets.dashboard_id = dashboards.dashboard_id - ) AS all_template_widgets ON (TRUE) LEFT JOIN LATERAL (SELECT COALESCE(JSONB_AGG(raw_metrics), '[]') AS widgets - FROM (SELECT metrics.*, metric_series.series + FROM (SELECT dashboard_widgets.*, metrics.*, metric_series.series FROM metrics INNER JOIN dashboard_widgets USING (metric_id) LEFT JOIN LATERAL (SELECT JSONB_AGG(metric_series.* ORDER BY index) AS series @@ -59,35 +72,84 @@ def get_dashboard(project_id, user_id, dashboard_id): print(cur.mogrify(pg_query, params)) cur.execute(cur.mogrify(pg_query, params)) row = cur.fetchone() - row["widgets"] = row.pop("template_widgets") + row.pop("metric_widgets") + return helper.dict_to_camel_case(row) + + +def delete_dashboard(project_id, user_id, dashboard_id): + with pg_client.PostgresClient() as cur: + pg_query = """UPDATE dashboards + SET deleted_at = timezone('utc'::text, now()) + WHERE 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)) + return {"data": {"success": True}} + + +def update_dashboard(project_id, user_id, dashboard_id, data: schemas.CreateDashboardSchema): + with pg_client.PostgresClient() as cur: + pg_query = """UPDATE dashboards + SET name = %(name)s, is_pinned = %(is_pinned)s, is_public = %(is_public)s + WHERE dashboards.project_id = %(projectId)s + 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()} + cur.execute(cur.mogrify(pg_query, params)) + row = cur.fetchone() return helper.dict_to_camel_case(row) def add_widget(project_id, user_id, dashboard_id, data: schemas.AddWidgetToDashboardPayloadSchema): - ref_key = "metric_id" - if data.template_id is not None: - ref_key = "template_id" with pg_client.PostgresClient() as cur: - pg_query = f"""INSERT INTO dashboard_widgets(dashboard_id, {ref_key}, user_id, configuration, name) - VALUES (%(dashboard_id)s, %({ref_key})s, %(userId)s, %(configuration)s::jsonb, %(name)s) + pg_query = """INSERT INTO dashboard_widgets(dashboard_id, metric_id, user_id, config, name) + SELECT %(dashboard_id)s AS dashboard_id, %(metric_id)s AS metric_id, + %(userId)s AS user_id, %(config)s::jsonb AS config, %(name)s AS name + WHERE EXISTS(SELECT 1 FROM dashboards + 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)) RETURNING *;""" params = {"userId": user_id, "projectId": project_id, "dashboard_id": dashboard_id, **data.dict()} - params["configuration"] = json.dumps(params.get("configuration", {})) + params["config"] = json.dumps(data.config) cur.execute(cur.mogrify(pg_query, params)) row = cur.fetchone() return helper.dict_to_camel_case(row) -def remove_widget(project_id, user_id, dashboard_id,widget_id): - ref_key = "metric_id" - if data.template_id is not None: - ref_key = "template_id" + +def update_widget(project_id, user_id, dashboard_id, widget_id, data: schemas.AddWidgetToDashboardPayloadSchema): with pg_client.PostgresClient() as cur: - pg_query = f"""INSERT INTO dashboard_widgets(dashboard_id, {ref_key}, user_id, configuration, name) - VALUES (%(dashboard_id)s, %({ref_key})s, %(userId)s, %(configuration)s::jsonb, %(name)s) - RETURNING *;""" - params = {"userId": user_id, "projectId": project_id, "dashboard_id": dashboard_id, **data.dict()} - params["configuration"] = json.dumps(params.get("configuration", {})) + pg_query = """UPDATE dashboard_widgets + SET name= %(name)s, config= %(config)s + WHERE dashboard_id=%(dashboard_id)s AND widget_id=%(widget_id)s + RETURNINIG *;""" + params = {"userId": user_id, "projectId": project_id, "dashboard_id": dashboard_id, + "widget_id": widget_id, **data.dict()} cur.execute(cur.mogrify(pg_query, params)) row = cur.fetchone() return helper.dict_to_camel_case(row) + +def remove_widget(project_id, user_id, dashboard_id, widget_id): + with pg_client.PostgresClient() as cur: + pg_query = """DELETE FROM dashboard_widgets + WHERE dashboard_id=%(dashboard_id)s AND widget_id=%(widget_id)s;""" + params = {"userId": user_id, "projectId": project_id, "dashboard_id": dashboard_id, "widget_id": widget_id} + cur.execute(cur.mogrify(pg_query, params)) + return {"data": {"success": True}} + + +def pin_dashboard(project_id, user_id, dashboard_id): + with pg_client.PostgresClient() as cur: + pg_query = """UPDATE dashboards + SET is_pinned = FALSE + WHERE dashboard_id=%(dashboard_id)s AND project_id=%(project_id)s; + UPDATE dashboards + SET is_pinned = True + WHERE dashboard_id=%(dashboard_id)s AND project_id=%(project_id)s AND deleted_at ISNULL + RETURNING *;""" + params = {"userId": user_id, "project_id": project_id, "dashboard_id": dashboard_id} + cur.execute(cur.mogrify(pg_query, params)) + row = cur.fetchone() + return helper.dict_to_camel_case(row) diff --git a/api/chalicelib/core/templates.py b/api/chalicelib/core/templates.py deleted file mode 100644 index 8e42668d3..000000000 --- a/api/chalicelib/core/templates.py +++ /dev/null @@ -1,20 +0,0 @@ -from chalicelib.utils import helper -from chalicelib.utils import pg_client - -CATEGORY_DESCRIPTION = { - 'categ1': 'lorem', -} - - -def get_templates(project_id, user_id): - with pg_client.PostgresClient() as cur: - pg_query = cur.mogrify(f"""SELECT category, jsonb_agg(metrics ORDER BY name) AS widgets - FROM metrics - WHERE project_id ISNULL OR (project_id = %(project_id)s AND (is_public OR user_id= %(userId)s)) - GROUP BY category - ORDER BY category;""", {"project_id": project_id, "userId": user_id}) - cur.execute(pg_query) - rows = cur.fetchall() - for r in rows: - r["description"] = CATEGORY_DESCRIPTION.get(r["category"], "") - return helper.list_to_camel_case(rows) diff --git a/api/routers/subs/metrics.py b/api/routers/subs/metrics.py index 56b486977..2d3c116d3 100644 --- a/api/routers/subs/metrics.py +++ b/api/routers/subs/metrics.py @@ -1,32 +1,50 @@ from fastapi import Body, Depends import schemas -from chalicelib.core import dashboards2, templates, custom_metrics +from chalicelib.core import dashboards2, custom_metrics from or_dependencies import OR_context from routers.base import get_routers public_app, app, app_apikey = get_routers() -@app.post('/{projectId}/dashboards', tags=["dashboard", "metrics"]) -@app.put('/{projectId}/dashboards', tags=["dashboard", "metrics"]) +@app.post('/{projectId}/dashboards', tags=["dashboard"]) +@app.put('/{projectId}/dashboards', tags=["dashboard"]) def create_dashboards(projectId: int, data: schemas.CreateDashboardSchema = Body(...), context: schemas.CurrentContext = Depends(OR_context)): return {"data": dashboards2.create_dashboard(project_id=projectId, user_id=context.user_id, data=data)} -@app.get('/{projectId}/dashboards', tags=["dashboard", "metrics"]) +@app.get('/{projectId}/dashboards', tags=["dashboard"]) 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"]) +@app.get('/{projectId}/dashboards/{dashboardId}', tags=["dashboard"]) def get_dashboard(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.post('/{projectId}/dashboards/{dashboardId}/metrics', tags=["dashboard", "metrics"]) -@app.put('/{projectId}/dashboards/{dashboardId}/metrics', tags=["dashboard", "metrics"]) +@app.post('/{projectId}/dashboards/{dashboardId}', tags=["dashboard"]) +@app.put('/{projectId}/dashboards/{dashboardId}', tags=["dashboard"]) +def update_dashboard(projectId: int, dashboardId: int, data: schemas.CreateDashboardSchema = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): + return {"data": dashboards2.update_dashboard(project_id=projectId, user_id=context.user_id, + dashboard_id=dashboardId, data=data)} + + +@app.delete('/{projectId}/dashboards/{dashboardId}', tags=["dashboard"]) +def delete_dashboard(projectId: int, dashboardId: int, context: schemas.CurrentContext = Depends(OR_context)): + return dashboards2.delete_dashboard(project_id=projectId, user_id=context.user_id, dashboard_id=dashboardId) + + +@app.get('/{projectId}/dashboards/{dashboardId}/pin', tags=["dashboard"]) +def pin_dashboard(projectId: int, dashboardId: int, context: schemas.CurrentContext = Depends(OR_context)): + return {"data": dashboards2.pin_dashboard(project_id=projectId, user_id=context.user_id, dashboard_id=dashboardId)} + + +@app.post('/{projectId}/dashboards/{dashboardId}/widgets', tags=["dashboard"]) +@app.put('/{projectId}/dashboards/{dashboardId}/widgets', tags=["dashboard"]) def add_widget_to_dashboard(projectId: int, dashboardId: int, data: schemas.AddWidgetToDashboardPayloadSchema = Body(...), context: schemas.CurrentContext = Depends(OR_context)): @@ -34,22 +52,25 @@ def add_widget_to_dashboard(projectId: int, dashboardId: int, data=data)} -@app.delete('/{projectId}/dashboards/{dashboardId}/metrics/{metricId}', tags=["dashboard", "metrics"]) -def remove_widget_from_dashboard(projectId: int, dashboardId: int, metricId: int, - data: schemas.AddWidgetToDashboardPayloadSchema = Body(...), +@app.post('/{projectId}/dashboards/{dashboardId}/widgets/{widgetId}', tags=["dashboard"]) +@app.put('/{projectId}/dashboards/{dashboardId}/widgets/{widgetId}', tags=["dashboard"]) +def update_widget_in_dashboard(projectId: int, dashboardId: int, widgetId: int, + data: schemas.AddWidgetToDashboardPayloadSchema = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): + return dashboards2.update_widget(project_id=projectId, user_id=context.user_id, dashboard_id=dashboardId, + widget_id=widgetId, data=data) + + +@app.delete('/{projectId}/dashboards/{dashboardId}/widgets/{widgetId}', tags=["dashboard"]) +def remove_widget_from_dashboard(projectId: int, dashboardId: int, widgetId: int, context: schemas.CurrentContext = Depends(OR_context)): - return {"data": dashboards2.add_widget(project_id=projectId, user_id=context.user_id, dashboard_id=dashboardId, - data=data)} + return dashboards2.remove_widget(project_id=projectId, user_id=context.user_id, dashboard_id=dashboardId, + widget_id=widgetId) -# @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)} - - -@app.get('/{projectId}/metrics/templates', tags=["dashboard", "metrics"]) +@app.get('/{projectId}/metrics/templates', tags=["dashboard"]) def get_templates(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): - return {"data": templates.get_templates(project_id=projectId, user_id=context.user_id)} + return {"data": dashboards2.get_templates(project_id=projectId, user_id=context.user_id)} @app.post('/{projectId}/metrics/try', tags=["dashboard"]) diff --git a/api/schemas.py b/api/schemas.py index bb111d61b..ad9558a4a 100644 --- a/api/schemas.py +++ b/api/schemas.py @@ -878,7 +878,7 @@ class SavedSearchSchema(FunnelSchema): class CreateDashboardSchema(BaseModel): - name: str = Field(...) + name: str = Field(..., min_length=1) is_public: bool = Field(default=False) is_pinned: bool = Field(default=False) @@ -887,10 +887,9 @@ class CreateDashboardSchema(BaseModel): class AddWidgetToDashboardPayloadSchema(BaseModel): - template_id: Optional[int] = Field(default=None) metric_id: Optional[int] = Field(default=None) name: Optional[str] = Field(default=None) - configuration: dict = Field(default={}) + config: dict = Field(default={}) @root_validator def validator(cls, values): 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 index d0d31ecce..13e9e9e14 100644 --- 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 @@ -53,6 +53,7 @@ ALTER TABLE IF EXISTS metrics DROP CONSTRAINT IF EXISTS null_project_id_for_template_only; ALTER TABLE IF EXISTS metrics + ADD COLUMN IF NOT EXISTS is_pinned boolean NOT NULL DEFAULT FALSE, ADD COLUMN IF NOT EXISTS category text NULL DEFAULT 'custom', ADD COLUMN IF NOT EXISTS is_predefined boolean NOT NULL DEFAULT FALSE, ADD COLUMN IF NOT EXISTS is_template boolean NOT NULL DEFAULT FALSE, diff --git a/scripts/helm/db/init_dbs/postgresql/init_schema.sql b/scripts/helm/db/init_dbs/postgresql/init_schema.sql index 52f367e8e..35926bc96 100644 --- a/scripts/helm/db/init_dbs/postgresql/init_schema.sql +++ b/scripts/helm/db/init_dbs/postgresql/init_schema.sql @@ -953,6 +953,7 @@ $$ metric_value text[] NOT NULL DEFAULT '{}'::text[], metric_format text, category text NULL DEFAULT 'custom', + is_pinned boolean NOT NULL DEFAULT FALSE, is_predefined boolean NOT NULL DEFAULT FALSE, is_template boolean NOT NULL DEFAULT FALSE, key text NULL DEFAULT NULL,