Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/vue_shared/security_reports/security_reports_app.vue')
-rw-r--r--app/assets/javascripts/vue_shared/security_reports/security_reports_app.vue275
1 files changed, 237 insertions, 38 deletions
diff --git a/app/assets/javascripts/vue_shared/security_reports/security_reports_app.vue b/app/assets/javascripts/vue_shared/security_reports/security_reports_app.vue
index 89253cc7116..bdbf9957ad4 100644
--- a/app/assets/javascripts/vue_shared/security_reports/security_reports_app.vue
+++ b/app/assets/javascripts/vue_shared/security_reports/security_reports_app.vue
@@ -1,19 +1,37 @@
<script>
-import { GlIcon, GlLink, GlSprintf } from '@gitlab/ui';
+import { mapActions, mapGetters } from 'vuex';
+import { GlLink, GlSprintf } from '@gitlab/ui';
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import ReportSection from '~/reports/components/report_section.vue';
-import { status } from '~/reports/constants';
+import { LOADING, ERROR, SLOT_SUCCESS, SLOT_LOADING, SLOT_ERROR } from '~/reports/constants';
import { s__ } from '~/locale';
import { normalizeHeaders, parseIntPagination } from '~/lib/utils/common_utils';
-import Flash from '~/flash';
+import createFlash from '~/flash';
import Api from '~/api';
+import HelpIcon from './components/help_icon.vue';
+import SecurityReportDownloadDropdown from './components/security_report_download_dropdown.vue';
+import SecuritySummary from './components/security_summary.vue';
+import store from './store';
+import { MODULE_SAST, MODULE_SECRET_DETECTION } from './store/constants';
+import {
+ REPORT_TYPE_SAST,
+ REPORT_TYPE_SECRET_DETECTION,
+ reportTypeToSecurityReportTypeEnum,
+} from './constants';
+import securityReportDownloadPathsQuery from './queries/security_report_download_paths.query.graphql';
+import { extractSecurityReportArtifacts } from './utils';
export default {
+ store,
components: {
- GlIcon,
GlLink,
GlSprintf,
ReportSection,
+ HelpIcon,
+ SecurityReportDownloadDropdown,
+ SecuritySummary,
},
+ mixins: [glFeatureFlagsMixin()],
props: {
pipelineId: {
type: Number,
@@ -27,33 +45,131 @@ export default {
type: String,
required: true,
},
+ discoverProjectSecurityPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ sastComparisonPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ secretScanningComparisonPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ targetProjectFullPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ mrIid: {
+ type: Number,
+ required: false,
+ default: 0,
+ },
+ canDiscoverProjectSecurity: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
data() {
return {
- hasSecurityReports: false,
+ availableSecurityReports: [],
+ canShowCounts: false,
- // Error state is shown even when successfully loaded, since success
+ // When core_security_mr_widget_counts is not enabled, the
+ // error state is shown even when successfully loaded, since success
// state suggests that the security scans detected no security problems,
// which is not necessarily the case. A future iteration will actually
// check whether problems were found and display the appropriate status.
- status: status.ERROR,
+ status: ERROR,
};
},
+ apollo: {
+ reportArtifacts: {
+ query: securityReportDownloadPathsQuery,
+ variables() {
+ return {
+ projectPath: this.targetProjectFullPath,
+ iid: String(this.mrIid),
+ reportTypes: this.$options.reportTypes.map(
+ reportType => reportTypeToSecurityReportTypeEnum[reportType],
+ ),
+ };
+ },
+ skip() {
+ return !this.canShowDownloads;
+ },
+ update(data) {
+ return extractSecurityReportArtifacts(this.$options.reportTypes, data);
+ },
+ error(error) {
+ this.showError(error);
+ },
+ result({ loading }) {
+ if (loading) {
+ return;
+ }
+
+ // Query has completed, so populate the availableSecurityReports.
+ this.onCheckingAvailableSecurityReports(
+ this.reportArtifacts.map(({ reportType }) => reportType),
+ );
+ },
+ },
+ },
+ computed: {
+ ...mapGetters(['groupedSummaryText', 'summaryStatus']),
+ canShowDownloads() {
+ return this.glFeatures.coreSecurityMrWidgetDownloads;
+ },
+ hasSecurityReports() {
+ return this.availableSecurityReports.length > 0;
+ },
+ hasSastReports() {
+ return this.availableSecurityReports.includes(REPORT_TYPE_SAST);
+ },
+ hasSecretDetectionReports() {
+ return this.availableSecurityReports.includes(REPORT_TYPE_SECRET_DETECTION);
+ },
+ isLoadingReportArtifacts() {
+ return this.$apollo.queries.reportArtifacts.loading;
+ },
+ shouldShowDownloadGuidance() {
+ return !this.canShowDownloads && this.summaryStatus !== LOADING;
+ },
+ scansHaveRunMessage() {
+ return this.canShowDownloads
+ ? this.$options.i18n.scansHaveRun
+ : this.$options.i18n.scansHaveRunWithDownloadGuidance;
+ },
+ },
created() {
- this.checkHasSecurityReports(this.$options.reportTypes)
- .then(hasSecurityReports => {
- this.hasSecurityReports = hasSecurityReports;
- })
- .catch(error => {
- Flash({
- message: this.$options.i18n.apiError,
- captureError: true,
- error,
- });
- });
+ if (!this.canShowDownloads) {
+ this.checkAvailableSecurityReports(this.$options.reportTypes)
+ .then(availableSecurityReports => {
+ this.onCheckingAvailableSecurityReports(Array.from(availableSecurityReports));
+ })
+ .catch(this.showError);
+ }
},
methods: {
- async checkHasSecurityReports(reportTypes) {
+ ...mapActions(MODULE_SAST, {
+ setSastDiffEndpoint: 'setDiffEndpoint',
+ fetchSastDiff: 'fetchDiff',
+ }),
+ ...mapActions(MODULE_SECRET_DETECTION, {
+ setSecretDetectionDiffEndpoint: 'setDiffEndpoint',
+ fetchSecretDetectionDiff: 'fetchDiff',
+ }),
+ async checkAvailableSecurityReports(reportTypes) {
+ const reportTypesSet = new Set(reportTypes);
+ const availableReportTypes = new Set();
+
let page = 1;
while (page) {
// eslint-disable-next-line no-await-in-loop
@@ -62,47 +178,127 @@ export default {
page,
});
- const hasSecurityReports = jobs.some(({ artifacts = [] }) =>
- artifacts.some(({ file_type }) => reportTypes.includes(file_type)),
- );
+ jobs.forEach(({ artifacts = [] }) => {
+ artifacts.forEach(({ file_type }) => {
+ if (reportTypesSet.has(file_type)) {
+ availableReportTypes.add(file_type);
+ }
+ });
+ });
- if (hasSecurityReports) {
- return true;
+ // If we've found artifacts for all the report types, stop looking!
+ if (availableReportTypes.size === reportTypesSet.size) {
+ return availableReportTypes;
}
page = parseIntPagination(normalizeHeaders(headers)).nextPage;
}
- return false;
+ return availableReportTypes;
+ },
+ fetchCounts() {
+ if (!this.glFeatures.coreSecurityMrWidgetCounts) {
+ return;
+ }
+
+ if (this.sastComparisonPath && this.hasSastReports) {
+ this.setSastDiffEndpoint(this.sastComparisonPath);
+ this.fetchSastDiff();
+ this.canShowCounts = true;
+ }
+
+ if (this.secretScanningComparisonPath && this.hasSecretDetectionReports) {
+ this.setSecretDetectionDiffEndpoint(this.secretScanningComparisonPath);
+ this.fetchSecretDetectionDiff();
+ this.canShowCounts = true;
+ }
},
activatePipelinesTab() {
if (window.mrTabs) {
window.mrTabs.tabShown('pipelines');
}
},
+ onCheckingAvailableSecurityReports(availableSecurityReports) {
+ this.availableSecurityReports = availableSecurityReports;
+ this.fetchCounts();
+ },
+ showError(error) {
+ createFlash({
+ message: this.$options.i18n.apiError,
+ captureError: true,
+ error,
+ });
+ },
},
- reportTypes: ['sast', 'secret_detection'],
+ reportTypes: [REPORT_TYPE_SAST, REPORT_TYPE_SECRET_DETECTION],
i18n: {
apiError: s__(
'SecurityReports|Failed to get security report information. Please reload the page or try again later.',
),
- scansHaveRun: s__(
+ scansHaveRun: s__('SecurityReports|Security scans have run'),
+ scansHaveRunWithDownloadGuidance: s__(
'SecurityReports|Security scans have run. Go to the %{linkStart}pipelines tab%{linkEnd} to download the security reports',
),
- securityReportsHelp: s__('SecurityReports|Security reports help page link'),
+ downloadFromPipelineTab: s__(
+ 'SecurityReports|Go to the %{linkStart}pipelines tab%{linkEnd} to download the security reports',
+ ),
},
+ summarySlots: [SLOT_SUCCESS, SLOT_LOADING, SLOT_ERROR],
};
</script>
<template>
<report-section
- v-if="hasSecurityReports"
+ v-if="canShowCounts"
+ :status="summaryStatus"
+ :has-issues="false"
+ class="mr-widget-border-top mr-report"
+ data-testid="security-mr-widget"
+ >
+ <template v-for="slot in $options.summarySlots" #[slot]>
+ <span :key="slot">
+ <security-summary :message="groupedSummaryText" />
+
+ <help-icon
+ :help-path="securityReportsDocsPath"
+ :discover-project-security-path="discoverProjectSecurityPath"
+ />
+ </span>
+ </template>
+
+ <template v-if="shouldShowDownloadGuidance" #sub-heading>
+ <span class="gl-font-sm">
+ <gl-sprintf :message="$options.i18n.downloadFromPipelineTab">
+ <template #link="{ content }">
+ <gl-link
+ class="gl-font-sm"
+ data-testid="show-pipelines"
+ @click="activatePipelinesTab"
+ >{{ content }}</gl-link
+ >
+ </template>
+ </gl-sprintf>
+ </span>
+ </template>
+
+ <template v-if="canShowDownloads" #action-buttons>
+ <security-report-download-dropdown
+ :artifacts="reportArtifacts"
+ :loading="isLoadingReportArtifacts"
+ />
+ </template>
+ </report-section>
+
+ <!-- TODO: Remove this section when removing core_security_mr_widget_counts
+ feature flag. See https://gitlab.com/gitlab-org/gitlab/-/issues/284097 -->
+ <report-section
+ v-else-if="hasSecurityReports"
:status="status"
:has-issues="false"
class="mr-widget-border-top mr-report"
data-testid="security-mr-widget"
>
<template #error>
- <gl-sprintf :message="$options.i18n.scansHaveRun">
+ <gl-sprintf :message="scansHaveRunMessage">
<template #link="{ content }">
<gl-link data-testid="show-pipelines" @click="activatePipelinesTab">{{
content
@@ -110,14 +306,17 @@ export default {
</template>
</gl-sprintf>
- <gl-link
- target="_blank"
- data-testid="help"
- :href="securityReportsDocsPath"
- :aria-label="$options.i18n.securityReportsHelp"
- >
- <gl-icon name="question" />
- </gl-link>
+ <help-icon
+ :help-path="securityReportsDocsPath"
+ :discover-project-security-path="discoverProjectSecurityPath"
+ />
+ </template>
+
+ <template v-if="canShowDownloads" #action-buttons>
+ <security-report-download-dropdown
+ :artifacts="reportArtifacts"
+ :loading="isLoadingReportArtifacts"
+ />
</template>
</report-section>
</template>