From 3fa1cfd1e5d60327985af89b7ffdb1103ee4a85c Mon Sep 17 00:00:00 2001 From: Jasmine Francois Date: Wed, 2 Jun 2021 16:09:44 -0500 Subject: [PATCH 1/4] WIP, grouping occurrences by the grouped artifact for git resources --- .../resources/GitResourceOccurrences.js | 122 ++++++++++++++++++ components/resources/ResourceOccurrences.js | 20 ++- pages/api/utils/occurrence-utils.js | 4 + styles/modules/Occurrences.module.scss | 37 +++++- 4 files changed, 178 insertions(+), 5 deletions(-) create mode 100644 components/resources/GitResourceOccurrences.js diff --git a/components/resources/GitResourceOccurrences.js b/components/resources/GitResourceOccurrences.js new file mode 100644 index 00000000..04df0285 --- /dev/null +++ b/components/resources/GitResourceOccurrences.js @@ -0,0 +1,122 @@ +/** + * 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 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 "./ResourceVersion"; +import LabelWithValue from "../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 version = null; + if (occGroup.build.length > 0) { + [version] = occGroup.build[0].artifacts.map((artifact) => { + const { resourceVersion } = getResourceDetails(artifact.id); + return resourceVersion; + }); + } + + 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 && ( + }/> + )} + {!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 1aebe694..770a2158 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 "./GitResourceOccurrences"; const ResourceOccurrences = (props) => { const { occurrences } = props; @@ -33,10 +35,20 @@ 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 f4f07d6d..fe96168c 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/Occurrences.module.scss b/styles/modules/Occurrences.module.scss index 5ddf0803..3c5ba84d 100644 --- a/styles/modules/Occurrences.module.scss +++ b/styles/modules/Occurrences.module.scss @@ -83,6 +83,17 @@ $INDENTATION: 1rem; } } +.occurrenceSection { + margin: 1rem 0; +} + +.occurrenceSectionHeader { + margin-left: 1rem; + padding-left: 0.5rem; + font-size: 1.25rem; + border-left: solid 0.25rem transparent; +} + .active { border-bottom-width: 0; border-left-width: 8px; @@ -138,7 +149,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 +166,18 @@ $INDENTATION: 1rem; color: $DT_SUB_TEXT; } + .occurrenceSectionHeader { + color: $DT_MAIN_TEXT; + border-left-color: $ACCENT; + svg { + color: $ACCENT; + + &:hover { + color: $DT_HOVER_GREEN; + } + } + } + .previewContainer { background-color: $DT_BACKGROUND_MEDIUM; color: $DT_MAIN_TEXT; @@ -182,6 +205,18 @@ $INDENTATION: 1rem; color: $LT_SUB_TEXT; } + .occurrenceSectionHeader { + color: $LT_MAIN_TEXT; + border-left-color: $ACCENT; + svg { + color: $ACCENT; + + &:hover { + color: $LT_HOVER_GREEN; + } + } + } + .previewContainer { background-color: $LT_BACKGROUND_LIGHT; color: $LT_MAIN_TEXT; From 64db97af682038159d31ddc63078b6fcdab4df7f Mon Sep 17 00:00:00 2001 From: Jasmine Francois Date: Wed, 2 Jun 2021 17:03:44 -0500 Subject: [PATCH 2/4] Fix imports --- components/resources/GitResourceOccurrences.js | 8 ++++---- components/resources/ResourceOccurrences.js | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/components/resources/GitResourceOccurrences.js b/components/resources/GitResourceOccurrences.js index 15898a6f..40006291 100644 --- a/components/resources/GitResourceOccurrences.js +++ b/components/resources/GitResourceOccurrences.js @@ -21,11 +21,11 @@ import SecureOccurrenceSection from "components/occurrences/SecureOccurrenceSect 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 { getResourceDetails } from "utils/resource-utils"; +import { mapOccurrencesToSections } from "pages/api/utils/occurrence-utils"; import styles from "styles/modules/Occurrences.module.scss"; -import ResourceVersion from "./ResourceVersion"; -import LabelWithValue from "../LabelWithValue"; +import ResourceVersion from "components/resources/ResourceVersion"; +import LabelWithValue from "components/LabelWithValue"; const GitResourceOccurrences = (props) => { const { occurrences } = props; diff --git a/components/resources/ResourceOccurrences.js b/components/resources/ResourceOccurrences.js index d4a5ed67..e86d0f92 100644 --- a/components/resources/ResourceOccurrences.js +++ b/components/resources/ResourceOccurrences.js @@ -25,7 +25,7 @@ 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 "./GitResourceOccurrences"; +import GitResourceOccurrences from "components/resources/GitResourceOccurrences"; const ResourceOccurrences = (props) => { const { occurrences } = props; From 2b09655a8e1f4e3e684395fd213c5737811561ac Mon Sep 17 00:00:00 2001 From: Jasmine Francois Date: Thu, 3 Jun 2021 09:44:14 -0500 Subject: [PATCH 3/4] Remove artifact version from build card to avoid duplicate info --- .../occurrences/BuildOccurrenceSection.js | 22 +--------- .../resources/GitResourceOccurrences.js | 5 +-- .../BuildOccurrenceSection.spec.js | 42 ++----------------- 3 files changed, 5 insertions(+), 64 deletions(-) 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 index 40006291..0ce73d66 100644 --- a/components/resources/GitResourceOccurrences.js +++ b/components/resources/GitResourceOccurrences.js @@ -109,10 +109,7 @@ const GitResourceOccurrences = (props) => { Git Occurrences

)} - + 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(); - }); - }); }); }); From 0cd6fc85885b1b6a650927539c13a693f4930408 Mon Sep 17 00:00:00 2001 From: Jasmine Francois Date: Thu, 3 Jun 2021 11:41:39 -0500 Subject: [PATCH 4/4] Show resource name and version when grouping occurrences, first pass at mobile-friendly styles but word breaks need some work --- .../resources/GitResourceOccurrences.js | 18 +++++++++--------- styles/modules/OccurrenceDetails.module.scss | 4 ++++ styles/modules/Occurrences.module.scss | 19 +++++++++++++------ styles/modules/Typography.module.scss | 2 ++ 4 files changed, 28 insertions(+), 15 deletions(-) diff --git a/components/resources/GitResourceOccurrences.js b/components/resources/GitResourceOccurrences.js index 0ce73d66..0c942767 100644 --- a/components/resources/GitResourceOccurrences.js +++ b/components/resources/GitResourceOccurrences.js @@ -16,6 +16,7 @@ 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"; @@ -77,11 +78,10 @@ const GitResourceOccurrences = (props) => { return ( <> {mappedOccurrences.map((occGroup) => { - let version = null; + let buildDetails = null; if (occGroup.build.length > 0) { - [version] = occGroup.build[0].artifacts.map((artifact) => { - const { resourceVersion } = getResourceDetails(artifact.id); - return resourceVersion; + [buildDetails] = occGroup.build[0].artifacts.map((artifact) => { + return getResourceDetails(artifact.id); }); } @@ -95,11 +95,11 @@ const GitResourceOccurrences = (props) => { return (
{occGroup.build.length > 0 && ( - } - /> + )} {!occGroup.build.length && (occGroup.secure.length > 0 || 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 3c5ba84d..dbed9060 100644 --- a/styles/modules/Occurrences.module.scss +++ b/styles/modules/Occurrences.module.scss @@ -84,14 +84,20 @@ $INDENTATION: 1rem; } .occurrenceSection { - margin: 1rem 0; + margin: 1rem auto; } .occurrenceSectionHeader { - margin-left: 1rem; - padding-left: 0.5rem; + display: flex; + flex-direction: column; + width: 100%; + padding: 0.5rem; font-size: 1.25rem; - border-left: solid 0.25rem transparent; + + > a { + display: block; + @include ellipsis(calc(100% - 1rem)); + } } .active { @@ -168,7 +174,8 @@ $INDENTATION: 1rem; .occurrenceSectionHeader { color: $DT_MAIN_TEXT; - border-left-color: $ACCENT; + border-top-color: $ACCENT; + background-color: $DT_BACKGROUND_DARK; svg { color: $ACCENT; @@ -207,7 +214,7 @@ $INDENTATION: 1rem; .occurrenceSectionHeader { color: $LT_MAIN_TEXT; - border-left-color: $ACCENT; + border-top-color: $ACCENT; svg { color: $ACCENT; 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 {