Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
12bfc52
feat(heureka): adds image details page (#1393)
hodanoori Jan 8, 2026
5d21f3d
feat(heureka): put the search box inside the vulnerabilities top navi…
hodanoori Jan 20, 2026
864acdb
chore(heureka): extends codegen to be adjusted with apollo client V4
hodanoori Jan 20, 2026
35ab5a0
chore(heureka): display the seven chars after sha256 based on github …
hodanoori Jan 20, 2026
4bc758b
feat(heureka): adds messages to remediation actions
hodanoori Jan 20, 2026
d48b237
chore(heureka): fix typescheck
hodanoori Jan 20, 2026
5768fc1
Merge main into hoda-heureka-false-positive
hodanoori Jan 20, 2026
0242166
chore(heureka): resolve conflicts
hodanoori Jan 20, 2026
43b8fb6
chore(heureka): adds changeset
hodanoori Jan 20, 2026
17260fd
chore: ignore TanStack Router cache directory (.tanstack)
hodanoori Jan 20, 2026
6325218
chore(heureka): improves changeset text
hodanoori Jan 20, 2026
8e69870
Merge main into hoda-heureka-false-positive
hodanoori Feb 4, 2026
eeddd1f
fix(carbon): remove unnecessary type assertion in ErrorFallback
hodanoori Feb 4, 2026
984048d
fix(heureka): fixes type issues
hodanoori Feb 5, 2026
dea3eb9
chore(heureka): fixes test
hodanoori Feb 5, 2026
eaf680a
fix(heureka): show default message in ErrorFallback when error.messag…
hodanoori Feb 6, 2026
b0916cc
fix(heureka): assert vulFilter as VulnerabilityFilter in ImageIssuesList
hodanoori Feb 6, 2026
62f3085
fix(heureka): assert vulFilter as VulnerabilityFilter in ImageIssuesList
hodanoori Feb 6, 2026
319c3a2
fix(heureka): assert vulFilter as VulnerabilityFilter in ImageIssuesList
hodanoori Feb 6, 2026
56fbdeb
feat(heureka): adds remediation action panel
hodanoori Feb 9, 2026
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
7 changes: 4 additions & 3 deletions apps/carbon/src/components/ErrorBoundary/ErrorFallback.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import React from "react"
import { FallbackProps } from "react-error-boundary"
import { Message } from "@cloudoperators/juno-ui-components"

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

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