Backport changes from refactor sidebar weight block Vue and move to Issue Boards
See https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3566
This commit is contained in:
parent
e0f8413056
commit
f3433d8a68
|
@ -20,6 +20,7 @@ class ListIssue {
|
|||
this.isFetching = {
|
||||
subscriptions: true,
|
||||
};
|
||||
this.isLoading = {};
|
||||
this.sidebarInfoEndpoint = obj.issue_sidebar_endpoint;
|
||||
this.toggleSubscriptionEndpoint = obj.toggle_subscription_endpoint;
|
||||
|
||||
|
@ -86,6 +87,10 @@ class ListIssue {
|
|||
this.isFetching[key] = value;
|
||||
}
|
||||
|
||||
setLoadingState(key, value) {
|
||||
this.isLoading[key] = value;
|
||||
}
|
||||
|
||||
update (url) {
|
||||
const data = {
|
||||
issue: {
|
||||
|
|
|
@ -514,10 +514,11 @@ GitLabDropdown = (function() {
|
|||
|
||||
const dropdownToggle = this.dropdown.find('.dropdown-menu-toggle');
|
||||
const hasFilterBulkUpdate = dropdownToggle.hasClass('js-filter-bulk-update');
|
||||
const shouldRefreshOnOpen = dropdownToggle.hasClass('js-gl-dropdown-refresh-on-open');
|
||||
const hasMultiSelect = dropdownToggle.hasClass('js-multiselect');
|
||||
|
||||
// Makes indeterminate items effective
|
||||
if (this.fullData && hasFilterBulkUpdate) {
|
||||
if (this.fullData && (shouldRefreshOnOpen || hasFilterBulkUpdate)) {
|
||||
this.parseData(this.fullData);
|
||||
}
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ import Cookies from 'js-cookie';
|
|||
|
||||
Sidebar.prototype.removeListeners = function () {
|
||||
this.sidebar.off('click', '.sidebar-collapsed-icon');
|
||||
$('.dropdown').off('hidden.gl.dropdown');
|
||||
this.sidebar.off('hidden.gl.dropdown');
|
||||
$('.dropdown').off('loading.gl.dropdown');
|
||||
$('.dropdown').off('loaded.gl.dropdown');
|
||||
$(document).off('click', '.js-sidebar-toggle');
|
||||
|
@ -25,7 +25,7 @@ import Cookies from 'js-cookie';
|
|||
const $document = $(document);
|
||||
|
||||
this.sidebar.on('click', '.sidebar-collapsed-icon', this, this.sidebarCollapseClicked);
|
||||
$('.dropdown').on('hidden.gl.dropdown', this, this.onSidebarDropdownHidden);
|
||||
this.sidebar.on('hidden.gl.dropdown', this, this.onSidebarDropdownHidden);
|
||||
$('.dropdown').on('loading.gl.dropdown', this.sidebarDropdownLoading);
|
||||
$('.dropdown').on('loaded.gl.dropdown', this.sidebarDropdownLoaded);
|
||||
|
||||
|
@ -180,7 +180,7 @@ import Cookies from 'js-cookie';
|
|||
var $block, sidebar;
|
||||
sidebar = e.data;
|
||||
e.preventDefault();
|
||||
$block = $(this).closest('.block');
|
||||
$block = $(e.target).closest('.block');
|
||||
return sidebar.sidebarDropdownHidden($block);
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
import Vue from 'vue';
|
||||
import SidebarTimeTracking from './components/time_tracking/sidebar_time_tracking';
|
||||
import SidebarAssignees from './components/assignees/sidebar_assignees';
|
||||
import ConfidentialIssueSidebar from './components/confidential/confidential_issue_sidebar.vue';
|
||||
import SidebarMoveIssue from './lib/sidebar_move_issue';
|
||||
import LockIssueSidebar from './components/lock/lock_issue_sidebar.vue';
|
||||
import sidebarParticipants from './components/participants/sidebar_participants.vue';
|
||||
import sidebarSubscriptions from './components/subscriptions/sidebar_subscriptions.vue';
|
||||
import Translate from '../vue_shared/translate';
|
||||
|
||||
Vue.use(Translate);
|
||||
|
||||
function mountConfidentialComponent(mediator) {
|
||||
const el = document.getElementById('js-confidential-entry-point');
|
||||
|
||||
if (!el) return;
|
||||
|
||||
const dataNode = document.getElementById('js-confidential-issue-data');
|
||||
const initialData = JSON.parse(dataNode.innerHTML);
|
||||
|
||||
const ConfidentialComp = Vue.extend(ConfidentialIssueSidebar);
|
||||
|
||||
new ConfidentialComp({
|
||||
propsData: {
|
||||
isConfidential: initialData.is_confidential,
|
||||
isEditable: initialData.is_editable,
|
||||
service: mediator.service,
|
||||
},
|
||||
}).$mount(el);
|
||||
}
|
||||
|
||||
function mountLockComponent(mediator) {
|
||||
const el = document.getElementById('js-lock-entry-point');
|
||||
|
||||
if (!el) return;
|
||||
|
||||
const dataNode = document.getElementById('js-lock-issue-data');
|
||||
const initialData = JSON.parse(dataNode.innerHTML);
|
||||
|
||||
const LockComp = Vue.extend(LockIssueSidebar);
|
||||
|
||||
new LockComp({
|
||||
propsData: {
|
||||
isLocked: initialData.is_locked,
|
||||
isEditable: initialData.is_editable,
|
||||
mediator,
|
||||
issuableType: gl.utils.isInIssuePage() ? 'issue' : 'merge_request',
|
||||
},
|
||||
}).$mount(el);
|
||||
}
|
||||
|
||||
function mountParticipantsComponent() {
|
||||
const el = document.querySelector('.js-sidebar-participants-entry-point');
|
||||
|
||||
if (!el) return;
|
||||
|
||||
// eslint-disable-next-line no-new
|
||||
new Vue({
|
||||
el,
|
||||
components: {
|
||||
sidebarParticipants,
|
||||
},
|
||||
render: createElement => createElement('sidebar-participants', {}),
|
||||
});
|
||||
}
|
||||
|
||||
function mountSubscriptionsComponent() {
|
||||
const el = document.querySelector('.js-sidebar-subscriptions-entry-point');
|
||||
|
||||
if (!el) return;
|
||||
|
||||
// eslint-disable-next-line no-new
|
||||
new Vue({
|
||||
el,
|
||||
components: {
|
||||
sidebarSubscriptions,
|
||||
},
|
||||
render: createElement => createElement('sidebar-subscriptions', {}),
|
||||
});
|
||||
}
|
||||
|
||||
function mount(mediator) {
|
||||
const sidebarAssigneesEl = document.getElementById('js-vue-sidebar-assignees');
|
||||
// Only create the sidebarAssignees vue app if it is found in the DOM
|
||||
// We currently do not use sidebarAssignees for the MR page
|
||||
if (sidebarAssigneesEl) {
|
||||
new Vue(SidebarAssignees).$mount(sidebarAssigneesEl);
|
||||
}
|
||||
|
||||
mountConfidentialComponent(mediator);
|
||||
mountLockComponent(mediator);
|
||||
mountParticipantsComponent();
|
||||
mountSubscriptionsComponent();
|
||||
|
||||
new SidebarMoveIssue(
|
||||
mediator,
|
||||
$('.js-move-issue'),
|
||||
$('.js-move-issue-confirmation-button'),
|
||||
).init();
|
||||
|
||||
new Vue(SidebarTimeTracking).$mount('#issuable-time-tracker');
|
||||
}
|
||||
|
||||
export default mount;
|
|
@ -1,110 +1,12 @@
|
|||
import Vue from 'vue';
|
||||
import SidebarTimeTracking from './components/time_tracking/sidebar_time_tracking';
|
||||
import SidebarAssignees from './components/assignees/sidebar_assignees';
|
||||
import ConfidentialIssueSidebar from './components/confidential/confidential_issue_sidebar.vue';
|
||||
import SidebarMoveIssue from './lib/sidebar_move_issue';
|
||||
import LockIssueSidebar from './components/lock/lock_issue_sidebar.vue';
|
||||
import sidebarParticipants from './components/participants/sidebar_participants.vue';
|
||||
import sidebarSubscriptions from './components/subscriptions/sidebar_subscriptions.vue';
|
||||
import Translate from '../vue_shared/translate';
|
||||
|
||||
import Mediator from './sidebar_mediator';
|
||||
|
||||
Vue.use(Translate);
|
||||
|
||||
function mountConfidentialComponent(mediator) {
|
||||
const el = document.getElementById('js-confidential-entry-point');
|
||||
|
||||
if (!el) return;
|
||||
|
||||
const dataNode = document.getElementById('js-confidential-issue-data');
|
||||
const initialData = JSON.parse(dataNode.innerHTML);
|
||||
|
||||
const ConfidentialComp = Vue.extend(ConfidentialIssueSidebar);
|
||||
|
||||
new ConfidentialComp({
|
||||
propsData: {
|
||||
isConfidential: initialData.is_confidential,
|
||||
isEditable: initialData.is_editable,
|
||||
service: mediator.service,
|
||||
},
|
||||
}).$mount(el);
|
||||
}
|
||||
|
||||
function mountLockComponent(mediator) {
|
||||
const el = document.getElementById('js-lock-entry-point');
|
||||
|
||||
if (!el) return;
|
||||
|
||||
const dataNode = document.getElementById('js-lock-issue-data');
|
||||
const initialData = JSON.parse(dataNode.innerHTML);
|
||||
|
||||
const LockComp = Vue.extend(LockIssueSidebar);
|
||||
|
||||
new LockComp({
|
||||
propsData: {
|
||||
isLocked: initialData.is_locked,
|
||||
isEditable: initialData.is_editable,
|
||||
mediator,
|
||||
issuableType: gl.utils.isInIssuePage() ? 'issue' : 'merge_request',
|
||||
},
|
||||
}).$mount(el);
|
||||
}
|
||||
|
||||
function mountParticipantsComponent() {
|
||||
const el = document.querySelector('.js-sidebar-participants-entry-point');
|
||||
|
||||
if (!el) return;
|
||||
|
||||
// eslint-disable-next-line no-new
|
||||
new Vue({
|
||||
el,
|
||||
components: {
|
||||
sidebarParticipants,
|
||||
},
|
||||
render: createElement => createElement('sidebar-participants', {}),
|
||||
});
|
||||
}
|
||||
|
||||
function mountSubscriptionsComponent() {
|
||||
const el = document.querySelector('.js-sidebar-subscriptions-entry-point');
|
||||
|
||||
if (!el) return;
|
||||
|
||||
// eslint-disable-next-line no-new
|
||||
new Vue({
|
||||
el,
|
||||
components: {
|
||||
sidebarSubscriptions,
|
||||
},
|
||||
render: createElement => createElement('sidebar-subscriptions', {}),
|
||||
});
|
||||
}
|
||||
import mountSidebar from './mount_sidebar';
|
||||
|
||||
function domContentLoaded() {
|
||||
const sidebarOptions = JSON.parse(document.querySelector('.js-sidebar-options').innerHTML);
|
||||
const mediator = new Mediator(sidebarOptions);
|
||||
mediator.fetch();
|
||||
|
||||
const sidebarAssigneesEl = document.getElementById('js-vue-sidebar-assignees');
|
||||
// Only create the sidebarAssignees vue app if it is found in the DOM
|
||||
// We currently do not use sidebarAssignees for the MR page
|
||||
if (sidebarAssigneesEl) {
|
||||
new Vue(SidebarAssignees).$mount(sidebarAssigneesEl);
|
||||
}
|
||||
|
||||
mountConfidentialComponent(mediator);
|
||||
mountLockComponent(mediator);
|
||||
mountParticipantsComponent();
|
||||
mountSubscriptionsComponent();
|
||||
|
||||
new SidebarMoveIssue(
|
||||
mediator,
|
||||
$('.js-move-issue'),
|
||||
$('.js-move-issue-confirmation-button'),
|
||||
).init();
|
||||
|
||||
new Vue(SidebarTimeTracking).$mount('#issuable-time-tracker');
|
||||
mountSidebar(mediator);
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', domContentLoaded);
|
||||
|
|
|
@ -5,19 +5,23 @@ import Store from './stores/sidebar_store';
|
|||
export default class SidebarMediator {
|
||||
constructor(options) {
|
||||
if (!SidebarMediator.singleton) {
|
||||
this.store = new Store(options);
|
||||
this.service = new Service({
|
||||
endpoint: options.endpoint,
|
||||
toggleSubscriptionEndpoint: options.toggleSubscriptionEndpoint,
|
||||
moveIssueEndpoint: options.moveIssueEndpoint,
|
||||
projectsAutocompleteEndpoint: options.projectsAutocompleteEndpoint,
|
||||
});
|
||||
SidebarMediator.singleton = this;
|
||||
this.initSingleton(options);
|
||||
}
|
||||
|
||||
return SidebarMediator.singleton;
|
||||
}
|
||||
|
||||
initSingleton(options) {
|
||||
this.store = new Store(options);
|
||||
this.service = new Service({
|
||||
endpoint: options.endpoint,
|
||||
toggleSubscriptionEndpoint: options.toggleSubscriptionEndpoint,
|
||||
moveIssueEndpoint: options.moveIssueEndpoint,
|
||||
projectsAutocompleteEndpoint: options.projectsAutocompleteEndpoint,
|
||||
});
|
||||
SidebarMediator.singleton = this;
|
||||
}
|
||||
|
||||
assignYourself() {
|
||||
this.store.addAssignee(this.store.currentUser);
|
||||
}
|
||||
|
@ -35,17 +39,21 @@ export default class SidebarMediator {
|
|||
}
|
||||
|
||||
fetch() {
|
||||
this.service.get()
|
||||
return this.service.get()
|
||||
.then(response => response.json())
|
||||
.then((data) => {
|
||||
this.store.setAssigneeData(data);
|
||||
this.store.setTimeTrackingData(data);
|
||||
this.store.setParticipantsData(data);
|
||||
this.store.setSubscriptionsData(data);
|
||||
this.processFetchedData(data);
|
||||
})
|
||||
.catch(() => new Flash('Error occurred when fetching sidebar data'));
|
||||
}
|
||||
|
||||
processFetchedData(data) {
|
||||
this.store.setAssigneeData(data);
|
||||
this.store.setTimeTrackingData(data);
|
||||
this.store.setParticipantsData(data);
|
||||
this.store.setSubscriptionsData(data);
|
||||
}
|
||||
|
||||
toggleSubscription() {
|
||||
this.store.setFetchingState('subscriptions', true);
|
||||
return this.service.toggleSubscription()
|
||||
|
|
|
@ -15,6 +15,7 @@ export default class SidebarStore {
|
|||
participants: true,
|
||||
subscriptions: true,
|
||||
};
|
||||
this.isLoading = {};
|
||||
this.autocompleteProjects = [];
|
||||
this.moveToProjectId = 0;
|
||||
this.isLockDialogOpen = false;
|
||||
|
@ -55,6 +56,10 @@ export default class SidebarStore {
|
|||
this.isFetching[key] = value;
|
||||
}
|
||||
|
||||
setLoadingState(key, value) {
|
||||
this.isLoading[key] = value;
|
||||
}
|
||||
|
||||
addAssignee(assignee) {
|
||||
if (!this.findAssignee(assignee)) {
|
||||
this.assignees.push(assignee);
|
||||
|
|
|
@ -146,6 +146,12 @@ describe('Issue model', () => {
|
|||
expect(issue.isFetching.subscriptions).toBe(false);
|
||||
});
|
||||
|
||||
it('sets loading state', () => {
|
||||
issue.setLoadingState('foo', true);
|
||||
|
||||
expect(issue.isLoading.foo).toBe(true);
|
||||
});
|
||||
|
||||
describe('update', () => {
|
||||
it('passes assignee ids when there are assignees', (done) => {
|
||||
spyOn(Vue.http, 'patch').and.callFake((url, data) => {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/* eslint-disable quote-props*/
|
||||
|
||||
const sidebarMockData = {
|
||||
const RESPONSE_MAP = {
|
||||
'GET': {
|
||||
'/gitlab-org/gitlab-shell/issues/5.json': {
|
||||
id: 45,
|
||||
|
@ -66,6 +66,65 @@ const sidebarMockData = {
|
|||
},
|
||||
labels: [],
|
||||
},
|
||||
'/gitlab-org/gitlab-shell/issues/5.json?serializer=sidebar': {
|
||||
assignees: [
|
||||
{
|
||||
name: 'User 0',
|
||||
username: 'user0',
|
||||
id: 22,
|
||||
state: 'active',
|
||||
avatar_url: 'http: //www.gravatar.com/avatar/52e4ce24a915fb7e51e1ad3b57f4b00a?s=80\u0026d=identicon',
|
||||
web_url: 'http: //localhost:3001/user0',
|
||||
},
|
||||
{
|
||||
name: 'Marguerite Bartell',
|
||||
username: 'tajuana',
|
||||
id: 18,
|
||||
state: 'active',
|
||||
avatar_url: 'http: //www.gravatar.com/avatar/4852a41fb41616bf8f140d3701673f53?s=80\u0026d=identicon',
|
||||
web_url: 'http: //localhost:3001/tajuana',
|
||||
},
|
||||
{
|
||||
name: 'Laureen Ritchie',
|
||||
username: 'michaele.will',
|
||||
id: 16,
|
||||
state: 'active',
|
||||
avatar_url: 'http: //www.gravatar.com/avatar/e301827eb03be955c9c172cb9a8e4e8a?s=80\u0026d=identicon',
|
||||
web_url: 'http: //localhost:3001/michaele.will',
|
||||
},
|
||||
],
|
||||
human_time_estimate: null,
|
||||
human_total_time_spent: null,
|
||||
participants: [
|
||||
{
|
||||
name: 'User 0',
|
||||
username: 'user0',
|
||||
id: 22,
|
||||
state: 'active',
|
||||
avatar_url: 'http: //www.gravatar.com/avatar/52e4ce24a915fb7e51e1ad3b57f4b00a?s=80\u0026d=identicon',
|
||||
web_url: 'http: //localhost:3001/user0',
|
||||
},
|
||||
{
|
||||
name: 'Marguerite Bartell',
|
||||
username: 'tajuana',
|
||||
id: 18,
|
||||
state: 'active',
|
||||
avatar_url: 'http: //www.gravatar.com/avatar/4852a41fb41616bf8f140d3701673f53?s=80\u0026d=identicon',
|
||||
web_url: 'http: //localhost:3001/tajuana',
|
||||
},
|
||||
{
|
||||
name: 'Laureen Ritchie',
|
||||
username: 'michaele.will',
|
||||
id: 16,
|
||||
state: 'active',
|
||||
avatar_url: 'http: //www.gravatar.com/avatar/e301827eb03be955c9c172cb9a8e4e8a?s=80\u0026d=identicon',
|
||||
web_url: 'http: //localhost:3001/michaele.will',
|
||||
},
|
||||
],
|
||||
subscribed: true,
|
||||
time_estimate: 0,
|
||||
total_time_spent: 0,
|
||||
},
|
||||
'/autocomplete/projects?project_id=15': [
|
||||
{
|
||||
'id': 0,
|
||||
|
@ -113,9 +172,10 @@ const sidebarMockData = {
|
|||
},
|
||||
};
|
||||
|
||||
export default {
|
||||
const mockData = {
|
||||
responseMap: RESPONSE_MAP,
|
||||
mediator: {
|
||||
endpoint: '/gitlab-org/gitlab-shell/issues/5.json',
|
||||
endpoint: '/gitlab-org/gitlab-shell/issues/5.json?serializer=sidebar',
|
||||
toggleSubscriptionEndpoint: '/gitlab-org/gitlab-shell/issues/5/toggle_subscription',
|
||||
moveIssueEndpoint: '/gitlab-org/gitlab-shell/issues/5/move',
|
||||
projectsAutocompleteEndpoint: '/autocomplete/projects?project_id=15',
|
||||
|
@ -141,12 +201,14 @@ export default {
|
|||
name: 'Administrator',
|
||||
username: 'root',
|
||||
},
|
||||
|
||||
sidebarMockInterceptor(request, next) {
|
||||
const body = sidebarMockData[request.method.toUpperCase()][request.url];
|
||||
|
||||
next(request.respondWith(JSON.stringify(body), {
|
||||
status: 200,
|
||||
}));
|
||||
},
|
||||
};
|
||||
|
||||
mockData.sidebarMockInterceptor = function (request, next) {
|
||||
const body = this.responseMap[request.method.toUpperCase()][request.url];
|
||||
|
||||
next(request.respondWith(JSON.stringify(body), {
|
||||
status: 200,
|
||||
}));
|
||||
}.bind(mockData);
|
||||
|
||||
export default mockData;
|
||||
|
|
|
@ -33,10 +33,29 @@ describe('Sidebar mediator', () => {
|
|||
.catch(done.fail);
|
||||
});
|
||||
|
||||
it('fetches the data', () => {
|
||||
spyOn(this.mediator.service, 'get').and.callThrough();
|
||||
this.mediator.fetch();
|
||||
expect(this.mediator.service.get).toHaveBeenCalled();
|
||||
it('fetches the data', (done) => {
|
||||
const mockData = Mock.responseMap.GET['/gitlab-org/gitlab-shell/issues/5.json?serializer=sidebar'];
|
||||
spyOn(this.mediator, 'processFetchedData').and.callThrough();
|
||||
|
||||
this.mediator.fetch()
|
||||
.then(() => {
|
||||
expect(this.mediator.processFetchedData).toHaveBeenCalledWith(mockData);
|
||||
})
|
||||
.then(done)
|
||||
.catch(done.fail);
|
||||
});
|
||||
|
||||
it('processes fetched data', () => {
|
||||
const mockData = Mock.responseMap.GET['/gitlab-org/gitlab-shell/issues/5.json?serializer=sidebar'];
|
||||
this.mediator.processFetchedData(mockData);
|
||||
|
||||
expect(this.mediator.store.assignees).toEqual(mockData.assignees);
|
||||
expect(this.mediator.store.humanTimeEstimate).toEqual(mockData.human_time_estimate);
|
||||
expect(this.mediator.store.humanTotalTimeSpent).toEqual(mockData.human_total_time_spent);
|
||||
expect(this.mediator.store.participants).toEqual(mockData.participants);
|
||||
expect(this.mediator.store.subscribed).toEqual(mockData.subscribed);
|
||||
expect(this.mediator.store.timeEstimate).toEqual(mockData.time_estimate);
|
||||
expect(this.mediator.store.totalTimeSpent).toEqual(mockData.total_time_spent);
|
||||
});
|
||||
|
||||
it('sets moveToProjectId', () => {
|
||||
|
|
|
@ -120,6 +120,12 @@ describe('Sidebar store', () => {
|
|||
expect(this.store.isFetching.participants).toEqual(false);
|
||||
});
|
||||
|
||||
it('sets loading state', () => {
|
||||
this.store.setLoadingState('assignees', true);
|
||||
|
||||
expect(this.store.isLoading.assignees).toEqual(true);
|
||||
});
|
||||
|
||||
it('set time tracking data', () => {
|
||||
this.store.setTimeTrackingData(Mock.time);
|
||||
expect(this.store.timeEstimate).toEqual(Mock.time.time_estimate);
|
||||
|
|
Loading…
Reference in New Issue