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 && ( + + )} +
+ +
+
); }