Commit 1b301cc1 authored by Kushal Pandya's avatar Kushal Pandya

Make Epics and Issues count dynamic in Epic tree

Makes Epics/Issues count in Epic Tree header dynamic to account for
child item additions and removals.
parent 5eac6d0f
......@@ -46,6 +46,12 @@ export default {
itemReference() {
return this.item.reference;
},
itemWebPath() {
// Here, GraphQL API (during item fetch) returns `webPath`
// and Rails API (during item add) returns `path`,
// we need to make both accessible.
return this.item.path || this.item.webPath;
},
isOpen() {
return this.item.state === ChildState.Open;
},
......@@ -74,7 +80,7 @@ export default {
return this.itemReference.split(this.item.pathIdSeparator).pop();
},
computedPath() {
return this.item.webPath.length ? this.item.webPath : null;
return this.itemWebPath.length ? this.itemWebPath : null;
},
itemActionInProgress() {
return (
......
......@@ -23,6 +23,22 @@ export const setInitialConfig = ({ commit }, data) => commit(types.SET_INITIAL_C
export const setInitialParentItem = ({ commit }, data) =>
commit(types.SET_INITIAL_PARENT_ITEM, data);
export const setChildrenCount = ({ commit, state }, { children, isRemoved = false }) => {
const [epicsCount, issuesCount] = children.reduce(
(acc, item) => {
if (item.type === ChildType.Epic) {
acc[0] += isRemoved ? -1 : 1;
} else {
acc[1] += isRemoved ? -1 : 1;
}
return acc;
},
[state.epicsCount || 0, state.issuesCount || 0],
);
commit(types.SET_CHILDREN_COUNT, { epicsCount, issuesCount });
};
export const expandItem = ({ commit }, data) => commit(types.EXPAND_ITEM, data);
export const collapseItem = ({ commit }, data) => commit(types.COLLAPSE_ITEM, data);
......@@ -33,6 +49,8 @@ export const setItemChildren = ({ commit, dispatch }, { parentItem, children, is
isSubItem,
});
dispatch('setChildrenCount', { children });
if (isSubItem) {
dispatch('expandItem', {
parentItem,
......@@ -131,6 +149,8 @@ export const removeItem = ({ dispatch }, { parentItem, item }) => {
parentItem,
item,
});
dispatch('setChildrenCount', { children: [item], isRemoved: true });
})
.catch(({ status }) => {
dispatch('receiveRemoveItemFailure', {
......@@ -168,6 +188,8 @@ export const receiveAddItemSuccess = ({ dispatch, commit, getters }, { actionTyp
items,
});
dispatch('setChildrenCount', { children: items });
dispatch('setItemChildrenFlags', {
children: items,
isSubItem: false,
......@@ -224,6 +246,8 @@ export const receiveCreateItemSuccess = (
item,
});
dispatch('setChildrenCount', { children: [item] });
dispatch('setItemChildrenFlags', {
children: [item],
isSubItem: false,
......
......@@ -7,34 +7,20 @@ export const directChildren = state => state.children[state.parentItem.reference
export const anyParentHasChildren = (state, getters) =>
getters.directChildren.some(item => item.hasChildren || item.hasIssues);
export const headerItems = (state, getters) => {
const children = getters.directChildren || [];
let totalEpics = 0;
let totalIssues = 0;
children.forEach(item => {
if (item.type === ChildType.Epic) {
totalEpics += 1;
} else {
totalIssues += 1;
}
});
return [
{
iconName: 'epic',
count: totalEpics,
qaClass: 'qa-add-epics-button',
type: ChildType.Epic,
},
{
iconName: 'issues',
count: totalIssues,
qaClass: 'qa-add-issues-button',
type: ChildType.Issue,
},
];
};
export const headerItems = state => [
{
iconName: 'epic',
count: state.epicsCount,
qaClass: 'qa-add-epics-button',
type: ChildType.Epic,
},
{
iconName: 'issues',
count: state.issuesCount,
qaClass: 'qa-add-issues-button',
type: ChildType.Issue,
},
];
export const epicsBeginAtIndex = (state, getters) =>
getters.directChildren.findIndex(item => item.type === ChildType.Epic);
......
......@@ -2,6 +2,7 @@ export const SET_INITIAL_CONFIG = 'SET_INITIAL_CONFIG';
export const SET_INITIAL_PARENT_ITEM = 'SET_INITIAL_PARENT_ITEM';
export const SET_CHILDREN_COUNT = 'SET_CHILDREN_COUNT';
export const SET_ITEM_CHILDREN = 'SET_ITEM_CHILDREN';
export const SET_ITEM_CHILDREN_FLAGS = 'SET_ITEM_CHILDREN_FLAGS';
......
......@@ -18,6 +18,11 @@ export default {
state.childrenFlags[state.parentItem.reference] = {};
},
[types.SET_CHILDREN_COUNT](state, { epicsCount, issuesCount }) {
state.epicsCount = epicsCount;
state.issuesCount = issuesCount;
},
[types.SET_ITEM_CHILDREN](state, { parentItem, children }) {
Vue.set(state.children, parentItem.reference, children);
},
......
......@@ -6,6 +6,8 @@ export default () => ({
children: {},
childrenFlags: {},
epicsCount: 0,
issuesCount: 0,
// Add Item Form Data
actionType: '',
......
......@@ -74,7 +74,8 @@ describe('RelatedItemsTree', () => {
describe('headerItems', () => {
it('returns an item within array containing Epic iconName, count, qaClass & type props', () => {
const epicHeaderItem = getters.headerItems(state, mockGetters)[0];
state.epicsCount = 2;
const epicHeaderItem = getters.headerItems(state)[0];
expect(epicHeaderItem).toEqual(
expect.objectContaining({
......@@ -87,7 +88,8 @@ describe('RelatedItemsTree', () => {
});
it('returns an item within array containing Issue iconName, count, qaClass & type props', () => {
const epicHeaderItem = getters.headerItems(state, mockGetters)[1];
state.issuesCount = 2;
const epicHeaderItem = getters.headerItems(state)[1];
expect(epicHeaderItem).toEqual(
expect.objectContaining({
......
......@@ -46,6 +46,20 @@ describe('RelatedItemsTree', () => {
});
});
describe(types.SET_CHILDREN_COUNT, () => {
it('should set provided `epicsCount` and `issuesCount` to state', () => {
const data = {
epicsCount: 4,
issuesCount: 5,
};
mutations[types.SET_CHILDREN_COUNT](state, data);
expect(state.epicsCount).toBe(data.epicsCount);
expect(state.issuesCount).toBe(data.issuesCount);
});
});
describe(types.SET_ITEM_CHILDREN, () => {
it('should set provided `data.children` to `state.children` with reference key as present in `data.parentItem`', () => {
const data = {
......
......@@ -67,6 +67,40 @@ describe('RelatedItemsTree', () => {
});
});
describe('itemWebPath', () => {
const mockPath = '/foo/bar';
it('returns value of `item.path`', done => {
wrapper.setProps({
item: Object.assign({}, mockItem, {
path: mockPath,
webPath: undefined,
}),
});
wrapper.vm.$nextTick(() => {
expect(wrapper.vm.itemWebPath).toBe(mockPath);
done();
});
});
it('returns value of `item.webPath` when `item.path` is undefined', done => {
wrapper.setProps({
item: Object.assign({}, mockItem, {
path: undefined,
webPath: mockPath,
}),
});
wrapper.vm.$nextTick(() => {
expect(wrapper.vm.itemWebPath).toBe(mockPath);
done();
});
});
});
describe('isOpen', () => {
it('returns true when `item.state` value is `opened`', done => {
wrapper.setProps({
......@@ -214,11 +248,11 @@ describe('RelatedItemsTree', () => {
});
describe('computedPath', () => {
it('returns value of `item.webPath` when it is defined', () => {
it('returns value of `itemWebPath` when it is defined', () => {
expect(wrapper.vm.computedPath).toBe(mockItem.webPath);
});
it('returns `null` when `item.webPath` is empty', done => {
it('returns `null` when `itemWebPath` is empty', done => {
wrapper.setProps({
item: Object.assign({}, mockItem, {
webPath: '',
......
......@@ -110,6 +110,8 @@ export const mockIssue2 = {
export const mockEpics = [mockEpic1, mockEpic2];
export const mockIssues = [mockIssue1, mockIssue2];
export const mockQueryResponse = {
data: {
group: {
......
......@@ -20,6 +20,7 @@ import {
mockParentItem,
mockQueryResponse,
mockEpics,
mockIssues,
mockEpic1,
} from '../mock_data';
......@@ -61,6 +62,57 @@ describe('RelatedItemTree', () => {
});
});
describe('setChildrenCount', () => {
const mockEpicsWithType = mockEpics.map(item =>
Object.assign({}, item, {
type: ChildType.Epic,
}),
);
const mockIssuesWithType = mockIssues.map(item =>
Object.assign({}, item, {
type: ChildType.Issue,
}),
);
const mockChildren = [...mockEpicsWithType, ...mockIssuesWithType];
it('should set `epicsCount` and `issuesCount`, by incrementing it, on state', done => {
testAction(
actions.setChildrenCount,
{ children: mockChildren, isRemoved: false },
{},
[
{
type: types.SET_CHILDREN_COUNT,
payload: { epicsCount: mockEpics.length, issuesCount: mockIssues.length },
},
],
[],
done,
);
});
it('should set `epicsCount` and `issuesCount`, by decrementing it, on state', done => {
testAction(
actions.setChildrenCount,
{ children: mockChildren, isRemoved: true },
{
epicsCount: mockEpics.length,
issuesCount: mockIssues.length,
},
[
{
type: types.SET_CHILDREN_COUNT,
payload: { epicsCount: 0, issuesCount: 0 },
},
],
[],
done,
);
});
});
describe('expandItem', () => {
it('should set `itemExpanded` to true on state.childrenFlags', done => {
testAction(
......@@ -101,7 +153,12 @@ describe('RelatedItemTree', () => {
payload: mockPayload,
},
],
[],
[
{
type: 'setChildrenCount',
payload: { children: mockPayload.children },
},
],
done,
);
});
......@@ -120,6 +177,10 @@ describe('RelatedItemTree', () => {
},
],
[
{
type: 'setChildrenCount',
payload: { children: mockPayload.children },
},
{
type: 'expandItem',
payload: { parentItem: mockPayload.parentItem },
......@@ -470,6 +531,10 @@ describe('RelatedItemTree', () => {
type: 'receiveRemoveItemSuccess',
payload: { parentItem: data.parentItem, item: data.item },
},
{
type: 'setChildrenCount',
payload: { children: [data.item], isRemoved: true },
},
],
done,
);
......@@ -606,6 +671,10 @@ describe('RelatedItemTree', () => {
},
],
[
{
type: 'setChildrenCount',
payload: { children: mockEpicsWithoutPerm },
},
{
type: 'setItemChildrenFlags',
payload: { children: mockEpicsWithoutPerm, isSubItem: false },
......@@ -755,6 +824,10 @@ describe('RelatedItemTree', () => {
},
],
[
{
type: 'setChildrenCount',
payload: { children: [mockItems[0]] },
},
{
type: 'setItemChildrenFlags',
payload: { children: [mockItems[0]], isSubItem: false },
......
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