Skip to content

Commit 29ccff0

Browse files
committed
fix(heureka): gracefully handle fatal errors
1 parent 3942ed0 commit 29ccff0

24 files changed

Lines changed: 431 additions & 342 deletions

File tree

.changeset/proud-facts-happen.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@cloudoperators/juno-app-heureka": patch
3+
"@cloudoperators/juno-app-greenhouse": patch
4+
---
5+
6+
Heureka now gracefully handles fata errors which would prevent app layout from being invisible.

apps/heureka/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
"clean:cache": "rm -rf .turbo"
2929
},
3030
"dependencies": {
31-
"@cloudoperators/juno-messages-provider": "workspace:*",
3231
"@cloudoperators/juno-ui-components": "workspace:*",
3332
"@cloudoperators/juno-url-state-provider": "workspace:*",
3433
"@tanstack/react-query": "5.89.0",

apps/heureka/src/App.tsx

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import React, { StrictMode } from "react"
77
import { ApolloProvider } from "@apollo/client"
88
import { createRouter, RouterProvider, createHashHistory, createBrowserHistory } from "@tanstack/react-router"
99
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
10-
import { AppShellProvider } from "@cloudoperators/juno-ui-components"
10+
import { AppShell, AppShellProvider, PageHeader } from "@cloudoperators/juno-ui-components"
1111
import { encodeV2, decodeV2 } from "@cloudoperators/juno-url-state-provider"
1212
import styles from "./styles.css?inline"
1313
import { ErrorBoundary } from "./components/common/ErrorBoundary"
@@ -89,11 +89,15 @@ const App = (props: AppProps) => {
8989
<AppShellProvider theme={`${props.theme ? props.theme : "theme-dark"}`}>
9090
{/* load styles inside the shadow dom */}
9191
<style>{styles.toString()}</style>
92-
<ErrorBoundary>
93-
<StrictMode>
94-
<RouterProvider basepath={props.basePath || "/"} router={router} />
95-
</StrictMode>
96-
</ErrorBoundary>
92+
<StrictMode>
93+
<AppShell embedded={props.embedded} pageHeader={<PageHeader heading="Heureka" />}>
94+
<ErrorBoundary>
95+
<StrictMode>
96+
<RouterProvider basepath={props.basePath || "/"} router={router} />
97+
</StrictMode>
98+
</ErrorBoundary>
99+
</AppShell>
100+
</StrictMode>
97101
</AppShellProvider>
98102
</ApolloProvider>
99103
</QueryClientProvider>

apps/heureka/src/components/Service/ImageVersionDetailsPanel/ImageVersionIssuesList/index.tsx

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,16 @@ import {
1111
DataGridHeadCell,
1212
Icon,
1313
Stack,
14-
Spinner,
1514
SearchInput,
1615
ContentHeading,
1716
} from "@cloudoperators/juno-ui-components"
18-
import { EmptyDataGridRow } from "../../../common/EmptyDataGridRow"
1917
import { getNormalizedImageVersionIssuesResponse, ServiceImageVersion } from "../../../Services/utils"
2018
import { fetchImageVersionIssues } from "../../../../api/fetchImageVersionIssues"
2119
import { IssuesDataRows } from "./IssuesDataRows"
2220
import { CursorPagination } from "../../../common/CursorPagination"
21+
import { ErrorBoundary } from "../../../common/ErrorBoundary"
22+
import { getErrorDataRowComponent } from "../../../common/getErrorDataRow"
23+
import { LoadingDataRow } from "../../../common/LoadingDataRow"
2324

2425
type ImageVersionIssuesListProps = {
2526
service: string
@@ -64,28 +65,27 @@ export const ImageVersionIssuesList = ({ service, imageVersion }: ImageVersionIs
6465
</DataGridRow>
6566

6667
{issuesPromise && (
67-
<Suspense
68-
fallback={
69-
<EmptyDataGridRow colSpan={4}>
70-
<Stack gap="2" alignment="center">
71-
<div>Loading vulnerabilities</div>
72-
<Spinner variant="primary"></Spinner>
73-
</Stack>
74-
</EmptyDataGridRow>
75-
}
68+
<ErrorBoundary
69+
displayErrorMessage
70+
fallbackRender={getErrorDataRowComponent({ colspan: 4 })}
71+
resetKeys={[issuesPromise]}
7672
>
77-
<IssuesDataRows issuesPromise={issuesPromise} />
78-
</Suspense>
73+
<Suspense fallback={<LoadingDataRow colSpan={4} />}>
74+
<IssuesDataRows issuesPromise={issuesPromise} />
75+
</Suspense>
76+
</ErrorBoundary>
7977
)}
8078
</DataGrid>
8179
{issuesPromise && (
82-
<Suspense>
83-
<CursorPagination
84-
dataPromise={issuesPromise}
85-
dataNormalizationMethod={getNormalizedImageVersionIssuesResponse}
86-
goToPage={setPageCursor}
87-
/>
88-
</Suspense>
80+
<ErrorBoundary resetKeys={[issuesPromise]}>
81+
<Suspense>
82+
<CursorPagination
83+
dataPromise={issuesPromise}
84+
dataNormalizationMethod={getNormalizedImageVersionIssuesResponse}
85+
goToPage={setPageCursor}
86+
/>
87+
</Suspense>
88+
</ErrorBoundary>
8989
)}
9090
</>
9191
)

apps/heureka/src/components/Service/ImageVersionDetailsPanel/index.tsx

Lines changed: 52 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,9 @@ import {
1313
DataGridRow,
1414
DataGridHeadCell,
1515
DataGridCell,
16-
Container,
1716
} from "@cloudoperators/juno-ui-components"
1817
import { useNavigate, useParams, useSearch } from "@tanstack/react-router"
1918
import { ApolloQueryResult } from "@apollo/client"
20-
import { MessagesProvider, Messages } from "@cloudoperators/juno-messages-provider"
2119
import { getNormalizedImageVersionsResponse, ServiceImageVersion } from "../../Services/utils"
2220
import ImageVersionOccurrences from "./ImageVersionOccurrences"
2321
import { IssueCountsPerSeverityLevel } from "../../common/IssueCountsPerSeverityLevel"
@@ -41,68 +39,58 @@ export const ImageVersionDetailsPanel = ({ imageVersionsPromise }: ImageVersionD
4139
}
4240

4341
return (
44-
<MessagesProvider>
45-
<Panel
46-
heading={`Image ${imageVersion.repository} Information`}
47-
opened={!!service}
48-
onClose={() =>
49-
navigate({
50-
to: "/services/$service",
51-
params: { service },
52-
})
53-
}
54-
size="large"
55-
>
56-
<PanelBody>
57-
<Container py px={false}>
58-
<Messages />
59-
</Container>
60-
<DataGrid columns={2} gridColumnTemplate="20% auto">
61-
<DataGridRow>
62-
<DataGridHeadCell>Details</DataGridHeadCell>
63-
<DataGridCell>
64-
<Stack gap="1" direction="horizontal" wrap>
65-
<Pill
66-
pillKey="tag"
67-
pillKeyLabel="tag"
68-
pillValue={imageVersion.tag}
69-
pillValueLabel={imageVersion.tag}
70-
/>
71-
<Pill
72-
pillKey="repository"
73-
pillKeyLabel="repository"
74-
pillValue={imageVersion.repository}
75-
pillValueLabel={imageVersion.repository}
76-
/>
77-
<Pill
78-
pillKey="version"
79-
pillKeyLabel="version"
80-
pillValue={imageVersion.version}
81-
pillValueLabel={imageVersion.version}
82-
/>
83-
</Stack>
84-
</DataGridCell>
85-
</DataGridRow>
86-
<DataGridRow>
87-
<DataGridHeadCell>Vulnerabilities Counts</DataGridHeadCell>
88-
<DataGridCell>
89-
<IssueCountsPerSeverityLevel counts={imageVersion.issueCounts} />
90-
</DataGridCell>
91-
</DataGridRow>
92-
<DataGridRow>
93-
<DataGridHeadCell className="whitespace-nowrap">{`Occurrences (${imageVersion.componentInstancesCount || 0})`}</DataGridHeadCell>
94-
<DataGridCell>
95-
<ImageVersionOccurrences imageVersion={imageVersion} />
96-
</DataGridCell>
97-
</DataGridRow>
98-
</DataGrid>
42+
<Panel
43+
heading={`Image ${imageVersion.repository} Information`}
44+
opened={!!service}
45+
onClose={() =>
46+
navigate({
47+
to: "/services/$service",
48+
params: { service },
49+
})
50+
}
51+
size="large"
52+
>
53+
<PanelBody>
54+
<DataGrid columns={2} gridColumnTemplate="20% auto">
55+
<DataGridRow>
56+
<DataGridHeadCell>Details</DataGridHeadCell>
57+
<DataGridCell>
58+
<Stack gap="1" direction="horizontal" wrap>
59+
<Pill pillKey="tag" pillKeyLabel="tag" pillValue={imageVersion.tag} pillValueLabel={imageVersion.tag} />
60+
<Pill
61+
pillKey="repository"
62+
pillKeyLabel="repository"
63+
pillValue={imageVersion.repository}
64+
pillValueLabel={imageVersion.repository}
65+
/>
66+
<Pill
67+
pillKey="version"
68+
pillKeyLabel="version"
69+
pillValue={imageVersion.version}
70+
pillValueLabel={imageVersion.version}
71+
/>
72+
</Stack>
73+
</DataGridCell>
74+
</DataGridRow>
75+
<DataGridRow>
76+
<DataGridHeadCell>Vulnerabilities Counts</DataGridHeadCell>
77+
<DataGridCell>
78+
<IssueCountsPerSeverityLevel counts={imageVersion.issueCounts} />
79+
</DataGridCell>
80+
</DataGridRow>
81+
<DataGridRow>
82+
<DataGridHeadCell className="whitespace-nowrap">{`Occurrences (${imageVersion.componentInstancesCount || 0})`}</DataGridHeadCell>
83+
<DataGridCell>
84+
<ImageVersionOccurrences imageVersion={imageVersion} />
85+
</DataGridCell>
86+
</DataGridRow>
87+
</DataGrid>
9988

100-
{/* Second Section: Issues List */}
101-
{service && selectedImageVersion && imageVersion && (
102-
<ImageVersionIssuesList service={service} imageVersion={imageVersion} />
103-
)}
104-
</PanelBody>
105-
</Panel>
106-
</MessagesProvider>
89+
{/* Second Section: Issues List */}
90+
{service && selectedImageVersion && imageVersion && (
91+
<ImageVersionIssuesList service={service} imageVersion={imageVersion} />
92+
)}
93+
</PanelBody>
94+
</Panel>
10795
)
10896
}

apps/heureka/src/components/Service/index.tsx

Lines changed: 28 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@
66
import React, { Suspense, useEffect, useState } from "react"
77
import { useLoaderData, useNavigate, useParams, useRouteContext, useSearch } from "@tanstack/react-router"
88
import { Spinner } from "@cloudoperators/juno-ui-components"
9-
import { MessagesProvider, Messages } from "@cloudoperators/juno-messages-provider"
9+
import { ApolloQueryResult } from "@apollo/client"
1010
import { ServiceImageVersions } from "../common/ServiceImageVersions"
1111
import { ImageVersionDetailsPanel } from "./ImageVersionDetailsPanel"
1212
import { ServiceDetails } from "./ServiceDetails"
1313
import { fetchImageVersions } from "../../api/fetchImageVersions"
1414
import { GetServiceImageVersionsQuery } from "../../generated/graphql"
15-
import { ApolloQueryResult } from "@apollo/client"
15+
import { ErrorBoundary } from "../common/ErrorBoundary"
1616

1717
export const Service = () => {
1818
const navigate = useNavigate()
@@ -37,28 +37,33 @@ export const Service = () => {
3737
}, [pageCursor])
3838

3939
return (
40-
<MessagesProvider>
41-
<Messages className="mb-4" />
42-
<Suspense fallback={<Spinner className="mt-4" />}>
43-
<ServiceDetails servicePromise={servicePromise} />
44-
</Suspense>
40+
<>
41+
<ErrorBoundary displayErrorMessage>
42+
<Suspense fallback={<Spinner className="mt-4" />}>
43+
<ServiceDetails servicePromise={servicePromise} />
44+
</Suspense>
45+
</ErrorBoundary>
4546
{imageVersionsPromise && (
46-
<ServiceImageVersions
47-
selectedImageVersion={imageVersion}
48-
imageVersionsPromise={imageVersionsPromise}
49-
onImageVersionItemClick={(iv) => {
50-
navigate({
51-
to: "/services/$service",
52-
params: { service },
53-
search: { imageVersion: iv.version },
54-
})
55-
}}
56-
goToPage={setPageCursor}
57-
/>
47+
<>
48+
<ServiceImageVersions
49+
selectedImageVersion={imageVersion}
50+
imageVersionsPromise={imageVersionsPromise}
51+
onImageVersionItemClick={(iv) => {
52+
navigate({
53+
to: "/services/$service",
54+
params: { service },
55+
search: { imageVersion: iv.version },
56+
})
57+
}}
58+
goToPage={setPageCursor}
59+
/>
60+
<ErrorBoundary>
61+
<Suspense>
62+
<ImageVersionDetailsPanel imageVersionsPromise={imageVersionsPromise} />
63+
</Suspense>
64+
</ErrorBoundary>
65+
</>
5866
)}
59-
<Suspense>
60-
{imageVersionsPromise && <ImageVersionDetailsPanel imageVersionsPromise={imageVersionsPromise} />}
61-
</Suspense>
62-
</MessagesProvider>
67+
</>
6368
)
6469
}

apps/heureka/src/components/Services/AllServicesIssuesCount.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { Spinner, Stack } from "@cloudoperators/juno-ui-components"
1010
import { IssueCountsPerSeverityLevel } from "../common/IssueCountsPerSeverityLevel"
1111
import { GetServicesQuery } from "../../generated/graphql"
1212
import { getNormalizedServicesResponse } from "./utils"
13+
import { ErrorBoundary } from "../common/ErrorBoundary"
1314

1415
type AllServicesIssuesCountProps = {
1516
servicesPromise: Promise<ApolloQueryResult<GetServicesQuery>>
@@ -33,10 +34,12 @@ const IssuesCount = ({ servicesPromise }: AllServicesIssuesCountProps) => {
3334
export const AllServicesIssuesCount = () => {
3435
const { servicesPromise } = useLoaderData({ from: "/services/" })
3536
return (
36-
<Stack className="bg-theme-background-lvl-1 py-1.5 px-4 my-px text-theme-light" alignment="center">
37-
<Suspense fallback={<Spinner size="small" />}>
38-
<IssuesCount servicesPromise={servicesPromise} />
39-
</Suspense>
40-
</Stack>
37+
<ErrorBoundary>
38+
<Stack className="bg-theme-background-lvl-1 py-1.5 px-4 my-px text-theme-light" alignment="center">
39+
<Suspense fallback={<Spinner size="small" />}>
40+
<IssuesCount servicesPromise={servicesPromise} />
41+
</Suspense>
42+
</Stack>
43+
</ErrorBoundary>
4144
)
4245
}

apps/heureka/src/components/Services/ServicesList/ServicePanel.tsx

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@
66
import React, { useCallback } from "react"
77
import { useNavigate, useRouteContext, useSearch } from "@tanstack/react-router"
88
import { Panel, PanelBody } from "@cloudoperators/juno-ui-components"
9-
import { MessagesProvider } from "@cloudoperators/juno-messages-provider"
109
import { capitalizeFirstLetter } from "../../../utils"
1110
import { ServiceImageVersions } from "../../common/ServiceImageVersions"
1211
import { ServiceImageVersion } from "../utils"
1312
import { fetchImageVersions } from "../../../api/fetchImageVersions"
13+
import { ErrorBoundary } from "../../common/ErrorBoundary"
1414

1515
export const ServicePanel = () => {
1616
const navigate = useNavigate()
@@ -52,14 +52,14 @@ export const ServicePanel = () => {
5252
)
5353

5454
return (
55-
<MessagesProvider>
56-
<Panel
57-
heading={!!service ? `${capitalizeFirstLetter(service)} Overview` : undefined}
58-
opened={!!service}
59-
onClose={closeServiceOverviewPanel}
60-
size="large"
61-
>
62-
<PanelBody>
55+
<Panel
56+
heading={!!service ? `${capitalizeFirstLetter(service)} Overview` : undefined}
57+
opened={!!service}
58+
onClose={closeServiceOverviewPanel}
59+
size="large"
60+
>
61+
<PanelBody>
62+
<ErrorBoundary>
6363
{service && imageVersionsPromise && (
6464
<ServiceImageVersions
6565
displayActions
@@ -68,8 +68,8 @@ export const ServicePanel = () => {
6868
goToPage={setCurrentPageCursor}
6969
/>
7070
)}
71-
</PanelBody>
72-
</Panel>
73-
</MessagesProvider>
71+
</ErrorBoundary>
72+
</PanelBody>
73+
</Panel>
7474
)
7575
}

0 commit comments

Comments
 (0)