Commit 4ca4b0ff authored by Phil Hughes's avatar Phil Hughes

Merge branch 'ce-1379-update-sidebar-weight-to-vue-and-add-to-boards' into 'master'

Backport EE changes from refactor sidebar weight block Vue and move to Issue Boards

See merge request gitlab-org/gitlab-ce!15706
parents 0131c97c f3433d8a
...@@ -20,6 +20,7 @@ class ListIssue { ...@@ -20,6 +20,7 @@ class ListIssue {
this.isFetching = { this.isFetching = {
subscriptions: true, subscriptions: true,
}; };
this.isLoading = {};
this.sidebarInfoEndpoint = obj.issue_sidebar_endpoint; this.sidebarInfoEndpoint = obj.issue_sidebar_endpoint;
this.toggleSubscriptionEndpoint = obj.toggle_subscription_endpoint; this.toggleSubscriptionEndpoint = obj.toggle_subscription_endpoint;
...@@ -86,6 +87,10 @@ class ListIssue { ...@@ -86,6 +87,10 @@ class ListIssue {
this.isFetching[key] = value; this.isFetching[key] = value;
} }
setLoadingState(key, value) {
this.isLoading[key] = value;
}
update (url) { update (url) {
const data = { const data = {
issue: { issue: {
......
...@@ -514,10 +514,11 @@ GitLabDropdown = (function() { ...@@ -514,10 +514,11 @@ GitLabDropdown = (function() {
const dropdownToggle = this.dropdown.find('.dropdown-menu-toggle'); const dropdownToggle = this.dropdown.find('.dropdown-menu-toggle');
const hasFilterBulkUpdate = dropdownToggle.hasClass('js-filter-bulk-update'); const hasFilterBulkUpdate = dropdownToggle.hasClass('js-filter-bulk-update');
const shouldRefreshOnOpen = dropdownToggle.hasClass('js-gl-dropdown-refresh-on-open');
const hasMultiSelect = dropdownToggle.hasClass('js-multiselect'); const hasMultiSelect = dropdownToggle.hasClass('js-multiselect');
// Makes indeterminate items effective // Makes indeterminate items effective
if (this.fullData && hasFilterBulkUpdate) { if (this.fullData && (shouldRefreshOnOpen || hasFilterBulkUpdate)) {
this.parseData(this.fullData); this.parseData(this.fullData);
} }
......
...@@ -15,7 +15,7 @@ import Cookies from 'js-cookie'; ...@@ -15,7 +15,7 @@ import Cookies from 'js-cookie';
Sidebar.prototype.removeListeners = function () { Sidebar.prototype.removeListeners = function () {
this.sidebar.off('click', '.sidebar-collapsed-icon'); 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('loading.gl.dropdown');
$('.dropdown').off('loaded.gl.dropdown'); $('.dropdown').off('loaded.gl.dropdown');
$(document).off('click', '.js-sidebar-toggle'); $(document).off('click', '.js-sidebar-toggle');
...@@ -25,7 +25,7 @@ import Cookies from 'js-cookie'; ...@@ -25,7 +25,7 @@ import Cookies from 'js-cookie';
const $document = $(document); const $document = $(document);
this.sidebar.on('click', '.sidebar-collapsed-icon', this, this.sidebarCollapseClicked); 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('loading.gl.dropdown', this.sidebarDropdownLoading);
$('.dropdown').on('loaded.gl.dropdown', this.sidebarDropdownLoaded); $('.dropdown').on('loaded.gl.dropdown', this.sidebarDropdownLoaded);
...@@ -180,7 +180,7 @@ import Cookies from 'js-cookie'; ...@@ -180,7 +180,7 @@ import Cookies from 'js-cookie';
var $block, sidebar; var $block, sidebar;
sidebar = e.data; sidebar = e.data;
e.preventDefault(); e.preventDefault();
$block = $(this).closest('.block'); $block = $(e.target).closest('.block');
return sidebar.sidebarDropdownHidden($block); return sidebar.sidebarDropdownHidden($block);
}; };
......
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;
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'; import Mediator from './sidebar_mediator';
import mountSidebar from './mount_sidebar';
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 domContentLoaded() { function domContentLoaded() {
const sidebarOptions = JSON.parse(document.querySelector('.js-sidebar-options').innerHTML); const sidebarOptions = JSON.parse(document.querySelector('.js-sidebar-options').innerHTML);
const mediator = new Mediator(sidebarOptions); const mediator = new Mediator(sidebarOptions);
mediator.fetch(); mediator.fetch();
const sidebarAssigneesEl = document.getElementById('js-vue-sidebar-assignees'); mountSidebar(mediator);
// 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');
} }
document.addEventListener('DOMContentLoaded', domContentLoaded); document.addEventListener('DOMContentLoaded', domContentLoaded);
......
...@@ -5,6 +5,13 @@ import Store from './stores/sidebar_store'; ...@@ -5,6 +5,13 @@ import Store from './stores/sidebar_store';
export default class SidebarMediator { export default class SidebarMediator {
constructor(options) { constructor(options) {
if (!SidebarMediator.singleton) { if (!SidebarMediator.singleton) {
this.initSingleton(options);
}
return SidebarMediator.singleton;
}
initSingleton(options) {
this.store = new Store(options); this.store = new Store(options);
this.service = new Service({ this.service = new Service({
endpoint: options.endpoint, endpoint: options.endpoint,
...@@ -15,9 +22,6 @@ export default class SidebarMediator { ...@@ -15,9 +22,6 @@ export default class SidebarMediator {
SidebarMediator.singleton = this; SidebarMediator.singleton = this;
} }
return SidebarMediator.singleton;
}
assignYourself() { assignYourself() {
this.store.addAssignee(this.store.currentUser); this.store.addAssignee(this.store.currentUser);
} }
...@@ -35,15 +39,19 @@ export default class SidebarMediator { ...@@ -35,15 +39,19 @@ export default class SidebarMediator {
} }
fetch() { fetch() {
this.service.get() return this.service.get()
.then(response => response.json()) .then(response => response.json())
.then((data) => { .then((data) => {
this.processFetchedData(data);
})
.catch(() => new Flash('Error occurred when fetching sidebar data'));
}
processFetchedData(data) {
this.store.setAssigneeData(data); this.store.setAssigneeData(data);
this.store.setTimeTrackingData(data); this.store.setTimeTrackingData(data);
this.store.setParticipantsData(data); this.store.setParticipantsData(data);
this.store.setSubscriptionsData(data); this.store.setSubscriptionsData(data);
})
.catch(() => new Flash('Error occurred when fetching sidebar data'));
} }
toggleSubscription() { toggleSubscription() {
......
...@@ -15,6 +15,7 @@ export default class SidebarStore { ...@@ -15,6 +15,7 @@ export default class SidebarStore {
participants: true, participants: true,
subscriptions: true, subscriptions: true,
}; };
this.isLoading = {};
this.autocompleteProjects = []; this.autocompleteProjects = [];
this.moveToProjectId = 0; this.moveToProjectId = 0;
this.isLockDialogOpen = false; this.isLockDialogOpen = false;
...@@ -55,6 +56,10 @@ export default class SidebarStore { ...@@ -55,6 +56,10 @@ export default class SidebarStore {
this.isFetching[key] = value; this.isFetching[key] = value;
} }
setLoadingState(key, value) {
this.isLoading[key] = value;
}
addAssignee(assignee) { addAssignee(assignee) {
if (!this.findAssignee(assignee)) { if (!this.findAssignee(assignee)) {
this.assignees.push(assignee); this.assignees.push(assignee);
......
...@@ -146,6 +146,12 @@ describe('Issue model', () => { ...@@ -146,6 +146,12 @@ describe('Issue model', () => {
expect(issue.isFetching.subscriptions).toBe(false); expect(issue.isFetching.subscriptions).toBe(false);
}); });
it('sets loading state', () => {
issue.setLoadingState('foo', true);
expect(issue.isLoading.foo).toBe(true);
});
describe('update', () => { describe('update', () => {
it('passes assignee ids when there are assignees', (done) => { it('passes assignee ids when there are assignees', (done) => {
spyOn(Vue.http, 'patch').and.callFake((url, data) => { spyOn(Vue.http, 'patch').and.callFake((url, data) => {
......
/* eslint-disable quote-props*/ /* eslint-disable quote-props*/
const sidebarMockData = { const RESPONSE_MAP = {
'GET': { 'GET': {
'/gitlab-org/gitlab-shell/issues/5.json': { '/gitlab-org/gitlab-shell/issues/5.json': {
id: 45, id: 45,
...@@ -66,6 +66,65 @@ const sidebarMockData = { ...@@ -66,6 +66,65 @@ const sidebarMockData = {
}, },
labels: [], 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': [ '/autocomplete/projects?project_id=15': [
{ {
'id': 0, 'id': 0,
...@@ -113,9 +172,10 @@ const sidebarMockData = { ...@@ -113,9 +172,10 @@ const sidebarMockData = {
}, },
}; };
export default { const mockData = {
responseMap: RESPONSE_MAP,
mediator: { 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', toggleSubscriptionEndpoint: '/gitlab-org/gitlab-shell/issues/5/toggle_subscription',
moveIssueEndpoint: '/gitlab-org/gitlab-shell/issues/5/move', moveIssueEndpoint: '/gitlab-org/gitlab-shell/issues/5/move',
projectsAutocompleteEndpoint: '/autocomplete/projects?project_id=15', projectsAutocompleteEndpoint: '/autocomplete/projects?project_id=15',
...@@ -141,12 +201,14 @@ export default { ...@@ -141,12 +201,14 @@ export default {
name: 'Administrator', name: 'Administrator',
username: 'root', username: 'root',
}, },
};
sidebarMockInterceptor(request, next) { mockData.sidebarMockInterceptor = function (request, next) {
const body = sidebarMockData[request.method.toUpperCase()][request.url]; const body = this.responseMap[request.method.toUpperCase()][request.url];
next(request.respondWith(JSON.stringify(body), { next(request.respondWith(JSON.stringify(body), {
status: 200, status: 200,
})); }));
}, }.bind(mockData);
};
export default mockData;
...@@ -33,10 +33,29 @@ describe('Sidebar mediator', () => { ...@@ -33,10 +33,29 @@ describe('Sidebar mediator', () => {
.catch(done.fail); .catch(done.fail);
}); });
it('fetches the data', () => { it('fetches the data', (done) => {
spyOn(this.mediator.service, 'get').and.callThrough(); const mockData = Mock.responseMap.GET['/gitlab-org/gitlab-shell/issues/5.json?serializer=sidebar'];
this.mediator.fetch(); spyOn(this.mediator, 'processFetchedData').and.callThrough();
expect(this.mediator.service.get).toHaveBeenCalled();
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', () => { it('sets moveToProjectId', () => {
......
...@@ -120,6 +120,12 @@ describe('Sidebar store', () => { ...@@ -120,6 +120,12 @@ describe('Sidebar store', () => {
expect(this.store.isFetching.participants).toEqual(false); 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', () => { it('set time tracking data', () => {
this.store.setTimeTrackingData(Mock.time); this.store.setTimeTrackingData(Mock.time);
expect(this.store.timeEstimate).toEqual(Mock.time.time_estimate); expect(this.store.timeEstimate).toEqual(Mock.time.time_estimate);
......
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