diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_alert_message.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_alert_message.vue new file mode 100644 index 0000000000000000000000000000000000000000..040315b3c66510c1043d9a526d406bf08ccc6f1e --- /dev/null +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_alert_message.vue @@ -0,0 +1,46 @@ + + + diff --git a/app/assets/javascripts/vue_merge_request_widget/constants.js b/app/assets/javascripts/vue_merge_request_widget/constants.js new file mode 100644 index 0000000000000000000000000000000000000000..0a29d55fbd62a0733a4c0a74d0d6deed4177702b --- /dev/null +++ b/app/assets/javascripts/vue_merge_request_widget/constants.js @@ -0,0 +1,5 @@ +export const WARNING = 'warning'; +export const DANGER = 'danger'; + +export const WARNING_MESSAGE_CLASS = 'warning_message'; +export const DANGER_MESSAGE_CLASS = 'danger_message'; 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 57c4dfbe3b76b6b583f6c333022fb330e551ea9a..aa4ecb0aac3acee3f14e8c7b8a80a6944aa480e1 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 @@ -12,6 +12,7 @@ import WidgetMergeHelp from './components/mr_widget_merge_help.vue'; import MrWidgetPipelineContainer from './components/mr_widget_pipeline_container.vue'; import Deployment from './components/deployment.vue'; import WidgetRelatedLinks from './components/mr_widget_related_links.vue'; +import MrWidgetAlertMessage from './components/mr_widget_alert_message.vue'; import MergedState from './components/states/mr_widget_merged.vue'; import ClosedState from './components/states/mr_widget_closed.vue'; import MergingState from './components/states/mr_widget_merging.vue'; @@ -46,6 +47,7 @@ export default { MrWidgetPipelineContainer, Deployment, 'mr-widget-related-links': WidgetRelatedLinks, + MrWidgetAlertMessage, 'mr-widget-merged': MergedState, 'mr-widget-closed': ClosedState, 'mr-widget-merging': MergingState, @@ -110,6 +112,18 @@ export default { shouldRenderMergedPipeline() { return this.mr.state === 'merged' && !_.isEmpty(this.mr.mergePipeline); }, + showMergePipelineForkWarning() { + return Boolean( + this.mr.mergePipelinesEnabled && this.mr.sourceProjectId !== this.mr.targetProjectId, + ); + }, + showTargetBranchAdvancedError() { + return Boolean( + this.mr.pipeline && + this.mr.pipeline.target_sha && + this.mr.pipeline.target_sha !== this.mr.targetBranchSha, + ); + }, }, watch: { state(newVal, oldVal) { @@ -328,6 +342,30 @@ export default { :related-links="mr.relatedLinks" /> + + {{ + s__( + 'mrWidget|Fork merge requests do not create merge request pipelines which validate a post merge result', + ) + }} + + + + {{ + s__( + 'mrWidget|The target branch has advanced, which invalidates the merge request pipeline. Please update the source branch and retry merging', + ) + }} + + 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 58363f632a9cc18ce17e34baab29800faa52d916..5fa6ec9328c3c8eba2a7d1a8f3970dfc8143f5d0 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 @@ -28,9 +28,11 @@ export default class MergeRequestStore { this.iid = data.iid; this.title = data.title; this.targetBranch = data.target_branch; + this.targetBranchSha = data.target_branch_sha; this.sourceBranch = data.source_branch; this.sourceBranchProtected = data.source_branch_protected; this.conflictsDocsPath = data.conflicts_docs_path; + this.mergeRequestPipelinesHelpPath = data.merge_request_pipelines_docs_path; this.mergeStatus = data.merge_status; this.commitMessage = data.default_merge_commit_message; this.shortMergeCommitSha = data.short_merge_commit_sha; @@ -99,6 +101,9 @@ export default class MergeRequestStore { this.allowCollaboration = data.allow_collaboration; this.targetProjectFullPath = data.target_project_full_path; this.sourceProjectFullPath = data.source_project_full_path; + this.sourceProjectId = data.source_project_id; + this.targetProjectId = data.target_project_id; + this.mergePipelinesEnabled = data.merge_pipelines_enabled; // Cherry-pick and Revert actions related this.canCherryPickInCurrentMR = currentUser.can_cherry_pick_on_current_merge_request || false; diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss index db6c107210ed06a143a8abf8cd68b7b461e266c1..71f471df43779c1fb0216470381b5dc8335087a0 100644 --- a/app/assets/stylesheets/framework/common.scss +++ b/app/assets/stylesheets/framework/common.scss @@ -205,12 +205,12 @@ li.note { } } -.warning_message { - border-left: 4px solid $orange-200; - color: $orange-700; +@mixin message($background-color, $border-color, $text-color) { + border-left: 4px solid $border-color; + color: $text-color; padding: 10px; margin-bottom: 10px; - background: $orange-100; + background: $background-color; padding-left: 20px; &.centered { @@ -218,6 +218,14 @@ li.note { } } +.warning_message { + @include message($orange-100, $orange-200, $orange-700); +} + +.danger_message { + @include message($red-100, $red-200, $red-900); +} + .gitlab-promo { a { color: $gl-gray-350; diff --git a/app/presenters/merge_request_presenter.rb b/app/presenters/merge_request_presenter.rb index 284b1ad9b554b3772884b746a1bdd43803748c89..55103c0a95cbdfe117376e3404ba8c8181bd21b6 100644 --- a/app/presenters/merge_request_presenter.rb +++ b/app/presenters/merge_request_presenter.rb @@ -209,6 +209,10 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated help_page_path('user/project/merge_requests/resolve_conflicts.md') end + def merge_request_pipelines_docs_path + help_page_path('ci/merge_request_pipelines/index.md') + end + private def cached_can_be_reverted? diff --git a/app/serializers/merge_request_widget_entity.rb b/app/serializers/merge_request_widget_entity.rb index 4831eb32c9650fd8559156252d3737149a6511bd..b130f447ccebaa8bfcd14a4f51d96af01505d3e4 100644 --- a/app/serializers/merge_request_widget_entity.rb +++ b/app/serializers/merge_request_widget_entity.rb @@ -256,6 +256,10 @@ class MergeRequestWidgetEntity < IssuableEntity presenter(merge_request).conflicts_docs_path end + expose :merge_request_pipelines_docs_path do |merge_request| + presenter(merge_request).merge_request_pipelines_docs_path + end + private delegate :current_user, to: :request diff --git a/changelogs/unreleased/nfriend-update-merge-request-widget-for-post-merge-pipelines.yml b/changelogs/unreleased/nfriend-update-merge-request-widget-for-post-merge-pipelines.yml new file mode 100644 index 0000000000000000000000000000000000000000..420c8f2923c6aa8402dd6555d330f55aadb78056 --- /dev/null +++ b/changelogs/unreleased/nfriend-update-merge-request-widget-for-post-merge-pipelines.yml @@ -0,0 +1,5 @@ +--- +title: Add two new warning messages to the MR widget about merge request pipelines +merge_request: 25983 +author: +type: added diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 2f0358440d57376c54821fa6bcda4a7df09b37d2..3b1b231bb8c0e8932aa2c08d488f0fdb9693ce99 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -10062,6 +10062,9 @@ msgstr "" msgid "mrWidget|Fast-forward merge is not possible. To merge this request, first rebase locally." msgstr "" +msgid "mrWidget|Fork merge requests do not create merge request pipelines which validate a post merge result" +msgstr "" + msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the" msgstr "" @@ -10155,6 +10158,9 @@ msgstr "" msgid "mrWidget|The source branch will not be deleted" msgstr "" +msgid "mrWidget|The target branch has advanced, which invalidates the merge request pipeline. Please update the source branch and retry merging" +msgstr "" + msgid "mrWidget|There are merge conflicts" msgstr "" diff --git a/spec/fixtures/api/schemas/entities/merge_request_widget.json b/spec/fixtures/api/schemas/entities/merge_request_widget.json index 6b1cd60c25d5547292b2a623cc761838696fe813..7018cb9a3059dec9b62a853763cc0dbbb46f6036 100644 --- a/spec/fixtures/api/schemas/entities/merge_request_widget.json +++ b/spec/fixtures/api/schemas/entities/merge_request_widget.json @@ -125,6 +125,7 @@ "test_reports_path": { "type": ["string", "null"] }, "can_receive_suggestion": { "type": "boolean" }, "source_branch_protected": { "type": "boolean" }, - "conflicts_docs_path": { "type": ["string", "null"] } + "conflicts_docs_path": { "type": ["string", "null"] }, + "merge_request_pipelines_docs_path": { "type": ["string", "null"] } } } diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_alert_message_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_alert_message_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..f115cb457e5fc4732a15c9dbae75d1a4571a2471 --- /dev/null +++ b/spec/javascripts/vue_mr_widget/components/mr_widget_alert_message_spec.js @@ -0,0 +1,63 @@ +import { shallowMount, createLocalVue } from '@vue/test-utils'; +import MrWidgetAlertMessage from '~/vue_merge_request_widget/components/mr_widget_alert_message.vue'; +import { GlLink } from '@gitlab/ui'; + +describe('MrWidgetAlertMessage', () => { + let wrapper; + + beforeEach(() => { + const localVue = createLocalVue(); + + wrapper = shallowMount(localVue.extend(MrWidgetAlertMessage), { + propsData: {}, + localVue, + }); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + describe('when type is not provided', () => { + it('should render a red message', () => { + expect(wrapper.classes()).toContain('danger_message'); + expect(wrapper.classes()).not.toContain('warning_message'); + }); + }); + + describe('when type === "danger"', () => { + it('should render a red message', () => { + wrapper.setProps({ type: 'danger' }); + + expect(wrapper.classes()).toContain('danger_message'); + expect(wrapper.classes()).not.toContain('warning_message'); + }); + }); + + describe('when type === "warning"', () => { + it('should render a red message', () => { + wrapper.setProps({ type: 'warning' }); + + expect(wrapper.classes()).toContain('warning_message'); + expect(wrapper.classes()).not.toContain('danger_message'); + }); + }); + + describe('when helpPath is not provided', () => { + it('should not render a help icon/link', () => { + const link = wrapper.find(GlLink); + + expect(link.exists()).toBe(false); + }); + }); + + describe('when helpPath is provided', () => { + it('should render a help icon/link', () => { + wrapper.setProps({ helpPath: '/path/to/help/docs' }); + const link = wrapper.find(GlLink); + + expect(link.exists()).toBe(true); + expect(link.attributes().href).toBe('/path/to/help/docs'); + }); + }); +}); diff --git a/spec/javascripts/vue_mr_widget/mock_data.js b/spec/javascripts/vue_mr_widget/mock_data.js index 7ab203a6011e0b39b423edf64b5062caca7c006c..dda16375103a747a8736779bee9bae0e55e90f8b 100644 --- a/spec/javascripts/vue_mr_widget/mock_data.js +++ b/spec/javascripts/vue_mr_widget/mock_data.js @@ -233,6 +233,7 @@ export default { merge_commit_path: 'http://localhost:3000/root/acets-app/commit/53027d060246c8f47e4a9310fb332aa52f221775', troubleshooting_docs_path: 'help', + merge_request_pipelines_docs_path: '/help/ci/merge_request_pipelines/index.md', squash: true, }; diff --git a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js index 3e8f73646c8e2bcbb6b8a3cfaed1e20ebfedf8d6..690fcd3e224d369f80613e007b8984d9d6199d26 100644 --- a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js +++ b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js @@ -183,6 +183,85 @@ describe('mrWidgetOptions', () => { }); }); }); + + describe('showMergePipelineForkWarning', () => { + describe('when the source project and target project are the same', () => { + beforeEach(done => { + Vue.set(vm.mr, 'mergePipelinesEnabled', true); + Vue.set(vm.mr, 'sourceProjectId', 1); + Vue.set(vm.mr, 'targetProjectId', 1); + vm.$nextTick(done); + }); + + it('should be false', () => { + expect(vm.showMergePipelineForkWarning).toEqual(false); + }); + }); + + describe('when merge pipelines are not enabled', () => { + beforeEach(done => { + Vue.set(vm.mr, 'mergePipelinesEnabled', false); + Vue.set(vm.mr, 'sourceProjectId', 1); + Vue.set(vm.mr, 'targetProjectId', 2); + vm.$nextTick(done); + }); + + it('should be false', () => { + expect(vm.showMergePipelineForkWarning).toEqual(false); + }); + }); + + describe('when merge pipelines are enabled _and_ the source project and target project are different', () => { + beforeEach(done => { + Vue.set(vm.mr, 'mergePipelinesEnabled', true); + Vue.set(vm.mr, 'sourceProjectId', 1); + Vue.set(vm.mr, 'targetProjectId', 2); + vm.$nextTick(done); + }); + + it('should be true', () => { + expect(vm.showMergePipelineForkWarning).toEqual(true); + }); + }); + }); + + describe('showTargetBranchAdvancedError', () => { + describe(`when the pipeline's target_sha property doesn't exist`, () => { + beforeEach(done => { + Vue.set(vm.mr.pipeline, 'target_sha', undefined); + Vue.set(vm.mr, 'targetBranchSha', 'abcd'); + vm.$nextTick(done); + }); + + it('should be false', () => { + expect(vm.showTargetBranchAdvancedError).toEqual(false); + }); + }); + + describe(`when the pipeline's target_sha matches the target branch's sha`, () => { + beforeEach(done => { + Vue.set(vm.mr.pipeline, 'target_sha', 'abcd'); + Vue.set(vm.mr, 'targetBranchSha', 'abcd'); + vm.$nextTick(done); + }); + + it('should be false', () => { + expect(vm.showTargetBranchAdvancedError).toEqual(false); + }); + }); + + describe(`when the pipeline's target_sha does not match the target branch's sha`, () => { + beforeEach(done => { + Vue.set(vm.mr.pipeline, 'target_sha', 'abcd'); + Vue.set(vm.mr, 'targetBranchSha', 'bcde'); + vm.$nextTick(done); + }); + + it('should be true', () => { + expect(vm.showTargetBranchAdvancedError).toEqual(true); + }); + }); + }); }); describe('methods', () => {