diff options
Diffstat (limited to 'app/assets/javascripts/vue_merge_request_widget')
16 files changed, 240 insertions, 28 deletions
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals_summary.vue b/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals_summary.vue index 25dbb614c1d..0e31f97b9db 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals_summary.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals_summary.vue @@ -102,7 +102,11 @@ export default { <template v-if="hasApprovers"> <span v-if="approvalLeftMessage">{{ message }}</span> <strong v-else>{{ message }}</strong> - <user-avatar-list class="d-inline-block align-middle" :items="approvers" /> + <user-avatar-list + class="gl-display-inline-block gl-vertical-align-middle" + :img-size="24" + :items="approvers" + /> </template> </div> </template> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue b/app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue index 684386883c8..f1b89c42fb5 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue @@ -66,7 +66,15 @@ export default { return this.loadingState === LOADING_STATES.expandedLoading; }, isCollapsible() { - return !this.isLoadingSummary && this.loadingState !== LOADING_STATES.collapsedError; + if (!this.isLoadingSummary && this.loadingState !== LOADING_STATES.collapsedError) { + if (this.shouldCollapse) { + return this.shouldCollapse(); + } + + return true; + } + + return false; }, hasFullData() { return this.fullData.length > 0; @@ -86,7 +94,7 @@ export default { ); }, statusIconName() { - if (this.hasFetchError) return EXTENSION_ICONS.error; + if (this.hasFetchError) return EXTENSION_ICONS.failed; if (this.isLoadingSummary) return null; return this.statusIcon(this.collapsedData); @@ -128,7 +136,7 @@ export default { } }), toggleCollapsed(e) { - if (!e?.target?.closest('.btn:not(.btn-icon),a')) { + if (this.isCollapsible && !e?.target?.closest('.btn:not(.btn-icon),a')) { this.isCollapsed = !this.isCollapsed; this.triggerRedisTracking(); @@ -214,7 +222,7 @@ export default { // To allow for text to be selected we check if the the user is clicking // or selecting, if they are selecting the time difference should be // more than 200ms - if (up - this.down < 200) { + if (up - this.down < 200 && !e?.target?.closest('.btn-icon')) { this.toggleCollapsed(e); } }, @@ -226,7 +234,12 @@ export default { <template> <section class="media-section" data-testid="widget-extension"> - <div class="media gl-p-5 gl-cursor-pointer" @mousedown="onRowMouseDown" @mouseup="onRowMouseUp"> + <div + :class="{ 'gl-cursor-pointer': isCollapsible }" + class="media gl-p-5" + @mousedown="onRowMouseDown" + @mouseup="onRowMouseUp" + > <status-icon :name="$options.label || $options.name" :is-loading="isLoadingSummary" @@ -264,7 +277,7 @@ export default { category="tertiary" data-testid="toggle-button" size="small" - @click.self="toggleCollapsed" + @click="toggleCollapsed" /> </div> </div> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/extensions/child_content.vue b/app/assets/javascripts/vue_merge_request_widget/components/extensions/child_content.vue index 5f42c6c7acb..5cfee21dd5e 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/extensions/child_content.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/extensions/child_content.vue @@ -55,19 +55,21 @@ export default { <div class="gl-display-flex"> <status-icon v-if="data.icon" :icon-name="data.icon.name" :size="12" class="gl-pl-0" /> <div class="gl-w-full"> - <div class="gl-flex-wrap gl-display-flex gl-w-full"> - <div class="gl-mr-4 gl-display-flex gl-align-items-center"> - <p v-safe-html="generateText(data.text)" class="gl-m-0"></p> + <div class="gl-display-flex gl-flex-nowrap"> + <div class="gl-flex-wrap gl-display-flex gl-w-full"> + <div class="gl-mr-4 gl-display-flex gl-align-items-center"> + <p v-safe-html="generateText(data.text)" class="gl-m-0"></p> + </div> + <div v-if="data.link"> + <gl-link :href="data.link.href">{{ data.link.text }}</gl-link> + </div> + <div v-if="data.supportingText"> + <p v-safe-html="generateText(data.supportingText)" class="gl-m-0"></p> + </div> + <gl-badge v-if="data.badge" :variant="data.badge.variant || 'info'"> + {{ data.badge.text }} + </gl-badge> </div> - <div v-if="data.link"> - <gl-link :href="data.link.href">{{ data.link.text }}</gl-link> - </div> - <div v-if="data.supportingText"> - <p v-safe-html="generateText(data.supportingText)" class="gl-m-0"></p> - </div> - <gl-badge v-if="data.badge" :variant="data.badge.variant || 'info'"> - {{ data.badge.text }} - </gl-badge> <actions :widget="widgetLabel" :tertiary-buttons="data.actions" class="gl-ml-auto" /> </div> <p diff --git a/app/assets/javascripts/vue_merge_request_widget/components/extensions/utils.js b/app/assets/javascripts/vue_merge_request_widget/components/extensions/utils.js index 8ba13cf8252..5fba070f79c 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/extensions/utils.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/extensions/utils.js @@ -32,7 +32,7 @@ const textStyleTags = { [getStartTag('critical')]: '<span class="gl-font-weight-bold gl-text-red-800">', [getStartTag('same')]: '<span class="gl-font-weight-bold gl-text-gray-700">', [getStartTag('strong')]: '<span class="gl-font-weight-bold">', - [getStartTag('small')]: '<span class="gl-font-sm">', + [getStartTag('small')]: '<span class="gl-font-sm gl-text-gray-700">', }; export const generateText = (text) => { diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_how_to_merge_modal.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_how_to_merge_modal.vue index b062833cdf8..e906b8c3b59 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_how_to_merge_modal.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_how_to_merge_modal.vue @@ -79,7 +79,7 @@ export default { }, data() { return { - resolveConflictsFromCli: helpPagePath('ee/user/project/merge_requests/conflicts.html', { + resolveConflictsFromCli: helpPagePath('user/project/merge_requests/conflicts', { anchor: 'resolve-conflicts-from-the-command-line', }), }; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue index 2e3a02b1712..9499603163b 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue @@ -1,7 +1,7 @@ <script> import { GlButton, GlLoadingIcon } from '@gitlab/ui'; import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; -import ciIcon from '../../vue_shared/components/ci_icon.vue'; +import ciIcon from '~/vue_shared/components/ci_icon.vue'; export default { components: { diff --git a/app/assets/javascripts/vue_merge_request_widget/components/source_branch_removal_status.vue b/app/assets/javascripts/vue_merge_request_widget/components/source_branch_removal_status.vue index caafd6b995e..e86724d133a 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/source_branch_removal_status.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/source_branch_removal_status.vue @@ -1,6 +1,6 @@ <script> import { GlIcon, GlTooltipDirective } from '@gitlab/ui'; -import { __ } from '../../locale'; +import { __ } from '~/locale'; export default { i18n: { diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merging.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merging.vue index e0c4679b983..887d1aab524 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merging.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merging.vue @@ -1,7 +1,7 @@ <script> import { refreshUserMergeRequestCounts } from '~/commons/nav/user_merge_requests'; import simplePoll from '~/lib/utils/simple_poll'; -import MergeRequest from '../../../merge_request'; +import MergeRequest from '~/merge_request'; import eventHub from '../../event_hub'; import { MERGE_ACTIVE_STATUS_PHRASES, STATE_MACHINE } from '../../constants'; import statusIcon from '../mr_widget_status_icon.vue'; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue index ebdc8309cd5..3511fffcfbb 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue @@ -3,7 +3,7 @@ import { GlButton, GlSkeletonLoader } from '@gitlab/ui'; import createFlash from '~/flash'; import { __ } from '~/locale'; import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; -import simplePoll from '../../../lib/utils/simple_poll'; +import simplePoll from '~/lib/utils/simple_poll'; import eventHub from '../../event_hub'; import mergeRequestQueryVariablesMixin from '../../mixins/merge_request_query_variables'; import rebaseQuery from '../../queries/states/rebase.query.graphql'; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/nothing_to_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/nothing_to_merge.vue index e43319d42ca..4902c9b45e8 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/nothing_to_merge.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/nothing_to_merge.vue @@ -28,7 +28,7 @@ export default { api.trackRedisHllUserEvent('i_code_review_widget_nothing_merge_click_new_file'); }, }, - ciHelpPage: helpPagePath('/ci/quick_start/index.html'), + ciHelpPage: helpPagePath('ci/quick_start/index.html'), safeHtmlConfig: { ADD_TAGS: ['use'] }, }; </script> diff --git a/app/assets/javascripts/vue_merge_request_widget/extensions/issues.js b/app/assets/javascripts/vue_merge_request_widget/extensions/issues.js index e52f2c2c666..6ca0ea9c4e7 100644 --- a/app/assets/javascripts/vue_merge_request_widget/extensions/issues.js +++ b/app/assets/javascripts/vue_merge_request_widget/extensions/issues.js @@ -48,6 +48,9 @@ export default { { text: 'Full report', href: this.conflictsDocsPath, target: '_blank' }, ]; }, + shouldCollapse() { + return true; + }, }, methods: { // Fetches the collapsed data diff --git a/app/assets/javascripts/vue_merge_request_widget/extensions/test_report/constants.js b/app/assets/javascripts/vue_merge_request_widget/extensions/test_report/constants.js new file mode 100644 index 00000000000..cd5cfb6837c --- /dev/null +++ b/app/assets/javascripts/vue_merge_request_widget/extensions/test_report/constants.js @@ -0,0 +1,39 @@ +import { __, n__, s__, sprintf } from '~/locale'; + +const digitText = (bold = false) => (bold ? '%{strong_start}%d%{strong_end}' : '%d'); +const noText = (bold = false) => (bold ? '%{strong_start}no%{strong_end}' : 'no'); + +export const TESTS_FAILED_STATUS = 'failed'; +export const ERROR_STATUS = 'error'; + +export const i18n = { + label: s__('Reports|Test summary'), + loading: s__('Reports|Test summary results are loading'), + error: s__('Reports|Test summary failed to load results'), + fullReport: s__('Reports|Full report'), + + noChanges: (bold) => s__(`Reports|${noText(bold)} changed test results`), + resultsString: (combinedString, resolvedString) => + sprintf(s__('Reports|%{combinedString} and %{resolvedString}'), { + combinedString, + resolvedString, + }), + + summaryText: (name, resultsString) => + sprintf(__('%{name}: %{resultsString}'), { name, resultsString }), + + failedClause: (failed, bold) => + n__(`${digitText(bold)} failed`, `${digitText(bold)} failed`, failed), + erroredClause: (errored, bold) => + n__(`${digitText(bold)} error`, `${digitText(bold)} errors`, errored), + resolvedClause: (resolved, bold) => + n__(`${digitText(bold)} fixed test result`, `${digitText(bold)} fixed test results`, resolved), + totalClause: (total, bold) => + n__(`${digitText(bold)} total test`, `${digitText(bold)} total tests`, total), + + reportError: s__('Reports|An error occurred while loading report'), + reportErrorWithName: (name) => + sprintf(s__('Reports|An error occurred while loading %{name} results'), { name }), + headReportParsingError: s__('Reports|Head report parsing error:'), + baseReportParsingError: s__('Reports|Base report parsing error:'), +}; 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 new file mode 100644 index 00000000000..65d9257903f --- /dev/null +++ b/app/assets/javascripts/vue_merge_request_widget/extensions/test_report/index.js @@ -0,0 +1,82 @@ +import { uniqueId } from 'lodash'; +import axios from '~/lib/utils/axios_utils'; +import { EXTENSION_ICONS } from '../../constants'; +import { summaryTextBuilder, reportTextBuilder, reportSubTextBuilder } from './utils'; +import { i18n, TESTS_FAILED_STATUS, ERROR_STATUS } from './constants'; + +export default { + name: 'WidgetTestSummary', + enablePolling: true, + i18n, + expandEvent: 'i_testing_summary_widget_total', + props: ['testResultsPath', 'headBlobPath', 'pipeline'], + computed: { + summary(data) { + if (data.parsingInProgress) { + return this.$options.i18n.loading; + } + if (data.hasSuiteError) { + return this.$options.i18n.error; + } + return summaryTextBuilder(this.$options.i18n.label, data.summary); + }, + statusIcon(data) { + if (data.parsingInProgress) { + return null; + } + if (data.status === TESTS_FAILED_STATUS) { + return EXTENSION_ICONS.warning; + } + if (data.hasSuiteError) { + return EXTENSION_ICONS.failed; + } + return EXTENSION_ICONS.success; + }, + tertiaryButtons() { + return [ + { + text: this.$options.i18n.fullReport, + href: `${this.pipeline.path}/test_report`, + target: '_blank', + }, + ]; + }, + }, + methods: { + fetchCollapsedData() { + return axios.get(this.testResultsPath).then(({ data = {}, status }) => { + return { + data: { + hasSuiteError: data.suites?.some((suite) => suite.status === ERROR_STATUS), + parsingInProgress: status === 204, + ...data, + }, + }; + }); + }, + 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; + }, + prepareReports() { + return this.collapsedData.suites.map((suite) => { + return { + id: uniqueId('suite-'), + text: reportTextBuilder(suite), + subtext: reportSubTextBuilder(suite), + icon: { + name: this.suiteIcon(suite), + }, + }; + }); + }, + }, +}; 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 new file mode 100644 index 00000000000..a74ed20362f --- /dev/null +++ b/app/assets/javascripts/vue_merge_request_widget/extensions/test_report/utils.js @@ -0,0 +1,55 @@ +import { i18n } from './constants'; + +const textBuilder = (results, boldNumbers = false) => { + const { failed, errored, resolved, total } = results; + + const failedOrErrored = (failed || 0) + (errored || 0); + const failedString = failed ? i18n.failedClause(failed, boldNumbers) : null; + const erroredString = errored ? i18n.erroredClause(errored, boldNumbers) : null; + const combinedString = + failed && errored ? `${failedString}, ${erroredString}` : failedString || erroredString; + const resolvedString = resolved ? i18n.resolvedClause(resolved, boldNumbers) : null; + const totalString = total ? i18n.totalClause(total, boldNumbers) : null; + + let resultsString = i18n.noChanges(boldNumbers); + + if (failedOrErrored) { + if (resolved) { + resultsString = i18n.resultsString(combinedString, resolvedString); + } else { + resultsString = combinedString; + } + } else if (resolved) { + resultsString = resolvedString; + } + + return `${resultsString}, ${totalString}`; +}; + +export const summaryTextBuilder = (name = '', results = {}) => { + const resultsString = textBuilder(results, true); + return i18n.summaryText(name, resultsString); +}; + +export const reportTextBuilder = ({ name = '', summary = {}, status }) => { + if (!name) { + return i18n.reportError; + } + if (status === 'error') { + return i18n.reportErrorWithName(name); + } + + const resultsString = textBuilder(summary); + return i18n.summaryText(name, resultsString); +}; + +export const reportSubTextBuilder = ({ suite_errors }) => { + const errors = []; + if (suite_errors?.head) { + errors.push(`${i18n.headReportParsingError} ${suite_errors.head}`); + } + if (suite_errors?.base) { + errors.push(`${i18n.baseReportParsingError} ${suite_errors.base}`); + } + return errors.join('<br />'); +}; diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue index 965746e79fb..4b3ad288768 100644 --- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue +++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue @@ -47,6 +47,7 @@ import getStateQuery from './queries/get_state.query.graphql'; import terraformExtension from './extensions/terraform'; import accessibilityExtension from './extensions/accessibility'; import codeQualityExtension from './extensions/code_quality'; +import testReportExtension from './extensions/test_report'; export default { // False positive i18n lint: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/25 @@ -191,6 +192,9 @@ export default { shouldRenderTerraformPlans() { return Boolean(this.mr?.terraformReportsPath); }, + shouldRenderTestReport() { + return Boolean(this.mr?.testResultsPath); + }, mergeError() { let { mergeError } = this.mr; @@ -252,6 +256,11 @@ export default { this.registerAccessibilityExtension(); } }, + shouldRenderTestReport(newVal) { + if (newVal) { + this.registerTestReportExtension(); + } + }, }, mounted() { MRWidgetService.fetchInitialData() @@ -502,6 +511,11 @@ export default { registerExtension(codeQualityExtension); } }, + registerTestReportExtension() { + if (this.shouldRenderTestReport && this.shouldShowExtension) { + registerExtension(testReportExtension); + } + }, }, }; </script> @@ -574,7 +588,7 @@ export default { /> <grouped-test-reports-app - v-if="mr.testResultsPath" + v-if="mr.testResultsPath && !shouldShowExtension" class="js-reports-container" :endpoint="mr.testResultsPath" :head-blob-path="mr.headBlobPath" diff --git a/app/assets/javascripts/vue_merge_request_widget/services/mr_widget_service.js b/app/assets/javascripts/vue_merge_request_widget/services/mr_widget_service.js index 7b803b0fcbb..6515d76c17e 100644 --- a/app/assets/javascripts/vue_merge_request_widget/services/mr_widget_service.js +++ b/app/assets/javascripts/vue_merge_request_widget/services/mr_widget_service.js @@ -1,5 +1,5 @@ import { normalizeHeaders } from '~/lib/utils/common_utils'; -import axios from '../../lib/utils/axios_utils'; +import axios from '~/lib/utils/axios_utils'; export default class MRWidgetService { constructor(endpoints) { |