From 552adc5e7253825691babe4ea86b29d8627fbada Mon Sep 17 00:00:00 2001 From: 1Pro-L4R8 Date: Thu, 22 Oct 2026 09:04:07 +0700 Subject: [PATCH 1/2] docs(reference): regenerate the REST reference from platform main Co-Authored-By: Claude Opus 5.5 --- platform/reference/rest-api/data-api.md | 22 ++++++- platform/reference/rest-api/datasets.md | 86 ++++++++++++++++++++++--- platform/reference/rest-api/index.md | 2 +- 3 files changed, 99 insertions(+), 11 deletions(-) diff --git a/platform/reference/rest-api/data-api.md b/platform/reference/rest-api/data-api.md index 171e1d6..f847a07 100644 --- a/platform/reference/rest-api/data-api.md +++ b/platform/reference/rest-api/data-api.md @@ -160,6 +160,8 @@ Operation id: `list_endpoints`. Auth: Session cookie. Forbidden (403): Missing d | `source_kind` | string | yes | | | `source_ref` | string (uuid), nullable | no | | | `live_version_id` | string (uuid), nullable | no | The version currently served. Never written by this module — publishing is a separate lifecycle. | +| `live_method` | string, nullable | no | The method and path the LIVE version is served at, or `null` for an endpoint never published. Written only by publish and rollback, from the snapshot they make live; the serving router matches these, never `method`/`path`. When they differ... | +| `live_path` | string, nullable | no | See [`Self::live_method`]. | | `state` | string | yes | | | `created_by` | string (uuid), nullable | no | | | `created_at` | string (date-time) | yes | | @@ -221,6 +223,8 @@ Operation id: `create_endpoint`. Auth: Session cookie and `X-Egret-CSRF` header. | `source_kind` | string | yes | | | `source_ref` | string (uuid), nullable | no | | | `live_version_id` | string (uuid), nullable | no | The version currently served. Never written by this module — publishing is a separate lifecycle. | +| `live_method` | string, nullable | no | The method and path the LIVE version is served at, or `null` for an endpoint never published. Written only by publish and rollback, from the snapshot they make live; the serving router matches these, never `method`/`path`. When they differ... | +| `live_path` | string, nullable | no | See [`Self::live_method`]. | | `state` | string | yes | | | `created_by` | string (uuid), nullable | no | | | `created_at` | string (date-time) | yes | | @@ -234,7 +238,7 @@ Operation id: `create_endpoint`. Auth: Session cookie and `X-Egret-CSRF` header. | 401 | Not authenticated | | 403 | Missing data_api_endpoint:manage permission — :read does not imply authoring | | 404 | No such database in this workspace | -| 409 | Another endpoint already serves that method and path | +| 409 | Another endpoint's draft already sits at that method and path | | 500 | Internal error | ## GET `/api/ws/{workspace}/data-api/endpoints/{id}` @@ -267,6 +271,8 @@ Operation id: `get_endpoint`. Auth: Session cookie. Forbidden (403): Missing dat | `source_kind` | string | yes | | | `source_ref` | string (uuid), nullable | no | | | `live_version_id` | string (uuid), nullable | no | The version currently served. Never written by this module — publishing is a separate lifecycle. | +| `live_method` | string, nullable | no | The method and path the LIVE version is served at, or `null` for an endpoint never published. Written only by publish and rollback, from the snapshot they make live; the serving router matches these, never `method`/`path`. When they differ... | +| `live_path` | string, nullable | no | See [`Self::live_method`]. | | `state` | string | yes | | | `created_by` | string (uuid), nullable | no | | | `created_at` | string (date-time) | yes | | @@ -330,6 +336,8 @@ Operation id: `update_endpoint`. Auth: Session cookie and `X-Egret-CSRF` header. | `source_kind` | string | yes | | | `source_ref` | string (uuid), nullable | no | | | `live_version_id` | string (uuid), nullable | no | The version currently served. Never written by this module — publishing is a separate lifecycle. | +| `live_method` | string, nullable | no | The method and path the LIVE version is served at, or `null` for an endpoint never published. Written only by publish and rollback, from the snapshot they make live; the serving router matches these, never `method`/`path`. When they differ... | +| `live_path` | string, nullable | no | See [`Self::live_method`]. | | `state` | string | yes | | | `created_by` | string (uuid), nullable | no | | | `created_at` | string (date-time) | yes | | @@ -343,7 +351,7 @@ Operation id: `update_endpoint`. Auth: Session cookie and `X-Egret-CSRF` header. | 401 | Not authenticated | | 403 | Missing data_api_endpoint:manage permission | | 404 | No such endpoint, or no such database, in this workspace | -| 409 | Another endpoint already serves that method and path, or the endpoint is in the trash — restore it before editing it | +| 409 | Another endpoint's draft already sits at that method and path, or the endpoint is in the trash — restore it before editing it. A new path or method on a live endpoint's draft is saved but not served: it goes live at the next publish | | 500 | Internal error | ## DELETE `/api/ws/{workspace}/data-api/endpoints/{id}` @@ -448,6 +456,8 @@ Operation id: `disable_endpoint`. Auth: Session cookie and `X-Egret-CSRF` header | `source_kind` | string | yes | | | `source_ref` | string (uuid), nullable | no | | | `live_version_id` | string (uuid), nullable | no | The version currently served. Never written by this module — publishing is a separate lifecycle. | +| `live_method` | string, nullable | no | The method and path the LIVE version is served at, or `null` for an endpoint never published. Written only by publish and rollback, from the snapshot they make live; the serving router matches these, never `method`/`path`. When they differ... | +| `live_path` | string, nullable | no | See [`Self::live_method`]. | | `state` | string | yes | | | `created_by` | string (uuid), nullable | no | | | `created_at` | string (date-time) | yes | | @@ -495,6 +505,8 @@ The other half of `disable`, and the reason `disable` keeps `live_version_id`: w | `source_kind` | string | yes | | | `source_ref` | string (uuid), nullable | no | | | `live_version_id` | string (uuid), nullable | no | The version currently served. Never written by this module — publishing is a separate lifecycle. | +| `live_method` | string, nullable | no | The method and path the LIVE version is served at, or `null` for an endpoint never published. Written only by publish and rollback, from the snapshot they make live; the serving router matches these, never `method`/`path`. When they differ... | +| `live_path` | string, nullable | no | See [`Self::live_method`]. | | `state` | string | yes | | | `created_by` | string (uuid), nullable | no | | | `created_at` | string (date-time) | yes | | @@ -528,6 +540,7 @@ Operation id: `list_endpoint_grants`. Auth: Session cookie. Forbidden (403): Mis | `service_account_id` | string (uuid) | yes | The granted service account (its `users.id`). | | `service_account_name` | string, nullable | no | The account's display name, so the list is readable without a second call. `None` if the account row has since been deleted. | | `created_at` | string (date-time) | yes | | +| `inherits_invoke` | boolean | yes | Whether the account ALSO reaches invoke through the workspace (a workspace role carrying `data_api_endpoint:invoke`), so that revoking this grant would not remove its access. Asked of the PDP at workspace scope, where this grant cannot... | | Status | Meaning | |---|---| @@ -561,6 +574,7 @@ Operation id: `grant_endpoint_invoke`. Auth: Session cookie and `X-Egret-CSRF` h | `service_account_id` | string (uuid) | yes | The granted service account (its `users.id`). | | `service_account_name` | string, nullable | no | The account's display name, so the list is readable without a second call. `None` if the account row has since been deleted. | | `created_at` | string (date-time) | yes | | +| `inherits_invoke` | boolean | yes | Whether the account ALSO reaches invoke through the workspace (a workspace role carrying `data_api_endpoint:invoke`), so that revoking this grant would not remove its access. Asked of the PDP at workspace scope, where this grant cannot... | | Status | Meaning | |---|---| @@ -707,6 +721,8 @@ Comes back `disabled`, never `live`. See [`EndpointStore::restore`]. | `source_kind` | string | yes | | | `source_ref` | string (uuid), nullable | no | | | `live_version_id` | string (uuid), nullable | no | The version currently served. Never written by this module — publishing is a separate lifecycle. | +| `live_method` | string, nullable | no | The method and path the LIVE version is served at, or `null` for an endpoint never published. Written only by publish and rollback, from the snapshot they make live; the serving router matches these, never `method`/`path`. When they differ... | +| `live_path` | string, nullable | no | See [`Self::live_method`]. | | `state` | string | yes | | | `created_by` | string (uuid), nullable | no | | | `created_at` | string (date-time) | yes | | @@ -790,6 +806,8 @@ Operation id: `trash_endpoint`. Auth: Session cookie and `X-Egret-CSRF` header. | `source_kind` | string | yes | | | `source_ref` | string (uuid), nullable | no | | | `live_version_id` | string (uuid), nullable | no | The version currently served. Never written by this module — publishing is a separate lifecycle. | +| `live_method` | string, nullable | no | The method and path the LIVE version is served at, or `null` for an endpoint never published. Written only by publish and rollback, from the snapshot they make live; the serving router matches these, never `method`/`path`. When they differ... | +| `live_path` | string, nullable | no | See [`Self::live_method`]. | | `state` | string | yes | | | `created_by` | string (uuid), nullable | no | | | `created_at` | string (date-time) | yes | | diff --git a/platform/reference/rest-api/datasets.md b/platform/reference/rest-api/datasets.md index 47e8aad..c10f32c 100644 --- a/platform/reference/rest-api/datasets.md +++ b/platform/reference/rest-api/datasets.md @@ -8,7 +8,7 @@ description: REST endpoints in the datasets group. BI datasets: sources, columns, metrics, folders, trash + bookmarks. -This page lists 21 endpoints (OpenAPI tag `datasets`). It is generated from `openapi.json`; see the [REST API overview](../rest-api) for authentication, CSRF and error conventions. +This page lists 22 endpoints (OpenAPI tag `datasets`). It is generated from `openapi.json`; see the [REST API overview](../rest-api) for authentication, CSRF and error conventions. | Method | Path | Operation id | Notes | |---|---|---|---| @@ -24,6 +24,7 @@ This page lists 21 endpoints (OpenAPI tag `datasets`). It is generated from `ope | PUT | `/api/ws/{workspace}/datasets/{id}/columns/{column_name}` | `ds_update_column` | | | DELETE | `/api/ws/{workspace}/datasets/{id}/columns/{column_name}` | `ds_delete_column` | | | POST | `/api/ws/{workspace}/datasets/{id}/data` | `ds_data` | Object-scoped dataset data-preview: runs ONLY a bounded `SELECT * FROM <the dataset's own source> LIMIT n` (n from the optional body, clamped to [100, 1000]). | +| POST | `/api/ws/{workspace}/datasets/{id}/duplicate` | `ds_duplicate` | | | PATCH | `/api/ws/{workspace}/datasets/{id}/folder` | `ds_set_folder` | | | POST | `/api/ws/{workspace}/datasets/{id}/metrics` | `ds_add_metric` | | | PUT | `/api/ws/{workspace}/datasets/{id}/metrics/{metric_id}` | `ds_update_metric` | | @@ -105,6 +106,7 @@ Operation id: `ds_create`. Auth: Session cookie and `X-Egret-CSRF` header. Forbi | `certified` | boolean | yes | Certified as a trusted/official dataset. | | `certified_by` | string | yes | | | `certification_details` | string | yes | | +| `deleted_at` | string (date-time), nullable | no | When the dataset was moved to the trash; null while it is live. Its charts keep running until it is deleted, and say so. | | Status | Meaning | |---|---| @@ -143,6 +145,7 @@ Operation id: `ds_get`. Auth: Session cookie. Forbidden (403): Missing dataset:r | `certified` | boolean | yes | Certified as a trusted/official dataset. | | `certified_by` | string | yes | | | `certification_details` | string | yes | | +| `deleted_at` | string (date-time), nullable | no | When the dataset was moved to the trash; null while it is live. Its charts keep running until it is deleted, and say so. | | Status | Meaning | |---|---| @@ -189,6 +192,7 @@ Operation id: `ds_update`. Auth: Session cookie and `X-Egret-CSRF` header. Forbi | `certified` | boolean | yes | Certified as a trusted/official dataset. | | `certified_by` | string | yes | | | `certification_details` | string | yes | | +| `deleted_at` | string (date-time), nullable | no | When the dataset was moved to the trash; null while it is live. Its charts keep running until it is deleted, and say so. | | Status | Meaning | |---|---| @@ -206,6 +210,7 @@ Operation id: `ds_remove`. Auth: Session cookie and `X-Egret-CSRF` header. Forbi |---|---|---|---|---| | `workspace` | path | string | yes | Workspace slug | | `id` | path | string (uuid) | yes | Dataset id | +| `confirm` | query | string | no | The dataset's exact name. Required while charts are built on it: with it, those charts are deleted too. | | `workspace` | path | string | yes | | **Response 200 (application/json)**: `Ack` @@ -220,6 +225,7 @@ Operation id: `ds_remove`. Auth: Session cookie and `X-Egret-CSRF` header. Forbi | 401 | Not authenticated | | 403 | Missing dataset:manage permission, or not the dataset owner | | 404 | No such dataset in this workspace | +| 409 | Charts are built on this dataset (live or in the trash) and `confirm` is not its name. Body: `\{error, code: "in_use", charts: [\{id, name, viz_type, trashed\}]\}` | | 500 | Internal error | ## POST `/api/ws/{workspace}/datasets/{id}/bookmark` @@ -306,6 +312,7 @@ Operation id: `ds_set_certification`. Auth: Session cookie and `X-Egret-CSRF` he | `certified` | boolean | yes | Certified as a trusted/official dataset. | | `certified_by` | string | yes | | | `certification_details` | string | yes | | +| `deleted_at` | string (date-time), nullable | no | When the dataset was moved to the trash; null while it is live. Its charts keep running until it is deleted, and say so. | | Status | Meaning | |---|---| @@ -353,11 +360,12 @@ Operation id: `ds_add_column`. Auth: Session cookie and `X-Egret-CSRF` header. F | `certified` | boolean | yes | Certified as a trusted/official dataset. | | `certified_by` | string | yes | | | `certification_details` | string | yes | | +| `deleted_at` | string (date-time), nullable | no | When the dataset was moved to the trash; null while it is live. Its charts keep running until it is deleted, and say so. | | Status | Meaning | |---|---| | 200 | Calculated column added (expression validated) | -| 400 | Invalid column expression | +| 400 | Empty column name, or invalid column expression | | 401 | Not authenticated | | 403 | Missing dataset:manage permission, or not the dataset owner | | 404 | No such dataset in this workspace | @@ -406,6 +414,7 @@ Operation id: `ds_update_column`. Auth: Session cookie and `X-Egret-CSRF` header | `certified` | boolean | yes | Certified as a trusted/official dataset. | | `certified_by` | string | yes | | | `certification_details` | string | yes | | +| `deleted_at` | string (date-time), nullable | no | When the dataset was moved to the trash; null while it is live. Its charts keep running until it is deleted, and say so. | | Status | Meaning | |---|---| @@ -446,6 +455,7 @@ Operation id: `ds_delete_column`. Auth: Session cookie and `X-Egret-CSRF` header | `certified` | boolean | yes | Certified as a trusted/official dataset. | | `certified_by` | string | yes | | | `certification_details` | string | yes | | +| `deleted_at` | string (date-time), nullable | no | When the dataset was moved to the trash; null while it is live. Its charts keep running until it is deleted, and say so. | | Status | Meaning | |---|---| @@ -490,6 +500,53 @@ Object-scoped dataset data-preview: runs ONLY a bounded `SELECT * FROM <the d | 500 | Internal error | | 502 | Query engine error | +## POST `/api/ws/{workspace}/datasets/{id}/duplicate` + +Operation id: `ds_duplicate`. Auth: Session cookie and `X-Egret-CSRF` header. Forbidden (403): No dataset:read on the source, or no dataset:manage (or the database is not granted). + +| Parameter | In | Type | Required | Description | +|---|---|---|---|---| +| `workspace` | path | string | yes | Workspace slug | +| `id` | path | string (uuid) | yes | Dataset id | +| `workspace` | path | string | yes | | + +**Request body (application/json)**: `DuplicateDataset` + +| Field | Type | Required | Description | +|---|---|---|---| +| `name` | string | yes | Name of the copy; unique in the workspace. | + +**Response 200 (application/json)**: `DatasetDetailDto` + +| Field | Type | Required | Description | +|---|---|---|---| +| `id` | string | yes | | +| `name` | string | yes | | +| `description` | string | yes | | +| `database_id` | string | yes | | +| `kind` | string | yes | | +| `catalog` | string, nullable | no | | +| `schema` | string, nullable | no | | +| `table` | string, nullable | no | | +| `sql` | string, nullable | no | | +| `updated_at` | string (date-time) | yes | | +| `columns` | DatasetColumnDto[] | yes | | +| `metrics` | MetricDto[] | yes | | +| `certified` | boolean | yes | Certified as a trusted/official dataset. | +| `certified_by` | string | yes | | +| `certification_details` | string | yes | | +| `deleted_at` | string (date-time), nullable | no | When the dataset was moved to the trash; null while it is live. Its charts keep running until it is deleted, and say so. | + +| Status | Meaning | +|---|---| +| 200 | The copy: same source, every column setting and metric (not the certification) | +| 400 | Empty name | +| 401 | Not authenticated | +| 403 | No dataset:read on the source, or no dataset:manage (or the database is not granted) | +| 404 | No such dataset in this workspace | +| 409 | A dataset with this name already exists | +| 500 | Internal error | + ## PATCH `/api/ws/{workspace}/datasets/{id}/folder` Operation id: `ds_set_folder`. Auth: Session cookie and `X-Egret-CSRF` header. Forbidden (403): Missing dataset:manage permission. @@ -559,11 +616,12 @@ Operation id: `ds_add_metric`. Auth: Session cookie and `X-Egret-CSRF` header. F | `certified` | boolean | yes | Certified as a trusted/official dataset. | | `certified_by` | string | yes | | | `certification_details` | string | yes | | +| `deleted_at` | string (date-time), nullable | no | When the dataset was moved to the trash; null while it is live. Its charts keep running until it is deleted, and say so. | | Status | Meaning | |---|---| | 200 | Metric added (expression validated) | -| 400 | Invalid metric expression | +| 400 | Invalid metric expression, or not an aggregate | | 401 | Not authenticated | | 403 | Missing dataset:manage permission, or not the dataset owner | | 404 | No such dataset in this workspace | @@ -609,14 +667,16 @@ Operation id: `ds_update_metric`. Auth: Session cookie and `X-Egret-CSRF` header | `certified` | boolean | yes | Certified as a trusted/official dataset. | | `certified_by` | string | yes | | | `certification_details` | string | yes | | +| `deleted_at` | string (date-time), nullable | no | When the dataset was moved to the trash; null while it is live. Its charts keep running until it is deleted, and say so. | | Status | Meaning | |---|---| -| 200 | Metric updated (expression validated) | -| 400 | Invalid metric expression | +| 200 | Metric updated (expression validated). A rename rewrites the charts that use the metric in the same transaction. | +| 400 | Invalid metric expression, or not an aggregate | | 401 | Not authenticated | | 403 | Missing dataset:manage permission, or not the dataset owner | | 404 | No such dataset, or metric not in this dataset | +| 409 | Name taken, or a rename of a used metric whose old or new name is also a column name (the charts could not be rewritten unambiguously). Body of the latter: `\{error, code: "in_use", charts\}` | | 500 | Internal error | ## DELETE `/api/ws/{workspace}/datasets/{id}/metrics/{metric_id}` @@ -628,6 +688,7 @@ Operation id: `ds_delete_metric`. Auth: Session cookie and `X-Egret-CSRF` header | `workspace` | path | string | yes | Workspace slug | | `id` | path | string (uuid) | yes | Dataset id | | `metric_id` | path | string (uuid) | yes | Metric id | +| `confirm` | query | string | no | The metric's exact name. Required while charts use it: with it, the metric is deleted and those charts stop running. | | `workspace` | path | string | yes | | **Response 200 (application/json)**: `DatasetDetailDto` @@ -649,6 +710,7 @@ Operation id: `ds_delete_metric`. Auth: Session cookie and `X-Egret-CSRF` header | `certified` | boolean | yes | Certified as a trusted/official dataset. | | `certified_by` | string | yes | | | `certification_details` | string | yes | | +| `deleted_at` | string (date-time), nullable | no | When the dataset was moved to the trash; null while it is live. Its charts keep running until it is deleted, and say so. | | Status | Meaning | |---|---| @@ -656,6 +718,7 @@ Operation id: `ds_delete_metric`. Auth: Session cookie and `X-Egret-CSRF` header | 401 | Not authenticated | | 403 | Missing dataset:manage permission, or not the dataset owner | | 404 | No such dataset, or metric not in this dataset | +| 409 | Charts use this metric and `confirm` is not its name. Body: `\{error, code: "in_use", charts\}` | | 500 | Internal error | ## PUT `/api/ws/{workspace}/datasets/{id}/metrics/{metric_id}/certification` @@ -695,6 +758,7 @@ Operation id: `ds_set_metric_certification`. Auth: Session cookie and `X-Egret-C | `certified` | boolean | yes | Certified as a trusted/official dataset. | | `certified_by` | string | yes | | | `certification_details` | string | yes | | +| `deleted_at` | string (date-time), nullable | no | When the dataset was moved to the trash; null while it is live. Its charts keep running until it is deleted, and say so. | | Status | Meaning | |---|---| @@ -714,7 +778,7 @@ Operation id: `ds_refresh`. Auth: Session cookie and `X-Egret-CSRF` header. Forb | `id` | path | string (uuid) | yes | Dataset id | | `workspace` | path | string | yes | | -**Response 200 (application/json)**: `DatasetDetailDto` +**Response 200 (application/json)**: `DatasetRefreshDto` | Field | Type | Required | Description | |---|---|---|---| @@ -733,10 +797,13 @@ Operation id: `ds_refresh`. Auth: Session cookie and `X-Egret-CSRF` header. Forb | `certified` | boolean | yes | Certified as a trusted/official dataset. | | `certified_by` | string | yes | | | `certification_details` | string | yes | | +| `deleted_at` | string (date-time), nullable | no | When the dataset was moved to the trash; null while it is live. Its charts keep running until it is deleted, and say so. | +| `added_columns` | string[] | yes | | +| `removed_columns` | string[] | yes | | | Status | Meaning | |---|---| -| 200 | Physical columns reconciled from the source | +| 200 | Physical columns reconciled from the source; names the columns added and removed. A column that kept its engine type keeps its Time switch. | | 401 | Not authenticated | | 403 | Missing dataset:manage permission, or not the dataset owner | | 404 | No such dataset in this workspace | @@ -798,6 +865,7 @@ Operation id: `ds_usage`. Auth: Session cookie. Forbidden (403): Missing dataset |---|---|---|---|---| | `workspace` | path | string | yes | Workspace slug | | `id` | path | string (uuid) | yes | Dataset id | +| `include_trashed` | query | boolean | no | Also list charts in the trash: every chart a delete of the dataset would delete with it | | `workspace` | path | string | yes | | **Response 200 (application/json)**: array of `DatasetUsageDto` @@ -806,10 +874,12 @@ Operation id: `ds_usage`. Auth: Session cookie. Forbidden (403): Missing dataset |---|---|---|---| | `id` | string | yes | | | `name` | string | yes | | +| `viz_type` | string | yes | The chart's viz type, for its icon. | +| `trashed` | boolean | yes | The chart is in the trash (listed only with `include_trashed`). | | Status | Meaning | |---|---| -| 200 | Charts that use this dataset | +| 200 | Charts that use this dataset, live ones first | | 403 | Missing dataset:read permission | | 404 | No such dataset in this workspace | diff --git a/platform/reference/rest-api/index.md b/platform/reference/rest-api/index.md index 281686b..b5d3cab 100644 --- a/platform/reference/rest-api/index.md +++ b/platform/reference/rest-api/index.md @@ -118,7 +118,7 @@ described by their auth line here. | Explore (BI) | [Databases](rest-api/databases) | 9 | | Explore (BI) | [Queries](rest-api/queries) | 6 | | Explore (BI) | [SQL Lab](rest-api/sqllab) | 15 | -| Explore (BI) | [Datasets](rest-api/datasets) | 21 | +| Explore (BI) | [Datasets](rest-api/datasets) | 22 | | Explore (BI) | [Charts](rest-api/charts) | 14 | | Explore (BI) | [Dashboards](rest-api/dashboards) | 15 | | Explore (BI) | [Folders](rest-api/folders) | 4 | From 268c632ac147dc1b296d3d9923d7b19241c9a17d Mon Sep 17 00:00:00 2001 From: 1Pro-L4R8 Date: Thu, 22 Oct 2026 09:19:37 +0700 Subject: [PATCH 2/2] docs: Wave 2 fixes (KI-004 to KI-211, KI-247 to KI-259) Describe the product as it is on platform main after #720, #721, #724 and #725: SQL Lab row limit that keeps ORDER BY and marks cut results, rounded floats, cancel and pre-start error panels, the timeout message, Cmd K in the editor; chart export (.png only for chart images, 10,000 rows), Progress goal, row chart order, map unmatched-region note, Change keeping cartesian config; dataset delete refused while charts use it, typed confirmation, Trash notice, metric rename rewrite, Dimension and Filter switches, display names, Duplicate for both kinds; Data API live route and Pending publish, honest revoke note, constraint labels. Close 38 known-issue rows (KI-101 partly) and add KI-247 to KI-259. Co-Authored-By: Claude Opus 5.5 --- platform/data-api/endpoints/access.md | 7 +- platform/data-api/endpoints/config.md | 4 +- platform/data-api/endpoints/index.md | 6 +- platform/data-api/endpoints/parameters.md | 15 ++- platform/data-api/endpoints/publishing.md | 13 ++- platform/data-api/endpoints/try-it.md | 2 +- platform/data-api/openapi.md | 2 +- platform/data-api/overview.md | 2 +- .../troubleshooting/engine-406-502.md | 2 +- .../troubleshooting/queries-fail.md | 7 +- platform/explore/charts/cartesian-settings.md | 2 +- platform/explore/charts/column-formatting.md | 2 +- platform/explore/charts/funnel.md | 3 +- platform/explore/charts/gauge.md | 4 +- platform/explore/charts/map.md | 4 +- platform/explore/charts/number.md | 3 +- platform/explore/charts/progress.md | 10 +- platform/explore/charts/row.md | 12 +-- platform/explore/charts/table.md | 2 + platform/explore/charts/the-chart-editor.md | 14 +-- platform/explore/charts/waterfall.md | 2 +- .../explore/dashboards/export-and-reports.md | 6 +- platform/explore/dashboards/filters.md | 2 +- .../calculated-columns.md | 10 +- .../explore/datasets-and-metrics/columns.md | 26 ++++-- .../creating-a-dataset.md | 16 +++- .../explore/datasets-and-metrics/index.md | 6 +- .../explore/datasets-and-metrics/metrics.md | 18 ++-- .../explore/datasets-and-metrics/organize.md | 30 ++++-- .../datasets-and-metrics/permissions.md | 3 +- .../datasets-and-metrics/refresh-and-sync.md | 6 +- .../datasets-and-metrics/usage-and-preview.md | 18 ++-- platform/explore/sql-lab/editor.md | 22 ++--- platform/explore/sql-lab/index.md | 4 - platform/explore/sql-lab/parameters.md | 2 +- platform/explore/sql-lab/query-errors.md | 28 +++--- platform/explore/sql-lab/results.md | 10 +- platform/explore/sql-lab/save-as-dataset.md | 13 ++- platform/get-started/first-dashboard.md | 5 +- platform/known-issues.md | 91 +++++++++++-------- 40 files changed, 231 insertions(+), 203 deletions(-) diff --git a/platform/data-api/endpoints/access.md b/platform/data-api/endpoints/access.md index 0fac986..d486704 100644 --- a/platform/data-api/endpoints/access.md +++ b/platform/data-api/endpoints/access.md @@ -35,12 +35,9 @@ The status under **Invoke** is one of: | **Not granted** | The account cannot call this endpoint. | | **No grant on this endpoint** | Egret could not check whether the account inherits invoke, usually because the endpoint is not live yet. | -Two notes appear on a row when they matter: +A granted row can carry the note **Revoking will not remove access** (*This account also reaches invoke through the workspace, so revoking here removes the grant but not the access.*, with a link to **Workspace membership**). It appears only when the account's workspace role gives it invoke on every endpoint in the workspace. Egret asks this at workspace scope, where the grant on this endpoint does not count, so a granted account with no such role carries no note and loses access when you revoke it. The answer comes with the grant list, so it is given on a draft or disabled endpoint too. -- **Revoking will not remove access**, meant for a granted account that also reaches invoke through the workspace: revoking removes the grant but not the access. On the demo stack this note is shown on **every** granted service account, including ones with no workspace role, and is wrong for them: revoking `finance-dashboard` on **Orders by status** made its next call answer `403 not_granted`. Trust the result of **Try it** after a revoke, not the note. -- **Revoking may not remove access**, shown when Egret cannot check the inherited access, for example while the endpoint is not live. - -The check for inherited access runs the account's own OpenAPI document and looks for this endpoint in it, so it only works on a live endpoint. A draft or disabled endpoint says so above the table: *This endpoint is not live, so nothing is being served yet and egret cannot check workspace-inherited invoke rights against it. Grants recorded here are real and take effect when it is published.* +For an account with no grant here, Egret checks whether it inherits invoke by running the account's own OpenAPI document and looking for this endpoint in it. That only works on a live endpoint, so a draft or disabled endpoint says so above the table: *This endpoint is not live, so nothing is being served yet and egret cannot check workspace-inherited invoke rights against it. Grants recorded here are real and take effect when it is published.* While the check runs the row reads *Checking workspace-inherited invoke…*; if it fails, *egret could not check whether this account inherits invoke from the workspace.* ### The Try it action diff --git a/platform/data-api/endpoints/config.md b/platform/data-api/endpoints/config.md index 2d74a16..4bab8b3 100644 --- a/platform/data-api/endpoints/config.md +++ b/platform/data-api/endpoints/config.md @@ -20,9 +20,11 @@ The tab is a table with **Setting**, **Details** and **Value** columns, grouped | **Path** | Appended to the workspace's Data API base URL to form the URL callers call. Same rules as at creation: see [Endpoints](../endpoints#path-rules). | The path you gave it | :::caution -Method and path are part of the contract. Publishing a change to either is reported as a breaking change, and saving a new path moves the live URL at once; see [Publishing](./publishing#breaking-changes). +Method and path are part of the contract. Publishing a change to either is reported as a breaking change; see [Publishing](./publishing#breaking-changes). ::: +A new method or path on a live endpoint is saved on the draft only. Callers keep using the live URL until you publish; the field then says *The new path goes live when you publish. Callers still use GET /orders/by-status.* (or *The new method goes live…* for a method change), and the Endpoints list shows *Pending publish: GET /orders/lookup* under the live URL. When you publish, the new URL starts serving and the old one answers `404 not_found`; there is no redirect. + ## Cache An endpoint can serve repeat calls from a stored answer instead of running the query again. A cached answer sets `meta.cached` to `true` in the response. diff --git a/platform/data-api/endpoints/index.md b/platform/data-api/endpoints/index.md index b2e811c..c63557e 100644 --- a/platform/data-api/endpoints/index.md +++ b/platform/data-api/endpoints/index.md @@ -15,7 +15,7 @@ Open **Explore → Data API → Endpoints**. The list shows every endpoint in th | Column | What it shows | |---|---| | **Name** | The endpoint's name, with a star to bookmark it. | -| **Method & path** | The HTTP method and the path callers append to the workspace's base URL, for example `GET /orders/by-status`. | +| **Method & path** | The HTTP method and the path callers append to the workspace's base URL, for example `GET /orders/by-status`. For a published endpoint this is the URL it is served on. When the draft has a new method or path, the line under it reads *Pending publish:* and the new one until you publish. | | **State** | **Live**, **Draft**, or **Disabled · was live** (see [lifecycle](./overview#endpoint-lifecycle)). | | **Engine** | The engine of the endpoint's database connection, for example Trino. | | **Last edited** | When the draft last changed. | @@ -106,8 +106,8 @@ Opening an endpoint shows its editor. The header carries the name, a state badge The header badge reads **Draft · never published**, **Live · v2**, **Live · v1 · unpublished changes** (the saved draft differs from what is served, as on **Top customers** in the walk-through) or **Disabled · was live**. Pressing the badge opens **Versions**. -:::caution Changing the path moves the live URL -Saving a new **Path** in **Config** changes the URL that is served immediately, before you publish. A call to the old path returns `404 not_found` while the new path answers with the live version's data. Publishing then reports the change as breaking. Treat a path change as a breaking change to make deliberately. See [Publishing](./endpoints/publishing#breaking-changes). +:::note A new path goes live at publish +Saving a new **Path** or **Method** in **Config** changes only the draft. Callers keep the live URL until you publish; then the new URL serves and the old one answers `404 not_found`, with no redirect. Publishing reports the change as breaking. See [Cache, limits and rate limits](./endpoints/config) and [Publishing](./endpoints/publishing#breaking-changes). ::: ## Limits and known issues diff --git a/platform/data-api/endpoints/parameters.md b/platform/data-api/endpoints/parameters.md index d60247b..f60728c 100644 --- a/platform/data-api/endpoints/parameters.md +++ b/platform/data-api/endpoints/parameters.md @@ -31,8 +31,8 @@ Every parameter is read from the query string, for `GET` and `POST` endpoints al | Type | Accepted values | Notes | |---|---|---| -| `string` | Any text. | Use **min length**, **max length** and **pattern** to constrain it. | -| `number` | `-?digits` with an optional decimal part, such as `12`, `-3` or `12.5`. | Exponent notation (`1e3`), a leading `+`, and non-numbers are refused. A value outside **minimum** and **maximum** is refused. | +| `string` | Any text. | Use **Min length**, **Max length** and **Pattern (regex)** to constrain it. | +| `number` | `-?digits` with an optional decimal part, such as `12`, `-3` or `12.5`. | Exponent notation (`1e3`), a leading `+`, and non-numbers are refused. A value outside **Minimum** and **Maximum** is refused. | | `boolean` | `true`, `false`, `TRUE`, `1`, `0`. | Anything else, such as `yes`, is `400 param_invalid`. | | `date` | `YYYY-MM-DD`. `1998-6-1` is also read. | A timestamp or `06/01/1998` is refused. | | `timestamp` | `YYYY-MM-DDTHH:MM:SS` or `YYYY-MM-DD HH:MM:SS`, with optional fractional seconds. No time zone. | A trailing `Z` or an offset is refused. Use **not before** and **not after** to bound it, written in the same form. | @@ -42,14 +42,14 @@ An empty value (`?priority=`) is not treated as absent. It is checked like any o ### Constraints -Constraints are checked on every call, before the query runs and before the cache is consulted. They also appear in the OpenAPI document, so a client generator can enforce them. +In the editor the constraint boxes sit under the parameter's type, each with its own label. Constraints are checked on every call, before the query runs and before the cache is consulted. They also appear in the OpenAPI document, so a client generator can enforce them. | Type | Constraint fields | Meaning | |---|---|---| -| `string` | **min length**, **max length** | Length in Unicode characters, not bytes. | -| `string` | **pattern (regex)** | The value must match. | -| `number` | **minimum**, **maximum** | Inclusive bounds. Stored as text so decimals are compared exactly. | -| `date`, `timestamp` | **not before**, **not after** | Inclusive bounds. | +| `string` | **Min length**, **Max length** | Length in Unicode characters, not bytes. | +| `string` | **Pattern (regex)** | The value must match. | +| `number` | **Minimum**, **Maximum** | Inclusive bounds. Stored as text so decimals are compared exactly. | +| `date`, `timestamp` | **Not before**, **Not after** | Inclusive bounds. | | `enum` | **allowed values** | The closed list. | Changing a parameter's type clears the constraints that no longer apply. A constraint on a type that cannot carry it is refused at publish. @@ -143,7 +143,6 @@ The **Preview** panel under the editor has one input per declared parameter. Fil - Parameter errors never name the parameter. - A parameter name may be referenced from several statements; a key sent once is a list wherever any statement loops over it. -- Number constraint boxes in the editor show their value without a label; they are **minimum** then **maximum** in that order. ## Further reading diff --git a/platform/data-api/endpoints/publishing.md b/platform/data-api/endpoints/publishing.md index 8c32028..24c4447 100644 --- a/platform/data-api/endpoints/publishing.md +++ b/platform/data-api/endpoints/publishing.md @@ -59,9 +59,11 @@ Under the hood the acknowledgement is a token that `POST …/validate` issues fo The version row keeps the record. **1 breaking change** on `v2` of the walk-through endpoint *Orders by status* shows that someone acknowledged the removal of the `orders` column. -:::caution Changing the path moves the live URL -Saving a new **Path** changes the URL served at once, before any publish. See [Endpoints](../endpoints#the-editor). Treat it as breaking even though it only becomes visible as such when you publish. -::: +### A new method or path + +A new method or path takes effect when you publish, not when you save it. Until then the endpoint is served on its live URL, which the generated OpenAPI document and **Try it** use too. On publish the new URL starts serving and the old one answers `404 not_found`. There is no redirect, so tell your callers before you publish. + +Only one endpoint can be served at a URL. An endpoint keeps its published URL until it is published again, even when its draft has moved on. Publishing a second endpoint at that URL is refused with `409 conflict`: *another endpoint in this workspace is served at GET /x; an endpoint keeps its published URL until it is published again, so choose another path or publish the move of the endpoint that holds it first*. A disabled or trashed endpoint keeps its claim on its URL. ## Roll back @@ -77,6 +79,8 @@ A message confirms *Rolled back to vN*. Nothing is deleted. The draft is untouch Rollback re-checks the rules whose inputs are live now, such as whether the connection is governed, and refuses if the old version could not be served safely today. +The rolled-back version is served at the URL it was published at. If its method or path differs from the current one, callers must switch URLs. A rollback is refused with `409 conflict` when another endpoint is now served at that URL. + ## Pin a consumer to a version A consumer that wants to keep calling a specific version while you publish others can send the `X-Egret-Version` header with the version number: @@ -104,8 +108,7 @@ An endpoint in the trash cannot be edited, published, rolled back, enabled or di ## Limits and known issues - The **Roll back** button needs the endpoint to be out of the trash. -- Saving a changed **Path** moves live traffic before publish. -- The breaking-change badge can overlap the version's note text in a narrow **Version** column. +- A draft can be saved at a URL that another endpoint still serves; only its publish is refused. ## Further reading diff --git a/platform/data-api/endpoints/try-it.md b/platform/data-api/endpoints/try-it.md index 1f05117..add6743 100644 --- a/platform/data-api/endpoints/try-it.md +++ b/platform/data-api/endpoints/try-it.md @@ -86,7 +86,7 @@ A trial runs as the service account, not as you. It uses the endpoint-wide rate- ## Limits and known issues -- **Try it** runs the published version. Draft edits, saved or not, are not part of it. +- **Try it** runs the published version at its live URL. Draft edits, saved or not, are not part of it, including a new method or path. - A result too large to display shows *This result is too large to display here. The call still ran for real*; check Activity. - **Try it** needs at least one service account. With none, it says *This workspace has no service accounts yet* and links to **Create one in Clients**. diff --git a/platform/data-api/openapi.md b/platform/data-api/openapi.md index 2349f20..f3105a0 100644 --- a/platform/data-api/openapi.md +++ b/platform/data-api/openapi.md @@ -101,7 +101,7 @@ The error statuses documented on every operation are `400`, `401`, `403`, `404`, ## Limits and known issues -- The document is generated from the **published** version. Draft changes do not appear. +- The document is generated from the **published** version. Draft changes do not appear, including a new method or path: each endpoint is listed at the URL it is served on until you publish the move. - It is generated on demand, so a very large workspace can hit the generation limit; Egret answers `500 internal` rather than an incomplete document. - Two columns that collapse onto one key under **Naming** make an endpoint refuse to document (`503 output_naming_collision`); republish after fixing it. diff --git a/platform/data-api/overview.md b/platform/data-api/overview.md index cb6d234..89a7e47 100644 --- a/platform/data-api/overview.md +++ b/platform/data-api/overview.md @@ -50,7 +50,7 @@ A client gives a service account the ability to *get a token*. Two separate thin ## Endpoint lifecycle -An endpoint is never edited in place for its consumers. You edit the **draft**; consumers only ever see a **version**, which is a frozen copy of the draft taken when you press **Publish**. +An endpoint is never edited in place for its consumers. You edit the **draft**; consumers only ever see a **version**, which is a frozen copy of the draft taken when you press **Publish**. That includes the URL: a new method or path on the draft is served only from the next publish. | State | Meaning | Serves calls | |---|---|---| diff --git a/platform/deploy-operate/troubleshooting/engine-406-502.md b/platform/deploy-operate/troubleshooting/engine-406-502.md index f075bf3..6a4593f 100644 --- a/platform/deploy-operate/troubleshooting/engine-406-502.md +++ b/platform/deploy-operate/troubleshooting/engine-406-502.md @@ -12,7 +12,7 @@ or with *502 Bad Gateway*. This is the most common first-run failure with a Trin | Where | Message | |---|---| | **Test** on a connection, or `POST /api/ws/{workspace}/databases/test` | The request answers `200`, but the body says `{"ok":false,"detail":"engine: http 406 Not Acceptable"}` | -| Creating a dataset, `POST /api/ws/{workspace}/datasets` | HTTP **502** with `{"error":"engine: engine: http 406 Not Acceptable"}` | +| Creating a dataset, `POST /api/ws/{workspace}/datasets` | HTTP **502** with `{"error":"engine: http 406 Not Acceptable"}` | | A query in SQL Lab | The query status ends as `failed` with an `engine:` error | | Saving a connection through the API | It saves, with status `unknown`: the connection is stored without being proven, and the failure appears at the first use or on **Test**. | diff --git a/platform/deploy-operate/troubleshooting/queries-fail.md b/platform/deploy-operate/troubleshooting/queries-fail.md index c0fece1..ccccd96 100644 --- a/platform/deploy-operate/troubleshooting/queries-fail.md +++ b/platform/deploy-operate/troubleshooting/queries-fail.md @@ -51,7 +51,9 @@ See [Engine 406 and 502 errors](./engine-406-502). ## The query times out -The per-query timeout is `engine.query_timeout_secs` (default 60 seconds). Data Apps have their own limit +The per-query timeout is `engine.query_timeout_secs` (default 60 seconds). In SQL Lab a query that runs longer fails with +*timeout: the query ran longer than the 60 s limit for SQL Lab queries and was stopped. The limit is the server setting +`engine.query_timeout_secs`.* (or with Trino's *exceeded maximum time limit* and the same hint). Data Apps have their own limit (`apps.timeout_secs`, 60). Raise the key if long queries are legitimate, or make the query cheaper. The key must stay at least 1. @@ -65,7 +67,8 @@ least 1. | Row limit on a chart | The chart's Data tab | 1,000 (up to 10,000) | | `apps.max_rows` | Data Apps gateway | 10,000 | -A result that hit a cap is marked as truncated in the query's status (`"truncated": true`). +A result that hit a cap is marked as truncated in the query's status (`"truncated": true`), and SQL Lab shows the chip +*Results were limited to n rows.* ## *Missing query:run permission* (403) diff --git a/platform/explore/charts/cartesian-settings.md b/platform/explore/charts/cartesian-settings.md index e3216d3..c007876 100644 --- a/platform/explore/charts/cartesian-settings.md +++ b/platform/explore/charts/cartesian-settings.md @@ -42,7 +42,7 @@ Bar, row, line, area and combo charts. Empty by default (**Keep every category** ### Sort and Row limit -The same query controls every chart has. See [Sort](./the-chart-editor#sort) and [Row limit](./the-chart-editor#row-limit). The chart is always ordered by the X-axis, ascending, after any sort you add. +The same query controls every chart has. See [Sort](./the-chart-editor#sort) and [Row limit](./the-chart-editor#row-limit). The chart is always ordered by the X-axis, ascending, after any sort you add. A row chart with no sort is the exception: it orders by its first metric, descending, then by the X-axis (see [Row chart](./row)). ## Display tab diff --git a/platform/explore/charts/column-formatting.md b/platform/explore/charts/column-formatting.md index e1a69a4..3a73959 100644 --- a/platform/explore/charts/column-formatting.md +++ b/platform/explore/charts/column-formatting.md @@ -19,7 +19,7 @@ Which columns are listed depends on the chart: the ones it plots (for a bar char ### Column title -Replaces the column's name in headers, legends and tooltips. The placeholder is the original name. Clearing the box goes back to the original. +Replaces the column's name in headers, legends and tooltips. The placeholder is the original name. Clearing the box goes back to the original. When the dataset gives the column or metric a **Display name**, that name is used as the title in the chart editor and the dataset's Preview unless you set a title here; see [Columns](../datasets-and-metrics/columns#display-name). Dashboard cards do not apply the dataset's display names. ## Number columns diff --git a/platform/explore/charts/funnel.md b/platform/explore/charts/funnel.md index f81e7ae..3b7f454 100644 --- a/platform/explore/charts/funnel.md +++ b/platform/explore/charts/funnel.md @@ -53,7 +53,7 @@ One row for the dimension and one for the metric. See [Column formatting and num ![The same data as a Bar funnel, one column per stage](/img/platform/charts/funnel-bar.png) -With **Bar — a column per step**, each stage is a column on a bar chart with its own legend entry, which is easier to read when stages are many or the values very different. +With **Bar — a column per step**, each stage is a column on a bar chart with its own legend entry, which is easier to read when stages are many or the values very different. The stage names under the columns follow the same fit rule as a bar chart's X axis: all of them are drawn, upright or rotated, or none when they cannot all fit. ## Settings at a glance @@ -72,6 +72,7 @@ There is no **Axes** tab. - The percentage under each stage is relative to the **first stage in the displayed order**, not to the previous stage. - Stage names cannot be renamed here; rename or alias them in the dataset or the query. - Clicking a stage opens the drill menu for that stage. +- **.png** export is offered only with **Bar — a column per step**. The default funnel is not drawn as one image, so it offers **.csv**, **.xlsx** and **.json** only. ## Further reading diff --git a/platform/explore/charts/gauge.md b/platform/explore/charts/gauge.md index 8af763b..b72888d 100644 --- a/platform/explore/charts/gauge.md +++ b/platform/explore/charts/gauge.md @@ -19,8 +19,7 @@ A gauge shows one value on a dial, with the dial divided into colored ranges. Us | Field | What it does | |---|---| | **Dataset** | The dataset to read. | -| **Metric** | The value the needle points to. Required. The query returns one row. | -| **Row limit** | Present but ignored. | +| **Metric** | The value the needle points to. Required. The query returns one row, so there is no **Row limit**. | ## Display @@ -61,7 +60,6 @@ Open **Formatting → *metric*** and set **Number style** to **Percent**. The di | Setting | Where | Default | |---|---|---| | **Metric** | Data | Required | -| **Row limit** | Data | Ignored | | **Ranges** (color, **Min**, **Max**, **Label**) | Display | Three equal ranges from 0 to twice the value (red, yellow, green) | | **Formatting** (one row for the metric) | Display | Dataset's format | | **Axes** tab | | Not applicable | diff --git a/platform/explore/charts/map.md b/platform/explore/charts/map.md index 47f6619..8e18308 100644 --- a/platform/explore/charts/map.md +++ b/platform/explore/charts/map.md @@ -27,7 +27,7 @@ A region value can be the map's **code** or its **name**, in any letter case: | **World countries** (175 countries) | Two-letter country codes, for example `US`, `VN` | Country names as the map spells them, for example `United States of America`, `W. Sahara` | | **Vietnam provinces** (34 provinces) | The province name | The province name as the map spells it, for example `Hà Nội`, `TP. Hồ Chí Minh`, `Huế` | -Rows whose value matches no region are dropped from the map. Regions with no row are drawn in the palest shade. The legend on the left of the map shows the range of the metric from low to high. +Rows whose value matches no region are not drawn, and they do not count in the legend. A note under the map says how many rows were left out and names up to three of their values, as stored, so you can find the spelling to fix. For example: *Showing 1 of 2 rows: 1 matches no region on the Vietnam provinces map ("Thành phố Hồ Chí Minh").* Regions with no row are drawn in the palest shade. The legend on the left of the map shows the range of the metric, from low to high, over the rows that are drawn. ![The Vietnam provinces base map with revenue per province](/img/platform/charts/map-vietnam.png) @@ -79,7 +79,7 @@ For a region map, one row for the metric (see [Column formatting and number form - There are three base maps. Others (for example a US county or a custom shape) are not available. - There is no grid or heat map of points; for density over an area use a region map. -- A pin map says why rows are missing: a note under the map reports how many rows are outside the chosen map, have a latitude or longitude out of range, or have none. When none can be drawn, the card explains the cause, including a hint to check whether latitude and longitude are swapped. +- A region map names the values that match no region in a note under the map (see above). A pin map says why rows are missing: a note under the map reports how many rows are outside the chosen map, have a latitude or longitude out of range, or have none. When none can be drawn, the card explains the cause, including a hint to check whether latitude and longitude are swapped. - The first load of a base map downloads its shapes (the Vietnam map is the largest); the card shows **Loading map…** meanwhile. ## Further reading diff --git a/platform/explore/charts/number.md b/platform/explore/charts/number.md index 60dfbd2..15401c0 100644 --- a/platform/explore/charts/number.md +++ b/platform/explore/charts/number.md @@ -23,9 +23,8 @@ The chart runs a single-row query: the metric is aggregated over the whole datas |---|---| | **Dataset** | The dataset to read. | | **Metric** | The metric to show. Required. | -| **Row limit** | Present, as on every chart, but ignored: the query always returns one row. | -There is no **Sort**, because there is nothing to order. +There is no **Sort** and no **Row limit**, because there is only one row. ## Display diff --git a/platform/explore/charts/progress.md b/platform/explore/charts/progress.md index a160282..a6b9320 100644 --- a/platform/explore/charts/progress.md +++ b/platform/explore/charts/progress.md @@ -24,7 +24,6 @@ A progress bar shows one value against a goal as a filled bar. Use it for target | **Dataset** | The dataset to read. | | **Metric** | The value shown as the filled part of the bar. Required. | | **Goal** | A target number, with the placeholder **Target value**. Accepts decimals. | -| **Row limit** | Present but ignored; the query returns one row. | ## Display @@ -49,21 +48,17 @@ The bar fills to the metric's share of the goal. A small triangle above the bar | **Goal met** | The value equals the goal. | | **Goal exceeded** | The value is greater than the goal. The bar is full and its track turns darker. | | **No value data** | The query returned no value. | +| **No goal set** | There is a value but no goal: **Goal** is empty and no **Goal from a metric** is chosen, or the goal metric has no value. The bar stays empty and the scale under it is not drawn. | | **No data available** | There is neither a value nor a goal. | When the value is below the goal there is no message. -:::warning -An empty **Goal** is treated as `0`, not as "no goal". A progress bar with a metric but no goal reads **Goal 0** and **Goal exceeded**. Always set a **Goal** or a **Goal from a metric**. -::: - ## Settings at a glance | Setting | Where | Default | |---|---|---| | **Metric** | Data | Required | -| **Goal** | Data | Empty, read as `0` | -| **Row limit** | Data | Ignored | +| **Goal** | Data | Empty (no goal) | | **Colour** | Display | The palette's second color (green) | | **Goal from a metric** | Display | **None — use the fixed goal** | | **Formatting** (one row for the metric) | Display | Dataset's format | @@ -76,6 +71,7 @@ With **Metric** = **Orders** (15,000 orders in the sample) and **Goal** = `20000 - The panel uses the spelling **Colour** here, **Color** on most other types. - Clicking the bar opens the drill menu with only **See these records**. +- There is no **Row limit**: the query always returns one row. ## Further reading diff --git a/platform/explore/charts/row.md b/platform/explore/charts/row.md index a8239f2..fd1cd23 100644 --- a/platform/explore/charts/row.md +++ b/platform/explore/charts/row.md @@ -14,15 +14,12 @@ Row charts use the same settings as [bar charts](./bar) and the rest of the fami 1. Go to **Explore → Charts**, press **+ Chart** and pick **Row**. 2. Choose a **Dataset**, set **X-axis** to the dimension to rank (for example `clerk`) and add a metric under **Y-axis** (for example **Revenue**). -3. Under **Sort**, press **Add a sort…**, choose **revenue** and switch the direction to descending. -4. Set **Row limit** to `10`. -5. Press **Save**. +3. Set **Row limit** to `10`. +4. Press **Save**. The dimension is still called **X-axis** on the **Data** tab even though it is drawn vertically. The settings follow the drawing, not the field names: on the **Axes** tab the vertical category axis is labelled **Y-axis**. -:::note -Despite the gallery description ("sorted by value"), a row chart is not sorted by value on its own. Categories appear in alphabetical order until you add a **Sort**. Sort by the metric, descending, to get a ranking. -::: +A row chart is sorted by value on its own, as the gallery says: with no **Sort**, it orders by its first metric, largest first, with the category as the tie-breaker. Rows are drawn top to bottom in that order, so the largest bar is at the top. A **Sort** you add replaces the default. ## Data @@ -53,11 +50,10 @@ There is no **Horizontal** switch. The first group is the category axis, labelle ## Ranking categories -To rank: set **Sort** to the metric, descending, and **Row limit** to the number of rows you want. In the screenshot the ten largest clerks are listed, but the largest bar is drawn at the bottom, because a row chart draws the first row at the bottom. If you want the largest at the top, sort ascending and accept that **Row limit** then keeps the *smallest* rows, or limit the rows another way (a dataset that already holds only the rows you want to rank). +To rank, leave **Sort** empty and set **Row limit** to the number of rows you want: the default order is the first metric, descending, so a limit of 10 keeps the ten largest, drawn with the largest at the top. To list the categories alphabetically instead, add a **Sort** on the category, ascending. A row chart saved before this default existed may carry that alphabetical sort already; it shows in the **Sort** control, and removing it brings back the ranking. ## Limits and known issues -- Gallery text says rows are "sorted by value". They are only sorted when you add a **Sort**. - The **Data** tab labels the category field **X-axis** although it is drawn vertically. ## Further reading diff --git a/platform/explore/charts/table.md b/platform/explore/charts/table.md index a7b7dee..71397c4 100644 --- a/platform/explore/charts/table.md +++ b/platform/explore/charts/table.md @@ -50,6 +50,8 @@ Hiding a column only hides it in the table. It is still fetched, and conditional The gear opens the per-column format: **Column title**, number style, decimals, prefix and suffix, **Show a mini bar chart**, date style, **Display as** and more. All of it is documented in [Column formatting and number formats](./column-formatting). +A column header shows the dataset's **Display name** for the column or metric unless you set a **Column title**; with neither, it shows the column name. On a dashboard card the header shows the column name or your **Column title**, not the dataset's display name. + ![The format popover of a numeric column](/img/platform/charts/table-column-settings.png) ## Display diff --git a/platform/explore/charts/the-chart-editor.md b/platform/explore/charts/the-chart-editor.md index 0c74cb6..753ef16 100644 --- a/platform/explore/charts/the-chart-editor.md +++ b/platform/explore/charts/the-chart-editor.md @@ -18,7 +18,7 @@ The editor is where you build and change a chart. Settings are on the left, a li The left panel is 400 px wide and scrolls on its own. From top to bottom it has: 1. **← Charts**: back to the list. -2. The current chart type with a **Change** button. **Change** opens **Change visualization**, the same gallery you see when you create a chart. Changing the type keeps the dataset but clears the fields and the sort, so pick the type first. +2. The current chart type with a **Change** button. **Change** opens **Change visualization**, the same gallery you see when you create a chart. Between bar, row, line, area, combo and scatter, which share one set of settings, the change keeps the fields, the display settings and the sort. A change to any other type keeps the dataset but clears the fields and the sort, so pick the type first. 3. The tabs. **Data** and **Display** are always there. **Axes** appears for the types that have axis settings (bar, row, line, area, scatter, combo, waterfall and box plot). **Conditional formatting** appears for [Table](./table) and [Pivot Table](./pivot-table) only. If you switch to a type that does not have the open tab, the editor falls back to **Display**. ![The Change visualization dialog](/img/platform/charts/editor-change-viz.png) @@ -60,6 +60,8 @@ Egret picks fields from the dataset's **dimensions** (columns) and **metrics** ( Field names show the dataset's *Display name* when one is set (**Orders** above its metric name `order_count`). Hover a name to see the underlying column or metric name. +The pickers offer only the dataset columns whose **Dimension** switch is on, plus any column the chart already uses. A table with no metric and Object detail list every column. See [Columns](../datasets-and-metrics/columns#dimension). + ### Time grouping When a field you pick is a time column, a second select appears under it, labelled **Group *column* by**. Egret groups time columns by **Month** until you change it. The menu has two groups: @@ -89,6 +91,8 @@ The maximum number of rows the query returns. Default **1000**. The hint under t The row limit applies to the grouped result, not to the underlying table. A bar chart grouped by status with a limit of 1000 still aggregates the whole table. +**Row limit** is not shown for [Big number](./number), [Progress](./progress) and [Gauge](./gauge): their query always returns one row. + ## The Display and Axes tabs **Display** holds the settings that change how the chart looks: legends, labels, colors, number formats. For most types it ends with a **Formatting** section, one row per column the chart uses. Clicking a row opens that column's number or date format. See [Column formatting and number formats](./column-formatting). @@ -123,17 +127,13 @@ Dashboard cards have the same click menu. The menu names columns by their databa The download icon opens **Download data**: -1. Choose a format: **.csv**, **.xlsx** or **.json**. **.png** is also offered for the charts that are drawn as a vector image: bar, row, line, area, scatter, combo, pie, gauge, funnel, waterfall, box plot, Sankey and map. It is not offered for Big number, Progress, Trend, Table or Pivot Table. +1. Choose a format: **.csv**, **.xlsx** or **.json**. **.png** is also offered for the charts that are drawn as one chart image: bar, row, line, area, scatter, combo, pie, gauge, waterfall, box plot, Sankey, map, and a funnel with **Funnel type** = **Bar — a column per step**. It is not offered for Big number, Progress, Trend, Table, Pivot Table, Object detail or the default funnel. 2. For the table formats, tick or clear **Keep the data formatted**. When ticked (the default), values are exported the way they appear on screen, for example **September 6, 2026** or **$187.50**. When cleared, they are exported as stored, for example **2026-09-06** or **187.50**. 3. Press **Download**. The file is named after the chart. ![The Download data popover](/img/platform/charts/editor-export.png) -The popover says **The maximum download size is 1 million rows.** The export contains the rows the chart fetched, and a chart fetches at most 10,000. - -:::note -[Object detail](./object-detail) also lists **.png**, but the file it produces is a tiny image of an arrow icon, not the record. Use **.csv**, **.xlsx** or **.json** for it. -::: +For the table formats the popover says **The maximum download size is 10,000 rows.** The export contains the rows the chart fetched, and a chart fetches at most 10,000. ## Related tasks diff --git a/platform/explore/charts/waterfall.md b/platform/explore/charts/waterfall.md index 092b279..1832dca 100644 --- a/platform/explore/charts/waterfall.md +++ b/platform/explore/charts/waterfall.md @@ -38,7 +38,7 @@ The **Display** tab has two sections, **Bars** and **Format**. | **Increase color** | The color of rises. Default green. | | **Decrease color** | The color of falls. Default red. | | **Show total** | A switch, on by default. Adds a final bar named **Total** with the running sum. | -| **Total color** | The total bar's color. Shown while **Show total** is on. | +| **Total color** | The total bar's color. Shown while **Show total** is on. By default the bar uses the text color (dark in the light theme, light in the dark theme), and the swatch shows that color. | Each color is a swatch that opens the palette or **Custom…**. diff --git a/platform/explore/dashboards/export-and-reports.md b/platform/explore/dashboards/export-and-reports.md index 6bdee18..9c69bc8 100644 --- a/platform/explore/dashboards/export-and-reports.md +++ b/platform/explore/dashboards/export-and-reports.md @@ -19,7 +19,7 @@ You can take a dashboard's data out of Egret in three ways: download the data of | **.csv** | A comma-separated file with a header row. Values that contain commas, quotes or line breaks are quoted. | | **.xlsx** | An Excel workbook. Numbers and dates keep their types. | | **.json** | A list with one object per row, keyed by the column headers. | -| **.png** | A picture of the chart as it is drawn on the card. Offered only for cards that draw a chart, not for tables. | +| **.png** | A picture of the chart as it is drawn on the card. Offered only for cards drawn as one chart image: not for a table, pivot table, Big number, Progress, Trend, Object detail or the default funnel. See [Export](../charts/the-chart-editor#export) for the full list. | **Keep the data formatted** is on by default. On, the file has the values as the card shows them, for example "September 6, 2026" and "$187.50". Off, it has the raw values, for example `2026-09-06` and `187.50`. The hint under the checkbox changes with the choice. The checkbox does not apply to PNG. @@ -29,9 +29,7 @@ The file is named after the card title, with the characters `\ / : * ? " < > |` The download is built in your browser from the result already shown on the card, so it reflects the dashboard filters you have applied and any rename or format set in the card's visualization settings. Nothing is queried again. -:::note -The popover says "The maximum download size is 1 million rows." The result a card holds is limited by the chart's row limit, which Egret caps at 10,000 rows, so a file never has more rows than the card loaded. -::: +For the table formats the popover says "The maximum download size is 10,000 rows." The result a card holds is limited by the chart's row limit, which Egret caps at 10,000 rows, so a file never has more rows than the card loaded. The download button is not shown while the card is loading, has an error, is blocked by a required filter, or returned no columns. It is not shown in edit mode. diff --git a/platform/explore/dashboards/filters.md b/platform/explore/dashboards/filters.md index 7fd2305..b4941dd 100644 --- a/platform/explore/dashboards/filters.md +++ b/platform/explore/dashboards/filters.md @@ -153,7 +153,7 @@ The overlay explains cards that cannot be connected: | "The selected filter is on another tab." | A filter placed on a card of another tab. | :::note -The list of columns is the chart's own dimensions and plain columns, not every column of its dataset, and it is not narrowed by the filter's type. Add the column to the chart if you need to filter on it, and choose a column of a matching kind: a date filter connected to a text column produces a condition that fails when the query runs. Metric-only charts, such as a single-number card, therefore cannot be connected in the editor. +The list of columns is the chart's own dimensions and plain columns, not every column of its dataset, and it is not narrowed by the filter's type. Columns whose **Filter** switch is off in the card's dataset are left out, except the column the filter is already connected to ([Columns](../datasets-and-metrics/columns#filter)). Add the column to the chart if you need to filter on it, and choose a column of a matching kind: a date filter connected to a text column produces a condition that fails when the query runs. Metric-only charts, such as a single-number card, therefore cannot be connected in the editor. ::: A card can be connected to several filters, and a filter to several cards. Connections are stored on the card (`paramMappings`), so replacing a card's chart clears them. diff --git a/platform/explore/datasets-and-metrics/calculated-columns.md b/platform/explore/datasets-and-metrics/calculated-columns.md index affc31b..5a3ad11 100644 --- a/platform/explore/datasets-and-metrics/calculated-columns.md +++ b/platform/explore/datasets-and-metrics/calculated-columns.md @@ -24,9 +24,9 @@ The new column appears at the bottom of the table with a *calculated* tag and a Before saving, Egret asks the engine to run `SELECT AS _egret_expr FROM ` with no rows, and uses the answer to learn the column's type. - An empty formula, or one with a semicolon outside quotes (a second statement), is refused with *expression must be a single statement*. -- A formula the engine cannot parse or resolve is refused with the engine's message, shown in an error toast, for example `engine: line 2:24: mismatched input '*'. Expecting: `. The line number counts the line Egret adds in front. +- A formula the engine cannot parse or resolve is refused with the engine's message, shown in an error toast, for example `engine: line 1:24: mismatched input '*'. Expecting: `. The position counts from the start of the check query (`SELECT …`), so the column number is larger than the position in your formula. - A name that is already taken is refused with *column name already exists*. -- An empty name or formula shows *Name and expression required* in the dialog. +- An empty name or formula shows *Name and expression required* in the dialog. Through the API, a name that is empty or only spaces is refused with *column name is required*. The inferred type decides the icon and the format choices. A formula that returns a date or timestamp turns **Time** on automatically. @@ -58,7 +58,9 @@ The other settings of a calculated column (role, switches, format, display name, ## Delete a calculated column -Press **Delete** on its row. The column is removed immediately, with no confirmation. Charts that group by or filter on it will fail until you edit them, so check the dataset's [Usage](./usage-and-preview#usage) tab first. +Press **Delete** on its row. A dialog asks *Delete calculated column “name”?* and says *Charts that use this column will stop running until it is removed from them. This cannot be undone.* Type `delete` and press **Delete column**. The toast says *Deleted column name*. + +The dialog does not list the charts that use the column, so check the dataset's [Usage](./usage-and-preview#usage) tab first. ## How calculated columns behave @@ -71,7 +73,7 @@ Press **Delete** on its row. The column is removed immediately, with no confirma - A formula can only be a single line in the **Add** dialog. The **Edit** dialog is multi-line. - Calculated columns cannot reference each other. -- Deleting one does not warn you about charts that use it. +- Deleting one asks for confirmation but does not name the charts that use it. ## Further reading diff --git a/platform/explore/datasets-and-metrics/columns.md b/platform/explore/datasets-and-metrics/columns.md index 0d7e5f8..aa53604 100644 --- a/platform/explore/datasets-and-metrics/columns.md +++ b/platform/explore/datasets-and-metrics/columns.md @@ -47,21 +47,26 @@ A list that says what kind of thing the column holds. The default is **—** (no Only these values are accepted. The server refuses anything else with a *bad request* error that lists them. -:::note Dimension and Filter are stored but not used yet -The **Dimension** and **Filter** switches below are saved with the dataset, but the chart editor does not read them. A column with **Dimension** off still appears in the X-axis and series pickers, and a column with **Filter** off can still be filtered on. They are kept so that charts can start honouring them; do not rely on them to hide a column. -::: - ### Dimension -A switch. On means the column is meant to be used to group a chart by (a category such as *country* or *product*). It is on for every new column. +A switch. On means the column can be used to group a chart by (a category such as *country* or *product*). It is on for every new column. + +With **Dimension** off, the chart editor's field pickers (X-axis, series, the columns of a grouped table, and so on) no longer offer the column. Two exceptions keep existing work running: + +- A chart that already uses the column keeps it: the chart still runs and its picker still shows the column. +- A view that lists records rather than groups keeps every column: a [table chart](../charts/table) with no metric, and [Object detail](../charts/object-detail). + +The switch is read by the chart editor only. The server does not refuse a chart that groups by such a column. ### Filter -A switch. On means viewers are meant to be able to filter charts by this column. It is on for every new column. +A switch. On means dashboard filters can be connected to this column. It is on for every new column. + +With **Filter** off, a dashboard filter's card mapping no longer offers the column for cards built on this dataset. A filter that is already connected to the column keeps its connection. See [Dashboard filters](../dashboards/filters). ### Time -A switch that marks the column as a date or a time. Turn it on to use the column on a time axis and to get time grains (day, week, month, and so on) in the chart editor. New columns get it on automatically when the engine reports a date or timestamp type. **Refresh** resets this switch to what the engine type says, so a manual change on a physical or virtual column does not survive a refresh. +A switch that marks the column as a date or a time. Turn it on to use the column on a time axis and to get time grains (day, week, month, and so on) in the chart editor. New columns get it on automatically when the engine reports a date or timestamp type. **Refresh** keeps a manual choice as long as the column's engine type does not change; when the type changes, the switch is set again from the new type. ### Format @@ -96,7 +101,7 @@ A format set here is a **default**. Charts and tables built on the dataset use i ### Display name -A friendly label, for example *Order total* for `totalprice`. The chart editor shows it instead of the raw column name in its field lists. Column headers of a table chart and of the dataset's **Preview** still show the column name. Leave it empty to show the column name. A name made only of spaces is treated as empty. +A friendly label, for example *Order total* for `totalprice`. The chart editor shows it instead of the raw column name in its field lists, and it is the default **Column title** of the column on the chart page and in the dataset's **Preview**, so a table chart's header reads *Order total*. A chart that sets its own **Column title** for the column keeps it. The display name is applied when the chart is drawn and is not saved into the chart, so changing it later changes every chart that did not set its own title. Dashboard cards still show the column name in table headers. Leave it empty to show the column name. A name made only of spaces is treated as empty. ### Description @@ -104,7 +109,7 @@ A short note about what the column contains, for people who maintain the dataset ### Calculated columns: Edit and Delete -Rows of calculated columns have two extra buttons at the right: **Edit** opens a dialog to change the formula, and **Delete** removes the column at once, without a confirmation. See [Calculated columns](./calculated-columns). +Rows of calculated columns have two extra buttons at the right: **Edit** opens a dialog to change the formula, and **Delete** asks for confirmation before it removes the column. See [Calculated columns](./calculated-columns#delete-a-calculated-column). ## Example: prepare the Orders with customers dataset @@ -116,7 +121,8 @@ Rows of calculated columns have two extra buttons at the right: **Edit** opens a ## Limits and known issues -- **Dimension** and **Filter** do not affect the chart editor yet; **Category** and **Country** roles are not read anywhere. +- **Dimension** and **Filter** are applied in the chart editor and in the dashboard filter mapping only, not by the server. **Category** and **Country** roles are not read anywhere. +- Dashboard cards show column names, not display names, in table headers. - A format is stored as a small JSON text (for example `{"currency":"USD","decimals":2,"number_style":"currency"}`) that the API returns in the column's `format` field. - Column names cannot be renamed or hidden. Hide a column by leaving it out of the SQL of a virtual dataset. diff --git a/platform/explore/datasets-and-metrics/creating-a-dataset.md b/platform/explore/datasets-and-metrics/creating-a-dataset.md index 6bb56e7..a4c325a 100644 --- a/platform/explore/datasets-and-metrics/creating-a-dataset.md +++ b/platform/explore/datasets-and-metrics/creating-a-dataset.md @@ -4,7 +4,7 @@ sidebar_position: 2 # Creating a dataset -You can create a dataset in three ways: from a table in one of your databases (a physical dataset), from a SQL query in [SQL Lab](../sql-lab) (a virtual dataset), or by duplicating a virtual dataset that already exists. All three end on the new dataset's page, with its columns already detected. +You can create a dataset in three ways: from a table in one of your databases (a physical dataset), from a SQL query in [SQL Lab](../sql-lab) (a virtual dataset), or by duplicating a dataset that already exists. All three end on the new dataset's page, with its columns already detected. ![The New dataset dialog filled in for the tpch customer table](/img/platform/sql-datasets/datasets-create.png) @@ -39,9 +39,17 @@ The dialog links to **Need a custom query? Use SQL Lab**. A physical dataset is The steps and the rules the SQL must follow are on [Save a query as a dataset](../sql-lab/save-as-dataset). Virtual datasets cannot be created from the Datasets page itself. -## Duplicate a virtual dataset +## Duplicate a dataset -In the datasets list, open the **…** menu of a virtual dataset and choose **Duplicate**. The **Save as dataset** dialog opens with the dataset's SQL filled in (read only) and the name *copy of …*. Change the name and press **Create dataset**. The copy is a new dataset with the same SQL; its column settings, calculated columns and metrics start empty again, because the copy is created from the SQL like any new dataset. **Duplicate** is not offered for physical datasets. +In the datasets list, open the **…** menu of a dataset, physical or virtual, and choose **Duplicate**. + +1. The **Duplicate dataset** dialog says *The copy keeps the source, the column settings and the metrics of “name”.* and proposes the **Name** *copy of name*. +2. Change the name if you like and press **Duplicate** (or **Enter**). The button reads **Duplicating…** while it works. +3. The copy's page opens. + +The copy has the same database and source (the same table, or the same SQL) and every column setting: display names, roles, formats, descriptions, the **Dimension**, **Filter** and **Time** switches, and calculated columns. It also gets every metric. Certification, shares and tags are not copied, and no chart moves to the copy. + +If the name is taken, the dialog keeps it and shows *a dataset with this name already exists* under the field. Duplicating needs read access to the source dataset and the same rights as creating a dataset (`dataset:manage` and a granted database). The API call is `POST /api/ws/{workspace}/datasets/{id}/duplicate` with `{"name": "..."}`. ## What a new dataset contains @@ -95,13 +103,13 @@ For a virtual dataset use `"source": {"kind": "virtual", "sql": "SELECT ..."}`. | *Pick a database* | No database chosen. | | *Name required* | The name is empty. | | *a dataset with this name already exists* | Names are unique per workspace. | +| ``the query returns more than one column named `x`; give each column its own name with AS`` | A virtual dataset's query returns two columns with the same name. Alias one of them. | | *engine: … Table '…' does not exist* | The table was dropped or renamed after you opened the dialog. | | An engine syntax error | For a virtual dataset, the SQL is not a single query. See [Save a query as a dataset](../sql-lab/save-as-dataset). | ## Limits and known issues - The dialog's lists load from the engine with the same identity as SQL Lab's Explorer, so a table you cannot see there cannot be chosen here. -- Two columns with the same name in a virtual dataset's query make creation fail with *internal error*; alias them. - A virtual dataset's columns are read when it is created or refreshed; see [Refresh and schema sync](./refresh-and-sync). ## Further reading diff --git a/platform/explore/datasets-and-metrics/index.md b/platform/explore/datasets-and-metrics/index.md index 6931997..a4c6120 100644 --- a/platform/explore/datasets-and-metrics/index.md +++ b/platform/explore/datasets-and-metrics/index.md @@ -16,7 +16,7 @@ There are two kinds, shown in the **Kind** column of the list and as a tag in th |---|---|---| | **Defined by** | A table (or view) in one of the workspace's databases: *catalog, schema, table* | A SQL query that you write | | **Source** column in the list | The table name, for example `tpch.tiny.lineitem` | `SQL` | -| **Created from** | **Create** on the Datasets page | **Save as dataset** in [SQL Lab](../sql-lab/save-as-dataset.md), or **Duplicate** on an existing virtual dataset | +| **Created from** | **Create** on the Datasets page, or **Duplicate** on an existing physical dataset | **Save as dataset** in [SQL Lab](../sql-lab/save-as-dataset.md), or **Duplicate** on an existing virtual dataset | | **SQL is editable** | No. The source is the table. | Yes, on the dataset page, followed by **Refresh** | | **Typical use** | A clean table that analysts can chart as it is | Joins, filters, renames and derived columns kept under one name | @@ -56,7 +56,7 @@ Below the toolbar: The table columns are **Name**, **Kind**, **Database**, **Source**, **Columns** and **Updated**. Click **Name**, **Columns** or **Updated** to sort; the default order is the most recently updated first. In the **Name** cell, the star bookmarks the dataset, the name opens it, and a green check marks a certified dataset. -The **…** menu at the end of a row offers **Edit**, **Refresh**, **Duplicate** (virtual datasets only), the folder commands, **Share…**, **Tags…** and **Move to Trash**. +The **…** menu at the end of a row offers **Edit**, **Refresh**, **Duplicate**, the folder commands, **Share…**, **Tags…** and **Move to Trash**. ![The row menu of a dataset](/img/platform/sql-datasets/datasets-row-menu.png) @@ -66,7 +66,7 @@ Click a dataset's name to open it. The page has a header and four tabs. ![A dataset page with its Columns tab](/img/platform/sql-datasets/ds-columns.png) -- The **header** shows the name, the kind, a **Certified** badge when it applies, the description, counters (*10 columns*, *5 metrics*, *updated a minute ago*) and the source: the SQL for a virtual dataset, or `catalog.schema.table` for a physical one. On the right, the buttons are **Create chart**, **Certification** (only for people who may certify), **Edit**, **Refresh columns** and **Delete**. +- The **header** shows the name, the kind, a **Certified** badge when it applies, the description, counters (*10 columns*, *5 metrics*, or *1 column*, *1 metric* in the singular, *updated a minute ago*) and the source: the SQL for a virtual dataset, or `catalog.schema.table` for a physical one. On the right, the buttons are **Create chart**, **Certification** (only for people who may certify), **Edit**, **Refresh columns** and **Delete**. - **Columns (n)**: every column and its settings. See [Columns](./columns.md). - **Metrics (n)**: the dataset's aggregations. See [Metrics](./metrics.md). - **Usage**: the charts built on this dataset, and **Preview**: the first 100 rows. See [Usage and preview](./usage-and-preview.md). diff --git a/platform/explore/datasets-and-metrics/metrics.md b/platform/explore/datasets-and-metrics/metrics.md index 23b266d..ca4db6f 100644 --- a/platform/explore/datasets-and-metrics/metrics.md +++ b/platform/explore/datasets-and-metrics/metrics.md @@ -33,7 +33,7 @@ A dataset with no metrics shows *No metrics yet.* ![The Add metric dialog](/img/platform/sql-datasets/ds-metric-dialog.png) -Egret checks the expression the same way as for a [calculated column](./calculated-columns#what-egret-checks): the engine runs it against the dataset's source and returns its type. If the engine rejects it, an error toast shows the message, for example `engine: line 2:12: Column 'nonexistent' cannot be resolved`, and nothing is saved. A name that already exists is refused with *metric name already exists*. An empty name or expression shows *Name and expression required*. +Egret checks the expression in two steps. First, the same way as for a [calculated column](./calculated-columns#what-egret-checks): the engine runs it against the dataset's source and returns its type. If the engine rejects it, an error toast shows the message, for example `engine: line 1:12: Column 'nonexistent' cannot be resolved`, and nothing is saved. Second, the engine runs it next to `count(*)` with no grouping, the way a chart uses a metric. An expression that is not an aggregate fails this step and is refused with *metric expression must be an aggregate, such as SUM(column) or COUNT(\*):* followed by the engine's message. A name that already exists is refused with *metric name already exists*. An empty name or expression shows *Name and expression required*. ### Write the expression @@ -46,7 +46,7 @@ The expression is a single SQL expression in the database's dialect. It may use: It cannot use [calculated columns](./calculated-columns), because the check runs against the source columns only; repeat the calculated column's formula inside the metric instead. A semicolon outside quotes is refused. -Egret does not check that the expression is an aggregate. A plain column such as `totalprice` is accepted by the Metrics tab and then fails when a chart uses it, with the engine's message `'totalprice' must be an aggregate expression or appear in GROUP BY clause`, so test every new metric in a chart. +A plain column such as `totalprice` is therefore refused when you save, with the engine's message `'totalprice' must be an aggregate expression or appear in GROUP BY clause` after the sentence above. Wrap it in an aggregate such as `SUM(totalprice)`. Metrics that work on the demo data: @@ -65,15 +65,17 @@ Metrics that work on the demo data: Press **Edit** on a row. The **Edit metric** dialog has the same fields as **Add metric**, including the name. Press **Save**; the expression is validated again. -:::warning Renaming breaks charts -Charts store a metric by its name. If you rename a metric that charts use, those charts fail with *unknown metric: old_name* until you pick the metric again. Change the **Display name** instead when you only want a different label. -::: +Charts store a metric by its name. When you rename a metric, Egret rewrites every chart on the dataset that uses the old name (its metrics, its sort and its settings) and the dashboard cards' overrides of those charts, in the same save, so the charts keep running under the new name. + +A rename of a metric that charts use is refused when the old or the new name is also the name of a column of the dataset, because Egret could not tell the metric from the column in the charts. The error says *cannot rename a metric that charts use while a column is also named "name": the charts could not be rewritten unambiguously*. Pick another name, or change the **Display name** when you only want a different label. The **Format** column on the table is a shortcut: changing it saves the metric straight away, resending the current name, expression and other fields, and so repeats the engine check. ## Delete a metric -Press **Delete** on the row. The metric is removed at once, with no confirmation, even when charts use it. Check the [Usage](./usage-and-preview#usage) tab first. +Press **Delete** on the row. A dialog asks *Delete metric “name”?* with the text *This cannot be undone.* Type `delete` and press **Delete metric**. The toast says *Deleted metric name*. + +If charts use the metric, Egret refuses this first delete, and the dialog changes: it lists those charts (with their type icon, a link, and an *in Trash* tag for a trashed chart), says *N charts use this metric and will stop running until it is removed from them. This cannot be undone.*, and asks you to type the metric's exact name (case matters) instead of `delete`. Press **Delete metric** again to delete it. The charts are not changed; they fail when they run until you pick another metric in them. ## Use metrics in a chart @@ -84,8 +86,8 @@ Press **Create chart** on the dataset page, choose a chart type, and the metrics ## Limits and known issues - Metric names are not validated beyond being unique and non-empty; keep to letters, digits and underscores so that charts and the REST API can refer to them without quoting. -- Egret does not warn before renaming or deleting a metric that charts use. -- A non-aggregate expression is accepted; the failure shows up in the chart. +- A metric that charts use cannot be renamed to, or from, a name that is also a column name. +- Deleting a metric that charts use leaves those charts broken until they are edited. ## Further reading diff --git a/platform/explore/datasets-and-metrics/organize.md b/platform/explore/datasets-and-metrics/organize.md index 70f1fdf..718f2ca 100644 --- a/platform/explore/datasets-and-metrics/organize.md +++ b/platform/explore/datasets-and-metrics/organize.md @@ -44,10 +44,14 @@ A folder heading's **…** menu has: ## Trash -**Move to Trash** (row menu, or **Move n to Trash** after bulk select) hides a dataset from the main list without deleting it. Press the **Trash** button in the toolbar to open the **Trash** view, where each row shows the same columns as the main list and a **…** menu with: +**Move to Trash** (row menu, or **Move n to Trash** after bulk select) hides a dataset from the main list without deleting it. + +From the row menu, a dialog asks *Move “name” to Trash?* first. It lists the charts built on the dataset and says *N charts use this dataset and will keep working until the dataset is deleted. You can restore it from the Trash.* (or *No charts use this dataset. You can restore it from the Trash.*). Press **Move to Trash** to confirm; the toast says *Moved to Trash*. **Move n to Trash** after bulk select trashes the selected datasets without this dialog. + +Press the **Trash** button in the toolbar to open the **Trash** view, where each row shows the same columns as the main list and a **…** menu with: - **Restore**, which returns the dataset to the list. -- **Delete permanently**, which asks *Permanently delete "name"? This cannot be undone.* and then deletes it for good. +- **Delete permanently**, which deletes it for good after a confirmation (see [Delete a dataset](#delete-a-dataset)). ![The Trash view with one trashed dataset and its Restore and Delete permanently menu](/img/platform/sql-datasets/datasets-trash.png) @@ -56,20 +60,31 @@ Press the **Trash** button again to leave the view. An empty trash says *Trash i What trashing does, as tested on the demo: - The dataset disappears from the main list and can be restored with all its settings. -- Charts built on it **keep working** and keep running queries; they are not trashed with it. +- Charts built on it **keep working** and keep running queries; they are not trashed with it. Each of them shows a notice in the chart editor: *This chart's dataset “name” is in the Trash. The chart keeps working until the dataset is deleted; restore the dataset from the Trash to keep it.* The chart editor's **Dataset** picker lists live datasets only, so it is empty for such a chart. - Nothing is purged automatically. A trashed dataset stays until someone restores it or deletes it permanently. ### Delete a dataset -**Delete permanently** in the Trash, and the trash-can button in a dataset's header, both remove the dataset for good. The header button opens *Delete dataset “name”?* with the text *This removes the dataset and its column snapshot. The underlying table or query is not affected. This cannot be undone.* After you confirm, the toast says *Deleted* and the Datasets list opens. +**Delete permanently** in the Trash, and the trash-can button in a dataset's header, both remove the dataset for good. Both open the same dialog, *Delete dataset “name”?*, and what it asks depends on whether charts are built on the dataset, counting charts in the Trash: + +| Charts on the dataset | What the dialog says | To confirm | +|---|---|---| +| None | *This removes the dataset and its column snapshot. The underlying table or query is not affected. This cannot be undone.* | Type `delete`, press **Delete**. | +| One or more | *N charts are built on this dataset and will be deleted with it. The underlying table or query is not affected. This cannot be undone.*, followed by the list of those charts (type icon, link, *in Trash* tag) | Type the dataset's exact name (case matters), press **Delete dataset and N charts**. | + +The confirm button stays off until the chart list has loaded. If the list cannot be read, the dialog says *Could not check which charts use this dataset, so it cannot be deleted right now.* and offers no delete. + +After the delete, the toast says *Deleted* (or *Deleted the dataset and N charts*); from the header button the Datasets list opens instead. :::warning Charts are deleted too -A permanent delete also deletes every chart built on the dataset; the dialog does not say so. Use **Move to Trash** if you only want to hide a dataset, and check the [Usage](./usage-and-preview#usage) tab before deleting. +A permanent delete also deletes every chart built on the dataset, live or in the Trash. Use **Move to Trash** if you only want to hide a dataset. ::: +Through the REST API, `DELETE /api/ws/{workspace}/datasets/{id}` on a dataset that charts are built on answers `409` with `code: "in_use"`, the list of charts and the message *N charts built on this dataset would be deleted with it; confirm with the dataset name to delete them all*. Send `?confirm=` to delete the dataset and its charts. + ## Duplicate -For virtual datasets, **Duplicate** in the row menu opens a **Save as dataset** dialog with the SQL filled in and the name *copy of …*. See [Creating a dataset](./creating-a-dataset#duplicate-a-virtual-dataset). +**Duplicate** in the row menu copies a dataset of either kind, with its column settings, calculated columns and metrics. See [Creating a dataset](./creating-a-dataset#duplicate-a-dataset). ## Tags @@ -95,7 +110,8 @@ The roles map to the dataset permissions: **Viewer** can read the dataset and it - Folders are one level deep. - Bookmarks are personal and cannot be shared. -- **Delete permanently** removes the charts built on the dataset without warning. +- **Delete permanently** removes the charts built on the dataset, after you type the dataset's name. +- **Move n to Trash** (bulk select) does not show which charts use the datasets. ## Further reading diff --git a/platform/explore/datasets-and-metrics/permissions.md b/platform/explore/datasets-and-metrics/permissions.md index e2e3472..b5c95f4 100644 --- a/platform/explore/datasets-and-metrics/permissions.md +++ b/platform/explore/datasets-and-metrics/permissions.md @@ -43,7 +43,8 @@ The checks on every dataset action use the dataset itself, so a share, a folder |---|---| | Open the Datasets list, a dataset page, **Usage**, **Preview** | `dataset:read` | | Bookmark a dataset | `dataset:read` | -| **Create**, **Duplicate** | `dataset:manage` on the workspace, a granted database | +| **Create** | `dataset:manage` on the workspace, a granted database | +| **Duplicate** | `dataset:read` on the source dataset, plus `dataset:manage` on the workspace and a granted database | | **Edit** (name, description, SQL) | `dataset:manage` on the dataset | | **Refresh**, column settings, **+ Calculated column**, **+ Metric**, metric edit and delete | `dataset:manage` on the dataset | | **Move to Trash**, **Restore**, **Delete permanently**, header **Delete** | `dataset:manage` on the dataset | diff --git a/platform/explore/datasets-and-metrics/refresh-and-sync.md b/platform/explore/datasets-and-metrics/refresh-and-sync.md index 5f13d18..764c89f 100644 --- a/platform/explore/datasets-and-metrics/refresh-and-sync.md +++ b/platform/explore/datasets-and-metrics/refresh-and-sync.md @@ -13,7 +13,7 @@ You can refresh from two places: - On the dataset page, press the **Refresh columns** button (circular arrows) in the header. The icon spins while Egret works. - In the Datasets list, open the **…** menu of the row and choose **Refresh**. The item reads **Refreshing…** while it runs. -A toast says *Refreshed* when it is done. Refresh needs the `dataset:manage` permission; see [Permissions](./permissions). +When it is done, a toast says what changed in the column list: *Refreshed: no columns added or removed*, or for example *Refreshed: 2 new columns (discount_band, region); 1 column removed (comment)*. More than three names are shortened to the first three and *and N more*. Refresh needs the `dataset:manage` permission; see [Permissions](./permissions). ### What a refresh does @@ -22,7 +22,7 @@ Egret asks the engine for the source's current columns (for a physical dataset, | Situation | Result | |---|---| | A column is new in the source | It is added, with **Dimension** and **Filter** on and everything else empty. | -| A column still exists | Its type is updated. Your **Display name**, **Description**, **Role**, **Format**, **Dimension** and **Filter** settings stay. Its **Time** switch is set again from the engine's type, so a manual choice on this switch is lost. | +| A column still exists | Its type is updated. Your **Display name**, **Description**, **Role**, **Format**, **Dimension** and **Filter** settings stay. Its **Time** switch stays as you set it when the engine type is the same; when the type changed, the switch is set again from the new type. | | A column no longer exists in the source | It is removed, together with its settings. | | A calculated column | Never touched. It stays even if its formula refers to a column that no longer exists. | | A metric | Never touched, on the same terms. | @@ -71,7 +71,7 @@ Renaming the dataset does not affect charts. They follow the dataset by its iden ## Limits and known issues - The snapshot can drift from the source silently. -- **Time** manual changes are reset by **Refresh**. +- A manual **Time** choice is reset by **Refresh** when the column's engine type changes. - Removing a source column removes its settings for good; adding it back creates a fresh column with default settings. ## Further reading diff --git a/platform/explore/datasets-and-metrics/usage-and-preview.md b/platform/explore/datasets-and-metrics/usage-and-preview.md index 7e7ad51..1683a90 100644 --- a/platform/explore/datasets-and-metrics/usage-and-preview.md +++ b/platform/explore/datasets-and-metrics/usage-and-preview.md @@ -10,21 +10,21 @@ The last two tabs of a dataset answer two practical questions: *who depends on t ## Usage -Open the **Usage** tab to see every chart built on the dataset. The line at the top counts them, for example *23 charts built on this dataset* for the demo's `orders` dataset, and each row shows a chart's name (always with the bar-chart icon, whatever the chart type) and an open icon. Click a row to open the chart in the chart editor. Charts in the Trash are not listed. +Open the **Usage** tab to see every chart built on the dataset. The line at the top counts them, for example *23 charts built on this dataset* for the demo's `orders` dataset, and each row shows a chart's name with the icon of its chart type, and an open icon. Click a row to open the chart in the chart editor. Charts in the Trash are not listed here; the delete dialog lists them too, because a delete takes them with it. A dataset with no charts shows *No charts use this dataset yet.* with a **Create a chart** button that opens the chart editor with this dataset already chosen. Press **Create chart** in the dataset header for the same effect. Use this tab before you: -- change or delete a [metric](./metrics#edit-a-metric) or a [calculated column](./calculated-columns#delete-a-calculated-column); -- rename a metric; -- change the SQL of a virtual dataset; -- [delete the dataset](./organize#delete-a-dataset). +- delete a [calculated column](./calculated-columns#delete-a-calculated-column), whose delete dialog does not list the charts; +- change the SQL of a virtual dataset, or refresh after a column was dropped from the source. + +Deleting a [metric](./metrics#delete-a-metric) or [the dataset](./organize#delete-a-dataset) lists the affected charts in its own dialog. The list shows charts only. Dashboards that contain those charts are not listed, so open a chart's page or the [Dashboards](../dashboards) list to find where it is used. Reading the list needs read access to the dataset, not manage access. :::warning Deleting a dataset deletes its charts -Deleting a dataset permanently also deletes every chart built on it. The confirmation dialog says only that the table or query is not affected. Check **Usage** first. +Deleting a dataset permanently also deletes every chart built on it, including charts in the Trash. The delete dialog lists them and asks for the dataset's name. ::: ## Preview @@ -33,10 +33,11 @@ The **Preview** tab shows the first 100 rows of the dataset with the dataset's c ![The Preview tab with currency and date formats applied](/img/platform/sql-datasets/ds-preview.png) -The tab opens with the line *Preview — first 100 rows* and a **Refresh** button that reads the rows again (the button reads **Loading…** while it works). The grid below is a plain table with the column names as headers. +The tab opens with the line *Preview — first 100 rows* and a **Refresh** button that reads the rows again (the button reads **Loading…** while it works). The grid below is a plain table. Each header shows the column's **Display name**, or the column name when it has none. What Preview applies from the **Columns** tab: +- Each column's **Display name**, as the header. - Each column's **Format**: currency, percent, decimals, prefix or suffix for numbers; the date pattern for dates; link, email or image display for text. - The **Role** as a default when there is no explicit format: **Currency** shows money, **Email**, **URL** and **Image** show links and images. @@ -44,7 +45,6 @@ What Preview does not show: - **Calculated columns.** Preview runs `SELECT *` on the dataset's own source, which has none of them. To check a calculated column, build a chart or a table chart on it. - **Metrics.** They are aggregations and need a chart. -- **Display names.** The header shows the source column name. Preview is bounded: it always reads at most 100 rows through a fixed query and cannot be given SQL. A reader who only has access to this dataset can still see Preview, because it runs on the dataset's own definition and not on free-form SQL. @@ -70,7 +70,7 @@ curl -s -X POST "$BASE/api/ws/default/datasets/$DATASET_ID/data" -b "$COOKIES" \ -d '{"limit": 100}' ``` -The usage call returns a list of `{id, name}` objects, one per chart. The data call returns `columns` (name and type) and `rows`. The optional `limit` is clamped to the range 100 to 1,000 (200 when you send none); the page asks for 100. The endpoint accepts no SQL, by design. +The usage call returns a list of `{id, name, viz_type, trashed}` objects, one per chart, live charts first. Add `?include_trashed=true` to list the charts in the Trash as well. The data call returns `columns` (name and type) and `rows`. The optional `limit` is clamped to the range 100 to 1,000 (200 when you send none); the page asks for 100. The endpoint accepts no SQL, by design. ## Tips diff --git a/platform/explore/sql-lab/editor.md b/platform/explore/sql-lab/editor.md index b2e4b2b..c4b066b 100644 --- a/platform/explore/sql-lab/editor.md +++ b/platform/explore/sql-lab/editor.md @@ -92,8 +92,8 @@ Press **Run**, or **⌘ Enter** (macOS) or **Ctrl Enter** (Windows and Linux) wh - **Run selection.** When you select text, the button changes to **Run selection** and only the selected text is sent. Selecting one statement out of several is the way to run them one at a time. Selected text that is only whitespace is ignored and the whole editor text runs. - **Explain.** **Explain query** in the **…** menu (and in the command palette) runs `EXPLAIN` in front of the selection or, with no selection, the whole text, and shows the plan in the grid as one wide cell. Click the cell to read the whole plan in a dialog. - **Only one run at a time per tab.** **Run** is disabled while a query is running. -- **Cancel.** Press **Cancel** to stop the query. The engine is asked to stop and the run is recorded in History with the status *cancelled*. The status badge disappears and the Results panel goes back to *No results yet.*; nothing tells you that the run was cancelled. -- **Time out.** A query that runs longer than the server's `engine.query_timeout_secs` (60 seconds by default) is stopped and fails with the error `timeout`. +- **Cancel.** Press **Cancel** to stop the query. The engine is asked to stop and the run is recorded in History with the status *cancelled*. The Results panel shows **Query cancelled** (*The run was stopped before it returned rows. Run it again to see results.*), and the status badge and the footer read *Cancelled after* and the time, for example *Cancelled after 4.2 s*. +- **Time out.** A query that runs longer than the server's `engine.query_timeout_secs` (60 seconds by default) is stopped and fails with *timeout: the query ran longer than the 60 s limit for SQL Lab queries and was stopped. The limit is the server setting `engine.query_timeout_secs`.* When Trino stops the query itself at the same limit, its own message (*exceeded maximum time limit*) is followed by the same hint. ![The plan of an EXPLAIN in the results grid](/img/platform/sql-datasets/sqllab-explain.png) @@ -107,16 +107,16 @@ Press **Run**, or **⌘ Enter** (macOS) or **Ctrl Enter** (Windows and Linux) wh How the limit is applied depends on the statement: -- A single `SELECT` or `WITH` statement is wrapped as `SELECT * FROM () AS _egret_lim LIMIT n`, so the engine itself stops at `n` rows. Your own `LIMIT` still applies inside. -- Anything else (for example `SHOW`, `DESCRIBE`, `EXPLAIN`, or several statements in one text) is sent unchanged and Egret stops reading after `n` rows. The Results panel then shows the amber chip *Results were limited to n rows.* +- A single query (`SELECT`, `WITH`, a `UNION` or `VALUES`) without its own `LIMIT`, `OFFSET` or `FETCH` gets `LIMIT n + 1` added on a new line after your text. Your `ORDER BY` stays the outermost ordering, so the rows come back in the order you asked for, and the error positions the engine reports match the lines in the editor. A trailing semicolon or comment is fine. +- A query with its own `LIMIT`, `OFFSET` or `FETCH` is sent unchanged. +- Anything else (for example `SHOW`, `DESCRIBE`, `EXPLAIN`, or several statements in one text) is sent unchanged. +- SQL that Egret cannot read (an engine-specific construct, a `/*!` hint or a nested `/*` comment) but that starts with `SELECT` or `WITH` is still wrapped as `SELECT * FROM () AS _egret_lim LIMIT n + 1`. For that SQL an `ORDER BY` without its own `LIMIT` is not guaranteed to be kept on Trino. -For the first kind there is no chip, so a result that has exactly as many rows as your limit may have been cut. If the row count equals the limit, raise the limit or add a `WHERE` clause. See [Query results](./results#limits) for how many of those rows the grid loads. - -**`ORDER BY` without `LIMIT` is not kept on Trino.** Because of that wrapping, Trino treats your `ORDER BY` as the order of a subquery, which it is free to ignore. A query such as `SELECT orderstatus, sum(totalprice) AS revenue FROM tpch.tiny.orders GROUP BY 1 ORDER BY 2 DESC` came back in a different row order on almost every run on the demo stack. Add your own `LIMIT` after the `ORDER BY` (for example `ORDER BY 2 DESC LIMIT 1000`), which Trino keeps, or click a column header to sort the loaded rows in the grid. +In every case Egret stops reading after `n` rows. When there were more, the Results panel shows the amber chip *Results were limited to n rows.*, so a cut result is always marked. See [Query results](./results#limits) for how many of those rows the grid loads. ## The command palette -Press **⌘ K** (macOS) or **Ctrl K** to open the palette, or press the **⌘K** button in the toolbar. It is a search box over a flat list of actions; type to narrow the list by name, move with **↑** and **↓**, run the highlighted action with **Enter** and close it with **Escape**. Pressing the shortcut again closes it. +Press **⌘ K** (macOS) or **Ctrl K** to open the palette, or press the **⌘K** button in the toolbar. The shortcut works with the cursor in the editor too. It is a search box over a flat list of actions; type to narrow the list by name, move with **↑** and **↓**, run the highlighted action with **Enter** and close it with **Escape**. Pressing the shortcut again closes it. ![The command palette](/img/platform/sql-datasets/sqllab-palette.png) @@ -133,7 +133,7 @@ Press **⌘ K** (macOS) or **Ctrl K** to open the palette, or press the **⌘K** | **Download results as CSV** | Only listed after a successful run. | :::note ⌘K inside the editor -While the text cursor is inside the editor, Monaco handles **⌘ K** itself (it starts a chord) and the palette does not open. Click outside the editor, in the results area for example, then press the shortcut, or use the **⌘K** button. +SQL Lab takes **⌘ K** / **Ctrl K** before the editor sees it, so the editor's own shortcuts that start with **⌘ K** are not available in SQL Lab. ::: ## Keyboard shortcuts @@ -155,9 +155,9 @@ The **Run** shortcut belongs to the editor it was pressed in, so it never runs a ## Limits and known issues - Switching tabs discards the visible result (see the note above). -- Cancelling shows no confirmation in the page. - Autocomplete does not resolve aliases and only knows expanded Explorer nodes. -- On Trino, `ORDER BY` without `LIMIT` in a single `SELECT` is not guaranteed to be kept (see [Row limit](#row-limit)). +- On Trino, `ORDER BY` without `LIMIT` is not guaranteed to be kept for SQL that Egret cannot read and therefore wraps (see [Row limit](#row-limit)). +- Editor chords that start with **⌘ K** are not available (see [The command palette](#the-command-palette)). ## Further reading diff --git a/platform/explore/sql-lab/index.md b/platform/explore/sql-lab/index.md index 85db5e6..7182674 100644 --- a/platform/explore/sql-lab/index.md +++ b/platform/explore/sql-lab/index.md @@ -46,8 +46,6 @@ The page is one resizable workspace. Drag the grey bars between the panels to ch 4. Leave **Limit** at **1,000 rows** or choose another value. 5. Press **Run** (or **⌘ Enter** on macOS, **Ctrl Enter** elsewhere). -Keep the `LIMIT` after an `ORDER BY`: without it, Trino may return the rows in another order (see [Row limit](./editor.md#row-limit)). - While the query runs, a *Running…* badge shows in the toolbar and the **Cancel** button is enabled. When it ends the badge reads, for example, *Done in 397 ms · 3 rows* (or *Failed in 125 ms*), and the rows appear in **Results**. :::tip Name tables completely @@ -94,8 +92,6 @@ Because tabs live in the browser, clearing site data or switching browsers gives - SQL Lab needs at least one database connection granted to the workspace. Without one the database picker is empty, the Explorer says *No connection is granted to this workspace. Ask a workspace admin.*, and **Run** reports *Pick a database*. - The row limit, the 60-second query timeout and the 100,000-row ceiling are server settings; see [Query results](./results.md#limits). -- Cancelling a query leaves the Results panel empty without saying the query was cancelled. See the [editor page](./editor.md#run-and-cancel). -- On Trino, an `ORDER BY` without a `LIMIT` can be lost, because Egret wraps the query to apply the row limit. See [Row limit](./editor.md#row-limit). ## Further reading diff --git a/platform/explore/sql-lab/parameters.md b/platform/explore/sql-lab/parameters.md index d711534..64cd367 100644 --- a/platform/explore/sql-lab/parameters.md +++ b/platform/explore/sql-lab/parameters.md @@ -102,7 +102,7 @@ With the value `TRUE` the result is `true`. | You see | Cause | |---|---| -| A toast `Error: {"error":"invalid input: parameter 'min_total' is not a number: abc"}` | A **num** parameter holds text that is not a number. The toast appears in the bottom-right corner and the query is not run. | +| **Query not started** with `invalid input: parameter 'min_total' is not a number: abc` | A **num** parameter holds text that is not a number. The message appears in the **Results** panel and the query is not run. | | *invalid input: missing value for parameter: name* | The request named a placeholder that no parameter supplies. The editor always sends every placeholder it finds, so you meet this only if you call the [REST API](../../reference/rest-api) yourself. | | `unterminated '{{' in query template` | A `{{` has no closing `}}`. | | An engine error about `{` | The placeholder was not replaced, for example because it was typed with unsupported characters. | diff --git a/platform/explore/sql-lab/query-errors.md b/platform/explore/sql-lab/query-errors.md index b36e896..28de005 100644 --- a/platform/explore/sql-lab/query-errors.md +++ b/platform/explore/sql-lab/query-errors.md @@ -18,7 +18,7 @@ There are two kinds, depending on when the problem is found. - In **Results**, a red banner **Query failed** with a **Copy** button, then the message in a monospaced box. **Copy** puts the whole message on the clipboard and a toast says *Error copied*. - A *Failed* entry in [History](./history-saved-snippets#history). -**The query was refused before it started.** Examples are a missing database, a bad [parameter](./parameters#errors), or a permission problem. An error toast appears in the corner of the page, the run is not recorded as a failed query, and the previous result stays where it was. The toast shows the server's reply as it came, so it can look like `Error: {"error":"invalid input: …"}`. +**The query was refused before it started.** Examples are a bad [parameter](./parameters#errors) or a permission problem. The **Results** panel shows a red banner **Query not started** with a **Copy** button and the server's message, for example `invalid input: parameter 'n' is not a number: abc`. The run is not recorded in History. A tab with no database chosen is the exception: it shows the toast *Pick a database*. ## How the message is built @@ -28,33 +28,31 @@ A failed query's message starts with a short prefix that tells you who said it. |---|---|---| | `engine: …` | The database engine | The engine received the query and rejected it: a syntax error, an unknown table or column, an unsupported statement, a type mismatch. The rest of the text is the engine's own message. | | `connect: …` | Egret | Egret could not reach the engine (host unreachable, port closed, TLS failure). Check the connection under [Databases](../../workspace-settings/databases). | -| `timeout` | Egret | The query ran longer than the server's `engine.query_timeout_secs` (60 seconds by default). | +| `timeout: the query ran longer than the 60 s limit for SQL Lab queries and was stopped. …` | Egret | The query ran longer than the server's `engine.query_timeout_secs` (60 seconds by default). The message names the setting. When Trino stops the query itself at that limit, the message is Trino's *exceeded maximum time limit* followed by the same hint. | | `engine authentication failed` | Egret | The engine refused the identity Egret presented. With per-user delegation this usually means your engine token could not be verified. | | `forbidden` | Egret or the engine | You are not allowed to run this. On a delegated connection, a user without an SSO session gets it. | -(The `connect:`, `timeout` and authentication texts come from the server code. Only the `engine:` family was reproduced on the demo stack: its one Trino connection is healthy and not delegated, and every `tpch.tiny` query ends well inside the 60-second timeout.) +(The `connect:` and authentication texts come from the server code. They were not reproduced on the demo stack: its one Trino connection is healthy and not delegated.) ## Read the position in the message -Trino reports where the problem is as `line L:C`. In SQL Lab the numbers can differ from your editor: +Trino reports where the problem is as `line L:C` (StarRocks as `at line L, column C`). The numbers match the lines of your editor: the row limit is added on a line after your text, and when Egret has to wrap a query it cannot read (see [Row limit](./editor#row-limit)), it maps the line back to yours. -- Egret wraps a single `SELECT` or `WITH` statement in `SELECT * FROM ( … ) AS _egret_lim LIMIT n`, which pushes everything one line down. A typo on line 1 of the editor is reported on **line 2**. The column is correct. -- With **Run selection**, the numbers count from the first character of the selected text, not from the top of the editor. -- A statement that is not wrapped (for example `SHOW TABLES`) keeps its own line numbers. +With **Run selection**, the numbers count from the first character of the selected text, not from the top of the editor. ## Common errors -These were produced on the demo Trino with the `tpch` catalog. +These were produced on the demo Trino with the `tpch` catalog, each from a one-line query. | Message | Cause and fix | |---|---| -| `engine: line 2:35: Table 'tpch.tiny.ordres' does not exist` | A misspelt table. Open the table in the [Explorer](./schema-browser) or use autocomplete to insert the name. | -| `engine: line 2:8: Column 'x' cannot be resolved` | The column is not in the tables in `FROM`. Expand the table in the Explorer to see its columns. | +| `engine: line 1:35: Table 'tpch.tiny.ordres' does not exist` | A misspelt table. Open the table in the [Explorer](./schema-browser) or use autocomplete to insert the name. | +| `engine: line 1:8: Column 'x' cannot be resolved` | The column is not in the tables in `FROM`. Expand the table in the Explorer to see its columns. | | `engine: line 1:9: mismatched input ';'. Expecting: …` | Two statements in one run. Select one statement and use **Run selection**. | | `engine: Division by zero` | A runtime error in a row. Guard the divisor, for example with `NULLIF(x, 0)`. | | `engine: Cannot cast 'abc' to INT` | A value cannot be converted. Use `TRY_CAST` on Trino to get `NULL` instead of an error. | -| `engine: line 2:8: Function 'nosuchfn' not registered` | The function name is wrong or belongs to another engine. Trino and StarRocks have different functions. | -| `engine: line 2:15: Catalog 'nosuchcatalog' not found` | A catalog that does not exist on this connection. Check the first level of the [Explorer](./schema-browser). | +| `engine: line 1:8: Function 'nosuchfn' not registered` | The function name is wrong or belongs to another engine. Trino and StarRocks have different functions. | +| `engine: line 1:15: Catalog 'nosuchcatalog' not found` | A catalog that does not exist on this connection. Check the first level of the [Explorer](./schema-browser). | | `engine: This connector does not support dropping tables` | The statement reached the engine and the connector refused it. SQL Lab does not block statements; the engine and its access policies decide. | ## When a query fails @@ -73,13 +71,11 @@ These were produced on the demo Trino with the `tpch` catalog. | The page *403 — Forbidden* | The **SQL Lab** module is switched off for this workspace. See [Permissions and availability](./permissions). | | The toast *Pick a database* | The tab has no database chosen. Choose one in the Explorer. | | *Couldn’t load — no access or engine error.* in the Explorer | The browse request failed; see [The schema browser](./schema-browser#states-you-can-meet). | -| A toast containing `forbidden` after pressing **Run** | Your role does not include `query:run`. | +| **Query not started** with `forbidden` after pressing **Run** | Your role does not include `query:run`. | ## Limits and known issues -- Parameter and permission errors appear as toasts with raw JSON text. -- Line numbers in engine errors are shifted by one line for wrapped queries. -- The *engine:* prefix is sometimes doubled (`engine: engine: …`), for example when a dataset is created from a query the engine rejects. +- With **Run selection**, line numbers count from the start of the selection. ## Further reading diff --git a/platform/explore/sql-lab/results.md b/platform/explore/sql-lab/results.md index e0743ad..48cd731 100644 --- a/platform/explore/sql-lab/results.md +++ b/platform/explore/sql-lab/results.md @@ -19,6 +19,8 @@ After a successful run, the panel shows: Numeric columns (the integer, decimal and floating-point types of Trino and StarRocks) are right-aligned; other columns are left-aligned. `NULL` shows as grey italic *NULL*. Long values are cut off with an ellipsis; see **Expand a value** below. +Floating-point cells (`double`, `real` and `float`) are shown rounded to 15 significant digits, which hides binary representation noise: `63339475.32000001` shows as `63339475.32`, and `0.1E0 + 0.2E0` as `0.3`. Only the display is rounded. **Copy**, the exports, **Download CSV** and the expanded cell keep the exact value. Integer and `decimal` columns are shown as the engine returned them. + ### Sort Click a column name to sort the rows by it: the first click sorts ascending, the second descending, the third removes the sort. Numeric columns sort by number and the rest by text. `NULL` values always go last. Sorting happens in your browser on the rows that are loaded; it does not change the query. @@ -73,26 +75,22 @@ Three different numbers decide how many rows you can work with. |---|---|---| | **Limit** in the toolbar | 1,000 | How many rows the engine returns for the run. Up to 100,000 (the server setting `engine.max_rows`). See [Row limit](./editor#row-limit). | | Rows loaded in the grid | 1,000 | How many of those rows your browser loads for sorting, filtering, charting, **Copy** and **Download JSON**. | -| Query time out | 60 seconds | The server setting `engine.query_timeout_secs`. A longer query fails with `timeout`. | +| Query time out | 60 seconds | The server setting `engine.query_timeout_secs`. A longer query fails with a message that names the limit and the setting (see [Run and cancel](./editor#run-and-cancel)). | When a run returns more than 1,000 rows, a grey note next to the counter says *interactive over first 1,000 rows*: the row count shows the real total, but sort, filter, chart, copy and JSON apply to the first 1,000. Use **Download CSV** for everything. -When Egret itself stopped reading at the limit (this happens for statements that are not a single `SELECT` or `WITH`, such as `SHOW` or `TABLE`), an amber chip reads *Results were limited to 1,000 rows.* +When the run had more rows than the limit, an amber chip reads *Results were limited to 1,000 rows.* This holds for every kind of statement: a `SELECT`, a `SHOW`, a `TABLE` or a query with its own larger `LIMIT`. ![A result that Egret stopped at the limit](/img/platform/sql-datasets/sqllab-truncated.png) -A single `SELECT` is limited inside the engine instead, and then there is no chip: a result that has exactly as many rows as the limit may have been cut. - ## Who can see a result A result belongs to the user who ran the query. Another workspace member cannot open it, even with the query id; platform admins can. Result rows are stored by Egret in its object store, one file per run. ## Limits and known issues -- The status chip for a cancelled run is missing; a cancelled query leaves the panel in its empty state. - A zero-row result is reported with the filter message *No rows match “”.* - The chart groups with a sum, so a repeated X value adds its Y values together; it is not a replacement for a dataset chart. -- The row order of a single `SELECT` with `ORDER BY` but no `LIMIT` is not guaranteed on Trino; sort in the grid or add a `LIMIT` (see [Row limit](./editor#row-limit)). - Counters read *1 rows* for a one-row result. ## Further reading diff --git a/platform/explore/sql-lab/save-as-dataset.md b/platform/explore/sql-lab/save-as-dataset.md index 943cb19..c453788 100644 --- a/platform/explore/sql-lab/save-as-dataset.md +++ b/platform/explore/sql-lab/save-as-dataset.md @@ -26,14 +26,13 @@ Egret embeds the saved query as a subquery, `SELECT … FROM () AS _ | Do | Why | |---|---| | Give every computed column an alias. | An unnamed expression such as `select 1+1` becomes `_col0`. | -| Keep the column names unique. | Two columns with the same name, such as `select 1 as x, 2 as x`, make the dataset creation fail with a generic *internal error*. | -| Remove the trailing semicolon. | `select 1 as x;` works when you run it in SQL Lab but fails as a dataset, with a syntax error from the engine at the closing parenthesis. | +| Keep the column names unique. | Two columns with the same name, such as `select 1 as x, 2 as x`, are refused with *the query returns more than one column named `x`; give each column its own name with AS*. | | Remove `{{parameter}}` placeholders. | A dataset has no parameter values, so the engine sees the braces. See [Query parameters](./parameters#where-parameters-work). | | Use one `SELECT` or `WITH` statement. | `SHOW`, `DESCRIBE`, `EXPLAIN`, `CREATE` and several statements are not queries and are refused by the engine. | -`WITH` clauses, joins, window functions and `ORDER BY` are fine. A line comment at the very end of the query is also fine. +`WITH` clauses, joins, window functions and `ORDER BY` are fine. A trailing semicolon and a line comment at the very end of the query are also fine: Egret saves the SQL without the final `;`, so `select 1 as x;` is stored as `select 1 as x`. -If the engine rejects the query, the dialog shows an error toast with the engine's message, for example `engine: engine: line 2:14: mismatched input ';'`. The prefix *engine:* appears twice and the line number is one too high, because Egret adds a line in front of your text. +If the engine rejects the query, the dialog shows an error toast with the engine's message, for example `engine: line 1:22: Table 'tpch.tiny.ordres' does not exist`. The line and column are those of your text. ## Worked example: orders with their customers @@ -65,8 +64,8 @@ The result is a dataset with eight columns. From there you add two [calculated c | Symptom | What to check | |---|---| -| The dialog shows an error toast with a syntax error mentioning `)` | A trailing semicolon, or a comment on the last line that swallows the closing parenthesis. | -| The dialog shows *internal error* | Two columns have the same name. Alias one. | +| The dialog shows an error toast with a syntax error mentioning `)` | A comment on the last line that swallows the closing parenthesis. | +| The dialog shows *the query returns more than one column named …* | Two columns have the same name. Alias one. | | The dataset is created but a chart on it is very slow | The query is slow. Add filters inside the saved SQL, or save a smaller query. | | Columns are named `_col0`, `_col1` | Unnamed expressions. Add `AS name`. | | You edited the query after saving and the dataset did not change | The dataset keeps its own copy of the SQL; edit the dataset instead. | @@ -76,7 +75,7 @@ The result is a dataset with eight columns. From there you add two [calculated c - The dataset appears in **Explore → Datasets** with the *virtual* tag and `SQL` as its source. - Editing the dataset's SQL later is done on the dataset page, followed by **Refresh** (see [Refresh and schema sync](../datasets-and-metrics/refresh-and-sync)). - The dataset keeps its own copy of the SQL. Changing or deleting a saved query in SQL Lab does not affect it, and neither does editing the editor tab afterwards. -- To turn an existing virtual dataset into a new one, use **Duplicate** in the dataset's row menu. It opens the same dialog with the SQL filled in and the name *copy of …*. +- To copy an existing dataset, use **Duplicate** in the dataset's row menu. It opens the **Duplicate dataset** dialog with the name *copy of …*, and the copy keeps the SQL, the column settings and the metrics. See [Duplicate](../datasets-and-metrics/organize#duplicate). ## Charts straight from a result diff --git a/platform/get-started/first-dashboard.md b/platform/get-started/first-dashboard.md index dab56d7..5332fdb 100644 --- a/platform/get-started/first-dashboard.md +++ b/platform/get-started/first-dashboard.md @@ -57,10 +57,7 @@ errors](../deploy-operate/troubleshooting/engine-406-502). ORDER BY 3 DESC ``` -3. Press **Run**. The results appear under **Results**, with the run time and row count in the toolbar. - - The rows may not come back in the order of the `ORDER BY`: SQL Lab wraps the query in `SELECT * FROM (…) LIMIT 1000` to - apply the **Limit**, and Trino drops the ordering of a subquery. Sort a column by its header if the order matters. +3. Press **Run**. The results appear under **Results**, sorted by revenue, with the run time and row count in the toolbar. ![SQL Lab with the query results](/img/platform/ops/get-started/sql-lab-run.png) diff --git a/platform/known-issues.md b/platform/known-issues.md index 8f55acf..baada9d 100644 --- a/platform/known-issues.md +++ b/platform/known-issues.md @@ -15,9 +15,9 @@ and missing pieces. Entries are **recorded, not fixed**; they feed the product r | KI-001 | Databases | UX | S2 | Registering a dataset against a Trino without `http-server.process-forwarded=true` fails with a generic *502 Bad Gateway*. Egret always sends `X-Forwarded-Proto` and Trino answers HTTP 406. The UI should say what is wrong. | | KI-002 | Auth | UX | S3 | Local sign-in is limited to 5 attempts per 5 minutes and rejected (429) attempts keep the window open. There is no message telling the user when they can retry. | | KI-003 | Dashboards | UX | S2 | A chart that is not fully configured (no X-axis) renders as an **empty card** on a dashboard, with no hint, while the chart editor says *Set up your Bar*. | -| KI-004 | SQL Lab | UX | S3 | Floating-point results are shown with representation noise (`63339475.32000001`). Provide display rounding or a number format in results. | +| KI-004 | SQL Lab | UX | S3 | Floating-point results are shown with representation noise (`63339475.32000001`). Provide display rounding or a number format in results. *Status:* fixed in platform #721 (October 2026): `double`, `real` and `float` cells are shown rounded to 15 significant digits (`63339475.32000001` reads `63339475.32`, `0.1 + 0.2` reads `0.3`). Copy, export, CSV and the expanded cell keep the exact value; integers and decimals are not changed. | | KI-005 | Workspace settings | UX | S3 | The Workspace settings page shows two **Collapse** buttons (the app sidebar and the settings sidebar). | -| KI-006 | SQL Lab | Defect? | S2 | verify: typing a long statement quickly into the editor via browser automation produced scrambled characters (autocomplete interference). Inserting the text at once works. Needs a manual check with a real keyboard. | +| KI-006 | SQL Lab | Defect? | S2 | verify: typing a long statement quickly into the editor via browser automation produced scrambled characters (autocomplete interference). Inserting the text at once works. Needs a manual check with a real keyboard. *Status:* fixed in platform #721 (October 2026). It reproduced with a 1,000-row result on screen: typing at one key every 40 ms lost characters, and at 20 ms the text was garbled, because the editor was handed back its own text one keystroke late. The editor now only takes text it did not send itself (Format, a snippet, a history entry); typed text stays exact at 0 to 40 ms a key. The results grid still re-renders on each keystroke: KI-248. | | KI-007 | Naming | Consistency | S3 | The UI calls them *Engines* (Workspace settings → Engines), the API and parts of the code call them *databases*, and the Control Plane has its own *Engines*. Pick one vocabulary. | | KI-008 | Charts | UX | S3 | Saving an existing chart takes two steps: **Update** opens a dialog and a second **Update** confirms. | | KI-009 | Charts API | Docs | S3 | Creating a chart through the API requires `params.version = 2` and a `settings` object; the axis mapping lives in `settings`, not in `spec.dimensions`. Charts created with only `spec` load with the X-axis empty. Document the payload. | @@ -37,16 +37,16 @@ and missing pieces. Entries are **recorded, not fixed**; they feed the product r ## Findings from the detailed pages -Collected while writing the detailed pages for each area (October 2026), checked against the running demo instance and the product source. KI-154 to KI-213 come from re-verifying every area on the Docker demo stack (`just demo-up`, October 2026), which also closed or updated older rows (marked *Status* or *Docker demo stack*). KI-214 to KI-235 come from a whole-app UI consistency sweep of 78 screens (admin, light theme, 1440×900, same stack), then the dark theme, 768 and 390 px widths, the editors and a non-admin member. KI-236 to KI-246 come from the Wave 1 fixes of the Known-issues fix programme (October 2026), which also closed the rows marked *fixed in platform #…*. Same legend as above. **Open S1 items:** none (KI-013, KI-040, KI-059, KI-060, KI-112, KI-136, KI-159, KI-175 and KI-187 are closed). +Collected while writing the detailed pages for each area (October 2026), checked against the running demo instance and the product source. KI-154 to KI-213 come from re-verifying every area on the Docker demo stack (`just demo-up`, October 2026), which also closed or updated older rows (marked *Status* or *Docker demo stack*). KI-214 to KI-235 come from a whole-app UI consistency sweep of 78 screens (admin, light theme, 1440×900, same stack), then the dark theme, 768 and 390 px widths, the editors and a non-admin member. KI-236 to KI-246 come from the Wave 1 fixes of the Known-issues fix programme (October 2026), which also closed the rows marked *fixed in platform #…*. KI-247 to KI-259 come from the Wave 2 fixes (SQL Lab, charts, datasets and the Data API: platform #720, #721, #724 and #725), which closed or partly closed the rows they name. Same legend as above. **Open S1 items:** none (KI-013, KI-040, KI-059, KI-060, KI-112, KI-136, KI-159, KI-175 and KI-187 are closed). ### Data API, Alerts and Apps | ID | Area | Type | Sev | Description | |---|---|---|---|---| -| KI-023 | Data API | Bug | S2 | Saving a new **Path** on an endpoint's draft changes the served URL immediately, before publish. The live version's snapshot still has the old path, yet the router matches the draft path: the old URL answers `404 not_found` and the new URL answers with the live version's data. The doc claim "consumers only ever see the live version, never the draft" is false for path (and probably method). Publish then flags it as breaking, after the damage. *Steps:* `PATCH /api/ws/default/data-api/endpoints/{id}` `{"path":"orders/lookup"}` on a live endpoint at `x/probe`, then `GET /data-api/v1/default/x/probe` is 404 and `/orders/lookup` is 200 with `meta.version` 1; `GET .../versions` shows live snapshot path `/x/probe`. | -| KI-024 | Data API | Bug | S3 | Versions tab: the **N breaking change** badge overlaps the note text because the Version column is too narrow. *Steps:* `/explore/default/data-api/endpoints/{id}/versions` for an endpoint with a breaking publish (screenshot `data-api/endpoint-versions`). | +| KI-023 | Data API | Bug | S2 | Saving a new **Path** on an endpoint's draft changes the served URL immediately, before publish. The live version's snapshot still has the old path, yet the router matches the draft path: the old URL answers `404 not_found` and the new URL answers with the live version's data. The doc claim "consumers only ever see the live version, never the draft" is false for path (and probably method). Publish then flags it as breaking, after the damage. *Steps:* `PATCH /api/ws/default/data-api/endpoints/{id}` `{"path":"orders/lookup"}` on a live endpoint at `x/probe`, then `GET /data-api/v1/default/x/probe` is 404 and `/orders/lookup` is 200 with `meta.version` 1; `GET .../versions` shows live snapshot path `/x/probe`. *Status:* fixed in platform #720 (October 2026, migration 0204): the served URL is the live version's method and path. A new path or method saved on the draft goes live only at publish (Config says *The new path goes live when you publish. Callers still use GET /x.* and the endpoint list shows *Pending publish: METHOD /path*); after publish the old URL answers 404, with no redirect. Rollback serves the older version at its own URL, the OpenAPI document and Try it use the live route, and publishing at a URL another endpoint still serves is refused with 409. A draft can still be saved at such a URL: KI-253. | +| KI-024 | Data API | Bug | S3 | Versions tab: the **N breaking change** badge overlaps the note text because the Version column is too narrow. *Steps:* `/explore/default/data-api/endpoints/{id}/versions` for an endpoint with a breaking publish (screenshot `data-api/endpoint-versions`). *Status:* fixed in platform #720 (October 2026): the Version column is wider and its chips wrap, so the badge no longer covers the note at 1440 or 1024 px. | | KI-025 | Data API | UX | S3 | Expired client shows `expires 7 years ago` next to the **Expired** badge; a past expiry reads as a future one. A revoked client row has no actions menu while an expired one does. *Steps:* `/explore/default/data-api/clients` (screenshot `data-api/clients-list`). | -| KI-026 | Data API | UX | S3 | Number parameter constraints (**minimum**, **maximum**) are two unlabeled boxes under the type; once filled, the placeholder that names them disappears, so `0` and `1000000` appear with no meaning. String and date boxes have the same problem. *Steps:* Query tab, **Database and parameters**, parameter `min_total` (screenshot `data-api/endpoint-params`). | +| KI-026 | Data API | UX | S3 | Number parameter constraints (**minimum**, **maximum**) are two unlabeled boxes under the type; once filled, the placeholder that names them disappears, so `0` and `1000000` appear with no meaning. String and date boxes have the same problem. *Steps:* Query tab, **Database and parameters**, parameter `min_total` (screenshot `data-api/endpoint-params`). *Status:* fixed in platform #720 (October 2026): each constraint box has a visible label: **Minimum** and **Maximum** for numbers; **Min length**, **Max length** and **Pattern (regex)** for strings; **Not before** and **Not after** for dates and timestamps. | | KI-027 | Data API / Apps | UX | S3 | **Who** in the Versions history shows a shortened user id (`6d60b4c4`) instead of the publisher's email for a platform admin. The Apps version list does the same. *Steps:* Versions tab; Apps **Versions** dialog. *Docker demo stack (Oct 2026):* Data API **Versions → Who** now shows `admin@egret.local`; the Apps **Versions** dialog still shows `by 8a66d837`, so the row now applies to Apps only. | | KI-028 | Data API | Gap | S3 | `meta.columns` in a response ignores **Naming**: with camelCase the data keys are `orderStatus` but `meta.columns[].name` is still `order_status`. *Steps:* Walk-through endpoint with `naming: camel`. | | KI-029 | Data API | Gap | S3 | `GET /api/ws/{ws}/data-api/activity/summary` returns `total.endpoint_name` set to one arbitrary endpoint's name ("Yearly revenue summary") although `total` covers all endpoints. The UI does not show it. *Steps:* `GET .../activity/summary?range=24h`. | @@ -76,7 +76,7 @@ Collected while writing the detailed pages for each area (October 2026), checked | KI-177 | Apps | UX | S2 | **Publish draft rev N** pressed right after Ctrl+S answers `409 {"error":"the draft changed since it was read","current_revision":2}` (toast *The draft changed while you were publishing…*), because the header re-reads the draft revision only every 5 s (`useApp(..., {refetchMs})`). *Workaround:* wait until the button shows the new revision, then publish. *Steps:* new app → cell `1 + 1` → Ctrl+S → **Publish draft rev 1** within 0.3 s → 409; about 7 s later **Publish draft rev 2** works. | | KI-178 | Apps | Gap | S3 | The Data Apps image ships `plotly` (7.1.0) and `altair` but not `numpy` or `pandas`, so `import plotly.express` fails (*Plotly Express requires numpy to be installed*) and so does anything that calls `.to_pandas()`. Only Altair (`mo.ui.altair_chart`) and `plotly.graph_objects` with lists work. *Steps:* `python3 -c "import plotly.express"` in the egret container; `apps-image/constraints.txt` has no numpy. | | KI-179 | Apps / Security | Gap | S2 | Over plain http the session cookie is `egret_session` (host-only on `egret.localtest.me`) with no `__Host-` prefix, so author code served from the sibling `apps.localtest.me` can set `Domain=localtest.me` cookies that the browser sends to Egret (cookie tossing, session fixation). Demo-only in practice (production is https), but the demo stack is what new users run. *Steps:* cookie names in a signed-in storage state are `egret_session`, `egret_csrf`; demo guide "Safety" section. | -| KI-180 | Data API | Bug | S2 | Access tab: the note **Revoking will not remove access** (*This account also reaches invoke through the workspace…*) is shown on every granted service account, including ones with no workspace role, for which it is false. The inherited-access check seems to read the account's OpenAPI document, which lists the endpoint because of the grant. *Steps:* **Orders by status** → Access: `finance-dashboard` and `orders-reporting-bot` carry the note; revoke `finance-dashboard` → its next call answers `403 not_granted` and the path leaves its OpenAPI `paths`. | +| KI-180 | Data API | Bug | S2 | Access tab: the note **Revoking will not remove access** (*This account also reaches invoke through the workspace…*) is shown on every granted service account, including ones with no workspace role, for which it is false. The inherited-access check seems to read the account's OpenAPI document, which lists the endpoint because of the grant. *Steps:* **Orders by status** → Access: `finance-dashboard` and `orders-reporting-bot` carry the note; revoke `finance-dashboard` → its next call answers `403 not_granted` and the path leaves its OpenAPI `paths`. *Status:* fixed in platform #720 (October 2026): the note is shown only for an account that keeps invoke through a workspace role; an account with only the endpoint grant no longer carries it, on any endpoint state. | | KI-181 | Apps | Gap | S3 | A user shared into an app as **Viewer** who is not a workspace member gets `POST …/ticket` 200 and `GET …/apps/{id}` 200, but `GET /api/ws/{ws}/apps` answers 403, and no workspace switcher entry exists, so the app can only be opened by direct URL. *Steps:* share **Revenue explorer** with `member-sso@egret.local` (not in `default`): list 403, detail 200, run ticket 200, edit ticket 403. | | KI-182 | Apps | Consistency | S3 | The Apps list's row menu buttons are all named **Actions**; the Data API lists name them per row (*Actions for Top customers*), so screen-reader users cannot tell the Apps rows apart. *Steps:* `/explore/default/apps`, accessible names of the `⋯` buttons. | | KI-183 | Data API | Consistency | S3 | **New service account** puts **Create service account** under the page description on the left; **New endpoint** and **New client** put their create button at the top right. *Steps:* `/explore/default/data-api/clients/new` vs `/endpoints/new`. | @@ -87,25 +87,25 @@ Collected while writing the detailed pages for each area (October 2026), checked | ID | Area | Type | Sev | Description | |---|---|---|---|---| -| KI-043 | Charts / Object detail | Bug | S2 | The **Export chart** popover offers `.png` for Object detail, and the downloaded file is a 286-byte image of the pager arrow icon instead of the record (the exporter grabs the first `` in the preview). *Steps:* Open chart "Order record" → download icon → **.png** → **Download**; the file is 286 bytes. | -| KI-044 | Charts / Progress | Bug | S2 | An empty **Goal** is treated as `0`, not as "no goal": the bar reads **Goal 0** and **Goal exceeded**. The message **No goal set** can never appear for an empty goal. *Steps:* New Progress chart, **Metric** = Orders, no **Goal**. | -| KI-045 | Charts / Row | Misleading UI | S2 | The gallery says rows are "sorted by value", but a row chart is alphabetical until a **Sort** is added. With **Sort** = revenue descending, the largest bar is drawn at the bottom, so a "top 10" reads upside down; ascending order flips the ranking but then **Row limit** returns the smallest ten. *Steps:* "Top clerks by revenue" (Sort revenue desc, Row limit 10). | -| KI-046 | Charts / Map | UX | S2 | A region value that matches no region is dropped silently. There is no note (pin maps do have one), and the legend range still includes the dropped rows' values. Easy to hit with Vietnam, whose keys are names such as `TP. Hồ Chí Minh`. *Steps:* Map "Revenue by province" with a row named `Thành phố Hồ Chí Minh`: Ho Chi Minh City is unshaded, legend still tops out at its value. | -| KI-047 | Charts / Editor | Bug | S2 | **Change** (chart type) clears the X-axis, Y-axis and all other fields, even between bar, line, area and combo which share one settings namespace. Source comments say a bar → line switch keeps its config. *Steps:* Open "Revenue by order priority" → **Change** → **Line**: **X-axis** shows **Pick a dimension…**, **Y-axis** empty. Same for bar → pie. | +| KI-043 | Charts / Object detail | Bug | S2 | The **Export chart** popover offers `.png` for Object detail, and the downloaded file is a 286-byte image of the pager arrow icon instead of the record (the exporter grabs the first `` in the preview). *Steps:* Open chart "Order record" → download icon → **.png** → **Download**; the file is 286 bytes. *Status:* fixed in platform #724 (October 2026): `.png` is offered only for a chart drawn as an image, and the exporter takes the chart's own ``, never an icon. Object detail now offers `.csv`, `.xlsx` and `.json` only. | +| KI-044 | Charts / Progress | Bug | S2 | An empty **Goal** is treated as `0`, not as "no goal": the bar reads **Goal 0** and **Goal exceeded**. The message **No goal set** can never appear for an empty goal. *Steps:* New Progress chart, **Metric** = Orders, no **Goal**. *Status:* fixed in platform #724 (October 2026): an empty **Goal**, or a goal column whose value is empty, counts as no goal, and the chart says **No goal set**. | +| KI-045 | Charts / Row | Misleading UI | S2 | The gallery says rows are "sorted by value", but a row chart is alphabetical until a **Sort** is added. With **Sort** = revenue descending, the largest bar is drawn at the bottom, so a "top 10" reads upside down; ascending order flips the ranking but then **Row limit** returns the smallest ten. *Steps:* "Top clerks by revenue" (Sort revenue desc, Row limit 10). *Status:* fixed in platform #724 (October 2026): with no **Sort**, a row chart orders by its first metric, largest first (ties by the category), and bars are drawn top to bottom in query order, so a top 10 reads from the top. An authored sort still wins. A saved row chart whose stored sort was the old alphabetical default keeps it, and the **Sort** control now shows it. | +| KI-046 | Charts / Map | UX | S2 | A region value that matches no region is dropped silently. There is no note (pin maps do have one), and the legend range still includes the dropped rows' values. Easy to hit with Vietnam, whose keys are names such as `TP. Hồ Chí Minh`. *Steps:* Map "Revenue by province" with a row named `Thành phố Hồ Chí Minh`: Ho Chi Minh City is unshaded, legend still tops out at its value. *Status:* fixed in platform #724 (October 2026): region maps leave out rows that match no region before drawing, so the legend covers only the shaded regions, and a note under the chart names up to three of the unmatched values (*Showing 20 of 50 rows: 30 match no region on the World countries map (…)*). Checked live on the World map only; the Vietnam case is covered by a unit test on the shipped Vietnam map. | +| KI-047 | Charts / Editor | Bug | S2 | **Change** (chart type) clears the X-axis, Y-axis and all other fields, even between bar, line, area and combo which share one settings namespace. Source comments say a bar → line switch keeps its config. *Steps:* Open "Revenue by order priority" → **Change** → **Line**: **X-axis** shows **Pick a dimension…**, **Y-axis** empty. Same for bar → pie. *Status:* fixed in platform #724 (October 2026): **Change** between bar, line, area, combo, row and scatter keeps the fields, the sort and the result; a switch to any other type (for example bar to pie) still starts empty. | | KI-048 | Charts / Box plot | Gap | S2 | Quartiles are computed in the browser from the raw rows the query returns, capped by **Row limit** (max 10,000). On a bigger dataset the box plot summarises only the first rows the database returns, with no warning. *Steps:* "Line price spread by ship mode": Order lines has about 60,000 rows, the chart uses 5,000. | -| KI-049 | Charts / Editor | Misleading UI | S3 | The export popover says **The maximum download size is 1 million rows.** A chart fetches at most 10,000 rows, so the limit that applies is 10,000. *Steps:* Editor → download icon. | +| KI-049 | Charts / Editor | Misleading UI | S3 | The export popover says **The maximum download size is 1 million rows.** A chart fetches at most 10,000 rows, so the limit that applies is 10,000. *Steps:* Editor → download icon. *Status:* fixed in platform #724 (October 2026): the popover says **The maximum download size is 10,000 rows.**, from the same limit the chart fetches with. | | KI-050 | Charts / Editor | UX | S3 | Axis titles, tooltip series names, the **Sort** field menu and the drill menu show database names (`revenue`, `orderpriority`), not the dataset's display names (**Revenue**), unlike the field pickers. *Steps:* Bar chart "Revenue by order priority": tooltip reads `revenue: 434,187,711.87`; **Sort** menu lists `orderpriority`, `revenue`. | -| KI-051 | Charts / Editor | UX | S3 | **Row limit** is shown on Big number, Progress and Gauge, but the query always returns one row, so the box has no effect. *Steps:* New Big number → **Data**. | +| KI-051 | Charts / Editor | UX | S3 | **Row limit** is shown on Big number, Progress and Gauge, but the query always returns one row, so the box has no effect. *Steps:* New Big number → **Data**. *Status:* fixed in platform #724 (October 2026): **Row limit** is no longer shown for Big number, Progress and Gauge. | | KI-052 | Charts / Editor | Gap | S3 | There is no filter builder. Filters exist only as chips created by drilling into a value, so a chart cannot be restricted to a fixed condition without a custom dataset. *Steps:* **Charts** page and editor; `FilterChips` is fed only by the drill menu. | | KI-053 | Charts / Editor | Inconsistency | S3 | Spelling: **Colour** (Progress), **Conditional colours** (Big number), **Switch positive / negative colours** (Trend) against **color** on every other panel. *Steps:* Display tab of those three types. | | KI-054 | Charts / Editor | UX | S3 | Waterfall and box plot axes panels say **Show label** where the other types say **X label** / **Y label**, and have **Scale** instead of **Y scale**. *Steps:* Axes tab of "Profit bridge" vs "Revenue by order priority". | | KI-055 | Charts / Datasets list | UX | S3 | The Trash view's summary reads "1 chart · 1 folder" although folders are not shown in the Trash. *Steps:* Move a chart to Trash while a folder exists → Trash view. *Docker demo stack (Oct 2026):* the **Datasets** Trash view does the same (*1 dataset · 2 folders*). | | KI-056 | Charts / Funnel | UX | S3 | The **Steps** list only appears after the first run, and stage order follows the database order unless a **Sort** or the Steps list is used; nothing in the empty state says so. *Steps:* "Checkout conversion" with the **Sort** removed. | -| KI-057 | Charts / Waterfall | UX | S3 | The **Total color** swatch shows slate (`#4C5773`) while the total bar is drawn in the dark text color by default, so the swatch does not match the bar until a color is chosen. *Steps:* "Profit bridge" → **Display** → **Total color** vs the **Total** bar. | +| KI-057 | Charts / Waterfall | UX | S3 | The **Total color** swatch shows slate (`#4C5773`) while the total bar is drawn in the dark text color by default, so the swatch does not match the bar until a color is chosen. *Steps:* "Profit bridge" → **Display** → **Total color** vs the **Total** bar. *Status:* fixed in platform #724 (October 2026): with no colour chosen, the **Total color** swatch shows the colour the total bar is drawn in, in light and dark themes. | | KI-058 | Charts / Formatting | UX | S3 | Identifier columns (`orderkey`, `custkey`) are formatted as numbers with thousands separators by default (29,989) in tables and Object detail. *Steps:* "Order record" preview. | -| KI-163 | Charts / Scatter | UX | S3 | Scatter axis titles (`customers`, `avg_balance`) are drawn in a very light grey, almost invisible on the white plot, unlike the axis titles of bar, line and combo charts. *Steps:* chart "Customer balance vs size by nation". | -| KI-164 | Charts / Funnel | UX | S3 | With **Funnel type** = **Bar**, the X axis drops every other stage label at the default 1440 px editor width (only *Visited site*, *Added to cart*, *Paid*), so two bars have no name. *Steps:* "Checkout conversion" → Display → Funnel type **Bar — a column per step**. | -| KI-165 | Charts / Box plot | UX | S3 | With **Show values on data points** on, the last box's label is clipped at the right edge (`31.6` instead of `31.6K`). *Steps:* "Line price spread by ship mode" → Display → All points + Show values on data points. | +| KI-163 | Charts / Scatter | UX | S3 | Scatter axis titles (`customers`, `avg_balance`) are drawn in a very light grey, almost invisible on the white plot, unlike the axis titles of bar, line and combo charts. *Steps:* chart "Customer balance vs size by nation". *Status:* fixed in platform #724 (October 2026): scatter axis titles use the axis-label colour, the same as bar charts, in light and dark themes. | +| KI-164 | Charts / Funnel | UX | S3 | With **Funnel type** = **Bar**, the X axis drops every other stage label at the default 1440 px editor width (only *Visited site*, *Added to cart*, *Paid*), so two bars have no name. *Steps:* "Checkout conversion" → Display → Funnel type **Bar — a column per step**. *Status:* fixed in platform #724 (October 2026): in bar mode the funnel shows either every stage name or none, by the same fit rule as other bar charts; all five stages show at 1440 and 1024 px. | +| KI-165 | Charts / Box plot | UX | S3 | With **Show values on data points** on, the last box's label is clipped at the right edge (`31.6` instead of `31.6K`). *Steps:* "Line price spread by ship mode" → Display → All points + Show values on data points. *Status:* fixed in platform #724 (October 2026): the plot leaves room on the right for the last box's value labels, so the label is no longer cut at 1024 or 1440 px. | ### Dashboards @@ -124,7 +124,7 @@ Collected while writing the detailed pages for each area (October 2026), checked | KI-069 | dashboards / interactivity | Bug | S3 | The drill menu is positioned at the pointer without clamping to the viewport, so on a card at the right edge it is cut off. *Steps:* Sales performance → click a bar in "Top 10 clerks". | | KI-070 | dashboards / interactivity | Gap | S3 | "See these records" ignores the dashboard filters and shows the first 1000 rows of the whole dataset for the clicked value. *Steps:* Sales performance → click a pie slice → See these records (rows from 1992 shown while the date filter is 1995-1996). | | KI-071 | dashboards / interactivity | Gap | S3 | Custom URL clicks insert `{{value}}`, `{{column}}` and `{{slug}}` without URL encoding. *Steps:* `resolveClick` / `interpolateUrl`. | -| KI-072 | dashboards / export | Bug | S3 | The download popover promises "The maximum download size is 1 million rows", but the file is built in the browser from the rows the card loaded, which a chart caps at 10,000 (`MAX_CHART_ROWS`). *Steps:* Card → download. | +| KI-072 | dashboards / export | Bug | S3 | The download popover promises "The maximum download size is 1 million rows", but the file is built in the browser from the rows the card loaded, which a chart caps at 10,000 (`MAX_CHART_ROWS`). *Steps:* Card → download. *Status:* fixed in platform #724 (October 2026): the dashboard card's download popover says **The maximum download size is 10,000 rows.**, as in the chart editor. | | KI-073 | dashboards / export | Gap | S3 | The rendered print page (`/embed/print`) used for PNG/PDF reports draws chart cards only, with the chart's name and settings: card title and visualization overrides, text, heading and link cards, and tabs are ignored. *Steps:* `PrintObject.tsx`, `render/payload.rs` `build_dashboard`. *Docker demo stack (Oct 2026):* confirmed on a real PDF of Sales performance: "Top clerks" instead of the card title "Top 10 clerks", no heading or text card, the Orders tab stacked below. | | KI-074 | dashboards / list | UX | S3 | The Trash view still shows the **+ Dashboard** button and the folder count in the summary line. *Steps:* Dashboards → Trash. | | KI-075 | dashboards / list | UX | S3 | Deleting a folder, moving a dashboard to Trash and reverting a revision happen without confirmation. Deleting a folder moves its dashboards to Uncategorized. *Steps:* Folder ⋯ → Delete; row ⋯ → Move to Trash; History → Revert. | @@ -137,16 +137,16 @@ Collected while writing the detailed pages for each area (October 2026), checked | ID | Area | Type | Sev | Description | |---|---|---|---|---| -| KI-078 | SQL Lab | UX | S2 | A cancelled run leaves the Results panel on "No results yet." with no badge or message; only History shows status *cancelled*. *Steps:* Run `SELECT count(*) FROM tpch.sf1000.lineitem l JOIN tpch.sf1000.orders o ON l.orderkey = o.orderkey`, press **Cancel**. | -| KI-079 | SQL Lab | Bug | S2 | The command palette shortcut (Cmd/Ctrl K) does not open while the text cursor is in the editor: Monaco treats it as a chord prefix. It only works with focus outside the editor. The hint "⌘ K for commands" is therefore misleading. *Steps:* Click in the editor, press Cmd+K: nothing opens. Click the results area, press it: opens. | +| KI-078 | SQL Lab | UX | S2 | A cancelled run leaves the Results panel on "No results yet." with no badge or message; only History shows status *cancelled*. *Steps:* Run `SELECT count(*) FROM tpch.sf1000.lineitem l JOIN tpch.sf1000.orders o ON l.orderkey = o.orderkey`, press **Cancel**. *Status:* fixed in platform #721 (October 2026): a cancelled run shows **Query cancelled** in the Results panel and *Cancelled after N s* in the footer and the status chip. | +| KI-079 | SQL Lab | Bug | S2 | The command palette shortcut (Cmd/Ctrl K) does not open while the text cursor is in the editor: Monaco treats it as a chord prefix. It only works with focus outside the editor. The hint "⌘ K for commands" is therefore misleading. *Steps:* Click in the editor, press Cmd+K: nothing opens. Click the results area, press it: opens. *Status:* fixed in platform #721 (October 2026): Cmd K and Ctrl K open the command palette with the cursor in the editor. The trade-off: Monaco shortcuts that start with Cmd K are no longer available in SQL Lab (KI-249). | | KI-080 | SQL Lab | Gap | S2 | A saved query's SQL cannot be overwritten. **Save** always creates a new saved query; **Edit** changes only name and description. *Steps:* Saved tab, hover a row, **Edit** shows Name and Description only. | -| KI-081 | SQL Lab | UX | S2 | A single SELECT/WITH is wrapped in `SELECT * FROM (...) LIMIT n`, so the engine returns exactly n rows with `truncated=false`. The "Results were limited" chip never appears for it, so a cut result looks complete. Only non-SELECT statements (`TABLE`, `SHOW`) show the chip. *Steps:* `select * from tpch.sf1.orders` with limit 1,000: 1,000 rows, no chip; `TABLE tpch.sf1.orders`: chip shown. | +| KI-081 | SQL Lab | UX | S2 | A single SELECT/WITH is wrapped in `SELECT * FROM (...) LIMIT n`, so the engine returns exactly n rows with `truncated=false`. The "Results were limited" chip never appears for it, so a cut result looks complete. Only non-SELECT statements (`TABLE`, `SHOW`) show the chip. *Steps:* `select * from tpch.sf1.orders` with limit 1,000: 1,000 rows, no chip; `TABLE tpch.sf1.orders`: chip shown. *Status:* fixed in platform #721 (October 2026): a single query without its own `LIMIT`, `OFFSET` or `FETCH` gets `LIMIT n+1` appended instead of being wrapped, so a cut result shows **Results were limited to n rows.** A query with its own limit is sent unchanged. SQL the parser cannot read, or that uses comment forms the engines read differently (`/*!`, nested `/*`), is still wrapped, now also with `n+1`. The same path serves SQL Lab, chart data and alerts. | | KI-082 | SQL Lab | UX | S3 | Switching to another tab and back discards the result grid, status and parameter values (the tab body is remounted). *Steps:* Run a query, press **+**, click the first tab: "No results yet." | | KI-083 | SQL Lab | UX | S3 | Opening a saved query gives a tab titled "Saved" instead of the query's name. *Steps:* Saved tab, click a row. | | KI-084 | SQL Lab | UX | S3 | Clicking a table or column in the Explorer appends the name with no separator: `SELECT` becomes `SELECTtpch.tiny.nation`. *Steps:* Type `SELECT`, click a table. | | KI-085 | SQL Lab | UX | S3 | A zero-row result shows the filter message `No rows match “”.` *Steps:* Run a query with an empty-string parameter: 0 rows. | -| KI-086 | SQL Lab | UX | S3 | Errors found before the query starts (bad parameter, forbidden) appear as a toast with raw JSON: `Error: {"error":"invalid input: parameter 'n' is not a number: abc"}`. *Steps:* Parameter type **num**, value `abc`, **Run**. | -| KI-087 | SQL Lab | UX | S3 | Engine error positions are one line too high for wrapped queries (a typo on line 1 is reported on line 2); some messages carry a doubled prefix `engine: engine:` (dataset create, refresh, metric and column validation). *Steps:* `SELECT ... FROM tpch.tiny.ordres` shows `line 2:35`. `POST /datasets` with bad SQL returns `engine: engine: line 2:14`. | +| KI-086 | SQL Lab | UX | S3 | Errors found before the query starts (bad parameter, forbidden) appear as a toast with raw JSON: `Error: {"error":"invalid input: parameter 'n' is not a number: abc"}`. *Steps:* Parameter type **num**, value `abc`, **Run**. *Status:* fixed in platform #721 (October 2026): an error raised before the query starts is shown in the Results panel under **Query not started**, with the server's message only; no toast, no JSON. | +| KI-087 | SQL Lab | UX | S3 | Engine error positions are one line too high for wrapped queries (a typo on line 1 is reported on line 2); some messages carry a doubled prefix `engine: engine:` (dataset create, refresh, metric and column validation). *Steps:* `SELECT ... FROM tpch.tiny.ordres` shows `line 2:35`. `POST /datasets` with bad SQL returns `engine: engine: line 2:14`. *Status:* fixed in platform #721 (October 2026): positions match the user's SQL (`line 1:22` for a typo on line 1), including where a wrapper remains, and engine errors carry one `engine:` prefix. | | KI-088 | SQL Lab | Bug | S3 | Previewing a table from the Explorer submits the query twice (two identical History rows). Probably React StrictMode on the dev server; not checked in a production build. *Steps:* History after one **Preview**. *Status:* closed on the Docker demo stack (Oct 2026): not reproduced on the production frontend build the demo stack serves; one **Preview** of `tpch.tiny.region` added exactly one History row. Applies to the Vite dev server only, if at all. | | KI-089 | SQL Lab | UX | S3 | Download JSON and Copy export only the rows loaded in the grid (first 1,000, after filter), while Download CSV exports the full result. Nothing says so except the small "interactive over first 1,000 rows" note. *Steps:* Run a query returning 5,000 rows; Download JSON. | | KI-090 | SQL Lab | Gap | S3 | Autocomplete does not resolve table aliases (`o.` suggests tables) and only knows Explorer nodes that were expanded. *Steps:* Expand `orders`, type `SELECT orders.` (works) vs `FROM orders o ... o.` (does not). | @@ -154,31 +154,31 @@ Collected while writing the detailed pages for each area (October 2026), checked | KI-092 | SQL Lab | UX | S3 | History cannot be cleared and is capped at 50 per user, so test runs push useful entries out. *Steps:* History tab. | | KI-093 | SQL Lab | UX | S3 | Saved-query folders can only be created from a row's "Move to new folder…"; there is no New folder button as on the Datasets list. *Steps:* Saved tab. | | KI-094 | SQL Lab | UX | S3 | The tab rename box does not select the existing text, so typing appends to it. *Steps:* Double-click a tab title and type. | -| KI-095 | SQL Lab | Bug | S2 | **Save as dataset** fails for SQL ending in a semicolon (works in SQL Lab) and returns HTTP 500 "internal error" for duplicate column names. *Steps:* `select 1 as x;`; `select 1 as x, 2 as x`. | -| KI-096 | Datasets | Bug | S2 | The **Dimension** and **Filter** switches are stored but not read by the chart builder or the backend: a column with Dimension off still appears in the X-axis picker. *Steps:* Dataset "Orders with customers": `orderkey` has Dimension off and is offered as X-axis in a new Bar chart. | +| KI-095 | SQL Lab | Bug | S2 | **Save as dataset** fails for SQL ending in a semicolon (works in SQL Lab) and returns HTTP 500 "internal error" for duplicate column names. *Steps:* `select 1 as x;`; `select 1 as x, 2 as x`. *Status:* fixed in platform #721 (October 2026): a trailing `;` is dropped from the stored SQL, and repeated column names are a 400 that names the column (*the query returns more than one column named `x`; give each column its own name with AS*). Bad SQL still answers 502: KI-247. | +| KI-096 | Datasets | Bug | S2 | The **Dimension** and **Filter** switches are stored but not read by the chart builder or the backend: a column with Dimension off still appears in the X-axis picker. *Steps:* Dataset "Orders with customers": `orderkey` has Dimension off and is offered as X-axis in a new Bar chart. *Status:* fixed in platform #725 (October 2026): the chart builder's field pickers offer only columns with **Dimension** on, plus any column the chart already uses; a raw-records table and Object detail keep every column. A dashboard filter maps only onto columns with **Filter** on (an existing mapping stays). The filter-mapping half was unit-tested, not checked in a browser. The server does not refuse such charts, so saved charts keep working. | | KI-097 | Datasets | Gap | S3 | The roles **Category** and **Country** have no consumer in the frontend (only Currency, Email, URL, Image, Latitude, Longitude do). *Steps:* Source search for `semantic_type`. | -| KI-098 | Datasets | Bug | S2 | Deleting a dataset (header trash button or **Delete permanently**) also deletes every chart built on it. The confirmation says only that the table is not affected. *Steps:* Create a dataset and a chart on it, delete the dataset: the chart returns 404. | -| KI-099 | Datasets | Bug | S2 | Renaming a metric that charts use breaks those charts (`unknown metric: old_name`); deleting a used metric is allowed without warning. *Steps:* Edit a metric's name; run a chart that uses it. | -| KI-100 | Datasets | Bug | S3 | A metric whose expression is not an aggregate (for example `totalprice`) is accepted and only fails inside a chart: `'totalprice' must be an aggregate expression or appear in GROUP BY clause`. *Steps:* Add metric `plain` = `totalprice`. | -| KI-101 | Datasets | Bug | S3 | `POST /datasets/{id}/columns` accepts an empty column name (the UI blocks it). I could not delete the resulting column through the API (empty path segment). *Steps:* API call with `column_name: ""`. | -| KI-102 | Datasets | UX | S3 | **Refresh** silently resets the **Time** switch of every column to what the engine type says. *Steps:* Turn Time on for a varchar column, **Refresh**. | -| KI-103 | Datasets | UX | S3 | Table chart headers and the Preview tab show the column name, not the **Display name**, while the chart editor field lists show the Display name. *Steps:* "Orders by size band" chart: header `order_size`, Display name "Order size". | +| KI-098 | Datasets | Bug | S2 | Deleting a dataset (header trash button or **Delete permanently**) also deletes every chart built on it. The confirmation says only that the table is not affected. *Steps:* Create a dataset and a chart on it, delete the dataset: the chart returns 404. *Status:* fixed in platform #725 (October 2026): deleting a dataset that any chart (live or in the Trash) is built on is refused with 409 and the list of charts; the dialog lists them, says how many, and deletes the dataset and those charts only after you type the dataset's name. | +| KI-099 | Datasets | Bug | S2 | Renaming a metric that charts use breaks those charts (`unknown metric: old_name`); deleting a used metric is allowed without warning. *Steps:* Edit a metric's name; run a chart that uses it. *Status:* fixed in platform #725 (October 2026): renaming a metric rewrites every chart and dashboard card that names it in the same transaction; a rename from or to a name that is also a column name is refused (409). Deleting a metric that charts use is refused with the list of charts and goes ahead only after you type the metric's name. | +| KI-100 | Datasets | Bug | S3 | A metric whose expression is not an aggregate (for example `totalprice`) is accepted and only fails inside a chart: `'totalprice' must be an aggregate expression or appear in GROUP BY clause`. *Steps:* Add metric `plain` = `totalprice`. *Status:* fixed in platform #725 (October 2026): saving a metric whose expression is not an aggregate is refused with 400 *metric expression must be an aggregate, such as SUM(column) or COUNT(*): …*. | +| KI-101 | Datasets | Bug | S3 | `POST /datasets/{id}/columns` accepts an empty column name (the UI blocks it). I could not delete the resulting column through the API (empty path segment). *Steps:* API call with `column_name: ""`. *Status:* partly fixed in platform #725 (October 2026): an empty or blank column name is now refused with 400. A calculated column already saved with an empty name still cannot be deleted through the API (the delete URL needs the name). | +| KI-102 | Datasets | UX | S3 | **Refresh** silently resets the **Time** switch of every column to what the engine type says. *Steps:* Turn Time on for a varchar column, **Refresh**. *Status:* fixed in platform #725 (October 2026): **Refresh** keeps the **Time** switch of a column whose engine type did not change (a changed type takes the engine's answer), and the toast names the columns added and removed (*Refreshed: no columns added or removed*). | +| KI-103 | Datasets | UX | S3 | Table chart headers and the Preview tab show the column name, not the **Display name**, while the chart editor field lists show the Display name. *Steps:* "Orders by size band" chart: header `order_size`, Display name "Order size". *Status:* fixed in platform #725 (October 2026): the chart page's table and the Preview tab head a column with its **Display name**, unless the chart sets its own column title. A dashboard card's table still shows the column name: KI-251. | | KI-104 | Datasets | Gap | S3 | Calculated columns and metrics cannot reference calculated columns (the check probes the source columns only), and the dialog does not say so. *Steps:* Formula `net_price * 2` fails with `Column 'net_price' cannot be resolved`. | -| KI-105 | Datasets | UX | S3 | **Delete** on a calculated column and on a metric acts with no confirmation. *Steps:* Columns and Metrics tabs. | +| KI-105 | Datasets | UX | S3 | **Delete** on a calculated column and on a metric acts with no confirmation. *Steps:* Columns and Metrics tabs. *Status:* fixed in platform #725 (October 2026): deleting a calculated column or a metric asks for confirmation first. | | KI-106 | Datasets | Gap | S3 | **Preview** omits calculated columns. *Steps:* Preview of "Orders with customers" has 8 columns; the dataset has 10. | | KI-107 | Datasets | Gap | S3 | Metric certification details cannot be entered in the UI (the switch reuses the stored details). *Steps:* Metrics tab, Certified switch. | | KI-108 | Datasets | Gap | S3 | **Usage** lists charts only, not the dashboards that contain them. *Steps:* Usage tab. | | KI-109 | Datasets | Docs | S3 | OpenAPI says the update/refresh/delete endpoints return 403 "or not the dataset owner", but the handlers only check `dataset:manage` on the object. *Steps:* `crates/egret-bi/src/routes.rs`, `ds_remove`. | -| KI-110 | Datasets | UX | S3 | **Duplicate** is only offered for virtual datasets; there is no way to copy a physical dataset's column settings and metrics. *Steps:* Row menu. | -| KI-111 | Datasets | UX | S3 | Trashing a dataset hides it but its charts keep running; nothing tells the owner of those charts. *Steps:* Trash a dataset with a chart; the chart still returns rows. | -| KI-154 | SQL Lab | Bug | S2 | `ORDER BY` is ignored on Trino: Egret wraps a single SELECT as `SELECT * FROM () AS _egret_lim LIMIT n`, and Trino drops the ordering of a subquery, so rows come back in a different, unsorted order on each run. Hits the first-dashboard tutorial query. *Workaround:* add your own `LIMIT` after `ORDER BY` (then the order holds). *Fix idea:* append the limit instead of wrapping, or carry the `ORDER BY` outside. *Steps:* `SELECT orderstatus, count(*) AS orders, round(sum(totalprice), 2) AS revenue FROM tpch.tiny.orders GROUP BY 1 ORDER BY 3 DESC` five times: P,F,O / F,P,O / P,O,F; with `LIMIT 1000` appended, F,O,P every time. `system.runtime.queries` shows the wrapped text. (Reported by the start and sql-charts areas.) | -| KI-158 | Datasets | UX | S3 | The dataset header says "1 metrics" (no singular). *Steps:* dataset "Order priority summary" after adding one metric. | +| KI-110 | Datasets | UX | S3 | **Duplicate** is only offered for virtual datasets; there is no way to copy a physical dataset's column settings and metrics. *Steps:* Row menu. *Status:* fixed in platform #725 (October 2026): **Duplicate** is offered for physical and virtual datasets and copies the columns' settings and the metrics (not certification, shares or tags), then opens the copy. It needs the same rights as creating a dataset on that connection. | +| KI-111 | Datasets | UX | S3 | Trashing a dataset hides it but its charts keep running; nothing tells the owner of those charts. *Steps:* Trash a dataset with a chart; the chart still returns rows. *Status:* fixed in platform #725 (October 2026): **Move to Trash** first says how many charts use the dataset and that they keep working until it is deleted, and a chart whose dataset is in the Trash shows a notice. Bulk **Move to Trash** on the list still says nothing about charts: KI-252. | +| KI-154 | SQL Lab | Bug | S2 | `ORDER BY` is ignored on Trino: Egret wraps a single SELECT as `SELECT * FROM () AS _egret_lim LIMIT n`, and Trino drops the ordering of a subquery, so rows come back in a different, unsorted order on each run. Hits the first-dashboard tutorial query. *Workaround:* add your own `LIMIT` after `ORDER BY` (then the order holds). *Fix idea:* append the limit instead of wrapping, or carry the `ORDER BY` outside. *Steps:* `SELECT orderstatus, count(*) AS orders, round(sum(totalprice), 2) AS revenue FROM tpch.tiny.orders GROUP BY 1 ORDER BY 3 DESC` five times: P,F,O / F,P,O / P,O,F; with `LIMIT 1000` appended, F,O,P every time. `system.runtime.queries` shows the wrapped text. (Reported by the start and sql-charts areas.) *Status:* fixed in platform #721 (October 2026): the row limit is appended after the query's `ORDER BY` instead of wrapping it, so the order holds on every run and the workaround is no longer needed. See KI-081. | +| KI-158 | Datasets | UX | S3 | The dataset header says "1 metrics" (no singular). *Steps:* dataset "Order priority summary" after adding one metric. *Status:* fixed in platform #725 (October 2026): the header says *1 column* and *1 metric*. | | KI-159 | SQL Lab / Workspace settings | Bug | S1 | Only workspace admins can list a workspace's connections. Members with `ws_editor` (direct or through a group), `ws_viewer` or a custom role with `query:run` get 403 on `GET /api/ws/{ws}/databases` (`registry/routes.rs:109`), so their SQL Lab database picker is empty ("Select a database"), also for a provisioned engine assigned to the workspace, and nothing tells them why. Queries sent through the API with a `database_id` do run. As a result a delegated (per-user) query by a non-admin through the UI is still unproven. *Workaround:* use the API with `database_id`, or make the user a workspace admin. *Steps:* `bao.tran` (ws_editor) or `member-sso` in Docs sandbox → SQL Lab → Select a database: no options, console 403; ws_admin `alice.nguyen` sees two. (Reported by the start and admin areas.) *Status:* fixed in platform #712 (October 2026): a member holding `query:run` or `dataset:read` gets the workspace's granted connections from `GET /api/ws/{ws}/databases`, cut down to id, name, engine and delegated; admins keep the full rows, everyone else gets 403, and create, edit, delete, detail and test stay admin only. SQL Lab says *No connection is granted to this workspace. Ask a workspace admin.* for an empty list. Follow-ups: KI-243, KI-244, KI-245. | | KI-160 | SQL Lab | UX | S3 | Explorer search lists same-named tables from different schemas in no fixed order (`tpch.sf1000`, `tpch.sf300`, `tpch.sf100000`, `tpch.sf1`, …, `tpch.tiny`), so finding the `tiny` copy means reading every row. Sort by catalog.schema after the name. *Steps:* Explorer → Search tables… → `customer`. | | KI-161 | SQL Lab | UX | S3 | Singular is not handled: the status badge and counter read *Done in 1.1 s · 1 rows* and *1 rows*. *Steps:* `SELECT count(*) FROM tpch.tiny.orders`, or Explain query. | | KI-162 | SQL Lab | UX | S3 | The quick **Chart** view of a result uses fractional ticks for counts (`1,833.3`, `3,666.5`, …) and rotates one-letter X labels by 45°. *Steps:* run the order-status query of KI-154, press **Chart**. | -| KI-166 | Datasets | UX | S3 | The **Usage** tab shows the bar-chart icon for every chart, whatever its type. *Steps:* dataset `orders` → Usage. | -| KI-211 | SQL Lab | UX | S3 | SQL Lab runs every query with `query_max_run_time = 60s`; a longer statement (for example the first CTAS on a fresh engine) fails with the bare error *timeout* and no hint about the limit. *Steps:* `SHOW SESSION LIKE 'query_max_run_time'` → 60s. | +| KI-166 | Datasets | UX | S3 | The **Usage** tab shows the bar-chart icon for every chart, whatever its type. *Steps:* dataset `orders` → Usage. *Status:* fixed in platform #725 (October 2026): the **Usage** tab shows each chart's own type icon. | +| KI-211 | SQL Lab | UX | S3 | SQL Lab runs every query with `query_max_run_time = 60s`; a longer statement (for example the first CTAS on a fresh engine) fails with the bare error *timeout* and no hint about the limit. *Steps:* `SHOW SESSION LIKE 'query_max_run_time'` → 60s. *Status:* fixed in platform #721 (October 2026): the error reads *timeout: the query ran longer than the 60 s limit for SQL Lab queries and was stopped. The limit is the server setting `engine.query_timeout_secs`.*, and Trino's own *exceeded maximum time limit* gets the same hint. | ### Workflows @@ -297,6 +297,19 @@ Collected while writing the detailed pages for each area (October 2026), checked | KI-244 | Alerts | UX | S3 | The alert schedule editor's hint for an empty connection list tells non-admins to add a connection, which only an admin can do. (Found while fixing KI-159.) | | KI-245 | Audit log | UX | S3 | Every connection listing by a non-admin writes a denied `workspace:manage_members` decision to the audit log, because the endpoint asks the admin question first. (Found while fixing KI-159.) | | KI-246 | Workflows | Limitation | S3 | The outputs and the rendered values of tasks that use a SECRET input are not masked (the same rule as for `secret()` values); only the inputs and the run logs are. (Design choice of #719.) | +| KI-247 | Datasets | UX | S3 | Creating a dataset whose SQL the engine rejects answers **502 Bad Gateway** (`engine: line 1:…`), although the fault is in the SQL, not the engine; a 400 would say so. Every `BiError::Engine` maps to 502. *Steps:* `POST /api/ws/default/datasets` with `SELECT * FROM tpch.tiny.ordres`. (Found while fixing KI-087.) | +| KI-248 | SQL Lab | UX | S3 | Each keystroke in the editor re-renders the whole results grid (the tab's SQL lives in the same component as the grid, which is not memoised), so typing lags with a 1,000-row result on screen. Since #721 no characters are lost (KI-006), but the editor is slower than with an empty result. (Found while fixing KI-006.) | +| KI-249 | SQL Lab | Limitation | S3 | Editor shortcuts that start with **⌘ K** / **Ctrl K** (Monaco's two-key chords) cannot be used in SQL Lab: the command palette takes that key before the editor sees it. This is the trade-off of the KI-079 fix. | +| KI-250 | Charts / Datasets | UX | S3 | A chart whose dataset is in the Trash opens with an empty **Dataset** picker, because the picker lists live datasets only. The chart itself still renders and shows the Trash notice. *Steps:* Move a dataset with a chart to Trash, open the chart. (Found while fixing KI-111.) | +| KI-251 | Dashboards | UX | S3 | A dashboard card's table shows column names, not the dataset's **Display names**, and dataset column formats do not apply there either: the dashboard does not load each card's dataset. The chart page and Preview use display names since #725 (KI-103). (Found while fixing KI-103.) | +| KI-252 | Datasets | UX | S3 | Bulk **Move n to Trash** on the datasets list trashes the selection with a plain *Moved to Trash* toast and none of the single-dataset dialog's notice about the charts that use each dataset. (Found while fixing KI-111.) | +| KI-253 | Data API | Bug | S3 | A draft can be saved with a method and path that another endpoint is serving live: the draft update checks only the drafts' own unique key. Only the publish is refused (409), so the conflict shows up late. (Found while fixing KI-023.) | +| KI-254 | Data API | UX | S3 | The Query tab's parameter pane is capped at 40% of the window height (`max-h-[40vh]`), so with the constraint labels added for KI-026 a few parameters already make it scroll. (Found while fixing KI-026.) | +| KI-255 | Data API | UX | S3 | On Config, the note *The new path goes live when you publish. Callers still use GET /x.* sits in a value column 384 px wide (208 px below 1024 px), so it wraps over several lines and the method and the path of the live URL can break apart. (Found while fixing KI-023; read from the code, not measured.) | +| KI-256 | Charts / Scatter | UX | S3 | The metric-vs-metric scatter draws its Y-axis title at the top end of the axis, about 1 px from the top edge, so it is clipped; the other cartesian charts centre the Y title beside the axis. Since #724 the title has the axis-label colour (KI-163), so the clipping is now visible. (Found while fixing KI-163; read from the code, not measured.) | +| KI-257 | Charts / Box plot | UX | S3 | With **Values to display** = **All**, the value labels of a box can overlap: overlap hiding is switched off on purpose for that mode. (Found while fixing KI-165.) | +| KI-258 | Charts / Funnel | Gap | S3 | The default (trapezoid) funnel is not offered `.png`: it is not drawn as one chart image. Only **Bar — a column per step** can be downloaded as `.png`. Before #724 the trapezoid funnel was offered `.png`, but the file held only the first band. (Found while fixing KI-043.) | +| KI-259 | Charts / Map | UX | S3 | When rows exist but none matches a region, the region map still draws its colour legend, running from 0 to 1, next to an unshaded map and the *Showing 0 of N rows…* note. An empty result shows *No data* and no legend. (Found while fixing KI-046.) | ## Not verified