diff --git a/ui/apps/platform/cypress/integration-ocp/security/cveDetail.test.ts b/ui/apps/platform/cypress/integration-ocp/security/cveDetail.test.ts index 54880cfbc1039..1eba74dab6eca 100644 --- a/ui/apps/platform/cypress/integration-ocp/security/cveDetail.test.ts +++ b/ui/apps/platform/cypress/integration-ocp/security/cveDetail.test.ts @@ -7,53 +7,71 @@ import { selectors } from '../../integration/vulnerabilities/workloadCves/Worklo import { selectors as vulnerabilitiesSelectors } from '../../integration/vulnerabilities/vulnerabilities.selectors'; import pf6 from '../../selectors/pf6'; +function visitFirstCve() { + withOcpAuth(); + visitFromConsoleLeftNavExpandable('Security', 'Vulnerabilities'); + + return cy + .get(`${selectors.firstTableRow} td[data-label="CVE"]`) + .click() + .invoke('text') + .then((cveName) => { + cy.get('h1').contains(new RegExp(`^${cveName}$`)); + return Promise.resolve(cveName); + }); +} + describe('Security vulnerabilities - CVE Detail page', () => { it('should navigate to the CVE Detail page and account for the project filter', () => { - withOcpAuth(); - visitFromConsoleLeftNavExpandable('Security', 'Vulnerabilities'); + visitFirstCve().then(() => { + // Verify that "All projects" is selected + cy.get(`.co-namespace-bar ${pf6.menuToggle}`).contains('All Projects'); - // Visit a CVE page via link in the CVE table - cy.get(`${selectors.firstTableRow} td[data-label="CVE"]`) - .click() - .invoke('text') - .then((cveName) => { - cy.get('h1').contains(new RegExp(`^${cveName}$`)); + // Click the deployment entity toggle + cy.get(vulnerabilitiesSelectors.entityTypeToggleItem('Deployment')).click(); - // Verify that "All projects" is selected - cy.get(`.co-namespace-bar ${pf6.menuToggle}`).contains('All Projects'); + // Columns that are always present in the table + const baseColumns = [ + 'Row expansion', + 'Deployment', + 'Images by severity', + 'Images', + 'First discovered', + ]; - // Click the deployment entity toggle - cy.get(vulnerabilitiesSelectors.entityTypeToggleItem('Deployment')).click(); + const topLevelTableSelector = 'table:first-of-type'; - // Columns that are always present in the table - const baseColumns = [ - 'Row expansion', - 'Deployment', - 'Images by severity', - 'Images', - 'First discovered', - ]; + // Verify that the "Namespace" column is present + assertVisibleTableColumns(topLevelTableSelector, [...baseColumns, 'Namespace']); - const topLevelTableSelector = 'table:first-of-type'; + // Verify that Namespace is present in the search entities + assertSearchEntities(['Image', 'Image component', 'Deployment', 'Namespace']); - // Verify that the "Namespace" column is present - assertVisibleTableColumns(topLevelTableSelector, [...baseColumns, 'Namespace']); + // Change to the 'stackrox' project + selectProject('stackrox'); - // Verify that Namespace is present in the search entities - assertSearchEntities(['Image', 'Image component', 'Deployment', 'Namespace']); + // Wait for the table data to update + cy.get(selectors.loadingSpinner).should('exist'); + cy.get(selectors.loadingSpinner).should('not.exist'); - // Change to the 'stackrox' project - selectProject('stackrox'); + // Verify that the "Namespace" column is not present + assertVisibleTableColumns(topLevelTableSelector, [...baseColumns]); - // Wait for the table data to update - cy.get(selectors.loadingSpinner).should('exist'); - cy.get(selectors.loadingSpinner).should('not.exist'); + // Verify that Namespace is not present in the search entities + assertSearchEntities(['Image', 'Image component', 'Deployment']); + }); + }); - // Verify that the "Namespace" column is not present - assertVisibleTableColumns(topLevelTableSelector, [...baseColumns]); + it('should navigate to an affected image detail page', () => { + visitFirstCve().then(() => { + cy.get(vulnerabilitiesSelectors.entityTypeToggleItem('Image')).click(); - // Verify that Namespace is not present in the search entities - assertSearchEntities(['Image', 'Image component', 'Deployment']); - }); + cy.get(`${selectors.firstTableRow} td[data-label="Image"] a`) + .click() + .then(([$imageLink]) => { + const imageName = $imageLink.innerText.replace('\n', ''); + cy.get('h1').contains(imageName); + }); + }); }); }); diff --git a/ui/apps/platform/cypress/integration-ocp/security/imageDetail.test.ts b/ui/apps/platform/cypress/integration-ocp/security/imageDetail.test.ts new file mode 100644 index 0000000000000..9ef42e5f2dfc0 --- /dev/null +++ b/ui/apps/platform/cypress/integration-ocp/security/imageDetail.test.ts @@ -0,0 +1,62 @@ +import { visitFromConsoleLeftNavExpandable } from '../../helpers/nav'; +import { withOcpAuth } from '../../helpers/ocpAuth'; +import { assertVisibleTableColumns } from '../../helpers/tableHelpers'; +import { selectors } from '../../integration/vulnerabilities/workloadCves/WorkloadCves.selectors'; +import { selectors as vulnerabilitiesSelectors } from '../../integration/vulnerabilities/vulnerabilities.selectors'; +import { selectProject } from '../../helpers/ocpConsole'; + +function visitImageDetailPage() { + withOcpAuth(); + visitFromConsoleLeftNavExpandable('Security', 'Vulnerabilities'); + + cy.get(vulnerabilitiesSelectors.entityTypeToggleItem('Image')).click(); + + // Visit an image page via link in the image table + return cy + .get(`${selectors.firstTableRow} td[data-label="Image"] a`) + .click() + .then(([$imageLink]) => { + const imageName = $imageLink.innerText.replace('\n', ''); + cy.get('h1').contains(imageName); + return Promise.resolve(imageName); + }); +} + +describe('Security vulnerabilities - Image Detail page', () => { + it('should show the appropriate table columns on the workload resources tab', () => { + visitImageDetailPage() + .then(() => { + cy.get('button[role="tab"]:contains("Resources")').click(); + + // By default, the project filter should be "All projects" which will show the Namespace column + const expectedColumns = ['Name', 'Namespace', 'Created']; + assertVisibleTableColumns('table', expectedColumns); + + // The user could also navigate to this page when viewing a project that has a workload containing the image. + // Grab the namespace of a known workload so we can select that project. + return cy + .get(`${selectors.firstTableRow} td[data-label="Namespace"]`) + .then(([$ns]) => Promise.resolve($ns.innerText)); + }) + .then((namespace) => { + // Select the project that has the workload containing the image and verify the columns + selectProject(namespace); + + const expectedColumns = ['Name', 'Created']; + assertVisibleTableColumns('table', expectedColumns); + }); + }); + + it('should navigate to the CVE Detail from the vulnerability table for the image', () => { + visitImageDetailPage() + .then(() => { + return cy + .get(`${selectors.firstTableRow} td[data-label="CVE"] a`) + .click() + .then(([$cveLink]) => Promise.resolve($cveLink.innerText.replace('\n', ''))); + }) + .then((cveName) => { + cy.get('h1').contains(cveName); + }); + }); +}); diff --git a/ui/apps/platform/src/ConsolePlugin/ImageDetailPage/ImageDetailPage.tsx b/ui/apps/platform/src/ConsolePlugin/ImageDetailPage/ImageDetailPage.tsx index 9b1d2bf026d3d..f6562387c8a76 100644 --- a/ui/apps/platform/src/ConsolePlugin/ImageDetailPage/ImageDetailPage.tsx +++ b/ui/apps/platform/src/ConsolePlugin/ImageDetailPage/ImageDetailPage.tsx @@ -1,16 +1,32 @@ import React from 'react'; -import { useActiveNamespace } from '@openshift-console/dynamic-plugin-sdk'; -import { useParams } from 'react-router-dom-v5-compat'; +import { NamespaceBar, useActiveNamespace } from '@openshift-console/dynamic-plugin-sdk'; + +import { ALL_NAMESPACES_KEY } from 'ConsolePlugin/constants'; +import useURLSearch from 'hooks/useURLSearch'; +import ImagePage from 'Containers/Vulnerabilities/WorkloadCves/Image/ImagePage'; +import { useDefaultWorkloadCveViewContext } from 'ConsolePlugin/hooks/useDefaultWorkloadCveViewContext'; +import { WorkloadCveViewContext } from 'Containers/Vulnerabilities/WorkloadCves/WorkloadCveViewContext'; +import { hideColumnIf } from 'hooks/useManagedColumns'; export function ImageDetailPage() { - const [namespace] = useActiveNamespace(); - const { imageId } = useParams(); + const { searchFilter, setSearchFilter } = useURLSearch(); + const context = useDefaultWorkloadCveViewContext(); + const [activeNamespace] = useActiveNamespace(); return ( - <> -
| Name | -Cluster | -Namespace | -Created | ++ Name + | ++ Cluster + | ++ Namespace + | +Created |
|---|---|---|---|---|---|---|---|
| + | - | {clusterName} | -{namespace} | -+ | + {clusterName} + | ++ {namespace} + | +
|