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>
/* eslint-disable vue/no-v-html */
import { GlButton } from '@gitlab/ui';
import { GlButton, GlSkeletonLoader } from '@gitlab/ui';
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 eventHub from '../../event_hub';
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 { __, sprintf } from '~/locale';
export default {
name: 'MRWidgetRebase',
apollo: {
state: {
query: rebaseQuery,
skip() {
return !this.glFeatures.mergeRequestWidgetGraphql;
},
variables() {
return this.mergeRequestQueryVariables;
},
update: (data) => data.project.mergeRequest,
},
},
components: {
statusIcon,
GlButton,
GlSkeletonLoader,
},
mixins: [glFeatureFlagMixin(), mergeRequestQueryVariablesMixin],
props: {
mr: {
type: Object,
......@@ -26,16 +43,41 @@ export default {
},
data() {
return {
state: {},
isMakingRequest: false,
rebasingError: null,
};
},
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() {
if (this.mr.rebaseInProgress || this.isMakingRequest) {
if (this.rebaseInProgress || this.isMakingRequest) {
return 'loading';
}
if (!this.mr.canPushToSourceBranch && !this.mr.rebaseInProgress) {
if (!this.canPushToSourceBranch && !this.rebaseInProgress) {
return 'warning';
}
return 'success';
......@@ -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.',
),
{
targetBranch: `<span class="label-branch">${escape(this.mr.targetBranch)}</span>`,
targetBranch: `<span class="label-branch">${escape(this.targetBranch)}</span>`,
},
false,
);
......@@ -105,17 +147,30 @@ export default {
</script>
<template>
<div class="mr-widget-body media">
<status-icon :status="status" :show-disabled-button="showDisabledButton" />
<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" />
<div class="rebase-state-find-class-convention media media-body space-children">
<template v-if="mr.rebaseInProgress || isMakingRequest">
<span class="bold" data-testid="rebase-message">{{ __('Rebase in progress') }}</span>
</template>
<template v-if="!mr.rebaseInProgress && !mr.canPushToSourceBranch">
<span class="bold" data-testid="rebase-message" v-html="fastForwardMergeText"></span>
</template>
<template v-if="!mr.rebaseInProgress && mr.canPushToSourceBranch && !isMakingRequest">
<div class="rebase-state-find-class-convention media media-body space-children">
<span
v-if="rebaseInProgress || isMakingRequest"
class="gl-font-weight-bold"
data-testid="rebase-message"
>{{ __('Rebase in progress') }}</span
>
<span
v-if="!rebaseInProgress && !canPushToSourceBranch"
class="gl-font-weight-bold"
data-testid="rebase-message"
v-html="fastForwardMergeText"
></span>
<div
v-if="!rebaseInProgress && canPushToSourceBranch && !isMakingRequest"
class="accept-merge-holder clearfix js-toggle-container accept-action media space-children"
>
<gl-button
......@@ -126,14 +181,16 @@ export default {
>
{{ __('Rebase') }}
</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.',
)
}}</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>
</template>
</div>
</div>
</template>
</div>
</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;
margin: 0 0 0 10px;
}
.bold {
.bold,
.gl-font-weight-bold {
font-weight: $gl-font-weight-bold;
color: $gray-600;
margin-left: 10px;
......@@ -317,7 +318,8 @@ $mr-widget-min-height: 69px;
}
.spacing,
.bold {
.bold,
.gl-font-weight-bold {
vertical-align: middle;
}
......
import Vue from 'vue';
import mountComponent from 'helpers/vue_mount_component_helper';
import { nextTick } from 'vue';
import { shallowMount } from '@vue/test-utils';
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', () => {
let Component;
let vm;
const findRebaseMessageEl = () => vm.$el.querySelector('[data-testid="rebase-message"]');
const findRebaseMessageElText = () => findRebaseMessageEl().textContent.trim();
beforeEach(() => {
Component = Vue.extend(component);
});
const findRebaseMessageEl = () => wrapper.find('[data-testid="rebase-message"]');
const findRebaseMessageElText = () => findRebaseMessageEl().text();
afterEach(() => {
vm.$destroy();
wrapper.destroy();
wrapper = null;
});
describe('While rebasing', () => {
it('should show progress message', () => {
vm = mountComponent(Component, {
mr: { rebaseInProgress: true },
service: {},
[true, false].forEach((mergeRequestWidgetGraphql) => {
describe(`widget graphql is ${mergeRequestWidgetGraphql ? 'enabled' : 'dislabed'}`, () => {
describe('While rebasing', () => {
it('should show progress message', () => {
factory(
{
mr: { rebaseInProgress: true },
service: {},
},
mergeRequestWidgetGraphql,
);
expect(findRebaseMessageElText()).toContain('Rebase in progress');
});
});
expect(findRebaseMessageElText()).toContain('Rebase in progress');
});
});
describe('With permissions', () => {
beforeEach(() => {
vm = mountComponent(Component, {
mr: {
rebaseInProgress: false,
canPushToSourceBranch: true,
},
service: {},
});
});
it('it should render rebase button and warning message', () => {
const text = findRebaseMessageElText();
expect(text).toContain('Fast-forward merge is not possible.');
expect(text.replace(/\s\s+/g, ' ')).toContain(
'Rebase the source branch onto the target branch.',
);
});
describe('With permissions', () => {
it('it should render rebase button and warning message', () => {
factory(
{
mr: {
rebaseInProgress: false,
canPushToSourceBranch: true,
},
service: {},
},
mergeRequestWidgetGraphql,
);
const text = findRebaseMessageElText();
expect(text).toContain('Fast-forward merge is not possible.');
expect(text.replace(/\s\s+/g, ' ')).toContain(
'Rebase the source branch onto the target branch.',
);
});
it('it should render error message when it fails', async () => {
factory(
{
mr: {
rebaseInProgress: false,
canPushToSourceBranch: true,
},
service: {},
},
mergeRequestWidgetGraphql,
);
it('it should render error message when it fails', (done) => {
vm.rebasingError = 'Something went wrong!';
wrapper.setData({ rebasingError: 'Something went wrong!' });
Vue.nextTick(() => {
expect(findRebaseMessageElText()).toContain('Something went wrong!');
done();
await nextTick();
expect(findRebaseMessageElText()).toContain('Something went wrong!');
});
});
});
});
describe('Without permissions', () => {
it('should render a message explaining user does not have permissions', () => {
vm = mountComponent(Component, {
mr: {
rebaseInProgress: false,
canPushToSourceBranch: false,
targetBranch: 'foo',
},
service: {},
});
const text = findRebaseMessageElText();
describe('Without permissions', () => {
it('should render a message explaining user does not have permissions', () => {
factory(
{
mr: {
rebaseInProgress: false,
canPushToSourceBranch: false,
targetBranch: 'foo',
},
service: {},
},
mergeRequestWidgetGraphql,
);
const text = findRebaseMessageElText();
expect(text).toContain('Fast-forward merge is not possible.');
expect(text).toContain('Rebase the source branch onto');
expect(text).toContain('foo');
expect(text.replace(/\s\s+/g, ' ')).toContain(
'to allow this merge request to be merged.',
);
});
it('should render the correct target branch name', () => {
const targetBranch = 'fake-branch-to-test-with';
factory(
{
mr: {
rebaseInProgress: false,
canPushToSourceBranch: false,
targetBranch,
},
service: {},
},
mergeRequestWidgetGraphql,
);
expect(text).toContain('Fast-forward merge is not possible.');
expect(text).toContain('Rebase the source branch onto');
expect(text).toContain('foo');
expect(text.replace(/\s\s+/g, ' ')).toContain('to allow this merge request to be merged.');
});
const elem = findRebaseMessageEl();
it('should render the correct target branch name', () => {
const targetBranch = 'fake-branch-to-test-with';
vm = mountComponent(Component, {
mr: {
rebaseInProgress: false,
canPushToSourceBranch: false,
targetBranch,
},
service: {},
expect(elem.text()).toContain(
`Fast-forward merge is not possible. Rebase the source branch onto ${targetBranch} to allow this merge request to be merged.`,
);
});
});
const elem = findRebaseMessageEl();
expect(elem.innerHTML).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.`,
);
});
});
describe('methods', () => {
it('checkRebaseStatus', (done) => {
jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
vm = mountComponent(Component, {
mr: {},
service: {
rebase() {
return Promise.resolve();
},
poll() {
return Promise.resolve({
data: {
rebase_in_progress: false,
merge_error: null,
describe('methods', () => {
it('checkRebaseStatus', async () => {
jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
factory(
{
mr: {},
service: {
rebase() {
return Promise.resolve();
},
poll() {
return Promise.resolve({
data: {
rebase_in_progress: false,
merge_error: null,
},
});
},
},
});
},
},
});
},
mergeRequestWidgetGraphql,
);
vm.rebase();
wrapper.vm.rebase();
// Wait for the rebase request
await nextTick();
// Wait for the polling request
await nextTick();
// Wait for the eventHub to be called
await nextTick();
// Wait for the rebase request
vm.$nextTick()
// Wait for the polling request
.then(vm.$nextTick())
// Wait for the eventHub to be called
.then(vm.$nextTick())
.then(() => {
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