Skip to content
Open
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
5 changes: 5 additions & 0 deletions .changeset/dull-taxis-walk.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@cloudoperators/juno-app-heureka": major
---

Adds false positive remediation action to image details page with create and delete functionality. Introduces image version details page to display deployment locations for each image version.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ vite.config.*.timestamp-*
# pnpm local package store (if configured locally)
.pnpm-store/

# TanStack Router cache directory
.tanstack/
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this really needed? This is the first app ignoring this folder...


# act artifacts needed for testing workflows
.secrets
.env
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import { Message } from "@cloudoperators/juno-ui-components"

const ErrorFallback = ({ error }: FallbackProps) => (
<Message text={(error as Error)?.message || "An error occurred"} variant="danger" />
<Message text={error?.message || "An error occurred"} variant="danger" />

Check failure on line 11 in apps/carbon/src/components/ErrorBoundary/ErrorFallback.tsx

View workflow job for this annotation

GitHub Actions / lint-check

Unsafe assignment of an error typed value
)

export default ErrorFallback
3 changes: 3 additions & 0 deletions apps/heureka/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,6 @@ npm-debug.log*
.env.development.local
.env.test.local
.env.production.local

# TanStack Router cache directory
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here. How is this directory being created?

.tanstack/
5 changes: 5 additions & 0 deletions apps/heureka/codegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ const config: CodegenConfig = {
withHooks: false,
withHOC: false,
withComponent: false,
// Apollo Client v4 removed several legacy exported helper types (e.g. QueryResult, MutationFunction).
// Prevent codegen from generating those helper type aliases so the generated file stays compatible.
withResultType: false,
withMutationFn: false,
withMutationOptionsType: false,
},
},
},
Expand Down
1 change: 1 addition & 0 deletions apps/heureka/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"clean:cache": "rm -rf .turbo"
},
"dependencies": {
"@cloudoperators/juno-messages-provider": "workspace:*",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you want really to introduce the messages provider dependency? We can talk about this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not? it's because of msgs for remediation actions

"@cloudoperators/juno-ui-components": "workspace:*",
"@cloudoperators/juno-url-state-provider": "workspace:*",
"@tanstack/react-query": "5.90.16",
Expand Down
33 changes: 18 additions & 15 deletions apps/heureka/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { createRouter, RouterProvider, createHashHistory, createBrowserHistory }
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
import { AppShell, AppShellProvider, PageHeader } from "@cloudoperators/juno-ui-components"
import { encodeV2, decodeV2 } from "@cloudoperators/juno-url-state-provider"
import { MessagesProvider } from "@cloudoperators/juno-messages-provider"
import styles from "./styles.css?inline"
import { ErrorBoundary } from "./components/common/ErrorBoundary"
import { getClient } from "./apollo-client"
Expand Down Expand Up @@ -87,21 +88,23 @@ const App = (props: AppProps) => {
return (
<QueryClientProvider client={queryClient}>
<ApolloProvider client={apiClient}>
<AppShellProvider theme={`${props.theme ? props.theme : "theme-dark"}`}>
{/* load styles inside the shadow dom */}
<style>{styles.toString()}</style>
<StrictMode>
<AppShell embedded={props.embedded} pageHeader={<PageHeader applicationName="Heureka" />}>
<ErrorBoundary>
<StrictMode>
<StoreProvider>
<RouterProvider basepath={props.basePath || "/"} router={router} />
</StoreProvider>
</StrictMode>
</ErrorBoundary>
</AppShell>
</StrictMode>
</AppShellProvider>
<MessagesProvider>
<AppShellProvider theme={`${props.theme ? props.theme : "theme-dark"}`}>
{/* load styles inside the shadow dom */}
<style>{styles.toString()}</style>
<StrictMode>
<AppShell embedded={props.embedded} pageHeader={<PageHeader applicationName="Heureka" />}>
<ErrorBoundary>
<StrictMode>
<StoreProvider>
<RouterProvider basepath={props.basePath || "/"} router={router} />
</StoreProvider>
</StrictMode>
</ErrorBoundary>
</AppShell>
</StrictMode>
</AppShellProvider>
</MessagesProvider>
</ApolloProvider>
</QueryClientProvider>
)
Expand Down
33 changes: 33 additions & 0 deletions apps/heureka/src/api/createRemediation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and Juno contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { ApolloClient } from "@apollo/client"
import {
CreateRemediationDocument,
CreateRemediationMutation,
CreateRemediationMutationVariables,
RemediationInput,
} from "../generated/graphql"

type CreateRemediationParams = {
apiClient: ApolloClient
input: RemediationInput
}

export const createRemediation = async ({
apiClient,
input,
}: CreateRemediationParams): Promise<CreateRemediationMutation["createRemediation"]> => {
const result = await apiClient.mutate<CreateRemediationMutation, CreateRemediationMutationVariables>({
mutation: CreateRemediationDocument,
variables: { input },
})

if (!result.data?.createRemediation) {
throw new Error("Failed to create remediation")
}

return result.data.createRemediation
}
29 changes: 29 additions & 0 deletions apps/heureka/src/api/deleteRemediation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and Juno contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { ApolloClient } from "@apollo/client"
import {
DeleteRemediationDocument,
DeleteRemediationMutation,
DeleteRemediationMutationVariables,
} from "../generated/graphql"

type DeleteRemediationParams = {
apiClient: ApolloClient
remediationId: string
}

export const deleteRemediation = async ({ apiClient, remediationId }: DeleteRemediationParams): Promise<string> => {
const result = await apiClient.mutate<DeleteRemediationMutation, DeleteRemediationMutationVariables>({
mutation: DeleteRemediationDocument,
variables: { id: remediationId },
})

if (!result.data?.deleteRemediation) {
throw new Error("Failed to delete remediation")
}

return result.data.deleteRemediation
}
63 changes: 63 additions & 0 deletions apps/heureka/src/api/fetchImageVersions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and Juno contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { ObservableQuery } from "@apollo/client"
import { GetImageVersionsDocument, GetImageVersionsQuery, ImageVersionFilter } from "../generated/graphql"
import { RouteContext } from "../routes/-types"

type FetchImageVersionsParams = Pick<RouteContext, "queryClient" | "apiClient"> & {
filter: ImageVersionFilter
after?: string | null
first?: number
firstVulnerabilities?: number
afterVulnerabilities?: string | null
firstOccurences?: number
afterOccurences?: string | null
}

export const fetchImageVersions = ({
queryClient,
apiClient,
filter,
after,
first,
firstVulnerabilities,
afterVulnerabilities,
firstOccurences,
afterOccurences,
}: FetchImageVersionsParams): Promise<ObservableQuery.Result<GetImageVersionsQuery>> => {
const queryKey = [
"imageVersions",
filter,
after,
first,
firstVulnerabilities,
afterVulnerabilities,
firstOccurences,
afterOccurences,
]

// Invalidate cache first to ensure queryFn is always called (forces network request)
// Then use ensureQueryData to maintain promise stability (like other fetch functions)
queryClient.invalidateQueries({ queryKey })
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are you always fetching from network? Which benefit gives you then using react-query instead of just fetch?


return queryClient.ensureQueryData({
queryKey,
queryFn: () =>
apiClient.query<GetImageVersionsQuery>({
query: GetImageVersionsDocument,
variables: {
filter,
first,
after,
firstVulnerabilities,
afterVulnerabilities,
firstOccurences,
afterOccurences,
},
fetchPolicy: "network-only", // Force network request to always fetch fresh data
}),
})
}
4 changes: 2 additions & 2 deletions apps/heureka/src/api/fetchImages.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { ApolloQueryResult } from "@apollo/client"
import { ObservableQuery } from "@apollo/client"
import { GetImagesDocument, GetImagesQuery, ImageFilter, VulnerabilityFilter } from "../generated/graphql"
import { RouteContext } from "../routes/-types"

Expand All @@ -29,7 +29,7 @@ export const fetchImages = ({
firstVersions,
afterVersions,
vulFilter,
}: FetchImagesParams): Promise<ApolloQueryResult<GetImagesQuery>> => {
}: FetchImagesParams): Promise<ObservableQuery.Result<GetImagesQuery>> => {
return queryClient.ensureQueryData({
queryKey: [
"images",
Expand Down
31 changes: 31 additions & 0 deletions apps/heureka/src/api/fetchRemediations.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and Juno contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { ObservableQuery } from "@apollo/client"
import { GetRemediationsDocument, GetRemediationsQuery, RemediationFilter } from "../generated/graphql"
import { RouteContext } from "../routes/-types"

type FetchRemediationsParams = Pick<RouteContext, "queryClient" | "apiClient"> & {
filter?: RemediationFilter
}

export const fetchRemediations = ({
queryClient,
apiClient,
filter,
}: FetchRemediationsParams): Promise<ObservableQuery.Result<GetRemediationsQuery>> => {
const queryKey = ["remediations", filter]

return queryClient.ensureQueryData({
queryKey,
queryFn: () =>
apiClient.query<GetRemediationsQuery>({
query: GetRemediationsDocument,
variables: {
filter,
},
}),
})
}
4 changes: 2 additions & 2 deletions apps/heureka/src/api/fetchService.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { ApolloQueryResult } from "@apollo/client"
import { ObservableQuery } from "@apollo/client"
import { GetServicesDocument, GetServicesQuery, OrderDirection, ServiceOrderByField } from "../generated/graphql"
import { RouteContext } from "../routes/-types"

Expand All @@ -15,7 +15,7 @@ export const fetchService = ({
queryClient,
apiClient,
service,
}: FetchServiceParams): Promise<ApolloQueryResult<GetServicesQuery>> => {
}: FetchServiceParams): Promise<ObservableQuery.Result<GetServicesQuery>> => {
return queryClient.ensureQueryData({
queryKey: ["services", service],
queryFn: () =>
Expand Down
Loading
Loading