Skip to content

Commit 1b6daa3

Browse files
fix: show hardware/tree table even when no data is available (#1637)
1 parent 6d8da13 commit 1b6daa3

File tree

8 files changed

+171
-71
lines changed

8 files changed

+171
-71
lines changed

dashboard/src/components/IssueTable/IssueTable.tsx

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import type { LinkProps } from '@tanstack/react-router';
2020
import { useNavigate, useSearch } from '@tanstack/react-router';
2121

2222
import { useCallback, useMemo, useState } from 'react';
23+
import type { UseQueryResult } from '@tanstack/react-query';
2324

2425
import { FormattedMessage } from 'react-intl';
2526

@@ -46,6 +47,9 @@ import { TooltipDateTime } from '@/components/TooltipDateTime';
4647
import { shouldShowRelativeDate } from '@/lib/date';
4748
import { RedirectFrom } from '@/types/general';
4849

50+
import QuerySwitcher from '@/components/QuerySwitcher/QuerySwitcher';
51+
import { MemoizedSectionError } from '@/components/DetailsPages/SectionError';
52+
4953
const getLinkProps = (
5054
row: Row<IssueListingTableItem>,
5155
cell: Cell<IssueListingTableItem, unknown>,
@@ -178,9 +182,19 @@ const columns: ColumnDef<IssueListingTableItem>[] = [
178182

179183
interface IIssueTable {
180184
issueListing?: IssueListingResponse;
185+
status?: UseQueryResult['status'];
186+
queryData?: unknown;
187+
error?: Error | null;
188+
isLoading?: boolean;
181189
}
182190

183-
export const IssueTable = ({ issueListing }: IIssueTable): JSX.Element => {
191+
export const IssueTable = ({
192+
issueListing,
193+
status,
194+
queryData,
195+
error,
196+
isLoading,
197+
}: IIssueTable): JSX.Element => {
184198
const { listingSize } = useSearch({ from: '/_main/issues' });
185199
const navigate = useNavigate({ from: '/issues' });
186200

@@ -261,7 +275,7 @@ export const IssueTable = ({ issueListing }: IIssueTable): JSX.Element => {
261275
) : (
262276
<TableRow>
263277
<TableCell colSpan={columns.length} className="h-24 text-center">
264-
<FormattedMessage id="global.noResults" />
278+
<FormattedMessage id="issueListing.notFound" />
265279
</TableCell>
266280
</TableRow>
267281
);
@@ -279,9 +293,22 @@ export const IssueTable = ({ issueListing }: IIssueTable): JSX.Element => {
279293

280294
return (
281295
<>
282-
<BaseTable headerComponents={tableHeaders}>
283-
<TableBody>{tableBody}</TableBody>
284-
</BaseTable>
296+
<QuerySwitcher
297+
status={status}
298+
data={queryData}
299+
error={error}
300+
customError={
301+
<MemoizedSectionError
302+
isLoading={isLoading}
303+
errorMessage={error?.message}
304+
emptyLabel="issueListing.notFound"
305+
/>
306+
}
307+
>
308+
<BaseTable headerComponents={tableHeaders}>
309+
<TableBody>{tableBody}</TableBody>
310+
</BaseTable>
311+
</QuerySwitcher>
285312
<PaginationInfo
286313
table={table}
287314
intlLabel="global.issues"

dashboard/src/components/QuerySwitcher/QuerySwitcher.tsx

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { Fragment, type ReactNode, type JSX } from 'react';
33

44
import { FormattedMessage } from 'react-intl';
55

6+
import { HttpStatusCode } from 'axios';
7+
68
import { cn } from '@/lib/utils';
79

810
import { Skeleton } from '@/components/ui/skeleton';
@@ -17,6 +19,7 @@ type QuerySwitcherProps = {
1719
data?: unknown;
1820
customError?: ReactNode;
1921
customEmptyDataComponent?: JSX.Element;
22+
error?: Error | null;
2023
};
2124

2225
const QuerySwitcher = ({
@@ -26,6 +29,7 @@ const QuerySwitcher = ({
2629
data,
2730
customError,
2831
customEmptyDataComponent,
32+
error,
2933
}: QuerySwitcherProps): JSX.Element => {
3034
if (!status) {
3135
return customEmptyDataComponent ?? <Fragment />;
@@ -40,7 +44,20 @@ const QuerySwitcher = ({
4044
<FormattedMessage id="global.loading" />
4145
</Skeleton>
4246
);
43-
case 'error':
47+
case 'error': {
48+
let errorStatusCode: number | undefined;
49+
const errorMessage = error?.message;
50+
if (errorMessage) {
51+
const splittedError = errorMessage.split(':');
52+
if (splittedError.length > 1) {
53+
errorStatusCode = parseInt(splittedError[0]);
54+
}
55+
}
56+
57+
if (errorStatusCode === HttpStatusCode.Ok) {
58+
return <>{children}</>;
59+
}
60+
4461
return (
4562
<>
4663
{customError ?? (
@@ -50,6 +67,7 @@ const QuerySwitcher = ({
5067
)}
5168
</>
5269
);
70+
}
5371
}
5472

5573
if (!data) {

dashboard/src/components/TreeListingPage/TreeListingPage.tsx

Lines changed: 19 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,8 @@ import type {
88

99
import { useTreeTable, useTreeTableFast } from '@/api/tree';
1010

11-
import QuerySwitcher from '@/components/QuerySwitcher/QuerySwitcher';
12-
1311
import { Toaster } from '@/components/ui/toaster';
1412

15-
import { MemoizedSectionError } from '@/components/DetailsPages/SectionError';
16-
1713
import { matchesRegexOrIncludes } from '@/lib/string';
1814

1915
import { MemoizedKcidevFooter } from '@/components/Footer/KcidevFooter';
@@ -38,7 +34,7 @@ const TreeListingPage = ({ inputFilter }: ITreeListingPage): JSX.Element => {
3834
error: fastError,
3935
isLoading: isFastLoading,
4036
} = useTreeTableFast();
41-
const { data, error, isLoading } = useTreeTable({
37+
const { data, error, status, isLoading } = useTreeTable({
4238
enabled: fastStatus === 'success' && !!fastData,
4339
});
4440

@@ -108,28 +104,30 @@ const TreeListingPage = ({ inputFilter }: ITreeListingPage): JSX.Element => {
108104
[],
109105
);
110106

111-
if (error) {
112-
return <div>Error: {error.message}</div>;
113-
}
107+
const hasPartialFailure = fastStatus === 'success' && status === 'error';
108+
// Only show error in QuerySwitcher if the first or both queries fail
109+
const actualError = hasPartialFailure ? null : fastError || error;
110+
const actualStatus = hasPartialFailure
111+
? fastStatus
112+
: actualError
113+
? 'error'
114+
: fastStatus ?? status;
114115

115116
return (
116-
<QuerySwitcher
117-
status={fastStatus}
118-
data={fastData}
119-
customError={
120-
<MemoizedSectionError
121-
isLoading={isFastLoading}
122-
errorMessage={fastError?.message}
123-
emptyLabel="treeListing.notFound"
124-
/>
125-
}
126-
>
117+
<>
127118
<Toaster />
128119
<div className="flex flex-col gap-6">
129-
<TreeTable treeTableRows={listItems} />
120+
<TreeTable
121+
treeTableRows={listItems}
122+
status={actualStatus}
123+
queryData={fastData}
124+
error={actualError}
125+
isLoading={isFastLoading}
126+
showStatusUnavailable={hasPartialFailure}
127+
/>
130128
</div>
131129
{kcidevComponent}
132-
</QuerySwitcher>
130+
</>
133131
);
134132
};
135133

dashboard/src/components/TreeListingPage/TreeTable.tsx

Lines changed: 55 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
} from '@tanstack/react-table';
1515

1616
import { useCallback, useMemo, useState, type JSX } from 'react';
17+
import type { UseQueryResult } from '@tanstack/react-query';
1718

1819
import { FormattedMessage } from 'react-intl';
1920

@@ -60,6 +61,9 @@ import { valueOrEmpty } from '@/lib/string';
6061
import { PinnedTrees } from '@/utils/constants/tables';
6162
import { makeTreeIdentifierKey } from '@/utils/trees';
6263

64+
import QuerySwitcher from '@/components/QuerySwitcher/QuerySwitcher';
65+
import { MemoizedSectionError } from '@/components/DetailsPages/SectionError';
66+
6367
const getLinkProps = (
6468
row: Row<TreeTableBody>,
6569
origin: string,
@@ -154,7 +158,10 @@ const getLinkProps = (
154158
};
155159
};
156160

157-
const getColumns = (origin: string): ColumnDef<TreeTableBody>[] => {
161+
const getColumns = (
162+
origin: string,
163+
showStatusUnavailable?: boolean,
164+
): ColumnDef<TreeTableBody>[] => {
158165
return [
159166
{
160167
accessorKey: 'tree_name',
@@ -263,6 +270,8 @@ const getColumns = (origin: string): ColumnDef<TreeTableBody>[] => {
263270
},
264271
})}
265272
/>
273+
) : showStatusUnavailable ? (
274+
<span>-</span>
266275
) : (
267276
<FormattedMessage id="global.loading" defaultMessage="Loading..." />
268277
);
@@ -308,6 +317,8 @@ const getColumns = (origin: string): ColumnDef<TreeTableBody>[] => {
308317
},
309318
})}
310319
/>
320+
) : showStatusUnavailable ? (
321+
<span>-</span>
311322
) : (
312323
<FormattedMessage id="global.loading" defaultMessage="Loading..." />
313324
);
@@ -353,6 +364,8 @@ const getColumns = (origin: string): ColumnDef<TreeTableBody>[] => {
353364
},
354365
})}
355366
/>
367+
) : showStatusUnavailable ? (
368+
<span>-</span>
356369
) : (
357370
<FormattedMessage id="global.loading" defaultMessage="Loading..." />
358371
);
@@ -366,9 +379,21 @@ const getColumns = (origin: string): ColumnDef<TreeTableBody>[] => {
366379

367380
interface ITreeTable {
368381
treeTableRows: TreeTableBody[];
382+
status?: UseQueryResult['status'];
383+
queryData?: unknown;
384+
error?: Error | null;
385+
isLoading?: boolean;
386+
showStatusUnavailable?: boolean;
369387
}
370388

371-
export function TreeTable({ treeTableRows }: ITreeTable): JSX.Element {
389+
export function TreeTable({
390+
treeTableRows,
391+
status,
392+
queryData,
393+
error,
394+
isLoading,
395+
showStatusUnavailable,
396+
}: ITreeTable): JSX.Element {
372397
const { origin: unsafeOrigin, listingSize } = useSearch({ strict: false });
373398
const origin = unsafeOrigin ?? DEFAULT_ORIGIN;
374399
const navigate = useNavigate({ from: '/tree' });
@@ -407,7 +432,10 @@ export function TreeTable({ treeTableRows }: ITreeTable): JSX.Element {
407432
});
408433
}, [treeTableRows]);
409434

410-
const columns = useMemo(() => getColumns(origin), [origin]);
435+
const columns = useMemo(
436+
() => getColumns(origin, showStatusUnavailable),
437+
[origin, showStatusUnavailable],
438+
);
411439

412440
const table = useReactTable({
413441
data: orderedData,
@@ -468,7 +496,7 @@ export function TreeTable({ treeTableRows }: ITreeTable): JSX.Element {
468496
) : (
469497
<TableRow>
470498
<TableCell colSpan={columns.length} className="h-24 text-center">
471-
<FormattedMessage id="global.noResults" />
499+
<FormattedMessage id="treeListing.notFound" />
472500
</TableCell>
473501
</TableRow>
474502
);
@@ -506,9 +534,29 @@ export function TreeTable({ treeTableRows }: ITreeTable): JSX.Element {
506534
<PaginationButtons table={table} className="pl-4" />
507535
</div>
508536
</div>
509-
<BaseTable headerComponents={tableHeaders}>
510-
<TableBody>{tableBody}</TableBody>
511-
</BaseTable>
537+
{showStatusUnavailable && (
538+
<div className="rounded-md border border-red-500 bg-red-50 p-4 text-red-800">
539+
<p className="text-sm">
540+
<FormattedMessage id="treeListing.statusUnavailable" />
541+
</p>
542+
</div>
543+
)}
544+
<QuerySwitcher
545+
status={status}
546+
data={queryData}
547+
error={error}
548+
customError={
549+
<MemoizedSectionError
550+
isLoading={isLoading}
551+
errorMessage={error?.message}
552+
emptyLabel="treeListing.notFound"
553+
/>
554+
}
555+
>
556+
<BaseTable headerComponents={tableHeaders}>
557+
<TableBody>{tableBody}</TableBody>
558+
</BaseTable>
559+
</QuerySwitcher>
512560
<PaginationInfo
513561
table={table}
514562
intlLabel="global.trees"

dashboard/src/locales/messages/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,8 @@ export const messages = {
348348
'treeDetails.validBuilds': 'Success builds',
349349
'treeListing.description': 'List of trees for kernel builds and tests',
350350
'treeListing.notFound': 'No tree information available',
351+
'treeListing.statusUnavailable':
352+
'Error: Unable to load tree status information. Displaying basic information only.',
351353
'treeListing.title': 'Tree Listing ― KCI Dashboard',
352354
},
353355
};

dashboard/src/pages/Hardware/HardwareListingPage.tsx

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@ import { roundToNearestMinutes } from 'date-fns';
33

44
import { useSearch } from '@tanstack/react-router';
55

6-
import QuerySwitcher from '@/components/QuerySwitcher/QuerySwitcher';
7-
86
import { Toaster } from '@/components/ui/toaster';
97

108
import type { HardwareItem } from '@/types/hardware';
@@ -13,8 +11,6 @@ import { useHardwareListing } from '@/api/hardware';
1311

1412
import { dateObjectToTimestampInSeconds, daysToSeconds } from '@/utils/date';
1513

16-
import { MemoizedSectionError } from '@/components/DetailsPages/SectionError';
17-
1814
import type { RequiredStatusCount, StatusCount } from '@/types/general';
1915

2016
import {
@@ -144,27 +140,21 @@ const HardwareListingPage = ({
144140
);
145141

146142
return (
147-
<QuerySwitcher
148-
status={status}
149-
data={data}
150-
customError={
151-
<MemoizedSectionError
152-
isLoading={isLoading}
153-
errorMessage={error?.message}
154-
emptyLabel="hardwareListing.notFound"
155-
/>
156-
}
157-
>
143+
<>
158144
<Toaster />
159145
<div className="flex flex-col gap-6">
160146
<HardwareTable
161147
treeTableRows={listItems}
162148
endTimestampInSeconds={endTimestampInSeconds}
163149
startTimestampInSeconds={startTimestampInSeconds}
150+
status={status}
151+
queryData={data}
152+
error={error}
153+
isLoading={isLoading}
164154
/>
165155
</div>
166156
{kcidevComponent}
167-
</QuerySwitcher>
157+
</>
168158
);
169159
};
170160

0 commit comments

Comments
 (0)