Skip to content

Commit 00fdd4e

Browse files
Show partial domain fetch results + error banner for failed fetches (#692)
* Add error banner for domains page * Rename domain test to domains test * Fix tests to improve coverage * Fix snapshots * Remove snapshot tests * Add config for error banner text * Mock out error banner config
1 parent 0291802 commit 00fdd4e

File tree

11 files changed

+162
-52
lines changed

11 files changed

+162
-52
lines changed

src/config/theme/theme-light.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const themeLight = {
1212
colors: {
1313
borderOpaque: '#F3F3F3',
1414
backgroundSecondary: '#F3F3F3',
15+
backgroundWarningLight: '#FDF2DC',
1516
},
1617
} satisfies DeepPartial<MakeExtendable<Theme>>;
1718

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { MdWarning } from 'react-icons/md';
2+
3+
import { type Props } from '../domains-page-error-banner/domains-page-error-banner.types';
4+
5+
const domainsPageErrorBannerConfig = {
6+
icon: MdWarning,
7+
getErrorMessage: ({ failedClusters }: Props) =>
8+
`Failed to fetch domains for following clusters: ${failedClusters.join(', ')}`,
9+
};
10+
11+
export default domainsPageErrorBannerConfig;
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import React from 'react';
2+
3+
import { render, screen } from '@/test-utils/rtl';
4+
5+
import DomainsPageErrorBanner from '../domains-page-error-banner';
6+
import { type Props } from '../domains-page-error-banner.types';
7+
8+
jest.mock('../../config/domains-page-error-banner.config', () => ({
9+
icon: () => <div data-testid="mock-error-icon" />,
10+
getErrorMessage: ({ failedClusters }: Props) =>
11+
`Mock message for clusters failure: ${failedClusters.join(', ')}`,
12+
}));
13+
14+
describe(DomainsPageErrorBanner.name, () => {
15+
it('should render if there are failed clusters', async () => {
16+
render(
17+
<DomainsPageErrorBanner failedClusters={['cluster_1', 'cluster_2']} />
18+
);
19+
20+
expect(await screen.findByTestId('mock-error-icon')).toBeInTheDocument();
21+
expect(
22+
await screen.findByText(
23+
'Mock message for clusters failure: cluster_1, cluster_2'
24+
)
25+
).toBeInTheDocument();
26+
});
27+
28+
it('should not render anything if no clusters failed', async () => {
29+
render(<DomainsPageErrorBanner failedClusters={[]} />);
30+
31+
expect(screen.queryByTestId('mock-error-icon')).toBeNull();
32+
expect(screen.queryByText(/Mock message for clusters failure/)).toBeNull();
33+
});
34+
});
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { type Theme } from 'baseui';
2+
import type { BannerOverrides } from 'baseui/banner';
3+
import { type StyleObject } from 'styletron-react';
4+
5+
export const overrides = {
6+
banner: {
7+
Root: {
8+
style: ({ $theme }: { $theme: Theme }): StyleObject => ({
9+
backgroundColor: $theme.colors.backgroundWarningLight,
10+
marginLeft: 0,
11+
marginRight: 0,
12+
}),
13+
},
14+
Message: {
15+
style: ({ $theme }: { $theme: Theme }): StyleObject => ({
16+
...$theme.typography.LabelSmall,
17+
}),
18+
},
19+
} satisfies BannerOverrides,
20+
};
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
'use client';
2+
import React from 'react';
3+
4+
import { Banner, KIND, HIERARCHY } from 'baseui/banner';
5+
6+
import PageSection from '@/components/page-section/page-section';
7+
8+
import domainsPageErrorBannerConfig from '../config/domains-page-error-banner.config';
9+
10+
import { overrides } from './domains-page-error-banner.styles';
11+
import { type Props } from './domains-page-error-banner.types';
12+
13+
export default function DomainsPageErrorBanner({ failedClusters }: Props) {
14+
if (failedClusters.length === 0) return null;
15+
16+
return (
17+
<PageSection>
18+
<Banner
19+
hierarchy={HIERARCHY.low}
20+
kind={KIND.negative}
21+
artwork={{ icon: domainsPageErrorBannerConfig.icon }}
22+
overrides={overrides.banner}
23+
>
24+
{domainsPageErrorBannerConfig.getErrorMessage({ failedClusters })}
25+
</Banner>
26+
</PageSection>
27+
);
28+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export type Props = {
2+
failedClusters: Array<string>;
3+
};

src/views/domains-page/domains-page-title/__tests__/domain-page-title.test.tsx renamed to src/views/domains-page/domains-page-title/__tests__/domains-page-title.test.tsx

File renamed without changes.
Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use client';
2-
import { Cell, Grid } from 'baseui/layout-grid';
32
import { LabelLarge } from 'baseui/typography';
43

4+
import PageSection from '@/components/page-section/page-section';
55
import useStyletronClasses from '@/hooks/use-styletron-classes';
66

77
import { cssStyles } from './domains-page-title.styles';
@@ -11,15 +11,11 @@ export default function DomainsPageTitle({ countBadge }: Props) {
1111
const { cls } = useStyletronClasses(cssStyles);
1212

1313
return (
14-
<section>
15-
<Grid>
16-
<Cell span={12}>
17-
<div className={cls.titleContainer}>
18-
<LabelLarge>All domains</LabelLarge>
19-
{countBadge}
20-
</div>
21-
</Cell>
22-
</Grid>
23-
</section>
14+
<PageSection>
15+
<div className={cls.titleContainer}>
16+
<LabelLarge>All domains</LabelLarge>
17+
{countBadge}
18+
</div>
19+
</PageSection>
2420
);
2521
}

src/views/domains-page/domains-page.tsx

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ import React, { Suspense } from 'react';
22

33
import AsyncPropsLoader from '@/components/async-props-loader/async-props-loader';
44
import SectionLoadingIndicator from '@/components/section-loading-indicator/section-loading-indicator';
5-
import DomainsPageFilters from '@/views/domains-page/domains-page-filters/domains-page-filters';
6-
import DomainsPageTitle from '@/views/domains-page/domains-page-title/domains-page-title';
7-
import DomainsPageTitleBadge from '@/views/domains-page/domains-page-title-badge/domains-page-title-badge';
8-
import DomainsTable from '@/views/domains-page/domains-table/domains-table';
9-
import { getCachedAllDomains } from '@/views/domains-page/helpers/get-all-domains';
5+
6+
import DomainsPageErrorBanner from './domains-page-error-banner/domains-page-error-banner';
7+
import DomainsPageFilters from './domains-page-filters/domains-page-filters';
8+
import DomainsPageTitle from './domains-page-title/domains-page-title';
9+
import DomainsPageTitleBadge from './domains-page-title-badge/domains-page-title-badge';
10+
import DomainsTable from './domains-table/domains-table';
11+
import { getCachedAllDomains } from './helpers/get-all-domains';
1012

1113
export default async function DomainsPage() {
1214
return (
@@ -25,6 +27,15 @@ export default async function DomainsPage() {
2527
}
2628
/>
2729
<DomainsPageFilters />
30+
<Suspense>
31+
<AsyncPropsLoader
32+
component={DomainsPageErrorBanner}
33+
getAsyncProps={async () => {
34+
const res = await getCachedAllDomains();
35+
return { failedClusters: res.failedClusters };
36+
}}
37+
/>
38+
</Suspense>
2839
<Suspense fallback={<SectionLoadingIndicator />}>
2940
<AsyncPropsLoader
3041
component={DomainsTable}

src/views/domains-page/domains-table/domains-table.tsx

Lines changed: 32 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
'use client';
22
import React, { useEffect, useMemo, useState } from 'react';
33

4-
import { Cell, Grid } from 'baseui/layout-grid';
54
import { useInView } from 'react-intersection-observer';
65

6+
import PageSection from '@/components/page-section/page-section';
77
import Table from '@/components/table/table';
88
import usePageQueryParams from '@/hooks/use-page-query-params/use-page-query-params';
99
import useStyletronClasses from '@/hooks/use-styletron-classes';
@@ -77,39 +77,37 @@ function DomainsTable({
7777
});
7878

7979
return (
80-
<section className={cls.tableContainer}>
81-
<Grid>
82-
<Cell span={12}>
83-
<Table
84-
data={paginatedDomains}
85-
columns={tableColumns}
86-
shouldShowResults={true}
87-
onSort={(columnID) =>
88-
setQueryParams({
89-
sortColumn: columnID,
90-
sortOrder: toggleSortOrder({
91-
currentSortColumn: queryParams.sortColumn,
92-
currentSortOrder: queryParams.sortOrder,
93-
newSortColumn: columnID,
94-
}),
95-
})
96-
}
97-
sortColumn={queryParams.sortColumn}
98-
sortOrder={queryParams.sortOrder as SortOrder}
99-
endMessage={
100-
<DomainsTableEndMessage
101-
key={visibleListItems}
102-
canLoadMoreResults={
103-
paginatedDomains.length < sortedDomains.length
104-
}
105-
hasSearchResults={sortedDomains.length > 0}
106-
infiniteScrollTargetRef={loadMoreRef}
107-
/>
108-
}
109-
/>
110-
</Cell>
111-
</Grid>
112-
</section>
80+
<PageSection>
81+
<section className={cls.tableContainer}>
82+
<Table
83+
data={paginatedDomains}
84+
columns={tableColumns}
85+
shouldShowResults={true}
86+
onSort={(columnID) =>
87+
setQueryParams({
88+
sortColumn: columnID,
89+
sortOrder: toggleSortOrder({
90+
currentSortColumn: queryParams.sortColumn,
91+
currentSortOrder: queryParams.sortOrder,
92+
newSortColumn: columnID,
93+
}),
94+
})
95+
}
96+
sortColumn={queryParams.sortColumn}
97+
sortOrder={queryParams.sortOrder as SortOrder}
98+
endMessage={
99+
<DomainsTableEndMessage
100+
key={visibleListItems}
101+
canLoadMoreResults={
102+
paginatedDomains.length < sortedDomains.length
103+
}
104+
hasSearchResults={sortedDomains.length > 0}
105+
infiniteScrollTargetRef={loadMoreRef}
106+
/>
107+
}
108+
/>
109+
</section>
110+
</PageSection>
113111
);
114112
}
115113

0 commit comments

Comments
 (0)