Phase (f) of docs/DATA-ANALYSIS.md (§7, §8, §9.3). Stacked on #9 → #8 → #6 → #5. Draft until RC.6.
Extension 1.4.0, with 21 operations under /qqq/v1/qbit/data-analysis:
Saved analyses, any of the 7 kinds, as typed definition_json validated on save.
POST /saved/{id}/run re-runs as the viewer: it needs READ on the saved row plus <table>.analyze, and fields the viewer can't analyze are dropped with a note.
GET /saved lists the viewer's visible analyses, with the starred flag.
Shared READ through core record locks. A WRITE lock covers owner_user_id. A READ lock covers a hidden read_owner_user_id projection, set by core customizers to the owner for private rows and to null for shared rows.
Owner-only writes and the admin all-access key are unchanged; no custom query filter is involved.
A database CHECK (shared OR read_owner_user_id IS NOT NULL) makes a row written outside QQQ fail closed.
The README has the migration for existing hosts. Collaboration's sharing contract (#1136) will replace the flag.
Stars:PUT / DELETE /saved/{id}/star. They move to collaboration_favorite later.
Schedules:
the hidden process data_analysis_run_schedules runs every 5 minutes and computes the next run from frequency, day, time and time zone (or cron);
each run happens as the owner, and mail goes only to recipients who hold READ, .analyze and FULL field access; others are skipped and listed in last_status;
mail is an HTML summary plus a CSV/XLSX attachment through core messaging;
failures raise data.analysis.schedule.failed;
"Send now" record action.
An attachment-capable messaging provider is required. Stock core EMAIL is refused explicitly in last_status, never silently.
Export:GET /saved/{id}/export?format=csv|xlsx and POST /export (file specs).
SQL Workbench handoff:POST /tables/{t}/sql, available only when qbit-sql-workbench is installed and permitted. It returns the backend's statement, with a note that QQQ applies security filters at run time.
Widgets: a DATA_ANALYSIS_SAVED widget that renders a saved analysis as the viewer and links back. The Analyses home is now standard widgets: a statistics row, starred analyses, firing watches and my recent runs, all real counts with honest empty states.
Every endpoint has assistant-oriented descriptions, and the operations appear in UiApiInvoker.listOperations().
Test plan
./mvnw verify with Docker: green on memory, H2 and Postgres (~300 tests). JaCoCo ~97% instructions / 100% classes.
Save and re-run as another viewer (shared, unshared, dropped fields); definition validation; star toggle and list
Schedules: due computation (time zones, weekly and monthly), run as owner, recipient filtering, email through a capturing provider with attachment content, the failure alert
CSV/XLSX content; SQL handoff (H2 available, memory not); widget data; home widgets with real rows and empty states; HTTP endpoints; DDL CHECK
## Summary
Phase (f) of `docs/DATA-ANALYSIS.md` (§7, §8, §9.3). **Stacked on #9 → #8 → #6 → #5.** Draft until RC.6.
Extension 1.4.0, with 21 operations under `/qqq/v1/qbit/data-analysis`:
- **Saved analyses**, any of the 7 kinds, as typed `definition_json` validated on save.
- `POST /saved/{id}/run` re-runs as the **viewer**: it needs READ on the saved row plus `<table>.analyze`, and fields the viewer can't analyze are dropped with a note.
- `GET /saved` lists the viewer's visible analyses, with the starred flag.
- **Shared READ through core record locks.** A WRITE lock covers `owner_user_id`. A READ lock covers a hidden `read_owner_user_id` projection, set by core customizers to the owner for private rows and to null for shared rows.
- Owner-only writes and the admin all-access key are unchanged; no custom query filter is involved.
- A database `CHECK (shared OR read_owner_user_id IS NOT NULL)` makes a row written outside QQQ fail closed.
- The README has the migration for existing hosts. Collaboration's sharing contract (#1136) will replace the flag.
- **Stars:** `PUT` / `DELETE /saved/{id}/star`. They move to `collaboration_favorite` later.
- **Schedules:**
- the hidden process `data_analysis_run_schedules` runs every 5 minutes and computes the next run from frequency, day, time and time zone (or cron);
- each run happens **as the owner**, and mail goes only to recipients who hold READ, `.analyze` and FULL field access; others are skipped and listed in `last_status`;
- mail is an HTML summary plus a CSV/XLSX attachment through core messaging;
- failures raise `data.analysis.schedule.failed`;
- "Send now" record action.
- An attachment-capable messaging provider is required. Stock core EMAIL is refused explicitly in `last_status`, never silently.
- **Export:** `GET /saved/{id}/export?format=csv|xlsx` and `POST /export` (file specs).
- **SQL Workbench handoff:** `POST /tables/{t}/sql`, available only when qbit-sql-workbench is installed and permitted. It returns the backend's statement, with a note that QQQ applies security filters at run time.
- **Widgets:** a `DATA_ANALYSIS_SAVED` widget that renders a saved analysis as the viewer and links back. The **Analyses home** is now standard widgets: a statistics row, starred analyses, firing watches and my recent runs, all real counts with honest empty states.
- Every endpoint has assistant-oriented descriptions, and the operations appear in `UiApiInvoker.listOperations()`.
## Test plan
- [x] `./mvnw verify` with Docker: green on memory, H2 and Postgres (~300 tests). JaCoCo ~97% instructions / 100% classes.
- [x] Save and re-run as another viewer (shared, unshared, dropped fields); definition validation; star toggle and list
- [x] Schedules: due computation (time zones, weekly and monthly), run as owner, recipient filtering, email through a capturing provider with attachment content, the failure alert
- [x] CSV/XLSX content; SQL handoff (H2 available, memory not); widget data; home widgets with real rows and empty states; HTTP endpoints; DDL CHECK
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
Phase (f) of
docs/DATA-ANALYSIS.md(§7, §8, §9.3). Stacked on #9 → #8 → #6 → #5. Draft until RC.6.Extension 1.4.0, with 21 operations under
/qqq/v1/qbit/data-analysis:definition_jsonvalidated on save.POST /saved/{id}/runre-runs as the viewer: it needs READ on the saved row plus<table>.analyze, and fields the viewer can't analyze are dropped with a note.GET /savedlists the viewer's visible analyses, with the starred flag.owner_user_id. A READ lock covers a hiddenread_owner_user_idprojection, set by core customizers to the owner for private rows and to null for shared rows.CHECK (shared OR read_owner_user_id IS NOT NULL)makes a row written outside QQQ fail closed.PUT/DELETE /saved/{id}/star. They move tocollaboration_favoritelater.data_analysis_run_schedulesruns every 5 minutes and computes the next run from frequency, day, time and time zone (or cron);.analyzeand FULL field access; others are skipped and listed inlast_status;data.analysis.schedule.failed;last_status, never silently.GET /saved/{id}/export?format=csv|xlsxandPOST /export(file specs).POST /tables/{t}/sql, available only when qbit-sql-workbench is installed and permitted. It returns the backend's statement, with a note that QQQ applies security filters at run time.DATA_ANALYSIS_SAVEDwidget that renders a saved analysis as the viewer and links back. The Analyses home is now standard widgets: a statistics row, starred analyses, firing watches and my recent runs, all real counts with honest empty states.UiApiInvoker.listOperations().Test plan
./mvnw verifywith Docker: green on memory, H2 and Postgres (~300 tests). JaCoCo ~97% instructions / 100% classes.🤖 Generated with Claude Code