From 724ffbe0ef92af601f92861025c099ce518255f0 Mon Sep 17 00:00:00 2001 From: francisco Date: Fri, 7 Mar 2025 13:44:36 -0300 Subject: [PATCH] feat: add breadcrumb to issue listing Close #1014 --- .../components/Breadcrumb/IssueBreadcrumb.tsx | 42 +++++++++++++++++++ .../src/components/IssueTable/IssueTable.tsx | 3 ++ dashboard/src/locales/messages/index.ts | 1 + .../src/pages/IssueDetails/IssueDetails.tsx | 10 +++++ .../pages/IssueListing/IssueListingPage.tsx | 12 +++++- dashboard/src/types/general.ts | 1 + 6 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 dashboard/src/components/Breadcrumb/IssueBreadcrumb.tsx diff --git a/dashboard/src/components/Breadcrumb/IssueBreadcrumb.tsx b/dashboard/src/components/Breadcrumb/IssueBreadcrumb.tsx new file mode 100644 index 00000000..17ad038b --- /dev/null +++ b/dashboard/src/components/Breadcrumb/IssueBreadcrumb.tsx @@ -0,0 +1,42 @@ +import type { MessageDescriptor } from 'react-intl'; +import { FormattedMessage } from 'react-intl'; + +import { memo, type JSX } from 'react'; + +import type { LinkProps } from '@tanstack/react-router'; + +import { + Breadcrumb, + BreadcrumbItem, + BreadcrumbLink, + BreadcrumbList, + BreadcrumbPage, + BreadcrumbSeparator, +} from './Breadcrumb'; + +const IssueBreadcrumb = ({ + searchParams, + locationMessage, +}: { + searchParams: LinkProps['search']; + locationMessage: MessageDescriptor['id']; +}): JSX.Element => { + return ( + + + + + + + + + + + + + + + + ); +}; +export const MemoizedIssueBreadcrumb = memo(IssueBreadcrumb); diff --git a/dashboard/src/components/IssueTable/IssueTable.tsx b/dashboard/src/components/IssueTable/IssueTable.tsx index 8d204c3b..0f3331b7 100644 --- a/dashboard/src/components/IssueTable/IssueTable.tsx +++ b/dashboard/src/components/IssueTable/IssueTable.tsx @@ -41,6 +41,7 @@ import { valueOrEmpty } from '@/lib/string'; import { TooltipDateTime } from '@/components/TooltipDateTime'; import { shouldShowRelativeDate } from '@/lib/date'; +import { RedirectFrom } from '@/types/general'; const getLinkProps = ( row: Row, @@ -74,6 +75,8 @@ const getLinkProps = ( params: { issueId: row.original.id }, state: s => ({ ...s, + id: row.original.id, + from: RedirectFrom.Issues, }), }; }; diff --git a/dashboard/src/locales/messages/index.ts b/dashboard/src/locales/messages/index.ts index 80ba79f0..11202c7a 100644 --- a/dashboard/src/locales/messages/index.ts +++ b/dashboard/src/locales/messages/index.ts @@ -178,6 +178,7 @@ export const messages = { 'issue.firstSeen': 'First seen', 'issue.newIssue': 'New issue: This is the first time this issue was seen', 'issue.noIssueFound': 'No issue found.', + 'issue.path': 'Issues', 'issue.searchPlaceholder': 'Search by issue comment with a regex', 'issue.tooltip': 'Issues groups several builds or tests by matching result status and logs.{br}They may also be linked to an external issue tracker or mailing list discussion.', diff --git a/dashboard/src/pages/IssueDetails/IssueDetails.tsx b/dashboard/src/pages/IssueDetails/IssueDetails.tsx index 5eb3e2f8..eef11dc6 100644 --- a/dashboard/src/pages/IssueDetails/IssueDetails.tsx +++ b/dashboard/src/pages/IssueDetails/IssueDetails.tsx @@ -10,6 +10,7 @@ import { RedirectFrom } from '@/types/general'; import { MemoizedTreeBreadcrumb } from '@/components/Breadcrumb/TreeBreadcrumb'; import { MemoizedHardwareBreadcrumb } from '@/components/Breadcrumb/HardwareBreadcrumb'; import { useSearchStore } from '@/hooks/store/useSearchStore'; +import { MemoizedIssueBreadcrumb } from '@/components/Breadcrumb/IssueBreadcrumb'; const getBuildTableRowLink = (buildId: string): LinkProps => ({ to: '/build/$buildId', @@ -54,6 +55,15 @@ const IssueDetailsPage = (): JSX.Element => { /> ); } + + if (historyState.from === RedirectFrom.Issues) { + return ( + + ); + } } }, [historyState.from, historyState.id, previousSearch]); diff --git a/dashboard/src/pages/IssueListing/IssueListingPage.tsx b/dashboard/src/pages/IssueListing/IssueListingPage.tsx index dcd3774a..7e039648 100644 --- a/dashboard/src/pages/IssueListing/IssueListingPage.tsx +++ b/dashboard/src/pages/IssueListing/IssueListingPage.tsx @@ -1,4 +1,6 @@ -import { useMemo, type JSX } from 'react'; +import { useEffect, useMemo, type JSX } from 'react'; + +import { useSearch } from '@tanstack/react-router'; import QuerySwitcher from '@/components/QuerySwitcher/QuerySwitcher'; @@ -10,6 +12,7 @@ import { useIssueListing } from '@/api/issue'; import { IssueTable } from '@/components/IssueTable/IssueTable'; import { matchesRegexOrIncludes } from '@/lib/string'; import type { IssueListingResponse } from '@/types/issueListing'; +import { useSearchStore } from '@/hooks/store/useSearchStore'; interface IIssueListingPage { inputFilter: string; @@ -19,6 +22,13 @@ export const IssueListingPage = ({ inputFilter, }: IIssueListingPage): JSX.Element => { const { data, status, error, isLoading } = useIssueListing(); + const searchParams = useSearch({ from: '/_main/issues' }); + const updatePreviousSearch = useSearchStore(s => s.updatePreviousSearch); + + useEffect( + () => updatePreviousSearch(searchParams), + [searchParams, updatePreviousSearch], + ); const filteredData = useMemo((): IssueListingResponse => { if (!data) { diff --git a/dashboard/src/types/general.ts b/dashboard/src/types/general.ts index 3e2560a0..19f85e05 100644 --- a/dashboard/src/types/general.ts +++ b/dashboard/src/types/general.ts @@ -361,6 +361,7 @@ export const getTargetFilter = ( export enum RedirectFrom { Tree = 'tree', Hardware = 'hardware', + Issues = 'issues', } export type PossibleMonitorPath = '/tree' | '/hardware' | '/issues';