diff options
Diffstat (limited to 'app/assets/javascripts/vue_merge_request_widget')
43 files changed, 539 insertions, 223 deletions
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/added_commit_message.vue b/app/assets/javascripts/vue_merge_request_widget/components/added_commit_message.vue new file mode 100644 index 00000000000..492e68b636f --- /dev/null +++ b/app/assets/javascripts/vue_merge_request_widget/components/added_commit_message.vue @@ -0,0 +1,77 @@ +<script> +import { GlSprintf } from '@gitlab/ui'; +import { escape } from 'lodash'; +import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; +import { n__, s__ } from '~/locale'; + +const mergeCommitCount = s__('mrWidgetCommitsAdded|1 merge commit'); + +export default { + components: { + GlSprintf, + }, + mixins: [glFeatureFlagMixin()], + props: { + isSquashEnabled: { + type: Boolean, + required: false, + default: false, + }, + isFastForwardEnabled: { + type: Boolean, + required: true, + }, + commitsCount: { + type: Number, + required: false, + default: 0, + }, + targetBranch: { + type: String, + required: true, + }, + }, + computed: { + targetBranchEscaped() { + return escape(this.targetBranch); + }, + commitsCountMessage() { + return n__('%d commit', '%d commits', this.isSquashEnabled ? 1 : this.commitsCount); + }, + message() { + return this.isFastForwardEnabled + ? s__('mrWidgetCommitsAdded|Adds %{commitCount} to %{targetBranch}.') + : s__( + 'mrWidgetCommitsAdded|Adds %{commitCount} and %{mergeCommitCount} to %{targetBranch}%{squashedCommits}.', + ); + }, + textDecorativeComponent() { + return this.glFeatures.restructuredMrWidget ? 'span' : 'strong'; + }, + }, + mergeCommitCount, +}; +</script> + +<template> + <span> + <gl-sprintf :message="message"> + <template #commitCount> + <component :is="textDecorativeComponent" class="commits-count-message">{{ + commitsCountMessage + }}</component> + </template> + <template #mergeCommitCount> + <component :is="textDecorativeComponent">{{ $options.mergeCommitCount }}</component> + </template> + <template #targetBranch> + <span class="label-branch">{{ targetBranchEscaped }}</span> + </template> + <template #squashedCommits> + <template v-if="glFeatures.restructuredMrWidget && isSquashEnabled"> + {{ n__('(squashes %d commit)', '(squashes %d commits)', commitsCount) }}</template + ></template + > + </gl-sprintf> + </span> +</template> 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 0c4a5ee35d9..25dbb614c1d 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 @@ -1,7 +1,11 @@ <script> import { toNounSeriesText } from '~/lib/utils/grammar'; import { n__, sprintf } from '~/locale'; -import { APPROVED_MESSAGE } from '~/vue_merge_request_widget/components/approvals/messages'; +import { + APPROVED_BY_YOU_AND_OTHERS, + APPROVED_BY_YOU, + APPROVED_BY_OTHERS, +} from '~/vue_merge_request_widget/components/approvals/messages'; import UserAvatarList from '~/vue_shared/components/user_avatar/user_avatar_list.vue'; export default { @@ -29,12 +33,23 @@ export default { }, }, computed: { - message() { - if (this.approved) { - return APPROVED_MESSAGE; + approvalLeftMessage() { + if (this.rulesLeft.length) { + return sprintf( + n__( + 'Requires %{count} approval from %{names}.', + 'Requires %{count} approvals from %{names}.', + this.approvalsLeft, + ), + { + names: toNounSeriesText(this.rulesLeft), + count: this.approvalsLeft, + }, + false, + ); } - if (!this.rulesLeft.length) { + if (!this.approved) { return n__( 'Requires %d approval from eligible users.', 'Requires %d approvals from eligible users.', @@ -42,32 +57,51 @@ export default { ); } - return sprintf( - n__( - 'Requires %{count} approval from %{names}.', - 'Requires %{count} approvals from %{names}.', - this.approvalsLeft, - ), - { - names: toNounSeriesText(this.rulesLeft), - count: this.approvalsLeft, - }, - false, - ); + return ''; + }, + message() { + if (this.approvedByMe && this.approvedByOthers) { + return APPROVED_BY_YOU_AND_OTHERS; + } + + if (this.approvedByMe) { + return APPROVED_BY_YOU; + } + + if (this.approved) { + return APPROVED_BY_OTHERS; + } + + return ''; }, hasApprovers() { return Boolean(this.approvers.length); }, + approvedByMe() { + if (!this.currentUserId) { + return false; + } + return this.approvers.some((approver) => approver.id === this.currentUserId); + }, + approvedByOthers() { + if (!this.currentUserId) { + return false; + } + return this.approvers.some((approver) => approver.id !== this.currentUserId); + }, + currentUserId() { + return gon.current_user_id; + }, }, - APPROVED_MESSAGE, }; </script> <template> <div data-qa-selector="approvals_summary_content"> - <strong>{{ message }}</strong> + <strong>{{ approvalLeftMessage }}</strong> <template v-if="hasApprovers"> - <span>{{ s__('mrWidget|Approved by') }}</span> + <span v-if="approvalLeftMessage">{{ message }}</span> + <strong v-else>{{ message }}</strong> <user-avatar-list class="d-inline-block align-middle" :items="approvers" /> </template> </div> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/approvals/messages.js b/app/assets/javascripts/vue_merge_request_widget/components/approvals/messages.js index 0538c38307b..fbdefa95630 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/approvals/messages.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/approvals/messages.js @@ -6,4 +6,6 @@ export const FETCH_ERROR = s__( ); export const APPROVE_ERROR = s__('mrWidget|An error occurred while submitting your approval.'); export const UNAPPROVE_ERROR = s__('mrWidget|An error occurred while removing your approval.'); -export const APPROVED_MESSAGE = s__('mrWidget|Merge request approved.'); +export const APPROVED_BY_YOU_AND_OTHERS = s__('mrWidget|Approved by you and others'); +export const APPROVED_BY_YOU = s__('mrWidget|Approved by you'); +export const APPROVED_BY_OTHERS = s__('mrWidget|Approved by'); diff --git a/app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment_info.vue b/app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment_info.vue index cbace1ad57c..f4f611dfd1b 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment_info.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment_info.vue @@ -12,13 +12,12 @@ import { CANCELED, SKIPPED, } from './constants'; -import MemoryUsage from './memory_usage.vue'; export default { name: 'DeploymentInfo', components: { GlLink, - MemoryUsage, + MemoryUsage: () => import('./memory_usage.vue'), TooltipOnTruncate, }, directives: { diff --git a/app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment_list.vue b/app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment_list.vue index d3384903cce..655acf28253 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment_list.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment_list.vue @@ -2,10 +2,11 @@ import { GlSprintf } from '@gitlab/ui'; import { n__ } from '~/locale'; import MrCollapsibleExtension from '../mr_collapsible_extension.vue'; +import Deployment from './deployment.vue'; export default { components: { - Deployment: () => import('./deployment.vue'), + Deployment, GlSprintf, MrCollapsibleExtension, }, 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 023367a794e..33a83aef057 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 @@ -24,13 +24,20 @@ export default { return sprintf(__('%{widget} options'), { widget: this.widget }); }, }, + methods: { + onClickAction(action) { + if (action.onClick) { + action.onClick(); + } + }, + }, }; </script> <template> <div> <gl-dropdown - v-if="tertiaryButtons" + v-if="tertiaryButtons.length" :text="dropdownLabel" icon="ellipsis_v" no-caret @@ -47,6 +54,7 @@ export default { :key="index" :href="btn.href" :target="btn.target" + @click="onClickAction(btn)" > {{ btn.text }} </gl-dropdown-item> @@ -57,11 +65,12 @@ export default { :key="index" :href="btn.href" :target="btn.target" - :class="{ 'gl-mr-3': index > 1 }" + :class="{ 'gl-mr-3': index !== tertiaryButtons.length - 1 }" category="tertiary" variant="confirm" size="small" - class="gl-display-none gl-md-display-block" + class="gl-display-none gl-md-display-block gl-float-left" + @click="onClickAction(btn)" > {{ btn.text }} </gl-button> 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 298f7c7ad8c..6f10f788952 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 @@ -8,6 +8,8 @@ import { GlTooltipDirective, GlIntersectionObserver, } from '@gitlab/ui'; +import { once } from 'lodash'; +import api from '~/api'; import { sprintf, s__, __ } from '~/locale'; import SmartVirtualList from '~/vue_shared/components/smart_virtual_list.vue'; import { EXTENSION_ICON_CLASS } from '../../constants'; @@ -102,8 +104,15 @@ export default { }); }, methods: { + triggerRedisTracking: once(function triggerRedisTracking() { + if (this.$options.expandEvent) { + api.trackRedisHllUserEvent(this.$options.expandEvent); + } + }), toggleCollapsed() { this.isCollapsed = !this.isCollapsed; + + this.triggerRedisTracking(); }, loadAllData() { if (this.fullData) return; @@ -143,7 +152,10 @@ export default { :is-loading="isLoadingSummary" :icon-name="statusIconName" /> - <div class="media-body gl-display-flex gl-flex-direction-row!"> + <div + class="media-body gl-display-flex gl-flex-direction-row!" + data-testid="widget-extension-top-level" + > <div class="gl-flex-grow-1"> <template v-if="isLoadingSummary">{{ widgetLoadingText }}</template> <div v-else v-safe-html="summary(collapsedData)"></div> @@ -194,20 +206,28 @@ export default { class="gl-display-flex gl-align-items-center gl-py-3 gl-pl-7" data-testid="extension-list-item" > - <status-icon v-if="data.icon" :icon-name="data.icon.name" :size="12" /> + <status-icon v-if="data.icon" :icon-name="data.icon.name" :size="12" class="gl-pl-0" /> <gl-intersection-observer :options="{ rootMargin: '100px', thresholds: 0.1 }" - class="gl-flex-wrap gl-align-self-center gl-display-flex" + class="gl-flex-wrap gl-display-flex gl-w-full" @appear="appear(index)" @disappear="disappear(index)" > - <div v-safe-html="data.text" class="gl-mr-4"></div> + <div + v-safe-html="data.text" + class="gl-mr-4 gl-display-flex gl-align-items-center" + ></div> <div v-if="data.link"> <gl-link :href="data.link.href">{{ data.link.text }}</gl-link> </div> <gl-badge v-if="data.badge" :variant="data.badge.variant || 'info'"> {{ data.badge.text }} </gl-badge> + <actions + :widget="$options.label || $options.name" + :tertiary-buttons="data.actions" + class="gl-ml-auto" + /> </gl-intersection-observer> </li> </smart-virtual-list> 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 4ca0b660696..ec6e6ed2620 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 @@ -12,6 +12,7 @@ export const registerExtension = (extension) => { name: extension.name, props: extension.props, i18n: extension.i18n, + expandEvent: extension.expandEvent, computed: { ...Object.keys(extension.computed).reduce( (acc, computedKey) => ({ diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue index 5c67b9c7ab5..9070cb1fe65 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue @@ -151,7 +151,7 @@ export default { right data-qa-selector="download_dropdown" > - <gl-dropdown-section-header>{{ s__('Download as') }}</gl-dropdown-section-header> + <gl-dropdown-section-header>{{ __('Download as') }}</gl-dropdown-section-header> <gl-dropdown-item :href="mr.emailPatchesPath" class="js-download-email-patches" diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue index 9bb955c534f..f7c952f9ef6 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue @@ -101,6 +101,9 @@ export default { ? this.pipeline.details.status : {}; }, + artifacts() { + return this.pipeline?.details?.artifacts; + }, hasStages() { return this.pipeline?.details?.stages?.length > 0; }, @@ -285,7 +288,7 @@ export default { /> </span> <linked-pipelines-mini-list v-if="triggered.length" :triggered="triggered" /> - <pipeline-artifacts :pipeline-id="pipeline.id" class="gl-ml-3" /> + <pipeline-artifacts :pipeline-id="pipeline.id" :artifacts="artifacts" class="gl-ml-3" /> </span> </div> </div> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.vue index 306026072a3..c314261d3f5 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.vue @@ -1,8 +1,10 @@ <script> import { s__, n__ } from '~/locale'; +import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; export default { name: 'MRWidgetRelatedLinks', + mixins: [glFeatureFlagMixin()], props: { relatedLinks: { type: Object, @@ -14,6 +16,11 @@ export default { required: false, default: '', }, + showAssignToMe: { + type: Boolean, + required: false, + default: true, + }, }, computed: { closesText() { @@ -30,16 +37,25 @@ export default { }; </script> <template> - <section class="mr-info-list gl-ml-7 gl-pb-5"> - <p v-if="relatedLinks.closing"> + <section> + <p + v-if="relatedLinks.closing" + :class="{ 'gl-display-line gl-m-0': glFeatures.restructuredMrWidget }" + > {{ closesText }} <span v-html="relatedLinks.closing /* eslint-disable-line vue/no-v-html */"></span> </p> - <p v-if="relatedLinks.mentioned"> + <p + v-if="relatedLinks.mentioned" + :class="{ 'gl-display-line gl-m-0': glFeatures.restructuredMrWidget }" + > {{ n__('mrWidget|Mentions issue', 'mrWidget|Mentions issues', relatedLinks.mentionedCount) }} <span v-html="relatedLinks.mentioned /* eslint-disable-line vue/no-v-html */"></span> </p> - <p v-if="relatedLinks.assignToMe"> + <p + v-if="relatedLinks.assignToMe && showAssignToMe" + :class="{ 'gl-display-line gl-m-0': glFeatures.restructuredMrWidget }" + > <span v-html="relatedLinks.assignToMe /* eslint-disable-line vue/no-v-html */"></span> </p> </section> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_suggest_pipeline.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_suggest_pipeline.vue index f3673005c45..cd5b7c3110d 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_suggest_pipeline.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_suggest_pipeline.vue @@ -4,9 +4,7 @@ import Tracking from '~/tracking'; import DismissibleContainer from '~/vue_shared/components/dismissible_container.vue'; import { SP_TRACK_LABEL, - SP_LINK_TRACK_EVENT, SP_SHOW_TRACK_EVENT, - SP_LINK_TRACK_VALUE, SP_SHOW_TRACK_VALUE, SP_HELP_CONTENT, SP_HELP_URL, @@ -20,9 +18,7 @@ export default { name: 'MRWidgetSuggestPipeline', SP_ICON_NAME, SP_TRACK_LABEL, - SP_LINK_TRACK_EVENT, SP_SHOW_TRACK_EVENT, - SP_LINK_TRACK_VALUE, SP_SHOW_TRACK_VALUE, SP_HELP_CONTENT, SP_HELP_URL, @@ -81,29 +77,14 @@ export default { <div> <gl-sprintf :message=" - s__(`mrWidget|%{prefixToLinkStart}No pipeline%{prefixToLinkEnd} - %{addPipelineLinkStart}Add the .gitlab-ci.yml file%{addPipelineLinkEnd} - to create one.`) + s__(`mrWidget|%{boldHeaderStart}Looks like there's no pipeline here.%{boldHeaderEnd}`) " > - <template #prefixToLink="{ content }"> + <template #boldHeader="{ content }"> <strong> {{ content }} </strong> </template> - <template #addPipelineLink="{ content }"> - <gl-link - :href="pipelinePath" - class="gl-ml-1" - data-testid="add-pipeline-link" - :data-track-property="humanAccess" - :data-track-value="$options.SP_LINK_TRACK_VALUE" - :data-track-action="$options.SP_LINK_TRACK_EVENT" - :data-track-label="$options.SP_TRACK_LABEL" - > - {{ content }} - </gl-link> - </template> </gl-sprintf> </div> </template> @@ -115,9 +96,6 @@ export default { </div> <div class="col-md-7 order-md-first col-12"> <div class="ml-6 gl-pt-5"> - <strong> - {{ s__('mrWidget|Are you adding technical debt or code vulnerabilities?') }} - </strong> <p class="gl-mt-2"> <gl-sprintf :message="$options.SP_HELP_CONTENT"> <template #link="{ content }"> @@ -142,7 +120,7 @@ export default { :data-track-action="$options.SP_SHOW_TRACK_EVENT" :data-track-label="$options.SP_TRACK_LABEL" > - {{ __('Show me how to add a pipeline') }} + {{ __('Try out GitLab Pipelines') }} </gl-button> </div> </div> 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 9268e426954..caafd6b995e 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 @@ -4,7 +4,7 @@ import { __ } from '../../locale'; export default { i18n: { - removesBranchText: __('The source branch will be deleted'), + removesBranchText: __('Deletes the source branch'), tooltipTitle: __('A user with write access to the source branch selected this option'), }, components: { diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/commit_edit.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/commit_edit.vue index 44bdc4a3be8..3eda2828e97 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/commit_edit.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/commit_edit.vue @@ -1,5 +1,8 @@ <script> +import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; + export default { + mixins: [glFeatureFlagMixin()], props: { value: { type: String, @@ -20,7 +23,10 @@ export default { <template> <li> <div class="commit-message-editor"> - <div class="d-flex flex-wrap align-items-center justify-content-between"> + <div + :class="{ 'gl-mb-3': glFeatures.restructuredMrWidget }" + class="d-flex flex-wrap align-items-center justify-content-between" + > <label class="col-form-label" :for="inputId"> <strong>{{ label }}</strong> </label> @@ -35,7 +41,7 @@ export default { rows="7" @input="$emit('input', $event.target.value)" ></textarea> - <slot name="checkbox"></slot> + <slot name="text-muted"></slot> </div> </li> </template> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue index 3ca193514f1..5c4a526bcc3 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue @@ -1,15 +1,12 @@ <script> -import { GlButton, GlSprintf } from '@gitlab/ui'; -import { escape } from 'lodash'; -import { __, n__, s__ } from '~/locale'; - -const mergeCommitCount = s__('mrWidgetCommitsAdded|1 merge commit'); +import { GlButton } from '@gitlab/ui'; +import { __ } from '~/locale'; +import AddedCommitMessage from '../added_commit_message.vue'; export default { - mergeCommitCount, components: { GlButton, - GlSprintf, + AddedCommitMessage, }, props: { isSquashEnabled: { @@ -39,9 +36,6 @@ export default { collapseIcon() { return this.expanded ? 'chevron-down' : 'chevron-right'; }, - commitsCountMessage() { - return n__('%d commit', '%d commits', this.isSquashEnabled ? 1 : this.commitsCount); - }, modifyLinkMessage() { if (this.isFastForwardEnabled) return __('Modify commit message'); else if (this.isSquashEnabled) return __('Modify commit messages'); @@ -50,16 +44,6 @@ export default { ariaLabel() { return this.expanded ? __('Collapse') : __('Expand'); }, - targetBranchEscaped() { - return escape(this.targetBranch); - }, - message() { - return this.isFastForwardEnabled - ? s__('mrWidgetCommitsAdded|%{commitCount} will be added to %{targetBranch}.') - : s__( - 'mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}.', - ); - }, }, methods: { toggle() { @@ -86,17 +70,12 @@ export default { <span v-if="expanded">{{ __('Collapse') }}</span> <span v-else> <span class="vertical-align-middle"> - <gl-sprintf :message="message"> - <template #commitCount> - <strong class="commits-count-message">{{ commitsCountMessage }}</strong> - </template> - <template #mergeCommitCount> - <strong>{{ $options.mergeCommitCount }}</strong> - </template> - <template #targetBranch> - <span class="label-branch">{{ targetBranchEscaped }}</span> - </template> - </gl-sprintf> + <added-commit-message + :is-squash-enabled="isSquashEnabled" + :is-fast-forward-enabled="isFastForwardEnabled" + :commits-count="commitsCount" + :target-branch="targetBranch" + /> </span> <gl-button variant="link" class="modify-message-button"> {{ modifyLinkMessage }} diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue index 0eb173edbcb..a44caf886a4 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue @@ -177,10 +177,10 @@ export default { </h4> <section class="mr-info-list"> <p v-if="shouldRemoveSourceBranch"> - {{ s__('mrWidget|The source branch will be deleted') }} + {{ s__('mrWidget|Deletes the source branch') }} </p> <p v-else class="gl-display-flex"> - <span class="gl-mr-3">{{ s__('mrWidget|The source branch will not be deleted') }}</span> + <span class="gl-mr-3">{{ s__('mrWidget|Does not delete the source branch') }}</span> <gl-button v-if="canRemoveSourceBranch" :loading="isRemovingSourceBranch" diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.vue index e02be6dc2f7..10b93d7849f 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.vue @@ -1,4 +1,5 @@ <script> +import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import statusIcon from '../mr_widget_status_icon.vue'; export default { @@ -6,11 +7,12 @@ export default { components: { statusIcon, }, + mixins: [glFeatureFlagMixin()], }; </script> <template> <div class="mr-widget-body media"> - <status-icon :show-disabled-button="true" status="loading" /> + <status-icon :show-disabled-button="!glFeatures.restructuredMrWidget" status="loading" /> <div class="media-body space-children"> <span class="bold"> {{ s__('mrWidget|Checking if merge request can be merged…') }} </span> </div> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue index a1759b1a815..84dac95ce74 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue @@ -1,6 +1,7 @@ <script> /* eslint-disable @gitlab/vue-require-i18n-strings */ import { GlLoadingIcon, GlButton, GlTooltipDirective, GlIcon } from '@gitlab/ui'; +import api from '~/api'; import createFlash from '~/flash'; import { s__, __ } from '~/locale'; import { OPEN_REVERT_MODAL, OPEN_CHERRY_PICK_MODAL } from '~/projects/commit/constants'; @@ -83,6 +84,8 @@ export default { removeSourceBranch() { this.isMakingRequest = true; + api.trackRedisHllUserEvent('i_code_review_post_merge_delete_branch'); + this.service .removeSourceBranch() .then((res) => res.data) @@ -103,9 +106,13 @@ export default { }); }, openRevertModal() { + api.trackRedisHllUserEvent('i_code_review_post_merge_click_revert'); + modalEventHub.$emit(OPEN_REVERT_MODAL); }, openCherryPickModal() { + api.trackRedisHllUserEvent('i_code_review_post_merge_click_cherry_pick'); + modalEventHub.$emit(OPEN_CHERRY_PICK_MODAL); }, }, 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 1c245b584ea..247877a8235 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 @@ -32,7 +32,7 @@ export default { </h4> <section class="mr-info-list"> <p> - {{ s__('mrWidget|The changes will be merged into') }} + {{ s__('mrWidget|Merges changes into') }} <span class="label-branch"> <a :href="mr.targetBranchPath">{{ mr.targetBranch }}</a> </span> 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 1976d3639a6..9f2870d8d69 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 @@ -1,8 +1,7 @@ <script> import { GlButton, GlSkeletonLoader } from '@gitlab/ui'; -import { escape } from 'lodash'; import createFlash from '~/flash'; -import { __, sprintf } from '~/locale'; +import { __ } from '~/locale'; import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import simplePoll from '../../../lib/utils/simple_poll'; import eventHub from '../../event_hub'; @@ -85,13 +84,7 @@ export default { return ['failed', 'loading'].includes(this.status); }, fastForwardMergeText() { - return sprintf( - __('Merge blocked: the source branch must be rebased onto the target branch.'), - { - targetBranch: `<span class="label-branch">${escape(this.targetBranch)}</span>`, - }, - false, - ); + return __('Merge blocked: the source branch must be rebased onto the target branch.'); }, }, methods: { @@ -170,8 +163,8 @@ export default { v-if="!rebaseInProgress && !canPushToSourceBranch" class="gl-font-weight-bold gl-ml-0!" data-testid="rebase-message" - v-html="fastForwardMergeText /* eslint-disable-line vue/no-v-html */" - ></span> + >{{ fastForwardMergeText }}</span + > <div v-if="!rebaseInProgress && canPushToSourceBranch && !isMakingRequest" class="accept-merge-holder clearfix js-toggle-container accept-action media space-children" diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/new_ready_to_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/new_ready_to_merge.vue index 9a7743348ff..0b6aa104181 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/new_ready_to_merge.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/new_ready_to_merge.vue @@ -37,7 +37,7 @@ export default { <template> <div class="mr-widget-body media"> <status-icon status="success" /> - <p class="media-body gl-m-0! gl-font-weight-bold"> + <p class="media-body gl-m-0! gl-font-weight-bold gl-text-gray-900!"> <template v-if="canMerge"> {{ __('Ready to merge!') }} </template> 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 7827c79cd31..2d704d3b07a 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 @@ -1,6 +1,7 @@ <script> import { GlButton, GlSprintf, GlLink, GlSafeHtmlDirective } from '@gitlab/ui'; import emptyStateSVG from 'icons/_mr_widget_empty_state.svg'; +import api from '~/api'; import { helpPagePath } from '~/helpers/help_page_helper'; export default { @@ -22,6 +23,11 @@ export default { data() { return { emptyStateSVG }; }, + methods: { + onClickNewFile() { + api.trackRedisHllUserEvent('i_code_review_widget_nothing_merge_click_new_file'); + }, + }, ciHelpPage: helpPagePath('/ci/quick_start/index.html'), safeHtmlConfig: { ADD_TAGS: ['use'] }, }; @@ -59,6 +65,7 @@ export default { category="secondary" variant="success" data-testid="createFileButton" + @click="onClickNewFile" > {{ __('Create file') }} </gl-button> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue index 7d4bd4cf1bf..d2cc99302a9 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue @@ -18,9 +18,10 @@ import { refreshUserMergeRequestCounts } from '~/commons/nav/user_merge_requests import createFlash from '~/flash'; import { secondsToMilliseconds } from '~/lib/utils/datetime_utility'; import simplePoll from '~/lib/utils/simple_poll'; -import { __ } from '~/locale'; +import { __, s__ } from '~/locale'; import SmartInterval from '~/smart_interval'; import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; +import { helpPagePath } from '~/helpers/help_page_helper'; import MergeRequest from '../../../merge_request'; import { AUTO_MERGE_STRATEGIES, @@ -35,6 +36,8 @@ import eventHub from '../../event_hub'; import mergeRequestQueryVariablesMixin from '../../mixins/merge_request_query_variables'; import MergeRequestStore from '../../stores/mr_widget_store'; import statusIcon from '../mr_widget_status_icon.vue'; +import AddedCommitMessage from '../added_commit_message.vue'; +import RelatedLinks from '../mr_widget_related_links.vue'; import CommitEdit from './commit_edit.vue'; import CommitMessageDropdown from './commit_message_dropdown.vue'; import CommitsHeader from './commits_header.vue'; @@ -113,6 +116,8 @@ export default { import( 'ee_component/vue_merge_request_widget/components/merge_train_failed_pipeline_confirmation_dialog.vue' ), + AddedCommitMessage, + RelatedLinks, }, directives: { GlTooltip: GlTooltipDirective, @@ -134,6 +139,7 @@ export default { isSquashReadOnly: this.mr.squashIsReadonly, squashCommitMessage: this.mr.squashCommitMessage, isPipelineFailedModalVisible: false, + editCommitMessage: false, }; }, computed: { @@ -162,7 +168,7 @@ export default { }, isMergeAllowed() { if (this.glFeatures.mergeRequestWidgetGraphql) { - return this.state.mergeable || false; + return this.state.mergeable; } return this.mr.isMergeAllowed; @@ -174,6 +180,11 @@ export default { return this.mr.canRemoveSourceBranch; }, + commitTemplateHelpPage() { + return helpPagePath('user/project/merge_requests/commit_templates.md', { + anchor: 'merge-commit-message-template', + }); + }, commits() { if (this.glFeatures.mergeRequestWidgetGraphql) { return this.state.commitsWithoutMergeCommits.nodes; @@ -279,6 +290,10 @@ export default { return enableSquashBeforeMerge && this.commitsCount > 1; }, shouldShowMergeControls() { + if (this.glFeatures.restructuredMrWidget) { + return this.restructuredWidgetShowMergeButtons; + } + return this.isMergeAllowed || this.isAutoMergeAvailable; }, shouldShowSquashEdit() { @@ -297,15 +312,26 @@ export default { showDangerMessageForMergeTrain() { return this.preferredAutoMergeStrategy === MT_MERGE_STRATEGY && this.isPipelineFailed; }, + restructuredWidgetShowMergeButtons() { + if (this.glFeatures.restructuredMrWidget) { + return this.isMergeAllowed && this.state.userPermissions.canMerge; + } + + return true; + }, }, mounted() { if (this.glFeatures.mergeRequestWidgetGraphql) { eventHub.$on('ApprovalUpdated', this.updateGraphqlState); + eventHub.$on('MRWidgetUpdateRequested', this.updateGraphqlState); + eventHub.$on('mr.discussion.updated', this.updateGraphqlState); } }, beforeDestroy() { if (this.glFeatures.mergeRequestWidgetGraphql) { eventHub.$off('ApprovalUpdated', this.updateGraphqlState); + eventHub.$off('MRWidgetUpdateRequested', this.updateGraphqlState); + eventHub.$off('mr.discussion.updated', this.updateGraphqlState); } if (this.pollingInterval) { @@ -327,15 +353,6 @@ export default { updateGraphqlState() { return this.$apollo.queries.state.refetch(); }, - updateMergeCommitMessage(includeDescription) { - const commitMessage = this.glFeatures.mergeRequestWidgetGraphql - ? this.state.defaultMergeCommitMessage - : this.mr.commitMessage; - const commitMessageWithDescription = this.glFeatures.mergeRequestWidgetGraphql - ? this.state.defaultMergeCommitMessageWithDescription - : this.mr.commitMessageWithDescription; - this.commitMessage = includeDescription ? commitMessageWithDescription : commitMessage; - }, handleMergeButtonClick(useAutoMerge, mergeImmediately = false, confirmationClicked = false) { if (this.showFailedPipelineModal && !confirmationClicked) { this.isPipelineFailedModalVisible = true; @@ -488,11 +505,21 @@ export default { }); }, }, + i18n: { + mergeCommitTemplateHintText: s__( + 'mrWidget|To change this default message, edit the template for merge commit messages. %{linkStart}Learn more.%{linkEnd}', + ), + }, }; </script> <template> - <div> + <div + :class="{ + 'gl-border-t-1 gl-border-t-solid gl-border-gray-100 gl-bg-gray-10 gl-pl-7': + glFeatures.restructuredMrWidget, + }" + > <div v-if="loading" class="mr-widget-body"> <div class="gl-w-full mr-ready-to-merge-loader"> <gl-skeleton-loader :width="418" :height="30"> @@ -504,11 +531,16 @@ export default { </div> </div> <template v-else> - <div class="mr-widget-body media"> - <status-icon :status="iconClass" /> + <div + class="mr-widget-body media" + :class="{ + 'mr-widget-body-line-height-1': glFeatures.restructuredMrWidget, + }" + > + <status-icon v-if="!glFeatures.restructuredMrWidget" :status="iconClass" /> <div class="media-body"> - <div class="mr-widget-body-controls gl-display-flex gl-align-items-center"> - <gl-button-group class="gl-align-self-start"> + <div class="mr-widget-body-controls gl-display-flex gl-align-items-center gl-flex-wrap"> + <gl-button-group v-if="restructuredWidgetShowMergeButtons" class="gl-align-self-start"> <gl-button size="medium" category="primary" @@ -555,14 +587,27 @@ export default { </gl-button-group> <div v-if="shouldShowMergeControls" + :class="{ 'gl-w-full gl-order-n1 gl-mb-5': glFeatures.restructuredMrWidget }" class="gl-display-flex gl-align-items-center gl-flex-wrap" > + <merge-train-helper-icon + v-if="shouldRenderMergeTrainHelperIcon" + :merge-train-when-pipeline-succeeds-docs-path=" + mr.mergeTrainWhenPipelineSucceedsDocsPath + " + class="gl-mx-3" + /> + <gl-form-checkbox v-if="canRemoveSourceBranch" id="remove-source-branch-input" v-model="removeSourceBranch" :disabled="isRemoveSourceBranchButtonDisabled" - class="js-remove-source-branch-checkbox gl-mx-3 gl-display-flex gl-align-items-center" + :class="{ + 'gl-mx-3': !glFeatures.restructuredMrWidget, + 'gl-mr-5': glFeatures.restructuredMrWidget, + }" + class="js-remove-source-branch-checkbox gl-display-flex gl-align-items-center" > {{ __('Delete source branch') }} </gl-form-checkbox> @@ -573,38 +618,146 @@ export default { v-model="squashBeforeMerge" :help-path="mr.squashBeforeMergeHelpPath" :is-disabled="isSquashReadOnly" - class="gl-mx-3" + :class="{ + 'gl-mx-3': !glFeatures.restructuredMrWidget, + 'gl-mr-5': glFeatures.restructuredMrWidget, + }" /> - <merge-train-helper-icon - v-if="shouldRenderMergeTrainHelperIcon" - :merge-train-when-pipeline-succeeds-docs-path=" - mr.mergeTrainWhenPipelineSucceedsDocsPath + <gl-form-checkbox + v-if=" + glFeatures.restructuredMrWidget && (shouldShowSquashEdit || shouldShowMergeEdit) " - /> + v-model="editCommitMessage" + class="gl-display-flex gl-align-items-center" + > + {{ __('Edit commit message') }} + </gl-form-checkbox> + </div> + <div + v-else-if="!glFeatures.restructuredMrWidget" + class="bold js-resolve-mr-widget-items-message gl-ml-3" + > + <div + v-if="hasPipelineMustSucceedConflict" + class="gl-display-flex gl-align-items-center" + data-testid="pipeline-succeed-conflict" + > + <gl-sprintf :message="pipelineMustSucceedConflictText" /> + <gl-link + :href="mr.pipelineMustSucceedDocsPath" + target="_blank" + class="gl-display-flex gl-ml-2" + > + <gl-icon name="question" /> + </gl-link> + </div> + <gl-sprintf v-else :message="mergeDisabledText" /> </div> - <template v-else> - <div class="bold js-resolve-mr-widget-items-message gl-ml-3"> - <div - v-if="hasPipelineMustSucceedConflict" - class="gl-display-flex gl-align-items-center" - data-testid="pipeline-succeed-conflict" + <template v-if="glFeatures.restructuredMrWidget"> + <div v-show="editCommitMessage" class="gl-w-full gl-order-n1"> + <ul + :class="{ + 'content-list': !glFeatures.restructuredMrWidget, + 'gl-list-style-none gl-p-0 gl-pt-4': glFeatures.restructuredMrWidget, + }" + class="border-top commits-list flex-list" > - <gl-sprintf :message="pipelineMustSucceedConflictText" /> - <gl-link - :href="mr.pipelineMustSucceedDocsPath" - target="_blank" - class="gl-display-flex gl-ml-2" + <commit-edit + v-if="shouldShowSquashEdit" + v-model="squashCommitMessage" + :label="__('Squash commit message')" + input-id="squash-message-edit" + class="gl-m-0! gl-p-0!" + > + <template #header> + <commit-message-dropdown v-model="squashCommitMessage" :commits="commits" /> + </template> + </commit-edit> + <commit-edit + v-if="shouldShowMergeEdit" + v-model="commitMessage" + :label="__('Merge commit message')" + input-id="merge-message-edit" + class="gl-m-0! gl-p-0!" > - <gl-icon name="question" /> - </gl-link> - </div> - <gl-sprintf v-else :message="mergeDisabledText" /> + <template #text-muted> + <p class="form-text text-muted"> + <gl-sprintf :message="$options.i18n.mergeCommitTemplateHintText"> + <template #link="{ content }"> + <gl-link + :href="commitTemplateHelpPage" + class="inline-link" + target="_blank" + > + {{ content }} + </gl-link> + </template> + </gl-sprintf> + </p> + </template> + </commit-edit> + </ul> + </div> + <div + v-if="!restructuredWidgetShowMergeButtons" + class="gl-w-full gl-order-n1 gl-text-gray-500" + > + <strong> + {{ __('Merge details') }} + </strong> + <ul class="gl-pl-4 gl-m-0"> + <li class="gl-line-height-normal"> + <added-commit-message + :is-squash-enabled="squashBeforeMerge" + :is-fast-forward-enabled="!shouldShowMergeEdit" + :commits-count="commitsCount" + :target-branch="stateData.targetBranch" + /> + </li> + <li class="gl-line-height-normal"> + <template v-if="removeSourceBranch"> + {{ __('Deletes the source branch.') }} + </template> + <template v-else> + {{ __('Does not delete the source branch.') }} + </template> + </li> + <li v-if="mr.relatedLinks" class="gl-line-height-normal"> + <related-links + :state="mr.state" + :related-links="mr.relatedLinks" + :show-assign-to-me="false" + class="mr-ready-merge-related-links gl-display-inline" + /> + </li> + </ul> + </div> + <div + v-else + :class="{ 'gl-mb-5': restructuredWidgetShowMergeButtons }" + class="gl-w-full gl-order-n1 gl-text-gray-500" + > + <added-commit-message + :is-squash-enabled="squashBeforeMerge" + :is-fast-forward-enabled="!shouldShowMergeEdit" + :commits-count="commitsCount" + :target-branch="stateData.targetBranch" + /> + <template v-if="mr.relatedLinks"> + · + <related-links + :state="mr.state" + :related-links="mr.relatedLinks" + :show-assign-to-me="false" + class="mr-ready-merge-related-links gl-display-inline" + /> + </template> </div> </template> </div> <div - v-if="showDangerMessageForMergeTrain" + v-if="showDangerMessageForMergeTrain && !glFeatures.restructuredMrWidget" class="gl-mt-5 gl-text-gray-500" data-testid="failed-pipeline-merge-train-text" > @@ -612,7 +765,7 @@ export default { </div> </div> </div> - <template v-if="shouldShowMergeControls"> + <template v-if="shouldShowMergeControls && !glFeatures.restructuredMrWidget"> <div v-if="!shouldShowMergeEdit" class="mr-fast-forward-message" @@ -621,7 +774,7 @@ export default { {{ __('Fast-forward merge without a merge commit') }} </div> <commits-header - v-if="shouldShowSquashEdit || shouldShowMergeEdit" + v-if="!glFeatures.restructuredMrWidget && (shouldShowSquashEdit || shouldShowMergeEdit)" :is-squash-enabled="squashBeforeMerge" :commits-count="commitsCount" :target-branch="stateData.targetBranch" @@ -646,15 +799,16 @@ export default { :label="__('Merge commit message')" input-id="merge-message-edit" > - <template #checkbox> - <label> - <input - id="include-description" - type="checkbox" - @change="updateMergeCommitMessage($event.target.checked)" - /> - {{ __('Include merge request description') }} - </label> + <template #text-muted> + <p class="form-text text-muted"> + <gl-sprintf :message="$options.i18n.mergeCommitTemplateHintText"> + <template #link="{ content }"> + <gl-link :href="commitTemplateHelpPage" class="inline-link" target="_blank"> + {{ content }} + </gl-link> + </template> + </gl-sprintf> + </p> </template> </commit-edit> </ul> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/squash_before_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/squash_before_merge.vue index 41b5983ae0c..c6227c4394d 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/squash_before_merge.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/squash_before_merge.vue @@ -1,15 +1,18 @@ <script> -import { GlIcon, GlTooltipDirective, GlFormCheckbox } from '@gitlab/ui'; +import { GlIcon, GlTooltipDirective, GlFormCheckbox, GlLink } from '@gitlab/ui'; +import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import { SQUASH_BEFORE_MERGE } from '../../i18n'; export default { components: { GlIcon, GlFormCheckbox, + GlLink, }, directives: { GlTooltip: GlTooltipDirective, }, + mixins: [glFeatureFlagsMixin()], i18n: { ...SQUASH_BEFORE_MERGE, }, @@ -33,6 +36,9 @@ export default { tooltipTitle() { return this.isDisabled ? this.$options.i18n.tooltipTitle : null; }, + helpIconName() { + return this.glFeatures.restructuredMrWidget ? 'question-o' : 'question'; + }, }, }; </script> @@ -51,18 +57,18 @@ export default { > {{ $options.i18n.checkboxLabel }} </gl-form-checkbox> - <a + <gl-link v-if="helpPath" v-gl-tooltip :href="helpPath" :title="$options.i18n.helpLabel" + :class="{ 'gl-text-blue-600': glFeatures.restructuredMrWidget }" target="_blank" - rel="noopener noreferrer nofollow" > - <gl-icon name="question" /> + <gl-icon :name="helpIconName" /> <span class="sr-only"> {{ $options.i18n.helpLabel }} </span> - </a> + </gl-link> </div> </template> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/work_in_progress.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/work_in_progress.vue index 790870ee4c6..fa4f8b76cb9 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/work_in_progress.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/work_in_progress.vue @@ -10,8 +10,8 @@ import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import eventHub from '../../event_hub'; import mergeRequestQueryVariablesMixin from '../../mixins/merge_request_query_variables'; import getStateQuery from '../../queries/get_state.query.graphql'; -import workInProgressQuery from '../../queries/states/work_in_progress.query.graphql'; -import removeWipMutation from '../../queries/toggle_wip.mutation.graphql'; +import draftQuery from '../../queries/states/draft.query.graphql'; +import removeDraftMutation from '../../queries/toggle_draft.mutation.graphql'; import StatusIcon from '../mr_widget_status_icon.vue'; export default { @@ -23,7 +23,7 @@ export default { mixins: [glFeatureFlagMixin(), mergeRequestQueryVariablesMixin], apollo: { userPermissions: { - query: workInProgressQuery, + query: draftQuery, skip() { return !this.glFeatures.mergeRequestWidgetGraphql; }, @@ -53,25 +53,25 @@ export default { }, }, methods: { - removeWipMutation() { + removeDraftMutation() { const { mergeRequestQueryVariables } = this; this.isMakingRequest = true; this.$apollo .mutate({ - mutation: removeWipMutation, + mutation: removeDraftMutation, variables: { ...mergeRequestQueryVariables, - wip: false, + draft: false, }, update( store, { data: { - mergeRequestSetWip: { + mergeRequestSetDraft: { errors, - mergeRequest: { mergeableDiscussionsState, workInProgress, title }, + mergeRequest: { mergeableDiscussionsState, draft, title }, }, }, }, @@ -91,7 +91,7 @@ export default { const data = produce(sourceData, (draftState) => { draftState.project.mergeRequest.mergeableDiscussionsState = mergeableDiscussionsState; - draftState.project.mergeRequest.workInProgress = workInProgress; + draftState.project.mergeRequest.draft = draft; draftState.project.mergeRequest.title = title; }); @@ -104,14 +104,14 @@ export default { optimisticResponse: { // eslint-disable-next-line @gitlab/require-i18n-strings __typename: 'Mutation', - mergeRequestSetWip: { + mergeRequestSetDraft: { __typename: 'MergeRequestSetWipPayload', errors: [], mergeRequest: { __typename: 'MergeRequest', mergeableDiscussionsState: true, title: this.mr.title, - workInProgress: false, + draft: false, }, }, }, @@ -119,7 +119,7 @@ export default { .then( ({ data: { - mergeRequestSetWip: { + mergeRequestSetDraft: { mergeRequest: { title }, }, }, @@ -137,9 +137,9 @@ export default { this.isMakingRequest = false; }); }, - handleRemoveWIP() { + handleRemoveDraft() { if (this.glFeatures.mergeRequestWidgetGraphql) { - this.removeWipMutation(); + this.removeDraftMutation(); } else { this.isMakingRequest = true; this.service @@ -178,8 +178,8 @@ export default { size="small" :disabled="isMakingRequest" :loading="isMakingRequest" - class="js-remove-wip gl-ml-3" - @click="handleRemoveWIP" + class="js-remove-draft gl-ml-3" + @click="handleRemoveDraft" > {{ s__('mrWidget|Mark as ready') }} </gl-button> diff --git a/app/assets/javascripts/vue_merge_request_widget/constants.js b/app/assets/javascripts/vue_merge_request_widget/constants.js index b88e83ccb0f..d0c6cf12e25 100644 --- a/app/assets/javascripts/vue_merge_request_widget/constants.js +++ b/app/assets/javascripts/vue_merge_request_widget/constants.js @@ -17,14 +17,12 @@ export const AUTO_MERGE_STRATEGIES = [MWPS_MERGE_STRATEGY, MTWPS_MERGE_STRATEGY, // SP - "Suggest Pipelines" export const SP_TRACK_LABEL = 'no_pipeline_noticed'; -export const SP_LINK_TRACK_EVENT = 'click_link'; export const SP_SHOW_TRACK_EVENT = 'click_button'; -export const SP_LINK_TRACK_VALUE = 30; export const SP_SHOW_TRACK_VALUE = 10; export const SP_HELP_CONTENT = s__( - `mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd} by simply adding a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust.`, + `mrWidget|GitLab %{linkStart}CI/CD can automatically build, test, and deploy your application.%{linkEnd} It only takes a few minutes to get started, and we can help you create a pipeline configuration file.`, ); -export const SP_HELP_URL = 'https://about.gitlab.com/blog/2019/07/12/guide-to-ci-cd-pipelines/'; +export const SP_HELP_URL = 'https://docs.gitlab.com/ee/ci/quick_start/'; export const SP_ICON_NAME = 'status_notfound'; export const MERGE_ACTIVE_STATUS_PHRASES = [ 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 349e9d29355..9cbc0b0e5d1 100644 --- a/app/assets/javascripts/vue_merge_request_widget/extensions/issues.js +++ b/app/assets/javascripts/vue_merge_request_widget/extensions/issues.js @@ -11,6 +11,7 @@ export default { label: 'Issues', loading: 'Loading issues...', }, + expandEvent: 'i_testing_load_performance_widget_total', // Add an array of props // These then get mapped to values stored in the MR Widget store props: ['targetProjectFullPath', 'conflictsDocsPath'], @@ -29,7 +30,15 @@ export default { // Tertiary action buttons that will take the user elsewhere // in the GitLab app tertiaryButtons() { - return [{ text: 'Full report', href: this.conflictsDocsPath, target: '_blank' }]; + return [ + { + text: 'Click me', + onClick() { + console.log('Hello world'); + }, + }, + { text: 'Full report', href: this.conflictsDocsPath, target: '_blank' }, + ]; }, }, methods: { @@ -66,6 +75,7 @@ export default { // href: 'https://google.com', // Required: href for the link // text: 'Link text', // Required: Text to be used inside the link // }, + actions: [{ text: 'Full report', href: 'https://gitlab.com', target: '_blank' }], })); }); }, diff --git a/app/assets/javascripts/vue_merge_request_widget/extensions/issues_collapsed.query.graphql b/app/assets/javascripts/vue_merge_request_widget/extensions/issues_collapsed.query.graphql index 389a81e0a61..da1cace4598 100644 --- a/app/assets/javascripts/vue_merge_request_widget/extensions/issues_collapsed.query.graphql +++ b/app/assets/javascripts/vue_merge_request_widget/extensions/issues_collapsed.query.graphql @@ -1,4 +1,4 @@ -query getIssues($projectPath: ID!) { +query getProjectIssues($projectPath: ID!) { project(fullPath: $projectPath) { issues { count diff --git a/app/assets/javascripts/vue_merge_request_widget/index.js b/app/assets/javascripts/vue_merge_request_widget/index.js index f5dbcec7dbe..8d596465970 100644 --- a/app/assets/javascripts/vue_merge_request_widget/index.js +++ b/app/assets/javascripts/vue_merge_request_widget/index.js @@ -13,12 +13,7 @@ Vue.use(Translate); Vue.use(VueApollo); const apolloProvider = new VueApollo({ - defaultClient: createDefaultClient( - {}, - { - assumeImmutableResults: true, - }, - ), + defaultClient: createDefaultClient(), }); export default () => { diff --git a/app/assets/javascripts/vue_merge_request_widget/mixins/ready_to_merge.js b/app/assets/javascripts/vue_merge_request_widget/mixins/ready_to_merge.js index cf6472f2c8c..83789f10285 100644 --- a/app/assets/javascripts/vue_merge_request_widget/mixins/ready_to_merge.js +++ b/app/assets/javascripts/vue_merge_request_widget/mixins/ready_to_merge.js @@ -1,9 +1,13 @@ import { __ } from '~/locale'; export const MERGE_DISABLED_TEXT = __('You can only merge once the items above are resolved.'); +export const MERGE_DISABLED_SKIPPED_PIPELINE_TEXT = __( + "Merge blocked: pipeline must succeed. It's waiting for a manual job to continue.", +); export const PIPELINE_MUST_SUCCEED_CONFLICT_TEXT = __( 'A CI/CD pipeline must run and be successful before merge.', ); +export const PIPELINE_SKIPPED_STATUS = 'SKIPPED'; export default { computed: { @@ -17,6 +21,10 @@ export default { ); }, mergeDisabledText() { + if (this.pipeline?.status === PIPELINE_SKIPPED_STATUS) { + return MERGE_DISABLED_SKIPPED_PIPELINE_TEXT; + } + return MERGE_DISABLED_TEXT; }, pipelineMustSucceedConflictText() { 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 3ac1e881658..c98dc426224 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 @@ -91,6 +91,7 @@ export default { MrWidgetApprovals, SecurityReportsApp: () => import('~/vue_shared/security_reports/security_reports_app.vue'), MergeChecksFailed: () => import('./components/states/merge_checks_failed.vue'), + ReadyToMerge: ReadyToMergeState, }, apollo: { state: { @@ -213,6 +214,9 @@ export default { window.gon?.features?.refactorMrWidgetsExtensionsUser ); }, + isRestructuredMrWidgetEnabled() { + return window.gon?.features?.restructuredMrWidget; + }, }, watch: { 'mr.machineValue': { @@ -547,12 +551,17 @@ export default { <div class="mr-widget-section"> <component :is="componentName" :mr="mr" :service="service" /> - - <div class="mr-widget-info"> + <ready-to-merge + v-if="isRestructuredMrWidgetEnabled && mr.commitsCount" + :mr="mr" + :service="service" + /> + <div v-else class="mr-widget-info"> <mr-widget-related-links v-if="shouldRenderRelatedLinks" :state="mr.state" :related-links="mr.relatedLinks" + class="mr-info-list gl-ml-7 gl-pb-5" /> <source-branch-removal-status v-if="shouldRenderSourceBranchRemovalStatus" /> 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 871aa880b36..bfb1517be81 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 @@ -23,7 +23,7 @@ query getState($projectPath: ID!, $iid: String!) { userPermissions { canMerge } - workInProgress + draft } } } diff --git a/app/assets/javascripts/vue_merge_request_widget/queries/states/auto_merge_enabled.query.graphql b/app/assets/javascripts/vue_merge_request_widget/queries/states/auto_merge_enabled.query.graphql index daf21e75b3b..e0215fbd969 100644 --- a/app/assets/javascripts/vue_merge_request_widget/queries/states/auto_merge_enabled.query.graphql +++ b/app/assets/javascripts/vue_merge_request_widget/queries/states/auto_merge_enabled.query.graphql @@ -1,6 +1,6 @@ #import "./auto_merge_enabled.fragment.graphql" -query autoMergeEnabledQuery($projectPath: ID!, $iid: String!) { +query autoMergeEnabled($projectPath: ID!, $iid: String!) { project(fullPath: $projectPath) { mergeRequest(iid: $iid) { ...autoMergeEnabled diff --git a/app/assets/javascripts/vue_merge_request_widget/queries/states/conflicts.query.graphql b/app/assets/javascripts/vue_merge_request_widget/queries/states/conflicts.query.graphql index 186c0e64561..e66ac01ab12 100644 --- a/app/assets/javascripts/vue_merge_request_widget/queries/states/conflicts.query.graphql +++ b/app/assets/javascripts/vue_merge_request_widget/queries/states/conflicts.query.graphql @@ -1,4 +1,4 @@ -query workInProgressQuery($projectPath: ID!, $iid: String!) { +query workInProgress($projectPath: ID!, $iid: String!) { project(fullPath: $projectPath) { mergeRequest(iid: $iid) { shouldBeRebased diff --git a/app/assets/javascripts/vue_merge_request_widget/queries/states/work_in_progress.query.graphql b/app/assets/javascripts/vue_merge_request_widget/queries/states/draft.query.graphql index 73e205ebf2b..0983c28448e 100644 --- a/app/assets/javascripts/vue_merge_request_widget/queries/states/work_in_progress.query.graphql +++ b/app/assets/javascripts/vue_merge_request_widget/queries/states/draft.query.graphql @@ -1,4 +1,4 @@ -query workInProgressQuery($projectPath: ID!, $iid: String!) { +query mrUserPermission($projectPath: ID!, $iid: String!) { project(fullPath: $projectPath) { mergeRequest(iid: $iid) { userPermissions { diff --git a/app/assets/javascripts/vue_merge_request_widget/queries/states/new_ready_to_merge.query.graphql b/app/assets/javascripts/vue_merge_request_widget/queries/states/new_ready_to_merge.query.graphql index 3b34be73c15..21c3ffd8321 100644 --- a/app/assets/javascripts/vue_merge_request_widget/queries/states/new_ready_to_merge.query.graphql +++ b/app/assets/javascripts/vue_merge_request_widget/queries/states/new_ready_to_merge.query.graphql @@ -1,4 +1,4 @@ -query readyToMergeQuery($projectPath: ID!, $iid: String!) { +query getReadyToMergeStatus($projectPath: ID!, $iid: String!) { project(fullPath: $projectPath) { mergeRequest(iid: $iid) { userPermissions { diff --git a/app/assets/javascripts/vue_merge_request_widget/queries/states/ready_to_merge.fragment.graphql b/app/assets/javascripts/vue_merge_request_widget/queries/states/ready_to_merge.fragment.graphql index 367b9ad1cdf..b2a1be5c5a9 100644 --- a/app/assets/javascripts/vue_merge_request_widget/queries/states/ready_to_merge.fragment.graphql +++ b/app/assets/javascripts/vue_merge_request_widget/queries/states/ready_to_merge.fragment.graphql @@ -18,7 +18,9 @@ fragment ReadyToMerge on Project { commitCount diffHeadSha userPermissions { + canMerge removeSourceBranch + updateMergeRequest } targetBranch mergeError diff --git a/app/assets/javascripts/vue_merge_request_widget/queries/states/ready_to_merge.query.graphql b/app/assets/javascripts/vue_merge_request_widget/queries/states/ready_to_merge.query.graphql index 78259e1f553..f713739f65a 100644 --- a/app/assets/javascripts/vue_merge_request_widget/queries/states/ready_to_merge.query.graphql +++ b/app/assets/javascripts/vue_merge_request_widget/queries/states/ready_to_merge.query.graphql @@ -1,6 +1,6 @@ #import "./ready_to_merge.fragment.graphql" -query readyToMergeQuery($projectPath: ID!, $iid: String!) { +query readyToMerge($projectPath: ID!, $iid: String!) { project(fullPath: $projectPath) { ...ReadyToMerge } diff --git a/app/assets/javascripts/vue_merge_request_widget/queries/toggle_draft.mutation.graphql b/app/assets/javascripts/vue_merge_request_widget/queries/toggle_draft.mutation.graphql new file mode 100644 index 00000000000..200fb1b7ca5 --- /dev/null +++ b/app/assets/javascripts/vue_merge_request_widget/queries/toggle_draft.mutation.graphql @@ -0,0 +1,10 @@ +mutation toggleDraftStatus($projectPath: ID!, $iid: String!, $draft: Boolean!) { + mergeRequestSetDraft(input: { projectPath: $projectPath, iid: $iid, draft: $draft }) { + mergeRequest { + mergeableDiscussionsState + title + draft + } + errors + } +} diff --git a/app/assets/javascripts/vue_merge_request_widget/queries/toggle_wip.mutation.graphql b/app/assets/javascripts/vue_merge_request_widget/queries/toggle_wip.mutation.graphql deleted file mode 100644 index cfaa198d516..00000000000 --- a/app/assets/javascripts/vue_merge_request_widget/queries/toggle_wip.mutation.graphql +++ /dev/null @@ -1,10 +0,0 @@ -mutation toggleWIPStatus($projectPath: ID!, $iid: String!, $wip: Boolean!) { - mergeRequestSetWip(input: { projectPath: $projectPath, iid: $iid, wip: $wip }) { - mergeRequest { - mergeableDiscussionsState - title - workInProgress - } - errors - } -} diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/get_state_key.js b/app/assets/javascripts/vue_merge_request_widget/stores/get_state_key.js index 65d78fc283c..2ae4f4da2f3 100644 --- a/app/assets/javascripts/vue_merge_request_widget/stores/get_state_key.js +++ b/app/assets/javascripts/vue_merge_request_widget/stores/get_state_key.js @@ -1,14 +1,14 @@ import { stateKey } from './state_maps'; export default function deviseState() { - if (this.hasMergeChecksFailed) { + if (!this.commitsCount) { + return stateKey.nothingToMerge; + } else if (this.hasMergeChecksFailed && !this.autoMergeEnabled) { return stateKey.mergeChecksFailed; } else if (this.projectArchived) { return stateKey.archived; } else if (this.branchMissing) { return stateKey.missingBranch; - } else if (!this.commitsCount) { - return stateKey.nothingToMerge; } else if (this.mergeStatus === 'unchecked' || this.mergeStatus === 'checking') { return stateKey.checking; } else if (this.hasConflicts) { @@ -17,8 +17,8 @@ export default function deviseState() { return stateKey.rebase; } else if (this.onlyAllowMergeIfPipelineSucceeds && this.isPipelineFailed) { return stateKey.pipelineFailed; - } else if (this.workInProgress) { - return stateKey.workInProgress; + } else if (this.draft) { + return stateKey.draft; } else if (this.hasMergeableDiscussionsState && !this.autoMergeEnabled) { return stateKey.unresolvedDiscussions; } else if (this.isPipelineBlocked) { diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js index 6628225cd46..10a2907c81a 100644 --- a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js +++ b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js @@ -164,7 +164,7 @@ export default class MergeRequestStore { this.projectArchived = data.project_archived; this.isSHAMismatch = this.sha !== data.diff_head_sha; this.shouldBeRebased = Boolean(data.should_be_rebased); - this.workInProgress = data.work_in_progress; + this.draft = data.draft; } const currentUser = data.current_user; @@ -207,7 +207,7 @@ export default class MergeRequestStore { this.isPipelineFailed = this.ciStatus === 'failed' || this.ciStatus === 'canceled'; this.isSHAMismatch = this.sha !== mergeRequest.diffHeadSha; this.shouldBeRebased = mergeRequest.shouldBeRebased; - this.workInProgress = mergeRequest.workInProgress; + this.draft = mergeRequest.draft; this.mergeRequestState = mergeRequest.state; this.setState(); diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/state_maps.js b/app/assets/javascripts/vue_merge_request_widget/stores/state_maps.js index 4cb23407a74..9dfeaee905c 100644 --- a/app/assets/javascripts/vue_merge_request_widget/stores/state_maps.js +++ b/app/assets/javascripts/vue_merge_request_widget/stores/state_maps.js @@ -4,7 +4,7 @@ export const stateToComponentMap = { merging: 'mr-widget-merging', conflicts: 'mr-widget-conflicts', missingBranch: 'mr-widget-missing-branch', - workInProgress: 'mr-widget-wip', + draft: 'mr-widget-wip', readyToMerge: 'mr-widget-ready-to-merge', nothingToMerge: 'mr-widget-nothing-to-merge', notAllowedToMerge: 'mr-widget-not-allowed', @@ -24,7 +24,7 @@ export const stateToComponentMap = { export const statesToShowHelpWidget = [ 'merging', 'conflicts', - 'workInProgress', + 'draft', 'readyToMerge', 'checking', 'unresolvedDiscussions', @@ -40,7 +40,7 @@ export const stateKey = { nothingToMerge: 'nothingToMerge', checking: 'checking', conflicts: 'conflicts', - workInProgress: 'workInProgress', + draft: 'draft', pipelineFailed: 'pipelineFailed', unresolvedDiscussions: 'unresolvedDiscussions', pipelineBlocked: 'pipelineBlocked', |