diff --git a/tools/release-plan-dashboard/public/index.html b/tools/release-plan-dashboard/public/index.html index 85e65498b95..955ef9316b7 100644 --- a/tools/release-plan-dashboard/public/index.html +++ b/tools/release-plan-dashboard/public/index.html @@ -644,76 +644,113 @@

PM View — Release Plan Health

-
-

- 🏁 Ready to Complete Private - Preview Release Plans -

-
-
+
+
+ +
+

+ Needs Attention +

+

+ Priority-ordered groups that require PM follow-up +

+
+
+
+
+

+ 🏁 Ready to Complete Private + Preview Release Plans +

+
+
-
-

- ⚠️ Missing Namespace Approval - -

-
-
+
+

+ 🕐 Past Due Release Plans + +

+
+
-
-

- 📅 Approaching SDK Release Target - (This & Next Month) -

-
-
+
+

+ ⚠️ Stale Release Plans (No + activity for 3+ months) +

+
+
-
-

- 🕐 Past Due Release Plans - -

-
-
+
+

+ 🔴 Release Plans Without SDK + for All Languages +

+
+
-
-

- ⚠️ Stale Release Plans (No - activity for 3+ months) -

-
-
+
+

+ 🟡 Partially Released + +

+
+
-
-

- 🔴 Release Plans Without SDK for - All Languages -

-
-
+
+

+ ⚠️ Missing Namespace Approval + +

+
+
-
-

- 🟡 Partially Released - -

-
+
+

+ 📋 Missing Product Details + +

+
+
+
-
-

- 📋 Missing Product Details - -

-
-
+
+
+ +
+

+ Informational +

+

+ No immediate PM action required +

+
+
+
+
+

+ ✅ Recently Finished (Last 2 + Months) +

+
+
-
-

- ✅ Recently Finished (Last 2 - Months) -

-
+
+

+ 📅 Approaching SDK Release + Target (This & Next Month) + +

+
+
+
diff --git a/tools/release-plan-dashboard/public/style.css b/tools/release-plan-dashboard/public/style.css index 0f1be01e475..a40555a1660 100644 --- a/tools/release-plan-dashboard/public/style.css +++ b/tools/release-plan-dashboard/public/style.css @@ -452,6 +452,47 @@ main { opacity: 0.8; } +.pm-section-group { + border: 1px solid #e1dfdd; + border-radius: var(--radius); + background: #faf9f8; + padding: 16px 18px 8px; + margin-bottom: 20px; +} + +.pm-section-group-header { + margin-bottom: 8px; +} + +.pm-section-group-header.section-header { + padding-top: 0; +} + +.pm-section-group-header.section-header > div { + min-width: 0; +} + +.pm-section-group-header.section-header:hover .pm-section-group-title { + color: var(--azure-blue); +} + +.pm-section-group-title { + margin: 0; + font-size: 1.1rem; + font-weight: 700; + color: var(--azure-dark); +} + +.pm-section-group-desc { + margin: 4px 0 0; + color: var(--gray); + font-size: 0.9rem; +} + +.pm-section-group.collapsed > .pm-section-group-content { + display: none !important; +} + .section-header { cursor: pointer; user-select: none; diff --git a/tools/release-plan-dashboard/tests/pm-view-layout.test.js b/tools/release-plan-dashboard/tests/pm-view-layout.test.js new file mode 100644 index 00000000000..089100ade08 --- /dev/null +++ b/tools/release-plan-dashboard/tests/pm-view-layout.test.js @@ -0,0 +1,74 @@ +import { describe, expect, test } from "vitest"; +import { readFileSync } from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const indexHtml = readFileSync( + path.join(__dirname, "../public/index.html"), + "utf8", +); + +function expectOrdered(block, labels) { + let lastIndex = -1; + for (const label of labels) { + const index = block.indexOf(label); + expect(index, `Expected to find "${label}"`).toBeGreaterThanOrEqual(0); + expect(index, `Expected "${label}" after prior sections`).toBeGreaterThan( + lastIndex, + ); + lastIndex = index; + } +} + +describe("PM View Attention Required layout", () => { + test("groups informational and action sections in the expected order", () => { + const informationalIndex = indexHtml.indexOf('id="pm-informational"'); + const needsAttentionIndex = indexHtml.indexOf('id="pm-needs-attention"'); + + expect(needsAttentionIndex).toBeGreaterThanOrEqual(0); + expect(indexHtml).toMatch( + /aria-labelledby="pm-needs-attention"[\s\S]*class="pm-section-group-header section-header"[\s\S]*data-target="list-pm-needs-attention"[\s\S]*id="pm-needs-attention"[\s\S]*>\s*Needs Attention\s*\s*Informational\s* { + const sectionIds = [ + "section-pm-finished", + "section-pm-approaching", + "section-pm-pp-ready", + "section-pm-pastdue", + "section-pm-inactive", + "section-pm-tier1", + "section-pm-partial", + "section-pm-ns-missing", + "section-pm-product-missing", + ]; + + for (const sectionId of sectionIds) { + const matches = + indexHtml.match(new RegExp(`id="${sectionId}"`, "g")) || []; + expect(matches).toHaveLength(1); + } + }); +});