Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions packages/spec-dashboard/src/components/dashboard-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,15 @@ import type { ManifestTreeNode, TreeTableRow } from "./tree-table/types.js";
export interface DashboardTableProps {
coverageSummary: CoverageSummary;
emitterDisplayNames?: Record<string, string>;
/** When true, all tree rows are expanded regardless of user toggles. */
expandAll?: boolean;
}

function buildTreeRows(
node: ManifestTreeNode,
expandedRows: Record<string, boolean>,
toggleExpand: (key: string) => void,
expandAll = false,
depth = 0,
): TreeTableRow[] {
const rows: TreeTableRow[] = [];
Expand All @@ -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,
Expand All @@ -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);
}
}
Expand All @@ -57,6 +60,7 @@ function buildTreeRows(
export const DashboardTable: FunctionComponent<DashboardTableProps> = ({
coverageSummary,
emitterDisplayNames,
expandAll,
}) => {
const languages: string[] = Object.keys(coverageSummary.generatorReports) as any;
const tree = useMemo(() => createTree(coverageSummary.manifest), [coverageSummary.manifest]);
Expand All @@ -71,8 +75,8 @@ export const DashboardTable: FunctionComponent<DashboardTableProps> = ({
[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 (
Expand Down
14 changes: 14 additions & 0 deletions packages/spec-dashboard/src/components/dashboard.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
48 changes: 43 additions & 5 deletions packages/spec-dashboard/src/components/dashboard.tsx
Comment thread
timotheeguerin marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -26,20 +26,44 @@ export const Dashboard: FunctionComponent<DashboardProps> = ({
emitterDisplayNames,
}) => {
const [selectedTier, setSelectedTier] = useState<string | undefined>(undefined);
const [nameFilter, setNameFilter] = useState<string>("");

// 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,
scenarioTierConfig,
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) => (
<div key={i} className={style["summary-table"]}>
<DashboardTable
coverageSummary={coverageSummary}
emitterDisplayNames={emitterDisplayNames}
expandAll={Boolean(normalizedNameFilter)}
/>
</div>
));
Expand All @@ -65,7 +89,21 @@ export const Dashboard: FunctionComponent<DashboardProps> = ({
)}
<div className={style["specs-row"]}>{specsCardTable}</div>
<div className={style["spacer"]}></div>
{summaryTables}
<div className={style["name-filter"]}>
<SearchBox
className={style["name-filter-input"]}
placeholder="Filter scenarios by name..."
value={nameFilter}
onChange={(_, data) => setNameFilter(data.value)}
/>
</div>
{normalizedNameFilter && summaryTables.length === 0 ? (
<Text className={style["no-results"]}>
No scenarios match "{deferredNameFilter.trim()}".
</Text>
) : (
summaryTables
)}
</div>
);
};
Expand Down
Loading