diff --git a/api/chalicelib/core/projects.py b/api/chalicelib/core/projects.py index 42877cf8b..dd9758102 100644 --- a/api/chalicelib/core/projects.py +++ b/api/chalicelib/core/projects.py @@ -1,5 +1,6 @@ import json -from typing import Optional +from typing import Optional, List +from collections import Counter from fastapi import HTTPException, status @@ -69,7 +70,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, - s.created_at, s.sessions_last_check_at, s.sample_rate, s.platform + s.created_at, s.sessions_last_check_at, s.sample_rate, s.platform, + (SELECT count(*) FROM jsonb_array_elements_text(s.conditions)) AS conditions_count {extra_projection} FROM public.projects AS s WHERE s.deleted_at IS NULL @@ -250,6 +252,71 @@ def update_capture_status(project_id, changes: schemas.SampleRateSchema): return changes +def get_conditions(project_id): + with pg_client.PostgresClient() as cur: + query = cur.mogrify("""SELECT sample_rate AS rate, sample_rate=100 AS capture_all, conditions + FROM public.projects + WHERE project_id =%(project_id)s + AND deleted_at ISNULL;""", + {"project_id": project_id}) + cur.execute(query=query) + row = cur.fetchone() + row = helper.dict_to_camel_case(row) + if row["conditions"] is None: + row["conditions"] = [] + else: + row["conditions"] = [schemas.ProjectConditions(**c) for c in row["conditions"]] + + return row + + +def validate_conditions(conditions: List[schemas.ProjectConditions]) -> List[str]: + errors = [] + names = [condition.name for condition in conditions] + + # Check for empty strings + if any(name.strip() == "" for name in names): + errors.append("Condition names cannot be empty strings") + + # Check for duplicates + name_counts = Counter(names) + duplicates = [name for name, count in name_counts.items() if count > 1] + if duplicates: + errors.append(f"Duplicate condition names found: {duplicates}") + + return errors + + +def update_conditions(project_id, changes: schemas.ProjectSettings): + sample_rate = changes.rate + if changes.capture_all: + sample_rate = 100 + + validation_errors = validate_conditions(changes.conditions) + if validation_errors: + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=validation_errors) + + conditions = [] + for condition in changes.conditions: + conditions.append(condition.model_dump()) + + with pg_client.PostgresClient() as cur: + query = cur.mogrify("""UPDATE public.projects + SET + sample_rate= %(sample_rate)s, + conditions = %(conditions)s::jsonb + WHERE project_id =%(project_id)s + AND deleted_at ISNULL;""", + { + "project_id": project_id, + "sample_rate": sample_rate, + "conditions": json.dumps(conditions) + }) + cur.execute(query=query) + + return changes + + def get_projects_ids(tenant_id): with pg_client.PostgresClient() as cur: query = f"""SELECT s.project_id diff --git a/api/routers/core.py b/api/routers/core.py index 1e772038b..61f39b508 100644 --- a/api/routers/core.py +++ b/api/routers/core.py @@ -511,6 +511,17 @@ def update_capture_status(projectId: int, data: schemas.SampleRateSchema = Body( return {"data": projects.update_capture_status(project_id=projectId, changes=data)} +@app.post('/{projectId}/conditions', tags=["projects"]) +def update_conditions(projectId: int, data: schemas.ProjectSettings = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): + return {"data": projects.update_conditions(project_id=projectId, changes=data)} + + +@app.get('/{projectId}/conditions', tags=["projects"]) +def get_conditions(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): + return {"data": projects.get_conditions(project_id=projectId)} + + @app.get('/announcements', tags=["announcements"]) def get_all_announcements(context: schemas.CurrentContext = Depends(OR_context)): return {"data": announcements.get_all(user_id=context.user_id)} diff --git a/api/schemas/schemas.py b/api/schemas/schemas.py index ec0f40958..c6a6771d1 100644 --- a/api/schemas/schemas.py +++ b/api/schemas/schemas.py @@ -1323,6 +1323,18 @@ class SavedSearchSchema(BaseModel): filter: SessionsSearchPayloadSchema = Field([]) +class ProjectConditions(BaseModel): + name: str = Field(...) + capture_rate: int = Field(..., ge=0, le=100) + filters: List[GroupedFilterType] = Field(default=[]) + + +class ProjectSettings(BaseModel): + rate: int = Field(..., ge=0, le=100) + capture_all: bool = Field(default=False) + conditions: List[ProjectConditions] = Field(default=[]) + + class CreateDashboardSchema(BaseModel): name: str = Field(..., min_length=1) description: Optional[str] = Field(default='') diff --git a/scripts/schema/db/init_dbs/postgresql/1.17.0/1.17.0.sql b/scripts/schema/db/init_dbs/postgresql/1.17.0/1.17.0.sql index c6c0c4028..f8f51809e 100644 --- a/scripts/schema/db/init_dbs/postgresql/1.17.0/1.17.0.sql +++ b/scripts/schema/db/init_dbs/postgresql/1.17.0/1.17.0.sql @@ -29,6 +29,9 @@ UPDATE public.sessions SET has_ut_test= TRUE WHERE session_id IN (SELECT session_id FROM public.ut_tests_signals); +ALTER TABLE IF EXISTS public.projects + ADD COLUMN IF NOT EXISTS conditions jsonb DEFAULT NULL; + COMMIT; \elif :is_next