diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-11-19 11:27:35 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-11-19 11:27:35 +0300 |
commit | 7e9c479f7de77702622631cff2628a9c8dcbc627 (patch) | |
tree | c8f718a08e110ad7e1894510980d2155a6549197 /app/assets/javascripts/reports | |
parent | e852b0ae16db4052c1c567d9efa4facc81146e88 (diff) |
Add latest changes from gitlab-org/gitlab@13-6-stable-eev13.6.0-rc42
Diffstat (limited to 'app/assets/javascripts/reports')
9 files changed, 170 insertions, 19 deletions
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 index 0c758ee2b5c..d0a5615bb57 100644 --- a/app/assets/javascripts/reports/codequality_report/components/codequality_issue_body.vue +++ b/app/assets/javascripts/reports/codequality_report/components/codequality_issue_body.vue @@ -3,15 +3,21 @@ * Renders Code quality body text * Fixed: [name] in [link]:[line] */ +import { GlIcon, GlTooltipDirective } from '@gitlab/ui'; import ReportLink from '~/reports/components/report_link.vue'; import { STATUS_SUCCESS } from '~/reports/constants'; +import { s__ } from '~/locale'; +import { SEVERITY_CLASSES, SEVERITY_ICONS } from '../constants'; export default { name: 'CodequalityIssueBody', - components: { + GlIcon, ReportLink, }, + directives: { + tooltip: GlTooltipDirective, + }, props: { status: { type: String, @@ -23,20 +29,44 @@ export default { }, }, computed: { + issueName() { + return `${this.severityLabel} - ${this.issue.name}`; + }, isStatusSuccess() { return this.status === STATUS_SUCCESS; }, + severityClass() { + return SEVERITY_CLASSES[this.issue.severity] || SEVERITY_CLASSES.unknown; + }, + severityIcon() { + return SEVERITY_ICONS[this.issue.severity] || SEVERITY_ICONS.unknown; + }, + severityLabel() { + return this.$options.severityText[this.issue.severity] || this.$options.severityText.unknown; + }, + }, + severityText: { + info: s__('severity|Info'), + minor: s__('severity|Minor'), + major: s__('severity|Major'), + critical: s__('severity|Critical'), + blocker: s__('severity|Blocker'), + unknown: s__('severity|Unknown'), }, }; </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> + <div class="gl-display-flex gl-mt-2 gl-mb-2 gl-w-full"> + <span :class="severityClass" class="gl-mr-5" data-testid="codequality-severity-icon"> + <gl-icon v-tooltip="severityLabel" :name="severityIcon" :size="12" /> + </span> + <div class="gl-flex-fill-1"> + <div> + <strong v-if="isStatusSuccess">{{ s__('ciReport|Fixed:') }}</strong> + {{ issueName }} + </div> - {{ issue.name }} + <report-link v-if="issue.path" :issue="issue" /> </div> - - <report-link v-if="issue.path" :issue="issue" /> </div> </template> diff --git a/app/assets/javascripts/reports/codequality_report/constants.js b/app/assets/javascripts/reports/codequality_report/constants.js new file mode 100644 index 00000000000..502977e714c --- /dev/null +++ b/app/assets/javascripts/reports/codequality_report/constants.js @@ -0,0 +1,17 @@ +export const SEVERITY_CLASSES = { + info: 'text-primary-400', + minor: 'text-warning-200', + major: 'text-warning-400', + critical: 'text-danger-600', + blocker: 'text-danger-800', + unknown: 'text-secondary-400', +}; + +export const SEVERITY_ICONS = { + info: 'severity-info', + minor: 'severity-low', + major: 'severity-medium', + critical: 'severity-high', + blocker: 'severity-critical', + unknown: 'severity-unknown', +}; 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 index f3d5b1a80f8..5c8f31d7da0 100644 --- a/app/assets/javascripts/reports/codequality_report/grouped_codequality_reports_app.vue +++ b/app/assets/javascripts/reports/codequality_report/grouped_codequality_reports_app.vue @@ -78,6 +78,7 @@ export default { :has-issues="hasCodequalityIssues" :component="$options.componentNames.CodequalityIssueBody" :popover-options="codequalityPopover" + :show-report-section-status-icon="false" class="js-codequality-widget mr-widget-border-top mr-report" /> </template> diff --git a/app/assets/javascripts/reports/codequality_report/store/getters.js b/app/assets/javascripts/reports/codequality_report/store/getters.js index 5df58c7f85f..d7c31bcf459 100644 --- a/app/assets/javascripts/reports/codequality_report/store/getters.js +++ b/app/assets/javascripts/reports/codequality_report/store/getters.js @@ -1,5 +1,6 @@ import { LOADING, ERROR, SUCCESS } from '../../constants'; import { sprintf, __, s__, n__ } from '~/locale'; +import { spriteIcon } from '~/lib/utils/common_utils'; export const hasCodequalityIssues = state => Boolean(state.newIssues?.length || state.resolvedIssues?.length); @@ -48,7 +49,7 @@ export const codequalityPopover = state => { 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>', + linkEndTag: `${spriteIcon('external-link', 's16')}</a>`, }, false, ), 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 47f04019595..c13df60198b 100644 --- a/app/assets/javascripts/reports/components/grouped_test_reports_app.vue +++ b/app/assets/javascripts/reports/components/grouped_test_reports_app.vue @@ -1,5 +1,6 @@ <script> import { mapActions, mapGetters, mapState } from 'vuex'; +import { once } from 'lodash'; import { GlButton } from '@gitlab/ui'; import { sprintf, s__ } from '~/locale'; import { componentNames } from './issue_body'; @@ -8,8 +9,14 @@ import SummaryRow from './summary_row.vue'; import IssuesList from './issues_list.vue'; import Modal from './modal.vue'; import createStore from '../store'; +import Tracking from '~/tracking'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; -import { summaryTextBuilder, reportTextBuilder, statusIcon } from '../store/utils'; +import { + summaryTextBuilder, + reportTextBuilder, + statusIcon, + recentFailuresTextBuilder, +} from '../store/utils'; export default { name: 'GroupedTestReportsApp', @@ -21,7 +28,7 @@ export default { Modal, GlButton, }, - mixins: [glFeatureFlagsMixin()], + mixins: [glFeatureFlagsMixin(), Tracking.mixin()], props: { endpoint: { type: String, @@ -58,6 +65,11 @@ export default { showViewFullReport() { return this.pipelinePath.length; }, + handleToggleEvent() { + return once(() => { + this.track(this.$options.expandEvent); + }); + }, }, created() { this.setEndpoint(this.endpoint); @@ -79,6 +91,12 @@ export default { return reportTextBuilder(name, summary); }, + hasRecentFailures(summary) { + return this.glFeatures.testFailureHistory && summary?.recentlyFailed > 0; + }, + recentFailuresText(summary) { + return recentFailuresTextBuilder(summary); + }, getReportIcon(report) { return statusIcon(report.status); }, @@ -102,6 +120,7 @@ export default { return report.resolved_failures.concat(report.resolved_errors); }, }, + expandEvent: 'expand_test_report_widget', }; </script> <template> @@ -111,9 +130,11 @@ export default { :loading-text="groupedSummaryText" :error-text="groupedSummaryText" :has-issues="reports.length > 0" + :should-emit-toggle-event="true" class="mr-widget-section grouped-security-reports mr-report" + @toggleEvent="handleToggleEvent" > - <template v-if="showViewFullReport" #actionButtons> + <template v-if="showViewFullReport" #action-buttons> <gl-button :href="testTabURL" target="_blank" @@ -124,14 +145,22 @@ export default { {{ s__('ciReport|View full report') }} </gl-button> </template> + <template v-if="hasRecentFailures(summary)" #sub-heading> + {{ recentFailuresText(summary) }} + </template> <template #body> <div class="mr-widget-grouped-section report-block"> <template v-for="(report, i) in reports"> - <summary-row - :key="`summary-row-${i}`" - :summary="reportText(report)" - :status-icon="getReportIcon(report)" - /> + <summary-row :key="`summary-row-${i}`" :status-icon="getReportIcon(report)"> + <template #summary> + <div class="gl-display-inline-flex gl-flex-direction-column"> + <div>{{ reportText(report) }}</div> + <div v-if="hasRecentFailures(report.summary)"> + {{ recentFailuresText(report.summary) }} + </div> + </div> + </template> + </summary-row> <issues-list v-if="shouldRenderIssuesList(report)" :key="`issues-list-${i}`" diff --git a/app/assets/javascripts/reports/components/report_section.vue b/app/assets/javascripts/reports/components/report_section.vue index 63af8a5a9ac..f245e2bfd2f 100644 --- a/app/assets/javascripts/reports/components/report_section.vue +++ b/app/assets/javascripts/reports/components/report_section.vue @@ -181,14 +181,15 @@ export default { <slot :name="slotName"></slot> <popover v-if="hasPopover" :options="popoverOptions" class="gl-ml-2" /> </div> - <slot name="subHeading"></slot> + <slot name="sub-heading"></slot> </div> - <slot name="actionButtons"></slot> + <slot name="action-buttons"></slot> <button v-if="isCollapsible" type="button" + data-testid="report-section-expand-button" class="js-collapse-btn btn float-right btn-sm align-self-center qa-expand-report-button" @click="toggleCollapsed" > diff --git a/app/assets/javascripts/reports/components/test_issue_body.vue b/app/assets/javascripts/reports/components/test_issue_body.vue index 4e0631740d8..5e9a5b03543 100644 --- a/app/assets/javascripts/reports/components/test_issue_body.vue +++ b/app/assets/javascripts/reports/components/test_issue_body.vue @@ -1,8 +1,15 @@ <script> import { mapActions } from 'vuex'; +import { GlBadge } from '@gitlab/ui'; +import { n__ } from '~/locale'; +import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; export default { name: 'TestIssueBody', + components: { + GlBadge, + }, + mixins: [glFeatureFlagsMixin()], props: { issue: { type: Object, @@ -19,8 +26,20 @@ export default { default: false, }, }, + computed: { + showRecentFailures() { + return this.glFeatures.testFailureHistory && this.issue.recent_failures; + }, + }, methods: { ...mapActions(['openModal']), + recentFailuresText(count) { + return n__( + 'Failed %d time in the last 14 days', + 'Failed %d times in the last 14 days', + count, + ); + }, }, }; </script> @@ -32,7 +51,10 @@ export default { class="btn-link btn-blank text-left break-link vulnerability-name-button" @click="openModal({ issue })" > - <div v-if="isNew" class="badge badge-danger gl-mr-2">{{ s__('New') }}</div> + <gl-badge v-if="isNew" variant="danger" class="gl-mr-2">{{ s__('New') }}</gl-badge> + <gl-badge v-if="showRecentFailures" variant="warning" class="gl-mr-2"> + {{ recentFailuresText(issue.recent_failures) }} + </gl-badge> {{ issue.name }} </button> </div> diff --git a/app/assets/javascripts/reports/store/mutations.js b/app/assets/javascripts/reports/store/mutations.js index 35ab72bf694..acaa98754b0 100644 --- a/app/assets/javascripts/reports/store/mutations.js +++ b/app/assets/javascripts/reports/store/mutations.js @@ -1,4 +1,5 @@ import * as types from './mutation_types'; +import { countRecentlyFailedTests } from './utils'; export default { [types.SET_ENDPOINT](state, endpoint) { @@ -16,9 +17,15 @@ export default { state.summary.resolved = response.summary.resolved; state.summary.failed = response.summary.failed; state.summary.errored = response.summary.errored; + state.summary.recentlyFailed = countRecentlyFailedTests(response.suites); state.status = response.status; state.reports = response.suites; + + state.reports.forEach((report, i) => { + if (!state.reports[i].summary) return; + state.reports[i].summary.recentlyFailed = countRecentlyFailedTests(report); + }); }, [types.RECEIVE_REPORTS_ERROR](state) { state.isLoading = false; @@ -30,6 +37,7 @@ export default { resolved: 0, failed: 0, errored: 0, + recentlyFailed: 0, }; state.status = null; }, diff --git a/app/assets/javascripts/reports/store/utils.js b/app/assets/javascripts/reports/store/utils.js index 5d3d9ddda3b..fd6f4933cfa 100644 --- a/app/assets/javascripts/reports/store/utils.js +++ b/app/assets/javascripts/reports/store/utils.js @@ -48,6 +48,48 @@ export const reportTextBuilder = (name = '', results = {}) => { return sprintf(__('%{name} found %{resultsString}'), { name, resultsString }); }; +export const recentFailuresTextBuilder = (summary = {}) => { + const { failed, recentlyFailed } = summary; + if (!failed || !recentlyFailed) return ''; + + if (failed < 2) { + return sprintf( + s__( + 'Reports|%{recentlyFailed} out of %{failed} failed test has failed more than once in the last 14 days', + ), + { recentlyFailed, failed }, + ); + } + return sprintf( + n__( + s__( + 'Reports|%{recentlyFailed} out of %{failed} failed tests has failed more than once in the last 14 days', + ), + s__( + 'Reports|%{recentlyFailed} out of %{failed} failed tests have failed more than once in the last 14 days', + ), + recentlyFailed, + ), + { recentlyFailed, failed }, + ); +}; + +export const countRecentlyFailedTests = subject => { + // handle either a single report or an array of reports + const reports = !subject.length ? [subject] : subject; + + return reports + .map(report => { + return ( + [report.new_failures, report.existing_failures, report.resolved_failures] + // only count tests which have failed more than once + .map(failureArray => failureArray.filter(failure => failure.recent_failures > 1).length) + .reduce((total, count) => total + count, 0) + ); + }) + .reduce((total, count) => total + count, 0); +}; + export const statusIcon = status => { if (status === STATUS_FAILED) { return ICON_WARNING; |