custom dashboards - #228
Conversation
…payload Adds custom-dashboard fields to the dashboard DTO/model, wires tag-based filtering through the search store, and adds the supporting migration.
Heavier Geist-style font weights with negative heading tracking, layered low-opacity shadows, roomier panel padding/header height, white light-mode panel surfaces, and a 12px panel radius with a hover elevation transition.
Adopts @heroicons/react, the icon set used by coderabbit-ui, for the panel info icon so it matches the rest of the CodeRabbit design system.
Rounds the popover, converts quick ranges to inset pills, restyles section headers as small-caps eyebrows and recesses the footer. Also fixes the boxed outline on the active tab (caused by Tab's overflow clipping its rounded underline) and keeps select menus unclipped by dropping overflow:hidden.
📝 WalkthroughWalkthroughThe changes refresh theme colors, shadows, typography, and panel dimensions. Time range picker and panel chrome styles now use updated theme tokens. Dashboard metadata, persistence, search, and database filtering support optional workspace identifiers. The dashboard micro-frontend HTML references refreshed assets and bundles. Poem
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
✨ Simplify code
Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/grafana-data/src/themes/createColors.ts`:
- Around line 195-197: Update the light-theme border values in createColors so
colors.border.medium, and any border.weak/strong values used for controls or
panels, meet the required contrast against colors.background.primary (`#ffffff`).
Use contrast-tested colors or separate decorative weak-divider values from
control and panel border tokens while preserving decorative divider styling.
In `@pkg/api/dashboard.go`:
- Line 178: Update GetDashboard to validate that the requested dashboard’s
WorkspaceID matches the caller’s CodeRabbit workspace before returning it.
Reject workspace mismatches before the existing Grafana dashboard permission
checks or successful response, while preserving the current behavior for
matching workspaces.
In `@pkg/services/dashboards/service/dashboard_service.go`:
- Around line 219-229: Update PostDashboard and the SaveDashboardCommand
construction to authorize dto.WorkspaceID before persisting it: resolve the
workspace from the authenticated user’s trusted identity or validate that the
user is a member of the requested workspace, and reject unauthorized requests
before saving. Do not persist the request-supplied WorkspaceID without this
authorization check.
In `@public/microfrontends/fn_dashboard/index.html`:
- Around line 20-22: Update both dashboard bundle script tags in the document to
restore build-generated subresource integrity values and add
crossorigin="anonymous" attributes. Regenerate or reuse the correct integrity
hashes for runtime~fn_dashboard and fn_dashboard, preserving their existing
sources and script behavior.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Central YAML (base), Organization UI (inherited)
Review profile: CHILL
Plan: Enterprise
Run ID: 4605e5bc-1ead-4577-a2fc-d5235ca2427c
⛔ Files ignored due to path filters (2)
packages/grafana-ui/package.jsonis excluded by!**/*.jsonyarn.lockis excluded by!**/yarn.lock,!**/*.lock,!**/*.lock
📒 Files selected for processing (22)
packages/grafana-data/src/themes/createColors.tspackages/grafana-data/src/themes/createComponents.tspackages/grafana-data/src/themes/createShadows.tspackages/grafana-data/src/themes/createTypography.tspackages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimePickerContent.tsxpackages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimePickerFooter.tsxpackages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimePickerTitle.tsxpackages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimeRangeList.tsxpackages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimeRangeOption.tsxpackages/grafana-ui/src/components/PanelChrome/PanelChrome.tsxpackages/grafana-ui/src/components/PanelChrome/PanelDescription.tsxpkg/api/dashboard.gopkg/api/dtos/dashboard.gopkg/api/search.gopkg/services/dashboards/database/database.gopkg/services/dashboards/models.gopkg/services/dashboards/service/dashboard_service.gopkg/services/search/service.gopkg/services/sqlstore/migrations/dashboard_mig.gopkg/services/sqlstore/searchstore/filters.gopkg/services/sqlstore/searchstore/filters_test.gopublic/microfrontends/fn_dashboard/index.html
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
coderabbitai/bitbucket(manual)
📜 Review details
⏰ Context from checks skipped due to timeout. (1)
- GitHub Check: build-and-test
🧰 Additional context used
📓 Path-based instructions (1)
**/*.ts
⚙️ CodeRabbit configuration file
**/*.ts: - Do not allow use ofeslint-disable,@ts-expect-error, or@ts-ignoreunless there's a clear, inline comment explaining why it's necessary.
- Suggest early returns in place of nested
if,elseor loops with complex branching.- Flag function-wide scopes created by
try/catchor top-levelif/else. Recommend moving the inner logic to its own function.- Flag use of
try/catchfor control flow. Recommend using.catch()with appropriate error handling.- Flag
try/catchthat introduces aletwhere.catch()withconstcould be used instead.- Flag
catchblocks that narrow the caughterrortoError. Suggest typing thecatchparameter asunknown.- Flag cases where types are narrowed manually before passing a value to the logger. Suggest passing the value directly without narrowing.
- Flag logging expressions that extract
error.messageor convert the error to a string. Suggest logging the full error value instead.- Flag variables created from
error.messageorString(error)that are then logged. Suggest logging the originalerrorvalue directly.- When
letis used to accumulate a value through conditions, suggest replacing it with a function that returns the final value directly.- Flag
letfollowed by a single conditional reassignment. Suggest aconstwith a ternary or a helper that returns the final value.- Flag Map get-or-create patterns that assign to a
let(e.g.let x = map.get(k); if (!x) { x = new ...; map.set(k, x) }). Suggest agetOrCreate*helper that returns the value and keeps the variableconst.- Flag nested conditions that can be combined into a single guard. Suggest simplifying with an early return.
- When encountering side effects such as mutation in
forEach, suggest replacing withmap,filterorreduce.- Recommend introducing intermediate variables when string interpolation contains non-trivial logic.
- Ban all
astype assertions everywhere, including chains like ...
Files:
packages/grafana-data/src/themes/createComponents.tspackages/grafana-data/src/themes/createTypography.tspackages/grafana-data/src/themes/createColors.tspackages/grafana-data/src/themes/createShadows.ts
🔇 Additional comments (16)
public/microfrontends/fn_dashboard/index.html (1)
1-17: LGTM!Also applies to: 24-25
pkg/services/sqlstore/migrations/dashboard_mig.go (1)
242-253: LGTM!pkg/services/sqlstore/searchstore/filters.go (1)
222-234: LGTM!pkg/services/sqlstore/searchstore/filters_test.go (1)
61-68: LGTM!pkg/api/search.go (1)
99-101: 🔒 Security & PrivacyReachability path
● Entry pkg/services/search/service.go:68 SearchHandler: No starred dashboards will be found │ ▼ ● Sink pkg/api/search.goVerify workspace authorization for
workspaceId.This path passes
c.Query("workspaceId")toWorkspaceFilterwithout a workspace-membership check. If an upstream MFE does not authorize the identifier, a same-org user can enumerate dashboards from another workspace. Enforce authorization at a trusted boundary or derive the workspace from trusted identity.packages/grafana-data/src/themes/createShadows.ts (1)
15-28: LGTM!packages/grafana-data/src/themes/createTypography.ts (1)
70-75: LGTM!Also applies to: 116-125
packages/grafana-data/src/themes/createComponents.ts (1)
59-63: LGTM!packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimePickerContent.tsx (1)
165-165: LGTM!Also applies to: 200-200, 255-264, 292-293, 332-336
packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimePickerFooter.tsx (2)
232-247: 🎯 Functional CorrectnessVerify that active tabs keep a visible keyboard focus indicator.
The selector sets
border: noneandboxShadow: noneon every active tab. If the sharedTabcomponent uses either property for:focus-visible, the selected tab loses its focus indicator. Scope the reset to the non-focused state or reapply the focus style after this override. Add a keyboard-focus test for both tabs.
172-204: LGTM!packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimePickerTitle.tsx (1)
10-17: LGTM!packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimeRangeList.tsx (1)
4-4: LGTM!Also applies to: 85-90
packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimeRangeOption.tsx (1)
12-28: LGTM!Also applies to: 40-50
packages/grafana-ui/src/components/PanelChrome/PanelChrome.tsx (1)
298-302: LGTM!Also applies to: 394-411, 473-479, 495-503
packages/grafana-ui/src/components/PanelChrome/PanelDescription.tsx (1)
2-6: LGTM!Also applies to: 33-34, 52-64
| weak: '#ebe9ee', | ||
| medium: '#dcd9e1', | ||
| strong: '#c4c0cb', |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Keep light-theme control borders contrast-compliant.
createComponents.ts (Line 70-73) applies colors.border.medium to input borders while colors.background.primary is now #ffffff. The new #dcd9e1 border has only about 1.4:1 contrast against that background. border.weak and border.strong are also below 3:1. These values can make normal inputs and panel boundaries difficult to identify. Use contrast-tested control borders, or separate decorative weak dividers from control and panel borders.
Also applies to: 231-236
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/grafana-data/src/themes/createColors.ts` around lines 195 - 197,
Update the light-theme border values in createColors so colors.border.medium,
and any border.weak/strong values used for controls or panels, meet the required
contrast against colors.background.primary (`#ffffff`). Use contrast-tested colors
or separate decorative weak-divider values from control and panel border tokens
while preserving decorative divider styling.
| FolderTitle: "General", | ||
| AnnotationsPermissions: annotationPermissions, | ||
| PublicDashboardEnabled: publicDashboardEnabled, | ||
| WorkspaceID: dash.WorkspaceID, |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Find the dashboard read routes and any CodeRabbit MFE identity-to-workspace checks.
rg -n -C 5 'func \(.*\) GetDashboard|workspaceId|WorkspaceID|isCodeRabbitMFE|workspace.*member|member.*workspace' \
pkg/api pkg/services publicRepository: coderabbitai/grafana
Length of output: 50376
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- GetDashboard and helper ---'
sed -n '80,125p' pkg/api/dashboard.go
rg -n -C 8 'func \(hs \*HTTPServer\) getDashboardHelper|GetDashboardQuery\{.*UID|WorkspaceID' pkg/api pkg/services/dashboards pkg/middleware pkg/services/auth 2>/dev/null | head -n 240
printf '%s\n' '--- route and request identity clues ---'
rg -n -C 4 'GetDashboard|/api/dashboards/uid|workspaceId|WorkspaceID|workspace_id|workspace.*(header|claim|identity)|identity.*workspace' pkg/api pkg/services pkg/middleware public --glob '!*.sum' | head -n 320Repository: coderabbitai/grafana
Length of output: 39841
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- dashboard read query implementation ---'
sed -n '738,778p' pkg/services/dashboards/database/database.go
rg -n -C 12 'func \(hs \*HTTPServer\) getDashboardHelper' pkg/api
rg -n -C 8 'type GetDashboardQuery|type SaveDashboardCommand|WorkspaceID' pkg/services/dashboards/models.go pkg/api --glob '*.go' | head -n 260
printf '%s\n' '--- dashboard route registration ---'
rg -n -C 6 'GetDashboard' pkg/api pkg/server pkg/web --glob '*.go' | head -n 240Repository: coderabbitai/grafana
Length of output: 30168
Authorization Bypass (CWE-862): Missing Authorization
Reachability: External · Exploitability: Moderate
Reachability path
● Entry
pkg/services/dashboards/service/dashboard_service.go:647
FindDashboards
│
▼
● Hop
pkg/services/dashboards/database/database.go:738
GetDashboard
│
▼
● Sink
pkg/api/dashboard.go
Enforce workspace membership before returning a dashboard.
GetDashboard loads dashboards by UID and organization, then checks only Grafana dashboard permissions. It does not constrain access to the caller’s CodeRabbit workspace. Reject a workspace mismatch before returning the dashboard.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@pkg/api/dashboard.go` at line 178, Update GetDashboard to validate that the
requested dashboard’s WorkspaceID matches the caller’s CodeRabbit workspace
before returning it. Reject workspace mismatches before the existing Grafana
dashboard permission checks or successful response, while preserving the current
behavior for matching workspaces.
| cmd := &dashboards.SaveDashboardCommand{ | ||
| Dashboard: dash.Data, | ||
| Message: dto.Message, | ||
| OrgID: dto.OrgID, | ||
| Overwrite: dto.Overwrite, | ||
| UserID: userID, | ||
| FolderID: dash.FolderID, // nolint:staticcheck | ||
| FolderUID: dash.FolderUID, | ||
| IsFolder: dash.IsFolder, | ||
| PluginID: dash.PluginID, | ||
| Dashboard: dash.Data, | ||
| Message: dto.Message, | ||
| OrgID: dto.OrgID, | ||
| Overwrite: dto.Overwrite, | ||
| UserID: userID, | ||
| FolderID: dash.FolderID, // nolint:staticcheck | ||
| FolderUID: dash.FolderUID, | ||
| IsFolder: dash.IsFolder, | ||
| PluginID: dash.PluginID, | ||
| WorkspaceID: dash.WorkspaceID, |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Trace JSON binding for dashboard saves and locate workspace-membership authorization.
rg -n -C 6 'SaveDashboardCommand|SaveDashboardDTO|BuildSaveDashboardCommand|workspaceId|WorkspaceID|workspace.*member|member.*workspace' \
pkg/api pkg/servicesRepository: coderabbitai/grafana
Length of output: 50377
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- dashboard service save path ---'
sed -n '150,255p' pkg/services/dashboards/service/dashboard_service.go
printf '%s\n' '--- save command and dashboard DTO definitions ---'
rg -n -A35 -B8 'type SaveDashboardCommand|type SaveDashboardDTO|WorkspaceID' pkg/services/dashboards pkg/api/dtos
printf '%s\n' '--- HTTP save path ---'
sed -n '500,585p' pkg/api/dashboard.go
printf '%s\n' '--- workspace-related production code ---'
rg -n -g '*.go' -i 'workspace(id|_id)?|workspace.*(auth|member|owner)|member(ship)?.*workspace' pkg | head -200Repository: coderabbitai/grafana
Length of output: 50376
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
for p in [
Path("pkg/services/dashboards/service/dashboard_service.go"),
Path("pkg/api/dashboard.go"),
Path("pkg/services/dashboards/models.go"),
]:
text = p.read_text()
print(f"--- {p} ---")
for i, line in enumerate(text.splitlines(), 1):
if "buildSaveDashboardCommand" in line or "WorkspaceID" in line or "SaveDashboardCommand" in line:
lo, hi = max(1, i-12), min(len(text.splitlines()), i+25)
print("\n".join(f"{j:5}: {text.splitlines()[j-1]}" for j in range(lo, hi+1)))
print()
PY
printf '%s\n' '--- exact workspace symbols and routes ---'
rg -n -g '*.go' 'WorkspaceID|workspaceId|workspace_id|Workspace' pkg/api pkg/services | grep -v '_test.go' | head -250Repository: coderabbitai/grafana
Length of output: 33568
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
checks = {
"pkg/services/dashboards/models.go": [
'WorkspaceID string `json:"workspaceId" xorm:"workspace_id"`',
'dash.WorkspaceID = cmd.WorkspaceID',
],
"pkg/api/dashboard.go": [
'web.Bind(c.Req, &cmd)',
'dash := cmd.GetDashboardModel()',
],
"pkg/services/dashboards/service/dashboard_service.go": [
'WorkspaceID: dash.WorkspaceID',
],
}
for name, needles in checks.items():
text = Path(name).read_text()
print(f"--- {name} ---")
for needle in needles:
print(f"{needle!r}: {'present' if needle in text else 'MISSING'}")
print("--- save implementation references ---")
for p in Path("pkg/services").rglob("*.go"):
text = p.read_text(errors="replace")
if "SaveDashboard(ctx" in text or "WorkspaceID" in text:
for i, line in enumerate(text.splitlines(), 1):
if "SaveDashboard(ctx" in line or "WorkspaceID" in line:
print(f"{p}:{i}:{line.strip()}")
PY
printf '%s\n' '--- exact-path authorization and feature gates ---'
rg -n -g '*.go' -g '!**/*_test.go' -i \
'workspace.*(authorize|permission|member|owner|access|claim)|((authorize|permission|member|owner|access|claim).*workspace)' \
pkg/api/dashboard.go pkg/services/dashboards pkg/services/sqlstore pkg/middleware pkg/services/accesscontrol 2>/dev/null || trueRepository: coderabbitai/grafana
Length of output: 3476
Authorization Bypass (CWE-862): Missing Authorization
Reachability: External · Exploitability: Moderate
Reachability path
● Entry
pkg/services/dashboards/service/dashboard_service.go:647
FindDashboards
│
▼
● Hop
pkg/services/dashboards/database/database.go:738
GetDashboard
│
▼
● Sink
pkg/api/dashboard.go
Authorize the workspace before persisting WorkspaceID.
PostDashboard accepts workspaceId from the request, and the save path persists it without a workspace membership check. Resolve the workspace from trusted identity or validate membership before saving.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@pkg/services/dashboards/service/dashboard_service.go` around lines 219 - 229,
Update PostDashboard and the SaveDashboardCommand construction to authorize
dto.WorkspaceID before persisting it: resolve the workspace from the
authenticated user’s trusted identity or validate that the user is a member of
the requested workspace, and reject unauthorized requests before saving. Do not
persist the request-supplied WorkspaceID without this authorization check.
| <script nonce="" src="../../../public/build/runtime~fn_dashboard.bf82cca0bdd91aab521f.js" type="text/javascript"></script> | ||
|
|
||
| <script nonce="" src="../../../public/build/fn_dashboard.1bbf49e9b9bbd3087187.js" type="text/javascript"></script> |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- generated document ---'
cat -n public/microfrontends/fn_dashboard/index.html | sed -n '1,35p'
printf '%s\n' '--- template ---'
cat -n public/views/index-microfrontend-template.html | sed -n '1,45p'
printf '%s\n' '--- integrity configuration references ---'
rg -n --glob '!public/build/**' 'jsIntegrity|crossOriginLoading|Subresource|integrity' public packages webpack* 2>/dev/null | head -200 || true
printf '%s\n' '--- relevant tracked-file status ---'
git status --short -- public/microfrontends/fn_dashboard/index.html public/views/index-microfrontend-template.htmlRepository: coderabbitai/grafana
Length of output: 4270
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- micro-frontend and webpack configuration files ---'
fd -HI -t f 'webpack|microfrontend|fn_dashboard|package.json' . | head -200
printf '%s\n' '--- fn_dashboard references outside generated assets ---'
rg -n --glob '!public/build/**' --glob '!node_modules/**' 'fn_dashboard|index-microfrontend-template|jsIntegrity|crossOriginLoading|webpackConfig' . | head -300
printf '%s\n' '--- focused diff and file history summary ---'
git diff -- public/microfrontends/fn_dashboard/index.html public/views/index-microfrontend-template.html
git log -5 --oneline -- public/microfrontends/fn_dashboard/index.html public/views/index-microfrontend-template.htmlRepository: coderabbitai/grafana
Length of output: 4681
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- production webpack configuration ---'
cat -n scripts/webpack/webpack.prod.js | sed -n '1,165p'
printf '%s\n' '--- development webpack configuration ---'
cat -n scripts/webpack/webpack.dev.js | sed -n '105,145p'
printf '%s\n' '--- package dependencies related to integrity and HTML generation ---'
rg -n -i 'webpack-subresource|subresource|html-webpack-plugin|integrity' package.json yarn.lock pnpm-lock.yaml package-lock.json scripts pkg 2>/dev/null | head -200 || true
printf '%s\n' '--- generated file origin and recent commits ---'
git log -1 --format=fuller -- public/microfrontends/fn_dashboard/index.html
git show --stat --oneline 58b8aafa -- public/microfrontends/fn_dashboard/index.html scripts/webpack/webpack.prod.js public/views/index-microfrontend-template.htmlRepository: coderabbitai/grafana
Length of output: 50376
Security Misconfiguration (CWE-353)
Reachability: External
Restore subresource integrity for both dashboard bundles.
Regenerate this document with the build-generated integrity values and crossorigin="anonymous" attributes.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@public/microfrontends/fn_dashboard/index.html` around lines 20 - 22, Update
both dashboard bundle script tags in the document to restore build-generated
subresource integrity values and add crossorigin="anonymous" attributes.
Regenerate or reuse the correct integrity hashes for runtime~fn_dashboard and
fn_dashboard, preserving their existing sources and script behavior.
Summary
Brings the Grafana fork's panels and time range picker in line with the CodeRabbit (Carrot UI) design system, alongside the custom-dashboard backend work.
Changes
Custom dashboards (backend)
Adds custom-dashboard fields to the dashboard DTO/model, wires tag-based filtering through the search store, and adds the supporting migration.
Panel chrome — Carrot UI / Vercel-style cards
200/300/400/500to300/400/500/600; body text was rendering as Light and looked washed out. Headings now use negative tracking (Geist convention).1 → 1.5(12px gutters), header height4 → 5(40px).#f5f3f7canvas, with lighter hairline borders.Icons
Adopts
@heroicons/react@2.2.0— the icon set used bycoderabbit-ui(441 files there vs. 24 for lucide) — for the panel description icon.Time range picker
Rounded popover, quick ranges as inset pills, small-caps section eyebrows, and a recessed footer bar. Selected state deliberately uses the neutral secondary surface rather than brand orange, which fought the panel accents and hurt contrast.
Two CSS bug fixes
Tab'sactiveStylesetsoverflow: hidden, which clipped its own absolutely-positioned 4px/6px-radius::beforeunderline into a box around the tab. Fixed withoverflow: visibleand a square-cut 2px underline.overflow: hiddenadded for rounded corners also clipped the timezone and fiscal-year menus, which render inline viamenuShouldPortal={false}. Removed it and rounded the footer's own bottom corners instead (via:last-child, so whichever block ends the popover is rounded).Verification
yarn devcompiles with no errors from any changed file.localhost:3003in both light and dark themes: panel styling, both dropdowns rendering unclipped, and the tab underline.yarn jeston the touched areas matches the stashed baseline exactly — no regressions.npx prettier --checkpasses on all changed files.Notes
volkovlabs-table-panelplugin (missing@volkovlabs/jest-selectors) and theDateTimePickertimezone/onblur tests. Both confirmed identical on a clean baseline.Tabcomponent is unchanged elsewhere in Grafana.Summary by CodeRabbit
UI Improvements
Dashboard Management