Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: issue listing changes #1033

Merged
merged 3 commits into from
Mar 7, 2025
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
2 changes: 1 addition & 1 deletion dashboard/src/api/issue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const fetchIssueListing = async (
};

export const useIssueListing = (): UseQueryResult<IssueListingResponse> => {
const { origin, intervalInDays } = useSearch({ from: '/_main/issue' });
const { origin, intervalInDays } = useSearch({ from: '/_main/issues' });

const queryKey = ['issueTable', origin, intervalInDays];

Expand Down
42 changes: 42 additions & 0 deletions dashboard/src/components/Breadcrumb/IssueBreadcrumb.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<Breadcrumb className="pt-6 pb-6">
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink to="/issues" search={searchParams}>
<FormattedMessage id="issue.path" />
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbPage>
<FormattedMessage id={locationMessage} />
</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
);
};
export const MemoizedIssueBreadcrumb = memo(IssueBreadcrumb);
3 changes: 3 additions & 0 deletions dashboard/src/components/IssueDetails/IssueDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ import { getIssueCulprit } from '@/lib/issue';

import { MemoizedIssueDetailsOGTags } from '@/components/OpenGraphTags/IssueDetailsOGTags';

import { TooltipIcon } from '@/components/Icons/TooltipIcon';

import { IssueDetailsTestSection } from './IssueDetailsTestSection';

import { IssueDetailsBuildSection } from './IssueDetailsBuildSection';
Expand Down Expand Up @@ -168,6 +170,7 @@ export const IssueDetails = ({
{
title: 'issueDetails.culpritTitle',
linkText: issueCulprit,
titleIcon: <TooltipIcon tooltipId="issueListing.culpritInfo" />,
},
{
title: 'issueDetails.id',
Expand Down
25 changes: 13 additions & 12 deletions dashboard/src/components/IssueTable/IssueTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<IssueListingTableItem>,
Expand All @@ -51,7 +52,7 @@ const getLinkProps = (
row.original.git_commit_hash !== undefined
) {
return {
from: '/issue',
from: '/issues',
to: '/tree/$treeId',
params: { treeId: row.original.git_commit_hash },
state: s => s,
Expand All @@ -69,11 +70,13 @@ const getLinkProps = (
}

return {
from: '/issue',
from: '/issues',
to: '/issue/$issueId',
params: { issueId: row.original.id },
state: s => ({
...s,
id: row.original.id,
from: RedirectFrom.Issues,
}),
};
};
Expand All @@ -97,12 +100,6 @@ const columns: ColumnDef<IssueListingTableItem>[] = [
row.original.id
),
},
{
accessorKey: 'version',
header: ({ column }): JSX.Element => (
<TableHeader column={column} intlKey="issueDetails.version" />
),
},
{
id: 'culprit',
accessorFn: (original, _): boolean[] => {
Expand All @@ -113,7 +110,11 @@ const columns: ColumnDef<IssueListingTableItem>[] = [
];
},
header: ({ column }): JSX.Element => (
<TableHeader column={column} intlKey="issueDetails.culpritTitle" />
<TableHeader
column={column}
intlKey="issueDetails.culpritTitle"
tooltipId="issueListing.culpritInfo"
/>
),
cell: ({ row }): JSX.Element => (
<IssueCulprit
Expand Down Expand Up @@ -162,8 +163,8 @@ interface IIssueTable {
}

export const IssueTable = ({ issueListing }: IIssueTable): JSX.Element => {
const { listingSize } = useSearch({ strict: false });
const navigate = useNavigate({ from: '/issue' });
const { listingSize } = useSearch({ from: '/_main/issues' });
const navigate = useNavigate({ from: '/issues' });

const [sorting, setSorting] = useState<SortingState>([
{
Expand Down Expand Up @@ -267,7 +268,7 @@ export const IssueTable = ({ issueListing }: IIssueTable): JSX.Element => {
/>
</span>
<div className="flex items-center justify-between gap-10">
<MemoizedInputTime navigateFrom="/issue" />
<MemoizedInputTime navigateFrom="/issues" />
<PaginationInfo
table={table}
intlLabel="global.issues"
Expand Down
11 changes: 8 additions & 3 deletions dashboard/src/components/LinkWithIcon/LinkWithIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export interface ILinkWithIcon {
linkComponent?: ReactElement;
onClick?: () => void;
unformattedTitle?: string;
titleIcon?: JSX.Element;
}

const LinkWithIcon = ({
Expand All @@ -23,14 +24,18 @@ const LinkWithIcon = ({
linkComponent,
onClick,
unformattedTitle,
titleIcon,
}: ILinkWithIcon): JSX.Element => {
const WrapperLink = link ? 'a' : 'div';
return (
<div className="flex flex-col items-start gap-2 text-sm">
{(title && (
<span className="font-bold">
<FormattedMessage id={title} />
</span>
<div className="flex flex-row gap-[5px]">
<span className="font-bold">
<FormattedMessage id={title} />
</span>
{titleIcon}
</div>
)) ||
(unformattedTitle && <p className="font-bold">{unformattedTitle}</p>)}
{linkComponent ?? (
Expand Down
4 changes: 2 additions & 2 deletions dashboard/src/components/OpenGraphTags/ListingOGTags.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const ListingOGTags = ({
case '/hardware':
descriptionId = 'hardwareListing.description';
break;
case '/issue':
case '/issues':
descriptionId = 'issueListing.description';
break;
}
Expand All @@ -45,7 +45,7 @@ const ListingOGTags = ({
return formatMessage({ id: 'treeListing.title' });
case '/hardware':
return formatMessage({ id: 'hardwareListing.title' });
case '/issue':
case '/issues':
return formatMessage({ id: 'issueListing.title' });
}
}, [formatMessage, monitor]);
Expand Down
1 change: 1 addition & 0 deletions dashboard/src/components/Section/Section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export const Subsection = ({ infos, title }: ISubsection): JSX.Element => {
key={info.title?.toString()}
title={info.title}
link={info.link}
titleIcon={info.titleIcon}
linkComponent={info.linkComponent}
linkText={info.linkText}
unformattedTitle={info.unformattedTitle}
Expand Down
2 changes: 1 addition & 1 deletion dashboard/src/components/SideMenu/SideMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ const SideMenu = (): JSX.Element => {
selected: false,
},
{
navigateTo: '/issue',
navigateTo: '/issues',
idIntl: 'routes.issueMonitor',
icon: <RxRadiobutton className="size-5" />,
selected: false,
Expand Down
10 changes: 5 additions & 5 deletions dashboard/src/components/TopBar/TopBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,10 @@ import { zOrigin, zOriginEnum } from '@/types/general';

const getTargetPath = (basePath: string): PossibleMonitorPath => {
switch (basePath) {
case 'tree':
return '/tree';
case 'hardware':
return '/hardware';
case 'issue':
return '/issue';
case 'issues':
return '/issues';
default:
return '/tree';
}
Expand Down Expand Up @@ -79,6 +77,8 @@ const TitleName = ({ basePath }: { basePath: string }): JSX.Element => {
return <FormattedMessage id="routes.treeMonitor" />;
case 'hardware':
return <FormattedMessage id="routes.hardwareMonitor" />;
case 'issues':
return <FormattedMessage id="routes.issueMonitor" />;
case 'build':
return <FormattedMessage id="routes.buildDetails" />;
case 'test':
Expand All @@ -104,7 +104,7 @@ const TopBar = (): JSX.Element => {
</span>
{(basePath === 'tree' ||
basePath === 'hardware' ||
basePath === 'issue') && <OriginSelect basePath={basePath} />}
basePath === 'issues') && <OriginSelect basePath={basePath} />}
</div>
</div>
);
Expand Down
3 changes: 3 additions & 0 deletions dashboard/src/locales/messages/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.',
Expand All @@ -196,6 +197,8 @@ export const messages = {
'issueDetails.reportSubject': 'Report Subject',
'issueDetails.reportUrl': 'Report URL',
'issueDetails.version': 'Version',
'issueListing.culpritInfo':
'Layers of the execution stack responsible for the issue. If all are false, the issue is considered invalid.',
'issueListing.description': 'List of issues from builds and tests',
'issueListing.title': 'Issue Listing ― KCI Dashboard',
'issueListing.treeBranchTooltip':
Expand Down
10 changes: 10 additions & 0 deletions dashboard/src/pages/IssueDetails/IssueDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -54,6 +55,15 @@ const IssueDetailsPage = (): JSX.Element => {
/>
);
}

if (historyState.from === RedirectFrom.Issues) {
return (
<MemoizedIssueBreadcrumb
searchParams={previousSearch}
locationMessage="issueDetails.issueDetails"
/>
);
}
}
}, [historyState.from, historyState.id, previousSearch]);

Expand Down
4 changes: 2 additions & 2 deletions dashboard/src/pages/IssueListing/IssueListing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const IssueListing = (): JSX.Element => {

const issueSearch = z.string().catch('').parse(unsafeIssueSearch);

const navigate = useNavigate({ from: '/issue' });
const navigate = useNavigate({ from: '/issues' });

const onInputSearchTextChange = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
Expand All @@ -38,7 +38,7 @@ const IssueListing = (): JSX.Element => {

return (
<>
<MemoizedListingOGTags monitor="/issue" search={issueSearch} />
<MemoizedListingOGTags monitor="/issues" search={issueSearch} />
<div className="fixed top-0 z-10 mx-[380px] flex w-full pt-5 pr-12 pl-6">
<div className="flex w-2/3 items-center px-6">
<DebounceInput
Expand Down
12 changes: 11 additions & 1 deletion dashboard/src/pages/IssueListing/IssueListingPage.tsx
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -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;
Expand All @@ -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) {
Expand Down
Loading