Skip to content

Commit e7dda2c

Browse files
fix(plugin-dashboard): a viewer whose every tile is refused sees one no-access panel, not a blank canvas (#27777)
domain:ui#2 · os-bill · session_01Utp6CY2n1JmZZKrHgtp9jC <!-- claude-projects-attribution: {"github_login":"os-bill"} --> _Requested by **Bill** · [project thread](https://claude.ai/code/project/chan_016iMXWSZDzqWRWvWzdNo8wg?thread=cmsg_016iMXWSZDzqWRWvWzdNo8wgP6XkfpjCfyzzG2KwQPUZfy)_ Fixes #27491 Clause-②: no Metadata-protocol: none ## 维护者速读 - 改了什么:观看者在仪表盘上的每个卡片都被服务器以读权限拒绝(403 / PERMISSION_DENIED)时,画布里显示一个「无权访问」面板(锁图标 + 标题 + 一句说明),不再是标题下一片空白;部分卡片被拒、设计模式、非权限错误的表现都不变。 - 为什么改:CRM 的 Pipeline 仪表盘六个卡片共用一个数据集,普通成员打开只看到标题和「Refresh All」;维护者裁决 A(6508997538):保留逐卡隐藏,什么都不剩时说一次。 - 风险与回滚:只动渲染层和十个语言包,不动 spec、服务端、DatasetWidget、DashboardView;403 仍是唯一判据。回滚即 revert 本 PR。 - 先看:`ui/packages/plugin-dashboard/src/DashboardRenderer.tsx`(`nothingLeftForViewer` 与 `allTilesRefusedPanel`,桌面和移动两处挂载)→ 新测试 `DashboardRenderer.allTilesRefused-27491.test.tsx` → `ui/packages/i18n/src/locales/en.ts` 新键。 - 两分钟验证:`pnpm --dir ui exec vitest run packages/plugin-dashboard/src/__tests__/DashboardRenderer.allTilesRefused-27491.test.tsx`,预期 5/5 通过。 - 需要你做的:无,席位落地。 ## What changes Outside design mode, a dataset tile whose query the server refuses as a read refusal is left out of the dashboard for that viewer (the rule the spec states in the dashboard widget docblock). When that leaves nothing to draw, the viewer saw the dashboard header over an empty canvas, with no message. Ruling-ref: 6508997538 (maintainer ruling A). - `DashboardRenderer` renders its tiles once per layout (`desktopTiles`; `metricTiles` and `otherTiles` in the mobile stack) and asks `nothingLeftForViewer(tiles)`: not design mode, at least one refused tile, at least one widget, and every rendered tile is `null`. The only `null` a tile renders is the refusal drop, so the trigger is exactly "every widget was left out as refused". A dashboard that still has any drawable widget (a non-dataset widget, a readable tile, a tile that failed for another reason) draws it and no panel. - The panel is the `Empty` composition the console draws for a dashboard the viewer may not open (`dashboard-access-denied` in `DashboardView`): lock icon, title `dashboard.widgetForbiddenTitle`, and one new message `dashboard.allWidgetsForbiddenMessage`. It carries `data-testid="dashboard-all-widgets-forbidden"` and spans the full grid (`col-span-full`). The header, its description and the refresh control are untouched. - `@object-ui/i18n`: `dashboard.allWidgetsForbiddenMessage` in all ten locales, beside `widgetForbiddenMessage`. en: "You don’t have permission to view the data behind this dashboard." (the typographic apostrophe the neighbouring `dashboard.*` strings use). ## Tests New `ui/packages/plugin-dashboard/src/__tests__/DashboardRenderer.allTilesRefused-27491.test.tsx` (5 tests), with the refusal shape `AnalyticsForbiddenError` carries (`httpStatus: 403`, `code: 'PERMISSION_DENIED'`): - every tile refused: the panel with both strings, both tiles queried, no tile title, no per-tile no-access face; - the same in the mobile layout (non-vacuity: the 2-column metric grid is drawn); - partial refusal: no panel, the readable tile present, the refused ones gone; - CONTROL design mode, every tile refused: no panel, each tile's objectui#10899 face present; - CONTROL a 400 on every tile: no panel, every tile and its error alert present. `DashboardRenderer.refusedTile-26971.test.tsx` and `DatasetWidget.forbiddenState-10899.test.tsx` pass unchanged. <details><summary>Readings</summary> - Premise (before the ruling, report 6508422494): the real `examples/app-crm` `pipeline_dashboard` through the real `ObjectStackAdapter` with fetch answering 403 `PERMISSION_DENIED` rendered an empty canvas; ablating the refusal drop brought the six frames back. Scratch test, never committed. - Ablation of this change, through `scripts/ablation-replace.mjs` on commit 76be307: the trigger's `tiles.length > 0 && tiles.every((tile) => tile == null)` replaced by `false` (anchor 1 → 0, blob c9adcbb → eb6c069cbd78): the two all-refused tests red, the three others green (2 failed / 3 passed); restored, blob == HEAD c9adcbb, `git diff HEAD` empty. The test imports the renderer by relative path, so no dist leg. - After merging `main` (head 24bc0b9), closure `pnpm --filter '@object-ui/plugin-dashboard^...' build` VERDICT command-exit 0; `pnpm --dir ui exec vitest run --maxWorkers=2 packages/plugin-dashboard/ packages/i18n/`: Test Files 286 passed (286), Tests 3266 passed, 19 skipped; lock VERDICT command-exit 0. - Gates: see the os-dev-report on #27491. </details> ## Acceptance notes - The trigger is "nothing left to draw", not "every dataset tile refused", per the ruling: a dashboard that keeps any other widget draws no panel. - A refused tile is still not re-queried until remount; "Refresh All" stays as #27065 left it (ruling item 2). - Option C of the ruling (the CRM app declaring `requiredPermissions` on `pipeline_dashboard` and its nav item) is a separate app-lane card, not this PR. ## Verification `pnpm --dir ui exec vitest run packages/plugin-dashboard/src/__tests__/DashboardRenderer.allTilesRefused-27491.test.tsx packages/plugin-dashboard/src/__tests__/DashboardRenderer.refusedTile-26971.test.tsx packages/plugin-dashboard/src/__tests__/DatasetWidget.forbiddenState-10899.test.tsx` → 3 files, 11 tests passed. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01Utp6CXTsLkvW2GpvDwNQre --- _Generated by [Claude Code](https://claude.ai/code/session_01Utp6CXTsLkvW2GpvDwNQre)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 946395d commit e7dda2c

13 files changed

Lines changed: 227 additions & 5 deletions

File tree

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
'@object-ui/plugin-dashboard': patch
3+
'@object-ui/i18n': patch
4+
---
5+
6+
fix(plugin-dashboard): a viewer whose every dashboard tile is refused sees one no-access panel, not an empty canvas
7+
8+
Clause-②: no
9+
Metadata-protocol: none
10+
11+
A tile whose data the viewer may not read is left out of the dashboard outside design mode. When every tile was left out, the viewer saw the dashboard's header over an empty canvas, with no message. `DashboardRenderer` now draws one no-access panel in the canvas in that case (lock icon, "You don’t have access", "You don’t have permission to view the data behind this dashboard."), the same face the console shows for a dashboard the viewer may not open. The header, its description and the refresh control stay as they are.
12+
13+
- A partial refusal is unchanged: the refused tiles leave, the rest reflow, no message.
14+
- In design mode every tile stays and draws its own no-access state; no panel.
15+
- A failure that is not a read refusal keeps every tile and its error; no panel.
16+
- A dashboard with no widgets draws no panel.
17+
- `@object-ui/i18n` adds `dashboard.allWidgetsForbiddenMessage` in all ten locales.

‎ui/packages/i18n/src/locales/ar.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1612,6 +1612,7 @@ const ar = {
16121612
datasetUnsupported: "مصدر البيانات هذا لا يدعم استعلامات مجموعات البيانات.",
16131613
widgetForbiddenTitle: "لا تملك صلاحية الوصول",
16141614
widgetForbiddenMessage: "ليست لديك صلاحية لعرض البيانات الخاصة بهذه الأداة.",
1615+
allWidgetsForbiddenMessage: "ليست لديك صلاحية لعرض البيانات الخاصة بلوحة التحكم هذه.",
16151616
details: "التفاصيل",
16161617
exportCsv: "تصدير CSV",
16171618
openInList: "فتح في القائمة",

‎ui/packages/i18n/src/locales/de.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1421,6 +1421,7 @@ const de = {
14211421
datasetUnsupported: "Diese Datenquelle unterstützt keine Dataset-Abfragen.",
14221422
widgetForbiddenTitle: "Kein Zugriff",
14231423
widgetForbiddenMessage: "Sie haben keine Berechtigung, die Daten dieses Widgets anzuzeigen.",
1424+
allWidgetsForbiddenMessage: "Sie haben keine Berechtigung, die Daten dieses Dashboards anzuzeigen.",
14241425
details: "Details",
14251426
exportCsv: "CSV exportieren",
14261427
openInList: "In Liste öffnen",

‎ui/packages/i18n/src/locales/en.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1806,6 +1806,7 @@ const en = {
18061806
datasetUnsupported: 'This data source does not support dataset queries.',
18071807
widgetForbiddenTitle: 'You don’t have access',
18081808
widgetForbiddenMessage: 'You don’t have permission to view the data behind this widget.',
1809+
allWidgetsForbiddenMessage: 'You don’t have permission to view the data behind this dashboard.',
18091810
details: 'Details',
18101811
exportCsv: 'Export CSV',
18111812
openInList: 'Open in list',

‎ui/packages/i18n/src/locales/es.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1471,6 +1471,7 @@ const es = {
14711471
datasetUnsupported: "Esta fuente de datos no admite consultas de dataset.",
14721472
widgetForbiddenTitle: "Sin acceso",
14731473
widgetForbiddenMessage: "No tienes permiso para ver los datos de este widget.",
1474+
allWidgetsForbiddenMessage: "No tienes permiso para ver los datos de este panel.",
14741475
details: "Detalles",
14751476
exportCsv: "Exportar CSV",
14761477
openInList: "Abrir en la lista",

‎ui/packages/i18n/src/locales/fr.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1467,6 +1467,7 @@ const fr = {
14671467
datasetUnsupported: "Cette source de données ne prend pas en charge les requêtes de dataset.",
14681468
widgetForbiddenTitle: "Accès refusé",
14691469
widgetForbiddenMessage: "Vous n’avez pas l’autorisation de voir les données de ce widget.",
1470+
allWidgetsForbiddenMessage: "Vous n’avez pas l’autorisation de voir les données de ce tableau de bord.",
14701471
details: "Détails",
14711472
exportCsv: "Exporter en CSV",
14721473
openInList: "Ouvrir dans la liste",

‎ui/packages/i18n/src/locales/ja.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1421,6 +1421,7 @@ const ja = {
14211421
datasetUnsupported: "このデータソースはデータセットクエリに対応していません。",
14221422
widgetForbiddenTitle: "アクセス権がありません",
14231423
widgetForbiddenMessage: "このウィジェットのデータを表示する権限がありません。",
1424+
allWidgetsForbiddenMessage: "このダッシュボードのデータを表示する権限がありません。",
14241425
details: "詳細",
14251426
exportCsv: "CSV で書き出す",
14261427
openInList: "リストで開く",

‎ui/packages/i18n/src/locales/ko.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1421,6 +1421,7 @@ const ko = {
14211421
datasetUnsupported: "이 데이터 소스는 데이터셋 쿼리를 지원하지 않습니다.",
14221422
widgetForbiddenTitle: "접근 권한 없음",
14231423
widgetForbiddenMessage: "이 위젯의 데이터를 볼 수 있는 권한이 없습니다.",
1424+
allWidgetsForbiddenMessage: "이 대시보드의 데이터를 볼 수 있는 권한이 없습니다.",
14241425
details: "세부 정보",
14251426
exportCsv: "CSV 내보내기",
14261427
openInList: "목록에서 열기",

‎ui/packages/i18n/src/locales/pt.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1466,6 +1466,7 @@ const pt = {
14661466
datasetUnsupported: "Esta fonte de dados não oferece suporte a consultas de dataset.",
14671467
widgetForbiddenTitle: "Sem acesso",
14681468
widgetForbiddenMessage: "Você não tem permissão para ver os dados deste widget.",
1469+
allWidgetsForbiddenMessage: "Você não tem permissão para ver os dados deste painel.",
14691470
details: "Detalhes",
14701471
exportCsv: "Exportar CSV",
14711472
openInList: "Abrir na lista",

‎ui/packages/i18n/src/locales/ru.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1521,6 +1521,7 @@ const ru = {
15211521
datasetUnsupported: "Этот источник данных не поддерживает запросы к наборам данных.",
15221522
widgetForbiddenTitle: "Нет доступа",
15231523
widgetForbiddenMessage: "У вас нет прав на просмотр данных этого виджета.",
1524+
allWidgetsForbiddenMessage: "У вас нет прав на просмотр данных этой панели.",
15241525
details: "Подробности",
15251526
exportCsv: "Экспорт в CSV",
15261527
openInList: "Открыть в списке",

0 commit comments

Comments
 (0)