Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 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
69ba82c
feat(heureka): add success error msg after revert false positive
hodanoori Feb 11, 2026
eacb22c
Merge main into hoda-heureka-false-positive
hodanoori Feb 11, 2026
aae5fde
feat(heureka): adds routing for active and remediated vulnerabilities…
hodanoori Feb 11, 2026
a677e18
Merge branch 'main' into hoda-heureka-false-positive
hodanoori Feb 11, 2026
ba8898e
chore(heureka): remove ./tanstack from gitignore
hodanoori Feb 11, 2026
be26b61
chore(heureka): remove message provider lib and its usage
hodanoori Feb 11, 2026
f871a67
feat(heureka): adds tests on image details page components
hodanoori Feb 11, 2026
d4df3eb
chore(heureka): updates pnpm-lock.yaml
hodanoori Feb 11, 2026
5b47f5a
fix(heureka): type assertions for RemediatedIssuesDataRows test mocks
hodanoori Feb 11, 2026
7c13802
chore(heureka): updates the changeset text
hodanoori Feb 11, 2026
39b5e7f
fix(heureka): use React Query cache for fetchImageVersions
hodanoori Feb 11, 2026
eefbc58
chore(heureka): uses descriptionTrimmed once in FalsePositiveModal
hodanoori Feb 11, 2026
fdaa134
fix(heureka): avoid setState after unmount in FalsePositiveModal
hodanoori Feb 11, 2026
71afcb7
chore(heureka): use functional setState for expand toggle in Remediat…
hodanoori Feb 11, 2026
03000ca
chore(heureka): use functional setState for expand toggle in Remediat…
hodanoori Feb 11, 2026
1f94616
fix(heureka): drop rel on internal anchor in ImageVersionOccurrences
hodanoori Feb 11, 2026
3d76a8a
fix(heureka): type edge params with VulnerabilityEdge and ComponentIn…
hodanoori Feb 11, 2026
3b6ad5c
fix(heureka): type image version normalizers with GetImageVersionsQuery
hodanoori Feb 11, 2026
9d2c08a
chore(heureka): uses Stack with gap for tooltip content in ImageVersi…
hodanoori Feb 11, 2026
808429c
fix(heureka): disable all revert actions while any revert is in progress
hodanoori Feb 11, 2026
d85da31
fix(heureka): clear search on version-details nav and decode version …
hodanoori Feb 11, 2026
3bcd194
feat(heureka): show first 30 versions with Show more for the rest
hodanoori Feb 11, 2026
2f8a510
feat(heureka): show first 30 versions with Show more for the rest
hodanoori Feb 11, 2026
2b63753
fix(heureka): refetches image and vulnerability scoped remediations a…
hodanoori Feb 12, 2026
5d7e70d
Merge remote-tracking branch 'origin/main' into hoda-heureka-false-po…
hodanoori Feb 12, 2026
35fec10
Merge remote-tracking branch 'origin/main' into hoda-heureka-false-po…
hodanoori Feb 12, 2026
3a59b53
fix(heureka): adds missing end of file line
hodanoori Feb 12, 2026
53cd4db
chore(heureka): displays success and error msg on the remediation pan…
hodanoori Feb 12, 2026
927460f
chore(heureka): displays success and error msg on the remediation pan…
hodanoori Feb 12, 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 on the image details page with create and revert. Introduces image version details page to show deployment locations for each image version.
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 instanceof Error && error.message ? 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
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
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
}
58 changes: 58 additions & 0 deletions apps/heureka/src/api/fetchImageVersions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* 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,
]

return queryClient.ensureQueryData({
queryKey,
queryFn: () =>
apiClient.query<GetImageVersionsQuery>({
query: GetImageVersionsDocument,
variables: {
filter,
first,
after,
firstVulnerabilities,
afterVulnerabilities,
firstOccurences,
afterOccurences,
},
}),
})
}
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and Juno contributors
* SPDX-License-Identifier: Apache-2.0
*/

import React from "react"
import { render, screen } from "@testing-library/react"
import userEvent from "@testing-library/user-event"
import { PortalProvider } from "@cloudoperators/juno-ui-components"
import { FalsePositiveModal } from "./index"

const defaultProps = {
open: true,
onClose: () => {},
onConfirm: () => Promise.resolve(),
vulnerability: "CVE-2024-1234",
service: "my-service",
image: "my-image",
}

const renderModal = (props = {}) => {
return render(
<PortalProvider>
<FalsePositiveModal {...defaultProps} {...props} />
</PortalProvider>
)
}

describe("FalsePositiveModal", () => {
it("renders with title and vulnerability details when open", () => {
renderModal()
expect(screen.getByRole("heading", { name: "Mark as False Positive" })).toBeInTheDocument()
expect(screen.getByText(/Vulnerability:/)).toBeInTheDocument()
expect(screen.getByText("CVE-2024-1234")).toBeInTheDocument()
expect(screen.getByText(/Service:/)).toBeInTheDocument()
expect(screen.getByText("my-service")).toBeInTheDocument()
expect(screen.getByText(/Image:/)).toBeInTheDocument()
expect(screen.getByText("my-image")).toBeInTheDocument()
})

it("renders Cancel and Mark as False Positive buttons", () => {
renderModal()
expect(screen.getByRole("button", { name: "Cancel" })).toBeInTheDocument()
expect(screen.getByRole("button", { name: "Mark as False Positive" })).toBeInTheDocument()
})

it("calls onClose when Cancel is clicked", async () => {
const onClose = vi.fn()
const user = userEvent.setup()
renderModal({ onClose })
await user.click(screen.getByRole("button", { name: "Cancel" }))
expect(onClose).toHaveBeenCalledTimes(1)
})
})
Loading
Loading