feat(analytics): read-only SQL views layer with analytics schema - #12367
Conversation
Introduces an analytics/ layer that wraps production Postgres data in safe, read-only views exposed under the analytics schema. - 14 documented query files in queries/ (one per Looker data source) covering auth activities, user activity, execution metrics, onboarding funnel, and cohort retention (login + execution, weekly + daily) - setup.sql — one-time schema creation and role/grant setup for the analytics_readonly role (auth, platform, analytics schemas) - generate_views.py — reads queries/*.sql and applies CREATE OR REPLACE VIEW analytics.<name> to the database; supports --dry-run, --only, and --db-url flags - views.sql — pre-generated combined reference output - README.md — full setup, deployment, and integration guide Looker, PostHog Data Warehouse, and Supabase MCP (for Otto) all connect to the same analytics.* views instead of raw tables.
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughAdds 14 new Postgres analytics views covering auth, execution, node/block, retention, onboarding, spending, and user activity, plus a new Python CLI utility to create/apply those views and related schema/grants. Changes
Sequence Diagram(s)sequenceDiagram
participant Dev as Developer/CI
participant FS as File system (analytics/queries/*.sql)
participant CLI as generate_views.py
participant DB as Postgres (analytics schema)
Dev->>FS: add/modify SQL view files
Dev->>CLI: run `analytics-views` (or call script)
CLI->>FS: discover .sql files (load_views)
CLI->>DB: connect(db_url)
CLI->>DB: execute SETUP_SQL (create schema/role/grants)
CLI->>DB: for each view: CREATE OR REPLACE VIEW ... (run_sql)
DB-->>CLI: success / error per statement
CLI->>DB: grant refresh to analytics_readonly
CLI->>Dev: print summary / exit
Estimated code review effort🎯 4 (Complex) | ⏱️ ~65 minutes Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
📝 Coding Plan
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…via security_invoker=false views
…n analytics-setup/analytics-views scripts
🔍 PR Overlap DetectionThis check compares your PR against all other open PRs targeting the same branch to detect potential merge conflicts early. 🟢 Low Risk — File Overlap OnlyThese PRs touch the same files but different sections (click to expand)
Summary: 0 conflict(s), 0 medium risk, 11 low risk (out of 11 PRs with file overlap) Auto-generated on push. Ignores: |
There was a problem hiding this comment.
Actionable comments posted: 9
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@autogpt_platform/analytics/generate_views.py`:
- Around line 75-80: The SQL that creates the role analytics_readonly should not
provision a LOGIN with a hardcoded password; update the CREATE ROLE statement in
the block that checks for 'analytics_readonly' so it creates the role with
NOLOGIN (remove LOGIN and PASSWORD 'CHANGE_ME') and leave enabling LOGIN and
setting a real password to the later setup step that performs credential
provisioning.
- Around line 109-116: The DB URI builder currently interpolates raw
user/password into the connection string (variables host, port, user, password,
dbname) which breaks for reserved characters; percent-encode credentials before
composing the URI—use urllib.parse.quote_plus (or urllib.parse.quote) to encode
user and password (and optionally dbname) and then return
f"postgresql://{encoded_user}:{encoded_password}@{host}:{port}/{encoded_dbname}";
after making the change run `poetry run format`.
In `@autogpt_platform/analytics/queries/graph_execution.sql`:
- Around line 96-105: The current subquery filters LibraryAgent rows with WHERE
... = TRUE before applying DISTINCT ON, causing older true flags to persist; fix
it by selecting the latest row per ("userId","agentGraphId") first (keep
DISTINCT ON ("userId","agentGraphId") with ORDER BY
"userId","agentGraphId","agentGraphVersion" DESC) and remove the WHERE that
pre-filters by sensitive_action_safe_mode so that possibly_ai is derived only
from the most recent row's
("settings"::jsonb->>'sensitive_action_safe_mode')::boolean; update the la
subquery (alias la, table "LibraryAgent", field agentGraphVersion and the
possibly_ai projection) accordingly.
- Around line 84-91: The current groupedErrorMessage expression only strips URLs
and digit-bearing tokens but still leaks emails, API keys, UUIDs and long
alphanumeric identifiers from ge."stats"::jsonb->>'error'; update the expression
to apply additional REGEXP_REPLACE passes (after the URL and digit replacements)
to: redact email addresses (e.g. \S+@\S+\.\S+), redact UUIDs (standard
8-4-4-4-12 hex pattern), redact long hexadecimal or alphanumeric tokens (e.g.
contiguous [A-Fa-f0-9]{16,} or [A-Za-z0-9_-]{12,}), and collapse repeated
redaction markers and trim length to a safe maximum; keep the final alias
groupedErrorMessage and ensure order is URL -> email -> UUID -> long hex/alnum
-> collapse/trim.
In `@autogpt_platform/analytics/queries/retention_agent.sql`:
- Line 79: The agent_names CTE currently uses MAX(g."name") which picks the
lexicographically largest historical name; instead select the name from the
deterministic latest graph version for each agent by replacing the aggregation
with a lookup of the row with the newest version (or timestamp) for that agent.
Concretely, change the agent_names CTE (referencing AgentGraph, agent_id,
agent_name) to select g."name" from the AgentGraph row with the maximum
g."version" (or max g."updated_at") per g."id"—for example via a subquery that
finds MAX(version) per id or via DISTINCT ON (g."id") ORDER BY g."version"
DESC—so agent_name always reflects the latest graph version.
In `@autogpt_platform/analytics/queries/retention_login_daily.sql`:
- Around line 39-49: The query never applies the documented 90-day cohort
window: update the params CTE to reflect 90 days (set max_days to 90) and filter
the first_login cohorts to only include cohort_day_start within that window by
joining or WHERE cohort_day_start >= (CURRENT_DATE - (params.max_days || '
days')::interval) (or equivalent using params.max_days) so the first_login CTE
only contains cohorts from the last 90 days; reference params and first_login
when making the changes.
In `@autogpt_platform/analytics/queries/user_block_spending.sql`:
- Around line 64-67: The JSONB extractions for ne."stats" used with direct
casting should use ->> (text) before casting to int; update the expressions that
produce llm_call_count, llm_retry_count, llm_input_token_count, and
llm_output_token_count to use (ne."stats"->>'llm_call_count')::int,
(ne."stats"->>'llm_retry_count')::int, (ne."stats"->>'input_token_count')::int,
and (ne."stats"->>'output_token_count')::int respectively so PostgreSQL casts
from text not JSONB.
In `@autogpt_platform/analytics/queries/user_onboarding_funnel.sql`:
- Around line 45-60: The CASE mapping for step_txt -> step_order (used in the
user_onboarding_funnel query building step_counts/funnel) misses 8
OnboardingStep enum values causing NULL step_order; either extend the CASE in
the SELECT that defines step_order to include the missing enum members
(VISIT_COPILOT, RE_RUN_AGENT, SCHEDULE_AGENT, RUN_AGENTS, RUN_3_DAYS,
TRIGGER_WEBHOOK, RUN_14_DAYS, RUN_AGENTS_100) with appropriate numeric ordering,
or add an ELSE clause (e.g., ELSE 0 or ELSE -1) to assign a default step_order
for unmapped completedSteps so they are not silently excluded from
step_counts/funnel; update any downstream logic that depends on ordering
(step_order) to handle the chosen default.
In `@autogpt_platform/analytics/queries/users_activities.sql`:
- Around line 89-101: In the user_node_runs CTE the query uses COUNT(*) which
counts the user row even when no node exists and also counts execution rows
without node records; change all counts to count the node identifier instead
(e.g., replace COUNT(*) and the filtered COUNT(*) FILTER (...) with
COUNT(n."id") and COUNT(n."id") FILTER (WHERE n."executionStatus" = '...')) so
only actual AgentNodeExecution rows are tallied (refer to user_node_runs,
n."id", and the node_execution_* columns).
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 4cb8f4b2-8425-4fcb-91b5-5d593f695910
📒 Files selected for processing (16)
autogpt_platform/analytics/.gitignoreautogpt_platform/analytics/generate_views.pyautogpt_platform/analytics/queries/auth_activities.sqlautogpt_platform/analytics/queries/graph_execution.sqlautogpt_platform/analytics/queries/node_block_execution.sqlautogpt_platform/analytics/queries/retention_agent.sqlautogpt_platform/analytics/queries/retention_execution_daily.sqlautogpt_platform/analytics/queries/retention_execution_weekly.sqlautogpt_platform/analytics/queries/retention_login_daily.sqlautogpt_platform/analytics/queries/retention_login_onboarded_weekly.sqlautogpt_platform/analytics/queries/retention_login_weekly.sqlautogpt_platform/analytics/queries/user_block_spending.sqlautogpt_platform/analytics/queries/user_onboarding.sqlautogpt_platform/analytics/queries/user_onboarding_funnel.sqlautogpt_platform/analytics/queries/user_onboarding_integration.sqlautogpt_platform/analytics/queries/users_activities.sql
📜 Review details
🧰 Additional context used
📓 Path-based instructions (1)
autogpt_platform/**/*.py
📄 CodeRabbit inference engine (AGENTS.md)
Format Python code with
poetry run format
Files:
autogpt_platform/analytics/generate_views.py
🧠 Learnings (2)
📚 Learning: 2026-02-26T21:29:27.619Z
Learnt from: CR
Repo: Significant-Gravitas/AutoGPT PR: 0
File: autogpt_platform/CLAUDE.md:0-0
Timestamp: 2026-02-26T21:29:27.619Z
Learning: Applies to autogpt_platform/**/.env* : Platform-level configuration should use `.env.default` (Supabase/shared defaults, tracked in git) and `.env` for user overrides (gitignored)
Applied to files:
autogpt_platform/analytics/.gitignore
📚 Learning: 2026-02-04T16:50:20.508Z
Learnt from: CR
Repo: Significant-Gravitas/AutoGPT PR: 0
File: autogpt_platform/backend/CLAUDE.md:0-0
Timestamp: 2026-02-04T16:50:20.508Z
Learning: Applies to autogpt_platform/backend/**/schema.prisma : Define key database models (User, AgentGraph, AgentGraphExecution, AgentNode, StoreListing) in `schema.prisma`
Applied to files:
autogpt_platform/analytics/queries/graph_execution.sql
🪛 Ruff (0.15.5)
autogpt_platform/analytics/generate_views.py
[warning] 174-174: Do not catch blind exception: Exception
(BLE001)
🔇 Additional comments (6)
autogpt_platform/analytics/.gitignore (1)
1-2: LGTM!Ignoring generated SQL artifacts (
views.sql,setup.sql) is appropriate since these are produced bygenerate_views.pyand should not be committed.autogpt_platform/analytics/queries/user_onboarding_integration.sql (1)
28-41: LGTM!The query correctly uses double-quoted identifiers (
platform."UserOnboarding",u."userId", etc.) matching the Prisma-generated table naming. The logic to explode the integrations array, filter nulls/empty strings, and aggregate distinct users per integration is sound.autogpt_platform/analytics/queries/auth_activities.sql (1)
34-40: LGTM!The query correctly extracts authentication event data from Supabase's internal audit log. The JSON field extraction using
->>and the 90-day rolling window are implemented properly.autogpt_platform/analytics/queries/user_onboarding.sql (1)
36-45: LGTM!The view correctly exposes onboarding record fields without a date filter, which is appropriate since onboarding occurs once per user. The mixed quoting style (quoted for camelCase, unquoted for lowercase) is valid PostgreSQL.
autogpt_platform/analytics/queries/retention_login_onboarded_weekly.sql (1)
41-50: LGTM!The
onboardersCTE correctly identifies users who executed an agent within the 365-day onboarding window after their first login. The EXISTS subquery efficiently checks for qualifyingAgentGraphExecutionrecords.autogpt_platform/analytics/queries/node_block_execution.sql (1)
64-101: LGTM!The query correctly:
- Uses
->>for JSONB field extraction with appropriate type casts- Joins through
AgentNodetoAgentBlockfor block metadata- Applies aggressive error scrubbing via regex to anonymize IDs/URLs for safe grouping
- Conditionally joins error output only for failed executions
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@autogpt_platform/backend/generate_views.py`:
- Around line 97-104: The connection URL construction currently interpolates
DB_USER/DB_PASS raw into the URI, which breaks when they contain reserved
characters; import urllib.parse.quote and apply quote(...) to user and password
(e.g., encoded_user = quote(user, safe='') and encoded_password =
quote(password, safe='')) before building the f"postgresql://{...}" string while
keeping the existing empty-password check and variables (host, port, user,
password, dbname) the same.
- Around line 67-71: The SQL block creates a role analytics_readonly with a
hardcoded password 'CHANGE_ME'; change the creation to avoid enabling a public
default password by either creating the role as NOLOGIN (CREATE ROLE
analytics_readonly NOLOGIN) and only enabling LOGIN with a provided password
later, or require an explicit password parameter during setup instead of
'CHANGE_ME' (remove the hardcoded PASSWORD 'CHANGE_ME' from the CREATE ROLE
statement and add a step to set a secure password via ALTER ROLE
analytics_readonly WITH LOGIN PASSWORD '<secure_password>' at setup time).
- Around line 141-145: The current build_view_sql function returns views with
"security_invoker = false", which grants definer privileges and allows
analytics_readonly to access sensitive tables; update build_view_sql to use
"security_invoker = true" in the generated SQL so views run with the caller's
privileges, and then ensure the deployment/migration that creates these views
also issues explicit "GRANT SELECT" only on the specific auth/platform tables
required (or, as an alternative remediation, modify the query_body before
building the view to remove sensitive columns like actor_id, actor_via_sso, and
credential provider). Locate the build_view_sql function and change the WITH
clause to "security_invoker = true" in the returned string, and coordinate
adding targeted GRANT SELECT statements (or scrubbing) for the affected views
(e.g., analytics.auth_activities, analytics.user_block_spending) so
analytics_readonly only receives the minimal allowed access.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 139d048b-6875-45c7-871f-c9403cf368ee
📒 Files selected for processing (2)
autogpt_platform/backend/generate_views.pyautogpt_platform/backend/pyproject.toml
📜 Review details
🧰 Additional context used
📓 Path-based instructions (3)
autogpt_platform/backend/**/*.py
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
autogpt_platform/backend/**/*.py: Use Python 3.11 (required; managed by Poetry via pyproject.toml) for backend development
Always run 'poetry run format' (Black + isort) before linting in backend development
Always run 'poetry run lint' (ruff) after formatting in backend development
Files:
autogpt_platform/backend/generate_views.py
autogpt_platform/backend/**/*.{py,txt}
📄 CodeRabbit inference engine (autogpt_platform/backend/CLAUDE.md)
Use
poetry runprefix for all Python commands, including testing, linting, formatting, and migrations
Files:
autogpt_platform/backend/generate_views.py
autogpt_platform/**/*.py
📄 CodeRabbit inference engine (AGENTS.md)
Format Python code with
poetry run format
Files:
autogpt_platform/backend/generate_views.py
🧠 Learnings (7)
📓 Common learnings
Learnt from: CR
Repo: Significant-Gravitas/AutoGPT PR: 0
File: autogpt_platform/backend/CLAUDE.md:0-0
Timestamp: 2026-02-04T16:50:20.508Z
Learning: Applies to autogpt_platform/backend/backend/**/*.py : Use Prisma ORM for database operations in PostgreSQL with pgvector for embeddings
📚 Learning: 2026-02-26T17:02:22.448Z
Learnt from: Pwuts
Repo: Significant-Gravitas/AutoGPT PR: 12211
File: .pre-commit-config.yaml:160-179
Timestamp: 2026-02-26T17:02:22.448Z
Learning: Keep the pre-commit hook pattern broad for autogpt_platform/backend to ensure OpenAPI schema changes are captured. Do not narrow to backend/api/ alone, since the generated schema depends on Pydantic models across multiple directories (backend/data/, backend/blocks/, backend/copilot/, backend/integrations/, backend/util/). Narrowing could miss schema changes and cause frontend type desynchronization.
Applied to files:
autogpt_platform/backend/generate_views.pyautogpt_platform/backend/pyproject.toml
📚 Learning: 2026-03-05T15:42:08.207Z
Learnt from: ntindle
Repo: Significant-Gravitas/AutoGPT PR: 12297
File: .claude/skills/backend-check/SKILL.md:14-16
Timestamp: 2026-03-05T15:42:08.207Z
Learning: In Python files under autogpt_platform/backend (recursively), rely on poetry run format to perform formatting (Black + isort) and linting (ruff). Do not run poetry run lint as a separate step after poetry run format, since format already includes linting checks.
Applied to files:
autogpt_platform/backend/generate_views.py
📚 Learning: 2026-02-04T16:50:20.508Z
Learnt from: CR
Repo: Significant-Gravitas/AutoGPT PR: 0
File: autogpt_platform/backend/CLAUDE.md:0-0
Timestamp: 2026-02-04T16:50:20.508Z
Learning: Applies to autogpt_platform/backend/**/*.{py,txt} : Use `poetry run` prefix for all Python commands, including testing, linting, formatting, and migrations
Applied to files:
autogpt_platform/backend/pyproject.toml
📚 Learning: 2026-02-04T16:49:42.490Z
Learnt from: CR
Repo: Significant-Gravitas/AutoGPT PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-02-04T16:49:42.490Z
Learning: Applies to autogpt_platform/backend/schema.prisma : Run database migrations with 'poetry run prisma migrate dev' and 'poetry run prisma generate' after schema changes in backend
Applied to files:
autogpt_platform/backend/pyproject.toml
📚 Learning: 2026-02-04T16:50:20.508Z
Learnt from: CR
Repo: Significant-Gravitas/AutoGPT PR: 0
File: autogpt_platform/backend/CLAUDE.md:0-0
Timestamp: 2026-02-04T16:50:20.508Z
Learning: Applies to autogpt_platform/backend/**/*_test.py : Always review snapshot changes with `git diff` before committing when updating snapshots with `poetry run pytest --snapshot-update`
Applied to files:
autogpt_platform/backend/pyproject.toml
📚 Learning: 2026-01-23T19:58:10.520Z
Learnt from: ntindle
Repo: Significant-Gravitas/AutoGPT PR: 11796
File: autogpt_platform/backend/backend/blocks/video/loop.py:80-87
Timestamp: 2026-01-23T19:58:10.520Z
Learning: Ensure MoviePy is constrained to version ^2.1.2 (2.x) in pyproject.toml files where MoviePy is declared, so the backend video processing relies on a compatible API. This should cover all relevant pyproject.toml files (e.g., autogpt_platform/backend/pyproject.toml) to maintain consistency.
Applied to files:
autogpt_platform/backend/pyproject.toml
🔇 Additional comments (1)
autogpt_platform/backend/pyproject.toml (1)
123-124: Nice CLI surfacing.These entry points match the documented analytics workflow and make the setup / refresh steps discoverable from Poetry.
The original CTEs drove all joins from user_logins, causing a O(users × executions × node_executions) fan-out that made the view too heavy for Supabase to serve. Rewrote each CTE to aggregate its own source table directly by userId, then LEFT JOIN the aggregates in the final SELECT.
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
autogpt_platform/analytics/queries/users_activities.sql (1)
117-122: Consider consistent NULL handling across all count columns.The
node_execution_*columns useCOALESCE(..., 0)(lines 123-128), butagent_count,unique_agent_runs, andagent_runsdo not. This inconsistency may confuse analytics users — a user with no agents will haveagent_count = NULLwhile a user with no node executions will havenode_execution_count = 0.For consistency, consider applying
COALESCEto the agent-related counts as well, or document the intentional distinction.ua.last_agent_save_time, - ua.agent_count, + COALESCE(ua.agent_count, 0) AS agent_count, gr.first_agent_run_time, gr.last_agent_run_time, - gr.unique_agent_runs, - gr.agent_runs, + COALESCE(gr.unique_agent_runs, 0) AS unique_agent_runs, + COALESCE(gr.agent_runs, 0) AS agent_runs,🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@autogpt_platform/analytics/queries/users_activities.sql` around lines 117 - 122, The agent-related count columns (agent_count, unique_agent_runs, agent_runs in the ua and gr SELECTs) are currently nullable while node_execution_* columns use COALESCE(..., 0); update those columns to use COALESCE(column, 0) so they return 0 instead of NULL for users with no agents (e.g., replace ua.agent_count with COALESCE(ua.agent_count, 0) and gr.unique_agent_runs/gr.agent_runs with COALESCE(..., 0)), ensuring consistent NULL handling across counts.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@autogpt_platform/analytics/queries/users_activities.sql`:
- Around line 101-106: The per-status breakdown in the query is missing counts
for the INCOMPLETE and REVIEW statuses, causing node_execution_count to not
equal the sum of node_execution_* columns; update the SELECT to add COUNT(*)
FILTER (WHERE n."executionStatus" = 'INCOMPLETE') AS node_execution_incomplete
and COUNT(*) FILTER (WHERE n."executionStatus" = 'REVIEW') AS
node_execution_review (or similarly named columns) so the AgentExecutionStatus
enum values are represented alongside the existing
node_execution_failed/completed/terminated/queued/running columns.
- Around line 74-83: In the user_agents CTE that aggregates
platform."AgentGraph", change the timestamp used for last_agent_save_time from
MAX("createdAt") to MAX("updatedAt") so the alias last_agent_save_time reflects
the graph's last modification time; update the SELECT expression in the
user_agents CTE (referencing "AgentGraph", "createdAt", "updatedAt", and the
alias last_agent_save_time) accordingly.
---
Nitpick comments:
In `@autogpt_platform/analytics/queries/users_activities.sql`:
- Around line 117-122: The agent-related count columns (agent_count,
unique_agent_runs, agent_runs in the ua and gr SELECTs) are currently nullable
while node_execution_* columns use COALESCE(..., 0); update those columns to use
COALESCE(column, 0) so they return 0 instead of NULL for users with no agents
(e.g., replace ua.agent_count with COALESCE(ua.agent_count, 0) and
gr.unique_agent_runs/gr.agent_runs with COALESCE(..., 0)), ensuring consistent
NULL handling across counts.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 60a01786-1253-4aff-b6a0-1721a0564e6b
📒 Files selected for processing (1)
autogpt_platform/analytics/queries/users_activities.sql
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
- GitHub Check: types
- GitHub Check: Seer Code Review
- GitHub Check: test (3.11)
- GitHub Check: test (3.12)
- GitHub Check: test (3.13)
- GitHub Check: Check PR Status
- GitHub Check: Analyze (python)
🔇 Additional comments (2)
autogpt_platform/analytics/queries/users_activities.sql (2)
1-60: Well-documented view header.The documentation is comprehensive with clear descriptions of source tables, output columns, performance considerations, and example queries. This will be very helpful for analytics users.
62-73: LGTM!The
user_loginsCTE correctly aggregates session data per user, usingGREATESTto capture the most recent activity regardless of whether it was a new login or a token refresh.
- user_block_spending: use ->> instead of -> for JSONB field extraction before casting to int (avoids runtime cast errors) - generate_views: create analytics_readonly as NOLOGIN to avoid a usable role with a known default password - generate_views: percent-encode DB credentials in the URI builder so passwords with reserved chars (@, :, /) connect correctly - graph_execution: remove WHERE filter on sensitive_action_safe_mode before DISTINCT ON so the latest LibraryAgent version always wins (fixes possibly_ai being sticky once any version had the flag set) - retention_agent: use DISTINCT ON ordered by version DESC instead of MAX(name) so renamed agents resolve to their latest name - retention_login_daily: add 90-day cohort_start filter to first_login CTE so the view matches its documented window - user_onboarding_funnel: map the 8 missing OnboardingStep enum values (VISIT_COPILOT, RE_RUN_AGENT, SCHEDULE_AGENT, RUN_AGENTS, RUN_3_DAYS, TRIGGER_WEBHOOK, RUN_14_DAYS, RUN_AGENTS_100) to step_order 15-22 - users_activities: use updatedAt instead of createdAt for last_agent_save_time; add node_execution_incomplete and node_execution_review status columns
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@autogpt_platform/analytics/queries/user_onboarding_funnel.sql`:
- Around line 6-8: The pct_from_prev calculation is using LAG(users_completed)
over the sparse result from raw, which skips steps with zero completions; fix
this by creating a fixed steps table or CTE enumerating steps 1..22 (e.g.,
steps(step_num)) and LEFT JOINing the aggregated counts from raw onto that full
step list so missing steps yield users_completed = 0, then compute
LAG(users_completed) and pct_from_prev over the full step sequence; apply the
same change to the other similar block referenced (lines with pct_from_prev
around 73-84) so both funnel outputs keep one row per onboarding step.
- Around line 15-16: The view's inline documentation still describes a 14-step
funnel while the CASE that computes step_order in the user_onboarding_funnel
view now assigns values up to 22; update the comment blocks above the view (the
header that mentions "step_order INT" and the step-list comment sections) to say
"22=last" and enumerate the current 22 step names to match the CASE logic
(ensure the descriptive step names and numeric mapping match the CASE in the
step_order computation so readers aren’t misled).
In `@autogpt_platform/backend/generate_views.py`:
- Around line 153-165: load_views currently silently ignores unknown names in
the only parameter and returns exit code 0 when nothing matches; update
load_views to validate the provided only list against available SQL filenames
(use files and f.stem/name) and if any requested name is missing or if the
filtered result would be empty, print a clear error to stderr and exit non‑zero
(sys.exit(1)) so the process fails fast; ensure the same validation logic is
applied to the analogous function that handles materialized views (the code
around build_view_sql and any counterpart at the later block) so typos don't
silently drop views.
- Around line 21-25: The docstring and setup instructions reference altering the
analytics_readonly role but only set a password; update the ALTER ROLE
statements shown in the examples to include WITH LOGIN so the role can accept
connections (e.g., change "ALTER ROLE analytics_readonly WITH PASSWORD
'your-password';" to include "WITH LOGIN" in the same statement). Edit the
occurrences tied to SETUP_SQL / analytics_readonly examples so both the one near
the top (Step 3) and the second instance (lines ~67-72) are updated to "ALTER
ROLE analytics_readonly WITH LOGIN PASSWORD 'your-password';" ensuring the
examples match the NOLOGIN creation in SETUP_SQL.
- Around line 103-109: The current logic returns None when password is empty;
instead construct and return a valid libpq URI without the password component.
Update the code that builds the connection string (using the same symbols user,
password, host, port, dbname and quote) so that if password is falsy you return
"postgresql://" + f"{quote(user, safe='')}@{host}:{port}/{quote(dbname,
safe='')}", and otherwise keep the existing behavior that includes the quoted
password; ensure user and dbname remain percent-encoded via quote in both
branches.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 64fbdfa8-8a9b-4fe3-a709-c9f0f6f27359
📒 Files selected for processing (7)
autogpt_platform/analytics/queries/graph_execution.sqlautogpt_platform/analytics/queries/retention_agent.sqlautogpt_platform/analytics/queries/retention_login_daily.sqlautogpt_platform/analytics/queries/user_block_spending.sqlautogpt_platform/analytics/queries/user_onboarding_funnel.sqlautogpt_platform/analytics/queries/users_activities.sqlautogpt_platform/backend/generate_views.py
✅ Files skipped from review due to trivial changes (1)
- autogpt_platform/analytics/queries/user_block_spending.sql
🚧 Files skipped from review as they are similar to previous changes (3)
- autogpt_platform/analytics/queries/retention_login_daily.sql
- autogpt_platform/analytics/queries/graph_execution.sql
- autogpt_platform/analytics/queries/users_activities.sql
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
- GitHub Check: types
- GitHub Check: Seer Code Review
- GitHub Check: Analyze (python)
- GitHub Check: Analyze (typescript)
- GitHub Check: test (3.12)
- GitHub Check: test (3.13)
- GitHub Check: test (3.11)
- GitHub Check: Check PR Status
🧰 Additional context used
📓 Path-based instructions (3)
autogpt_platform/backend/**/*.py
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
autogpt_platform/backend/**/*.py: Use Python 3.11 (required; managed by Poetry via pyproject.toml) for backend development
Always run 'poetry run format' (Black + isort) before linting in backend development
Always run 'poetry run lint' (ruff) after formatting in backend development
Files:
autogpt_platform/backend/generate_views.py
autogpt_platform/backend/**/*.{py,txt}
📄 CodeRabbit inference engine (autogpt_platform/backend/CLAUDE.md)
Use
poetry runprefix for all Python commands, including testing, linting, formatting, and migrations
Files:
autogpt_platform/backend/generate_views.py
autogpt_platform/**/*.py
📄 CodeRabbit inference engine (AGENTS.md)
Format Python code with
poetry run format
Files:
autogpt_platform/backend/generate_views.py
🧠 Learnings (10)
📓 Common learnings
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12367
File: autogpt_platform/backend/generate_views.py:146-150
Timestamp: 2026-03-11T16:50:37.459Z
Learning: In Significant-Gravitas/AutoGPT PR `#12367` (`autogpt_platform/backend/generate_views.py`), the `security_invoker = false` setting on analytics views is intentional. `analytics_readonly` is granted access only to the `analytics` schema. The views execute as their owner (postgres) to read `auth.*` and `platform.*` tables, but expose only curated, scrubbed columns via their SELECT lists. This is the correct PostgreSQL "view as security boundary" pattern — equivalent to a stored-procedure security definer. Do not flag this as a privilege escalation; switching to `security_invoker = true` would require granting `analytics_readonly` direct SELECT on `auth.sessions`, `auth.audit_log_entries`, `platform.AgentGraphExecution`, etc., which is a much wider blast radius than the current design.
📚 Learning: 2026-03-11T16:50:37.459Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12367
File: autogpt_platform/backend/generate_views.py:146-150
Timestamp: 2026-03-11T16:50:37.459Z
Learning: In Significant-Gravitas/AutoGPT PR `#12367` (`autogpt_platform/backend/generate_views.py`), the `security_invoker = false` setting on analytics views is intentional. `analytics_readonly` is granted access only to the `analytics` schema. The views execute as their owner (postgres) to read `auth.*` and `platform.*` tables, but expose only curated, scrubbed columns via their SELECT lists. This is the correct PostgreSQL "view as security boundary" pattern — equivalent to a stored-procedure security definer. Do not flag this as a privilege escalation; switching to `security_invoker = true` would require granting `analytics_readonly` direct SELECT on `auth.sessions`, `auth.audit_log_entries`, `platform.AgentGraphExecution`, etc., which is a much wider blast radius than the current design.
Applied to files:
autogpt_platform/analytics/queries/user_onboarding_funnel.sqlautogpt_platform/analytics/queries/retention_agent.sqlautogpt_platform/backend/generate_views.py
📚 Learning: 2026-02-04T16:50:20.508Z
Learnt from: CR
Repo: Significant-Gravitas/AutoGPT PR: 0
File: autogpt_platform/backend/CLAUDE.md:0-0
Timestamp: 2026-02-04T16:50:20.508Z
Learning: Applies to autogpt_platform/backend/**/schema.prisma : Define key database models (User, AgentGraph, AgentGraphExecution, AgentNode, StoreListing) in `schema.prisma`
Applied to files:
autogpt_platform/analytics/queries/retention_agent.sql
📚 Learning: 2026-03-05T00:13:52.412Z
Learnt from: Pwuts
Repo: Significant-Gravitas/AutoGPT PR: 12284
File: autogpt_platform/backend/schema.prisma:284-284
Timestamp: 2026-03-05T00:13:52.412Z
Learning: In `autogpt_platform/backend/schema.prisma`, the `AgentGraph` ↔ `StoreListing` relation uses the pattern: `AgentGraph` declares `StoreListing? relation(fields: [id], references: [agentGraphId], onDelete: NoAction)` and `StoreListing` declares `AgentGraph AgentGraph[]` with `agentGraphId String unique`. This is intentional and valid because `AgentGraph` has a composite PK `@id([id, version])` (multiple rows per graph id, one per version), while `StoreListing.agentGraphId` is `unique` (one listing per graph id). The `fields: [id], references: [agentGraphId]` on the `AgentGraph` side joins `AgentGraph.id` against `StoreListing.agentGraphId`. Do NOT flag this as a cardinality mismatch or malformed relation — `prisma validate` passes cleanly.
Applied to files:
autogpt_platform/analytics/queries/retention_agent.sql
📚 Learning: 2026-03-10T08:39:13.707Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12356
File: autogpt_platform/backend/backend/copilot/constants.py:9-12
Timestamp: 2026-03-10T08:39:13.707Z
Learning: In Significant-Gravitas/AutoGPT PR `#12356`, the `COPILOT_SYNTHETIC_ID_PREFIX = "copilot-"` check in `create_auto_approval_record` (human_review.py) is intentional and safe. The `graph_exec_id` passed to this function comes from server-side `PendingHumanReview` DB records (not from user input); the API only accepts `node_exec_id` from users. Synthetic `copilot-*` IDs are only ever created server-side in `run_block.py`. The prefix skip avoids a DB lookup for a `AgentGraphExecution` record that legitimately does not exist for CoPilot sessions, while `user_id` scoping is enforced at the auth layer and on the resulting auto-approval record.
Applied to files:
autogpt_platform/analytics/queries/retention_agent.sqlautogpt_platform/backend/generate_views.py
📚 Learning: 2026-03-10T11:22:14.861Z
Learnt from: Swiftyos
Repo: Significant-Gravitas/AutoGPT PR: 12347
File: autogpt_platform/backend/backend/data/invited_user.py:193-193
Timestamp: 2026-03-10T11:22:14.861Z
Learning: In Significant-Gravitas/AutoGPT, the admin data-layer functions in `autogpt_platform/backend/backend/data/invited_user.py` (`list_invited_users`, `create_invited_user`, `revoke_invited_user`, `retry_invited_user_tally`, `bulk_create_invited_users_from_file`) intentionally omit an acting-user/admin ID parameter. Authorization for these functions is enforced entirely at the FastAPI router layer via `Security(requires_admin_user)` in `user_admin_routes.py`. Do not flag the absence of a user_id/actor_id parameter in these functions as a missing data-access guardrail violation.
Applied to files:
autogpt_platform/backend/generate_views.py
📚 Learning: 2026-02-04T16:49:42.490Z
Learnt from: CR
Repo: Significant-Gravitas/AutoGPT PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-02-04T16:49:42.490Z
Learning: Applies to autogpt_platform/backend/backend/data/**/*.py : All data access in backend requires user ID checks; verify this for any 'data/*.py' changes
Applied to files:
autogpt_platform/backend/generate_views.py
📚 Learning: 2026-02-04T16:50:51.495Z
Learnt from: CR
Repo: Significant-Gravitas/AutoGPT PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-04T16:50:51.495Z
Learning: Applies to autogpt_platform/**/data/*.py : For changes touching `data/*.py`, validate user ID checks or explain why not needed
Applied to files:
autogpt_platform/backend/generate_views.py
📚 Learning: 2026-02-26T17:02:22.448Z
Learnt from: Pwuts
Repo: Significant-Gravitas/AutoGPT PR: 12211
File: .pre-commit-config.yaml:160-179
Timestamp: 2026-02-26T17:02:22.448Z
Learning: Keep the pre-commit hook pattern broad for autogpt_platform/backend to ensure OpenAPI schema changes are captured. Do not narrow to backend/api/ alone, since the generated schema depends on Pydantic models across multiple directories (backend/data/, backend/blocks/, backend/copilot/, backend/integrations/, backend/util/). Narrowing could miss schema changes and cause frontend type desynchronization.
Applied to files:
autogpt_platform/backend/generate_views.py
📚 Learning: 2026-03-05T15:42:08.207Z
Learnt from: ntindle
Repo: Significant-Gravitas/AutoGPT PR: 12297
File: .claude/skills/backend-check/SKILL.md:14-16
Timestamp: 2026-03-05T15:42:08.207Z
Learning: In Python files under autogpt_platform/backend (recursively), rely on poetry run format to perform formatting (Black + isort) and linting (ruff). Do not run poetry run lint as a separate step after poetry run format, since format already includes linting checks.
Applied to files:
autogpt_platform/backend/generate_views.py
🔇 Additional comments (1)
autogpt_platform/analytics/queries/retention_agent.sql (1)
79-80: Deterministic latest-version name lookup looks good.
DISTINCT ON (g."id") ... ORDER BY g."version" DESCavoids the oldMAX(name)drift and keeps labels aligned with the newest graph version.
…LECT CREATE OR REPLACE VIEW requires existing columns to stay in position. Moving node_execution_incomplete and node_execution_review after is_active_after_7d so the replacement doesn't shift existing columns.
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@autogpt_platform/analytics/queries/users_activities.sql`:
- Around line 25-42: The documented output column header is missing the final
two columns returned by the SELECT; update the header block so the declared
column list and order match the view by appending entries for
node_execution_incomplete and node_execution_review (with their appropriate
types, e.g., BIGINT or INT depending on the SELECT) and brief descriptions;
ensure the new lines appear after node_execution_running in the same comment
block so the header order exactly mirrors the SELECT output.
- Around line 77-80: The query currently uses COUNT("id") AS agent_count which
counts versioned rows rather than distinct agents; change the aggregation to
COUNT(DISTINCT "id") AS agent_count in the SELECT so each graph id is counted
once (keep the "userId"::text AS user_id and MAX("updatedAt") AS
last_agent_save_time unchanged and reference the AgentGraph table/column names
exactly as used).
- Around line 119-124: Normalize the count columns by converting NULLs to 0:
update the SELECT expressions that return ua.agent_count, gr.unique_agent_runs,
and gr.agent_runs to wrap them with COALESCE (or equivalent) so they return 0
instead of NULL for users with logins but no agents or executions, matching the
existing normalization applied to the node counters; ensure you modify the
SELECT projection where ua.agent_count, gr.first_agent_run_time,
gr.last_agent_run_time, gr.unique_agent_runs, gr.agent_runs are listed so
downstream aggregations no longer need special-casing.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 2c035eeb-ee0a-45bf-a966-1b895a5b9936
📒 Files selected for processing (1)
autogpt_platform/analytics/queries/users_activities.sql
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
- GitHub Check: types
- GitHub Check: Seer Code Review
- GitHub Check: test (3.12)
- GitHub Check: test (3.13)
- GitHub Check: test (3.11)
- GitHub Check: Analyze (typescript)
- GitHub Check: Analyze (python)
- GitHub Check: Check PR Status
🧰 Additional context used
🧠 Learnings (6)
📓 Common learnings
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12367
File: autogpt_platform/backend/generate_views.py:146-150
Timestamp: 2026-03-11T16:50:37.459Z
Learning: In Significant-Gravitas/AutoGPT PR `#12367` (`autogpt_platform/backend/generate_views.py`), the `security_invoker = false` setting on analytics views is intentional. `analytics_readonly` is granted access only to the `analytics` schema. The views execute as their owner (postgres) to read `auth.*` and `platform.*` tables, but expose only curated, scrubbed columns via their SELECT lists. This is the correct PostgreSQL "view as security boundary" pattern — equivalent to a stored-procedure security definer. Do not flag this as a privilege escalation; switching to `security_invoker = true` would require granting `analytics_readonly` direct SELECT on `auth.sessions`, `auth.audit_log_entries`, `platform.AgentGraphExecution`, etc., which is a much wider blast radius than the current design.
📚 Learning: 2026-03-11T16:50:37.459Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12367
File: autogpt_platform/backend/generate_views.py:146-150
Timestamp: 2026-03-11T16:50:37.459Z
Learning: In Significant-Gravitas/AutoGPT PR `#12367` (`autogpt_platform/backend/generate_views.py`), the `security_invoker = false` setting on analytics views is intentional. `analytics_readonly` is granted access only to the `analytics` schema. The views execute as their owner (postgres) to read `auth.*` and `platform.*` tables, but expose only curated, scrubbed columns via their SELECT lists. This is the correct PostgreSQL "view as security boundary" pattern — equivalent to a stored-procedure security definer. Do not flag this as a privilege escalation; switching to `security_invoker = true` would require granting `analytics_readonly` direct SELECT on `auth.sessions`, `auth.audit_log_entries`, `platform.AgentGraphExecution`, etc., which is a much wider blast radius than the current design.
Applied to files:
autogpt_platform/analytics/queries/users_activities.sql
📚 Learning: 2026-03-04T23:58:18.476Z
Learnt from: Pwuts
Repo: Significant-Gravitas/AutoGPT PR: 12284
File: autogpt_platform/frontend/src/app/api/openapi.json:11897-11900
Timestamp: 2026-03-04T23:58:18.476Z
Learning: Repo: Significant-Gravitas/AutoGPT — PR `#12284`
Backend/frontend OpenAPI codegen convention: In backend/api/features/store/model.py, the StoreSubmission and StoreSubmissionAdminView models define submitted_at: datetime | None, changes_summary: str | None, and instructions: str | None with no default. This is intentional to produce “required but nullable” fields in OpenAPI (properties appear in required[] and use anyOf [type, null]). This matches Prisma’s submittedAt DateTime? and changesSummary String?. Do not flag this as a required/nullable mismatch.
Applied to files:
autogpt_platform/analytics/queries/users_activities.sql
📚 Learning: 2026-02-04T16:50:20.508Z
Learnt from: CR
Repo: Significant-Gravitas/AutoGPT PR: 0
File: autogpt_platform/backend/CLAUDE.md:0-0
Timestamp: 2026-02-04T16:50:20.508Z
Learning: Applies to autogpt_platform/backend/**/schema.prisma : Define key database models (User, AgentGraph, AgentGraphExecution, AgentNode, StoreListing) in `schema.prisma`
Applied to files:
autogpt_platform/analytics/queries/users_activities.sql
📚 Learning: 2026-03-05T00:13:52.412Z
Learnt from: Pwuts
Repo: Significant-Gravitas/AutoGPT PR: 12284
File: autogpt_platform/backend/schema.prisma:284-284
Timestamp: 2026-03-05T00:13:52.412Z
Learning: In `autogpt_platform/backend/schema.prisma`, the `AgentGraph` ↔ `StoreListing` relation uses the pattern: `AgentGraph` declares `StoreListing? relation(fields: [id], references: [agentGraphId], onDelete: NoAction)` and `StoreListing` declares `AgentGraph AgentGraph[]` with `agentGraphId String unique`. This is intentional and valid because `AgentGraph` has a composite PK `@id([id, version])` (multiple rows per graph id, one per version), while `StoreListing.agentGraphId` is `unique` (one listing per graph id). The `fields: [id], references: [agentGraphId]` on the `AgentGraph` side joins `AgentGraph.id` against `StoreListing.agentGraphId`. Do NOT flag this as a cardinality mismatch or malformed relation — `prisma validate` passes cleanly.
Applied to files:
autogpt_platform/analytics/queries/users_activities.sql
📚 Learning: 2026-03-10T08:39:13.707Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12356
File: autogpt_platform/backend/backend/copilot/constants.py:9-12
Timestamp: 2026-03-10T08:39:13.707Z
Learning: In Significant-Gravitas/AutoGPT PR `#12356`, the `COPILOT_SYNTHETIC_ID_PREFIX = "copilot-"` check in `create_auto_approval_record` (human_review.py) is intentional and safe. The `graph_exec_id` passed to this function comes from server-side `PendingHumanReview` DB records (not from user input); the API only accepts `node_exec_id` from users. Synthetic `copilot-*` IDs are only ever created server-side in `run_block.py`. The prefix skip avoids a DB lookup for a `AgentGraphExecution` record that legitimately does not exist for CoPilot sessions, while `user_id` scoping is enforced at the auth layer and on the resulting auto-approval record.
Applied to files:
autogpt_platform/analytics/queries/users_activities.sql
- user_onboarding_funnel: build complete 22-step grid with VALUES CTE so zero-completion steps are always present, fixing LAG comparisons against wrong predecessors; update docs to reflect all 22 steps - users_activities: use COUNT(DISTINCT "id") for agent_count to avoid counting multiple version rows per graph; add COALESCE(..., 0) for agent_count, unique_agent_runs, agent_runs; update docs column list to include node_execution_incomplete and node_execution_review - generate_views: update Step 3 comment to clarify NOLOGIN role needs WITH LOGIN PASSWORD not just WITH PASSWORD; add fail-fast validation for unknown --only view names with helpful error message
The completedSteps column is a platform."OnboardingStep" enum array. UNNEST produces enum values that can't be compared directly to text from the VALUES clause. Adding ::text cast fixes the type mismatch.
) ### Changes 🏗️ Adds `autogpt_platform/analytics/` — 14 SQL view definitions that expose production data safely through a locked-down `analytics` schema. **Security model:** - Views use `security_invoker = false` (PostgreSQL 15+), so they execute as their owner (`postgres`), not the caller - `analytics_readonly` role only has access to `analytics.*` — cannot touch `platform` or `auth` tables directly **Files:** - `backend/generate_views.py` — does everything; auto-reads credentials from `backend/.env` - `analytics/queries/*.sql` — 14 documented view definitions (auth, user activity, executions, onboarding funnel, cohort retention) --- ### Running locally (dev) ```bash cd autogpt_platform/backend # First time only — creates analytics schema, role, grants poetry run analytics-setup # Create / refresh views (auto-reads backend/.env) poetry run analytics-views ``` ### Running in production (Supabase) ```bash cd autogpt_platform/backend # Step 1 — first time only (run in Supabase SQL Editor as postgres superuser) poetry run analytics-setup --dry-run # Paste the output into Supabase SQL Editor and run # Step 2 — apply views (use direct connection host, not pooler) poetry run analytics-views --db-url "postgresql://postgres:PASSWORD@db.<ref>.supabase.co:5432/postgres" # Step 3 — set password for analytics_readonly so external tools can connect # Run in Supabase SQL Editor: # ALTER ROLE analytics_readonly WITH PASSWORD 'your-password'; ``` --- ### Checklist 📋 #### For code changes: - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [x] I have tested my changes according to the test plan: - [x] Setup + views applied cleanly on local Postgres 15 - [x] `analytics_readonly` can `SELECT` from all 14 `analytics.*` views - [x] `analytics_readonly` gets `permission denied` on `platform.*` and `auth.*` directly --------- Co-authored-by: Otto (AGPT) <otto@agpt.co>
…nificant-Gravitas#12367) ### Changes 🏗️ Adds `autogpt_platform/analytics/` — 14 SQL view definitions that expose production data safely through a locked-down `analytics` schema. **Security model:** - Views use `security_invoker = false` (PostgreSQL 15+), so they execute as their owner (`postgres`), not the caller - `analytics_readonly` role only has access to `analytics.*` — cannot touch `platform` or `auth` tables directly **Files:** - `backend/generate_views.py` — does everything; auto-reads credentials from `backend/.env` - `analytics/queries/*.sql` — 14 documented view definitions (auth, user activity, executions, onboarding funnel, cohort retention) --- ### Running locally (dev) ```bash cd autogpt_platform/backend # First time only — creates analytics schema, role, grants poetry run analytics-setup # Create / refresh views (auto-reads backend/.env) poetry run analytics-views ``` ### Running in production (Supabase) ```bash cd autogpt_platform/backend # Step 1 — first time only (run in Supabase SQL Editor as postgres superuser) poetry run analytics-setup --dry-run # Paste the output into Supabase SQL Editor and run # Step 2 — apply views (use direct connection host, not pooler) poetry run analytics-views --db-url "postgresql://postgres:PASSWORD@db.<ref>.supabase.co:5432/postgres" # Step 3 — set password for analytics_readonly so external tools can connect # Run in Supabase SQL Editor: # ALTER ROLE analytics_readonly WITH PASSWORD 'your-password'; ``` --- ### Checklist 📋 #### For code changes: - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [x] I have tested my changes according to the test plan: - [x] Setup + views applied cleanly on local Postgres 15 - [x] `analytics_readonly` can `SELECT` from all 14 `analytics.*` views - [x] `analytics_readonly` gets `permission denied` on `platform.*` and `auth.*` directly --------- Co-authored-by: Otto (AGPT) <otto@agpt.co>
Changes 🏗️
Adds
autogpt_platform/analytics/— 14 SQL view definitions that expose production data safely through a locked-downanalyticsschema.Security model:
security_invoker = false(PostgreSQL 15+), so they execute as their owner (postgres), not the calleranalytics_readonlyrole only has access toanalytics.*— cannot touchplatformorauthtables directlyFiles:
backend/generate_views.py— does everything; auto-reads credentials frombackend/.envanalytics/queries/*.sql— 14 documented view definitions (auth, user activity, executions, onboarding funnel, cohort retention)Running locally (dev)
Running in production (Supabase)
Checklist 📋
For code changes:
analytics_readonlycanSELECTfrom all 14analytics.*viewsanalytics_readonlygetspermission deniedonplatform.*andauth.*directly