diff --git a/components/occurrences/BuildOccurrenceSection.js b/components/occurrences/BuildOccurrenceSection.js index d80e8d4b..e0282f0f 100644 --- a/components/occurrences/BuildOccurrenceSection.js +++ b/components/occurrences/BuildOccurrenceSection.js @@ -23,11 +23,8 @@ import Icon from "components/Icon"; import ExternalLink from "components/ExternalLink"; import dayjs from "dayjs"; import { DATE_TIME_FORMAT } from "utils/constants"; -import { getResourceDetails, RESOURCE_TYPES } from "utils/resource-utils"; -import LabelWithValue from "components/LabelWithValue"; -import ResourceVersion from "components/resources/ResourceVersion"; -const BuildOccurrenceSection = ({ occurrences, type }) => { +const BuildOccurrenceSection = ({ occurrences }) => { if (!occurrences?.length) { return null; } @@ -39,14 +36,6 @@ const BuildOccurrenceSection = ({ occurrences, type }) => {

Build

{occurrences.map((occurrence) => { - let producedArtifactVersions = null; - if (type === RESOURCE_TYPES.GIT) { - producedArtifactVersions = occurrence.artifacts.map((artifact) => { - const { resourceVersion } = getResourceDetails(artifact.id); - return resourceVersion; - }); - } - return ( { )}`} subText={ <> - - ) - } - /> { }; BuildOccurrenceSection.propTypes = { occurrences: PropTypes.array, - type: PropTypes.oneOf(Object.values(RESOURCE_TYPES)), }; export default BuildOccurrenceSection; diff --git a/components/resources/GitResourceOccurrences.js b/components/resources/GitResourceOccurrences.js new file mode 100644 index 00000000..0c942767 --- /dev/null +++ b/components/resources/GitResourceOccurrences.js @@ -0,0 +1,127 @@ +/** + * Copyright 2021 The Rode Authors + * + * 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 PropTypes from "prop-types"; +import Link from "next/link"; +import BuildOccurrenceSection from "components/occurrences/BuildOccurrenceSection"; +import SecureOccurrenceSection from "components/occurrences/SecureOccurrenceSection"; +import DeploymentOccurrenceSection from "components/occurrences/DeploymentOccurrenceSection"; +import OtherOccurrenceSection from "components/occurrences/OtherOccurrenceSection"; +import { useResources } from "providers/resources"; +import { getResourceDetails } from "utils/resource-utils"; +import { mapOccurrencesToSections } from "pages/api/utils/occurrence-utils"; +import styles from "styles/modules/Occurrences.module.scss"; +import ResourceVersion from "components/resources/ResourceVersion"; +import LabelWithValue from "components/LabelWithValue"; + +const GitResourceOccurrences = (props) => { + const { occurrences } = props; + const { state } = useResources(); + + const uniqueUris = Array.from( + new Set(occurrences?.originals?.occurrences.map((occ) => occ.resource.uri)) + ); + const uniqueNonGitUris = uniqueUris.filter( + (uri) => uri !== state.currentResource.uri + ); + + const matchedNonGitOccurrences = uniqueNonGitUris.map((uri) => { + return occurrences?.originals?.occurrences.filter( + (occ) => occ.resource.uri === uri + ); + }); + + const gitBuildOccurrences = occurrences?.originals?.occurrences.filter( + (occ) => + occ.resource.uri === state.currentResource.uri && occ.kind === "BUILD" + ); + const otherGitOccurrences = occurrences?.originals?.occurrences.filter( + (occ) => + occ.resource.uri === state.currentResource.uri && occ.kind !== "BUILD" + ); + + const matchedWithBuilds = gitBuildOccurrences.map((build) => { + const artifactIds = build.build.provenance.builtArtifacts.map( + (artifact) => artifact.id + ); + + const matchingGroup = matchedNonGitOccurrences.filter((occurrenceGroup) => + artifactIds.includes(occurrenceGroup[0].resource.uri) + ); + + return [build, ...matchingGroup.flat()]; + }); + + const mappedGroupedOccurrences = matchedWithBuilds.map((occs) => + mapOccurrencesToSections(occs, state.currentResource.uri) + ); + + const mappedOccurrences = [ + ...mappedGroupedOccurrences, + mapOccurrencesToSections(otherGitOccurrences, state.currentResource.uri), + ]; + + return ( + <> + {mappedOccurrences.map((occGroup) => { + let buildDetails = null; + if (occGroup.build.length > 0) { + [buildDetails] = occGroup.build[0].artifacts.map((artifact) => { + return getResourceDetails(artifact.id); + }); + } + + let key = null; + if (occGroup.build.length > 0) { + key = occGroup.build[0].name; + } else if (occGroup.secure.length > 0) { + key = occGroup.secure[0].name; + } + + return ( +
+ {occGroup.build.length > 0 && ( +
+ {buildDetails.resourceName} + }/> + +
+ )} + {!occGroup.build.length && + (occGroup.secure.length > 0 || + occGroup.deploy.length > 0 || + occGroup.other.length > 0) && ( +

+ Git Occurrences +

+ )} + + + + +
+ ); + })} + + ); +}; + +GitResourceOccurrences.propTypes = { + occurrences: PropTypes.object.isRequired, +}; + +export default GitResourceOccurrences; diff --git a/components/resources/ResourceOccurrences.js b/components/resources/ResourceOccurrences.js index 9fed658d..e86d0f92 100644 --- a/components/resources/ResourceOccurrences.js +++ b/components/resources/ResourceOccurrences.js @@ -24,6 +24,8 @@ import OtherOccurrenceSection from "components/occurrences/OtherOccurrenceSectio import styles from "styles/modules/Occurrences.module.scss"; import { useResources } from "providers/resources"; import { useTheme } from "providers/theme"; +import { RESOURCE_TYPES } from "utils/resource-utils"; +import GitResourceOccurrences from "components/resources/GitResourceOccurrences"; const ResourceOccurrences = (props) => { const { occurrences } = props; @@ -33,13 +35,18 @@ const ResourceOccurrences = (props) => { return (
- - - - + {state.currentResource?.resourceType === RESOURCE_TYPES.GIT ? ( + <> + + + ) : ( + <> + + + + + + )}
{state.occurrenceDetails && (
diff --git a/pages/api/utils/occurrence-utils.js b/pages/api/utils/occurrence-utils.js index 96c93554..bbc7286f 100644 --- a/pages/api/utils/occurrence-utils.js +++ b/pages/api/utils/occurrence-utils.js @@ -128,6 +128,7 @@ const matchAndMapVulnerabilities = (occurrences) => { if (!endScan) { return { + uri: startScan.resource.uri, name: startScan.name, started: startScan.createTime, completed: null, @@ -139,6 +140,7 @@ const matchAndMapVulnerabilities = (occurrences) => { } return { + uri: startScan.resource.uri, name: startScan.name, started: startScan.createTime, completed: endScan.createTime, @@ -202,6 +204,7 @@ const mapBuilds = (occurrences, resourceUri) => { return relatedBuildOccurrences.map((occ) => { return { + uri: occ.resource.uri, name: occ.name, started: occ.build.provenance.startTime, completed: occ.build.provenance.endTime, @@ -217,6 +220,7 @@ const mapBuilds = (occurrences, resourceUri) => { const mapDeployments = (occurrences) => { return occurrences.map((occ) => { return { + uri: occ.resource.uri, name: occ.name, deploymentStart: occ.deployment.deployment.deployTime, deploymentEnd: occ.deployment.deployment.undeployTime, diff --git a/styles/modules/OccurrenceDetails.module.scss b/styles/modules/OccurrenceDetails.module.scss index 42cc731a..a6ab8905 100644 --- a/styles/modules/OccurrenceDetails.module.scss +++ b/styles/modules/OccurrenceDetails.module.scss @@ -103,6 +103,10 @@ $SIDE_SPACING: 1rem; width: 97%; margin: 0 0 0.25rem auto; } + + > p { + overflow-wrap: break-word; + } } .cardTitle { diff --git a/styles/modules/Occurrences.module.scss b/styles/modules/Occurrences.module.scss index 5ddf0803..dbed9060 100644 --- a/styles/modules/Occurrences.module.scss +++ b/styles/modules/Occurrences.module.scss @@ -83,6 +83,23 @@ $INDENTATION: 1rem; } } +.occurrenceSection { + margin: 1rem auto; +} + +.occurrenceSectionHeader { + display: flex; + flex-direction: column; + width: 100%; + padding: 0.5rem; + font-size: 1.25rem; + + > a { + display: block; + @include ellipsis(calc(100% - 1rem)); + } +} + .active { border-bottom-width: 0; border-left-width: 8px; @@ -138,7 +155,7 @@ $INDENTATION: 1rem; justify-content: flex-start; margin-left: 1rem; font-weight: 600; - font-size: 1.5rem; + font-size: 1.25rem; > p { margin-left: 0.25rem; @@ -155,6 +172,19 @@ $INDENTATION: 1rem; color: $DT_SUB_TEXT; } + .occurrenceSectionHeader { + color: $DT_MAIN_TEXT; + border-top-color: $ACCENT; + background-color: $DT_BACKGROUND_DARK; + svg { + color: $ACCENT; + + &:hover { + color: $DT_HOVER_GREEN; + } + } + } + .previewContainer { background-color: $DT_BACKGROUND_MEDIUM; color: $DT_MAIN_TEXT; @@ -182,6 +212,18 @@ $INDENTATION: 1rem; color: $LT_SUB_TEXT; } + .occurrenceSectionHeader { + color: $LT_MAIN_TEXT; + border-top-color: $ACCENT; + svg { + color: $ACCENT; + + &:hover { + color: $LT_HOVER_GREEN; + } + } + } + .previewContainer { background-color: $LT_BACKGROUND_LIGHT; color: $LT_MAIN_TEXT; diff --git a/styles/modules/Typography.module.scss b/styles/modules/Typography.module.scss index 821ce295..5226128d 100644 --- a/styles/modules/Typography.module.scss +++ b/styles/modules/Typography.module.scss @@ -14,6 +14,7 @@ * limitations under the License. */ +@import "styles/mixins"; @import "styles/constants"; .externalLink { @@ -34,6 +35,7 @@ display: flex; flex-direction: row; width: fit-content; + @include ellipsis(100%); } .verticalLabelWithValueContainer { diff --git a/test/components/occurrences/BuildOccurrenceSection.spec.js b/test/components/occurrences/BuildOccurrenceSection.spec.js index 01716d81..ae702188 100644 --- a/test/components/occurrences/BuildOccurrenceSection.spec.js +++ b/test/components/occurrences/BuildOccurrenceSection.spec.js @@ -18,32 +18,20 @@ import React from "react"; import { render, screen } from "test/testing-utils/renderer"; import BuildOccurrenceSection from "components/occurrences/BuildOccurrenceSection"; import { createMockMappedBuildOccurrence } from "test/testing-utils/mocks"; -import { getResourceDetails, RESOURCE_TYPES } from "utils/resource-utils"; describe("BuildOccurrenceSection", () => { - let occurrences, rerender; + let occurrences; it("should return null if there are no relevant occurrences", () => { occurrences = []; - render( - - ); + render(); expect(screen.queryByText(/build/i)).not.toBeInTheDocument(); }); describe("build occurrences exist", () => { beforeEach(() => { occurrences = chance.n(createMockMappedBuildOccurrence, chance.d4()); - const utils = render( - - ); - rerender = utils.rerender; + render(); }); it("should render the section title", () => { @@ -80,29 +68,5 @@ describe("BuildOccurrenceSection", () => { ); }); }); - - it("should render the produced artifact if the build occurrence is representing a git resource", () => { - rerender( - - ); - - occurrences.forEach((occurrence, index) => { - const renderedTimestamp = screen.queryAllByText(/^completed at/i); - expect(renderedTimestamp).toHaveLength(occurrences.length); - expect(renderedTimestamp[index]).toBeInTheDocument(); - - const artifactVersions = occurrence.artifacts.map((artifact) => { - const { resourceVersion } = getResourceDetails(artifact.id); - return resourceVersion; - }); - - expect( - screen.getByText(artifactVersions[0].substring(0, 12)) - ).toBeInTheDocument(); - }); - }); }); });