Commit b005b8e8 authored by Piotr Stankowski's avatar Piotr Stankowski Committed by Paul Slaughter

MR widget: update merge commit message when default changed

Changelog: added
parent 65bb9b20
......@@ -82,6 +82,13 @@ export default {
};
this.loading = false;
if (!this.commitMessageIsTouched) {
this.commitMessage = this.state.defaultMergeCommitMessage;
}
if (!this.squashCommitMessageIsTouched) {
this.squashCommitMessage = this.state.defaultSquashCommitMessage;
}
if (this.state.mergeTrainsCount !== null && this.state.mergeTrainsCount !== undefined) {
this.initPolling();
}
......@@ -133,9 +140,11 @@ export default {
isMakingRequest: false,
isMergingImmediately: false,
commitMessage: this.mr.commitMessage,
commitMessageIsTouched: false,
squashBeforeMerge: this.mr.squashIsSelected,
isSquashReadOnly: this.mr.squashIsReadonly,
squashCommitMessage: this.mr.squashCommitMessage,
squashCommitMessageIsTouched: false,
isPipelineFailedModalVisibleMergeTrain: false,
isPipelineFailedModalVisibleNormalMerge: false,
editCommitMessage: false,
......@@ -465,6 +474,14 @@ export default {
});
});
},
setCommitMessage(val) {
this.commitMessage = val;
this.commitMessageIsTouched = true;
},
setSquashCommitMessage(val) {
this.squashCommitMessage = val;
this.squashCommitMessageIsTouched = true;
},
},
i18n: {
mergeCommitTemplateHintText: s__(
......@@ -630,21 +647,23 @@ export default {
>
<commit-edit
v-if="shouldShowSquashEdit"
v-model="squashCommitMessage"
:value="squashCommitMessage"
:label="__('Squash commit message')"
input-id="squash-message-edit"
class="gl-m-0! gl-p-0!"
@input="setSquashCommitMessage"
>
<template #header>
<commit-message-dropdown v-model="squashCommitMessage" :commits="commits" />
<commit-message-dropdown :commits="commits" @input="setSquashCommitMessage" />
</template>
</commit-edit>
<commit-edit
v-if="shouldShowMergeEdit"
v-model="commitMessage"
:value="commitMessage"
:label="__('Merge commit message')"
input-id="merge-message-edit"
class="gl-m-0! gl-p-0!"
@input="setCommitMessage"
/>
<li class="gl-m-0! gl-p-0!">
<p class="form-text text-muted">
......@@ -748,20 +767,22 @@ export default {
<ul class="border-top content-list commits-list flex-list">
<commit-edit
v-if="shouldShowSquashEdit"
v-model="squashCommitMessage"
:value="squashCommitMessage"
:label="__('Squash commit message')"
input-id="squash-message-edit"
squash
@input="setSquashCommitMessage"
>
<template #header>
<commit-message-dropdown v-model="squashCommitMessage" :commits="commits" />
<commit-message-dropdown :commits="commits" @input="setSquashCommitMessage" />
</template>
</commit-edit>
<commit-edit
v-if="shouldShowMergeEdit"
v-model="commitMessage"
:value="commitMessage"
:label="__('Merge commit message')"
input-id="merge-message-edit"
@input="setCommitMessage"
/>
<li>
<p class="form-text text-muted">
......
fragment ReadyToMerge on Project {
__typename
id
onlyAllowMergeIfPipelineSucceeds
mergeRequestsFfOnlyEnabled
squashReadOnly
mergeRequest(iid: $iid) {
__typename
id
autoMergeEnabled
shouldRemoveSourceBranch
......
......@@ -84,7 +84,7 @@ Commit message templates support these variables:
| `%{first_commit}` | Full message of the first commit in merge request diff. | `Update README.md` |
| `%{first_multiline_commit}` | Full message of the first commit that's not a merge commit and has more than one line in message body. Merge request title if all commits aren't multiline. | `Update README.md`<br><br>`Improved project description in readme file.` |
| `%{url}` | Full URL to the merge request. | `https://gitlab.com/gitlab-org/gitlab/-/merge_requests/1` |
| `%{approved_by}` | Line-separated list of the merge request approvers. This value is not updated until the first page refresh after an approval. | `Approved-by: Sidney Jones <sjones@example.com>` <br> `Approved-by: Zhang Wei <zwei@example.com>` |
| `%{approved_by}` | Line-separated list of the merge request approvers. | `Approved-by: Sidney Jones <sjones@example.com>` <br> `Approved-by: Zhang Wei <zwei@example.com>` |
| `%{merged_by}` | User who merged the merge request. | `Alex Garcia <agarcia@example.com>` |
| `%{co_authored_by}` | Names and emails of commit authors in a `Co-authored-by` Git commit trailer format. Limited to authors of 100 most recent commits in merge request. | `Co-authored-by: Zane Doe <zdoe@example.com>` <br> `Co-authored-by: Blake Smith <bsmith@example.com>` |
| `%{all_commits}` | Messages from all commits in the merge request. Limited to 100 most recent commits. Skips commit bodies exceeding 100KiB and merge commit messages. | `* Feature introduced` <br><br> `This commit implements feature` <br> `Changelog:added` <br><br> `* Bug fixed` <br><br> `* Documentation improved` <br><br>`This commit introduced better docs.`|
......@@ -92,6 +92,10 @@ Commit message templates support these variables:
Any line containing only an empty variable is removed. If the line to be removed is both
preceded and followed by an empty line, the preceding empty line is also removed.
After you edit a commit message on an open merge request, GitLab will
not automatically update the commit message again.
To restore the commit message to the project template, reload the page.
## Related topics
- [Squash and merge](squash_and_merge.md).
......@@ -130,6 +130,25 @@ RSpec.describe Projects::MergeRequestsController, '(JavaScript fixtures)', type:
expect(response).to be_successful
end
describe GraphQL::Query, type: :request do
include ApiHelpers
include GraphqlHelpers
context 'merge request in state readyToMerge query' do
base_input_path = 'vue_merge_request_widget/queries/states/'
base_output_path = 'graphql/merge_requests/states/'
query_name = 'ready_to_merge.query.graphql'
it "#{base_output_path}#{query_name}.json" do
query = get_graphql_query_as_string("#{base_input_path}#{query_name}", ee: true)
post_graphql(query, current_user: user, variables: { projectPath: project.full_path, iid: merge_request.iid.to_s })
expect_graphql_errors_to_be_empty
end
end
end
private
def render_discussions_json(merge_request)
......
import { shallowMount } from '@vue/test-utils';
import { createLocalVue, shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import { GlSprintf } from '@gitlab/ui';
import VueApollo from 'vue-apollo';
import produce from 'immer';
import readyToMergeResponse from 'test_fixtures/graphql/merge_requests/states/ready_to_merge.query.graphql.json';
import waitForPromises from 'helpers/wait_for_promises';
import createMockApollo from 'helpers/mock_apollo_helper';
import readyToMergeQuery from 'ee_else_ce/vue_merge_request_widget/queries/states/ready_to_merge.query.graphql';
import simplePoll from '~/lib/utils/simple_poll';
import CommitEdit from '~/vue_merge_request_widget/components/states/commit_edit.vue';
import CommitMessageDropdown from '~/vue_merge_request_widget/components/states/commit_message_dropdown.vue';
......@@ -19,9 +24,11 @@ jest.mock('~/commons/nav/user_merge_requests', () => ({
refreshUserMergeRequestCounts: jest.fn(),
}));
const commitMessage = 'This is the commit message';
const squashCommitMessage = 'This is the squash commit message';
const commitMessageWithDescription = 'This is the commit message description';
const commitMessage = readyToMergeResponse.data.project.mergeRequest.defaultMergeCommitMessage;
const squashCommitMessage =
readyToMergeResponse.data.project.mergeRequest.defaultSquashCommitMessage;
const commitMessageWithDescription =
readyToMergeResponse.data.project.mergeRequest.defaultMergeCommitMessageWithDescription;
const createTestMr = (customConfig) => {
const mr = {
isPipelineActive: false,
......@@ -42,6 +49,8 @@ const createTestMr = (customConfig) => {
commitMessage,
squashCommitMessage,
commitMessageWithDescription,
defaultMergeCommitMessage: commitMessage,
defaultSquashCommitMessage: squashCommitMessage,
shouldRemoveSourceBranch: true,
canRemoveSourceBranch: false,
targetBranch: 'main',
......@@ -61,15 +70,25 @@ const createTestService = () => ({
merge: jest.fn(),
poll: jest.fn().mockResolvedValue(),
});
const localVue = createLocalVue();
localVue.use(VueApollo);
let wrapper;
let readyToMergeResponseSpy;
const findMergeButton = () => wrapper.find('[data-testid="merge-button"]');
const findPipelineFailedConfirmModal = () =>
wrapper.findComponent(MergeFailedPipelineConfirmationDialog);
const createReadyToMergeResponse = (customMr) => {
return produce(readyToMergeResponse, (draft) => {
Object.assign(draft.data.project.mergeRequest, customMr);
});
};
const createComponent = (customConfig = {}, mergeRequestWidgetGraphql = false) => {
wrapper = shallowMount(ReadyToMerge, {
localVue,
propsData: {
mr: createTestMr(customConfig),
service: createTestService(),
......@@ -82,10 +101,29 @@ const createComponent = (customConfig = {}, mergeRequestWidgetGraphql = false) =
stubs: {
CommitEdit,
},
apolloProvider: createMockApollo([[readyToMergeQuery, readyToMergeResponseSpy]]),
});
};
const findCheckboxElement = () => wrapper.find(SquashBeforeMerge);
const findCommitsHeaderElement = () => wrapper.find(CommitsHeader);
const findCommitEditElements = () => wrapper.findAll(CommitEdit);
const findCommitDropdownElement = () => wrapper.find(CommitMessageDropdown);
const findFirstCommitEditLabel = () => findCommitEditElements().at(0).props('label');
const findTipLink = () => wrapper.find(GlSprintf);
const findCommitEditWithInputId = (inputId) =>
findCommitEditElements().wrappers.find((x) => x.props('inputId') === inputId);
const findMergeCommitMessage = () => findCommitEditWithInputId('merge-message-edit').props('value');
const findSquashCommitMessage = () =>
findCommitEditWithInputId('squash-message-edit').props('value');
const triggerApprovalUpdated = () => eventHub.$emit('ApprovalUpdated');
describe('ReadyToMerge', () => {
beforeEach(() => {
readyToMergeResponseSpy = jest.fn().mockResolvedValueOnce(readyToMergeResponse);
});
afterEach(() => {
wrapper.destroy();
});
......@@ -447,13 +485,6 @@ describe('ReadyToMerge', () => {
});
describe('render children components', () => {
const findCheckboxElement = () => wrapper.find(SquashBeforeMerge);
const findCommitsHeaderElement = () => wrapper.find(CommitsHeader);
const findCommitEditElements = () => wrapper.findAll(CommitEdit);
const findCommitDropdownElement = () => wrapper.find(CommitMessageDropdown);
const findFirstCommitEditLabel = () => findCommitEditElements().at(0).props('label');
const findTipLink = () => wrapper.find(GlSprintf);
describe('squash checkbox', () => {
it('should be rendered when squash before merge is enabled and there is more than 1 commit', () => {
createComponent({
......@@ -772,4 +803,65 @@ describe('ReadyToMerge', () => {
expect(findPipelineFailedConfirmModal().props()).toEqual({ visible: true });
});
});
describe('updating graphql data triggers commit message update when default changed', () => {
const UPDATED_MERGE_COMMIT_MESSAGE = 'New merge message from BE';
const UPDATED_SQUASH_COMMIT_MESSAGE = 'New squash message from BE';
const USER_COMMIT_MESSAGE = 'Merge message provided manually by user';
const createDefaultGqlComponent = () =>
createComponent({ mr: { commitsCount: 2, enableSquashBeforeMerge: true } }, true);
beforeEach(() => {
readyToMergeResponseSpy = jest
.fn()
.mockResolvedValueOnce(createReadyToMergeResponse({ squash: true, squashOnMerge: true }))
.mockResolvedValue(
createReadyToMergeResponse({
squash: true,
squashOnMerge: true,
defaultMergeCommitMessage: UPDATED_MERGE_COMMIT_MESSAGE,
defaultSquashCommitMessage: UPDATED_SQUASH_COMMIT_MESSAGE,
}),
);
});
describe.each`
desc | finderFn | initialValue | updatedValue | inputId
${'merge commit message'} | ${findMergeCommitMessage} | ${commitMessage} | ${UPDATED_MERGE_COMMIT_MESSAGE} | ${'#merge-message-edit'}
${'squash commit message'} | ${findSquashCommitMessage} | ${squashCommitMessage} | ${UPDATED_SQUASH_COMMIT_MESSAGE} | ${'#squash-message-edit'}
`('with $desc', ({ finderFn, initialValue, updatedValue, inputId }) => {
it('should have initial value', async () => {
createDefaultGqlComponent();
await waitForPromises();
expect(finderFn()).toBe(initialValue);
});
it('should have updated value after graphql refetch', async () => {
createDefaultGqlComponent();
await waitForPromises();
triggerApprovalUpdated();
await waitForPromises();
expect(finderFn()).toBe(updatedValue);
});
it('should not update if user has touched', async () => {
createDefaultGqlComponent();
await waitForPromises();
const input = wrapper.find(inputId);
input.element.value = USER_COMMIT_MESSAGE;
input.trigger('input');
triggerApprovalUpdated();
await waitForPromises();
expect(finderFn()).toBe(USER_COMMIT_MESSAGE);
});
});
});
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment