From 12bfc52ee6ffa8ff1682a7445edbe52e2391292a Mon Sep 17 00:00:00 2001 From: Hoda <107242553+hodanoori@users.noreply.github.com> Date: Thu, 8 Jan 2026 09:38:37 +0100 Subject: [PATCH 01/11] feat(heureka): adds image details page (#1393) * feat(heureka): adds image details page * chore(heureka): removes image details panel * feat(heureka): navigate from service panel to image details page * chore(heureka): adds changeset * chore(heureka): adjusts tests * chore(heureka): makes navigation checks generic * chore(heureka): makes navigation checks generic * fix(heureka): fixes prettier issue * feat(heureka): improves navigation and url definition * chore(heureka): adjusts test --- .changeset/sixty-worms-play.md | 5 + apps/heureka/src/api/createRemediation.tsx | 33 ++ apps/heureka/src/api/deleteRemediation.tsx | 29 ++ apps/heureka/src/api/fetchImageVersions.tsx | 67 +++ apps/heureka/src/api/fetchRemediations.tsx | 35 ++ .../ImageDetails/FalsePositiveModal/index.tsx | 94 ++++ .../IssuesDataRows/IssuesDataRow/index.tsx | 135 ++++++ .../ImageIssuesList/IssuesDataRows/index.tsx | 14 +- .../RemediatedIssueDataRow/index.tsx | 97 +++++ .../RemediatedIssuesDataRows/index.tsx | 59 +++ .../ImageDetails/ImageIssuesList/index.tsx | 150 +++++++ .../ImageVersionOccurrences.tsx | 0 .../ImageDetails/ImageVersionsList/index.tsx | 74 ++++ .../components/Service/ImageDetails/index.tsx | 142 ++++++ .../IssuesDataRows/IssuesDataRow/index.tsx | 79 ---- .../ImageIssuesList/index.tsx | 97 ----- .../Service/ImageDetailsPanel/index.tsx | 88 ---- .../ImageVersionIssuesDataRows/index.tsx | 43 ++ .../ImageVersionIssuesList/index.tsx | 57 +++ .../ImageVersionOccurrences.tsx | 115 +++++ .../Service/ImageVersionDetails/index.tsx | 124 ++++++ apps/heureka/src/components/Service/index.tsx | 58 +-- .../ServicesList/ServicePanel.test.tsx | 4 +- .../Services/ServicesList/ServicePanel.tsx | 44 +- apps/heureka/src/components/Services/utils.ts | 212 +++++++++ .../src/components/common/Navigation.tsx | 47 +- apps/heureka/src/generated/graphql.ts | 403 +++++++++++++++++- .../Remediations/createRemediation.graphql | 21 + .../Remediations/deleteRemediation.graphql | 7 + .../Remediations/getRemediations.graphql | 19 + .../graphql/Services/getImageVersions.graphql | 97 +++++ apps/heureka/src/routeTree.gen.ts | 83 +++- apps/heureka/src/routes/services/$service.tsx | 40 +- .../services/$service/images/$image.tsx | 57 +++ .../images/$image/versions/$version.tsx | 56 +++ 35 files changed, 2332 insertions(+), 353 deletions(-) create mode 100644 .changeset/sixty-worms-play.md create mode 100644 apps/heureka/src/api/createRemediation.tsx create mode 100644 apps/heureka/src/api/deleteRemediation.tsx create mode 100644 apps/heureka/src/api/fetchImageVersions.tsx create mode 100644 apps/heureka/src/api/fetchRemediations.tsx create mode 100644 apps/heureka/src/components/Service/ImageDetails/FalsePositiveModal/index.tsx create mode 100644 apps/heureka/src/components/Service/ImageDetails/ImageIssuesList/IssuesDataRows/IssuesDataRow/index.tsx rename apps/heureka/src/components/Service/{ImageDetailsPanel => ImageDetails}/ImageIssuesList/IssuesDataRows/index.tsx (67%) create mode 100644 apps/heureka/src/components/Service/ImageDetails/ImageIssuesList/RemediatedIssuesDataRows/RemediatedIssueDataRow/index.tsx create mode 100644 apps/heureka/src/components/Service/ImageDetails/ImageIssuesList/RemediatedIssuesDataRows/index.tsx create mode 100644 apps/heureka/src/components/Service/ImageDetails/ImageIssuesList/index.tsx rename apps/heureka/src/components/Service/{ImageDetailsPanel => ImageDetails}/ImageVersionOccurrences.tsx (100%) create mode 100644 apps/heureka/src/components/Service/ImageDetails/ImageVersionsList/index.tsx create mode 100644 apps/heureka/src/components/Service/ImageDetails/index.tsx delete mode 100644 apps/heureka/src/components/Service/ImageDetailsPanel/ImageIssuesList/IssuesDataRows/IssuesDataRow/index.tsx delete mode 100644 apps/heureka/src/components/Service/ImageDetailsPanel/ImageIssuesList/index.tsx delete mode 100644 apps/heureka/src/components/Service/ImageDetailsPanel/index.tsx create mode 100644 apps/heureka/src/components/Service/ImageVersionDetails/ImageVersionIssuesList/ImageVersionIssuesDataRows/index.tsx create mode 100644 apps/heureka/src/components/Service/ImageVersionDetails/ImageVersionIssuesList/index.tsx create mode 100644 apps/heureka/src/components/Service/ImageVersionDetails/ImageVersionOccurrences.tsx create mode 100644 apps/heureka/src/components/Service/ImageVersionDetails/index.tsx create mode 100644 apps/heureka/src/graphql/Remediations/createRemediation.graphql create mode 100644 apps/heureka/src/graphql/Remediations/deleteRemediation.graphql create mode 100644 apps/heureka/src/graphql/Remediations/getRemediations.graphql create mode 100644 apps/heureka/src/graphql/Services/getImageVersions.graphql create mode 100644 apps/heureka/src/routes/services/$service/images/$image.tsx create mode 100644 apps/heureka/src/routes/services/$service/images/$image/versions/$version.tsx diff --git a/.changeset/sixty-worms-play.md b/.changeset/sixty-worms-play.md new file mode 100644 index 0000000000..2704986b59 --- /dev/null +++ b/.changeset/sixty-worms-play.md @@ -0,0 +1,5 @@ +--- +"@cloudoperators/juno-app-heureka": patch +--- + +Adds image details page and removes image details panel diff --git a/apps/heureka/src/api/createRemediation.tsx b/apps/heureka/src/api/createRemediation.tsx new file mode 100644 index 0000000000..4830d0922e --- /dev/null +++ b/apps/heureka/src/api/createRemediation.tsx @@ -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 => { + const result = await apiClient.mutate({ + mutation: CreateRemediationDocument, + variables: { input }, + }) + + if (!result.data?.createRemediation) { + throw new Error("Failed to create remediation") + } + + return result.data.createRemediation +} diff --git a/apps/heureka/src/api/deleteRemediation.tsx b/apps/heureka/src/api/deleteRemediation.tsx new file mode 100644 index 0000000000..3563ca4e11 --- /dev/null +++ b/apps/heureka/src/api/deleteRemediation.tsx @@ -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 => { + const result = await apiClient.mutate({ + mutation: DeleteRemediationDocument, + variables: { id: remediationId }, + }) + + if (!result.data?.deleteRemediation) { + throw new Error("Failed to delete remediation") + } + + return result.data.deleteRemediation +} diff --git a/apps/heureka/src/api/fetchImageVersions.tsx b/apps/heureka/src/api/fetchImageVersions.tsx new file mode 100644 index 0000000000..5d1c579668 --- /dev/null +++ b/apps/heureka/src/api/fetchImageVersions.tsx @@ -0,0 +1,67 @@ +/* + * SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and Juno contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + GetImageVersionsDocument, + GetImageVersionsQuery, + GetImageVersionsQueryResult, + ImageVersionFilter, +} from "../generated/graphql" +import { RouteContext } from "../routes/-types" + +type FetchImageVersionsParams = Pick & { + 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 => { + 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 }) + + return queryClient.ensureQueryData({ + queryKey, + queryFn: () => + apiClient.query({ + query: GetImageVersionsDocument, + variables: { + filter, + first, + after, + firstVulnerabilities, + afterVulnerabilities, + firstOccurences, + afterOccurences, + }, + fetchPolicy: "network-only", // Force network request to always fetch fresh data + }), + }) +} diff --git a/apps/heureka/src/api/fetchRemediations.tsx b/apps/heureka/src/api/fetchRemediations.tsx new file mode 100644 index 0000000000..e2cf8b125f --- /dev/null +++ b/apps/heureka/src/api/fetchRemediations.tsx @@ -0,0 +1,35 @@ +/* + * SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and Juno contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + GetRemediationsDocument, + GetRemediationsQuery, + GetRemediationsQueryResult, + RemediationFilter, +} from "../generated/graphql" +import { RouteContext } from "../routes/-types" + +type FetchRemediationsParams = Pick & { + filter?: RemediationFilter +} + +export const fetchRemediations = ({ + queryClient, + apiClient, + filter, +}: FetchRemediationsParams): Promise => { + const queryKey = ["remediations", filter] + + return queryClient.ensureQueryData({ + queryKey, + queryFn: () => + apiClient.query({ + query: GetRemediationsDocument, + variables: { + filter, + }, + }), + }) +} diff --git a/apps/heureka/src/components/Service/ImageDetails/FalsePositiveModal/index.tsx b/apps/heureka/src/components/Service/ImageDetails/FalsePositiveModal/index.tsx new file mode 100644 index 0000000000..6ab4ea3a05 --- /dev/null +++ b/apps/heureka/src/components/Service/ImageDetails/FalsePositiveModal/index.tsx @@ -0,0 +1,94 @@ +/* + * SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and Juno contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React, { useState } from "react" +import { Modal, ModalFooter, Button, Stack, Textarea } from "@cloudoperators/juno-ui-components" +import { RemediationInput, RemediationTypeValues } from "../../../../generated/graphql" + +type FalsePositiveModalProps = { + open: boolean + onClose: () => void + onConfirm: (input: RemediationInput) => Promise + vulnerability: string + service: string + image: string +} + +const CONFIRM_LABEL = "Mark as False Positive" +const CANCEL_LABEL = "Cancel" + +export const FalsePositiveModal: React.FC = ({ + open, + onClose, + onConfirm, + vulnerability, + service, + image, +}) => { + const [description, setDescription] = useState("") + const [isSubmitting, setIsSubmitting] = useState(false) + + const handleConfirm = async () => { + setIsSubmitting(true) + try { + const input: RemediationInput = { + type: RemediationTypeValues.FalsePositive, + vulnerability, + service, + image, + description: description || undefined, + } + await onConfirm(input) + setDescription("") + } catch (error) { + console.error("Failed to create remediation:", error) + // Error handling is done in the parent component + } finally { + setIsSubmitting(false) + } + } + + const handleClose = () => { + setDescription("") + onClose() + } + + return ( + + +