Commit bc1af6fe authored by Phil Hughes's avatar Phil Hughes

Move widget rebase state to GraphQL

Moves the rebase state in the widget to use GraphQL for its data.

Closes https://gitlab.com/gitlab-org/gitlab/-/issues/235713
parent 8b4c43c0
<script> <script>
/* eslint-disable vue/no-v-html */ /* eslint-disable vue/no-v-html */
import { GlButton } from '@gitlab/ui'; import { GlButton, GlSkeletonLoader } from '@gitlab/ui';
import { escape } from 'lodash'; import { escape } from 'lodash';
import { __, sprintf } from '~/locale';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import simplePoll from '../../../lib/utils/simple_poll'; import simplePoll from '../../../lib/utils/simple_poll';
import eventHub from '../../event_hub'; import eventHub from '../../event_hub';
import statusIcon from '../mr_widget_status_icon.vue'; import statusIcon from '../mr_widget_status_icon.vue';
import rebaseQuery from '../../queries/states/ready_to_merge.query.graphql';
import mergeRequestQueryVariablesMixin from '../../mixins/merge_request_query_variables';
import { deprecatedCreateFlash as Flash } from '../../../flash'; import { deprecatedCreateFlash as Flash } from '../../../flash';
import { __, sprintf } from '~/locale';
export default { export default {
name: 'MRWidgetRebase', name: 'MRWidgetRebase',
apollo: {
state: {
query: rebaseQuery,
skip() {
return !this.glFeatures.mergeRequestWidgetGraphql;
},
variables() {
return this.mergeRequestQueryVariables;
},
update: (data) => data.project.mergeRequest,
},
},
components: { components: {
statusIcon, statusIcon,
GlButton, GlButton,
GlSkeletonLoader,
}, },
mixins: [glFeatureFlagMixin(), mergeRequestQueryVariablesMixin],
props: { props: {
mr: { mr: {
type: Object, type: Object,
...@@ -26,16 +43,41 @@ export default { ...@@ -26,16 +43,41 @@ export default {
}, },
data() { data() {
return { return {
state: {},
isMakingRequest: false, isMakingRequest: false,
rebasingError: null, rebasingError: null,
}; };
}, },
computed: { computed: {
isLoading() {
return this.glFeatures.mergeRequestWidgetGraphql && this.$apollo.queries.state.loading;
},
rebaseInProgress() {
if (this.glFeatures.mergeRequestWidgetGraphql) {
return this.state.rebaseInProgress;
}
return this.mr.rebaseInProgress;
},
canPushToSourceBranch() {
if (this.glFeatures.mergeRequestWidgetGraphql) {
return this.state.userPermissions.pushToSourceBranch;
}
return this.mr.canPushToSourceBranch;
},
targetBranch() {
if (this.glFeatures.mergeRequestWidgetGraphql) {
return this.state.targetBranch;
}
return this.mr.targetBranch;
},
status() { status() {
if (this.mr.rebaseInProgress || this.isMakingRequest) { if (this.rebaseInProgress || this.isMakingRequest) {
return 'loading'; return 'loading';
} }
if (!this.mr.canPushToSourceBranch && !this.mr.rebaseInProgress) { if (!this.canPushToSourceBranch && !this.rebaseInProgress) {
return 'warning'; return 'warning';
} }
return 'success'; return 'success';
...@@ -49,7 +91,7 @@ export default { ...@@ -49,7 +91,7 @@ export default {
'Fast-forward merge is not possible. Rebase the source branch onto %{targetBranch} to allow this merge request to be merged.', 'Fast-forward merge is not possible. Rebase the source branch onto %{targetBranch} to allow this merge request to be merged.',
), ),
{ {
targetBranch: `<span class="label-branch">${escape(this.mr.targetBranch)}</span>`, targetBranch: `<span class="label-branch">${escape(this.targetBranch)}</span>`,
}, },
false, false,
); );
...@@ -105,17 +147,30 @@ export default { ...@@ -105,17 +147,30 @@ export default {
</script> </script>
<template> <template>
<div class="mr-widget-body media"> <div class="mr-widget-body media">
<div v-if="isLoading" class="gl-w-full mr-conflict-loader">
<gl-skeleton-loader :width="334" :height="30">
<rect x="0" y="3" width="24" height="24" rx="4" />
<rect x="32" y="5" width="302" height="20" rx="4" />
</gl-skeleton-loader>
</div>
<template v-else>
<status-icon :status="status" :show-disabled-button="showDisabledButton" /> <status-icon :status="status" :show-disabled-button="showDisabledButton" />
<div class="rebase-state-find-class-convention media media-body space-children"> <div class="rebase-state-find-class-convention media media-body space-children">
<template v-if="mr.rebaseInProgress || isMakingRequest"> <span
<span class="bold" data-testid="rebase-message">{{ __('Rebase in progress') }}</span> v-if="rebaseInProgress || isMakingRequest"
</template> class="gl-font-weight-bold"
<template v-if="!mr.rebaseInProgress && !mr.canPushToSourceBranch"> data-testid="rebase-message"
<span class="bold" data-testid="rebase-message" v-html="fastForwardMergeText"></span> >{{ __('Rebase in progress') }}</span
</template> >
<template v-if="!mr.rebaseInProgress && mr.canPushToSourceBranch && !isMakingRequest"> <span
v-if="!rebaseInProgress && !canPushToSourceBranch"
class="gl-font-weight-bold"
data-testid="rebase-message"
v-html="fastForwardMergeText"
></span>
<div <div
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 <gl-button
...@@ -126,14 +181,16 @@ export default { ...@@ -126,14 +181,16 @@ export default {
> >
{{ __('Rebase') }} {{ __('Rebase') }}
</gl-button> </gl-button>
<span v-if="!rebasingError" class="bold" data-testid="rebase-message">{{ <span v-if="!rebasingError" class="gl-font-weight-bold" data-testid="rebase-message">{{
__( __(
'Fast-forward merge is not possible. Rebase the source branch onto the target branch.', 'Fast-forward merge is not possible. Rebase the source branch onto the target branch.',
) )
}}</span> }}</span>
<span v-else class="bold danger" data-testid="rebase-message">{{ rebasingError }}</span> <span v-else class="gl-font-weight-bold danger" data-testid="rebase-message">{{
rebasingError
}}</span>
</div> </div>
</template>
</div> </div>
</template>
</div> </div>
</template> </template>
query rebaseQuery($projectPath: ID!, $iid: String!) {
project(fullPath: $projectPath) {
mergeRequest(iid: $iid) {
rebaseInProgress
targetBranch
userPermissions {
pushToSourceBranch
}
}
}
}
...@@ -301,7 +301,8 @@ $mr-widget-min-height: 69px; ...@@ -301,7 +301,8 @@ $mr-widget-min-height: 69px;
margin: 0 0 0 10px; margin: 0 0 0 10px;
} }
.bold { .bold,
.gl-font-weight-bold {
font-weight: $gl-font-weight-bold; font-weight: $gl-font-weight-bold;
color: $gray-600; color: $gray-600;
margin-left: 10px; margin-left: 10px;
...@@ -317,7 +318,8 @@ $mr-widget-min-height: 69px; ...@@ -317,7 +318,8 @@ $mr-widget-min-height: 69px;
} }
.spacing, .spacing,
.bold { .bold,
.gl-font-weight-bold {
vertical-align: middle; vertical-align: middle;
} }
......
import Vue from 'vue'; import { nextTick } from 'vue';
import mountComponent from 'helpers/vue_mount_component_helper'; import { shallowMount } from '@vue/test-utils';
import eventHub from '~/vue_merge_request_widget/event_hub'; import eventHub from '~/vue_merge_request_widget/event_hub';
import component from '~/vue_merge_request_widget/components/states/mr_widget_rebase.vue'; import WidgetRebase from '~/vue_merge_request_widget/components/states/mr_widget_rebase.vue';
let wrapper;
function factory(propsData, mergeRequestWidgetGraphql) {
wrapper = shallowMount(WidgetRebase, {
propsData,
data() {
return {
state: {
rebaseInProgress: propsData.mr.rebaseInProgress,
targetBranch: propsData.mr.targetBranch,
userPermissions: {
pushToSourceBranch: propsData.mr.canPushToSourceBranch,
},
},
};
},
provide: { glFeatures: { mergeRequestWidgetGraphql } },
mocks: {
$apollo: {
queries: {
state: { loading: false },
},
},
},
});
}
describe('Merge request widget rebase component', () => { describe('Merge request widget rebase component', () => {
let Component; const findRebaseMessageEl = () => wrapper.find('[data-testid="rebase-message"]');
let vm; const findRebaseMessageElText = () => findRebaseMessageEl().text();
const findRebaseMessageEl = () => vm.$el.querySelector('[data-testid="rebase-message"]');
const findRebaseMessageElText = () => findRebaseMessageEl().textContent.trim();
beforeEach(() => {
Component = Vue.extend(component);
});
afterEach(() => { afterEach(() => {
vm.$destroy(); wrapper.destroy();
wrapper = null;
}); });
[true, false].forEach((mergeRequestWidgetGraphql) => {
describe(`widget graphql is ${mergeRequestWidgetGraphql ? 'enabled' : 'dislabed'}`, () => {
describe('While rebasing', () => { describe('While rebasing', () => {
it('should show progress message', () => { it('should show progress message', () => {
vm = mountComponent(Component, { factory(
{
mr: { rebaseInProgress: true }, mr: { rebaseInProgress: true },
service: {}, service: {},
}); },
mergeRequestWidgetGraphql,
);
expect(findRebaseMessageElText()).toContain('Rebase in progress'); expect(findRebaseMessageElText()).toContain('Rebase in progress');
}); });
}); });
describe('With permissions', () => { describe('With permissions', () => {
beforeEach(() => { it('it should render rebase button and warning message', () => {
vm = mountComponent(Component, { factory(
{
mr: { mr: {
rebaseInProgress: false, rebaseInProgress: false,
canPushToSourceBranch: true, canPushToSourceBranch: true,
}, },
service: {}, service: {},
}); },
}); mergeRequestWidgetGraphql,
);
it('it should render rebase button and warning message', () => {
const text = findRebaseMessageElText(); const text = findRebaseMessageElText();
expect(text).toContain('Fast-forward merge is not possible.'); expect(text).toContain('Fast-forward merge is not possible.');
...@@ -49,58 +76,76 @@ describe('Merge request widget rebase component', () => { ...@@ -49,58 +76,76 @@ describe('Merge request widget rebase component', () => {
); );
}); });
it('it should render error message when it fails', (done) => { it('it should render error message when it fails', async () => {
vm.rebasingError = 'Something went wrong!'; factory(
{
mr: {
rebaseInProgress: false,
canPushToSourceBranch: true,
},
service: {},
},
mergeRequestWidgetGraphql,
);
wrapper.setData({ rebasingError: 'Something went wrong!' });
Vue.nextTick(() => { await nextTick();
expect(findRebaseMessageElText()).toContain('Something went wrong!'); expect(findRebaseMessageElText()).toContain('Something went wrong!');
done();
});
}); });
}); });
describe('Without permissions', () => { describe('Without permissions', () => {
it('should render a message explaining user does not have permissions', () => { it('should render a message explaining user does not have permissions', () => {
vm = mountComponent(Component, { factory(
{
mr: { mr: {
rebaseInProgress: false, rebaseInProgress: false,
canPushToSourceBranch: false, canPushToSourceBranch: false,
targetBranch: 'foo', targetBranch: 'foo',
}, },
service: {}, service: {},
}); },
mergeRequestWidgetGraphql,
);
const text = findRebaseMessageElText(); const text = findRebaseMessageElText();
expect(text).toContain('Fast-forward merge is not possible.'); expect(text).toContain('Fast-forward merge is not possible.');
expect(text).toContain('Rebase the source branch onto'); expect(text).toContain('Rebase the source branch onto');
expect(text).toContain('foo'); expect(text).toContain('foo');
expect(text.replace(/\s\s+/g, ' ')).toContain('to allow this merge request to be merged.'); expect(text.replace(/\s\s+/g, ' ')).toContain(
'to allow this merge request to be merged.',
);
}); });
it('should render the correct target branch name', () => { it('should render the correct target branch name', () => {
const targetBranch = 'fake-branch-to-test-with'; const targetBranch = 'fake-branch-to-test-with';
vm = mountComponent(Component, { factory(
{
mr: { mr: {
rebaseInProgress: false, rebaseInProgress: false,
canPushToSourceBranch: false, canPushToSourceBranch: false,
targetBranch, targetBranch,
}, },
service: {}, service: {},
}); },
mergeRequestWidgetGraphql,
);
const elem = findRebaseMessageEl(); const elem = findRebaseMessageEl();
expect(elem.innerHTML).toContain( expect(elem.text()).toContain(
`Fast-forward merge is not possible. Rebase the source branch onto <span class="label-branch">${targetBranch}</span> to allow this merge request to be merged.`, `Fast-forward merge is not possible. Rebase the source branch onto ${targetBranch} to allow this merge request to be merged.`,
); );
}); });
}); });
describe('methods', () => { describe('methods', () => {
it('checkRebaseStatus', (done) => { it('checkRebaseStatus', async () => {
jest.spyOn(eventHub, '$emit').mockImplementation(() => {}); jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
vm = mountComponent(Component, { factory(
{
mr: {}, mr: {},
service: { service: {
rebase() { rebase() {
...@@ -115,21 +160,22 @@ describe('Merge request widget rebase component', () => { ...@@ -115,21 +160,22 @@ describe('Merge request widget rebase component', () => {
}); });
}, },
}, },
}); },
mergeRequestWidgetGraphql,
);
vm.rebase(); wrapper.vm.rebase();
// Wait for the rebase request // Wait for the rebase request
vm.$nextTick() await nextTick();
// Wait for the polling request // Wait for the polling request
.then(vm.$nextTick()) await nextTick();
// Wait for the eventHub to be called // Wait for the eventHub to be called
.then(vm.$nextTick()) await nextTick();
.then(() => {
expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetRebaseSuccess'); expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetRebaseSuccess');
}) });
.then(done) });
.catch(done.fail);
}); });
}); });
}); });
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