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>2022-07-20 18:40:28 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-07-20 18:40:28 +0300
commitb595cb0c1dec83de5bdee18284abe86614bed33b (patch)
tree8c3d4540f193c5ff98019352f554e921b3a41a72 /app/assets/javascripts/vue_merge_request_widget
parent2f9104a328fc8a4bddeaa4627b595166d24671d0 (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')
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/extensions/actions.vue41
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue48
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/extensions/index.js3
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/extensions/status_icon.vue11
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/extensions/telemetry.js2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/merge_checks_failed.vue2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue16
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/extensions/accessibility/index.js3
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/extensions/issues.js2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/extensions/test_report/constants.js2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/extensions/test_report/index.js62
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/extensions/test_report/utils.js14
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue12
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/queries/get_state.query.graphql2
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