From 69a11ec09c0a1023b2142c0d6cb305b3d7c6c763 Mon Sep 17 00:00:00 2001 From: Niklas Date: Thu, 5 Sep 2024 21:03:16 +0000 Subject: [PATCH 1/8] Add frontend and controllers for scheduled merge Changelog: added --- .../components/checks/constants.js | 9 ++++ .../components/checks/merge_time.vue | 47 +++++++++++++++++++ .../components/checks/message.vue | 9 +--- .../queries/get_state.query.graphql | 1 + .../stores/mr_widget_store.js | 1 + .../merge_requests/application_controller.rb | 1 + .../merge_requests/creations_controller.rb | 9 +++- .../projects/merge_requests_controller.rb | 9 +++- app/models/merge_request.rb | 6 +++ .../shared/issuable/form/_metadata.html.haml | 8 ++++ doc/user/project/merge_requests/auto_merge.md | 22 +++++++++ locale/gitlab.pot | 12 +++++ .../components/checks/merge_time_spec.js | 36 ++++++++++++++ .../projects/merge_requests/creations_spec.rb | 38 +++++++++++++++ .../merge_requests_controller_spec.rb | 26 ++++++++++ 15 files changed, 224 insertions(+), 10 deletions(-) create mode 100644 app/assets/javascripts/vue_merge_request_widget/components/checks/merge_time.vue create mode 100644 spec/frontend/vue_merge_request_widget/components/checks/merge_time_spec.js diff --git a/app/assets/javascripts/vue_merge_request_widget/components/checks/constants.js b/app/assets/javascripts/vue_merge_request_widget/components/checks/constants.js index 3995ccfca08b0b..dd70b61282ee89 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/checks/constants.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/checks/constants.js @@ -4,6 +4,7 @@ export const COMPONENTS = { conflict: () => import('./conflicts.vue'), discussions_not_resolved: () => import('./unresolved_discussions.vue'), draft_status: () => import('./draft.vue'), + merge_time: () => import('./merge_time.vue'), need_rebase: () => import('./rebase.vue'), default: () => import('./message.vue'), requested_changes: () => @@ -38,4 +39,12 @@ export const FAILURE_REASONS = { locked_lfs_files: __('All LFS files must be unlocked.'), security_policy_evaluation: __('All security policies must be evaluated.'), security_policy_violations: __('All policy rules must be satisfied.'), + merge_time: __('Merge request must be merged at the configured time.'), +}; + +export const ICON_NAMES = { + failed: 'failed', + inactive: 'neutral', + success: 'success', + warning: 'warning', }; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/checks/merge_time.vue b/app/assets/javascripts/vue_merge_request_widget/components/checks/merge_time.vue new file mode 100644 index 00000000000000..bc364d390dbe0d --- /dev/null +++ b/app/assets/javascripts/vue_merge_request_widget/components/checks/merge_time.vue @@ -0,0 +1,47 @@ + + + diff --git a/app/assets/javascripts/vue_merge_request_widget/components/checks/message.vue b/app/assets/javascripts/vue_merge_request_widget/components/checks/message.vue index bfd7c525ea9f45..56c0f75248c8cb 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/checks/message.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/checks/message.vue @@ -1,14 +1,7 @@ + + diff --git a/app/assets/javascripts/pages/projects/merge_requests/init_merge_request.js b/app/assets/javascripts/pages/projects/merge_requests/init_merge_request.js index 0e66c3521dd594..0773b6aed5cc44 100644 --- a/app/assets/javascripts/pages/projects/merge_requests/init_merge_request.js +++ b/app/assets/javascripts/pages/projects/merge_requests/init_merge_request.js @@ -7,6 +7,7 @@ import { addShortcutsExtension } from '~/behaviors/shortcuts'; import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation'; import LabelsSelect from '~/labels/labels_select'; import { mountMilestoneDropdown } from '~/sidebar/mount_sidebar'; +import mountMergeScheduleInput from './mount_merge_schedule_input'; export default () => { addShortcutsExtension(ShortcutsNavigation); @@ -14,4 +15,5 @@ export default () => { IssuableLabelSelector(); new LabelsSelect(); mountMilestoneDropdown('[name="merge_request[milestone_id]"]'); + mountMergeScheduleInput(); }; diff --git a/app/assets/javascripts/pages/projects/merge_requests/mount_merge_schedule_input.js b/app/assets/javascripts/pages/projects/merge_requests/mount_merge_schedule_input.js new file mode 100644 index 00000000000000..acc7758e2cdc09 --- /dev/null +++ b/app/assets/javascripts/pages/projects/merge_requests/mount_merge_schedule_input.js @@ -0,0 +1,24 @@ +import Vue from 'vue'; +import MergeScheduleInput from '~/merge_requests/components/merge_schedule_input.vue'; + +export default () => { + const el = document.querySelector('.js-merge-request-schedule-input'); + + if (!el) return false; + + const { timezoneName, timezoneUtcOffset, mergeAfter, paramKey } = el.dataset; + + return new Vue({ + el, + render(h) { + return h(MergeScheduleInput, { + props: { + timezoneName, + timezoneUtcOffset, + mergeAfter, + paramKey, + }, + }); + }, + }); +}; diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb index d6398e1946c09e..2c367ab77b2021 100644 --- a/app/services/merge_requests/base_service.rb +++ b/app/services/merge_requests/base_service.rb @@ -304,6 +304,17 @@ def abort_auto_merge_with_todo(merge_request, reason) todo_service.merge_request_became_unmergeable(merge_request) end + + def set_merge_schedule(merge_request, merge_after) + if merge_after.present? + merge_after_time = Time.zone.parse(merge_after).change(zone: current_user&.timezone) + merge_schedule = merge_request.merge_schedule || merge_request.build_merge_schedule + merge_schedule.merge_after = merge_after_time + merge_schedule.save + elsif merge_request.merge_schedule.present? + merge_request.merge_schedule.destroy + end + end end end diff --git a/app/services/merge_requests/create_service.rb b/app/services/merge_requests/create_service.rb index 41542ed5941f99..6cd6b81e59f3d7 100644 --- a/app/services/merge_requests/create_service.rb +++ b/app/services/merge_requests/create_service.rb @@ -15,12 +15,7 @@ def execute created_merge_request = create(merge_request) - if merge_after.present? - merge_after_time = Time.zone.parse(merge_after).change(zone: current_user&.timezone) - merge_schedule = created_merge_request.merge_schedule || created_merge_request.build_merge_schedule - merge_schedule.merge_after = merge_after_time - merge_schedule.save - end + set_merge_schedule(created_merge_request, merge_after) created_merge_request end diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb index b991968d0816d9..a6f350daeae46a 100644 --- a/app/services/merge_requests/update_service.rb +++ b/app/services/merge_requests/update_service.rb @@ -15,12 +15,9 @@ def execute(merge_request) merge_request.title = merge_request.draft_title end - merge_after = params.delete(:merge_after) - if merge_after.present? || merge_request.merge_schedule.present? - merge_after_time = Time.zone.parse(merge_after).change(zone: current_user&.timezone) - merge_schedule = merge_request.merge_schedule || merge_request.build_merge_schedule - merge_schedule.merge_after = merge_after_time - merge_schedule.save + if params.key?(:merge_after) + merge_after = params.delete(:merge_after) + set_merge_schedule(merge_request, merge_after) end update_merge_request_with_specialized_service(merge_request) || general_fallback(merge_request) diff --git a/app/views/shared/issuable/form/_metadata.html.haml b/app/views/shared/issuable/form/_metadata.html.haml index 5b2e53fedd4a07..aad40fba839e6e 100644 --- a/app/views/shared/issuable/form/_metadata.html.haml +++ b/app/views/shared/issuable/form/_metadata.html.haml @@ -43,12 +43,18 @@ = render_if_exists "shared/issuable/form/merge_request_blocks", issuable: issuable, form: form - if issuable.is_a?(MergeRequest) - .form-group.row - = form.label :merge_after, _('Merge after'), class: 'col-12' - .col-12 - - timezone = current_user.timezone || Time.zone.now.time_zone.tzinfo.name - = form.text_field :merge_after, type: :'datetime-local', value: issuable.merge_schedule&.merge_after&.in_time_zone(timezone)&.strftime('%Y-%m-%dT%H:%M'), class: 'gl-form-input form-control' - %p.gl-text-gray-500= safe_format(s_('MergeRequests|This time is in timezone %{timezone}.'), timezone: timezone) + .row + = form.label :merge_after, s_('MergeRequests|Merge can start'), class: 'col-12' + - timezone_name = current_user.timezone || Time.zone.now.time_zone.tzinfo.name + - timezone_data = ActiveSupport::TimeZone[timezone_name] + - local_merge_after = issuable.merge_schedule&.merge_after&.in_time_zone(timezone_data)&.strftime('%Y-%m-%dT%H:%M') + .js-merge-request-schedule-input{ data: { + timezone_name: timezone_data.name, + timezone_utc_offset: timezone_data.utc_offset, + merge_after: local_merge_after, + param_key: issuable.class.model_name.param_key + } } + %p.gl-text-gray-500.col-12= s_('MergeRequests|Requires that merge checks pass.') - if has_due_date .col-lg-6 diff --git a/locale/gitlab.pot b/locale/gitlab.pot index ee0cf2f255bfe6..8f90f822118485 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -5070,6 +5070,9 @@ msgstr "" msgid "After it is removed, the fork relationship can only be restored by using the API. This project will no longer be able to receive or send merge requests to the upstream project or other forks." msgstr "" +msgid "After scheduled date" +msgstr "" + msgid "After the export is complete, download the data file from a notification email or from this page. You can then import the data file from the %{strong_text_start}Create new group%{strong_text_end} page of another GitLab instance." msgstr "" @@ -6678,6 +6681,9 @@ msgstr "" msgid "Any user who is added or requests access in excess of the user cap must be approved by an administrator" msgstr "" +msgid "Anytime" +msgstr "" + msgid "App ID" msgstr "" @@ -33841,9 +33847,6 @@ msgstr "" msgid "Merge Requests merged" msgstr "" -msgid "Merge after" -msgstr "" - msgid "Merge automatically (%{strategy})" msgstr "" @@ -34087,6 +34090,9 @@ msgstr "" msgid "MergeRequests|Mark as draft" msgstr "" +msgid "MergeRequests|Merge can start" +msgstr "" + msgid "MergeRequests|Merge request cherry-pick failed" msgstr "" @@ -34096,13 +34102,13 @@ msgstr "" msgid "MergeRequests|Reference copied" msgstr "" -msgid "MergeRequests|Squashing failed: Squash the commits locally, resolve any conflicts, then push the branch." +msgid "MergeRequests|Requires that merge checks pass." msgstr "" -msgid "MergeRequests|Squashing not allowed: This project doesn't allow you to squash commits when merging." +msgid "MergeRequests|Squashing failed: Squash the commits locally, resolve any conflicts, then push the branch." msgstr "" -msgid "MergeRequests|This time is in timezone %{timezone}." +msgid "MergeRequests|Squashing not allowed: This project doesn't allow you to squash commits when merging." msgstr "" msgid "MergeRequests|Thread stays resolved" diff --git a/spec/frontend/merge_requests/components/merge_schedule_input_spec.js b/spec/frontend/merge_requests/components/merge_schedule_input_spec.js new file mode 100644 index 00000000000000..19a3bad555f04e --- /dev/null +++ b/spec/frontend/merge_requests/components/merge_schedule_input_spec.js @@ -0,0 +1,36 @@ +import { shallowMount } from '@vue/test-utils'; +import { GlFormInput } from '@gitlab/ui'; +import MergeScheduleInput from '~/merge_requests/components/merge_schedule_input.vue'; + +let wrapper; + +function createComponent(propsData = {}) { + wrapper = shallowMount(MergeScheduleInput, { + propsData: { + timezoneName: 'Europe/Berlin', + timezoneUtcOffset: 3600, + ...propsData, + }, + }); +} + +const findInput = () => wrapper.findComponent(GlFormInput); +const findHiddenInput = () => wrapper.find('input[type="hidden"]'); + +describe('Merge schedule input component', () => { + it('Hides input if mergeAfter is undefined', () => { + createComponent({ mergeAfter: undefined }); + + expect(findInput().exists()).toBe(false); + expect(findHiddenInput().exists()).toBe(true); + expect(findHiddenInput().attributes('value')).toBe(undefined); + }); + + it('Shows input if mergeAfter is set', () => { + createComponent({ mergeAfter: '2024-10-27T20:40' }); + + expect(findInput().exists()).toBe(true); + expect(findHiddenInput().exists()).toBe(true); + expect(findHiddenInput().attributes('value')).toBe('2024-10-27T20:40'); + }); +}); diff --git a/spec/requests/projects/merge_requests_controller_spec.rb b/spec/requests/projects/merge_requests_controller_spec.rb index 08bc6ff60ce53f..53a95493dccab5 100644 --- a/spec/requests/projects/merge_requests_controller_spec.rb +++ b/spec/requests/projects/merge_requests_controller_spec.rb @@ -243,14 +243,6 @@ def create_pipeline end describe 'PUT #update' do - let(:update_params) do - { - merge_request: { - merge_after: '2024-09-03T21:18' - } - } - end - before do project.add_developer(user) user.timezone = 'Europe/Berlin' @@ -258,7 +250,7 @@ def create_pipeline end it 'applies correct timezone to merge_after' do - put project_merge_request_path(project, merge_request, update_params) + put project_merge_request_path(project, merge_request, merge_request: { merge_after: '2024-09-03T21:18' }) expect(response).to redirect_to(project_merge_request_path(project, merge_request)) @@ -266,5 +258,21 @@ def create_pipeline Time.zone.parse('2024-09-03T19:18').in_time_zone('Etc/UTC') ) end + + it 'resets merge_schedule if merge_after is not set' do + create(:merge_request_merge_schedule, merge_request: merge_request, merge_after: '2024-10-27T21:06') + + expect do + put project_merge_request_path(project, merge_request, merge_request: { merge_after: '' }) + end.to change { merge_request.reload.merge_schedule }.to(nil) + end + + it 'does not reset merge_schedule if merge_after is not sent' do + create(:merge_request_merge_schedule, merge_request: merge_request, merge_after: '2024-10-27T21:06') + + expect do + put project_merge_request_path(project, merge_request, merge_request: { title: 'Something' }) + end.not_to change { merge_request.reload.merge_schedule.merge_after } + end end end -- GitLab From f716e449f7b04ea85c15f3ae09851cc58eb18dae Mon Sep 17 00:00:00 2001 From: Niklas Date: Wed, 30 Oct 2024 16:49:18 +0000 Subject: [PATCH 5/8] Even more review feedback --- .../components/merge_schedule_input.vue | 2 +- .../components/checks/constants.js | 2 +- .../components/checks/merge_time.vue | 2 +- doc/user/project/merge_requests/auto_merge.md | 8 +++++--- locale/gitlab.pot | 12 ++++++------ .../components/merge_schedule_input_spec.js | 2 +- .../components/checks/merge_time_spec.js | 2 +- 7 files changed, 16 insertions(+), 14 deletions(-) diff --git a/app/assets/javascripts/merge_requests/components/merge_schedule_input.vue b/app/assets/javascripts/merge_requests/components/merge_schedule_input.vue index 3ba6886a4b38bf..9100b2599fd200 100644 --- a/app/assets/javascripts/merge_requests/components/merge_schedule_input.vue +++ b/app/assets/javascripts/merge_requests/components/merge_schedule_input.vue @@ -37,7 +37,7 @@ export default { }, computed: { actualMergeAfter() { - return this.selectedMode === AFTER ? this.localMergeAfter : null; + return this.selectedMode === AFTER ? this.localMergeAfter : ''; }, dropdownValues() { return [ diff --git a/app/assets/javascripts/vue_merge_request_widget/components/checks/constants.js b/app/assets/javascripts/vue_merge_request_widget/components/checks/constants.js index 1bbb38723a799b..b244ac45061d89 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/checks/constants.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/checks/constants.js @@ -39,7 +39,7 @@ export const FAILURE_REASONS = { locked_lfs_files: __('All LFS files must be unlocked.'), security_policy_evaluation: __('All security policies must be evaluated.'), security_policy_violations: __('All policy rules must be satisfied.'), - merge_time: __('May not be merged until after the specified time.'), + merge_time: __('Cannot merge until this date and time.'), }; export const ICON_NAMES = { diff --git a/app/assets/javascripts/vue_merge_request_widget/components/checks/merge_time.vue b/app/assets/javascripts/vue_merge_request_widget/components/checks/merge_time.vue index 35fa4b358a6759..7b201b0dfb9bf2 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/checks/merge_time.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/checks/merge_time.vue @@ -27,7 +27,7 @@ export default { return ICON_NAMES[this.check.status.toLowerCase()]; }, failureReason() { - return sprintf(s__('mrWidget|May not be merged until after %{mergeAfter}'), { + return sprintf(s__('mrWidget|Cannot merge until %{mergeAfter}'), { mergeAfter: dateFormatter.format(new Date(this.mr.mergeAfter)), }); }, diff --git a/doc/user/project/merge_requests/auto_merge.md b/doc/user/project/merge_requests/auto_merge.md index 5da78db1333e49..11eec7823c6a1f 100644 --- a/doc/user/project/merge_requests/auto_merge.md +++ b/doc/user/project/merge_requests/auto_merge.md @@ -175,11 +175,13 @@ To change this behavior: - Select **Skipped pipelines are considered successful**. 1. Select **Save**. -## Require merge time to be after a configured date +## Prevent merge before a specific date > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/14380) in GitLab 17.6. -To prevent a merge request from merging before a specific date and time, set a **Merge after** date. +If your merge request should not merge before a specific date and time, set a **Merge after** date. +This value sets when the merge (or merge train) can start. The exact time of merge can vary, +however, depending on the satisfaction of other merge checks or the length of your merge train. Prerequisites: @@ -192,7 +194,7 @@ To do this: 1. Select the merge request to edit. 1. Select **Edit**. 1. Find the **Merge after** input and select a date and time. This time uses the - timezone [configured in your profile](../../profile/index.md#set-your-time-zone). + time zone [configured in your profile](../../profile/index.md#set-your-time-zone). 1. Select **Save changes**. ## Troubleshooting diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 8f90f822118485..581750a5f7df74 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -10920,6 +10920,9 @@ msgstr "" msgid "Cannot merge" msgstr "" +msgid "Cannot merge until this date and time." +msgstr "" + msgid "Cannot modify %{profile_name} referenced in scan" msgstr "" @@ -33314,9 +33317,6 @@ msgstr "" msgid "May" msgstr "" -msgid "May not be merged until after the specified time." -msgstr "" - msgid "Mean time to merge" msgstr "" @@ -65570,6 +65570,9 @@ msgstr "" msgid "mrWidget|Cancel auto-merge" msgstr "" +msgid "mrWidget|Cannot merge until %{mergeAfter}" +msgstr "" + msgid "mrWidget|Checking if merge request can be merged…" msgstr "" @@ -65620,9 +65623,6 @@ msgstr "" msgid "mrWidget|Loading deployment statistics" msgstr "" -msgid "mrWidget|May not be merged until after %{mergeAfter}" -msgstr "" - msgid "mrWidget|Members who can merge are allowed to add commits." msgstr "" diff --git a/spec/frontend/merge_requests/components/merge_schedule_input_spec.js b/spec/frontend/merge_requests/components/merge_schedule_input_spec.js index 19a3bad555f04e..06b104fe866815 100644 --- a/spec/frontend/merge_requests/components/merge_schedule_input_spec.js +++ b/spec/frontend/merge_requests/components/merge_schedule_input_spec.js @@ -23,7 +23,7 @@ describe('Merge schedule input component', () => { expect(findInput().exists()).toBe(false); expect(findHiddenInput().exists()).toBe(true); - expect(findHiddenInput().attributes('value')).toBe(undefined); + expect([undefined, '']).toContain(findHiddenInput().attributes('value')); }); it('Shows input if mergeAfter is set', () => { diff --git a/spec/frontend/vue_merge_request_widget/components/checks/merge_time_spec.js b/spec/frontend/vue_merge_request_widget/components/checks/merge_time_spec.js index 144d8e2fe1742d..b7e31e8040a1a5 100644 --- a/spec/frontend/vue_merge_request_widget/components/checks/merge_time_spec.js +++ b/spec/frontend/vue_merge_request_widget/components/checks/merge_time_spec.js @@ -17,7 +17,7 @@ describe('Merge request merge checks merge time component', () => { mr: { mergeAfter: '2024-10-17T18:23:00Z' }, }); - expect(wrapper.text()).toBe('Must be merged after Oct 17, 2024, 6:23 PM'); + expect(wrapper.text()).toBe('Cannot merge until Oct 17, 2024, 6:23 PM'); }); it.each` -- GitLab From ac18419c99533e94b15fe825abbd3f7d83b4e32d Mon Sep 17 00:00:00 2001 From: Niklas Date: Sun, 3 Nov 2024 19:33:13 +0000 Subject: [PATCH 6/8] Move timezone conversion to frontend --- .../components/merge_schedule_input.vue | 22 ++++++------------- app/services/merge_requests/base_service.rb | 5 ++--- .../shared/issuable/form/_metadata.html.haml | 7 +----- doc/user/project/merge_requests/auto_merge.md | 3 +-- .../components/merge_schedule_input_spec.js | 10 ++------- .../projects/merge_requests/creations_spec.rb | 6 ++--- .../merge_requests_controller_spec.rb | 3 +-- 7 files changed, 17 insertions(+), 39 deletions(-) diff --git a/app/assets/javascripts/merge_requests/components/merge_schedule_input.vue b/app/assets/javascripts/merge_requests/components/merge_schedule_input.vue index 9100b2599fd200..b75e7724da71fb 100644 --- a/app/assets/javascripts/merge_requests/components/merge_schedule_input.vue +++ b/app/assets/javascripts/merge_requests/components/merge_schedule_input.vue @@ -1,7 +1,7 @@ @@ -67,7 +60,6 @@ export default {
- {{ formattedTimezone }} diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb index 2c367ab77b2021..4957e9ff1b85ef 100644 --- a/app/services/merge_requests/base_service.rb +++ b/app/services/merge_requests/base_service.rb @@ -307,10 +307,9 @@ def abort_auto_merge_with_todo(merge_request, reason) def set_merge_schedule(merge_request, merge_after) if merge_after.present? - merge_after_time = Time.zone.parse(merge_after).change(zone: current_user&.timezone) merge_schedule = merge_request.merge_schedule || merge_request.build_merge_schedule - merge_schedule.merge_after = merge_after_time - merge_schedule.save + merge_schedule.merge_after = merge_after + merge_request.merge_schedule = merge_schedule elsif merge_request.merge_schedule.present? merge_request.merge_schedule.destroy end diff --git a/app/views/shared/issuable/form/_metadata.html.haml b/app/views/shared/issuable/form/_metadata.html.haml index aad40fba839e6e..aeb819588688ac 100644 --- a/app/views/shared/issuable/form/_metadata.html.haml +++ b/app/views/shared/issuable/form/_metadata.html.haml @@ -45,13 +45,8 @@ - if issuable.is_a?(MergeRequest) .row = form.label :merge_after, s_('MergeRequests|Merge can start'), class: 'col-12' - - timezone_name = current_user.timezone || Time.zone.now.time_zone.tzinfo.name - - timezone_data = ActiveSupport::TimeZone[timezone_name] - - local_merge_after = issuable.merge_schedule&.merge_after&.in_time_zone(timezone_data)&.strftime('%Y-%m-%dT%H:%M') .js-merge-request-schedule-input{ data: { - timezone_name: timezone_data.name, - timezone_utc_offset: timezone_data.utc_offset, - merge_after: local_merge_after, + merge_after: issuable.merge_schedule&.merge_after&.strftime('%Y-%m-%dT%H:%MZ'), param_key: issuable.class.model_name.param_key } } %p.gl-text-gray-500.col-12= s_('MergeRequests|Requires that merge checks pass.') diff --git a/doc/user/project/merge_requests/auto_merge.md b/doc/user/project/merge_requests/auto_merge.md index 11eec7823c6a1f..3067db032a2d67 100644 --- a/doc/user/project/merge_requests/auto_merge.md +++ b/doc/user/project/merge_requests/auto_merge.md @@ -193,8 +193,7 @@ To do this: 1. Select **Code > Merge requests**. 1. Select the merge request to edit. 1. Select **Edit**. -1. Find the **Merge after** input and select a date and time. This time uses the - time zone [configured in your profile](../../profile/index.md#set-your-time-zone). +1. Find the **Merge after** input and select a date and time. 1. Select **Save changes**. ## Troubleshooting diff --git a/spec/frontend/merge_requests/components/merge_schedule_input_spec.js b/spec/frontend/merge_requests/components/merge_schedule_input_spec.js index 06b104fe866815..9f8894986c3563 100644 --- a/spec/frontend/merge_requests/components/merge_schedule_input_spec.js +++ b/spec/frontend/merge_requests/components/merge_schedule_input_spec.js @@ -5,13 +5,7 @@ import MergeScheduleInput from '~/merge_requests/components/merge_schedule_input let wrapper; function createComponent(propsData = {}) { - wrapper = shallowMount(MergeScheduleInput, { - propsData: { - timezoneName: 'Europe/Berlin', - timezoneUtcOffset: 3600, - ...propsData, - }, - }); + wrapper = shallowMount(MergeScheduleInput, { propsData }); } const findInput = () => wrapper.findComponent(GlFormInput); @@ -31,6 +25,6 @@ describe('Merge schedule input component', () => { expect(findInput().exists()).toBe(true); expect(findHiddenInput().exists()).toBe(true); - expect(findHiddenInput().attributes('value')).toBe('2024-10-27T20:40'); + expect(findHiddenInput().attributes('value')).toBe('2024-10-27T20:40:00.000Z'); }); }); diff --git a/spec/requests/projects/merge_requests/creations_spec.rb b/spec/requests/projects/merge_requests/creations_spec.rb index ab77bd7f1e8fb5..93a5e6cf5bc790 100644 --- a/spec/requests/projects/merge_requests/creations_spec.rb +++ b/spec/requests/projects/merge_requests/creations_spec.rb @@ -81,7 +81,7 @@ def get_new(params = get_params) describe 'POST /:namespace/:project/merge_requests' do let_it_be(:group) { create(:group) } let_it_be(:project) { create(:project, :repository, group: group) } - let_it_be(:user) { create(:user, timezone: 'Europe/Berlin') } + let_it_be(:user) { create(:user) } let(:create_params) do { @@ -104,14 +104,14 @@ def get_new(params = get_params) login_as(user) end - it 'applies correct timezone to merge_after' do + it 'creates correct merge schedule' do post namespace_project_merge_requests_path(create_params) expect(response).to redirect_to(project_merge_request_path(project, MergeRequest.last)) merge_request = MergeRequest.last expect(merge_request.merge_schedule.merge_after).to eq( - Time.zone.parse('2024-09-03T19:18').in_time_zone('Etc/UTC') + Time.zone.parse('2024-09-03T21:18') ) end end diff --git a/spec/requests/projects/merge_requests_controller_spec.rb b/spec/requests/projects/merge_requests_controller_spec.rb index 53a95493dccab5..1a7fcdb5d7dc84 100644 --- a/spec/requests/projects/merge_requests_controller_spec.rb +++ b/spec/requests/projects/merge_requests_controller_spec.rb @@ -245,7 +245,6 @@ def create_pipeline describe 'PUT #update' do before do project.add_developer(user) - user.timezone = 'Europe/Berlin' login_as(user) end @@ -255,7 +254,7 @@ def create_pipeline expect(response).to redirect_to(project_merge_request_path(project, merge_request)) expect(merge_request.reload.merge_schedule.merge_after).to eq( - Time.zone.parse('2024-09-03T19:18').in_time_zone('Etc/UTC') + Time.zone.parse('2024-09-03T21:18') ) end -- GitLab From 035e997bafed5ea97d32988843986718ee868d3c Mon Sep 17 00:00:00 2001 From: Niklas Date: Thu, 7 Nov 2024 18:34:47 +0000 Subject: [PATCH 7/8] Extract method to service and some frontend changes --- .../components/merge_schedule_input.vue | 11 +++-- .../components/checks/constants.js | 4 +- app/services/merge_requests/base_service.rb | 10 ----- app/services/merge_requests/create_service.rb | 2 +- .../update_merge_schedule_service.rb | 24 +++++++++++ app/services/merge_requests/update_service.rb | 2 +- .../update_merge_schedule_service_spec.rb | 42 +++++++++++++++++++ 7 files changed, 78 insertions(+), 17 deletions(-) create mode 100644 app/services/merge_requests/update_merge_schedule_service.rb create mode 100644 spec/services/merge_requests/update_merge_schedule_service_spec.rb diff --git a/app/assets/javascripts/merge_requests/components/merge_schedule_input.vue b/app/assets/javascripts/merge_requests/components/merge_schedule_input.vue index b75e7724da71fb..94f9c45b325788 100644 --- a/app/assets/javascripts/merge_requests/components/merge_schedule_input.vue +++ b/app/assets/javascripts/merge_requests/components/merge_schedule_input.vue @@ -14,7 +14,8 @@ export default { props: { mergeAfter: { type: String, - required: true, + required: false, + default: undefined, }, paramKey: { type: String, @@ -47,7 +48,7 @@ export default { }, ]; }, - showAfterInput() { + shouldDisplayAfterInput() { return this.selectedMode === AFTER; }, }, @@ -58,7 +59,11 @@ export default {
- +
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/checks/constants.js b/app/assets/javascripts/vue_merge_request_widget/components/checks/constants.js index b244ac45061d89..b6dbbea1add022 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/checks/constants.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/checks/constants.js @@ -42,9 +42,9 @@ export const FAILURE_REASONS = { merge_time: __('Cannot merge until this date and time.'), }; -export const ICON_NAMES = { +export const ICON_NAMES = Object.freeze({ failed: 'failed', inactive: 'neutral', success: 'success', warning: 'warning', -}; +}); diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb index 4957e9ff1b85ef..d6398e1946c09e 100644 --- a/app/services/merge_requests/base_service.rb +++ b/app/services/merge_requests/base_service.rb @@ -304,16 +304,6 @@ def abort_auto_merge_with_todo(merge_request, reason) todo_service.merge_request_became_unmergeable(merge_request) end - - def set_merge_schedule(merge_request, merge_after) - if merge_after.present? - merge_schedule = merge_request.merge_schedule || merge_request.build_merge_schedule - merge_schedule.merge_after = merge_after - merge_request.merge_schedule = merge_schedule - elsif merge_request.merge_schedule.present? - merge_request.merge_schedule.destroy - end - end end end diff --git a/app/services/merge_requests/create_service.rb b/app/services/merge_requests/create_service.rb index 6cd6b81e59f3d7..7d6fc42814b458 100644 --- a/app/services/merge_requests/create_service.rb +++ b/app/services/merge_requests/create_service.rb @@ -15,7 +15,7 @@ def execute created_merge_request = create(merge_request) - set_merge_schedule(created_merge_request, merge_after) + UpdateMergeScheduleService.new(created_merge_request, merge_after: merge_after).execute created_merge_request end diff --git a/app/services/merge_requests/update_merge_schedule_service.rb b/app/services/merge_requests/update_merge_schedule_service.rb new file mode 100644 index 00000000000000..ea4566d313f397 --- /dev/null +++ b/app/services/merge_requests/update_merge_schedule_service.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module MergeRequests + class UpdateMergeScheduleService + def initialize(merge_request, merge_after:) + @merge_request = merge_request + @merge_after = merge_after + end + + def execute + if merge_after.present? + merge_schedule = merge_request.merge_schedule || merge_request.build_merge_schedule + merge_schedule.merge_after = merge_after + merge_request.merge_schedule = merge_schedule + elsif merge_request.merge_schedule.present? + merge_request.merge_schedule.destroy! + end + end + + private + + attr_reader :merge_request, :merge_after + end +end diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb index a6f350daeae46a..ee23ea29e2736a 100644 --- a/app/services/merge_requests/update_service.rb +++ b/app/services/merge_requests/update_service.rb @@ -17,7 +17,7 @@ def execute(merge_request) if params.key?(:merge_after) merge_after = params.delete(:merge_after) - set_merge_schedule(merge_request, merge_after) + UpdateMergeScheduleService.new(merge_request, merge_after: merge_after).execute end update_merge_request_with_specialized_service(merge_request) || general_fallback(merge_request) diff --git a/spec/services/merge_requests/update_merge_schedule_service_spec.rb b/spec/services/merge_requests/update_merge_schedule_service_spec.rb new file mode 100644 index 00000000000000..629a7b226e648a --- /dev/null +++ b/spec/services/merge_requests/update_merge_schedule_service_spec.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe MergeRequests::UpdateMergeScheduleService, feature_category: :code_review_workflow do + let(:merge_request) { create(:merge_request) } + + let(:service) { described_class.new(merge_request, merge_after: merge_after) } + + subject(:execute) { service.execute } + + describe '#execute' do + context 'when passing a merge_after date' do + let(:merge_after) { '2024-11-07T19:17+0200' } + + specify do + expect { execute }.to change { merge_request.reload.merge_schedule&.merge_after } + .to(Time.zone.parse(merge_after).in_time_zone('Etc/UTC')) + end + + it { expect { execute }.to change { MergeRequests::MergeSchedule.count }.by(1) } + end + + context 'when passing nil for merge_after' do + let(:merge_after) { nil } + + context 'when merge_schedule exists' do + before do + merge_request.create_merge_schedule(merge_after: '2024-11-07T19:17+0200') + end + + it { expect { execute }.to change { merge_request.reload.merge_schedule&.merge_after }.to(nil) } + it { expect { execute }.to change { MergeRequests::MergeSchedule.count }.by(-1) } + end + + context 'when merge_schedule does not exist' do + it { expect { execute }.not_to change { merge_request.reload.merge_schedule } } + it { expect { execute }.not_to change { MergeRequests::MergeSchedule.count } } + end + end + end +end -- GitLab From 97ae937f5b6994dc0d86af736385d47909b6396e Mon Sep 17 00:00:00 2001 From: Niklas van Schrick Date: Thu, 7 Nov 2024 20:30:40 +0000 Subject: [PATCH 8/8] Remove props that got removed --- .../projects/merge_requests/mount_merge_schedule_input.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/assets/javascripts/pages/projects/merge_requests/mount_merge_schedule_input.js b/app/assets/javascripts/pages/projects/merge_requests/mount_merge_schedule_input.js index acc7758e2cdc09..93b2a62488a73b 100644 --- a/app/assets/javascripts/pages/projects/merge_requests/mount_merge_schedule_input.js +++ b/app/assets/javascripts/pages/projects/merge_requests/mount_merge_schedule_input.js @@ -6,15 +6,13 @@ export default () => { if (!el) return false; - const { timezoneName, timezoneUtcOffset, mergeAfter, paramKey } = el.dataset; + const { mergeAfter, paramKey } = el.dataset; return new Vue({ el, render(h) { return h(MergeScheduleInput, { props: { - timezoneName, - timezoneUtcOffset, mergeAfter, paramKey, }, -- GitLab