toga-ai 1.0.435 → 1.0.437
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/knowledge/2.0/apps/api2/INDEX.md +2 -1
- package/knowledge/2.0/apps/api2/features/record-scripts.md +48 -5
- package/knowledge/2.0/apps/api2/features/sprint-dashboard-api.md +140 -0
- package/knowledge/2.0/apps/worker2/features/team-sprint-management.md +20 -7
- package/knowledge/INDEX.md +1 -1
- package/knowledge/clients/true/profile.md +8 -2
- package/knowledge/sessions/2026-07-24-sprint-dashboard-kyalamarthi.md +72 -0
- package/package.json +1 -1
|
@@ -9,8 +9,9 @@
|
|
|
9
9
|
| [Language Translation Layer (audience.language + sidecar tables)](features/language-translation-layer.md) | Serves the same TOGa data (Item title/description/longDescription, plus item **feature** text — `Features.name`, `ItemCategoryFeatureGroups.name`, `ItemFeatures | api2/Component/Api/V2/V2.php, api2/Component/Api/V2/Response/Response.php, _underscore/Model/Core/Setting.php, _underscore/Model/Core/RecordField.php, _underscore/Model/Core/DefaultGlobalSetting.php, _underscore/Model/Client/ItemTranslation.php, _underscore/Model/Client/FeatureTranslation.php, _underscore/Model/Client/ItemCategoryFeatureGroupTranslation.php, _underscore/Model/Client/ItemFeatureTranslation.php, dbchanges2/Client/2026-06-23a - ItemTranslations.sql, dbchanges2/Client/2026-06-23b - ItemTranslationsAcl.sql, dbchanges2/Client/2026-07-13a - FeatureTranslations.sql, dbchanges2/Client/2026-07-13b - FeatureTranslationsAcl.sql, dbchanges2/Core/2026-06-23a - RecordFieldsTranslationColumn.sql, dbchanges2/Core/2026-06-23b - ItemTranslationsRecord.sql, dbchanges2/Core/2026-07-13 - FeatureTranslationsRecord.sql |
|
|
10
10
|
| [Nested FK object embedding is gated by the CHILD record's own ACL](features/nested-fk-acl-embedding.md) | When the V2 JSON engine serializes a foreign-key field into a **nested object** (in `getFullModelData()`, ~V2.php L6016-6060), it re-checks the **child** record | api2/Component/Api/V2/V2.php, dbchanges2/Client_Compass/2026-07-23b - PurchaseOrdersRecordReadAcl.sql |
|
|
11
11
|
| [Nested-relationship writes & child matching (link vs. create)](features/nested-relationship-writes.md) | When a 2.0 API write payload (`POST`/`PUT`) contains a **nested related object** (e.g. | api2/Component/Api/V2/V2.php |
|
|
12
|
-
| [Record Scripts (computed/aggregate /v2 endpoints — the authoring contract)](features/record-scripts.md) | In api2 you almost never write a controller. | api2/Component/Api/V2/V2.php, _underscore/Model/Team/Sprint.php |
|
|
12
|
+
| [Record Scripts (computed/aggregate /v2 endpoints — the authoring contract)](features/record-scripts.md) | In api2 you almost never write a controller. | api2/Component/Api/V2/V2.php, _underscore/Model/Team/Sprint.php, _underscore/Query.php |
|
|
13
13
|
| [POST + JSON-body args for scripted APIs](features/scripted-api-post-body-args.md) | The V2 engine can run a Record Script (scripted API) for a **POST** request, and a scripted API can receive its arguments from the **JSON request body** instead | api2/Component/Api/V2/V2.php |
|
|
14
|
+
| [TOGa IQ Sprint Dashboard API (Record Scripts)](features/sprint-dashboard-api.md) | The internal **TOGa IQ sprint dashboard** is served in production by **six api2 Record Scripts** on `_Model_Team_Sprint` (`_underscore/Model/Team/Sprint.php`), | _underscore/Model/Team/Sprint.php, api2/Component/Api/V2/V2.php, dbchanges2/Core/2026-07-24a - SprintDashboardRecordScripts.sql, dbchanges2/Client_True/2026-07-24b - SprintDashboardScriptAcl.sql |
|
|
14
15
|
| [Surface action-state via the surface=<slug> request option (M2M-safe)](features/surface-meta-option.md) | An opt-in V2 engine request option, `surface=<slug>`, that attaches per-record UI action state (`isVisible`/`isEnabled`) to a GET response **under `meta.surface | api2/Component/Api/V2/V2.php, _underscore/Model/Core/Surface.php |
|
|
15
16
|
| [TableView row-filtering via apiWhereClause (options.where grammar, end to end)](features/tableview-apiwhereclause-row-filtering.md) | `TableViews.apiWhereClause` (TEXT, nullable) is the sanctioned, code-free way to restrict or exclude rows from a 2.0 table view. | api2/Component/Api/V2/V2.php, _underscore/Model/Client/TableView.php, toga2-supply/src/api/toga.ts |
|
|
16
17
|
| [TableView field/column metadata (TableViewFields, hidden projected columns)](features/tableview-field-metadata.md) | The columns of a 2.0 table view are defined by DB metadata, not code. | _underscore/Model/Client/TableView.php, api2/Component/Api/V2/V2.php, dbchanges2/Client/2026-07-20 - ItemsUuidForPurchaseOrderItemsTableView.sql |
|
|
@@ -6,12 +6,14 @@ project: API
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-07-
|
|
9
|
+
updated: 2026-07-24
|
|
10
10
|
owners: ["kyalamarthi"]
|
|
11
11
|
files:
|
|
12
12
|
- api2/Component/Api/V2/V2.php
|
|
13
13
|
- _underscore/Model/Team/Sprint.php
|
|
14
|
+
- _underscore/Query.php
|
|
14
15
|
related:
|
|
16
|
+
- ./sprint-dashboard-api.md
|
|
15
17
|
- ./scripted-api-post-body-args.md
|
|
16
18
|
- ./tableview-apiwhereclause-row-filtering.md
|
|
17
19
|
- ../architecture.md
|
|
@@ -35,7 +37,13 @@ register one). For the POST + JSON-request-body variant (large argument values),
|
|
|
35
37
|
Motivating example: `_Model_Team_Sprint::committedTile()` — a read-only dashboard tile over
|
|
36
38
|
the internal TOGa IQ sprint (`Team` schema) data. **Decision recorded:** internal
|
|
37
39
|
sprint/dashboard read endpoints are served from **api2 via a Record Script**, not by building
|
|
38
|
-
a bespoke controller or standing the data up inside the talos / TOGa IQ app.
|
|
40
|
+
a bespoke controller or standing the data up inside the talos / TOGa IQ app. This contract was
|
|
41
|
+
subsequently validated by productionizing the whole sprint dashboard into six Record Scripts —
|
|
42
|
+
see [TOGa IQ Sprint Dashboard API](./sprint-dashboard-api.md) for the concrete endpoint set.
|
|
43
|
+
|
|
44
|
+
**The model class lives in `_underscore`, not api2.** `_Model_Team_Sprint` is
|
|
45
|
+
`_underscore/Model/Team/Sprint.php`, pulled in at deploy — api2 has **no `Model/` directory**
|
|
46
|
+
of its own. Write the static method in `_underscore`, not in the api2 repo.
|
|
39
47
|
|
|
40
48
|
## The method contract
|
|
41
49
|
|
|
@@ -51,9 +59,17 @@ Rules the engine enforces (see `getRecordScriptPhpMethod()` + the dispatch in
|
|
|
51
59
|
- **Remaining parameters are named** and are filled from the **query string**. Each query-string
|
|
52
60
|
key maps to the same-named PHP parameter; unmatched named args land in a `...$args` variadic
|
|
53
61
|
if the method declares one. (`transactionId` and `api` keys are reserved and never mapped.)
|
|
54
|
-
- **The return value
|
|
55
|
-
|
|
62
|
+
- **The return value becomes the envelope `data` — but nested under the route segment.** The
|
|
63
|
+
engine does `$outData[$requestedUuid] = method(...)` then `return (object)$outData`
|
|
64
|
+
(`V2.php` ~:4121, :6305), so the script's payload lands **under the script's route-segment
|
|
65
|
+
key**, not flat. `GET /v2/sprints/tile` yields `data: { tile: {…} }`, not `data: {…}`;
|
|
66
|
+
`campaigns/jobs` yields `data: { jobs: … }`. **A consumer must unwrap `data[<route>]`.** The
|
|
67
|
+
method itself still returns the raw object/array — do not echo, and never build the
|
|
68
|
+
success/error envelope yourself.
|
|
56
69
|
- Invocation is effectively `$model::$phpMethod(...$args)`.
|
|
70
|
+
- **No current-sprint (or any per-request context) middleware.** Unlike a hand-built app, the
|
|
71
|
+
`/v2` engine has no middleware that pre-resolves a "current" entity for a script. Each Record
|
|
72
|
+
Script must resolve its own context (e.g. the current sprint) inside the method.
|
|
57
73
|
|
|
58
74
|
## Running SQL inside a Record Script
|
|
59
75
|
|
|
@@ -66,7 +82,11 @@ then `->fetchRow()`) rather than going through model CRUD. Two framework realiti
|
|
|
66
82
|
arguments arrive straight from the query string (user input), so this sanitization is
|
|
67
83
|
mandatory — an unsanitized value in the query text is a SQL-injection hole. Treat
|
|
68
84
|
`\_Database::escape()` + `(int)` casting as the required substitute for the prepared
|
|
69
|
-
statements `_Query` does not offer.
|
|
85
|
+
statements `_Query` does not offer. For a query-string arg drawn from a **fixed set** (an
|
|
86
|
+
enum-like `?category=`), the safest handling is a **value allowlist** — map the arg to a
|
|
87
|
+
trusted server-side constant (e.g. the sprint dashboard maps `?category=` to the
|
|
88
|
+
`_Model_Team_Task::WORK_TYPE__*` constants) and reject anything unrecognized, so no raw
|
|
89
|
+
user value ever reaches the SQL text.
|
|
70
90
|
- **Query cache for "live" tiles.** A dashboard tile that must reflect current data should call
|
|
71
91
|
`\_Database::useQueryCache(false)` before the query and **restore the previous flag**
|
|
72
92
|
afterward (capture the value it returns and set it back), so it doesn't serve stale cached
|
|
@@ -104,6 +124,18 @@ grant under the per-client `Client_<Name>` folder. See the
|
|
|
104
124
|
[api2 architecture — metadata CRUD engine](../architecture.md) for the surrounding Records
|
|
105
125
|
metadata model.
|
|
106
126
|
|
|
127
|
+
**Registration conventions** (confirmed against
|
|
128
|
+
`Core/… - TrackingNumberReprintRecordScript.sql` + its `Client/… - …ScriptAcl.sql`):
|
|
129
|
+
|
|
130
|
+
- Write the migrations **id-agnostic** — resolve FKs by looking the parent up **by route**,
|
|
131
|
+
and guard every insert with a **`NOT EXISTS`** subquery on its natural key so the file is
|
|
132
|
+
re-runnable and environment-agnostic (auto-increment ids differ per environment).
|
|
133
|
+
- `Core.Records` carries the human-facing `name` / `description`.
|
|
134
|
+
- The `AclRecordScripts` grant is a **CLIENT-DB** table. Put it in the specific
|
|
135
|
+
**`Client_<Name>`** folder for the tenant that should get the script — an internal-only
|
|
136
|
+
endpoint goes in `Client_True`. The generic **`Client/`** folder runs against **every**
|
|
137
|
+
tenant DB, so putting an ACL grant there hands the endpoint to all clients.
|
|
138
|
+
|
|
107
139
|
## Data source note (core DB hosts/credentials)
|
|
108
140
|
|
|
109
141
|
The core cluster hosts and credentials this endpoint reads through are **not** documented here;
|
|
@@ -137,6 +169,15 @@ an `AclRecordScripts` grant in each client DB.
|
|
|
137
169
|
|
|
138
170
|
## Change history
|
|
139
171
|
|
|
172
|
+
- 2026-07-24 — Refined the engine contract from productionizing the sprint dashboard (verified
|
|
173
|
+
in `V2.php` + `_underscore/Query.php`): the return value **nests under the route-segment key**
|
|
174
|
+
(`data[<route>]`, not flat — consumers must unwrap); there is **no current-sprint/context
|
|
175
|
+
middleware** (each script resolves its own context); the `_Model` class lives in
|
|
176
|
+
**`_underscore`** (pulled at deploy — api2 has no `Model/` dir). Added registration
|
|
177
|
+
conventions (id-agnostic resolve-by-route + `NOT EXISTS` guards; the `AclRecordScripts` grant
|
|
178
|
+
belongs in the tenant-specific `Client_<Name>` folder, never generic `Client/`, or it grants
|
|
179
|
+
every tenant) and the value-allowlist pattern for enum-like query args. Linked the new
|
|
180
|
+
[Sprint Dashboard API](./sprint-dashboard-api.md). (kyalamarthi)
|
|
140
181
|
- 2026-07-23 — Initial documentation of the Record Script authoring contract (static
|
|
141
182
|
`fn(&$api, ...namedParams)` on the record model; query-string → named args; return → `/v2`
|
|
142
183
|
envelope `data`), the SQL specifics (`_Query` has no bound params → `_Database::escape()` +
|
|
@@ -149,6 +190,8 @@ an `AclRecordScripts` grant in each client DB.
|
|
|
149
190
|
|
|
150
191
|
## Related docs
|
|
151
192
|
|
|
193
|
+
- [TOGa IQ Sprint Dashboard API](./sprint-dashboard-api.md) — the concrete six-endpoint set
|
|
194
|
+
that productionized this contract.
|
|
152
195
|
- [POST + JSON-body args for scripted APIs](./scripted-api-post-body-args.md) — the POST /
|
|
153
196
|
JSON-request-body variant for large argument values.
|
|
154
197
|
- [api2 architecture](../architecture.md) — the metadata-driven /v2 CRUD engine that Record
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: TOGa IQ Sprint Dashboard API (Record Scripts)
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
repo: api2
|
|
5
|
+
project: API
|
|
6
|
+
client: shared
|
|
7
|
+
type: feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-07-24
|
|
10
|
+
owners: ["kyalamarthi"]
|
|
11
|
+
files:
|
|
12
|
+
- _underscore/Model/Team/Sprint.php
|
|
13
|
+
- api2/Component/Api/V2/V2.php
|
|
14
|
+
- dbchanges2/Core/2026-07-24a - SprintDashboardRecordScripts.sql
|
|
15
|
+
- dbchanges2/Client_True/2026-07-24b - SprintDashboardScriptAcl.sql
|
|
16
|
+
related:
|
|
17
|
+
- ./record-scripts.md
|
|
18
|
+
- ../architecture.md
|
|
19
|
+
- ../../worker2/features/team-sprint-management.md
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Summary
|
|
23
|
+
|
|
24
|
+
The internal **TOGa IQ sprint dashboard** is served in production by **six api2 Record
|
|
25
|
+
Scripts** on `_Model_Team_Sprint` (`_underscore/Model/Team/Sprint.php`), reading the `Team`
|
|
26
|
+
schema on the core cluster. This replaced the prior session's local Node/React + Express
|
|
27
|
+
(mysql2) stand-in — the prototype only ever proved the metric SQL; this is the real `/v2`
|
|
28
|
+
path. **No api2 code was written**: the endpoints are the generic `/v2` engine plus the
|
|
29
|
+
model methods (which live in `_underscore`) plus metadata rows seeded via dbchanges2.
|
|
30
|
+
|
|
31
|
+
Producer/consumer split: **worker2** (`_Worker_Team_Sprint`) captures ClickUp sprint data
|
|
32
|
+
into `DB_TEAM`; **api2** serves it read-only through these Record Scripts. The dashboard is
|
|
33
|
+
internal-only — its `AclRecordScripts` grant is scoped to the **True** (staff) tenant.
|
|
34
|
+
|
|
35
|
+
For the general authoring/registration contract these follow, see
|
|
36
|
+
[Record Scripts](./record-scripts.md). For the metric definitions the SQL implements, see
|
|
37
|
+
[Team Sprint Management](../../worker2/features/team-sprint-management.md).
|
|
38
|
+
|
|
39
|
+
## The six endpoints
|
|
40
|
+
|
|
41
|
+
All are `public static` methods on `_Model_Team_Sprint`, each run as `new \_Query($sql,
|
|
42
|
+
\_underscore::DB_TEAM)` returning a raw object the `/v2` engine envelopes. Route is
|
|
43
|
+
`GET /v2/sprints/<scriptRoute>` (record route `sprints` → `_Model_Team_Sprint`).
|
|
44
|
+
|
|
45
|
+
| phpMethod | Purpose |
|
|
46
|
+
|-----------|---------|
|
|
47
|
+
| `current` | Resolve the current sprint (never hardcoded — see below). |
|
|
48
|
+
| `sprintTile` | A single KPI tile; selected by **`?category=`** (Committed / Conditional / Stretch / Unplanned). |
|
|
49
|
+
| `statusBreakdown` | Status-rollup pie (the SWITCH-into-five-buckets, default → Review). |
|
|
50
|
+
| `workTypeBreakdown` | Work-type pie — `SUM(sprintPointsNow) GROUP BY workTypeNow`. |
|
|
51
|
+
| `pointsByDev` | Sprint points by developer (Tasks ⋈ Tasks_Developers ⋈ Developers). |
|
|
52
|
+
| `burndown` | Burndown over working days (CCU vs CCUS), `dayNumber` 1-based. |
|
|
53
|
+
|
|
54
|
+
## Envelope nesting — data is under the route-segment key
|
|
55
|
+
|
|
56
|
+
The engine nests a scripted return value **under the script's route segment**, it is not
|
|
57
|
+
flattened: `GET /v2/sprints/tile` returns `data: { tile: {…} }`, and `GET /v2/sprints/current`
|
|
58
|
+
returns `data: { current: {…} }`. A consumer must unwrap `data[<route>]`, not read `data`
|
|
59
|
+
directly. (Same shape as the live `campaigns/jobs` script → `data: { jobs: … }`.) This is the
|
|
60
|
+
single biggest gotcha for the frontend cutover.
|
|
61
|
+
|
|
62
|
+
## Current-state (Power BI) scoring — deliberate, not canonical
|
|
63
|
+
|
|
64
|
+
These endpoints reproduce the **Power BI current-state** numbers the dashboard was validated
|
|
65
|
+
against: done = `dtDone IS NOT NULL` and category from `workTypeNow`. They do **not** use the
|
|
66
|
+
canonical **at-lock** sprint scoring (`workTypeAtLock` + `statusNow IN (STATUS_IN__DONE)`) used
|
|
67
|
+
by the model's `_score*` / `_reliabilityScore` helpers. Reconciling current-state vs canonical
|
|
68
|
+
for a leadership view is a **separate, deliberate decision left open** — do not assume these
|
|
69
|
+
tiles agree with sprint scores. See the two-definitions section in
|
|
70
|
+
[Team Sprint Management](../../worker2/features/team-sprint-management.md).
|
|
71
|
+
|
|
72
|
+
Verified against live prod (sprint 83): the **Conditional** KPI tile reads `workTypeAtLock`
|
|
73
|
+
(72 pts) while the work-type pie reads `workTypeNow` (67 pts) — a real at-lock-vs-now
|
|
74
|
+
divergence, by design, not a bug.
|
|
75
|
+
|
|
76
|
+
## SQL safety (no bound params in `_Query`)
|
|
77
|
+
|
|
78
|
+
`_Query` has no `?`/`:name` binding, so injection safety is by construction, matching the
|
|
79
|
+
model's existing `_score*` helpers:
|
|
80
|
+
|
|
81
|
+
- The sprint number is `(int)`-cast.
|
|
82
|
+
- The `?category=` query-string value is **not interpolated raw** — it is mapped through a
|
|
83
|
+
value allowlist to the `_Model_Team_Task::WORK_TYPE__*` constants; an unrecognized category
|
|
84
|
+
is rejected, never placed into SQL.
|
|
85
|
+
- Every other column/value in the query text is a trusted server-side constant.
|
|
86
|
+
|
|
87
|
+
Work-type values in the `Team` schema are stored **UPPERCASE** (`COMMITTED` / `CONDITIONAL`
|
|
88
|
+
/ `STRETCH` / `UNPLANNED`); the productionized code sources them from the
|
|
89
|
+
`_Model_Team_Task::WORK_TYPE__*` constants rather than mixed-case literals (the prototype's
|
|
90
|
+
`'Committed'` only worked because the collation is case-insensitive).
|
|
91
|
+
|
|
92
|
+
## Registration (two dbchanges2 files)
|
|
93
|
+
|
|
94
|
+
Registered as metadata, not a code deploy (see [Record Scripts](./record-scripts.md) for the
|
|
95
|
+
full contract). Two migrations, both **id-agnostic resolve-by-route + `NOT EXISTS` guards** so
|
|
96
|
+
they are re-runnable and environment-agnostic:
|
|
97
|
+
|
|
98
|
+
- `dbchanges2/Core/2026-07-24a - SprintDashboardRecordScripts.sql` — one `Core.Records` row
|
|
99
|
+
(`route 'sprints'` → `_Model_Team_Sprint`, with `name`/`description`, `aclDatabase = 'CORE'`
|
|
100
|
+
because `Team` is co-located on the core cluster) + six `Core.RecordScripts` rows.
|
|
101
|
+
- `dbchanges2/Client_True/2026-07-24b - SprintDashboardScriptAcl.sql` — the
|
|
102
|
+
`Client.AclRecordScripts` grant. It is a **CLIENT-DB** table, so it lives in the per-client
|
|
103
|
+
**`Client_True`** folder (roles **1 / 3 / 4**). Putting it in the generic `Client/` folder
|
|
104
|
+
would grant the internal dashboard to **every** tenant.
|
|
105
|
+
|
|
106
|
+
## Data source note
|
|
107
|
+
|
|
108
|
+
The core-cluster hosts/credentials the `Team` reads flow through live in api2 `Config/*.ini`
|
|
109
|
+
(`[database]`) — never in knowledge or code.
|
|
110
|
+
|
|
111
|
+
## Open items (frontend cutover)
|
|
112
|
+
|
|
113
|
+
- Frontend `fetchEnvelope` must unwrap `data[<route>]` (see nesting above).
|
|
114
|
+
- Replace the prototype's empty-placeholder `AUTH_TOKEN` with real Bearer-JWT auth, and
|
|
115
|
+
reconcile the ACL role (True 1/3/4) to the dashboard's auth identity.
|
|
116
|
+
- Verify `Tasks_Developers` has a `UNIQUE (taskId, developerId)` key plus sprint/workType
|
|
117
|
+
indexes.
|
|
118
|
+
- `pointsByDev` filters to a first-name allowlist (excludes non-roster devs) — keep that
|
|
119
|
+
allowlist in sync with the roster.
|
|
120
|
+
|
|
121
|
+
## Change history
|
|
122
|
+
|
|
123
|
+
- 2026-07-24 — Productionized the TOGa IQ sprint dashboard as six api2 Record Scripts on
|
|
124
|
+
`_Model_Team_Sprint` (`current`, `sprintTile ?category=`, `statusBreakdown`,
|
|
125
|
+
`workTypeBreakdown`, `pointsByDev`, `burndown`), replacing the local Express/mysql2
|
|
126
|
+
prototype with no api2 code change. Documented the **route-segment envelope nesting**
|
|
127
|
+
(`data[<route>]`), the deliberate Power BI current-state scoring (Conditional tile
|
|
128
|
+
`workTypeAtLock` 72 pts vs work-type pie `workTypeNow` 67 pts on sprint 83), UPPERCASE
|
|
129
|
+
work-type values sourced from `_Model_Team_Task::WORK_TYPE__*`, the `?category=` allowlist
|
|
130
|
+
as the `_Query` injection defense, and the two-file dbchanges2 registration (Core records
|
|
131
|
+
+ `Client_True` ACL grant to roles 1/3/4). (kyalamarthi)
|
|
132
|
+
|
|
133
|
+
## Related docs
|
|
134
|
+
|
|
135
|
+
- [Record Scripts](./record-scripts.md) — the authoring/registration contract these follow.
|
|
136
|
+
- [Team Sprint Management & Reporting](../../worker2/features/team-sprint-management.md) — the
|
|
137
|
+
worker2 producer and the metric definitions the SQL implements.
|
|
138
|
+
- [api2 architecture](../architecture.md) — the metadata-driven `/v2` engine.
|
|
139
|
+
</content>
|
|
140
|
+
</invoke>
|
|
@@ -186,9 +186,11 @@ All sprint state lives in the Team database (`_underscore::DB_TEAM`). Core table
|
|
|
186
186
|
**Column vocabularies & datetime columns (dashboard-relevant).** For current-state reporting the
|
|
187
187
|
exact values matter:
|
|
188
188
|
|
|
189
|
-
- **`workTypeNow` / `workTypeAtLock`** — enum `COMMITTED / CONDITIONAL / STRETCH / UNPLANNED
|
|
190
|
-
MySQL string comparison is **case-insensitive** by default, so
|
|
191
|
-
|
|
189
|
+
- **`workTypeNow` / `workTypeAtLock`** — enum `COMMITTED / CONDITIONAL / STRETCH / UNPLANNED`,
|
|
190
|
+
stored **UPPERCASE** in prod. MySQL string comparison is **case-insensitive** by default, so
|
|
191
|
+
a mixed-case literal (`'Committed'`) still matches — but production code should source these
|
|
192
|
+
from the **`_Model_Team_Task::WORK_TYPE__*` constants** rather than typing literals (the api2
|
|
193
|
+
sprint-dashboard Record Scripts do this).
|
|
192
194
|
- **`statusNow` / `statusAtLock` / `statusAtEnd`** — free-form varchar, values stored
|
|
193
195
|
**lower-case**: `complete`, `to do`, `in progress`, `stage review`, `hotfix review`,
|
|
194
196
|
`back-end review`, `ui review`, `on hold`, `roadblocked`, `awaiting client`, `rework`,
|
|
@@ -319,7 +321,9 @@ Points = `sprintPointsNow`; each task is assigned **one** segment via a SWITCH e
|
|
|
319
321
|
3. else → the task's **`workTypeNow`** value (Committed / Conditional / Unplanned).
|
|
320
322
|
|
|
321
323
|
Joined `Tasks → Tasks_Developers → Developers` and grouped by developer. Because the join is
|
|
322
|
-
many-to-many, each assigned developer is credited the task's **full** `sprintPointsNow`.
|
|
324
|
+
many-to-many, each assigned developer is credited the task's **full** `sprintPointsNow`. The
|
|
325
|
+
chart also filters developers to a **first-name allowlist** (the active roster), so non-roster
|
|
326
|
+
developers who happen to be assigned are excluded from the per-dev breakdown.
|
|
323
327
|
|
|
324
328
|
### Sprint Burndown (over working days)
|
|
325
329
|
Plotted over **working days only** (Mon–Fri, ~10 per two-week sprint; call the count `N`,
|
|
@@ -350,12 +354,21 @@ middleware defaults every tile/chart endpoint to it when no `?sprint=` is suppli
|
|
|
350
354
|
> [per-client database connections](../../_underscore/features/per-client-database-connections.md))
|
|
351
355
|
> by a local Node/React + Express (mysql2) prototype that stands in for the api2 `/v2` engine and
|
|
352
356
|
> returns the standard api2 envelope. The prototype's DB credentials live in an **uncommitted
|
|
353
|
-
> `.env`** (never in the repo or this doc).
|
|
354
|
-
> Scripts registered via
|
|
355
|
-
>
|
|
357
|
+
> `.env`** (never in the repo or this doc). The dashboard has since been **productionized**:
|
|
358
|
+
> the tiles/charts are now six api2 Record Scripts on `_Model_Team_Sprint`, registered via
|
|
359
|
+
> **dbchanges2** and granted to the True tenant — see
|
|
360
|
+
> [TOGa IQ Sprint Dashboard API](../../api2/features/sprint-dashboard-api.md) and the general
|
|
361
|
+
> [Record Scripts](../../api2/features/record-scripts.md) contract. The Express stand-in is
|
|
362
|
+
> retired.
|
|
356
363
|
|
|
357
364
|
## Change history
|
|
358
365
|
|
|
366
|
+
- 2026-07-24 — Productionized the dashboard: the metric definitions above are now served by six
|
|
367
|
+
api2 Record Scripts on `_Model_Team_Sprint` (see
|
|
368
|
+
[Sprint Dashboard API](../../api2/features/sprint-dashboard-api.md)), retiring the Express
|
|
369
|
+
prototype. Refined the data model: work-type values are stored **UPPERCASE** and should be
|
|
370
|
+
sourced from the `_Model_Team_Task::WORK_TYPE__*` constants; the Sprint-Points-By-Dev chart
|
|
371
|
+
filters to a **first-name roster allowlist**. (kyalamarthi)
|
|
359
372
|
- 2026-07-24 — Documented the full **Power BI dashboard metric definitions (DAX → SQL)** for the
|
|
360
373
|
TOGa IQ sprint dashboard: KPI tiles by work type (with the non-uniformity that **Conditional
|
|
361
374
|
filters on `workTypeAtLock`** while Committed/Stretch/Unplanned filter on `workTypeNow`), the
|
package/knowledge/INDEX.md
CHANGED
|
@@ -19,7 +19,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
19
19
|
|
|
20
20
|
- **_underscore** (_Underscore) _(framework core)_ — 38 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
|
|
21
21
|
- **worker2** (Worker) — 31 doc(s) → [2.0/apps/worker2/INDEX.md](2.0/apps/worker2/INDEX.md)
|
|
22
|
-
- **api2** (API) —
|
|
22
|
+
- **api2** (API) — 17 doc(s) → [2.0/apps/api2/INDEX.md](2.0/apps/api2/INDEX.md)
|
|
23
23
|
- **dbchanges2** (Database Changes) _(framework core)_ — 3 doc(s) → [2.0/apps/dbchanges2/INDEX.md](2.0/apps/dbchanges2/INDEX.md)
|
|
24
24
|
- **toga2-supply** (TOGa Supply) — 3 doc(s) → [2.0/apps/toga2-supply/INDEX.md](2.0/apps/toga2-supply/INDEX.md)
|
|
25
25
|
- **saml** (SAML SSO Gateway) — 3 doc(s) → [2.0/apps/saml/INDEX.md](2.0/apps/saml/INDEX.md)
|
|
@@ -6,12 +6,13 @@ apps:
|
|
|
6
6
|
- tools
|
|
7
7
|
- worker2
|
|
8
8
|
- dbchanges2
|
|
9
|
+
- api2
|
|
9
10
|
project: _Underscore
|
|
10
11
|
client: true
|
|
11
12
|
type: profile
|
|
12
13
|
status: active
|
|
13
|
-
updated: 2026-
|
|
14
|
-
owners: [jcardinal]
|
|
14
|
+
updated: 2026-07-24
|
|
15
|
+
owners: [jcardinal, kyalamarthi]
|
|
15
16
|
files: []
|
|
16
17
|
related:
|
|
17
18
|
- clients/true/features/users-personas-data-model.md
|
|
@@ -28,6 +29,11 @@ by staff role (e.g. the planned Toolbox app) reads its `Users` / `Personas` mode
|
|
|
28
29
|
- **Client identifier:** `True`
|
|
29
30
|
- **SSO mapper:** `_Model_True_ClientAuthentication` (base; matches by email from NameID)
|
|
30
31
|
|
|
32
|
+
The internal **TOGa IQ sprint dashboard** is served to True from **api2** as six Record
|
|
33
|
+
Scripts on `_Model_Team_Sprint` (over the `Team` schema), with the `AclRecordScripts` grant
|
|
34
|
+
scoped to True roles 1/3/4 — hence `api2` is in this client's `apps`. See
|
|
35
|
+
[Sprint Dashboard API](../../2.0/apps/api2/features/sprint-dashboard-api.md).
|
|
36
|
+
|
|
31
37
|
The **Tools** app (1.0; repo `tools`) authenticates True users via **SSO** through
|
|
32
38
|
`saml.togahub.com` and reads the `Client_True` DB **read-only** to gate its internal tooling by
|
|
33
39
|
staff persona (see `1.0/apps/tools/`). Its gateway return domain is registered in `Core.Domains`
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: session
|
|
3
|
+
slug: sprint-dashboard
|
|
4
|
+
title: TOGa IQ Sprint Dashboard prototype (React + Express stand-in)
|
|
5
|
+
author: kyalamarthi
|
|
6
|
+
repos: [test]
|
|
7
|
+
framework: "standalone"
|
|
8
|
+
client: shared
|
|
9
|
+
status: active
|
|
10
|
+
created: 2026-07-24
|
|
11
|
+
updated: 2026-07-24
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Session: sprint-dashboard
|
|
15
|
+
**Date:** 2026-07-24
|
|
16
|
+
**Project/Repo:** test (standalone Node/React prototype in `@krishna/React`; reads 2.0 TOGa IQ `Team` data)
|
|
17
|
+
**Task:** Build a Power BI-parity TOGa IQ sprint dashboard in `test/@krishna/React` — React (CDN, no build) frontend + Express/mysql2 backend that stands in for the api2 `/v2` engine — with KPI tiles, Status/Work Type pies, Sprint Burndown, Sprint Points By Dev, and a Time-Progression header, all auto-resolving the current sprint from the live `Team` schema.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## What WORKED
|
|
22
|
+
- **`backend/server.js`** — Express + a single mysql2 pool, `MOCK_MODE` switch, api2 `/v2` envelope (`{transactionId,timestamp,isSuccess,status,error,messages,meta,data}`), and `express.static` serving the frontend with `Cache-Control: no-store`. Live-verified against the `Team` schema (core-cluster reader): server boots "LIVE (querying MySQL)".
|
|
23
|
+
- **Parameterized tile endpoint** `GET /v2/sprints/tile?category=committed|conditional|stretch|unplanned` driven by a `CATEGORIES` map (per-tile `column`+`value`+mock) and a `COLUMN_ALLOWLIST`. Injection-safe: `value`/`sprint` are bound `?` params, the work-type **column** is only ever taken from the server-side allowlist. Verified all four categories, a clean **400** on `category=bogus`, and the back-compat `/v2/sprints/committed-tile` alias.
|
|
24
|
+
- **Status rollup** (`/status-breakdown`) mirrors the Power BI "Status now Category Group" SWITCH (default→Review); returns **both `count` and `points`** per bucket. **Work Type pie** (`/worktype-breakdown`) = `SUM(sprintPointsNow) GROUP BY workTypeNow`. **Points-by-Dev** (`/points-by-dev`) joins `Tasks→Tasks_Developers→Developers` (many-to-many, full points per dev) with the "New Status" SWITCH (`dtCompleted`→Complete; else statusNow-not-in-active→In Review; else workTypeNow). **Burndown** (`/burndown`) = Target `CCU×(1−day/N)` + actual `total−done-to-date` (CCU vs CCUS) over Mon–Fri working days, actuals stop at today. All live-verified for sprint 83.
|
|
25
|
+
- **Current-sprint auto-resolution** — a `/v2/sprints` middleware defaults every endpoint to the sprint whose range contains today (`CURDATE() BETWEEN dateStart AND dateEnd`, else most recently started), and `GET /v2/sprints/current` feeds the header. Verified → auto-resolves to **sprint 83** (Jul 22–Aug 4); no hardcoded sprint anywhere.
|
|
26
|
+
- **`frontend/dashboard.html`** — 2×2 grid (tiles top; Status/Work Type left column `1fr`; Burndown/Points-by-Dev right column `2fr`), fits one viewport, no scroll. Reusable `Donut`/`PieChart`/`Legend`/`StackedBarChart`/`BurndownChart` + shared `fetchEnvelope`/`useApi`. Time-Progression header bar (business hours) + Full-screen button. Verified via browser screenshots and `evaluate_script` (10px row gaps, `docScrollH == viewportH`).
|
|
27
|
+
- **`frontend/dashboard1.html`** — copy of dashboard.html, since customized into a **donut** variant (center totals + value legend). `USE_MOCK=false` (live). Its **Status donut shows POINTS** (center "208 points", legend point values) via `b.points` + `unit="points"`.
|
|
28
|
+
- **Knowledge captured** — `/capture` UPDATED `knowledge/2.0/apps/worker2/features/team-sprint-management.md` with the full DAX→SQL metric definitions + Team-schema precision; **pushed to `_main`**.
|
|
29
|
+
|
|
30
|
+
## What did NOT work — DO NOT RETRY THESE
|
|
31
|
+
- **Layout attempt #1:** `.page { min-height:100vh }` + `.main-grid { grid-template-rows: minmax(240px,1fr) minmax(240px,1fr) }`. On a 768px viewport the grid grew to **750px** and the bottom row was clipped ~170px (`document.scrollHeight = 939 > 768`). Reason: `min-height` + the `minmax` row **minimums** let the grid exceed the viewport instead of shrinking.
|
|
32
|
+
- **Layout attempt #2:** `.page { height:100vh; overflow:hidden }` + `grid-template-rows: 1fr 1fr` **alone** → rows still **overlapped by exactly 20px** (row-1 cards ran 169→490, row-2 started at 470). Reason: cards were `box-sizing: content-box`, so their `14px×2` padding + `1px×2` border added **30px on top** of the computed `290.5px` track height → each card rendered **321px** and overflowed its grid row. **Root fix:** global `*, *::before, *::after { box-sizing: border-box }`.
|
|
33
|
+
- **Absolutely-positioning the chart SVGs** (`.chart-fill { position:absolute; inset:0 }`) did **not** fix the row overlap on its own — the overlap was the card padding (content-box), not the SVG's intrinsic height. (Kept the absolute-fill regardless — it's correct for scaling charts to their cell — but it was not the fix.)
|
|
34
|
+
- **`/committed-tile` alias via `app._router.handle(req,res)`** — reached into Express internals and was fragile. Refactored both routes to call a shared `serveTile()` function instead.
|
|
35
|
+
- **First full-content Write to each new file** was denied once by the fact-forcing gate (keys on path, ignores content). Use stub-first for large new files to avoid re-emitting the whole body.
|
|
36
|
+
|
|
37
|
+
## Not tried yet (candidates for next session)
|
|
38
|
+
- **Productionize the tiles as real api2 Record Scripts** (`_Model_Team_Sprint::sprintTile` on `_underscore`, registered via **dbchanges2**) to replace the Express stand-in. `sql/register_committed_tile_endpoint.sql` is the starting template. This is the natural next step (user asked how the real data path works: worker2 ingests → Team DB → api2 Record Script serves).
|
|
39
|
+
- **Auth** — `AUTH_TOKEN` in the frontend is an empty placeholder; wire the Bearer JWT + confirm header names against a real `/v2/auth` call.
|
|
40
|
+
- **Reconcile current-state vs canonical scoring** — dashboard uses Power BI current-state (`dtDone`+`workTypeNow`); canonical scoring uses `statusNow IN DONE`+`workTypeAtLock`. Decide which a leadership-facing view should use.
|
|
41
|
+
- **Burndown day-numbering** — currently 1-based (Target starts at 90% on day 1, hits 0 on day N). Confirm against Power BI whether it should be 0-based (start at 100%). One-char change (`k=i` vs `k=i+1`).
|
|
42
|
+
- **Multi-dev point splitting** — Points-by-Dev currently credits each assigned dev the task's full points (matches the KB rule). Add a split option only if Power BI splits.
|
|
43
|
+
- **Time-progression live refresh** — currently a snapshot computed on page load; add a timer if a wall display needs it to tick.
|
|
44
|
+
|
|
45
|
+
## Current file state
|
|
46
|
+
| File | Status | Notes |
|
|
47
|
+
|------|--------|-------|
|
|
48
|
+
| `test/@krishna/React/backend/server.js` | Created/rewritten | Live-verified; current-sprint middleware + 6 `/v2/sprints/*` endpoints; injection-safe; `no-store`; `FALLBACK_SPRINT=82` only if table empty. |
|
|
49
|
+
| `test/@krishna/React/frontend/dashboard.html` | Created | 2×2 dashboard; Status shows task **counts**; box-sizing fix applied; live. |
|
|
50
|
+
| `test/@krishna/React/frontend/dashboard1.html` | Created (copy, then edited) | Donut variant; `USE_MOCK=false`; Status donut shows **points** (`unit="points"`); live. |
|
|
51
|
+
| `test/@krishna/React/frontend/committed-tile.html` | Untouched | Original single-tile page; left as-is. |
|
|
52
|
+
| `test/@krishna/React/sql/tile_query.sql` | Created | Parameterized query reference (documents column/value per tile). |
|
|
53
|
+
| `test/@krishna/React/sql/committed_tile.sql`, `register_committed_tile_endpoint.sql` | Pre-existing | The latter is the api2 Record Script registration template for productionizing. |
|
|
54
|
+
| `test/@krishna/React/backend/.env` | Present, NOT committed | `MOCK_MODE=false`, `DB_NAME=Team`, core-cluster reader host + `admin` user. Credentials live here only — never commit. |
|
|
55
|
+
| `knowledge/2.0/apps/worker2/features/team-sprint-management.md` | UPDATED + pushed | DAX→SQL metric definitions + Team schema vocabularies (`dtDone` vs `dtCompleted`, many-to-many points). |
|
|
56
|
+
|
|
57
|
+
## Decisions made
|
|
58
|
+
- **One parameterized tile endpoint** over four near-identical routes (DRY). Rejected: separate `committed-tile`/`stretch-tile`/… files (the original single-tile approach) — too much duplication for a 4-tile dashboard.
|
|
59
|
+
- **Backend resolves the current sprint per request** (middleware) and the frontend sends **no `?sprint=`**. Rationale: "always show the current sprint, never hardcode." Accepted the minor cost of an extra resolve query per endpoint; `/current` skips the middleware to avoid a double query.
|
|
60
|
+
- **Express + mysql2 as a local api2 stand-in** that imitates the `/v2` envelope, so the React frontend is drop-in for the real api2 later. Rejected: querying the DB from the browser (impossible/insecure) or building the real Record Script now (out of scope for the prototype).
|
|
61
|
+
- **Knowledge placed in `worker2/features/team-sprint-management.md`**, not a new `test/` doc. Rationale: that doc already owns the Team-schema + sprint-metric subject and is cross-linked from api2; the `test` sandbox's per-developer folders are excluded from the KB.
|
|
62
|
+
- **`dashboard1.html` is a separate file** for the donut/points variant; `dashboard.html` stays on task counts. Rationale: user wanted a distinct second dashboard without changing the first.
|
|
63
|
+
- **Global `box-sizing: border-box`** — the correct fix for the row-overlap (not min-heights, not absolute positioning).
|
|
64
|
+
|
|
65
|
+
## Blockers
|
|
66
|
+
None functional. Operational note: **the dev server does not persist across Claude sessions** — it must be restarted each time (`cd test/@krishna/React/backend && npm start`). It also serves `dashboard1.html` automatically (static folder).
|
|
67
|
+
|
|
68
|
+
## Exact next step
|
|
69
|
+
> Restart the server (`cd c:/www/test/@krishna/React/backend && npm start`) and open `http://localhost:3001/dashboard1.html` to confirm it still auto-resolves the current sprint. Then, to move off the prototype stand-in, implement the **api2 Record Script** version of the tiles — `_Model_Team_Sprint::sprintTile()` on `_underscore`, registered via **dbchanges2** using `sql/register_committed_tile_endpoint.sql` as the template — so the dashboard fetches through the real api2/`_underscore` path (worker2 keeps populating the `Team` tables; api2 serves them).
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
_Saved by /session-save on 2026-07-24_
|
package/package.json
CHANGED