Commit d232ebae authored by Natalia Tepluhina's avatar Natalia Tepluhina

Merge branch 'ph/statusBoxGlobalStatePOC' into 'master'

Connects merge request status to whole page

See merge request gitlab-org/gitlab!51719
parents 07f277b8 a4d112fe
<script> <script>
import { GlIcon } from '@gitlab/ui'; import { GlIcon } from '@gitlab/ui';
import Vue from 'vue';
import { fetchPolicies } from '~/lib/graphql';
import { __ } from '~/locale'; import { __ } from '~/locale';
import mrEventHub from '../eventhub';
export const statusBoxState = Vue.observable({
state: '',
updateStatus: null,
});
const CLASSES = { const CLASSES = {
opened: 'status-box-open', opened: 'status-box-open',
...@@ -21,37 +27,63 @@ export default { ...@@ -21,37 +27,63 @@ export default {
components: { components: {
GlIcon, GlIcon,
}, },
inject: {
query: { default: null },
projectPath: { default: null },
iid: { default: null },
},
props: { props: {
initialState: { initialState: {
type: String, type: String,
required: true, required: false,
default: null,
},
issuableType: {
type: String,
required: false,
default: '',
}, },
}, },
data() { data() {
return { if (this.initialState) {
state: this.initialState, statusBoxState.state = this.initialState;
}; }
return statusBoxState;
}, },
computed: { computed: {
statusBoxClass() { statusBoxClass() {
return CLASSES[this.state]; return CLASSES[`${this.issuableType}_${this.state}`] || CLASSES[this.state];
}, },
statusHumanName() { statusHumanName() {
return STATUS[this.state][0]; return (STATUS[`${this.issuableType}_${this.state}`] || STATUS[this.state])[0];
}, },
statusIconName() { statusIconName() {
return STATUS[this.state][1]; return (STATUS[`${this.issuableType}_${this.state}`] || STATUS[this.state])[1];
}, },
}, },
created() { created() {
mrEventHub.$on('mr.state.updated', this.updateState); if (!statusBoxState.updateStatus) {
statusBoxState.updateStatus = this.fetchState;
}
}, },
beforeDestroy() { beforeDestroy() {
mrEventHub.$off('mr.state.updated', this.updateState); if (statusBoxState.updateStatus && this.query) {
statusBoxState.updateStatus = null;
}
}, },
methods: { methods: {
updateState({ state }) { async fetchState() {
this.state = state; const { data } = await this.$apollo.query({
query: this.query,
variables: {
projectPath: this.projectPath,
iid: this.iid,
},
fetchPolicy: fetchPolicies.NO_CACHE,
});
statusBoxState.state = data?.workspace?.issuable?.state;
}, },
}, },
}; };
......
import createEventHub from '~/helpers/event_hub_factory';
export default createEventHub();
...@@ -15,6 +15,7 @@ import { mapActions, mapGetters, mapState } from 'vuex'; ...@@ -15,6 +15,7 @@ import { mapActions, mapGetters, mapState } from 'vuex';
import Autosave from '~/autosave'; import Autosave from '~/autosave';
import { refreshUserMergeRequestCounts } from '~/commons/nav/user_merge_requests'; import { refreshUserMergeRequestCounts } from '~/commons/nav/user_merge_requests';
import { deprecatedCreateFlash as Flash } from '~/flash'; import { deprecatedCreateFlash as Flash } from '~/flash';
import { statusBoxState } from '~/issuable/components/status_box.vue';
import httpStatusCodes from '~/lib/utils/http_status'; import httpStatusCodes from '~/lib/utils/http_status';
import { import {
capitalizeFirstCharacter, capitalizeFirstCharacter,
...@@ -162,7 +163,7 @@ export default { ...@@ -162,7 +163,7 @@ export default {
canToggleIssueState() { canToggleIssueState() {
return ( return (
this.getNoteableData.current_user.can_update && this.getNoteableData.current_user.can_update &&
this.getNoteableData.state !== constants.MERGED && this.openState !== constants.MERGED &&
!this.closedAndLocked !this.closedAndLocked
); );
}, },
...@@ -283,6 +284,7 @@ export default { ...@@ -283,6 +284,7 @@ export default {
const toggleState = this.isOpen ? this.closeIssuable : this.reopenIssuable; const toggleState = this.isOpen ? this.closeIssuable : this.reopenIssuable;
toggleState() toggleState()
.then(() => statusBoxState.updateStatus && statusBoxState.updateStatus())
.then(refreshUserMergeRequestCounts) .then(refreshUserMergeRequestCounts)
.catch(() => Flash(constants.toggleStateErrorMessage[this.noteableType][this.openState])); .catch(() => Flash(constants.toggleStateErrorMessage[this.noteableType][this.openState]));
}, },
......
import { flattenDeep, clone } from 'lodash'; import { flattenDeep, clone } from 'lodash';
import { statusBoxState } from '~/issuable/components/status_box.vue';
import { isInMRPage } from '~/lib/utils/common_utils';
import * as constants from '../constants'; import * as constants from '../constants';
import { collapseSystemNotes } from './collapse_utils'; import { collapseSystemNotes } from './collapse_utils';
...@@ -82,7 +84,8 @@ export const getBlockedByIssues = (state) => state.noteableData.blocked_by_issue ...@@ -82,7 +84,8 @@ export const getBlockedByIssues = (state) => state.noteableData.blocked_by_issue
export const userCanReply = (state) => Boolean(state.noteableData.current_user.can_create_note); export const userCanReply = (state) => Boolean(state.noteableData.current_user.can_create_note);
export const openState = (state) => state.noteableData.state; export const openState = (state) =>
isInMRPage() ? statusBoxState.state : state.noteableData.state;
export const getUserData = (state) => state.userData || {}; export const getUserData = (state) => state.userData || {};
......
import Vue from 'vue'; import Vue from 'vue';
import VueApollo from 'vue-apollo';
import loadAwardsHandler from '~/awards_handler'; import loadAwardsHandler from '~/awards_handler';
import ShortcutsIssuable from '~/behaviors/shortcuts/shortcuts_issuable'; import ShortcutsIssuable from '~/behaviors/shortcuts/shortcuts_issuable';
import initPipelines from '~/commit/pipelines/pipelines_bundle'; import initPipelines from '~/commit/pipelines/pipelines_bundle';
import initIssuableSidebar from '~/init_issuable_sidebar'; import initIssuableSidebar from '~/init_issuable_sidebar';
import initInviteMembersModal from '~/invite_members/init_invite_members_modal'; import initInviteMembersModal from '~/invite_members/init_invite_members_modal';
import initInviteMembersTrigger from '~/invite_members/init_invite_members_trigger'; import initInviteMembersTrigger from '~/invite_members/init_invite_members_trigger';
import StatusBox from '~/issuable/components/status_box.vue';
import createDefaultClient from '~/lib/graphql';
import { handleLocationHash } from '~/lib/utils/common_utils'; import { handleLocationHash } from '~/lib/utils/common_utils';
import StatusBox from '~/merge_request/components/status_box.vue';
import initSourcegraph from '~/sourcegraph'; import initSourcegraph from '~/sourcegraph';
import ZenMode from '~/zen_mode'; import ZenMode from '~/zen_mode';
import getStateQuery from './queries/get_state.query.graphql';
export default function initMergeRequestShow() { export default function initMergeRequestShow() {
const awardEmojiEl = document.getElementById('js-vue-awards-block'); const awardEmojiEl = document.getElementById('js-vue-awards-block');
...@@ -30,9 +33,16 @@ export default function initMergeRequestShow() { ...@@ -30,9 +33,16 @@ export default function initMergeRequestShow() {
initInviteMembersTrigger(); initInviteMembersTrigger();
const el = document.querySelector('.js-mr-status-box'); const el = document.querySelector('.js-mr-status-box');
const apolloProvider = new VueApollo({ defaultClient: createDefaultClient() });
// eslint-disable-next-line no-new // eslint-disable-next-line no-new
new Vue({ new Vue({
el, el,
apolloProvider,
provide: {
query: getStateQuery,
projectPath: el.dataset.projectPath,
iid: el.dataset.iid,
},
render(h) { render(h) {
return h(StatusBox, { return h(StatusBox, {
props: { props: {
......
query getMergeRequestState($projectPath: ID!, $iid: String!) {
workspace: project(fullPath: $projectPath) {
issuable: mergeRequest(iid: $iid) {
state
}
}
}
import { format } from 'timeago.js'; import { format } from 'timeago.js';
import getStateKey from 'ee_else_ce/vue_merge_request_widget/stores/get_state_key'; import getStateKey from 'ee_else_ce/vue_merge_request_widget/stores/get_state_key';
import mrEventHub from '~/merge_request/eventhub'; import { statusBoxState } from '~/issuable/components/status_box.vue';
import { formatDate } from '../../lib/utils/datetime_utility'; import { formatDate } from '../../lib/utils/datetime_utility';
import { MTWPS_MERGE_STRATEGY, MT_MERGE_STRATEGY, MWPS_MERGE_STRATEGY } from '../constants'; import { MTWPS_MERGE_STRATEGY, MT_MERGE_STRATEGY, MWPS_MERGE_STRATEGY } from '../constants';
import { stateKey } from './state_maps'; import { stateKey } from './state_maps';
...@@ -23,6 +23,8 @@ export default class MergeRequestStore { ...@@ -23,6 +23,8 @@ export default class MergeRequestStore {
setData(data, isRebased) { setData(data, isRebased) {
this.initApprovals(); this.initApprovals();
this.updateStatusState(data.state);
if (isRebased) { if (isRebased) {
this.sha = data.diff_head_sha; this.sha = data.diff_head_sha;
} }
...@@ -156,16 +158,14 @@ export default class MergeRequestStore { ...@@ -156,16 +158,14 @@ export default class MergeRequestStore {
this.canRevertInCurrentMR = currentUser.can_revert_on_current_merge_request || false; this.canRevertInCurrentMR = currentUser.can_revert_on_current_merge_request || false;
this.setState(); this.setState();
if (!window.gon?.features?.mergeRequestWidgetGraphql) {
this.emitUpdatedState();
}
} }
setGraphqlData(project) { setGraphqlData(project) {
const { mergeRequest } = project; const { mergeRequest } = project;
const pipeline = mergeRequest.headPipeline; const pipeline = mergeRequest.headPipeline;
this.updateStatusState(mergeRequest.state);
this.projectArchived = project.archived; this.projectArchived = project.archived;
this.onlyAllowMergeIfPipelineSucceeds = project.onlyAllowMergeIfPipelineSucceeds; this.onlyAllowMergeIfPipelineSucceeds = project.onlyAllowMergeIfPipelineSucceeds;
...@@ -190,10 +190,15 @@ export default class MergeRequestStore { ...@@ -190,10 +190,15 @@ export default class MergeRequestStore {
this.workInProgress = mergeRequest.workInProgress; this.workInProgress = mergeRequest.workInProgress;
this.mergeRequestState = mergeRequest.state; this.mergeRequestState = mergeRequest.state;
this.emitUpdatedState();
this.setState(); this.setState();
} }
updateStatusState(state) {
if (this.mergeRequestState !== state && statusBoxState.updateStatus) {
statusBoxState.updateStatus();
}
}
setState() { setState() {
if (this.mergeOngoing) { if (this.mergeOngoing) {
this.state = 'merging'; this.state = 'merging';
...@@ -216,12 +221,6 @@ export default class MergeRequestStore { ...@@ -216,12 +221,6 @@ export default class MergeRequestStore {
} }
} }
emitUpdatedState() {
mrEventHub.$emit('mr.state.updated', {
state: this.mergeRequestState,
});
}
setPaths(data) { setPaths(data) {
// Paths are set on the first load of the page and not auto-refreshed // Paths are set on the first load of the page and not auto-refreshed
this.squashBeforeMergeHelpPath = data.squash_before_merge_help_path; this.squashBeforeMergeHelpPath = data.squash_before_merge_help_path;
......
...@@ -332,6 +332,18 @@ module IssuablesHelper ...@@ -332,6 +332,18 @@ module IssuablesHelper
end end
end end
def state_name_with_icon(issuable)
if issuable.is_a?(MergeRequest) && issuable.merged?
[_("Merged"), "git-merge"]
elsif issuable.is_a?(MergeRequest) && issuable.closed?
[_("Closed"), "close"]
elsif issuable.closed?
[_("Closed"), "mobile-issue-close"]
else
[_("Open"), "issue-open-m"]
end
end
private private
def sidebar_gutter_collapsed? def sidebar_gutter_collapsed?
......
...@@ -29,16 +29,6 @@ module MergeRequestsHelper ...@@ -29,16 +29,6 @@ module MergeRequestsHelper
classes.join(' ') classes.join(' ')
end end
def state_name_with_icon(merge_request)
if merge_request.merged?
[_("Merged"), "git-merge"]
elsif merge_request.closed?
[_("Closed"), "close"]
else
[_("Open"), "issue-open-m"]
end
end
def merge_path_description(merge_request, separator) def merge_path_description(merge_request, separator)
if merge_request.for_fork? if merge_request.for_fork?
"Project:Branches: #{@merge_request.source_project_path}:#{@merge_request.source_branch} #{separator} #{@merge_request.target_project.full_path}:#{@merge_request.target_branch}" "Project:Branches: #{@merge_request.source_project_path}:#{@merge_request.source_branch} #{separator} #{@merge_request.target_project.full_path}:#{@merge_request.target_branch}"
......
- @no_breadcrumb_border = true - @no_breadcrumb_border = true
- can_update_merge_request = can?(current_user, :update_merge_request, @merge_request) - can_update_merge_request = can?(current_user, :update_merge_request, @merge_request)
- can_reopen_merge_request = can?(current_user, :reopen_merge_request, @merge_request) - can_reopen_merge_request = can?(current_user, :reopen_merge_request, @merge_request)
- state_human_name, state_icon_name = state_name_with_icon(@merge_request)
- are_close_and_open_buttons_hidden = merge_request_button_hidden?(@merge_request, true) && merge_request_button_hidden?(@merge_request, false) - are_close_and_open_buttons_hidden = merge_request_button_hidden?(@merge_request, true) && merge_request_button_hidden?(@merge_request, false)
- if @merge_request.closed_or_merged_without_fork? - if @merge_request.closed_or_merged_without_fork?
...@@ -12,10 +11,7 @@ ...@@ -12,10 +11,7 @@
.detail-page-header.border-bottom-0.pt-0.pb-0 .detail-page-header.border-bottom-0.pt-0.pb-0
.detail-page-header-body .detail-page-header-body
.issuable-status-box.status-box.js-mr-status-box{ class: status_box_class(@merge_request), data: { state: @merge_request.state } } = render "shared/issuable/status_box", issuable: @merge_request
= sprite_icon(state_icon_name, css_class: 'gl-display-block gl-sm-display-none!')
%span.gl-display-none.gl-sm-display-block
= state_human_name
.issuable-meta .issuable-meta
#js-issuable-header-warnings #js-issuable-header-warnings
......
- state_human_name, state_icon_name = state_name_with_icon(issuable)
.issuable-status-box.status-box.js-mr-status-box{ class: status_box_class(issuable), data: { project_path: issuable.project.path_with_namespace, iid: issuable.iid, state: issuable.state } }
= sprite_icon(state_icon_name, css_class: 'gl-display-block gl-sm-display-none!')
%span.gl-display-none.gl-sm-display-block
= state_human_name
import { GlSprintf } from '@gitlab/ui'; import { GlSprintf } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue'; import StatusBox from '~/issuable/components/status_box.vue';
import StatusBox from '~/merge_request/components/status_box.vue';
import mrEventHub from '~/merge_request/eventhub';
let wrapper; let wrapper;
...@@ -70,18 +68,4 @@ describe('Merge request status box component', () => { ...@@ -70,18 +68,4 @@ describe('Merge request status box component', () => {
}); });
}); });
}); });
it('updates with eventhub event', async () => {
factory({
initialState: 'opened',
});
expect(wrapper.text()).toContain('Open');
mrEventHub.$emit('mr.state.updated', { state: 'closed' });
await nextTick();
expect(wrapper.text()).toContain('Closed');
});
}); });
...@@ -436,6 +436,7 @@ describe('issue_comment_form component', () => { ...@@ -436,6 +436,7 @@ describe('issue_comment_form component', () => {
await findCloseReopenButton().trigger('click'); await findCloseReopenButton().trigger('click');
await wrapper.vm.$nextTick;
await wrapper.vm.$nextTick; await wrapper.vm.$nextTick;
expect(flash).toHaveBeenCalledWith( expect(flash).toHaveBeenCalledWith(
...@@ -471,6 +472,7 @@ describe('issue_comment_form component', () => { ...@@ -471,6 +472,7 @@ describe('issue_comment_form component', () => {
await findCloseReopenButton().trigger('click'); await findCloseReopenButton().trigger('click');
await wrapper.vm.$nextTick;
await wrapper.vm.$nextTick; await wrapper.vm.$nextTick;
expect(flash).toHaveBeenCalledWith( expect(flash).toHaveBeenCalledWith(
...@@ -489,6 +491,8 @@ describe('issue_comment_form component', () => { ...@@ -489,6 +491,8 @@ describe('issue_comment_form component', () => {
await findCloseReopenButton().trigger('click'); await findCloseReopenButton().trigger('click');
await wrapper.vm.$nextTick();
expect(refreshUserMergeRequestCounts).toHaveBeenCalled(); expect(refreshUserMergeRequestCounts).toHaveBeenCalled();
}); });
}); });
......
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