diff --git a/packages/spec-dashboard/src/components/dashboard-table.tsx b/packages/spec-dashboard/src/components/dashboard-table.tsx index c7d78c7979c..fd5e0ab7f51 100644 --- a/packages/spec-dashboard/src/components/dashboard-table.tsx +++ b/packages/spec-dashboard/src/components/dashboard-table.tsx @@ -15,12 +15,15 @@ import type { ManifestTreeNode, TreeTableRow } from "./tree-table/types.js"; export interface DashboardTableProps { coverageSummary: CoverageSummary; emitterDisplayNames?: Record; + /** When true, all tree rows are expanded regardless of user toggles. */ + expandAll?: boolean; } function buildTreeRows( node: ManifestTreeNode, expandedRows: Record, toggleExpand: (key: string) => void, + expandAll = false, depth = 0, ): TreeTableRow[] { const rows: TreeTableRow[] = []; @@ -31,7 +34,7 @@ function buildTreeRows( const hasChildren = Boolean(child.children && Object.keys(child.children).length > 0); const key = child.fullName; - const expanded = expandedRows[key] ?? false; + const expanded = expandAll || (expandedRows[key] ?? false); rows.push({ key, item: child, @@ -42,7 +45,7 @@ function buildTreeRows( toggleExpand: () => toggleExpand(key), }); if (hasChildren && expanded) { - for (const row of buildTreeRows(child, expandedRows, toggleExpand, depth + 1)) { + for (const row of buildTreeRows(child, expandedRows, toggleExpand, expandAll, depth + 1)) { rows.push(row); } } @@ -57,6 +60,7 @@ function buildTreeRows( export const DashboardTable: FunctionComponent = ({ coverageSummary, emitterDisplayNames, + expandAll, }) => { const languages: string[] = Object.keys(coverageSummary.generatorReports) as any; const tree = useMemo(() => createTree(coverageSummary.manifest), [coverageSummary.manifest]); @@ -71,8 +75,8 @@ export const DashboardTable: FunctionComponent = ({ [setExpandedRows], ); const treeRows = useMemo(() => { - return buildTreeRows(tree, expandedRows, toggleExpand); - }, [tree, expandedRows, toggleExpand]); + return buildTreeRows(tree, expandedRows, toggleExpand, expandAll); + }, [tree, expandedRows, toggleExpand, expandAll]); const rows = treeRows.map((x) => { return ( diff --git a/packages/spec-dashboard/src/components/dashboard.module.css b/packages/spec-dashboard/src/components/dashboard.module.css index 0078383db0e..dd08fd6fd4d 100644 --- a/packages/spec-dashboard/src/components/dashboard.module.css +++ b/packages/spec-dashboard/src/components/dashboard.module.css @@ -22,3 +22,17 @@ .tier-filter { margin-bottom: 20px; } + +.name-filter { + margin-bottom: 20px; +} + +.name-filter-input { + width: 320px; + max-width: 100%; +} + +.no-results { + display: block; + margin: 10px 5px; +} diff --git a/packages/spec-dashboard/src/components/dashboard.tsx b/packages/spec-dashboard/src/components/dashboard.tsx index 6f715da3151..4c61858f546 100644 --- a/packages/spec-dashboard/src/components/dashboard.tsx +++ b/packages/spec-dashboard/src/components/dashboard.tsx @@ -1,6 +1,6 @@ -import { Card, CardHeader, Text } from "@fluentui/react-components"; +import { Card, CardHeader, SearchBox, Text } from "@fluentui/react-components"; import type { FunctionComponent } from "react"; -import { useState } from "react"; +import { useDeferredValue, useMemo, useState } from "react"; import type { CoverageSummary } from "../apis.js"; import { useTierFiltering } from "../hooks/use-tier-filtering.js"; import type { TierConfig } from "../utils/tier-filtering-utils.js"; @@ -26,6 +26,12 @@ export const Dashboard: FunctionComponent = ({ emitterDisplayNames, }) => { const [selectedTier, setSelectedTier] = useState(undefined); + const [nameFilter, setNameFilter] = useState(""); + + // Defer the filter value so typing stays responsive: the SearchBox updates + // instantly while the expensive filtering + tree expansion render happens on + // a lower-priority, non-blocking update. + const deferredNameFilter = useDeferredValue(nameFilter); const { filteredSummaries, allTiers } = useTierFiltering( coverageSummaries, @@ -33,13 +39,31 @@ export const Dashboard: FunctionComponent = ({ selectedTier, ); - const summaryTables = filteredSummaries - .filter((s) => !selectedTier || s.manifest.scenarios.length > 0) + const normalizedNameFilter = deferredNameFilter.trim().toLowerCase(); + + const searchedSummaries = useMemo(() => { + if (!normalizedNameFilter) { + return filteredSummaries; + } + return filteredSummaries.map((summary) => ({ + ...summary, + manifest: { + ...summary.manifest, + scenarios: summary.manifest.scenarios.filter((s) => + s.name.toLowerCase().includes(normalizedNameFilter), + ), + }, + })); + }, [filteredSummaries, normalizedNameFilter]); + + const summaryTables = searchedSummaries + .filter((s) => (!selectedTier && !normalizedNameFilter) || s.manifest.scenarios.length > 0) .map((coverageSummary, i) => (
)); @@ -65,7 +89,21 @@ export const Dashboard: FunctionComponent = ({ )}
{specsCardTable}
- {summaryTables} +
+ setNameFilter(data.value)} + /> +
+ {normalizedNameFilter && summaryTables.length === 0 ? ( + + No scenarios match "{deferredNameFilter.trim()}". + + ) : ( + summaryTables + )} ); };