diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-07-20 18:40:28 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-07-20 18:40:28 +0300 |
commit | b595cb0c1dec83de5bdee18284abe86614bed33b (patch) | |
tree | 8c3d4540f193c5ff98019352f554e921b3a41a72 /app/assets/javascripts/vue_merge_request_widget | |
parent | 2f9104a328fc8a4bddeaa4627b595166d24671d0 (diff) |
Add latest changes from gitlab-org/gitlab@15-2-stable-eev15.2.0-rc42
Diffstat (limited to 'app/assets/javascripts/vue_merge_request_widget')
14 files changed, 176 insertions, 44 deletions
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/extensions/actions.vue b/app/assets/javascripts/vue_merge_request_widget/components/extensions/actions.vue index 655ceb5f700..b76d5d90ead 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/extensions/actions.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/extensions/actions.vue @@ -1,5 +1,5 @@ <script> -import { GlButton, GlDropdown, GlDropdownItem } from '@gitlab/ui'; +import { GlButton, GlDropdown, GlDropdownItem, GlTooltipDirective } from '@gitlab/ui'; import { sprintf, __ } from '~/locale'; export default { @@ -8,6 +8,9 @@ export default { GlDropdown, GlDropdownItem, }, + directives: { + GlTooltip: GlTooltipDirective, + }, props: { widget: { type: String, @@ -19,6 +22,12 @@ export default { default: () => [], }, }, + data: () => { + return { + timeout: null, + updatingTooltip: false, + }; + }, computed: { dropdownLabel() { return sprintf(__('%{widget} options'), { widget: this.widget }); @@ -27,9 +36,29 @@ export default { methods: { onClickAction(action) { this.$emit('clickedAction', action); + if (action.onClick) { action.onClick(); } + + if (action.tooltipOnClick) { + this.updatingTooltip = true; + this.$root.$emit('bv::show::tooltip', action.id); + + clearTimeout(this.timeout); + + this.timeout = setTimeout(() => { + this.updatingTooltip = false; + this.$root.$emit('bv::hide::tooltip', action.id); + }, 1000); + } + }, + setTooltip(btn) { + if (this.updatingTooltip && btn.tooltipOnClick) { + return btn.tooltipOnClick; + } + + return btn.tooltipText; }, }, }; @@ -55,6 +84,7 @@ export default { :key="index" :href="btn.href" :target="btn.target" + :data-clipboard-text="btn.dataClipboardText" @click="onClickAction(btn)" > {{ btn.text }} @@ -63,15 +93,20 @@ export default { <template v-if="tertiaryButtons.length"> <gl-button v-for="(btn, index) in tertiaryButtons" + :id="btn.id" :key="index" + v-gl-tooltip.hover + :title="setTooltip(btn)" :href="btn.href" :target="btn.target" :class="{ 'gl-mr-3': index !== tertiaryButtons.length - 1 }" + :data-clipboard-text="btn.dataClipboardText" + :icon="btn.icon" + :data-testid="btn.testId || 'extension-actions-button'" + :variant="btn.variant || 'confirm'" category="tertiary" - variant="confirm" size="small" class="gl-display-none gl-md-display-block gl-float-left" - data-testid="extension-actions-button" @click="onClickAction(btn)" > {{ btn.text }} 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 4ba620da00a..410331004e4 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 @@ -194,6 +194,24 @@ export default { poll.makeRequest(); }, + initExtensionFullDataPolling() { + const poll = new Poll({ + resource: { + fetchData: () => this.fetchFullData(this), + }, + method: 'fetchData', + successCallback: (response) => { + this.headerCheck(response, (data) => { + this.setFullData(data); + }); + }, + errorCallback: (e) => { + this.setExpandedError(e); + }, + }); + + poll.makeRequest(); + }, headerCheck(response, callback) { const headers = normalizeHeaders(response.headers); @@ -220,6 +238,10 @@ export default { }); } }, + setFullData(data) { + this.loadingState = null; + this.fullData = data.map((x, i) => ({ id: i, ...x })); + }, setCollapsedData(data) { this.collapsedData = data; this.loadingState = null; @@ -229,21 +251,26 @@ export default { Sentry.captureException(e); }, + setExpandedError(e) { + this.loadingState = LOADING_STATES.expandedError; + Sentry.captureException(e); + }, loadAllData() { if (this.hasFullData) return; this.loadingState = LOADING_STATES.expandedLoading; - this.fetchFullData(this) - .then((data) => { - this.loadingState = null; - this.fullData = data.map((x, i) => ({ id: i, ...x })); - }) - .catch((e) => { - this.loadingState = LOADING_STATES.expandedError; - - Sentry.captureException(e); - }); + if (this.$options.enableExpandedPolling) { + this.initExtensionFullDataPolling(); + } else { + this.fetchFullData(this) + .then((data) => { + this.setFullData(data); + }) + .catch((e) => { + this.setExpandedError(e); + }); + } }, appear(index) { if (index === this.fullData.length - 1) { @@ -288,6 +315,7 @@ export default { @mouseup="onRowMouseUp" > <status-icon + :level="1" :name="$options.label || $options.name" :is-loading="isLoadingSummary" :icon-name="statusIconName" diff --git a/app/assets/javascripts/vue_merge_request_widget/components/extensions/index.js b/app/assets/javascripts/vue_merge_request_widget/components/extensions/index.js index f4fcf4c9571..7e329399957 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/extensions/index.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/extensions/index.js @@ -20,6 +20,7 @@ export const registerExtension = (extension) => { i18n: extension.i18n, expandEvent: extension.expandEvent, enablePolling: extension.enablePolling, + enableExpandedPolling: extension.enableExpandedPolling, modalComponent: extension.modalComponent, computed: { ...extension.props.reduce( @@ -35,7 +36,7 @@ export const registerExtension = (extension) => { (acc, computedKey) => ({ ...acc, // Making the computed property a method allows us to pass in arguments - // this allows for each computed property to recieve some data + // this allows for each computed property to receive some data [computedKey]() { return extension.computed[computedKey]; }, diff --git a/app/assets/javascripts/vue_merge_request_widget/components/extensions/status_icon.vue b/app/assets/javascripts/vue_merge_request_widget/components/extensions/status_icon.vue index bb626c9adba..dc748ba44f2 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/extensions/status_icon.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/extensions/status_icon.vue @@ -9,6 +9,11 @@ export default { GlIcon, }, props: { + level: { + type: Number, + required: false, + default: 0, + }, name: { type: String, required: false, @@ -27,7 +32,7 @@ export default { size: { type: Number, required: false, - default: 16, + default: 12, }, }, computed: { @@ -44,8 +49,8 @@ export default { <div :class="[ $options.EXTENSION_ICON_CLASS[iconName], - { 'mr-widget-extension-icon': !isLoading && size === 16 }, - { 'gl-p-2': isLoading || size === 16 }, + { 'mr-widget-extension-icon gl-w-6': !isLoading && level === 1 }, + { 'gl-p-2': isLoading || level === 1 }, ]" class="gl-rounded-full gl-mr-3 gl-relative gl-p-2" > diff --git a/app/assets/javascripts/vue_merge_request_widget/components/extensions/telemetry.js b/app/assets/javascripts/vue_merge_request_widget/components/extensions/telemetry.js index aec3a35f37c..b551cd2fd60 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/extensions/telemetry.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/extensions/telemetry.js @@ -65,7 +65,7 @@ function simplifyWidgetName(componentName) { function baseRedisEventName(extensionName) { const redisEventName = extensionName.replace(/([A-Z])/g, '_$1').toLowerCase(); - return `i_merge_request_widget_${redisEventName}`; + return `i_code_review_merge_request_widget_${redisEventName}`; } function whenable(bus) { diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/merge_checks_failed.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/merge_checks_failed.vue index 701ef89304c..a45823823f0 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/merge_checks_failed.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/merge_checks_failed.vue @@ -4,7 +4,7 @@ import StatusIcon from '../mr_widget_status_icon.vue'; export default { i18n: { - approvalNeeded: s__('mrWidget|Merge blocked: this merge request must be approved.'), + approvalNeeded: s__('mrWidget|Merge blocked: all required approvals must be given.'), blockingMergeRequests: s__( 'mrWidget|Merge blocked: you can only merge after the above items are resolved.', ), 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 3511fffcfbb..59767eb2e6e 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,6 +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 toast from '~/vue_shared/plugins/global_toast'; import simplePoll from '~/lib/utils/simple_poll'; import eventHub from '../../event_hub'; import mergeRequestQueryVariablesMixin from '../../mixins/merge_request_query_variables'; @@ -120,13 +121,15 @@ export default { .poll() .then((res) => res.data) .then((res) => { - if (res.rebase_in_progress) { + if (res.rebase_in_progress || res.should_be_rebased) { continuePolling(); } else { this.isMakingRequest = false; if (res.merge_error && res.merge_error.length) { this.rebasingError = res.merge_error; + } else { + toast(__('Rebase completed')); } eventHub.$emit('MRWidgetRebaseSuccess'); @@ -218,6 +221,17 @@ export default { > {{ __('Rebase') }} </gl-button> + <gl-button + v-if="glFeatures.restructuredMrWidget && showRebaseWithoutCi" + :loading="isMakingRequest" + variant="confirm" + size="small" + category="secondary" + data-testid="rebase-without-ci-button" + @click="rebaseWithoutCi" + > + {{ __('Rebase without pipeline') }} + </gl-button> </div> </div> </template> diff --git a/app/assets/javascripts/vue_merge_request_widget/extensions/accessibility/index.js b/app/assets/javascripts/vue_merge_request_widget/extensions/accessibility/index.js index f14e80d0be6..22e907f7e48 100644 --- a/app/assets/javascripts/vue_merge_request_widget/extensions/accessibility/index.js +++ b/app/assets/javascripts/vue_merge_request_widget/extensions/accessibility/index.js @@ -40,6 +40,9 @@ export default { return numOfResults === 0 ? successText : warningText; }, + shouldCollapse() { + return this.collapsedData?.summary?.errored > 0; + }, fetchCollapsedData() { return axios.get(this.accessibilityReportPath); }, 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 a7aaa2f4476..ca95e1b5de8 100644 --- a/app/assets/javascripts/vue_merge_request_widget/extensions/issues.js +++ b/app/assets/javascripts/vue_merge_request_widget/extensions/issues.js @@ -32,7 +32,7 @@ export default { // Status icon to be used next to the summary text // Receives the collapsed data as an argument statusIcon(count) { - return EXTENSION_ICONS.warning; + return EXTENSION_ICONS.failed; }, // Tertiary action buttons that will take the user elsewhere // in the GitLab app 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 index 23f14bea4e1..4994a0bcbeb 100644 --- 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 @@ -7,6 +7,8 @@ export const TESTS_FAILED_STATUS = 'failed'; export const ERROR_STATUS = 'error'; export const i18n = { + copyFailedSpecs: s__('Reports|Copy failed tests'), + copyFailedSpecsTooltip: s__('Reports|Copy failed test names to run locally'), label: s__('Reports|Test summary'), loading: s__('Reports|Test summary results are loading'), error: s__('Reports|Test summary failed to load results'), 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 index 164bda33b95..c74445a5b80 100644 --- 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 @@ -1,4 +1,5 @@ import { uniqueId } from 'lodash'; +import { __ } from '~/locale'; import axios from '~/lib/utils/axios_utils'; import TestCaseDetails from '~/pipelines/components/test_reports/test_case_details.vue'; import { EXTENSION_ICONS } from '../../constants'; @@ -19,6 +20,20 @@ export default { 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(' '); + }, summary(data) { if (data.parsingInProgress) { return this.$options.i18n.loading; @@ -32,9 +47,6 @@ export default { }; }, statusIcon(data) { - if (data.parsingInProgress) { - return null; - } if (data.status === TESTS_FAILED_STATUS) { return EXTENSION_ICONS.warning; } @@ -44,30 +56,46 @@ export default { return EXTENSION_ICONS.success; }, tertiaryButtons() { - return [ - { - text: this.$options.i18n.fullReport, - href: `${this.pipeline.path}/test_report`, - target: '_blank', - fullReport: true, - }, - ]; + 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', + fullReport: true, + testId: 'full-report-link', + }); + + return actionButtons; }, }, methods: { fetchCollapsedData() { - return axios.get(this.testResultsPath).then((res) => { - const { data = {}, status } = res; + return axios.get(this.testResultsPath).then((response) => { + const { data = {}, status } = response; + const { suites = [], summary = {} } = data; return { - ...res, + ...response, data: { - hasSuiteError: data.suites?.some((suite) => suite.status === ERROR_STATUS), + hasSuiteError: suites.some((suite) => suite.status === ERROR_STATUS), parsingInProgress: status === 204, ...data, summary: { - recentlyFailed: countRecentlyFailedTests(data.suites), - ...data.summary, + recentlyFailed: countRecentlyFailedTests(suites), + ...summary, }, }, }; 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 7bbcb0cd04a..4ffd06de61f 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 @@ -1,3 +1,4 @@ +import { isEmpty } from 'lodash'; import { i18n } from './constants'; const textBuilder = (results, boldNumbers = false) => { @@ -65,6 +66,11 @@ export const reportSubTextBuilder = ({ suite_errors, summary }) => { }; export const countRecentlyFailedTests = (subject) => { + // return 0 count if subject is [], null, or undefined + if (isEmpty(subject)) { + return 0; + } + // handle either a single report or an array of reports const reports = !subject.length ? [subject] : subject; @@ -73,10 +79,10 @@ export const countRecentlyFailedTests = (subject) => { 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?.count > 1).length, - ) + .map((failureArray) => { + if (!failureArray) return 0; + return failureArray.filter((failure) => failure.recent_failures?.count > 1).length; + }) .reduce((total, count) => total + count, 0) ); }) 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 c68437b9879..3e0ac236fdf 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 @@ -221,8 +221,11 @@ export default { formattedHumanAccess() { return (this.mr.humanAccess || '').toLowerCase(); }, + hasMergeError() { + return this.mr.mergeError && this.state !== 'closed'; + }, hasAlerts() { - return this.mr.mergeError || this.showMergePipelineForkWarning; + return this.hasMergeError || this.showMergePipelineForkWarning; }, shouldShowExtension() { return ( @@ -574,7 +577,12 @@ export default { /> <div class="mr-section-container mr-widget-workflow"> <div v-if="hasAlerts" class="gl-overflow-hidden mr-widget-alert-container"> - <mr-widget-alert-message v-if="mr.mergeError" type="danger" dismissible> + <mr-widget-alert-message + v-if="hasMergeError" + type="danger" + dismissible + data-testid="merge_error" + > <span v-safe-html="mergeError"></span> </mr-widget-alert-message> <mr-widget-alert-message diff --git a/app/assets/javascripts/vue_merge_request_widget/queries/get_state.query.graphql b/app/assets/javascripts/vue_merge_request_widget/queries/get_state.query.graphql index 25c44beaf18..981c667f27a 100644 --- a/app/assets/javascripts/vue_merge_request_widget/queries/get_state.query.graphql +++ b/app/assets/javascripts/vue_merge_request_widget/queries/get_state.query.graphql @@ -6,11 +6,13 @@ query getState($projectPath: ID!, $iid: String!) { mergeRequest(iid: $iid) { id autoMergeEnabled + availableAutoMergeStrategies commitCount conflicts diffHeadSha mergeError mergeStatus + mergeable mergeableDiscussionsState headPipeline { id |