Commit ac876fca authored by Phil Hughes's avatar Phil Hughes Committed by Nick Thomas

Move WIP state in MR widget to GraphQL

As part of https://gitlab.com/groups/gitlab-com/-/epics/828 we are
moving the widget states into GraphQL
so each widget can
request the data it needs.
This moves the WIP state into the GraphQL.
parent 433372bb
...@@ -3,6 +3,11 @@ import $ from 'jquery'; ...@@ -3,6 +3,11 @@ import $ from 'jquery';
import { GlButton } from '@gitlab/ui'; import { GlButton } from '@gitlab/ui';
import { __ } from '~/locale'; import { __ } from '~/locale';
import createFlash from '~/flash'; import createFlash from '~/flash';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import mergeRequestQueryVariablesMixin from '../../mixins/merge_request_query_variables';
import getStateQuery from '../../queries/get_state.query.graphql';
import workInProgressQuery from '../../queries/states/work_in_progress.query.graphql';
import removeWipMutation from '../../queries/toggle_wip.mutation.graphql';
import StatusIcon from '../mr_widget_status_icon.vue'; import StatusIcon from '../mr_widget_status_icon.vue';
import tooltip from '../../../vue_shared/directives/tooltip'; import tooltip from '../../../vue_shared/directives/tooltip';
import eventHub from '../../event_hub'; import eventHub from '../../event_hub';
...@@ -16,17 +21,105 @@ export default { ...@@ -16,17 +21,105 @@ export default {
directives: { directives: {
tooltip, tooltip,
}, },
mixins: [glFeatureFlagMixin(), mergeRequestQueryVariablesMixin],
apollo: {
userPermissions: {
query: workInProgressQuery,
skip() {
return !this.glFeatures.mergeRequestWidgetGraphql;
},
variables() {
return this.mergeRequestQueryVariables;
},
update: data => data.project.mergeRequest.userPermissions,
},
},
props: { props: {
mr: { type: Object, required: true }, mr: { type: Object, required: true },
service: { type: Object, required: true }, service: { type: Object, required: true },
}, },
data() { data() {
return { return {
userPermissions: {},
isMakingRequest: false, isMakingRequest: false,
}; };
}, },
computed: {
canUpdate() {
if (this.glFeatures.mergeRequestWidgetGraphql) {
return this.userPermissions.updateMergeRequest;
}
return Boolean(this.mr.removeWIPPath);
},
},
methods: { methods: {
removeWipMutation() {
this.isMakingRequest = true;
this.$apollo
.mutate({
mutation: removeWipMutation,
variables: {
...this.mergeRequestQueryVariables,
wip: false,
},
update(
store,
{
data: {
mergeRequestSetWip: {
errors,
mergeRequest: { workInProgress, title },
},
},
},
) {
if (errors?.length) {
createFlash(__('Something went wrong. Please try again.'));
return;
}
const data = store.readQuery({
query: getStateQuery,
variables: this.mergeRequestQueryVariables,
});
data.project.mergeRequest.workInProgress = workInProgress;
data.project.mergeRequest.title = title;
store.writeQuery({
query: getStateQuery,
data,
variables: this.mergeRequestQueryVariables,
});
},
optimisticResponse: {
// eslint-disable-next-line @gitlab/require-i18n-strings
__typename: 'Mutation',
mergeRequestSetWip: {
__typename: 'MergeRequestSetWipPayload',
errors: [],
mergeRequest: {
__typename: 'MergeRequest',
title: this.mr.title,
workInProgress: false,
},
},
},
})
.then(({ data: { mergeRequestSetWip: { mergeRequest: { title } } } }) => {
createFlash(__('The merge request can now be merged.'), 'notice');
$('.merge-request .detail-page-description .title').text(title);
})
.catch(() => createFlash(__('Something went wrong. Please try again.')))
.finally(() => {
this.isMakingRequest = false;
});
},
handleRemoveWIP() { handleRemoveWIP() {
if (this.glFeatures.mergeRequestWidgetGraphql) {
this.removeWipMutation();
} else {
this.isMakingRequest = true; this.isMakingRequest = true;
this.service this.service
.removeWIP() .removeWIP()
...@@ -40,6 +133,7 @@ export default { ...@@ -40,6 +133,7 @@ export default {
this.isMakingRequest = false; this.isMakingRequest = false;
createFlash(__('Something went wrong. Please try again.')); createFlash(__('Something went wrong. Please try again.'));
}); });
}
}, },
}, },
}; };
...@@ -47,7 +141,7 @@ export default { ...@@ -47,7 +141,7 @@ export default {
<template> <template>
<div class="mr-widget-body media"> <div class="mr-widget-body media">
<status-icon :show-disabled-button="Boolean(mr.removeWIPPath)" status="warning" /> <status-icon :show-disabled-button="canUpdate" status="warning" />
<div class="media-body"> <div class="media-body">
<div class="gl-ml-3 float-left"> <div class="gl-ml-3 float-left">
<span class="gl-font-weight-bold"> <span class="gl-font-weight-bold">
...@@ -58,7 +152,7 @@ export default { ...@@ -58,7 +152,7 @@ export default {
}}</span> }}</span>
</div> </div>
<gl-button <gl-button
v-if="mr.removeWIPPath" v-if="canUpdate"
size="small" size="small"
:disabled="isMakingRequest" :disabled="isMakingRequest"
:loading="isMakingRequest" :loading="isMakingRequest"
......
export default {
computed: {
mergeRequestQueryVariables() {
return {
projectPath: this.mr.targetProjectFullPath,
iid: `${this.mr.iid}`,
};
},
},
};
...@@ -8,6 +8,7 @@ import { sprintf, s__, __ } from '~/locale'; ...@@ -8,6 +8,7 @@ import { sprintf, s__, __ } from '~/locale';
import Project from '~/pages/projects/project'; import Project from '~/pages/projects/project';
import SmartInterval from '~/smart_interval'; import SmartInterval from '~/smart_interval';
import createFlash from '../flash'; import createFlash from '../flash';
import mergeRequestQueryVariablesMixin from './mixins/merge_request_query_variables';
import Loading from './components/loading.vue'; import Loading from './components/loading.vue';
import WidgetHeader from './components/mr_widget_header.vue'; import WidgetHeader from './components/mr_widget_header.vue';
import WidgetSuggestPipeline from './components/mr_widget_suggest_pipeline.vue'; import WidgetSuggestPipeline from './components/mr_widget_suggest_pipeline.vue';
...@@ -42,6 +43,7 @@ import GroupedCodequalityReportsApp from '../reports/codequality_report/grouped_ ...@@ -42,6 +43,7 @@ import GroupedCodequalityReportsApp from '../reports/codequality_report/grouped_
import GroupedTestReportsApp from '../reports/components/grouped_test_reports_app.vue'; import GroupedTestReportsApp from '../reports/components/grouped_test_reports_app.vue';
import { setFaviconOverlay } from '../lib/utils/common_utils'; import { setFaviconOverlay } from '../lib/utils/common_utils';
import GroupedAccessibilityReportsApp from '../reports/accessibility_report/grouped_accessibility_reports_app.vue'; import GroupedAccessibilityReportsApp from '../reports/accessibility_report/grouped_accessibility_reports_app.vue';
import getStateQuery from './queries/get_state.query.graphql';
export default { export default {
el: '#js-vue-mr-widget', el: '#js-vue-mr-widget',
...@@ -83,6 +85,27 @@ export default { ...@@ -83,6 +85,27 @@ export default {
GroupedAccessibilityReportsApp, GroupedAccessibilityReportsApp,
MrWidgetApprovals, MrWidgetApprovals,
}, },
apollo: {
state: {
query: getStateQuery,
manual: true,
pollInterval: 10 * 1000,
skip() {
return !this.mr || !window.gon?.features?.mergeRequestWidgetGraphql;
},
variables() {
return this.mergeRequestQueryVariables;
},
result({
data: {
project: { mergeRequest },
},
}) {
this.mr.setGraphqlData(mergeRequest);
},
},
},
mixins: [mergeRequestQueryVariablesMixin],
props: { props: {
mrData: { mrData: {
type: Object, type: Object,
......
query getState($projectPath: ID!, $iid: String!) {
project(fullPath: $projectPath) {
mergeRequest(iid: $iid) {
title
workInProgress
}
}
}
query workInProgressQuery($projectPath: ID!, $iid: String!) {
project(fullPath: $projectPath) {
mergeRequest(iid: $iid) {
userPermissions {
updateMergeRequest
}
}
}
}
mutation toggleWIPStatus($projectPath: ID!, $iid: String!, $wip: Boolean!) {
mergeRequestSetWip(input: { projectPath: $projectPath, iid: $iid, wip: $wip }) {
mergeRequest {
title
workInProgress
}
errors
}
}
import { stateKey } from './state_maps'; import { stateKey } from './state_maps';
export default function deviseState(data) { export default function deviseState() {
if (data.project_archived) { if (this.projectArchived) {
return stateKey.archived; return stateKey.archived;
} else if (data.branch_missing) { } else if (this.branchMissing) {
return stateKey.missingBranch; return stateKey.missingBranch;
} else if (!data.commits_count) { } else if (!this.commitsCount) {
return stateKey.nothingToMerge; return stateKey.nothingToMerge;
} else if (this.mergeStatus === 'unchecked' || this.mergeStatus === 'checking') { } else if (this.mergeStatus === 'unchecked' || this.mergeStatus === 'checking') {
return stateKey.checking; return stateKey.checking;
} else if (data.has_conflicts) { } else if (this.hasConflicts) {
return stateKey.conflicts; return stateKey.conflicts;
} else if (this.shouldBeRebased) { } else if (this.shouldBeRebased) {
return stateKey.rebase; return stateKey.rebase;
} else if (this.onlyAllowMergeIfPipelineSucceeds && this.isPipelineFailed) { } else if (this.onlyAllowMergeIfPipelineSucceeds && this.isPipelineFailed) {
return stateKey.pipelineFailed; return stateKey.pipelineFailed;
} else if (data.work_in_progress) { } else if (this.workInProgress) {
return stateKey.workInProgress; return stateKey.workInProgress;
} else if (this.hasMergeableDiscussionsState) { } else if (this.hasMergeableDiscussionsState) {
return stateKey.unresolvedDiscussions; return stateKey.unresolvedDiscussions;
......
...@@ -60,6 +60,9 @@ export default class MergeRequestStore { ...@@ -60,6 +60,9 @@ export default class MergeRequestStore {
this.rebaseInProgress = data.rebase_in_progress; this.rebaseInProgress = data.rebase_in_progress;
this.mergeRequestDiffsPath = data.diffs_path; this.mergeRequestDiffsPath = data.diffs_path;
this.approvalsWidgetType = data.approvals_widget_type; this.approvalsWidgetType = data.approvals_widget_type;
this.projectArchived = data.project_archived;
this.branchMissing = data.branch_missing;
this.hasConflicts = data.has_conflicts;
if (data.issues_links) { if (data.issues_links) {
const links = data.issues_links; const links = data.issues_links;
...@@ -90,7 +93,8 @@ export default class MergeRequestStore { ...@@ -90,7 +93,8 @@ export default class MergeRequestStore {
this.ffOnlyEnabled = data.ff_only_enabled; this.ffOnlyEnabled = data.ff_only_enabled;
this.shouldBeRebased = Boolean(data.should_be_rebased); this.shouldBeRebased = Boolean(data.should_be_rebased);
this.isRemovingSourceBranch = this.isRemovingSourceBranch || false; this.isRemovingSourceBranch = this.isRemovingSourceBranch || false;
this.isOpen = data.state === 'opened'; this.mergeRequestState = data.state;
this.isOpen = this.mergeRequestState === 'opened';
this.hasMergeableDiscussionsState = data.mergeable_discussions_state === false; this.hasMergeableDiscussionsState = data.mergeable_discussions_state === false;
this.isSHAMismatch = this.sha !== data.diff_head_sha; this.isSHAMismatch = this.sha !== data.diff_head_sha;
this.latestSHA = data.diff_head_sha; this.latestSHA = data.diff_head_sha;
...@@ -133,6 +137,10 @@ export default class MergeRequestStore { ...@@ -133,6 +137,10 @@ export default class MergeRequestStore {
this.mergeCommitPath = data.merge_commit_path; this.mergeCommitPath = data.merge_commit_path;
this.canPushToSourceBranch = data.can_push_to_source_branch; this.canPushToSourceBranch = data.can_push_to_source_branch;
if (data.work_in_progress !== undefined) {
this.workInProgress = data.work_in_progress;
}
const currentUser = data.current_user; const currentUser = data.current_user;
this.cherryPickInForkPath = currentUser.cherry_pick_in_fork_path; this.cherryPickInForkPath = currentUser.cherry_pick_in_fork_path;
...@@ -143,19 +151,25 @@ export default class MergeRequestStore { ...@@ -143,19 +151,25 @@ export default class MergeRequestStore {
this.canCherryPickInCurrentMR = currentUser.can_cherry_pick_on_current_merge_request || false; this.canCherryPickInCurrentMR = currentUser.can_cherry_pick_on_current_merge_request || false;
this.canRevertInCurrentMR = currentUser.can_revert_on_current_merge_request || false; this.canRevertInCurrentMR = currentUser.can_revert_on_current_merge_request || false;
this.setState(data); this.setState();
}
setGraphqlData(data) {
this.workInProgress = data.workInProgress;
this.setState();
} }
setState(data) { setState() {
if (this.mergeOngoing) { if (this.mergeOngoing) {
this.state = 'merging'; this.state = 'merging';
return; return;
} }
if (this.isOpen) { if (this.isOpen) {
this.state = getStateKey.call(this, data); this.state = getStateKey.call(this);
} else { } else {
switch (data.state) { switch (this.mergeRequestState) {
case 'merged': case 'merged':
this.state = 'merged'; this.state = 'merged';
break; break;
......
...@@ -39,6 +39,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo ...@@ -39,6 +39,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
push_frontend_feature_flag(:auto_expand_collapsed_diffs, @project, default_enabled: true) push_frontend_feature_flag(:auto_expand_collapsed_diffs, @project, default_enabled: true)
push_frontend_feature_flag(:approvals_commented_by, @project, default_enabled: true) push_frontend_feature_flag(:approvals_commented_by, @project, default_enabled: true)
push_frontend_feature_flag(:hide_jump_to_next_unresolved_in_threads, default_enabled: true) push_frontend_feature_flag(:hide_jump_to_next_unresolved_in_threads, default_enabled: true)
push_frontend_feature_flag(:merge_request_widget_graphql, @project)
end end
before_action do before_action do
......
import CEGetStateKey from '~/vue_merge_request_widget/stores/get_state_key'; import CEGetStateKey from '~/vue_merge_request_widget/stores/get_state_key';
import { stateKey } from './state_maps'; import { stateKey } from './state_maps';
export default function(data) { export default function() {
if (this.isGeoSecondaryNode) { if (this.isGeoSecondaryNode) {
return 'geoSecondaryNode'; return 'geoSecondaryNode';
} }
if (data.policy_violation) { if (this.policyViolation) {
return stateKey.policyViolation; return stateKey.policyViolation;
} }
return CEGetStateKey.call(this, data); return CEGetStateKey.call(this);
} }
...@@ -44,6 +44,7 @@ export default class MergeRequestStore extends CEMergeRequestStore { ...@@ -44,6 +44,7 @@ export default class MergeRequestStore extends CEMergeRequestStore {
this.mergePipelinesEnabled = Boolean(data.merge_pipelines_enabled); this.mergePipelinesEnabled = Boolean(data.merge_pipelines_enabled);
this.mergeTrainsCount = data.merge_trains_count || 0; this.mergeTrainsCount = data.merge_trains_count || 0;
this.mergeTrainIndex = data.merge_train_index; this.mergeTrainIndex = data.merge_train_index;
this.policyViolation = data.policy_violation;
super.setData(data, isRebased); super.setData(data, isRebased);
} }
......
...@@ -11,15 +11,13 @@ describe('getStateKey', () => { ...@@ -11,15 +11,13 @@ describe('getStateKey', () => {
hasMergeableDiscussionsState: false, hasMergeableDiscussionsState: false,
isPipelineBlocked: false, isPipelineBlocked: false,
canBeMerged: false, canBeMerged: false,
projectArchived: false,
branchMissing: false,
commitsCount: 2,
hasConflicts: false,
workInProgress: false,
}; };
const data = { const bound = getStateKey.bind(context);
project_archived: false,
branch_missing: false,
commits_count: 2,
has_conflicts: false,
work_in_progress: false,
};
const bound = getStateKey.bind(context, data);
expect(bound()).toEqual(null); expect(bound()).toEqual(null);
...@@ -49,7 +47,7 @@ describe('getStateKey', () => { ...@@ -49,7 +47,7 @@ describe('getStateKey', () => {
expect(bound()).toEqual('unresolvedDiscussions'); expect(bound()).toEqual('unresolvedDiscussions');
data.work_in_progress = true; context.workInProgress = true;
expect(bound()).toEqual('workInProgress'); expect(bound()).toEqual('workInProgress');
...@@ -62,7 +60,7 @@ describe('getStateKey', () => { ...@@ -62,7 +60,7 @@ describe('getStateKey', () => {
expect(bound()).toEqual('rebase'); expect(bound()).toEqual('rebase');
data.has_conflicts = true; context.hasConflicts = true;
expect(bound()).toEqual('conflicts'); expect(bound()).toEqual('conflicts');
...@@ -70,15 +68,15 @@ describe('getStateKey', () => { ...@@ -70,15 +68,15 @@ describe('getStateKey', () => {
expect(bound()).toEqual('checking'); expect(bound()).toEqual('checking');
data.commits_count = 0; context.commitsCount = 0;
expect(bound()).toEqual('nothingToMerge'); expect(bound()).toEqual('nothingToMerge');
data.branch_missing = true; context.branchMissing = true;
expect(bound()).toEqual('missingBranch'); expect(bound()).toEqual('missingBranch');
data.project_archived = true; context.projectArchived = true;
expect(bound()).toEqual('archived'); expect(bound()).toEqual('archived');
}); });
...@@ -94,15 +92,13 @@ describe('getStateKey', () => { ...@@ -94,15 +92,13 @@ describe('getStateKey', () => {
isPipelineBlocked: false, isPipelineBlocked: false,
canBeMerged: false, canBeMerged: false,
shouldBeRebased: true, shouldBeRebased: true,
projectArchived: false,
branchMissing: false,
commitsCount: 2,
hasConflicts: false,
workInProgress: false,
}; };
const data = { const bound = getStateKey.bind(context);
project_archived: false,
branch_missing: false,
commits_count: 2,
has_conflicts: false,
work_in_progress: false,
};
const bound = getStateKey.bind(context, data);
expect(bound()).toEqual('rebase'); expect(bound()).toEqual('rebase');
}); });
...@@ -115,15 +111,11 @@ describe('getStateKey', () => { ...@@ -115,15 +111,11 @@ describe('getStateKey', () => {
`( `(
'returns $stateKey when canMerge is $canMerge and isSHAMismatch is $isSHAMismatch', 'returns $stateKey when canMerge is $canMerge and isSHAMismatch is $isSHAMismatch',
({ canMerge, isSHAMismatch, stateKey }) => { ({ canMerge, isSHAMismatch, stateKey }) => {
const bound = getStateKey.bind( const bound = getStateKey.bind({
{
canMerge, canMerge,
isSHAMismatch, isSHAMismatch,
}, commitsCount: 2,
{ });
commits_count: 2,
},
);
expect(bound()).toEqual(stateKey); expect(bound()).toEqual(stateKey);
}, },
......
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