From d42905d39485de7fde77ea55aab66e3674bb597a Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Tue, 14 Jan 2025 15:21:19 +0100 Subject: [PATCH] feat(api): session highlights (#2947) --- api/Pipfile | 8 +- .../core/sessions/sessions_notes.py | 73 ++++++++++++++----- api/schemas/schemas.py | 4 + .../db/init_dbs/postgresql/1.22.0/1.22.0.sql | 5 ++ .../db/init_dbs/postgresql/init_schema.sql | 8 +- 5 files changed, 73 insertions(+), 25 deletions(-) diff --git a/api/Pipfile b/api/Pipfile index 65a080eda..b70bb321d 100644 --- a/api/Pipfile +++ b/api/Pipfile @@ -9,18 +9,18 @@ requests = "==2.32.3" boto3 = "==1.35.86" pyjwt = "==2.10.1" psycopg2-binary = "==2.9.10" -psycopg = {extras = ["pool", "binary"], version = "==3.2.3"} -clickhouse-driver = {extras = ["lz4"], version = "==0.2.9"} clickhouse-connect = "==0.8.11" elasticsearch = "==8.17.0" jira = "==3.8.0" cachetools = "==5.5.0" fastapi = "==0.115.6" -uvicorn = {extras = ["standard"], version = "==0.34.0"} python-decouple = "==3.8" -pydantic = {extras = ["email"], version = "==2.10.4"} apscheduler = "==3.11.0" redis = "==5.2.1" +psycopg = {extras = ["binary", "pool"], version = "==3.2.3"} +clickhouse-driver = {extras = ["lz4"], version = "==0.2.9"} +uvicorn = {extras = ["standard"], version = "==0.34.0"} +pydantic = {extras = ["email"], version = "==2.10.4"} [dev-packages] diff --git a/api/chalicelib/core/sessions/sessions_notes.py b/api/chalicelib/core/sessions/sessions_notes.py index 2e42b9c1f..aaef2fab6 100644 --- a/api/chalicelib/core/sessions/sessions_notes.py +++ b/api/chalicelib/core/sessions/sessions_notes.py @@ -56,41 +56,74 @@ def get_session_notes(tenant_id, project_id, session_id, user_id): def get_all_notes_by_project_id(tenant_id, project_id, user_id, data: schemas.SearchNoteSchema): with pg_client.PostgresClient() as cur: - conditions = ["sessions_notes.project_id = %(project_id)s", "sessions_notes.deleted_at IS NULL"] - extra_params = {} - if data.tags and len(data.tags) > 0: - k = "tag_value" + # base conditions + conditions = [ + "sessions_notes.project_id = %(project_id)s", + "sessions_notes.deleted_at IS NULL" + ] + params = {"project_id": project_id, "user_id": user_id, "tenant_id": tenant_id} + + # tag conditions + if data.tags: + tag_key = "tag_value" conditions.append( - sh.multi_conditions(f"%({k})s = sessions_notes.tag", data.tags, value_key=k)) - extra_params = sh.multi_values(data.tags, value_key=k) + sh.multi_conditions(f"%({tag_key})s = sessions_notes.tag", data.tags, value_key=tag_key) + ) + params.update(sh.multi_values(data.tags, value_key=tag_key)) + + # filter by ownership or shared status if data.shared_only: conditions.append("sessions_notes.is_public") elif data.mine_only: conditions.append("sessions_notes.user_id = %(user_id)s") else: conditions.append("(sessions_notes.user_id = %(user_id)s OR sessions_notes.is_public)") - query = cur.mogrify(f"""SELECT COUNT(1) OVER () AS full_count, 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} - LIMIT {data.limit} OFFSET {data.limit * (data.page - 1)};""", - {"project_id": project_id, "user_id": user_id, "tenant_id": tenant_id, **extra_params}) + + # search condition + if data.search: + conditions.append("sessions_notes.message ILIKE %(search)s") + params["search"] = f"%{data.search}%" + + query = f""" + SELECT + COUNT(1) OVER () AS full_count, + 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} + LIMIT + %(limit)s OFFSET %(offset)s; + """ + params.update({ + "limit": data.limit, + "offset": data.limit * (data.page - 1) + }) + + query = cur.mogrify(query, params) logger.debug(query) - cur.execute(query=query) + cur.execute(query) rows = cur.fetchall() + result = {"count": 0, "notes": helper.list_to_camel_case(rows)} - if len(rows) > 0: + if rows: result["count"] = rows[0]["fullCount"] - for row in rows: - row["createdAt"] = TimeUTC.datetime_to_timestamp(row["createdAt"]) - row.pop("fullCount") + for row in rows: + row["createdAt"] = TimeUTC.datetime_to_timestamp(row["createdAt"]) + row.pop("fullCount") + return result def create(tenant_id, user_id, project_id, session_id, data: schemas.SessionNoteSchema): with pg_client.PostgresClient() as cur: - 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) + query = cur.mogrify(f"""INSERT INTO public.sessions_notes (message, user_id, tag, session_id, project_id, timestamp, is_public, thumbnail, start_at, end_at) + VALUES (%(message)s, %(user_id)s, %(tag)s, %(session_id)s, %(project_id)s, %(timestamp)s, %(is_public)s, %(thumbnail)s, %(start_at)s, %(end_at)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.model_dump()}) @@ -111,6 +144,8 @@ def edit(tenant_id, user_id, project_id, note_id, data: schemas.SessionUpdateNot sub_query.append("is_public = %(is_public)s") if data.timestamp is not None: sub_query.append("timestamp = %(timestamp)s") + + sub_query.append("updated_at = timezone('utc'::text, now())") with pg_client.PostgresClient() as cur: cur.execute( cur.mogrify(f"""UPDATE public.sessions_notes diff --git a/api/schemas/schemas.py b/api/schemas/schemas.py index 0e18e87fa..809aca8e9 100644 --- a/api/schemas/schemas.py +++ b/api/schemas/schemas.py @@ -1382,6 +1382,7 @@ class SearchNoteSchema(_PaginatedSchema): tags: Optional[List[str]] = Field(default=[]) shared_only: bool = Field(default=False) mine_only: bool = Field(default=False) + search: Optional[str] = Field(default=None) class SessionNoteSchema(BaseModel): @@ -1389,6 +1390,9 @@ class SessionNoteSchema(BaseModel): tag: Optional[str] = Field(default=None) timestamp: int = Field(default=-1) is_public: bool = Field(default=False) + thumbnail: Optional[str] = Field(default=None) + start_at: int = Field(default=None) + end_at: int = Field(default=None) class SessionUpdateNoteSchema(SessionNoteSchema): diff --git a/scripts/schema/db/init_dbs/postgresql/1.22.0/1.22.0.sql b/scripts/schema/db/init_dbs/postgresql/1.22.0/1.22.0.sql index 4419d15df..dcf474499 100644 --- a/scripts/schema/db/init_dbs/postgresql/1.22.0/1.22.0.sql +++ b/scripts/schema/db/init_dbs/postgresql/1.22.0/1.22.0.sql @@ -26,6 +26,11 @@ WHERE metrics.metric_type = 'insights'; DROP TABLE IF EXISTS public.user_favorite_errors; DROP TABLE IF EXISTS public.user_viewed_errors; +ALTER TABLE IF EXISTS public.sessions_notes + ADD COLUMN start_at integer, + ADD COLUMN end_at integer, + ADD COLUMN thumbnail text, + ADD COLUMN updated_at timestamp DEFAULT NULL; COMMIT; diff --git a/scripts/schema/db/init_dbs/postgresql/init_schema.sql b/scripts/schema/db/init_dbs/postgresql/init_schema.sql index e664dd3d0..dbcbdf594 100644 --- a/scripts/schema/db/init_dbs/postgresql/init_schema.sql +++ b/scripts/schema/db/init_dbs/postgresql/init_schema.sql @@ -897,7 +897,11 @@ CREATE TABLE public.sessions_notes session_id bigint NOT NULL REFERENCES public.sessions (session_id) ON DELETE CASCADE, project_id integer NOT NULL REFERENCES public.projects (project_id) ON DELETE CASCADE, timestamp integer NOT NULL DEFAULT -1, - is_public boolean NOT NULL DEFAULT FALSE + is_public boolean NOT NULL DEFAULT FALSE, + thumbnail text NULL, + updated_at timestamp without time zone NULL DEFAULT NULL, + start_at integer NULL, + end_at integer NULL ); CREATE TABLE public.errors_tags @@ -1211,4 +1215,4 @@ CREATE TABLE IF NOT EXISTS public.session_integrations PRIMARY KEY (session_id, project_id, provider) ); -COMMIT; \ No newline at end of file +COMMIT;