diff --git a/web/src/ar/pages/version-details/DockerVersion/__tests__/DockerVersionArtifactDetailsPage.test.tsx b/web/src/ar/pages/version-details/DockerVersion/__tests__/DockerVersionArtifactDetailsPage.test.tsx new file mode 100644 index 000000000..44db4846d --- /dev/null +++ b/web/src/ar/pages/version-details/DockerVersion/__tests__/DockerVersionArtifactDetailsPage.test.tsx @@ -0,0 +1,246 @@ +/* + * Copyright 2024 Harness, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react' +import copy from 'clipboard-copy' +import userEvent from '@testing-library/user-event' +import { getByTestId, getByText, render, waitFor } from '@testing-library/react' +import { useGetDockerArtifactLayersQuery, useGetDockerArtifactManifestQuery } from '@harnessio/react-har-service-client' + +import { getTableColumn } from '@ar/utils/testUtils/utils' +import ArTestWrapper from '@ar/utils/testUtils/ArTestWrapper' + +import VersionDetailsPage from '../../VersionDetailsPage' +import { + mockDockerArtifactLayers, + mockDockerArtifactManifest, + mockDockerManifestList, + mockDockerVersionList, + mockDockerVersionSummary +} from './__mockData__' + +const mockHistoryPush = jest.fn() +// eslint-disable-next-line jest-no-mock +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useHistory: () => ({ + push: mockHistoryPush + }) +})) + +jest.mock('clipboard-copy', () => ({ + __esModule: true, + default: jest.fn() +})) + +jest.mock('@harnessio/react-har-service-client', () => ({ + useGetArtifactVersionSummaryQuery: jest.fn().mockImplementation(() => ({ + data: { content: mockDockerVersionSummary }, + error: null, + isLoading: false, + refetch: jest.fn() + })), + useGetDockerArtifactManifestsQuery: jest.fn().mockImplementation(() => ({ + data: { content: mockDockerManifestList }, + error: null, + isLoading: false, + refetch: jest.fn() + })), + getAllArtifactVersions: jest.fn().mockImplementation( + () => + new Promise(success => { + success({ content: mockDockerVersionList }) + }) + ), + useGetDockerArtifactLayersQuery: jest.fn().mockImplementation(() => ({ + data: { content: mockDockerArtifactLayers }, + error: null, + isLoading: false, + refetch: jest.fn() + })), + useGetDockerArtifactManifestQuery: jest.fn().mockImplementation(() => ({ + data: { content: mockDockerArtifactManifest }, + error: null, + isLoading: false, + refetch: jest.fn() + })) +})) + +describe('Verify Docker Version Artifact Details Tab', () => { + beforeAll(() => { + jest.clearAllMocks() + }) + + test('Verify Sub Tabs', async () => { + const { container } = render( + + + + ) + + expect(getByTestId(container, 'layers')).toBeInTheDocument() + expect(getByTestId(container, 'manifest')).toBeInTheDocument() + + const manifestTab = getByTestId(container, 'manifest') + await userEvent.click(manifestTab) + expect(mockHistoryPush).toHaveBeenCalledWith( + '/registries/1/artifacts/1/versions/1/artifact_details?digest=sha256%3A144cdab68a435424250fe06e9a4f8a5f6b6b8a8a55d257bc6ee77476a6ec520d&detailsTab=manifest' + ) + + const layersTab = getByTestId(container, 'layers') + await userEvent.click(layersTab) + expect(mockHistoryPush).toHaveBeenCalledWith( + '/registries/1/artifacts/1/versions/1/artifact_details?digest=sha256%3A144cdab68a435424250fe06e9a4f8a5f6b6b8a8a55d257bc6ee77476a6ec520d&detailsTab=layers' + ) + }) + + test('Verify Layers Tab', async () => { + const { container } = render( + + + + ) + + expect(container).toHaveTextContent('versionDetails.artifactDetails.layers.imageLayers') + + const getTableRowColumn = (row: number, col: number) => getTableColumn(row, col) as HTMLElement + + const data = mockDockerArtifactLayers.data.layers?.[0] + const row = 1 + expect(getTableRowColumn(row, 1)).toHaveTextContent(row.toString()) + expect(getTableRowColumn(row, 2)).toHaveTextContent(data?.command as string) + expect(getTableRowColumn(row, 3)).toHaveTextContent(data?.size as string) + const copyColumn = getTableRowColumn(row, 4) + const copyBtn = copyColumn.querySelector('[data-icon="code-copy"]') as HTMLElement + expect(copyBtn).toBeInTheDocument() + await userEvent.click(copyBtn) + expect(copy).toHaveBeenCalledWith(data?.command) + }) + + test('should show error message if failed to load layers data', async () => { + const refetchFn = jest.fn() + ;(useGetDockerArtifactLayersQuery as jest.Mock).mockImplementation(() => ({ + data: null, + error: { message: 'Failed to fetch artifact details' }, + isLoading: false, + refetch: refetchFn + })) + const { container } = render( + + + + ) + expect(getByText(container, 'Failed to fetch artifact details')).toBeInTheDocument() + const retryBtn = container.querySelector('button[aria-label=Retry]') + expect(retryBtn).toBeInTheDocument() + await userEvent.click(retryBtn!) + await waitFor(() => { + expect(refetchFn).toHaveBeenCalled() + }) + }) + + test('Verify Manifest Tab', async () => { + const { container } = render( + + + + ) + + expect(container).toHaveTextContent('versionDetails.artifactDetails.tabs.manifest') + + const copyBtn = container.querySelector('[data-icon="code-copy"]') as HTMLElement + expect(copyBtn).toBeInTheDocument() + await userEvent.click(copyBtn) + expect(copy).toHaveBeenCalledWith(mockDockerArtifactManifest.data.manifest) + }) + + test('should show error message if failed to load manifest data', async () => { + const refetchFn = jest.fn() + ;(useGetDockerArtifactManifestQuery as jest.Mock).mockImplementation(() => ({ + data: null, + error: { message: 'Failed to fetch artifact details' }, + isLoading: false, + refetch: refetchFn + })) + const { container } = render( + + + + ) + expect(getByText(container, 'Failed to fetch artifact details')).toBeInTheDocument() + const retryBtn = container.querySelector('button[aria-label=Retry]') + expect(retryBtn).toBeInTheDocument() + await userEvent.click(retryBtn!) + await waitFor(() => { + expect(refetchFn).toHaveBeenCalled() + }) + }) +}) diff --git a/web/src/ar/pages/version-details/DockerVersion/__tests__/DockerVersionHeader.test.tsx b/web/src/ar/pages/version-details/DockerVersion/__tests__/DockerVersionHeader.test.tsx index 70dbc8b2f..8d397dfd1 100644 --- a/web/src/ar/pages/version-details/DockerVersion/__tests__/DockerVersionHeader.test.tsx +++ b/web/src/ar/pages/version-details/DockerVersion/__tests__/DockerVersionHeader.test.tsx @@ -20,6 +20,7 @@ import { getByTestId, render, waitFor } from '@testing-library/react' import { type DockerManifestDetails, getAllArtifactVersions, + useGetArtifactVersionSummaryQuery, useGetDockerArtifactManifestsQuery } from '@harnessio/react-har-service-client' @@ -29,7 +30,12 @@ import { testSelectChange } from '@ar/utils/testUtils/utils' import ArTestWrapper from '@ar/utils/testUtils/ArTestWrapper' import VersionDetailsPage from '../../VersionDetailsPage' -import { mockDockerManifestList, mockDockerVersionList, mockDockerVersionSummary } from './__mockData__' +import { + mockDockerManifestList, + mockDockerVersionList, + mockDockerVersionSummary, + mockDockerVersionSummaryWithoutSscaAndStoData +} from './__mockData__' const mockHistoryPush = jest.fn() // eslint-disable-next-line jest-no-mock @@ -124,7 +130,7 @@ describe('Verify DockerVersionHeader component render', () => { await testSelectChange(versionSelector, '1.0.1', data.version) await waitFor(() => { - expect(mockHistoryPush).toHaveBeenLastCalledWith('/registries/artifacts/versions/1.0.1') + expect(mockHistoryPush).toHaveBeenCalledWith('/registries/artifacts/versions/1.0.1') }) }) @@ -262,4 +268,80 @@ describe('Verify DockerVersionHeader component render', () => { const selectPopover = dialogs[0] as HTMLElement expect(selectPopover).toHaveTextContent('No items found') }) + + test('verify tab navigation with ssca and sto data', async () => { + const { container } = render( + + + + ) + + const overviewTab = container.querySelector('div[data-tab-id=overview]') + await userEvent.click(overviewTab!) + expect(mockHistoryPush).toHaveBeenLastCalledWith( + '/registries/undefined/artifacts/undefined/versions/undefined/overview?digest=sha256:144cdab68a435424250fe06e9a4f8a5f6b6b8a8a55d257bc6ee77476a6ec520d' + ) + + const artifactDetailsTab = container.querySelector('div[data-tab-id=artifact_details]') + await userEvent.click(artifactDetailsTab!) + expect(mockHistoryPush).toHaveBeenLastCalledWith( + '/registries/undefined/artifacts/undefined/versions/undefined/artifact_details?digest=sha256:144cdab68a435424250fe06e9a4f8a5f6b6b8a8a55d257bc6ee77476a6ec520d' + ) + + const sscaTab = container.querySelector('div[data-tab-id=supply_chain]') + await userEvent.click(sscaTab!) + expect(mockHistoryPush).toHaveBeenLastCalledWith( + '/registries/undefined/artifacts/undefined/versions/undefined/artifact-sources/67a5dccf6d75916b0c3ea1b5/artifacts/67a5dccf6d75916b0c3ea1b6/supply_chain?digest=sha256:144cdab68a435424250fe06e9a4f8a5f6b6b8a8a55d257bc6ee77476a6ec520d' + ) + + const stoTab = container.querySelector('div[data-tab-id=security_tests]') + await userEvent.click(stoTab!) + expect(mockHistoryPush).toHaveBeenLastCalledWith( + '/registries/undefined/artifacts/undefined/versions/undefined/pipelines/HARNESS_ARTIFACT_SCAN_PIPELINE/executions/Tbi7s6nETjmOMKU3Qrnm7A/security_tests?digest=sha256:144cdab68a435424250fe06e9a4f8a5f6b6b8a8a55d257bc6ee77476a6ec520d' + ) + }) + + test('verify tab navigation with ssca and sto data', async () => { + ;(useGetArtifactVersionSummaryQuery as jest.Mock).mockImplementation(() => ({ + data: { content: mockDockerVersionSummaryWithoutSscaAndStoData }, + error: null, + isLoading: false, + refetch: jest.fn() + })) + const { container } = render( + + + + ) + + const overviewTab = container.querySelector('div[data-tab-id=overview]') + await userEvent.click(overviewTab!) + expect(mockHistoryPush).toHaveBeenLastCalledWith( + '/registries/undefined/artifacts/undefined/versions/undefined/overview?digest=sha256:144cdab68a435424250fe06e9a4f8a5f6b6b8a8a55d257bc6ee77476a6ec520d' + ) + + const artifactDetailsTab = container.querySelector('div[data-tab-id=artifact_details]') + await userEvent.click(artifactDetailsTab!) + expect(mockHistoryPush).toHaveBeenLastCalledWith( + '/registries/undefined/artifacts/undefined/versions/undefined/artifact_details?digest=sha256:144cdab68a435424250fe06e9a4f8a5f6b6b8a8a55d257bc6ee77476a6ec520d' + ) + + const sscaTab = container.querySelector('div[data-tab-id=supply_chain]') + await userEvent.click(sscaTab!) + expect(mockHistoryPush).toHaveBeenLastCalledWith( + '/registries/undefined/artifacts/undefined/versions/undefined/supply_chain?digest=sha256:144cdab68a435424250fe06e9a4f8a5f6b6b8a8a55d257bc6ee77476a6ec520d' + ) + + const stoTab = container.querySelector('div[data-tab-id=security_tests]') + await userEvent.click(stoTab!) + expect(mockHistoryPush).toHaveBeenLastCalledWith( + '/registries/undefined/artifacts/undefined/versions/undefined/security_tests?digest=sha256:144cdab68a435424250fe06e9a4f8a5f6b6b8a8a55d257bc6ee77476a6ec520d' + ) + }) }) diff --git a/web/src/ar/pages/version-details/DockerVersion/__tests__/DockerVersionOverviewPage.test.tsx b/web/src/ar/pages/version-details/DockerVersion/__tests__/DockerVersionOverviewPage.test.tsx index 3d3f11244..12269c10b 100644 --- a/web/src/ar/pages/version-details/DockerVersion/__tests__/DockerVersionOverviewPage.test.tsx +++ b/web/src/ar/pages/version-details/DockerVersion/__tests__/DockerVersionOverviewPage.test.tsx @@ -21,6 +21,7 @@ import { useGetDockerArtifactDetailsQuery, useGetDockerArtifactIntegrationDetailsQuery } from '@harnessio/react-har-service-client' +import { downloadSbom } from '@harnessio/react-ssca-manager-client' import ArTestWrapper from '@ar/utils/testUtils/ArTestWrapper' @@ -34,10 +35,31 @@ import { mockDockerArtifactDetails, mockDockerArtifactIntegrationDetails, mockDockerManifestList, + mockDockerSbomData, mockDockerVersionList, mockDockerVersionSummary } from './__mockData__' +const mockHistoryPush = jest.fn() +// eslint-disable-next-line jest-no-mock +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useHistory: () => ({ + push: mockHistoryPush + }) +})) + +const showErrorToast = jest.fn() + +jest.mock('@harnessio/uicore', () => ({ + ...jest.requireActual('@harnessio/uicore'), + useToaster: jest.fn().mockImplementation(() => ({ + showSuccess: jest.fn(), + showError: showErrorToast, + clear: jest.fn() + })) +})) + jest.mock('@harnessio/react-har-service-client', () => ({ useGetArtifactVersionSummaryQuery: jest.fn().mockImplementation(() => ({ data: { content: mockDockerVersionSummary }, @@ -71,6 +93,15 @@ jest.mock('@harnessio/react-har-service-client', () => ({ })) })) +jest.mock('@harnessio/react-ssca-manager-client', () => ({ + downloadSbom: jest.fn().mockImplementation( + () => + new Promise(success => { + success({ content: mockDockerSbomData }) + }) + ) +})) + describe('Verify docker version overview page', () => { beforeAll(() => { jest.clearAllMocks() @@ -99,12 +130,24 @@ describe('Verify docker version overview page', () => { expect(deploymentCard).toHaveTextContent(data.deploymentsDetails?.totalDeployment?.toString() as string) expect(deploymentCard).toHaveTextContent(data.buildDetails?.pipelineDisplayName as string) expect(deploymentCard).toHaveTextContent(data.buildDetails?.pipelineExecutionId as string) + await userEvent.click(deploymentCard) + await waitFor(() => { + expect(mockHistoryPush).toHaveBeenCalledWith( + '/registries/1/artifacts/1/versions/1/deployments?digest=sha256:144cdab68a435424250fe06e9a4f8a5f6b6b8a8a55d257bc6ee77476a6ec520d' + ) + }) const sscaCard = getByTestId(container, 'integration-supply-chain-card') expect(sscaCard).toBeInTheDocument() expect(sscaCard).toHaveTextContent(data.sbomDetails?.componentsCount?.toString() as string) expect(sscaCard).toHaveTextContent(Number(data.sbomDetails?.avgScore).toFixed(2) as string) expect(getByText(sscaCard, 'versionDetails.cards.supplyChain.downloadSbom')).toBeInTheDocument() + await userEvent.click(sscaCard) + await waitFor(() => { + expect(mockHistoryPush).toHaveBeenCalledWith( + '/registries/1/artifacts/1/versions/1/artifact-sources/67a5dccf6d75916b0c3ea1b5/artifacts/67a5dccf6d75916b0c3ea1b6/supply_chain?digest=sha256:144cdab68a435424250fe06e9a4f8a5f6b6b8a8a55d257bc6ee77476a6ec520d' + ) + }) const securityTestsCard = getByTestId(container, 'integration-security-tests-card') expect(securityTestsCard).toBeInTheDocument() @@ -113,6 +156,62 @@ describe('Verify docker version overview page', () => { expect(securityTestsCard).toHaveTextContent(data.stoDetails?.high?.toString() as string) expect(securityTestsCard).toHaveTextContent(data.stoDetails?.medium?.toString() as string) expect(securityTestsCard).toHaveTextContent(data.stoDetails?.low?.toString() as string) + await userEvent.click(securityTestsCard) + await waitFor(() => { + expect(mockHistoryPush).toHaveBeenCalledWith( + '/registries/1/artifacts/1/versions/1/pipelines/HARNESS_ARTIFACT_SCAN_PIPELINE/executions/Tbi7s6nETjmOMKU3Qrnm7A/security_tests?digest=sha256:144cdab68a435424250fe06e9a4f8a5f6b6b8a8a55d257bc6ee77476a6ec520d' + ) + }) + }) + + test('Verify action button on ssca card', async () => { + const { container } = render( + + + + ) + + expect(getByTestId(container, 'integration-cards')).toBeInTheDocument() + + const sscaCard = getByTestId(container, 'integration-supply-chain-card') + const downloadSbomBtn = getByText(sscaCard, 'versionDetails.cards.supplyChain.downloadSbom') + expect(downloadSbomBtn).toBeInTheDocument() + await userEvent.click(downloadSbomBtn) + await waitFor(() => { + expect(downloadSbom).toHaveBeenCalledWith({ 'orchestration-id': 'yw0D70fiTqetxx0HIyvEUQ', org: '', project: '' }) + }) + + // empty response scenario + ;(downloadSbom as jest.Mock).mockImplementationOnce( + () => + new Promise(success => { + success({ content: { sbom: null } }) + }) + ) + await userEvent.click(downloadSbomBtn) + await waitFor(() => { + expect(showErrorToast).toHaveBeenCalledWith('versionDetails.cards.supplyChain.SBOMDataNotAvailable') + }) + + // error scenario + ;(downloadSbom as jest.Mock).mockImplementationOnce( + () => + new Promise((_, reject) => { + reject({ message: 'error message' }) + }) + ) + await userEvent.click(downloadSbomBtn) + await waitFor(() => { + expect(showErrorToast).toHaveBeenCalledWith('error message') + }) }) test('should show error message if failed to load integration data', async () => { diff --git a/web/src/ar/pages/version-details/DockerVersion/__tests__/__mockData__.ts b/web/src/ar/pages/version-details/DockerVersion/__tests__/__mockData__.ts index a5e3bb9d2..4da6a2db3 100644 --- a/web/src/ar/pages/version-details/DockerVersion/__tests__/__mockData__.ts +++ b/web/src/ar/pages/version-details/DockerVersion/__tests__/__mockData__.ts @@ -18,6 +18,8 @@ import type { ArtifactVersionSummaryResponseResponse, DockerArtifactDetailIntegrationResponseResponse, DockerArtifactDetailResponseResponse, + DockerArtifactManifestResponseResponse, + DockerLayersResponseResponse, DockerManifestsResponseResponse, ListArtifactVersion, ListArtifactVersionResponseResponse @@ -86,6 +88,15 @@ export const mockDockerVersionSummary: ArtifactVersionSummaryResponseResponse = status: 'SUCCESS' } +export const mockDockerVersionSummaryWithoutSscaAndStoData: ArtifactVersionSummaryResponseResponse = { + data: { + imageName: 'maven-app', + packageType: 'DOCKER', + version: '1.0.0' + }, + status: 'SUCCESS' +} + export const mockDockerVersionList: ListArtifactVersionResponseResponse = { data: { artifactVersions: [ @@ -238,3 +249,80 @@ export const mockDockerArtifactIntegrationDetails: DockerArtifactDetailIntegrati }, status: 'SUCCESS' } + +export const mockDockerArtifactLayers: DockerLayersResponseResponse = { + data: { + digest: 'sha256:144cdab68a435424250fe06e9a4f8a5f6b6b8a8a55d257bc6ee77476a6ec520d', + layers: [ + { + command: '/bin/sh -c #(nop) ADD file:6fef7a4ab2de57c438dad76949e7eb87cfb1ea6f45b0f2423f71188efdaa0d8e in /', + size: '40.07 MB' + }, + { + command: '/bin/sh -c #(nop) CMD ["/bin/bash"]', + size: '0 B' + }, + { + command: + '/bin/sh -c set -eux; \tmicrodnf install \t\tgzip \t\ttar \t\t\t\tbinutils \t\tfreetype fontconfig \t; \tmicrodnf clean all', + size: '13.63 MB' + }, + { + command: '/bin/sh -c #(nop) ENV JAVA_HOME=/usr/java/openjdk-17', + size: '0 B' + }, + { + command: + '/bin/sh -c #(nop) ENV PATH=/usr/java/openjdk-17/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin', + size: '0 B' + }, + { + command: '/bin/sh -c #(nop) ENV LANG=C.UTF-8', + size: '0 B' + }, + { + command: '/bin/sh -c #(nop) ENV JAVA_VERSION=17.0.2', + size: '0 B' + }, + { + command: + '/bin/sh -c set -eux; \t\tarch="$(objdump="$(command -v objdump)" \u0026\u0026 objdump --file-headers "$objdump" | awk -F \'[:,]+[[:space:]]+\' \'$1 == "architecture" { print $2 }\')"; \tcase "$arch" in \t\t\'i386:x86-64\') \t\t\tdownloadUrl=\'https://download.java.net/java/GA/jdk17.0.2/dfd4a8d0985749f896bed50d7138ee7f/8/GPL/openjdk-17.0.2_linux-x64_bin.tar.gz\'; \t\t\tdownloadSha256=\'0022753d0cceecacdd3a795dd4cea2bd7ffdf9dc06e22ffd1be98411742fbb44\'; \t\t\t;; \t\t\'aarch64\') \t\t\tdownloadUrl=\'https://download.java.net/java/GA/jdk17.0.2/dfd4a8d0985749f896bed50d7138ee7f/8/GPL/openjdk-17.0.2_linux-aarch64_bin.tar.gz\'; \t\t\tdownloadSha256=\'13bfd976acf8803f862e82c7113fb0e9311ca5458b1decaef8a09ffd91119fa4\'; \t\t\t;; \t\t*) echo \u003e\u00262 "error: unsupported architecture: \'$arch\'"; exit 1 ;; \tesac; \t\tcurl -fL -o openjdk.tgz "$downloadUrl"; \techo "$downloadSha256 *openjdk.tgz" | sha256sum --strict --check -; \t\tmkdir -p "$JAVA_HOME"; \ttar --extract \t\t--file openjdk.tgz \t\t--directory "$JAVA_HOME" \t\t--strip-components 1 \t\t--no-same-owner \t; \trm openjdk.tgz*; \t\trm -rf "$JAVA_HOME/lib/security/cacerts"; \tln -sT /etc/pki/ca-trust/extracted/java/cacerts "$JAVA_HOME/lib/security/cacerts"; \t\tln -sfT "$JAVA_HOME" /usr/java/default; \tln -sfT "$JAVA_HOME" /usr/java/latest; \tfor bin in "$JAVA_HOME/bin/"*; do \t\tbase="$(basename "$bin")"; \t\t[ ! -e "/usr/bin/$base" ]; \t\talternatives --install "/usr/bin/$base" "$base" "$bin" 20000; \tdone; \t\tjava -Xshare:dump; \t\tfileEncoding="$(echo \'System.out.println(System.getProperty("file.encoding"))\' | jshell -s -)"; [ "$fileEncoding" = \'UTF-8\' ]; rm -rf ~/.java; \tjavac --version; \tjava --version', + size: '177.73 MB' + }, + { + command: '/bin/sh -c #(nop) CMD ["jshell"]', + size: '0 B' + }, + { + command: 'WORKDIR /app', + size: '93 B' + }, + { + command: 'COPY target/mavensampleapp-1.0.0.jar app.jar # buildkit', + size: '14.98 MB' + }, + { + command: 'EXPOSE map[8080/tcp:{}]', + size: '0 B' + }, + { + command: 'ENTRYPOINT ["java" "-jar" "app.jar"]', + size: '0 B' + } + ], + osArch: 'linux/arm64' + }, + status: 'SUCCESS' +} + +export const mockDockerArtifactManifest: DockerArtifactManifestResponseResponse = { + data: { + manifest: + '{\n "schemaVersion": 2,\n "mediaType": "application/vnd.docker.distribution.manifest.v2+json",\n "config": {\n "mediaType": "application/vnd.docker.container.image.v1+json",\n "digest": "sha256:f152582e19dbda7e3fb677e83af07fb60b05a56757b15edd8915aef2191aad62",\n "size": 4682\n },\n "layers": [\n {\n "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",\n "digest": "sha256:416105dc84fc8cf66df5d2c9f81570a2cc36a6cae58aedd4d58792f041f7a2f5",\n "size": 42018977\n },\n {\n "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",\n "digest": "sha256:fe66142579ff5bb0bb5cf989222e2bc77a97dcbd0283887dec04d5b9dfd48cfa",\n "size": 14294224\n },\n {\n "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",\n "digest": "sha256:1250d2aa493e8744c8f6cb528c8a882c14b6d7ff0af6862bbbfe676f60ea979e",\n "size": 186363988\n },\n {\n "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",\n "digest": "sha256:6a92a853917fafa754249a4f309e5c34caae0ee5df4369dc1c9383d7cd1b395e",\n "size": 93\n },\n {\n "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",\n "digest": "sha256:b2e80a78034b601c2513a1b00947bda6c6cda46a95e217b954ed84f5b1b5c0fe",\n "size": 15712414\n }\n ]\n}' + }, + status: 'SUCCESS' +} + +export const mockDockerSbomData = { + sbom: 'Test Data' +} diff --git a/web/src/ar/pages/version-details/GenericVersion/__tests__/GenericVersionArtifactDetailsPage.test.tsx b/web/src/ar/pages/version-details/GenericVersion/__tests__/GenericVersionArtifactDetailsPage.test.tsx new file mode 100644 index 000000000..b26304fd6 --- /dev/null +++ b/web/src/ar/pages/version-details/GenericVersion/__tests__/GenericVersionArtifactDetailsPage.test.tsx @@ -0,0 +1,202 @@ +/* + * Copyright 2024 Harness, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react' +import copy from 'clipboard-copy' +import userEvent from '@testing-library/user-event' +import { useGetArtifactFilesQuery } from '@harnessio/react-har-service-client' +import { getByText, queryByText, render, waitFor } from '@testing-library/react' + +import { getTableColumn } from '@ar/utils/testUtils/utils' +import ArTestWrapper from '@ar/utils/testUtils/ArTestWrapper' + +import VersionDetailsPage from '../../VersionDetailsPage' +import { mockGenericArtifactFiles, mockGenericVersionList, mockGenericVersionSummary } from './__mockData__' + +const mockHistoryPush = jest.fn() +// eslint-disable-next-line jest-no-mock +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useHistory: () => ({ + push: mockHistoryPush + }) +})) + +jest.mock('clipboard-copy', () => ({ + __esModule: true, + default: jest.fn() +})) + +jest.mock('@harnessio/react-har-service-client', () => ({ + useGetArtifactVersionSummaryQuery: jest.fn().mockImplementation(() => ({ + data: { content: mockGenericVersionSummary }, + error: null, + isLoading: false, + refetch: jest.fn() + })), + getAllArtifactVersions: jest.fn().mockImplementation( + () => + new Promise(success => { + success({ content: mockGenericVersionList }) + }) + ), + useGetArtifactFilesQuery: jest.fn().mockImplementation(() => ({ + data: { content: mockGenericArtifactFiles }, + error: null, + isLoading: false, + refetch: jest.fn() + })) +})) + +describe('Verify Generic Artifact Version Artifact Details Tab', () => { + test('verify file list table', async () => { + const { container } = render( + + + + ) + + expect(container).toBeInTheDocument() + + const getTableRowColumn = (row: number, col: number) => getTableColumn(row, col) as HTMLElement + + const data = mockGenericArtifactFiles.files[0] + const row = 1 + expect(getTableRowColumn(row, 1)).toHaveTextContent(data.name) + expect(getTableRowColumn(row, 2)).toHaveTextContent(data?.size as string) + + const column3Content = getTableRowColumn(row, 3) + data.checksums.forEach(async each => { + const [checksum, value] = each.split(': ') + const btn = getByText(column3Content, `copy ${checksum}`) + await userEvent.click(btn) + expect(copy).toHaveBeenCalledWith(value) + }) + + const copyColumn = getTableRowColumn(row, 4) + expect(copyColumn).toHaveTextContent('copy') + const copyBtn = copyColumn.querySelector('[data-icon="code-copy"]') as HTMLElement + expect(copyBtn).toBeInTheDocument() + await userEvent.click(copyBtn) + expect(copy).toHaveBeenCalledWith(data?.downloadCommand) + }) + + test('verify pagination and sorting actions', async () => { + const { container } = render( + + + + ) + const nextPageBtn = getByText(container, 'Next') + await userEvent.click(nextPageBtn) + + expect(useGetArtifactFilesQuery).toHaveBeenLastCalledWith({ + artifact: '1/+', + queryParams: { page: 0, size: 50, sort_field: 'updatedAt', sort_order: 'DESC' }, + registry_ref: 'undefined/1/+', + version: '1' + }) + + const fileNameSortIcon = getByText(container, 'versionDetails.artifactFiles.table.columns.name').nextSibling + ?.firstChild as HTMLElement + await userEvent.click(fileNameSortIcon) + + await waitFor(() => { + expect(useGetArtifactFilesQuery).toHaveBeenLastCalledWith({ + artifact: '1/+', + queryParams: { page: 0, size: 50, sort_field: 'updatedAt', sort_order: 'DESC' }, + registry_ref: 'undefined/1/+', + version: '1' + }) + }) + }) + + test('verify error message', async () => { + const mockRefetchFn = jest.fn().mockImplementation(() => undefined) + ;(useGetArtifactFilesQuery as jest.Mock).mockImplementationOnce(() => { + return { + data: null, + loading: false, + error: { message: 'error message' }, + refetch: mockRefetchFn + } + }) + + const { container } = render( + + + + ) + + const errorText = getByText(container, 'error message') + expect(errorText).toBeInTheDocument() + + const retryBtn = getByText(container, 'Retry') + expect(retryBtn).toBeInTheDocument() + + await userEvent.click(retryBtn) + expect(mockRefetchFn).toHaveBeenCalled() + }) + + test('verify with empty pagination response', async () => { + const mockRefetchFn = jest.fn().mockImplementation(() => undefined) + ;(useGetArtifactFilesQuery as jest.Mock).mockImplementationOnce(() => { + return { + data: { content: { files: mockGenericArtifactFiles.files } }, + loading: false, + error: null, + refetch: mockRefetchFn + } + }) + + const { container } = render( + + + + ) + + const nextPageBtn = queryByText(container, 'Next') + expect(nextPageBtn).not.toBeInTheDocument() + }) +}) diff --git a/web/src/ar/pages/version-details/GenericVersion/__tests__/__mockData__.ts b/web/src/ar/pages/version-details/GenericVersion/__tests__/__mockData__.ts index 8d376ae2b..458af733b 100644 --- a/web/src/ar/pages/version-details/GenericVersion/__tests__/__mockData__.ts +++ b/web/src/ar/pages/version-details/GenericVersion/__tests__/__mockData__.ts @@ -17,6 +17,8 @@ import type { ArtifactDetailResponseResponse, ArtifactVersionSummaryResponseResponse, + FileDetail, + FileDetailResponseResponse, ListArtifactVersion, ListArtifactVersionResponseResponse } from '@harnessio/react-har-service-client' @@ -119,3 +121,31 @@ export const mockGenericArtifactDetails: ArtifactDetailResponseResponse = { }, status: 'SUCCESS' } + +export const mockGenericArtifactFiles: FileDetailResponseResponse = { + files: [ + { + checksums: [ + 'SHA-512: ebfd0613c1c298d97fd7743ecd731b96a9c0a7f7cdbfdd9f19ec4682f9b4ceb400420a6191c9671bfb3e1cc5a9fef873ea1ad78f1b30794989a0fdb591f847cd', + 'SHA-256: cc5ac7831841b65901c5578a47d6b125259f9a4364d1d73b0b5d8891ad3b7d34', + 'SHA-1: b0e3200eb5eaca07d773916e306cd1ed9866d3a4', + 'MD5: cc576cbab9119ad7589cae7b57146af6' + ], + createdAt: '1738258177381', + downloadCommand: + "curl --location 'https://pkg.qa.harness.io/generic/iWnhltqOT7GFt7R-F_zP7Q/generic-registry/artifact:v1:image.png' --header 'x-api-key: \u003cAPI_KEY\u003e' -J -O", + name: 'image.png', + size: '170.18KB' + }, + { + createdAt: '1738085520008', + name: 'hello.yaml', + size: '2.79MB' + } as FileDetail + ], + itemCount: 2, + pageCount: 4, + pageIndex: 0, + pageSize: 50, + status: 'SUCCESS' +} diff --git a/web/src/ar/pages/version-details/GenericVersion/pages/deployments/GenericArtifactDeploymentsPage.tsx b/web/src/ar/pages/version-details/GenericVersion/pages/deployments/GenericArtifactDeploymentsPage.tsx deleted file mode 100644 index 010dff554..000000000 --- a/web/src/ar/pages/version-details/GenericVersion/pages/deployments/GenericArtifactDeploymentsPage.tsx +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 2024 Harness, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React from 'react' -import DeploymentsContent from '@ar/pages/version-details/components/DeploymentsContent/DeploymentsContent' - -export default function GenericArtifactDeploymentsPage() { - return -} diff --git a/web/src/ar/pages/version-details/HelmVersion/__tests__/HelmVersionArtifactDetailsPage.test.tsx b/web/src/ar/pages/version-details/HelmVersion/__tests__/HelmVersionArtifactDetailsPage.test.tsx new file mode 100644 index 000000000..44cfb082b --- /dev/null +++ b/web/src/ar/pages/version-details/HelmVersion/__tests__/HelmVersionArtifactDetailsPage.test.tsx @@ -0,0 +1,119 @@ +/* + * Copyright 2024 Harness, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react' +import copy from 'clipboard-copy' +import userEvent from '@testing-library/user-event' +import { getByText, render, waitFor } from '@testing-library/react' +import { useGetHelmArtifactManifestQuery } from '@harnessio/react-har-service-client' + +import ArTestWrapper from '@ar/utils/testUtils/ArTestWrapper' + +import { prettifyManifestJSON } from '../../utils' +import VersionDetailsPage from '../../VersionDetailsPage' +import { mockHelmArtifactManifest, mockHelmVersionList, mockHelmVersionSummary } from './__mockData__' + +const mockHistoryPush = jest.fn() +// eslint-disable-next-line jest-no-mock +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useHistory: () => ({ + push: mockHistoryPush + }) +})) + +jest.mock('clipboard-copy', () => ({ + __esModule: true, + default: jest.fn() +})) + +jest.mock('@harnessio/react-har-service-client', () => ({ + useGetArtifactVersionSummaryQuery: jest.fn().mockImplementation(() => ({ + data: { content: mockHelmVersionSummary }, + error: null, + isLoading: false, + refetch: jest.fn() + })), + getAllArtifactVersions: jest.fn().mockImplementation( + () => + new Promise(success => { + success({ content: mockHelmVersionList }) + }) + ), + useGetHelmArtifactManifestQuery: jest.fn().mockImplementation(() => ({ + data: { content: mockHelmArtifactManifest }, + error: null, + isLoading: false, + refetch: jest.fn() + })) +})) + +describe('Verify Helm Version Artifact Details Tab', () => { + beforeAll(() => { + jest.clearAllMocks() + }) + + test('Verify Manifest', async () => { + const { container } = render( + + + + ) + + expect(container).toHaveTextContent('versionDetails.artifactDetails.tabs.manifest') + + const copyBtn = container.querySelector('[data-icon="code-copy"]') as HTMLElement + expect(copyBtn).toBeInTheDocument() + await userEvent.click(copyBtn) + expect(copy).toHaveBeenCalledWith(prettifyManifestJSON(mockHelmArtifactManifest.data.manifest)) + }) + + test('should show error message if failed to load manifest data', async () => { + const refetchFn = jest.fn() + ;(useGetHelmArtifactManifestQuery as jest.Mock).mockImplementation(() => ({ + data: null, + error: { message: 'Failed to fetch artifact details' }, + isLoading: false, + refetch: refetchFn + })) + const { container } = render( + + + + ) + expect(getByText(container, 'Failed to fetch artifact details')).toBeInTheDocument() + const retryBtn = container.querySelector('button[aria-label=Retry]') + expect(retryBtn).toBeInTheDocument() + await userEvent.click(retryBtn!) + await waitFor(() => { + expect(refetchFn).toHaveBeenCalled() + }) + }) +}) diff --git a/web/src/ar/pages/version-details/HelmVersion/__tests__/__mockData__.ts b/web/src/ar/pages/version-details/HelmVersion/__tests__/__mockData__.ts index 3b2618450..67031bcb0 100644 --- a/web/src/ar/pages/version-details/HelmVersion/__tests__/__mockData__.ts +++ b/web/src/ar/pages/version-details/HelmVersion/__tests__/__mockData__.ts @@ -17,6 +17,7 @@ import type { ArtifactVersionSummaryResponseResponse, HelmArtifactDetailResponseResponse, + HelmArtifactManifestResponseResponse, ListArtifactVersion, ListArtifactVersionResponseResponse } from '@harnessio/react-har-service-client' @@ -128,3 +129,11 @@ export const mockHelmArtifactDetails: HelmArtifactDetailResponseResponse = { }, status: 'SUCCESS' } + +export const mockHelmArtifactManifest: HelmArtifactManifestResponseResponse = { + data: { + manifest: + '{"schemaVersion":2,"config":{"mediaType":"application/vnd.cncf.helm.config.v1+json","digest":"sha256:3d3ca122368140982b0a494f4f357fff2fa9894f9adcc8809fa8e74e2a327d94","size":161},"layers":[{"mediaType":"application/vnd.cncf.helm.chart.content.v1.tar+gzip","digest":"sha256:a8b12f90950f22927e8c2e4f3e9b32655ae5287e95ae801662cef7cf66bd9be3","size":8062}],"annotations":{"org.opencontainers.image.created":"2024-10-25T18:43:34+05:30","org.opencontainers.image.description":"A Helm chart for deploying harness-delegate","org.opencontainers.image.title":"production","org.opencontainers.image.version":"1.0.15"}}' + }, + status: 'SUCCESS' +} diff --git a/web/src/ar/pages/version-details/HelmVersion/components/OverviewCards/OverviewCards.tsx b/web/src/ar/pages/version-details/HelmVersion/components/OverviewCards/OverviewCards.tsx deleted file mode 100644 index d30d354cc..000000000 --- a/web/src/ar/pages/version-details/HelmVersion/components/OverviewCards/OverviewCards.tsx +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2024 Harness, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React from 'react' -import { Layout } from '@harnessio/uicore' - -import DeploymentsCard from '@ar/pages/version-details/components/DeploymentsCard/DeploymentsCard' - -export default function HelmVersionOverviewCards() { - return ( - - - - ) -} diff --git a/web/src/ar/pages/version-details/MavenVersion/__tests__/MavenVersionArtifactDetailsPage.test.tsx b/web/src/ar/pages/version-details/MavenVersion/__tests__/MavenVersionArtifactDetailsPage.test.tsx new file mode 100644 index 000000000..3ee743b39 --- /dev/null +++ b/web/src/ar/pages/version-details/MavenVersion/__tests__/MavenVersionArtifactDetailsPage.test.tsx @@ -0,0 +1,172 @@ +/* + * Copyright 2024 Harness, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react' +import copy from 'clipboard-copy' +import userEvent from '@testing-library/user-event' +import { getByText, render } from '@testing-library/react' +import { useGetArtifactFilesQuery } from '@harnessio/react-har-service-client' + +import { getTableColumn } from '@ar/utils/testUtils/utils' +import ArTestWrapper from '@ar/utils/testUtils/ArTestWrapper' + +import VersionDetailsPage from '../../VersionDetailsPage' +import { mockMavenArtifactFiles, mockMavenVersionList, mockMavenVersionSummary } from './__mockData__' + +const mockHistoryPush = jest.fn() +// eslint-disable-next-line jest-no-mock +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useHistory: () => ({ + push: mockHistoryPush + }) +})) + +jest.mock('clipboard-copy', () => ({ + __esModule: true, + default: jest.fn() +})) + +jest.mock('@harnessio/react-har-service-client', () => ({ + useGetArtifactVersionSummaryQuery: jest.fn().mockImplementation(() => ({ + data: { content: mockMavenVersionSummary }, + error: null, + isLoading: false, + refetch: jest.fn() + })), + getAllArtifactVersions: jest.fn().mockImplementation( + () => + new Promise(success => { + success({ content: mockMavenVersionList }) + }) + ), + useGetArtifactFilesQuery: jest.fn().mockImplementation(() => ({ + data: { content: mockMavenArtifactFiles }, + error: null, + isLoading: false, + refetch: jest.fn() + })) +})) + +describe('Verify Maven Artifact Version Artifact Details Tab', () => { + test('verify file list table', async () => { + const { container } = render( + + + + ) + + expect(container).toBeInTheDocument() + + const getTableRowColumn = (row: number, col: number) => getTableColumn(row, col) as HTMLElement + + const data = mockMavenArtifactFiles.files[0] + const row = 1 + expect(getTableRowColumn(row, 1)).toHaveTextContent(data.name) + expect(getTableRowColumn(row, 2)).toHaveTextContent(data?.size as string) + + const column3Content = getTableRowColumn(row, 3) + data.checksums.forEach(async each => { + const [checksum, value] = each.split(': ') + const btn = getByText(column3Content, `copy ${checksum}`) + await userEvent.click(btn) + expect(copy).toHaveBeenCalledWith(value) + }) + + const copyColumn = getTableRowColumn(row, 4) + expect(copyColumn).toHaveTextContent('copy') + const copyBtn = copyColumn.querySelector('[data-icon="code-copy"]') as HTMLElement + expect(copyBtn).toBeInTheDocument() + await userEvent.click(copyBtn) + expect(copy).toHaveBeenCalledWith(data?.downloadCommand) + }) + + test('verify pagination and sorting actions', async () => { + const { container } = render( + + + + ) + const nextPageBtn = getByText(container, 'Next') + await userEvent.click(nextPageBtn) + + expect(useGetArtifactFilesQuery).toHaveBeenLastCalledWith({ + artifact: '1/+', + queryParams: { page: 0, size: 50, sort_field: 'updatedAt', sort_order: 'DESC' }, + registry_ref: 'undefined/1/+', + version: '1' + }) + + const fileNameSortIcon = getByText(container, 'versionDetails.artifactFiles.table.columns.name').nextSibling + ?.firstChild as HTMLElement + await userEvent.click(fileNameSortIcon) + + expect(useGetArtifactFilesQuery).toHaveBeenLastCalledWith({ + artifact: '1/+', + queryParams: { page: 0, size: 50, sort_field: 'updatedAt', sort_order: 'DESC' }, + registry_ref: 'undefined/1/+', + version: '1' + }) + }) + + test('verify error message', async () => { + const mockRefetchFn = jest.fn().mockImplementation(() => undefined) + ;(useGetArtifactFilesQuery as jest.Mock).mockImplementationOnce(() => { + return { + data: null, + loading: false, + error: { message: 'error message' }, + refetch: mockRefetchFn + } + }) + + const { container } = render( + + + + ) + + const errorText = getByText(container, 'error message') + expect(errorText).toBeInTheDocument() + + const retryBtn = getByText(container, 'Retry') + expect(retryBtn).toBeInTheDocument() + + await userEvent.click(retryBtn) + expect(mockRefetchFn).toHaveBeenCalled() + }) +}) diff --git a/web/src/ar/pages/version-details/MavenVersion/__tests__/__mockData__.ts b/web/src/ar/pages/version-details/MavenVersion/__tests__/__mockData__.ts index ba695ac93..c081b2b70 100644 --- a/web/src/ar/pages/version-details/MavenVersion/__tests__/__mockData__.ts +++ b/web/src/ar/pages/version-details/MavenVersion/__tests__/__mockData__.ts @@ -17,6 +17,7 @@ import type { ArtifactDetailResponseResponse, ArtifactVersionSummaryResponseResponse, + FileDetailResponseResponse, ListArtifactVersion, ListArtifactVersionResponseResponse } from '@harnessio/react-har-service-client' @@ -121,3 +122,65 @@ export const mockMavenArtifactDetails: ArtifactDetailResponseResponse = { }, status: 'SUCCESS' } + +export const mockMavenArtifactFiles: FileDetailResponseResponse = { + files: [ + { + checksums: [ + 'SHA-512: 43b206d0651f2748d685c9ed63942091fe529a0c838effeb15b3d21139a5c25f1086bad9e2df57a3032bf1cf26837b7cfe504f7b033d872a6d2d41d311aba882', + 'SHA-256: 04cf2f4ad947a81667690d64059ee29d3edc0d74649f82df489565c6cc1edcc0', + 'SHA-1: 475741bfe798caedd27a1c2580ea211aeba32521', + 'MD5: 5eb4f955706b83204f7d4dbbdecdb0e6' + ], + createdAt: '1738316037624', + downloadCommand: + "curl --location 'https://pkg.qa.harness.io/maven/iWnhltqOT7GFt7R-F_zP7Q/maven-up-1/junit/junit/3.8.1/junit-3.8.1.jar.sha1' --header 'x-api-key: \u003cIDENTITY_TOKEN\u003e' -O", + name: 'junit-3.8.1.jar.sha1', + size: '40.00B' + }, + { + checksums: [ + 'SHA-512: cfc89ca8303af5c04c75a73db181b61a34371b9e0dcc59e4d746190ac2e7636f0b257303ebef4db9a2cd980d192ab8879c91d84682d472b03fd3b9a732f184b6', + 'SHA-256: ab9b2ba5775492d85d45240f6f12e5880eb0ce26385fd80a1083e3b4ded402c2', + 'SHA-1: 11c996e14e70c07f6758f325838ea07e3bdf0742', + 'MD5: 4f215459aacbaaac97d02c29c41b2a57' + ], + createdAt: '1738316032125', + downloadCommand: + "curl --location 'https://pkg.qa.harness.io/maven/iWnhltqOT7GFt7R-F_zP7Q/maven-up-1/junit/junit/3.8.1/junit-3.8.1.pom.sha1' --header 'x-api-key: \u003cIDENTITY_TOKEN\u003e' -O", + name: 'junit-3.8.1.pom.sha1', + size: '58.00B' + }, + { + checksums: [ + 'SHA-512: 8e6f9fa5eb3ba93a8b1b5a39e01a81c142b33078264dbd0a2030d60dd26735407249a12e66f5cdcab8056e93a5687124fe66e741c233b4c7a06cc8e49f82e98b', + 'SHA-256: b58e459509e190bed737f3592bc1950485322846cf10e78ded1d065153012d70', + 'SHA-1: 99129f16442844f6a4a11ae22fbbee40b14d774f', + 'MD5: 1f40fb782a4f2cf78f161d32670f7a3a' + ], + createdAt: '1738152520460', + downloadCommand: + "curl --location 'https://pkg.qa.harness.io/maven/iWnhltqOT7GFt7R-F_zP7Q/maven-up-1/junit/junit/3.8.1/junit-3.8.1.jar' --header 'x-api-key: \u003cIDENTITY_TOKEN\u003e' -O", + name: 'junit-3.8.1.jar', + size: '118.23KB' + }, + { + checksums: [ + 'SHA-512: d43bddd7228b108eab508871d64725a730f6f159b0cee0e25a62df61f5362dc4c3e7c3413b5562b22e20934b40b5d994c1b1f66fec0e1a340613913e05203396', + 'SHA-256: e68f33343d832398f3c8aa78afcd808d56b7c1020de4d3ad8ce47909095ee904', + 'SHA-1: 16d74791c801c89b0071b1680ea0bc85c93417bb', + 'MD5: 50b40cb7342f52b702e6337d5debf1ae' + ], + createdAt: '1738152517836', + downloadCommand: + "curl --location 'https://pkg.qa.harness.io/maven/iWnhltqOT7GFt7R-F_zP7Q/maven-up-1/junit/junit/3.8.1/junit-3.8.1.pom' --header 'x-api-key: \u003cIDENTITY_TOKEN\u003e' -O", + name: 'junit-3.8.1.pom', + size: '998.00B' + } + ], + itemCount: 4, + pageCount: 1, + pageIndex: 0, + pageSize: 50, + status: 'SUCCESS' +} diff --git a/web/src/ar/pages/version-details/components/VersionDetailsTabs/VersionDetailsTabs.tsx b/web/src/ar/pages/version-details/components/VersionDetailsTabs/VersionDetailsTabs.tsx index 0bdb4f6d3..4ad0120ee 100644 --- a/web/src/ar/pages/version-details/components/VersionDetailsTabs/VersionDetailsTabs.tsx +++ b/web/src/ar/pages/version-details/components/VersionDetailsTabs/VersionDetailsTabs.tsx @@ -39,7 +39,7 @@ import type { DockerVersionDetailsQueryParams } from '../../DockerVersion/types' import css from './VersionDetailsTab.module.scss' export default function VersionDetailsTabs(): JSX.Element { - const [tab, setTab] = useState(VersionDetailsTab.OVERVIEW) + const [tab, setTab] = useState('') const routes = useRoutes() const history = useHistory() @@ -109,7 +109,11 @@ export default function VersionDetailsTabs(): JSX.Element { routeDefinitions.toARVersionDetailsTab({ ...versionDetailsTabWithSSCADetailsPathParams }), routeDefinitions.toARVersionDetailsTab({ ...versionDetailsTabWithPipelineDetailsPathParams }) ]}> - +