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>2020-07-20 15:26:25 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-07-20 15:26:25 +0300
commita09983ae35713f5a2bbb100981116d31ce99826e (patch)
tree2ee2af7bd104d57086db360a7e6d8c9d5d43667a /app/assets/javascripts/reports
parent18c5ab32b738c0b6ecb4d0df3994000482f34bd8 (diff)
Add latest changes from gitlab-org/gitlab@13-2-stable-ee
Diffstat (limited to 'app/assets/javascripts/reports')
-rw-r--r--app/assets/javascripts/reports/accessibility_report/components/accessibility_issue_body.vue10
-rw-r--r--app/assets/javascripts/reports/codequality_report/components/codequality_issue_body.vue42
-rw-r--r--app/assets/javascripts/reports/codequality_report/grouped_codequality_reports_app.vue83
-rw-r--r--app/assets/javascripts/reports/codequality_report/store/actions.js30
-rw-r--r--app/assets/javascripts/reports/codequality_report/store/getters.js58
-rw-r--r--app/assets/javascripts/reports/codequality_report/store/index.js16
-rw-r--r--app/assets/javascripts/reports/codequality_report/store/mutation_types.js5
-rw-r--r--app/assets/javascripts/reports/codequality_report/store/mutations.js24
-rw-r--r--app/assets/javascripts/reports/codequality_report/store/state.js15
-rw-r--r--app/assets/javascripts/reports/codequality_report/store/utils/codequality_comparison.js41
-rw-r--r--app/assets/javascripts/reports/codequality_report/workers/codequality_comparison_worker.js28
-rw-r--r--app/assets/javascripts/reports/components/grouped_test_reports_app.vue25
-rw-r--r--app/assets/javascripts/reports/components/issue_body.js3
-rw-r--r--app/assets/javascripts/reports/components/report_item.vue2
-rw-r--r--app/assets/javascripts/reports/components/report_section.vue10
-rw-r--r--app/assets/javascripts/reports/components/summary_row.vue9
-rw-r--r--app/assets/javascripts/reports/components/test_issue_body.vue4
17 files changed, 390 insertions, 15 deletions
diff --git a/app/assets/javascripts/reports/accessibility_report/components/accessibility_issue_body.vue b/app/assets/javascripts/reports/accessibility_report/components/accessibility_issue_body.vue
index 653dcced98b..ed4f3c4e0fe 100644
--- a/app/assets/javascripts/reports/accessibility_report/components/accessibility_issue_body.vue
+++ b/app/assets/javascripts/reports/accessibility_report/components/accessibility_issue_body.vue
@@ -36,13 +36,9 @@ export default {
};
</script>
<template>
- <div class="report-block-list-issue-description prepend-top-5 append-bottom-5">
+ <div class="report-block-list-issue-description gl-mt-2 gl-mb-2">
<div ref="accessibility-issue-description" class="report-block-list-issue-description-text">
- <div
- v-if="isNew"
- ref="accessibility-issue-is-new-badge"
- class="badge badge-danger append-right-5"
- >
+ <div v-if="isNew" ref="accessibility-issue-is-new-badge" class="badge badge-danger gl-mr-2">
{{ s__('AccessibilityReport|New') }}
</div>
<div>
@@ -55,7 +51,7 @@ export default {
)
}}
<gl-link ref="accessibility-issue-learn-more" :href="learnMoreUrl" target="_blank">{{
- s__('AccessibilityReport|Learn More')
+ s__('AccessibilityReport|Learn more')
}}</gl-link>
</div>
{{ sprintf(s__('AccessibilityReport|Message: %{message}'), { message: issue.message }) }}
diff --git a/app/assets/javascripts/reports/codequality_report/components/codequality_issue_body.vue b/app/assets/javascripts/reports/codequality_report/components/codequality_issue_body.vue
new file mode 100644
index 00000000000..0c758ee2b5c
--- /dev/null
+++ b/app/assets/javascripts/reports/codequality_report/components/codequality_issue_body.vue
@@ -0,0 +1,42 @@
+<script>
+/**
+ * Renders Code quality body text
+ * Fixed: [name] in [link]:[line]
+ */
+import ReportLink from '~/reports/components/report_link.vue';
+import { STATUS_SUCCESS } from '~/reports/constants';
+
+export default {
+ name: 'CodequalityIssueBody',
+
+ components: {
+ ReportLink,
+ },
+ props: {
+ status: {
+ type: String,
+ required: true,
+ },
+ issue: {
+ type: Object,
+ required: true,
+ },
+ },
+ computed: {
+ isStatusSuccess() {
+ return this.status === STATUS_SUCCESS;
+ },
+ },
+};
+</script>
+<template>
+ <div class="report-block-list-issue-description gl-mt-2 gl-mb-2">
+ <div class="report-block-list-issue-description-text">
+ <template v-if="isStatusSuccess">{{ s__('ciReport|Fixed:') }}</template>
+
+ {{ issue.name }}
+ </div>
+
+ <report-link v-if="issue.path" :issue="issue" />
+ </div>
+</template>
diff --git a/app/assets/javascripts/reports/codequality_report/grouped_codequality_reports_app.vue b/app/assets/javascripts/reports/codequality_report/grouped_codequality_reports_app.vue
new file mode 100644
index 00000000000..f3d5b1a80f8
--- /dev/null
+++ b/app/assets/javascripts/reports/codequality_report/grouped_codequality_reports_app.vue
@@ -0,0 +1,83 @@
+<script>
+import { mapState, mapActions, mapGetters } from 'vuex';
+import { componentNames } from '~/reports/components/issue_body';
+import { s__, sprintf } from '~/locale';
+import ReportSection from '~/reports/components/report_section.vue';
+import createStore from './store';
+
+export default {
+ name: 'GroupedCodequalityReportsApp',
+ store: createStore(),
+ components: {
+ ReportSection,
+ },
+ props: {
+ headPath: {
+ type: String,
+ required: true,
+ },
+ headBlobPath: {
+ type: String,
+ required: true,
+ },
+ basePath: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ baseBlobPath: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ codequalityHelpPath: {
+ type: String,
+ required: true,
+ },
+ },
+ componentNames,
+ computed: {
+ ...mapState(['newIssues', 'resolvedIssues']),
+ ...mapGetters([
+ 'hasCodequalityIssues',
+ 'codequalityStatus',
+ 'codequalityText',
+ 'codequalityPopover',
+ ]),
+ },
+ created() {
+ this.setPaths({
+ basePath: this.basePath,
+ headPath: this.headPath,
+ baseBlobPath: this.baseBlobPath,
+ headBlobPath: this.headBlobPath,
+ helpPath: this.codequalityHelpPath,
+ });
+
+ this.fetchReports();
+ },
+ methods: {
+ ...mapActions(['fetchReports', 'setPaths']),
+ },
+ loadingText: sprintf(s__('ciReport|Loading %{reportName} report'), {
+ reportName: 'codeclimate',
+ }),
+ errorText: sprintf(s__('ciReport|Failed to load %{reportName} report'), {
+ reportName: 'codeclimate',
+ }),
+};
+</script>
+<template>
+ <report-section
+ :status="codequalityStatus"
+ :loading-text="$options.loadingText"
+ :error-text="$options.errorText"
+ :success-text="codequalityText"
+ :unresolved-issues="newIssues"
+ :resolved-issues="resolvedIssues"
+ :has-issues="hasCodequalityIssues"
+ :component="$options.componentNames.CodequalityIssueBody"
+ :popover-options="codequalityPopover"
+ class="js-codequality-widget mr-widget-border-top mr-report"
+ />
+</template>
diff --git a/app/assets/javascripts/reports/codequality_report/store/actions.js b/app/assets/javascripts/reports/codequality_report/store/actions.js
new file mode 100644
index 00000000000..bf84d27b5ea
--- /dev/null
+++ b/app/assets/javascripts/reports/codequality_report/store/actions.js
@@ -0,0 +1,30 @@
+import axios from '~/lib/utils/axios_utils';
+import * as types from './mutation_types';
+import { parseCodeclimateMetrics, doCodeClimateComparison } from './utils/codequality_comparison';
+
+export const setPaths = ({ commit }, paths) => commit(types.SET_PATHS, paths);
+
+export const fetchReports = ({ state, dispatch, commit }) => {
+ commit(types.REQUEST_REPORTS);
+
+ if (!state.basePath) {
+ return dispatch('receiveReportsError');
+ }
+ return Promise.all([axios.get(state.headPath), axios.get(state.basePath)])
+ .then(results =>
+ doCodeClimateComparison(
+ parseCodeclimateMetrics(results[0].data, state.headBlobPath),
+ parseCodeclimateMetrics(results[1].data, state.baseBlobPath),
+ ),
+ )
+ .then(data => dispatch('receiveReportsSuccess', data))
+ .catch(() => dispatch('receiveReportsError'));
+};
+
+export const receiveReportsSuccess = ({ commit }, data) => {
+ commit(types.RECEIVE_REPORTS_SUCCESS, data);
+};
+
+export const receiveReportsError = ({ commit }) => {
+ commit(types.RECEIVE_REPORTS_ERROR);
+};
diff --git a/app/assets/javascripts/reports/codequality_report/store/getters.js b/app/assets/javascripts/reports/codequality_report/store/getters.js
new file mode 100644
index 00000000000..5df58c7f85f
--- /dev/null
+++ b/app/assets/javascripts/reports/codequality_report/store/getters.js
@@ -0,0 +1,58 @@
+import { LOADING, ERROR, SUCCESS } from '../../constants';
+import { sprintf, __, s__, n__ } from '~/locale';
+
+export const hasCodequalityIssues = state =>
+ Boolean(state.newIssues?.length || state.resolvedIssues?.length);
+
+export const codequalityStatus = state => {
+ if (state.isLoading) {
+ return LOADING;
+ }
+ if (state.hasError) {
+ return ERROR;
+ }
+
+ return SUCCESS;
+};
+
+export const codequalityText = state => {
+ const { newIssues, resolvedIssues } = state;
+ const text = [];
+
+ if (!newIssues.length && !resolvedIssues.length) {
+ text.push(s__('ciReport|No changes to code quality'));
+ } else {
+ text.push(s__('ciReport|Code quality'));
+
+ if (resolvedIssues.length) {
+ text.push(n__(' improved on %d point', ' improved on %d points', resolvedIssues.length));
+ }
+
+ if (newIssues.length && resolvedIssues.length) {
+ text.push(__(' and'));
+ }
+
+ if (newIssues.length) {
+ text.push(n__(' degraded on %d point', ' degraded on %d points', newIssues.length));
+ }
+ }
+
+ return text.join('');
+};
+
+export const codequalityPopover = state => {
+ if (state.headPath && !state.basePath) {
+ return {
+ title: s__('ciReport|Base pipeline codequality artifact not found'),
+ content: sprintf(
+ s__('ciReport|%{linkStartTag}Learn more about codequality reports %{linkEndTag}'),
+ {
+ linkStartTag: `<a href="${state.helpPath}" target="_blank" rel="noopener noreferrer">`,
+ linkEndTag: '<i class="fa fa-external-link" aria-hidden="true"></i></a>',
+ },
+ false,
+ ),
+ };
+ }
+ return {};
+};
diff --git a/app/assets/javascripts/reports/codequality_report/store/index.js b/app/assets/javascripts/reports/codequality_report/store/index.js
new file mode 100644
index 00000000000..047964260ad
--- /dev/null
+++ b/app/assets/javascripts/reports/codequality_report/store/index.js
@@ -0,0 +1,16 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+import * as actions from './actions';
+import * as getters from './getters';
+import mutations from './mutations';
+import state from './state';
+
+Vue.use(Vuex);
+
+export default initialState =>
+ new Vuex.Store({
+ actions,
+ getters,
+ mutations,
+ state: state(initialState),
+ });
diff --git a/app/assets/javascripts/reports/codequality_report/store/mutation_types.js b/app/assets/javascripts/reports/codequality_report/store/mutation_types.js
new file mode 100644
index 00000000000..c362c973ae1
--- /dev/null
+++ b/app/assets/javascripts/reports/codequality_report/store/mutation_types.js
@@ -0,0 +1,5 @@
+export const SET_PATHS = 'SET_PATHS';
+
+export const REQUEST_REPORTS = 'REQUEST_REPORTS';
+export const RECEIVE_REPORTS_SUCCESS = 'RECEIVE_REPORTS_SUCCESS';
+export const RECEIVE_REPORTS_ERROR = 'RECEIVE_REPORTS_ERROR';
diff --git a/app/assets/javascripts/reports/codequality_report/store/mutations.js b/app/assets/javascripts/reports/codequality_report/store/mutations.js
new file mode 100644
index 00000000000..7ef4f3ce2db
--- /dev/null
+++ b/app/assets/javascripts/reports/codequality_report/store/mutations.js
@@ -0,0 +1,24 @@
+import * as types from './mutation_types';
+
+export default {
+ [types.SET_PATHS](state, paths) {
+ state.basePath = paths.basePath;
+ state.headPath = paths.headPath;
+ state.baseBlobPath = paths.baseBlobPath;
+ state.headBlobPath = paths.headBlobPath;
+ state.helpPath = paths.helpPath;
+ },
+ [types.REQUEST_REPORTS](state) {
+ state.isLoading = true;
+ },
+ [types.RECEIVE_REPORTS_SUCCESS](state, data) {
+ state.hasError = false;
+ state.isLoading = false;
+ state.newIssues = data.newIssues;
+ state.resolvedIssues = data.resolvedIssues;
+ },
+ [types.RECEIVE_REPORTS_ERROR](state) {
+ state.isLoading = false;
+ state.hasError = true;
+ },
+};
diff --git a/app/assets/javascripts/reports/codequality_report/store/state.js b/app/assets/javascripts/reports/codequality_report/store/state.js
new file mode 100644
index 00000000000..38ab53b432e
--- /dev/null
+++ b/app/assets/javascripts/reports/codequality_report/store/state.js
@@ -0,0 +1,15 @@
+export default () => ({
+ basePath: null,
+ headPath: null,
+
+ baseBlobPath: null,
+ headBlobPath: null,
+
+ isLoading: false,
+ hasError: false,
+
+ newIssues: [],
+ resolvedIssues: [],
+
+ helpPath: null,
+});
diff --git a/app/assets/javascripts/reports/codequality_report/store/utils/codequality_comparison.js b/app/assets/javascripts/reports/codequality_report/store/utils/codequality_comparison.js
new file mode 100644
index 00000000000..eba9e340c4e
--- /dev/null
+++ b/app/assets/javascripts/reports/codequality_report/store/utils/codequality_comparison.js
@@ -0,0 +1,41 @@
+import CodeQualityComparisonWorker from '../../workers/codequality_comparison_worker';
+
+export const parseCodeclimateMetrics = (issues = [], path = '') => {
+ return issues.map(issue => {
+ const parsedIssue = {
+ ...issue,
+ name: issue.description,
+ };
+
+ if (issue?.location?.path) {
+ let parseCodeQualityUrl = `${path}/${issue.location.path}`;
+ parsedIssue.path = issue.location.path;
+
+ if (issue?.location?.lines?.begin) {
+ parsedIssue.line = issue.location.lines.begin;
+ parseCodeQualityUrl += `#L${issue.location.lines.begin}`;
+ } else if (issue?.location?.positions?.begin?.line) {
+ parsedIssue.line = issue.location.positions.begin.line;
+ parseCodeQualityUrl += `#L${issue.location.positions.begin.line}`;
+ }
+
+ parsedIssue.urlPath = parseCodeQualityUrl;
+ }
+
+ return parsedIssue;
+ });
+};
+
+export const doCodeClimateComparison = (headIssues, baseIssues) => {
+ // Do these comparisons in worker threads to avoid blocking the main thread
+ return new Promise((resolve, reject) => {
+ const worker = new CodeQualityComparisonWorker();
+ worker.addEventListener('message', ({ data }) =>
+ data.newIssues && data.resolvedIssues ? resolve(data) : reject(data),
+ );
+ worker.postMessage({
+ headIssues,
+ baseIssues,
+ });
+ });
+};
diff --git a/app/assets/javascripts/reports/codequality_report/workers/codequality_comparison_worker.js b/app/assets/javascripts/reports/codequality_report/workers/codequality_comparison_worker.js
new file mode 100644
index 00000000000..fc55602f95c
--- /dev/null
+++ b/app/assets/javascripts/reports/codequality_report/workers/codequality_comparison_worker.js
@@ -0,0 +1,28 @@
+import { differenceBy } from 'lodash';
+
+const KEY_TO_FILTER_BY = 'fingerprint';
+
+// eslint-disable-next-line no-restricted-globals
+self.addEventListener('message', e => {
+ const { data } = e;
+
+ if (data === undefined) {
+ return null;
+ }
+
+ const { headIssues, baseIssues } = data;
+
+ if (!headIssues || !baseIssues) {
+ // eslint-disable-next-line no-restricted-globals
+ return self.postMessage({});
+ }
+
+ // eslint-disable-next-line no-restricted-globals
+ self.postMessage({
+ newIssues: differenceBy(headIssues, baseIssues, KEY_TO_FILTER_BY),
+ resolvedIssues: differenceBy(baseIssues, headIssues, KEY_TO_FILTER_BY),
+ });
+
+ // eslint-disable-next-line no-restricted-globals
+ return self.close();
+});
diff --git a/app/assets/javascripts/reports/components/grouped_test_reports_app.vue b/app/assets/javascripts/reports/components/grouped_test_reports_app.vue
index a670cad5f9f..b8a8cb940e7 100644
--- a/app/assets/javascripts/reports/components/grouped_test_reports_app.vue
+++ b/app/assets/javascripts/reports/components/grouped_test_reports_app.vue
@@ -6,7 +6,9 @@ import ReportSection from './report_section.vue';
import SummaryRow from './summary_row.vue';
import IssuesList from './issues_list.vue';
import Modal from './modal.vue';
+import { GlButton } from '@gitlab/ui';
import createStore from '../store';
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { summaryTextBuilder, reportTextBuilder, statusIcon } from '../store/utils';
export default {
@@ -17,12 +19,19 @@ export default {
SummaryRow,
IssuesList,
Modal,
+ GlButton,
},
+ mixins: [glFeatureFlagsMixin()],
props: {
endpoint: {
type: String,
required: true,
},
+ pipelinePath: {
+ type: String,
+ required: false,
+ default: '',
+ },
},
componentNames,
computed: {
@@ -43,6 +52,12 @@ export default {
return summaryTextBuilder(s__('Reports|Test summary'), this.summary);
},
+ testTabURL() {
+ return `${this.pipelinePath}/test_report`;
+ },
+ showViewFullReport() {
+ return Boolean(this.glFeatures.junitPipelineView) && this.pipelinePath.length;
+ },
},
created() {
this.setEndpoint(this.endpoint);
@@ -98,6 +113,16 @@ export default {
:has-issues="reports.length > 0"
class="mr-widget-section grouped-security-reports mr-report"
>
+ <template v-if="showViewFullReport" #actionButtons>
+ <gl-button
+ :href="testTabURL"
+ icon="external-link"
+ data-testid="group-test-reports-full-link"
+ class="gl-mr-3"
+ >
+ {{ s__('ciReport|View full report') }}
+ </gl-button>
+ </template>
<template #body>
<div class="mr-widget-grouped-section report-block">
<template v-for="(report, i) in reports">
diff --git a/app/assets/javascripts/reports/components/issue_body.js b/app/assets/javascripts/reports/components/issue_body.js
index e106e60951b..1e6dc4f8b78 100644
--- a/app/assets/javascripts/reports/components/issue_body.js
+++ b/app/assets/javascripts/reports/components/issue_body.js
@@ -1,12 +1,15 @@
import TestIssueBody from './test_issue_body.vue';
import AccessibilityIssueBody from '../accessibility_report/components/accessibility_issue_body.vue';
+import CodequalityIssueBody from '../codequality_report/components/codequality_issue_body.vue';
export const components = {
AccessibilityIssueBody,
+ CodequalityIssueBody,
TestIssueBody,
};
export const componentNames = {
AccessibilityIssueBody: AccessibilityIssueBody.name,
+ CodequalityIssueBody: CodequalityIssueBody.name,
TestIssueBody: TestIssueBody.name,
};
diff --git a/app/assets/javascripts/reports/components/report_item.vue b/app/assets/javascripts/reports/components/report_item.vue
index 51062cd7928..1b47d03aa01 100644
--- a/app/assets/javascripts/reports/components/report_item.vue
+++ b/app/assets/javascripts/reports/components/report_item.vue
@@ -52,7 +52,7 @@ export default {
v-if="showReportSectionStatusIcon"
:status="status"
:status-icon-size="statusIconSize"
- class="append-right-default"
+ class="gl-mr-3"
/>
<component :is="component" v-if="component" :issue="issue" :status="status" :is-new="isNew" />
diff --git a/app/assets/javascripts/reports/components/report_section.vue b/app/assets/javascripts/reports/components/report_section.vue
index 68956fc6d2b..63af8a5a9ac 100644
--- a/app/assets/javascripts/reports/components/report_section.vue
+++ b/app/assets/javascripts/reports/components/report_section.vue
@@ -91,6 +91,11 @@ export default {
required: false,
default: undefined,
},
+ shouldEmitToggleEvent: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
data() {
@@ -157,6 +162,9 @@ export default {
},
methods: {
toggleCollapsed() {
+ if (this.shouldEmitToggleEvent) {
+ this.$emit('toggleEvent');
+ }
this.isCollapsed = !this.isCollapsed;
},
},
@@ -171,7 +179,7 @@ export default {
<div>
{{ headerText }}
<slot :name="slotName"></slot>
- <popover v-if="hasPopover" :options="popoverOptions" class="prepend-left-5" />
+ <popover v-if="hasPopover" :options="popoverOptions" class="gl-ml-2" />
</div>
<slot name="subHeading"></slot>
</div>
diff --git a/app/assets/javascripts/reports/components/summary_row.vue b/app/assets/javascripts/reports/components/summary_row.vue
index b9fc902cd3a..3232c0edf96 100644
--- a/app/assets/javascripts/reports/components/summary_row.vue
+++ b/app/assets/javascripts/reports/components/summary_row.vue
@@ -21,7 +21,8 @@ export default {
props: {
summary: {
type: String,
- required: true,
+ required: false,
+ default: '',
},
statusIcon: {
type: String,
@@ -45,7 +46,7 @@ export default {
</script>
<template>
<div class="report-block-list-issue report-block-list-issue-parent align-items-center">
- <div class="report-block-list-icon append-right-default">
+ <div class="report-block-list-icon gl-mr-3">
<gl-loading-icon
v-if="statusIcon === 'loading'"
css-class="report-block-list-loading-icon"
@@ -58,8 +59,8 @@ export default {
class="report-block-list-issue-description-text"
data-testid="test-summary-row-description"
>
- {{ summary
- }}<span v-if="popoverOptions" class="text-nowrap"
+ <slot name="summary">{{ summary }}</slot
+ ><span v-if="popoverOptions" class="text-nowrap"
>&nbsp;<popover v-if="popoverOptions" :options="popoverOptions" class="align-top" />
</span>
</div>
diff --git a/app/assets/javascripts/reports/components/test_issue_body.vue b/app/assets/javascripts/reports/components/test_issue_body.vue
index c41238070b1..4e0631740d8 100644
--- a/app/assets/javascripts/reports/components/test_issue_body.vue
+++ b/app/assets/javascripts/reports/components/test_issue_body.vue
@@ -25,14 +25,14 @@ export default {
};
</script>
<template>
- <div class="report-block-list-issue-description prepend-top-5 append-bottom-5">
+ <div class="report-block-list-issue-description gl-mt-2 gl-mb-2">
<div class="report-block-list-issue-description-text" data-testid="test-issue-body-description">
<button
type="button"
class="btn-link btn-blank text-left break-link vulnerability-name-button"
@click="openModal({ issue })"
>
- <div v-if="isNew" class="badge badge-danger append-right-5">{{ s__('New') }}</div>
+ <div v-if="isNew" class="badge badge-danger gl-mr-2">{{ s__('New') }}</div>
{{ issue.name }}
</button>
</div>