Commit 96453ede authored by Jose Ivan Vargas's avatar Jose Ivan Vargas

Merge branch 'pb-add-ff-for-rebase-without-ci-feat' into 'master'

Add feature flag for rebase without CI

See merge request gitlab-org/gitlab!78194
parents 838c7261 1859ebfc
<script> <script>
import { GlSkeletonLoader } from '@gitlab/ui'; import { GlButton, GlSkeletonLoader } from '@gitlab/ui';
import createFlash from '~/flash'; import createFlash from '~/flash';
import { __ } from '~/locale'; import { __ } from '~/locale';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
...@@ -29,6 +29,7 @@ export default { ...@@ -29,6 +29,7 @@ export default {
statusIcon, statusIcon,
GlSkeletonLoader, GlSkeletonLoader,
ActionsButton, ActionsButton,
GlButton,
}, },
mixins: [glFeatureFlagMixin(), mergeRequestQueryVariablesMixin], mixins: [glFeatureFlagMixin(), mergeRequestQueryVariablesMixin],
props: { props: {
...@@ -53,6 +54,9 @@ export default { ...@@ -53,6 +54,9 @@ export default {
isLoading() { isLoading() {
return this.glFeatures.mergeRequestWidgetGraphql && this.$apollo.queries.state.loading; return this.glFeatures.mergeRequestWidgetGraphql && this.$apollo.queries.state.loading;
}, },
showRebaseWithoutCi() {
return this.glFeatures?.rebaseWithoutCiUi;
},
rebaseInProgress() { rebaseInProgress() {
if (this.glFeatures.mergeRequestWidgetGraphql) { if (this.glFeatures.mergeRequestWidgetGraphql) {
return this.state.rebaseInProgress; return this.state.rebaseInProgress;
...@@ -196,8 +200,18 @@ export default { ...@@ -196,8 +200,18 @@ export default {
v-if="!rebaseInProgress && canPushToSourceBranch && !isMakingRequest" v-if="!rebaseInProgress && canPushToSourceBranch && !isMakingRequest"
class="accept-merge-holder clearfix js-toggle-container accept-action media space-children" class="accept-merge-holder clearfix js-toggle-container accept-action media space-children"
> >
<gl-button
v-if="!glFeatures.restructuredMrWidget && !showRebaseWithoutCi"
:loading="isMakingRequest"
variant="confirm"
data-qa-selector="mr_rebase_button"
data-testid="standard-rebase-button"
@click="rebase"
>
{{ __('Rebase') }}
</gl-button>
<actions-button <actions-button
v-if="!glFeatures.restructuredMrWidget" v-if="!glFeatures.restructuredMrWidget && showRebaseWithoutCi"
:actions="actions" :actions="actions"
:selected-key="selectedRebaseAction" :selected-key="selectedRebaseAction"
variant="confirm" variant="confirm"
......
...@@ -43,6 +43,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo ...@@ -43,6 +43,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
push_frontend_feature_flag(:mr_changes_fluid_layout, project, default_enabled: :yaml) push_frontend_feature_flag(:mr_changes_fluid_layout, project, default_enabled: :yaml)
push_frontend_feature_flag(:mr_attention_requests, project, default_enabled: :yaml) push_frontend_feature_flag(:mr_attention_requests, project, default_enabled: :yaml)
push_frontend_feature_flag(:refactor_mr_widgets_extensions, @project, default_enabled: :yaml) push_frontend_feature_flag(:refactor_mr_widgets_extensions, @project, default_enabled: :yaml)
push_frontend_feature_flag(:rebase_without_ci_ui, @project, default_enabled: :yaml)
# Usage data feature flags # Usage data feature flags
push_frontend_feature_flag(:users_expanding_widgets_usage_data, @project, default_enabled: :yaml) push_frontend_feature_flag(:users_expanding_widgets_usage_data, @project, default_enabled: :yaml)
push_frontend_feature_flag(:diff_settings_usage_data, default_enabled: :yaml) push_frontend_feature_flag(:diff_settings_usage_data, default_enabled: :yaml)
......
---
name: rebase_without_ci_ui
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/78194
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/350262
milestone: '14.7'
type: development
group: group::pipeline execution
default_enabled: false
...@@ -10,7 +10,7 @@ import { ...@@ -10,7 +10,7 @@ import {
let wrapper; let wrapper;
function createWrapper(propsData, mergeRequestWidgetGraphql) { function createWrapper(propsData, mergeRequestWidgetGraphql, rebaseWithoutCiUi) {
wrapper = shallowMount(WidgetRebase, { wrapper = shallowMount(WidgetRebase, {
propsData, propsData,
data() { data() {
...@@ -24,7 +24,7 @@ function createWrapper(propsData, mergeRequestWidgetGraphql) { ...@@ -24,7 +24,7 @@ function createWrapper(propsData, mergeRequestWidgetGraphql) {
}, },
}; };
}, },
provide: { glFeatures: { mergeRequestWidgetGraphql } }, provide: { glFeatures: { mergeRequestWidgetGraphql, rebaseWithoutCiUi } },
mocks: { mocks: {
$apollo: { $apollo: {
queries: { queries: {
...@@ -38,7 +38,8 @@ function createWrapper(propsData, mergeRequestWidgetGraphql) { ...@@ -38,7 +38,8 @@ function createWrapper(propsData, mergeRequestWidgetGraphql) {
describe('Merge request widget rebase component', () => { describe('Merge request widget rebase component', () => {
const findRebaseMessage = () => wrapper.find('[data-testid="rebase-message"]'); const findRebaseMessage = () => wrapper.find('[data-testid="rebase-message"]');
const findRebaseMessageText = () => findRebaseMessage().text(); const findRebaseMessageText = () => findRebaseMessage().text();
const findRebaseButton = () => wrapper.find(ActionsButton); const findRebaseButtonActions = () => wrapper.find(ActionsButton);
const findStandardRebaseButton = () => wrapper.find('[data-testid="standard-rebase-button"]');
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
...@@ -65,7 +66,7 @@ describe('Merge request widget rebase component', () => { ...@@ -65,7 +66,7 @@ describe('Merge request widget rebase component', () => {
const rebaseMock = jest.fn().mockResolvedValue(); const rebaseMock = jest.fn().mockResolvedValue();
const pollMock = jest.fn().mockResolvedValue({}); const pollMock = jest.fn().mockResolvedValue({});
beforeEach(() => { it('renders the warning message', () => {
createWrapper( createWrapper(
{ {
mr: { mr: {
...@@ -79,9 +80,7 @@ describe('Merge request widget rebase component', () => { ...@@ -79,9 +80,7 @@ describe('Merge request widget rebase component', () => {
}, },
mergeRequestWidgetGraphql, mergeRequestWidgetGraphql,
); );
});
it('renders the warning message', () => {
const text = findRebaseMessageText(); const text = findRebaseMessageText();
expect(text).toContain('Merge blocked'); expect(text).toContain('Merge blocked');
...@@ -91,6 +90,20 @@ describe('Merge request widget rebase component', () => { ...@@ -91,6 +90,20 @@ describe('Merge request widget rebase component', () => {
}); });
it('renders an error message when rebasing has failed', async () => { it('renders an error message when rebasing has failed', async () => {
createWrapper(
{
mr: {
rebaseInProgress: false,
canPushToSourceBranch: true,
},
service: {
rebase: rebaseMock,
poll: pollMock,
},
},
mergeRequestWidgetGraphql,
);
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax // eslint-disable-next-line no-restricted-syntax
wrapper.setData({ rebasingError: 'Something went wrong!' }); wrapper.setData({ rebasingError: 'Something went wrong!' });
...@@ -99,13 +112,31 @@ describe('Merge request widget rebase component', () => { ...@@ -99,13 +112,31 @@ describe('Merge request widget rebase component', () => {
expect(findRebaseMessageText()).toContain('Something went wrong!'); expect(findRebaseMessageText()).toContain('Something went wrong!');
}); });
describe('"Rebase" button', () => { describe('Rebase button with flag rebaseWithoutCiUi', () => {
it('is rendered', () => { beforeEach(() => {
expect(findRebaseButton().exists()).toBe(true); createWrapper(
{
mr: {
rebaseInProgress: false,
canPushToSourceBranch: true,
},
service: {
rebase: rebaseMock,
poll: pollMock,
},
},
mergeRequestWidgetGraphql,
{ rebaseWithoutCiUi: true },
);
});
it('rebase button with actions is rendered', () => {
expect(findRebaseButtonActions().exists()).toBe(true);
expect(findStandardRebaseButton().exists()).toBe(false);
}); });
it('has rebase and rebase without CI actions', () => { it('has rebase and rebase without CI actions', () => {
const actionNames = findRebaseButton() const actionNames = findRebaseButtonActions()
.props('actions') .props('actions')
.map((action) => action.key); .map((action) => action.key);
...@@ -113,13 +144,13 @@ describe('Merge request widget rebase component', () => { ...@@ -113,13 +144,13 @@ describe('Merge request widget rebase component', () => {
}); });
it('defaults to rebase action', () => { it('defaults to rebase action', () => {
expect(findRebaseButton().props('selectedKey')).toStrictEqual(REBASE_BUTTON_KEY); expect(findRebaseButtonActions().props('selectedKey')).toStrictEqual(REBASE_BUTTON_KEY);
}); });
it('starts the rebase when clicking', async () => { it('starts the rebase when clicking', async () => {
// ActionButtons use the actions props instead of emitting // ActionButtons use the actions props instead of emitting
// a click event, therefore simulating the behavior here: // a click event, therefore simulating the behavior here:
findRebaseButton() findRebaseButtonActions()
.props('actions') .props('actions')
.find((x) => x.key === REBASE_BUTTON_KEY) .find((x) => x.key === REBASE_BUTTON_KEY)
.handle(); .handle();
...@@ -132,7 +163,7 @@ describe('Merge request widget rebase component', () => { ...@@ -132,7 +163,7 @@ describe('Merge request widget rebase component', () => {
it('starts the CI-skipping rebase when clicking on "Rebase without CI"', async () => { it('starts the CI-skipping rebase when clicking on "Rebase without CI"', async () => {
// ActionButtons use the actions props instead of emitting // ActionButtons use the actions props instead of emitting
// a click event, therefore simulating the behavior here: // a click event, therefore simulating the behavior here:
findRebaseButton() findRebaseButtonActions()
.props('actions') .props('actions')
.find((x) => x.key === REBASE_WITHOUT_CI_BUTTON_KEY) .find((x) => x.key === REBASE_WITHOUT_CI_BUTTON_KEY)
.handle(); .handle();
...@@ -142,12 +173,74 @@ describe('Merge request widget rebase component', () => { ...@@ -142,12 +173,74 @@ describe('Merge request widget rebase component', () => {
expect(rebaseMock).toHaveBeenCalledWith({ skipCi: true }); expect(rebaseMock).toHaveBeenCalledWith({ skipCi: true });
}); });
}); });
describe('Rebase button with rebaseWithoutCiUI flag disabled', () => {
beforeEach(() => {
createWrapper(
{
mr: {
rebaseInProgress: false,
canPushToSourceBranch: true,
},
service: {
rebase: rebaseMock,
poll: pollMock,
},
},
mergeRequestWidgetGraphql,
);
});
it('standard rebase button is rendered', () => {
expect(findStandardRebaseButton().exists()).toBe(true);
expect(findRebaseButtonActions().exists()).toBe(false);
});
it('calls rebase method with skip_ci false', () => {
findStandardRebaseButton().vm.$emit('click');
expect(rebaseMock).toHaveBeenCalledWith({ skipCi: false });
});
});
}); });
describe('without permissions', () => { describe('without permissions', () => {
const exampleTargetBranch = 'fake-branch-to-test-with'; const exampleTargetBranch = 'fake-branch-to-test-with';
beforeEach(() => { describe('UI text', () => {
beforeEach(() => {
createWrapper(
{
mr: {
rebaseInProgress: false,
canPushToSourceBranch: false,
targetBranch: exampleTargetBranch,
},
service: {},
},
mergeRequestWidgetGraphql,
);
});
it('renders a message explaining user does not have permissions', () => {
const text = findRebaseMessageText();
expect(text).toContain(
'Merge blocked: the source branch must be rebased onto the target branch.',
);
expect(text).toContain('the source branch must be rebased');
});
it('renders the correct target branch name', () => {
const elem = findRebaseMessage();
expect(elem.text()).toContain(
'Merge blocked: the source branch must be rebased onto the target branch.',
);
});
});
it('does not render the rebase actions button with rebaseWithoutCiUI flag enabled', () => {
createWrapper( createWrapper(
{ {
mr: { mr: {
...@@ -158,28 +251,26 @@ describe('Merge request widget rebase component', () => { ...@@ -158,28 +251,26 @@ describe('Merge request widget rebase component', () => {
service: {}, service: {},
}, },
mergeRequestWidgetGraphql, mergeRequestWidgetGraphql,
{ rebaseWithoutCiUi: true },
); );
});
it('renders a message explaining user does not have permissions', () => { expect(findRebaseButtonActions().exists()).toBe(false);
const text = findRebaseMessageText();
expect(text).toContain(
'Merge blocked: the source branch must be rebased onto the target branch.',
);
expect(text).toContain('the source branch must be rebased');
}); });
it('renders the correct target branch name', () => { it('does not render the standard rebase button with rebaseWithoutCiUI flag disabled', () => {
const elem = findRebaseMessage(); createWrapper(
{
expect(elem.text()).toContain( mr: {
`Merge blocked: the source branch must be rebased onto the target branch.`, rebaseInProgress: false,
canPushToSourceBranch: false,
targetBranch: exampleTargetBranch,
},
service: {},
},
mergeRequestWidgetGraphql,
); );
});
it('does not render the "Rebase" button', () => { expect(findStandardRebaseButton().exists()).toBe(false);
expect(findRebaseButton().exists()).toBe(false);
}); });
}); });
......
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