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:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-01-10 15:09:05 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-01-10 15:09:05 +0300
commitfbf183eebe154eea4734f80975dd403f08173398 (patch)
tree4a91625645e09eea05e38dba28b1e849bde59ec2 /app/assets/javascripts/vue_merge_request_widget
parent14b71b2795e7765989101241ee89d7bfa55bd838 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/vue_merge_request_widget')
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/report_widget_container.vue10
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/widget/app.vue10
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/widget/widget.vue41
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/extensions/code_quality/constants.js27
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/extensions/code_quality/index.js10
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/extensions/security_reports/graphql/security_report_merge_request_download_paths.query.graphql28
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/extensions/security_reports/mr_widget_security_reports.vue134
7 files changed, 231 insertions, 29 deletions
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/report_widget_container.vue b/app/assets/javascripts/vue_merge_request_widget/components/report_widget_container.vue
index 2683956e603..ecf08f78f57 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/report_widget_container.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/report_widget_container.vue
@@ -6,7 +6,15 @@ export default {
};
},
updated() {
- this.hasChildren = this.$scopedSlots.default?.()?.some((c) => c.tag);
+ this.hasChildren = this.checkSlots();
+ },
+ mounted() {
+ this.hasChildren = this.checkSlots();
+ },
+ methods: {
+ checkSlots() {
+ return this.$scopedSlots.default?.()?.some((c) => c.tag);
+ },
},
};
</script>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/widget/app.vue b/app/assets/javascripts/vue_merge_request_widget/components/widget/app.vue
index 18aa85484ea..5db5f1f8dcf 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/widget/app.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/widget/app.vue
@@ -1,5 +1,11 @@
<script>
export default {
+ components: {
+ MrSecurityWidget: () =>
+ import(
+ '~/vue_merge_request_widget/extensions/security_reports/mr_widget_security_reports.vue'
+ ),
+ },
props: {
mr: {
type: Object,
@@ -8,7 +14,9 @@ export default {
},
computed: {
widgets() {
- return [].filter((w) => w);
+ return [window.gon?.features?.refactorSecurityExtension && 'MrSecurityWidget'].filter(
+ (w) => w,
+ );
},
},
};
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/widget/widget.vue b/app/assets/javascripts/vue_merge_request_widget/components/widget/widget.vue
index cdf35033021..7343c98938c 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/widget/widget.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/widget/widget.vue
@@ -42,7 +42,8 @@ export default {
*/
value: {
type: Object,
- required: true,
+ required: false,
+ default: () => ({}),
},
loadingText: {
type: String,
@@ -56,7 +57,8 @@ export default {
},
fetchCollapsedData: {
type: Function,
- required: true,
+ required: false,
+ default: undefined,
},
fetchExpandedData: {
type: Function,
@@ -119,6 +121,12 @@ export default {
required: false,
default: null,
},
+ // When this is provided, the widget will display an error message in the summary section.
+ hasError: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
data() {
return {
@@ -138,8 +146,17 @@ export default {
summaryStatusIcon() {
return this.summaryError ? this.$options.failedStatusIcon : this.statusIconName;
},
+ hasActionButtons() {
+ return this.actionButtons.length > 0 || Boolean(this.$scopedSlots['action-buttons']);
+ },
},
watch: {
+ hasError: {
+ handler(newValue) {
+ this.summaryError = newValue ? this.errorText : null;
+ },
+ immediate: true,
+ },
isLoading(newValue) {
this.$emit('is-loading', newValue);
},
@@ -154,7 +171,9 @@ export default {
this.telemetryHub?.viewed();
try {
- await this.fetch(this.fetchCollapsedData, FETCH_TYPE_COLLAPSED);
+ if (this.fetchCollapsedData) {
+ await this.fetch(this.fetchCollapsedData, FETCH_TYPE_COLLAPSED);
+ }
} catch {
this.summaryError = this.errorText;
}
@@ -258,7 +277,7 @@ export default {
v-if="helpPopover"
icon="information-o"
:options="helpPopover.options"
- :class="{ 'gl-mr-3': actionButtons.length > 0 }"
+ :class="{ 'gl-mr-3': hasActionButtons }"
>
<template v-if="helpPopover.content">
<p
@@ -275,12 +294,14 @@ export default {
>
</template>
</help-popover>
- <action-buttons
- v-if="actionButtons.length > 0"
- :widget="widgetName"
- :tertiary-buttons="actionButtons"
- @clickedAction="onActionClick"
- />
+ <slot name="action-buttons">
+ <action-buttons
+ v-if="actionButtons.length > 0"
+ :widget="widgetName"
+ :tertiary-buttons="actionButtons"
+ @clickedAction="onActionClick"
+ />
+ </slot>
</div>
<div
v-if="isCollapsible"
diff --git a/app/assets/javascripts/vue_merge_request_widget/extensions/code_quality/constants.js b/app/assets/javascripts/vue_merge_request_widget/extensions/code_quality/constants.js
index 03af21a5019..363fa1b6a5d 100644
--- a/app/assets/javascripts/vue_merge_request_widget/extensions/code_quality/constants.js
+++ b/app/assets/javascripts/vue_merge_request_widget/extensions/code_quality/constants.js
@@ -1,4 +1,9 @@
-import { n__, s__, sprintf } from '~/locale';
+import { n__, s__, __, sprintf } from '~/locale';
+
+export const codeQualityPrefixes = {
+ fixed: 'fixed',
+ new: 'new',
+};
export const i18n = {
label: s__('ciReport|Code Quality'),
@@ -7,25 +12,23 @@ export const i18n = {
noChanges: s__(`ciReport|Code Quality hasn't changed.`),
prependText: s__(`ciReport|in`),
fixed: s__(`ciReport|Fixed`),
- pluralReport: (errors) =>
+ findings: (errors, prefix) =>
sprintf(
n__(
- '%{strong_start}%{errors}%{strong_end} point',
- '%{strong_start}%{errors}%{strong_end} points',
+ '%{strong_start}%{errors}%{strong_end} %{prefix} finding',
+ '%{strong_start}%{errors}%{strong_end} %{prefix} findings',
errors.length,
),
{
errors: errors.length,
+ prefix,
},
false,
),
- singularReport: (errors) => n__('%d point', '%d points', errors.length),
improvementAndDegradationCopy: (improvement, degradation) =>
- sprintf(
- s__(`ciReport|Code Quality improved on ${improvement} and degraded on ${degradation}.`),
- ),
- improvedCopy: (improvements) =>
- sprintf(s__(`ciReport|Code Quality improved on ${improvements}.`)),
- degradedCopy: (degradations) =>
- sprintf(s__(`ciReport|Code Quality degraded on ${degradations}.`)),
+ sprintf(__('Code Quality scans found %{degradation} and fixed %{improvement}.'), {
+ improvement,
+ degradation,
+ }),
+ singularCopy: (findings) => sprintf(__('Code Quality scans found %{findings}.'), { findings }),
};
diff --git a/app/assets/javascripts/vue_merge_request_widget/extensions/code_quality/index.js b/app/assets/javascripts/vue_merge_request_widget/extensions/code_quality/index.js
index 11bc2983f0f..23f83b8d6cc 100644
--- a/app/assets/javascripts/vue_merge_request_widget/extensions/code_quality/index.js
+++ b/app/assets/javascripts/vue_merge_request_widget/extensions/code_quality/index.js
@@ -4,7 +4,7 @@ import { SEVERITY_ICONS_MR_WIDGET } from '~/ci/reports/codequality_report/consta
import { HTTP_STATUS_NO_CONTENT } from '~/lib/utils/http_status';
import { parseCodeclimateMetrics } from '~/ci/reports/codequality_report/store/utils/codequality_parser';
import { capitalizeFirstCharacter } from '~/lib/utils/text_utility';
-import { i18n } from './constants';
+import { i18n, codeQualityPrefixes } from './constants';
export default {
name: 'WidgetCodeQuality',
@@ -25,13 +25,13 @@ export default {
return i18n.loading;
} else if (newErrors.length >= 1 && resolvedErrors.length >= 1) {
return i18n.improvementAndDegradationCopy(
- i18n.pluralReport(resolvedErrors),
- i18n.pluralReport(newErrors),
+ i18n.findings(resolvedErrors, codeQualityPrefixes.fixed),
+ i18n.findings(newErrors, codeQualityPrefixes.new),
);
} else if (resolvedErrors.length >= 1) {
- return i18n.improvedCopy(i18n.singularReport(resolvedErrors));
+ return i18n.singularCopy(i18n.findings(resolvedErrors, codeQualityPrefixes.fixed));
} else if (newErrors.length >= 1) {
- return i18n.degradedCopy(i18n.singularReport(newErrors));
+ return i18n.singularCopy(i18n.findings(newErrors, codeQualityPrefixes.new));
}
return i18n.noChanges;
},
diff --git a/app/assets/javascripts/vue_merge_request_widget/extensions/security_reports/graphql/security_report_merge_request_download_paths.query.graphql b/app/assets/javascripts/vue_merge_request_widget/extensions/security_reports/graphql/security_report_merge_request_download_paths.query.graphql
new file mode 100644
index 00000000000..c12e4d1febb
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/extensions/security_reports/graphql/security_report_merge_request_download_paths.query.graphql
@@ -0,0 +1,28 @@
+query securityReportsDownloadPaths(
+ $projectPath: ID!
+ $iid: String!
+ $reportTypes: [SecurityReportTypeEnum!]
+) {
+ project(fullPath: $projectPath) {
+ id
+ mergeRequest(iid: $iid) {
+ id
+ headPipeline {
+ id
+ jobs(securityReportTypes: $reportTypes) {
+ nodes {
+ id
+ name
+ artifacts {
+ # eslint-disable-next-line @graphql-eslint/require-id-when-available
+ nodes {
+ downloadPath
+ fileType
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/vue_merge_request_widget/extensions/security_reports/mr_widget_security_reports.vue b/app/assets/javascripts/vue_merge_request_widget/extensions/security_reports/mr_widget_security_reports.vue
new file mode 100644
index 00000000000..f0b20adc5cf
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/extensions/security_reports/mr_widget_security_reports.vue
@@ -0,0 +1,134 @@
+<script>
+import { GlDropdown, GlDropdownItem, GlTooltipDirective as GlTooltip } from '@gitlab/ui';
+import MrWidget from '~/vue_merge_request_widget/components/widget/widget.vue';
+import { helpPagePath } from '~/helpers/help_page_helper';
+import { s__, sprintf } from '~/locale';
+import { EXTENSION_ICONS } from '~/vue_merge_request_widget/constants';
+import securityReportMergeRequestDownloadPathsQuery from './graphql/security_report_merge_request_download_paths.query.graphql';
+
+export default {
+ name: 'WidgetSecurityReportsCE',
+ components: {
+ MrWidget,
+ GlDropdown,
+ GlDropdownItem,
+ },
+ directives: {
+ GlTooltip,
+ },
+ i18n: {
+ apiError: s__(
+ 'SecurityReports|Failed to get security report information. Please reload the page or try again later.',
+ ),
+ scansHaveRun: s__('SecurityReports|Security scans have run'),
+ },
+ props: {
+ mr: {
+ type: Object,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ hasError: false,
+ };
+ },
+ reportTypes: ['sast', 'secret_detection'],
+ apollo: {
+ reportArtifacts: {
+ query: securityReportMergeRequestDownloadPathsQuery,
+ variables() {
+ return {
+ projectPath: this.mr.targetProjectFullPath,
+ iid: String(this.mr.iid),
+ reportTypes: this.$options.reportTypes.map((r) => r.toUpperCase()),
+ };
+ },
+ update(data) {
+ const artifacts = [];
+
+ (data?.project?.mergeRequest?.headPipeline?.jobs?.nodes || []).forEach((reportType) => {
+ reportType.artifacts?.nodes.forEach((artifact) => {
+ if (artifact.fileType !== 'TRACE') {
+ artifacts.push({
+ name: reportType.name,
+ id: reportType.id,
+ path: artifact.downloadPath,
+ });
+ }
+ });
+ });
+
+ return artifacts;
+ },
+ error() {
+ this.hasError = true;
+ },
+ },
+ },
+ computed: {
+ artifacts() {
+ return this.reportArtifacts || [];
+ },
+ },
+ methods: {
+ handleIsLoading(value) {
+ this.isLoading = value;
+ },
+
+ artifactText({ name }) {
+ return sprintf(s__('SecurityReports|Download %{artifactName}'), {
+ artifactName: name,
+ });
+ },
+ },
+ widgetHelpPopover: {
+ options: { title: s__('ciReport|Security scan results') },
+ content: {
+ text: s__(
+ 'ciReport|New vulnerabilities are vulnerabilities that the security scan detects in the merge request that are different to existing vulnerabilities in the default branch.',
+ ),
+ learnMorePath: helpPagePath('user/application_security/index', {
+ anchor: 'view-security-scan-information-in-merge-requests',
+ }),
+ },
+ },
+ icons: EXTENSION_ICONS,
+};
+</script>
+
+<template>
+ <mr-widget
+ :has-error="hasError"
+ :error-text="$options.i18n.apiError"
+ :status-icon-name="$options.icons.warning"
+ :widget-name="$options.name"
+ :is-collapsible="false"
+ :help-popover="$options.widgetHelpPopover"
+ :summary="$options.i18n.scansHaveRun"
+ @is-loading="handleIsLoading"
+ >
+ <template v-if="artifacts.length > 0" #action-buttons>
+ <div class="gl-ml-3">
+ <gl-dropdown
+ v-gl-tooltip
+ icon="download"
+ size="small"
+ category="tertiary"
+ variant="confirm"
+ right
+ >
+ <gl-dropdown-item
+ v-for="artifact in artifacts"
+ :key="artifact.path"
+ :href="artifact.path"
+ :data-testid="`download-${artifact.name}`"
+ download
+ >
+ {{ artifactText(artifact) }}
+ </gl-dropdown-item>
+ </gl-dropdown>
+ </div>
+ </template>
+ </mr-widget>
+</template>