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';
import { GlButton } from '@gitlab/ui';
import { __ } from '~/locale';
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 tooltip from '../../../vue_shared/directives/tooltip';
import eventHub from '../../event_hub';
......@@ -16,38 +21,127 @@ export default {
directives: {
tooltip,
},
mixins: [glFeatureFlagMixin(), mergeRequestQueryVariablesMixin],
apollo: {
userPermissions: {
query: workInProgressQuery,
skip() {
return !this.glFeatures.mergeRequestWidgetGraphql;
},
variables() {
return this.mergeRequestQueryVariables;
},
update: data => data.project.mergeRequest.userPermissions,
},
},
props: {
mr: { type: Object, required: true },
service: { type: Object, required: true },
},
data() {
return {
userPermissions: {},
isMakingRequest: false,
};
},
computed: {
canUpdate() {
if (this.glFeatures.mergeRequestWidgetGraphql) {
return this.userPermissions.updateMergeRequest;
}
return Boolean(this.mr.removeWIPPath);
},
},
methods: {
handleRemoveWIP() {
removeWipMutation() {
this.isMakingRequest = true;
this.service
.removeWIP()
.then(res => res.data)
.then(data => {
eventHub.$emit('UpdateWidgetData', data);
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(this.mr.title);
$('.merge-request .detail-page-description .title').text(title);
})
.catch(() => {
.catch(() => createFlash(__('Something went wrong. Please try again.')))
.finally(() => {
this.isMakingRequest = false;
createFlash(__('Something went wrong. Please try again.'));
});
},
handleRemoveWIP() {
if (this.glFeatures.mergeRequestWidgetGraphql) {
this.removeWipMutation();
} else {
this.isMakingRequest = true;
this.service
.removeWIP()
.then(res => res.data)
.then(data => {
eventHub.$emit('UpdateWidgetData', data);
createFlash(__('The merge request can now be merged.'), 'notice');
$('.merge-request .detail-page-description .title').text(this.mr.title);
})
.catch(() => {
this.isMakingRequest = false;
createFlash(__('Something went wrong. Please try again.'));
});
}
},
},
};
</script>
<template>
<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="gl-ml-3 float-left">
<span class="gl-font-weight-bold">
......@@ -58,7 +152,7 @@ export default {
}}</span>
</div>
<gl-button
v-if="mr.removeWIPPath"
v-if="canUpdate"
size="small"
:disabled="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';
import Project from '~/pages/projects/project';
import SmartInterval from '~/smart_interval';
import createFlash from '../flash';
import mergeRequestQueryVariablesMixin from './mixins/merge_request_query_variables';
import Loading from './components/loading.vue';
import WidgetHeader from './components/mr_widget_header.vue';
import WidgetSuggestPipeline from './components/mr_widget_suggest_pipeline.vue';
......@@ -42,6 +43,7 @@ import GroupedCodequalityReportsApp from '../reports/codequality_report/grouped_
import GroupedTestReportsApp from '../reports/components/grouped_test_reports_app.vue';
import { setFaviconOverlay } from '../lib/utils/common_utils';
import GroupedAccessibilityReportsApp from '../reports/accessibility_report/grouped_accessibility_reports_app.vue';
import getStateQuery from './queries/get_state.query.graphql';
export default {
el: '#js-vue-mr-widget',
......@@ -83,6 +85,27 @@ export default {
GroupedAccessibilityReportsApp,
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: {
mrData: {
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';
export default function deviseState(data) {
if (data.project_archived) {
export default function deviseState() {
if (this.projectArchived) {
return stateKey.archived;
} else if (data.branch_missing) {
} else if (this.branchMissing) {
return stateKey.missingBranch;
} else if (!data.commits_count) {
} else if (!this.commitsCount) {
return stateKey.nothingToMerge;
} else if (this.mergeStatus === 'unchecked' || this.mergeStatus === 'checking') {
return stateKey.checking;
} else if (data.has_conflicts) {
} else if (this.hasConflicts) {
return stateKey.conflicts;
} else if (this.shouldBeRebased) {
return stateKey.rebase;
} else if (this.onlyAllowMergeIfPipelineSucceeds && this.isPipelineFailed) {
return stateKey.pipelineFailed;
} else if (data.work_in_progress) {
} else if (this.workInProgress) {
return stateKey.workInProgress;
} else if (this.hasMergeableDiscussionsState) {
return stateKey.unresolvedDiscussions;
......
......@@ -60,6 +60,9 @@ export default class MergeRequestStore {
this.rebaseInProgress = data.rebase_in_progress;
this.mergeRequestDiffsPath = data.diffs_path;
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) {
const links = data.issues_links;
......@@ -90,7 +93,8 @@ export default class MergeRequestStore {
this.ffOnlyEnabled = data.ff_only_enabled;
this.shouldBeRebased = Boolean(data.should_be_rebased);
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.isSHAMismatch = this.sha !== data.diff_head_sha;
this.latestSHA = data.diff_head_sha;
......@@ -133,6 +137,10 @@ export default class MergeRequestStore {
this.mergeCommitPath = data.merge_commit_path;
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;
this.cherryPickInForkPath = currentUser.cherry_pick_in_fork_path;
......@@ -143,19 +151,25 @@ export default class MergeRequestStore {
this.canCherryPickInCurrentMR = currentUser.can_cherry_pick_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) {
this.state = 'merging';
return;
}
if (this.isOpen) {
this.state = getStateKey.call(this, data);
this.state = getStateKey.call(this);
} else {
switch (data.state) {
switch (this.mergeRequestState) {
case 'merged':
this.state = 'merged';
break;
......
......@@ -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(: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(:merge_request_widget_graphql, @project)
end
before_action do
......
import CEGetStateKey from '~/vue_merge_request_widget/stores/get_state_key';
import { stateKey } from './state_maps';
export default function(data) {
export default function() {
if (this.isGeoSecondaryNode) {
return 'geoSecondaryNode';
}
if (data.policy_violation) {
if (this.policyViolation) {
return stateKey.policyViolation;
}
return CEGetStateKey.call(this, data);
return CEGetStateKey.call(this);
}
......@@ -44,6 +44,7 @@ export default class MergeRequestStore extends CEMergeRequestStore {
this.mergePipelinesEnabled = Boolean(data.merge_pipelines_enabled);
this.mergeTrainsCount = data.merge_trains_count || 0;
this.mergeTrainIndex = data.merge_train_index;
this.policyViolation = data.policy_violation;
super.setData(data, isRebased);
}
......
......@@ -11,15 +11,13 @@ describe('getStateKey', () => {
hasMergeableDiscussionsState: false,
isPipelineBlocked: false,
canBeMerged: false,
projectArchived: false,
branchMissing: false,
commitsCount: 2,
hasConflicts: false,
workInProgress: false,
};
const data = {
project_archived: false,
branch_missing: false,
commits_count: 2,
has_conflicts: false,
work_in_progress: false,
};
const bound = getStateKey.bind(context, data);
const bound = getStateKey.bind(context);
expect(bound()).toEqual(null);
......@@ -49,7 +47,7 @@ describe('getStateKey', () => {
expect(bound()).toEqual('unresolvedDiscussions');
data.work_in_progress = true;
context.workInProgress = true;
expect(bound()).toEqual('workInProgress');
......@@ -62,7 +60,7 @@ describe('getStateKey', () => {
expect(bound()).toEqual('rebase');
data.has_conflicts = true;
context.hasConflicts = true;
expect(bound()).toEqual('conflicts');
......@@ -70,15 +68,15 @@ describe('getStateKey', () => {
expect(bound()).toEqual('checking');
data.commits_count = 0;
context.commitsCount = 0;
expect(bound()).toEqual('nothingToMerge');
data.branch_missing = true;
context.branchMissing = true;
expect(bound()).toEqual('missingBranch');
data.project_archived = true;
context.projectArchived = true;
expect(bound()).toEqual('archived');
});
......@@ -94,15 +92,13 @@ describe('getStateKey', () => {
isPipelineBlocked: false,
canBeMerged: false,
shouldBeRebased: true,
projectArchived: false,
branchMissing: false,
commitsCount: 2,
hasConflicts: false,
workInProgress: false,
};
const data = {
project_archived: false,
branch_missing: false,
commits_count: 2,
has_conflicts: false,
work_in_progress: false,
};
const bound = getStateKey.bind(context, data);
const bound = getStateKey.bind(context);
expect(bound()).toEqual('rebase');
});
......@@ -115,15 +111,11 @@ describe('getStateKey', () => {
`(
'returns $stateKey when canMerge is $canMerge and isSHAMismatch is $isSHAMismatch',
({ canMerge, isSHAMismatch, stateKey }) => {
const bound = getStateKey.bind(
{
canMerge,
isSHAMismatch,
},
{
commits_count: 2,
},
);
const bound = getStateKey.bind({
canMerge,
isSHAMismatch,
commitsCount: 2,
});
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