diff --git a/dashboard/e2e/e2e-selectors.ts b/dashboard/e2e/e2e-selectors.ts
index f584cf1aa..78499fff1 100644
--- a/dashboard/e2e/e2e-selectors.ts
+++ b/dashboard/e2e/e2e-selectors.ts
@@ -49,4 +49,5 @@ export const HARDWARE_LISTING_SELECTORS = {
branchSelector: '[data-test-id="hardware-branch-selector"]',
revisionSelector: '[data-test-id="hardware-revision-selector"]',
clearSelection: '[data-test-id="hardware-selection-clear"]',
+ filterLabel: '[data-test-id="hardware-filter-label"]',
} as const;
diff --git a/dashboard/e2e/hardware-listing.spec.ts b/dashboard/e2e/hardware-listing.spec.ts
index c116597ba..b743f6e42 100644
--- a/dashboard/e2e/hardware-listing.spec.ts
+++ b/dashboard/e2e/hardware-listing.spec.ts
@@ -137,4 +137,49 @@ test.describe('Hardware Listing Page Tests', () => {
).toContainText('Select tree');
await expect(clearButton).toBeHidden();
});
+
+ test('filter label updates with query params', async ({ page }) => {
+ const filterLabel = page.locator(HARDWARE_LISTING_SELECTORS.filterLabel);
+ await expect(filterLabel).toBeVisible();
+ await expect(filterLabel).toContainText(
+ /Showing latest checkout for trees updated in the last \d+ days/,
+ );
+
+ const url = new URL(page.url());
+
+ url.searchParams.set('days', '2');
+ await page.goto(url.toString());
+ await expect(filterLabel).toContainText('last 2 days');
+
+ url.searchParams.set('days', '7');
+ await page.goto(url.toString());
+ await expect(filterLabel).toContainText('last 7 days');
+ });
+
+ test('selecting a revision toggles filter label', async ({ page }) => {
+ const filterLabel = page.locator(HARDWARE_LISTING_SELECTORS.filterLabel);
+ await expect(filterLabel).toBeVisible();
+
+ await selectComboboxOption(page, HARDWARE_LISTING_SELECTORS.treeSelector);
+ await expect(filterLabel).toBeHidden();
+
+ await page.locator(HARDWARE_LISTING_SELECTORS.clearSelection).click();
+ await expect(filterLabel).toBeVisible();
+ });
+
+ test('loading URL with revision does not show filter label', async ({
+ page,
+ }) => {
+ await selectComboboxOption(page, HARDWARE_LISTING_SELECTORS.treeSelector);
+ const urlWithRevision = page.url();
+ await expect(urlWithRevision).toMatch(/[?&]ch=/);
+
+ await page.goto(urlWithRevision);
+
+ const filterLabel = page.locator(HARDWARE_LISTING_SELECTORS.filterLabel);
+ await expect(filterLabel).toBeHidden();
+
+ await page.locator(HARDWARE_LISTING_SELECTORS.clearSelection).click();
+ await expect(filterLabel).toBeVisible();
+ });
});
diff --git a/dashboard/src/components/FilterLabel/FilterLabel.tsx b/dashboard/src/components/FilterLabel/FilterLabel.tsx
new file mode 100644
index 000000000..bec508508
--- /dev/null
+++ b/dashboard/src/components/FilterLabel/FilterLabel.tsx
@@ -0,0 +1,16 @@
+import type { JSX } from 'react';
+import { FormattedMessage } from 'react-intl';
+
+export function FilterLabel({ days }: { days: number }): JSX.Element {
+ return (
+
+
+
+ );
+}
diff --git a/dashboard/src/components/TreeListingPage/TreeTable.tsx b/dashboard/src/components/TreeListingPage/TreeTable.tsx
index 42dfd702b..5be6a8267 100644
--- a/dashboard/src/components/TreeListingPage/TreeTable.tsx
+++ b/dashboard/src/components/TreeListingPage/TreeTable.tsx
@@ -34,6 +34,7 @@ import { usePaginationState } from '@/hooks/usePaginationState';
import BaseTable, { TableHead } from '@/components/Table/BaseTable';
import { TableBody, TableCell, TableRow } from '@/components/ui/table';
import { ConditionalTableCell } from '@/components/Table/ConditionalTableCell';
+import { FilterLabel } from '@/components/FilterLabel/FilterLabel';
import { BaseGroupedStatusWithLink } from '@/components/Status/Status';
import { TableHeader } from '@/components/Table/TableHeader';
@@ -50,6 +51,7 @@ import QuerySwitcher from '@/components/QuerySwitcher/QuerySwitcher';
import { MemoizedSectionError } from '@/components/DetailsPages/SectionError';
import type { TreeListingRoutesMap } from '@/utils/constants/treeListing';
+import { DEFAULT_TIME_SEARCH } from '@/utils/constants/general';
import {
commonTreeTableColumns,
@@ -265,7 +267,7 @@ export function TreeTable({
isLoading?: boolean;
urlFromMap: TreeListingRoutesMap;
}): JSX.Element {
- const { origin, listingSize } = useSearch({
+ const { origin, listingSize, intervalInDays } = useSearch({
from: urlFromMap.search,
});
const navigate = useNavigate({ from: urlFromMap.navigate });
@@ -395,11 +397,16 @@ export function TreeTable({
{tableBody}
-
+
);
}
diff --git a/dashboard/src/locales/messages/index.ts b/dashboard/src/locales/messages/index.ts
index 08c5f0202..d972da1e0 100644
--- a/dashboard/src/locales/messages/index.ts
+++ b/dashboard/src/locales/messages/index.ts
@@ -223,6 +223,8 @@ export const messages = {
'hardwareListing.branchSelectorSearchPlaceholder': 'Search branch...',
'hardwareListing.clearSelection': 'Clear selection',
'hardwareListing.description': 'List of hardware from kernel tests',
+ 'hardwareListing.latestCheckoutFilterLabel':
+ 'Showing latest checkout for trees updated in the last {days} days',
'hardwareListing.notFound': 'No hardware information available',
'hardwareListing.revisionEmpty':
'The selected revision has no hardware rows yet. Data ingestion may still be in progress.',
diff --git a/dashboard/src/pages/Hardware/HardwareTable.tsx b/dashboard/src/pages/Hardware/HardwareTable.tsx
index 67deb9414..a797e79ad 100644
--- a/dashboard/src/pages/Hardware/HardwareTable.tsx
+++ b/dashboard/src/pages/Hardware/HardwareTable.tsx
@@ -46,6 +46,8 @@ import type {
import { sumStatus } from '@/utils/status';
+import { REDUCED_TIME_SEARCH } from '@/utils/constants/general';
+
import { usePaginationState } from '@/hooks/usePaginationState';
import { zPossibleTabValidator } from '@/types/tree/TreeDetails';
@@ -61,6 +63,8 @@ import QuerySwitcher from '@/components/QuerySwitcher/QuerySwitcher';
import { MemoizedSectionError } from '@/components/DetailsPages/SectionError';
import { LoadingCircle } from '@/components/ui/loading-circle';
+import { FilterLabel } from '@/components/FilterLabel/FilterLabel';
+
import { buildHardwareDetailsSearch } from './hardwareTableUtils';
import { HardwareRevisionSelectors } from './HardwareRevisionSelectors';
import type { HardwareRevisionSelectorValue } from './hardwareSelection';
@@ -390,7 +394,7 @@ export function HardwareTable({
onTreeChange = (): void => {},
onClearSelection = (): void => {},
}: IHardwareTable): JSX.Element {
- const { listingSize } = useSearch({ strict: false });
+ const { listingSize, intervalInDays } = useSearch({ strict: false });
const navigate = useNavigate({ from: navigateFrom });
const [sorting, setSorting] = useState([]);
@@ -546,11 +550,18 @@ export function HardwareTable({
{tableBody}
-
+
+ {!selection && (
+
+ )}
+
+
);
}