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_merge_request_widget/extensions')
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/extensions/code_quality/index.js12
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/extensions/code_quality/index.vue15
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/extensions/test_report/index.js189
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/extensions/test_report/index.vue313
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/extensions/test_report/utils.js2
5 files changed, 332 insertions, 199 deletions
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 713c9e610b3..3af984dcf6c 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
@@ -23,14 +23,17 @@ export default {
const { newErrors, resolvedErrors, parsingInProgress } = data;
if (parsingInProgress) {
return i18n.loading;
- } else if (newErrors.length >= 1 && resolvedErrors.length >= 1) {
+ }
+ if (newErrors.length >= 1 && resolvedErrors.length >= 1) {
return i18n.improvementAndDegradationCopy(
i18n.findings(resolvedErrors, codeQualityPrefixes.fixed),
i18n.findings(newErrors, codeQualityPrefixes.new),
);
- } else if (resolvedErrors.length >= 1) {
+ }
+ if (resolvedErrors.length >= 1) {
return i18n.singularCopy(i18n.findings(resolvedErrors, codeQualityPrefixes.fixed));
- } else if (newErrors.length >= 1) {
+ }
+ if (newErrors.length >= 1) {
return i18n.singularCopy(i18n.findings(newErrors, codeQualityPrefixes.new));
}
return i18n.noChanges;
@@ -38,7 +41,8 @@ export default {
statusIcon() {
if (this.collapsedData.newErrors.length >= 1) {
return EXTENSION_ICONS.warning;
- } else if (this.collapsedData.resolvedErrors.length >= 1) {
+ }
+ if (this.collapsedData.resolvedErrors.length >= 1) {
return EXTENSION_ICONS.success;
}
return EXTENSION_ICONS.neutral;
diff --git a/app/assets/javascripts/vue_merge_request_widget/extensions/code_quality/index.vue b/app/assets/javascripts/vue_merge_request_widget/extensions/code_quality/index.vue
index d30acf24684..cd3a98effa3 100644
--- a/app/assets/javascripts/vue_merge_request_widget/extensions/code_quality/index.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/extensions/code_quality/index.vue
@@ -36,9 +36,11 @@ export default {
if (!this.pollingFinished) {
return { title: i18n.loading };
- } else if (this.hasError) {
+ }
+ if (this.hasError) {
return { title: i18n.error };
- } else if (
+ }
+ if (
this.collapsedData?.new_errors?.length >= 1 &&
this.collapsedData?.resolved_errors?.length >= 1
) {
@@ -48,11 +50,13 @@ export default {
i18n.findings(new_errors, codeQualityPrefixes.new),
),
};
- } else if (this.collapsedData?.resolved_errors?.length >= 1) {
+ }
+ if (this.collapsedData?.resolved_errors?.length >= 1) {
return {
title: i18n.singularCopy(i18n.findings(resolved_errors, codeQualityPrefixes.fixed)),
};
- } else if (this.collapsedData?.new_errors?.length >= 1) {
+ }
+ if (this.collapsedData?.new_errors?.length >= 1) {
return { title: i18n.singularCopy(i18n.findings(new_errors, codeQualityPrefixes.new)) };
}
return { title: i18n.noChanges };
@@ -95,7 +99,8 @@ export default {
statusIcon() {
if (this.collapsedData?.new_errors?.length >= 1) {
return EXTENSION_ICONS.warning;
- } else if (this.collapsedData?.resolved_errors?.length >= 1) {
+ }
+ if (this.collapsedData?.resolved_errors?.length >= 1) {
return EXTENSION_ICONS.success;
}
return EXTENSION_ICONS.neutral;
diff --git a/app/assets/javascripts/vue_merge_request_widget/extensions/test_report/index.js b/app/assets/javascripts/vue_merge_request_widget/extensions/test_report/index.js
deleted file mode 100644
index 6ac462d4ad5..00000000000
--- a/app/assets/javascripts/vue_merge_request_widget/extensions/test_report/index.js
+++ /dev/null
@@ -1,189 +0,0 @@
-import { uniqueId } from 'lodash';
-import { __ } from '~/locale';
-import axios from '~/lib/utils/axios_utils';
-import { HTTP_STATUS_NO_CONTENT } from '~/lib/utils/http_status';
-import TestCaseDetails from '~/pipelines/components/test_reports/test_case_details.vue';
-import { EXTENSION_ICONS } from '../../constants';
-import {
- summaryTextBuilder,
- reportTextBuilder,
- reportSubTextBuilder,
- countRecentlyFailedTests,
- recentFailuresTextBuilder,
- formatFilePath,
-} from './utils';
-import { i18n, TESTS_FAILED_STATUS, ERROR_STATUS } from './constants';
-
-export default {
- name: 'WidgetTestSummary',
- enablePolling: true,
- i18n,
- props: ['testResultsPath', 'headBlobPath', 'pipeline'],
- modalComponent: TestCaseDetails,
- computed: {
- failedTestNames() {
- if (!this.collapsedData?.suites) {
- return '';
- }
-
- const newFailures = this.collapsedData?.suites.flatMap((suite) => [suite.new_failures || []]);
- const fileNames = newFailures.flatMap((newFailure) => {
- return newFailure.map((failure) => {
- return failure.file;
- });
- });
-
- return fileNames.join(' ').trim();
- },
- summary(data) {
- if (data.parsingInProgress) {
- return this.$options.i18n.loading;
- }
- if (data.hasSuiteError) {
- return this.$options.i18n.error;
- }
- return {
- subject: summaryTextBuilder(this.$options.i18n.label, data.summary),
- meta: recentFailuresTextBuilder(data.summary),
- };
- },
- statusIcon(data) {
- if (data.status === TESTS_FAILED_STATUS) {
- return EXTENSION_ICONS.warning;
- }
- if (data.hasSuiteError) {
- return EXTENSION_ICONS.failed;
- }
- return EXTENSION_ICONS.success;
- },
- tertiaryButtons() {
- const actionButtons = [];
-
- if (this.failedTestNames().length > 0) {
- actionButtons.push({
- dataClipboardText: this.failedTestNames(),
- id: uniqueId('copy-to-clipboard'),
- icon: 'copy-to-clipboard',
- testId: 'copy-failed-specs-btn',
- text: this.$options.i18n.copyFailedSpecs,
- tooltipText: this.$options.i18n.copyFailedSpecsTooltip,
- tooltipOnClick: __('Copied'),
- });
- }
-
- actionButtons.push({
- text: this.$options.i18n.fullReport,
- href: `${this.pipeline.path}/test_report`,
- target: '_blank',
- trackFullReportClicked: true,
- testId: 'full-report-link',
- });
-
- return actionButtons;
- },
- },
- methods: {
- fetchCollapsedData() {
- return axios.get(this.testResultsPath).then((response) => {
- const { data = {}, status } = response;
- const { suites = [], summary = {} } = data;
-
- return {
- ...response,
- data: {
- hasSuiteError: suites.some((suite) => suite.status === ERROR_STATUS),
- parsingInProgress: status === HTTP_STATUS_NO_CONTENT,
- ...data,
- summary: {
- recentlyFailed: countRecentlyFailedTests(suites),
- ...summary,
- },
- },
- };
- });
- },
- fetchFullData() {
- return Promise.resolve(this.prepareReports());
- },
- suiteIcon(suite) {
- if (suite.status === ERROR_STATUS) {
- return EXTENSION_ICONS.error;
- }
- if (suite.status === TESTS_FAILED_STATUS) {
- return EXTENSION_ICONS.failed;
- }
- return EXTENSION_ICONS.success;
- },
- testHeader(test, sectionHeader, index) {
- const headers = [];
- if (index === 0) {
- headers.push(sectionHeader);
- }
- if (test.recent_failures?.count && test.recent_failures?.base_branch) {
- headers.push(i18n.recentFailureCount(test.recent_failures));
- }
- return headers;
- },
- mapTestAsChild({ iconName, sectionHeader }) {
- return (test, index) => {
- return {
- id: uniqueId('test-'),
- header: this.testHeader(test, sectionHeader, index),
- modal: {
- text: test.name,
- onClick: () => {
- this.modalData = {
- testCase: {
- filePath: test.file && `${this.headBlobPath}/${formatFilePath(test.file)}`,
- ...test,
- },
- };
- },
- },
- icon: { name: iconName },
- };
- };
- },
- prepareReports() {
- return this.collapsedData.suites
- .map((suite) => {
- return {
- ...suite,
- summary: {
- recentlyFailed: countRecentlyFailedTests(suite),
- ...suite.summary,
- },
- };
- })
- .map((suite) => {
- return {
- id: uniqueId('suite-'),
- text: reportTextBuilder(suite),
- subtext: reportSubTextBuilder(suite),
- icon: {
- name: this.suiteIcon(suite),
- },
- children: [
- ...[...suite.new_failures, ...suite.new_errors].map(
- this.mapTestAsChild({
- sectionHeader: i18n.newHeader,
- iconName: EXTENSION_ICONS.failed,
- }),
- ),
- ...[...suite.existing_failures, ...suite.existing_errors].map(
- this.mapTestAsChild({
- iconName: EXTENSION_ICONS.failed,
- }),
- ),
- ...[...suite.resolved_failures, ...suite.resolved_errors].map(
- this.mapTestAsChild({
- sectionHeader: i18n.fixedHeader,
- iconName: EXTENSION_ICONS.success,
- }),
- ),
- ],
- };
- });
- },
- },
-};
diff --git a/app/assets/javascripts/vue_merge_request_widget/extensions/test_report/index.vue b/app/assets/javascripts/vue_merge_request_widget/extensions/test_report/index.vue
new file mode 100644
index 00000000000..1b03b9c04e1
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/extensions/test_report/index.vue
@@ -0,0 +1,313 @@
+<script>
+import { uniqueId, uniq } from 'lodash';
+import { __ } from '~/locale';
+import axios from '~/lib/utils/axios_utils';
+import { HTTP_STATUS_NO_CONTENT } from '~/lib/utils/http_status';
+import TestCaseDetails from '~/ci/pipeline_details/test_reports/test_case_details.vue';
+import MrWidget from '~/vue_merge_request_widget/components/widget/widget.vue';
+import MrWidgetRow from '~/vue_merge_request_widget/components/widget/widget_content_row.vue';
+import { DynamicScroller, DynamicScrollerItem } from 'vendor/vue-virtual-scroller';
+import { EXTENSION_ICONS } from '../../constants';
+import {
+ summaryTextBuilder,
+ reportTextBuilder,
+ reportSubTextBuilder,
+ countRecentlyFailedTests,
+ recentFailuresTextBuilder,
+ formatFilePath,
+} from './utils';
+import { i18n, TESTS_FAILED_STATUS, ERROR_STATUS } from './constants';
+
+export default {
+ name: 'WidgetTestReport',
+ components: {
+ MrWidget,
+ MrWidgetRow,
+ DynamicScroller,
+ DynamicScrollerItem,
+ TestCaseDetails,
+ },
+ i18n,
+ props: {
+ mr: {
+ type: Object,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ collapsedData: {},
+ suites: [],
+ modalData: null,
+ };
+ },
+ computed: {
+ failedTestNames() {
+ const { data: { suites = [] } = {} } = this.collapsedData;
+
+ if (!this.hasSuites) {
+ return '';
+ }
+
+ const newFailures = suites.flatMap((suite) => [suite.new_failures || []]);
+ const fileNames = newFailures.flatMap((newFailure) => {
+ return newFailure.map((failure) => {
+ return failure.file;
+ });
+ });
+
+ return uniq(fileNames).join(' ').trim();
+ },
+ summary() {
+ const {
+ data: { parsingInProgress = false, hasSuiteError = false, summary = {} } = {},
+ } = this.collapsedData;
+
+ if (parsingInProgress) {
+ return { title: this.$options.i18n.loading };
+ }
+ if (hasSuiteError) {
+ return { title: this.$options.i18n.error };
+ }
+ return {
+ title: summaryTextBuilder(this.$options.i18n.label, summary),
+ subtitle: recentFailuresTextBuilder(summary),
+ };
+ },
+ statusIcon() {
+ const { data: { status = null, hasSuiteError = false } = {} } = this.collapsedData;
+
+ if (status === TESTS_FAILED_STATUS) {
+ return EXTENSION_ICONS.warning;
+ }
+ if (hasSuiteError) {
+ return EXTENSION_ICONS.failed;
+ }
+ return EXTENSION_ICONS.success;
+ },
+ tertiaryButtons() {
+ const actionButtons = [];
+
+ if (this.failedTestNames.length > 0) {
+ actionButtons.push({
+ dataClipboardText: this.failedTestNames,
+ id: uniqueId('copy-to-clipboard'),
+ icon: 'copy-to-clipboard',
+ testId: 'copy-failed-specs-btn',
+ text: this.$options.i18n.copyFailedSpecs,
+ tooltipText: this.$options.i18n.copyFailedSpecsTooltip,
+ tooltipOnClick: __('Copied'),
+ });
+ }
+
+ actionButtons.push({
+ text: this.$options.i18n.fullReport,
+ href: `${this.mr.pipeline.path}/test_report`,
+ target: '_blank',
+ trackFullReportClicked: true,
+ testId: 'full-report-link',
+ });
+
+ return actionButtons;
+ },
+ testResultsPath() {
+ return this.mr.testResultsPath;
+ },
+ hasSuites() {
+ return this.suites.length > 0;
+ },
+ },
+ methods: {
+ fetchCollapsedData() {
+ return axios.get(this.testResultsPath).then((response) => {
+ const { data = {}, status } = response;
+ const { suites = [], summary = {} } = data;
+
+ this.collapsedData = {
+ ...response,
+ data: {
+ hasSuiteError: suites.some((suite) => suite.status === ERROR_STATUS),
+ parsingInProgress: status === HTTP_STATUS_NO_CONTENT,
+ ...data,
+ summary: {
+ recentlyFailed: countRecentlyFailedTests(suites),
+ ...summary,
+ },
+ },
+ };
+ this.suites = this.prepareSuites(this.collapsedData);
+
+ return response;
+ });
+ },
+ suiteIcon(suite) {
+ if (suite.status === ERROR_STATUS) {
+ return EXTENSION_ICONS.error;
+ }
+ if (suite.status === TESTS_FAILED_STATUS) {
+ return EXTENSION_ICONS.failed;
+ }
+ return EXTENSION_ICONS.success;
+ },
+ testHeader(test, sectionHeader, index) {
+ const headers = [];
+ if (index === 0) {
+ headers.push(sectionHeader);
+ }
+ if (test.recent_failures?.count && test.recent_failures?.base_branch) {
+ headers.push(i18n.recentFailureCount(test.recent_failures));
+ }
+ return headers;
+ },
+ mapTestAsChild({ iconName, sectionHeader }) {
+ return (test, index) => {
+ return {
+ id: uniqueId('test-'),
+ header: this.testHeader(test, sectionHeader, index),
+ text: test.name,
+ actions: [
+ {
+ text: __('View details'),
+ onClick: () => {
+ this.modalData = {
+ testCase: {
+ filePath: test.file && `${this.mr.headBlobPath}/${formatFilePath(test.file)}`,
+ ...test,
+ },
+ };
+ },
+ },
+ ],
+ icon: { name: iconName },
+ };
+ };
+ },
+ onModalHidden() {
+ this.modalData = null;
+ },
+ prepareSuites(collapsedData) {
+ const {
+ data: { suites = [] },
+ } = collapsedData;
+
+ return suites
+ .map((suite) => {
+ return {
+ ...suite,
+ summary: {
+ recentlyFailed: countRecentlyFailedTests(suite),
+ ...suite.summary,
+ },
+ };
+ })
+ .map((suite) => {
+ return {
+ id: uniqueId('suite-'),
+ text: reportTextBuilder(suite),
+ subtext: reportSubTextBuilder(suite),
+ icon: {
+ name: this.suiteIcon(suite),
+ },
+ children: [
+ ...[...suite.new_failures, ...suite.new_errors].map(
+ this.mapTestAsChild({
+ sectionHeader: i18n.newHeader,
+ iconName: EXTENSION_ICONS.failed,
+ }),
+ ),
+ ...[...suite.existing_failures, ...suite.existing_errors].map(
+ this.mapTestAsChild({
+ iconName: EXTENSION_ICONS.failed,
+ }),
+ ),
+ ...[...suite.resolved_failures, ...suite.resolved_errors].map(
+ this.mapTestAsChild({
+ sectionHeader: i18n.fixedHeader,
+ iconName: EXTENSION_ICONS.success,
+ }),
+ ),
+ ],
+ };
+ });
+ },
+ },
+};
+</script>
+<template>
+ <div>
+ <mr-widget
+ :error-text="$options.i18n.error"
+ :status-icon-name="statusIcon"
+ :loading-text="$options.i18n.loading"
+ :action-buttons="tertiaryButtons"
+ :help-popover="$options.helpPopover"
+ :widget-name="$options.name"
+ :summary="summary"
+ :fetch-collapsed-data="fetchCollapsedData"
+ :is-collapsible="hasSuites"
+ >
+ <template #content>
+ <mr-widget-row
+ v-for="suite in suites"
+ :key="suite.id"
+ :level="2"
+ :status-icon-name="suite.icon.name"
+ :widget-name="$options.name"
+ data-testid="extension-list-item"
+ >
+ <template #header>
+ <div class="gl-flex-direction-column">
+ <div>{{ suite.text }}</div>
+ <div
+ v-for="(subtext, i) in suite.subtext"
+ :key="`${suite.id}-subtext-${i}`"
+ class="gl-font-sm gl-text-gray-700"
+ >
+ {{ subtext }}
+ </div>
+ </div>
+ </template>
+ <template #body>
+ <div v-if="suite.children.length > 0" class="gl-mt-2 gl-w-full">
+ <dynamic-scroller
+ :items="suite.children"
+ :min-item-size="32"
+ :style="{ maxHeight: '170px' }"
+ key-field="id"
+ class="gl-pr-5"
+ >
+ <template #default="{ item, active }">
+ <dynamic-scroller-item :item="item" :active="active">
+ <strong
+ v-for="(headerText, i) in item.header"
+ :key="`${item.id}-headerText-${i}`"
+ class="gl-display-block gl-mt-2"
+ >
+ {{ headerText }}
+ </strong>
+ <mr-widget-row
+ :key="item.id"
+ :level="3"
+ :widget-name="$options.name"
+ :status-icon-name="item.icon.name"
+ :action-buttons="item.actions"
+ class="gl-mt-2"
+ >
+ <template #header>{{ item.text }}</template>
+ </mr-widget-row>
+ </dynamic-scroller-item>
+ </template>
+ </dynamic-scroller>
+ </div>
+ </template>
+ </mr-widget-row>
+ </template>
+ </mr-widget>
+ <test-case-details
+ :modal-id="`modal${$options.name}`"
+ :visible="modalData !== null"
+ v-bind="modalData"
+ @hidden="onModalHidden"
+ />
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/extensions/test_report/utils.js b/app/assets/javascripts/vue_merge_request_widget/extensions/test_report/utils.js
index 37f9964d23a..24f6b3e69ff 100644
--- a/app/assets/javascripts/vue_merge_request_widget/extensions/test_report/utils.js
+++ b/app/assets/javascripts/vue_merge_request_widget/extensions/test_report/utils.js
@@ -62,7 +62,7 @@ export const reportSubTextBuilder = ({ suite_errors: suiteErrors, summary }) =>
}
return errors;
}
- return recentFailuresTextBuilder(summary);
+ return [recentFailuresTextBuilder(summary)];
};
export const countRecentlyFailedTests = (subject) => {