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>
import { GlIcon } from '@gitlab/ui';
import Vue from 'vue';
import { fetchPolicies } from '~/lib/graphql';
import { __ } from '~/locale';
import mrEventHub from '../eventhub';
export const statusBoxState = Vue.observable({
state: '',
updateStatus: null,
});
const CLASSES = {
opened: 'status-box-open',
......@@ -21,37 +27,63 @@ export default {
components: {
GlIcon,
},
inject: {
query: { default: null },
projectPath: { default: null },
iid: { default: null },
},
props: {
initialState: {
type: String,
required: true,
required: false,
default: null,
},
issuableType: {
type: String,
required: false,
default: '',
},
},
data() {
return {
state: this.initialState,
};
if (this.initialState) {
statusBoxState.state = this.initialState;
}
return statusBoxState;
},
computed: {
statusBoxClass() {
return CLASSES[this.state];
return CLASSES[`${this.issuableType}_${this.state}`] || CLASSES[this.state];
},
statusHumanName() {
return STATUS[this.state][0];
return (STATUS[`${this.issuableType}_${this.state}`] || STATUS[this.state])[0];
},
statusIconName() {
return STATUS[this.state][1];
return (STATUS[`${this.issuableType}_${this.state}`] || STATUS[this.state])[1];
},
},
created() {
mrEventHub.$on('mr.state.updated', this.updateState);
if (!statusBoxState.updateStatus) {
statusBoxState.updateStatus = this.fetchState;
}
},
beforeDestroy() {
mrEventHub.$off('mr.state.updated', this.updateState);
if (statusBoxState.updateStatus && this.query) {
statusBoxState.updateStatus = null;
}
},
methods: {
updateState({ state }) {
this.state = state;
async fetchState() {
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';
import Autosave from '~/autosave';
import { refreshUserMergeRequestCounts } from '~/commons/nav/user_merge_requests';
import { deprecatedCreateFlash as Flash } from '~/flash';
import { statusBoxState } from '~/issuable/components/status_box.vue';
import httpStatusCodes from '~/lib/utils/http_status';
import {
capitalizeFirstCharacter,
......@@ -162,7 +163,7 @@ export default {
canToggleIssueState() {
return (
this.getNoteableData.current_user.can_update &&
this.getNoteableData.state !== constants.MERGED &&
this.openState !== constants.MERGED &&
!this.closedAndLocked
);
},
......@@ -283,6 +284,7 @@ export default {
const toggleState = this.isOpen ? this.closeIssuable : this.reopenIssuable;
toggleState()
.then(() => statusBoxState.updateStatus && statusBoxState.updateStatus())
.then(refreshUserMergeRequestCounts)
.catch(() => Flash(constants.toggleStateErrorMessage[this.noteableType][this.openState]));
},
......
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 { collapseSystemNotes } from './collapse_utils';
......@@ -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 openState = (state) => state.noteableData.state;
export const openState = (state) =>
isInMRPage() ? statusBoxState.state : state.noteableData.state;
export const getUserData = (state) => state.userData || {};
......
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import loadAwardsHandler from '~/awards_handler';
import ShortcutsIssuable from '~/behaviors/shortcuts/shortcuts_issuable';
import initPipelines from '~/commit/pipelines/pipelines_bundle';
import initIssuableSidebar from '~/init_issuable_sidebar';
import initInviteMembersModal from '~/invite_members/init_invite_members_modal';
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 StatusBox from '~/merge_request/components/status_box.vue';
import initSourcegraph from '~/sourcegraph';
import ZenMode from '~/zen_mode';
import getStateQuery from './queries/get_state.query.graphql';
export default function initMergeRequestShow() {
const awardEmojiEl = document.getElementById('js-vue-awards-block');
......@@ -30,9 +33,16 @@ export default function initMergeRequestShow() {
initInviteMembersTrigger();
const el = document.querySelector('.js-mr-status-box');
const apolloProvider = new VueApollo({ defaultClient: createDefaultClient() });
// eslint-disable-next-line no-new
new Vue({
el,
apolloProvider,
provide: {
query: getStateQuery,
projectPath: el.dataset.projectPath,
iid: el.dataset.iid,
},
render(h) {
return h(StatusBox, {
props: {
......
query getMergeRequestState($projectPath: ID!, $iid: String!) {
workspace: project(fullPath: $projectPath) {
issuable: mergeRequest(iid: $iid) {
state
}
}
}
import { format } from 'timeago.js';
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 { MTWPS_MERGE_STRATEGY, MT_MERGE_STRATEGY, MWPS_MERGE_STRATEGY } from '../constants';
import { stateKey } from './state_maps';
......@@ -23,6 +23,8 @@ export default class MergeRequestStore {
setData(data, isRebased) {
this.initApprovals();
this.updateStatusState(data.state);
if (isRebased) {
this.sha = data.diff_head_sha;
}
......@@ -156,16 +158,14 @@ export default class MergeRequestStore {
this.canRevertInCurrentMR = currentUser.can_revert_on_current_merge_request || false;
this.setState();
if (!window.gon?.features?.mergeRequestWidgetGraphql) {
this.emitUpdatedState();
}
}
setGraphqlData(project) {
const { mergeRequest } = project;
const pipeline = mergeRequest.headPipeline;
this.updateStatusState(mergeRequest.state);
this.projectArchived = project.archived;
this.onlyAllowMergeIfPipelineSucceeds = project.onlyAllowMergeIfPipelineSucceeds;
......@@ -190,10 +190,15 @@ export default class MergeRequestStore {
this.workInProgress = mergeRequest.workInProgress;
this.mergeRequestState = mergeRequest.state;
this.emitUpdatedState();
this.setState();
}
updateStatusState(state) {
if (this.mergeRequestState !== state && statusBoxState.updateStatus) {
statusBoxState.updateStatus();
}
}
setState() {
if (this.mergeOngoing) {
this.state = 'merging';
......@@ -216,12 +221,6 @@ export default class MergeRequestStore {
}
}
emitUpdatedState() {
mrEventHub.$emit('mr.state.updated', {
state: this.mergeRequestState,
});
}
setPaths(data) {
// Paths are set on the first load of the page and not auto-refreshed
this.squashBeforeMergeHelpPath = data.squash_before_merge_help_path;
......
......@@ -332,6 +332,18 @@ module IssuablesHelper
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
def sidebar_gutter_collapsed?
......
......@@ -29,16 +29,6 @@ module MergeRequestsHelper
classes.join(' ')
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)
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}"
......
- @no_breadcrumb_border = true
- can_update_merge_request = can?(current_user, :update_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)
- if @merge_request.closed_or_merged_without_fork?
......@@ -12,10 +11,7 @@
.detail-page-header.border-bottom-0.pt-0.pb-0
.detail-page-header-body
.issuable-status-box.status-box.js-mr-status-box{ class: status_box_class(@merge_request), data: { state: @merge_request.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
= render "shared/issuable/status_box", issuable: @merge_request
.issuable-meta
#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 { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import StatusBox from '~/merge_request/components/status_box.vue';
import mrEventHub from '~/merge_request/eventhub';
import StatusBox from '~/issuable/components/status_box.vue';
let wrapper;
......@@ -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', () => {
await findCloseReopenButton().trigger('click');
await wrapper.vm.$nextTick;
await wrapper.vm.$nextTick;
expect(flash).toHaveBeenCalledWith(
......@@ -471,6 +472,7 @@ describe('issue_comment_form component', () => {
await findCloseReopenButton().trigger('click');
await wrapper.vm.$nextTick;
await wrapper.vm.$nextTick;
expect(flash).toHaveBeenCalledWith(
......@@ -489,6 +491,8 @@ describe('issue_comment_form component', () => {
await findCloseReopenButton().trigger('click');
await wrapper.vm.$nextTick();
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