Commit 60f0a2dd authored by Eulyeon Ko's avatar Eulyeon Ko

Refactor epic sort comparator for roadmap

Use Number.NEGATIVE_INFINITY and Infinity when dates are undefined.

Always use original start/due dates to perform sorts.
parent 51fbe028
...@@ -60,10 +60,6 @@ export const PRESET_DEFAULTS = { ...@@ -60,10 +60,6 @@ export const PRESET_DEFAULTS = {
}, },
}; };
export const PAST_DATE = new Date(new Date().getFullYear() - 100, 0, 1);
export const FUTURE_DATE = new Date(new Date().getFullYear() + 100, 0, 1);
export const EPIC_LEVEL_MARGIN = { export const EPIC_LEVEL_MARGIN = {
1: 'ml-4', 1: 'ml-4',
2: 'ml-6', 2: 'ml-6',
......
...@@ -6,8 +6,6 @@ import { ...@@ -6,8 +6,6 @@ import {
EXTEND_AS, EXTEND_AS,
TIMELINE_CELL_MIN_WIDTH, TIMELINE_CELL_MIN_WIDTH,
DAYS_IN_WEEK, DAYS_IN_WEEK,
PAST_DATE,
FUTURE_DATE,
} from '../constants'; } from '../constants';
const monthsForQuarters = { const monthsForQuarters = {
...@@ -406,49 +404,39 @@ export const getEpicsTimeframeRange = ({ presetType = '', timeframe = [] }) => { ...@@ -406,49 +404,39 @@ export const getEpicsTimeframeRange = ({ presetType = '', timeframe = [] }) => {
}; };
}; };
/**
* This function takes two epics and return sortable dates depending on the '
* type of sorting order -- startDate or endDate.
*/
export function assignDates(a, b, { dateUndefined, outOfRange, originalDate, date, proxyDate }) {
let aDate;
let bDate;
if (a[dateUndefined]) {
// Set proxy date to be either far in the past or
// far in the future to ensure sort order is
// correct.
aDate = proxyDate;
} else {
aDate = a[outOfRange] ? a[originalDate] : a[date];
}
if (b[dateUndefined]) {
bDate = proxyDate;
} else {
bDate = b[outOfRange] ? b[originalDate] : b[date];
}
return [aDate, bDate];
}
export const sortEpics = (epics, sortedBy) => { export const sortEpics = (epics, sortedBy) => {
const sortByStartDate = sortedBy.indexOf('start_date') > -1; const sortByStartDate = sortedBy.indexOf('start_date') > -1;
const sortOrderAsc = sortedBy.indexOf('asc') > -1; const sortOrderAsc = sortedBy.indexOf('asc') > -1;
epics.sort((a, b) => { epics.sort((a, b) => {
const [aDate, bDate] = assignDates(a, b, { let aDate;
dateUndefined: sortByStartDate ? 'startDateUndefined' : 'endDateUndefined', let bDate;
outOfRange: sortByStartDate ? 'startDateOutOfRange' : 'endDateOutOfRange',
originalDate: sortByStartDate ? 'originalStartDate' : 'originalEndDate', if (sortByStartDate) {
date: sortByStartDate ? 'startDate' : 'endDate', // Always use the original start date.
proxyDate: sortByStartDate ? PAST_DATE : FUTURE_DATE, // if originalStartDate exists, it means startDate was changed to a proxy date
}); // (refer to roadmap_item_utils.js)
const startDateForA = a.originalStartDate ? a.originalStartDate : a.startDate;
const startDateForB = b.originalStartDate ? b.originalStartDate : b.startDate;
// When epic has no fixed start date, use Number.NEGATIVE_INFINITY for comparison.
// In other words, epics without fixed start date should, in theory, have the earliest start date.
// (the actual min possible value for Date object is much smaller; ECMA-262 20.4.1.1)
aDate = a.startDateUndefined ? Number.NEGATIVE_INFINITY : startDateForA.getTime();
bDate = b.startDateUndefined ? Number.NEGATIVE_INFINITY : startDateForB.getTime();
} else {
const endDateForA = a.originalEndDate ? a.originalEndDate : a.endDate;
const endDateForB = b.originalEndDate ? b.originalEndDate : b.endDate;
// Similarly, use Infinity when epic has no fixed due date.
aDate = a.endDateUndefined ? Infinity : endDateForA.getTime();
bDate = b.endDateUndefined ? Infinity : endDateForB.getTime();
}
// Sort in ascending or descending order // Sort in ascending or descending order
if (aDate.getTime() < bDate.getTime()) { if (aDate < bDate) {
return sortOrderAsc ? -1 : 1; return sortOrderAsc ? -1 : 1;
} else if (aDate.getTime() > bDate.getTime()) { } else if (aDate > bDate) {
return sortOrderAsc ? 1 : -1; return sortOrderAsc ? 1 : -1;
} }
return 0; return 0;
......
...@@ -449,18 +449,46 @@ export const rawEpics = [ ...@@ -449,18 +449,46 @@ export const rawEpics = [
export const mockUnsortedEpics = [ export const mockUnsortedEpics = [
{ {
title: 'Nov 10 2013 ~ Jun 01 2014; actual start date is Feb 1 2013',
originalStartDate: dateFromString('Feb 1 2013'),
startDate: dateFromString('Nov 10 2013'),
endDate: dateFromString('Jun 1, 2014'),
},
{
title: 'Oct 01 2013 ~ Nov 01 2013; actual due date is Nov 1 2014',
startDate: dateFromString('Oct 1 2013'),
originalEndDate: dateFromString('Nov 1 2014'),
endDate: dateFromString('Nov 1, 2013'),
},
{
title: 'Jan 01 2020 ~ Dec 01 2020; no fixed start date',
startDateUndefined: true,
startDate: dateFromString('Jan 1 2020'),
endDate: dateFromString('Dec 1 2020'),
},
{
title: 'Mar 01 2013 ~ Dec 01 2013; no fixed due date',
startDate: dateFromString('Mar 1 2013'),
endDateUndefined: true,
endDate: dateFromString('Dec 1 2013'),
},
{
title: 'Mar 12 2017 ~ Aug 20 2017',
startDate: new Date(2017, 2, 12), startDate: new Date(2017, 2, 12),
endDate: new Date(2017, 7, 20), endDate: new Date(2017, 7, 20),
}, },
{ {
title: 'Jun 08 2015 ~ Apr 01 2016',
startDate: new Date(2015, 5, 8), startDate: new Date(2015, 5, 8),
endDate: new Date(2016, 3, 1), endDate: new Date(2016, 3, 1),
}, },
{ {
title: 'Apr 12 2019 ~ Aug 30 2019',
startDate: new Date(2019, 4, 12), startDate: new Date(2019, 4, 12),
endDate: new Date(2019, 7, 30), endDate: new Date(2019, 7, 30),
}, },
{ {
title: 'Mar 17 2014 ~ Aug 15 2015',
startDate: new Date(2014, 3, 17), startDate: new Date(2014, 3, 17),
endDate: new Date(2015, 7, 15), endDate: new Date(2015, 7, 15),
}, },
......
...@@ -8,7 +8,6 @@ import { ...@@ -8,7 +8,6 @@ import {
extendTimeframeForWeeksView, extendTimeframeForWeeksView,
extendTimeframeForAvailableWidth, extendTimeframeForAvailableWidth,
getEpicsTimeframeRange, getEpicsTimeframeRange,
assignDates,
sortEpics, sortEpics,
} from 'ee/roadmap/utils/roadmap_utils'; } from 'ee/roadmap/utils/roadmap_utils';
...@@ -347,10 +346,14 @@ describe('sortEpics', () => { ...@@ -347,10 +346,14 @@ describe('sortEpics', () => {
it('sorts epics list by startDate in ascending order when `sortedBy` param is `start_date_asc`', () => { it('sorts epics list by startDate in ascending order when `sortedBy` param is `start_date_asc`', () => {
const epics = mockUnsortedEpics.slice(); const epics = mockUnsortedEpics.slice();
const sortedOrder = [ const sortedOrder = [
new Date(2014, 3, 17), 'Jan 01 2020 ~ Dec 01 2020; no fixed start date',
new Date(2015, 5, 8), 'Nov 10 2013 ~ Jun 01 2014; actual start date is Feb 1 2013',
new Date(2017, 2, 12), 'Mar 01 2013 ~ Dec 01 2013; no fixed due date',
new Date(2019, 4, 12), 'Oct 01 2013 ~ Nov 01 2013; actual due date is Nov 1 2014',
'Mar 17 2014 ~ Aug 15 2015',
'Jun 08 2015 ~ Apr 01 2016',
'Mar 12 2017 ~ Aug 20 2017',
'Apr 12 2019 ~ Aug 30 2019',
]; ];
sortEpics(epics, 'start_date_asc'); sortEpics(epics, 'start_date_asc');
...@@ -358,17 +361,21 @@ describe('sortEpics', () => { ...@@ -358,17 +361,21 @@ describe('sortEpics', () => {
expect(epics).toHaveLength(mockUnsortedEpics.length); expect(epics).toHaveLength(mockUnsortedEpics.length);
epics.forEach((epic, index) => { epics.forEach((epic, index) => {
expect(epic.startDate.getTime()).toBe(sortedOrder[index].getTime()); expect(epic.title).toEqual(sortedOrder[index]);
}); });
}); });
it('sorts epics list by startDate in descending order when `sortedBy` param is `start_date_desc`', () => { it('sorts epics list by startDate in descending order when `sortedBy` param is `start_date_desc`', () => {
const epics = mockUnsortedEpics.slice(); const epics = mockUnsortedEpics.slice();
const sortedOrder = [ const sortedOrder = [
new Date(2019, 4, 12), 'Apr 12 2019 ~ Aug 30 2019',
new Date(2017, 2, 12), 'Mar 12 2017 ~ Aug 20 2017',
new Date(2015, 5, 8), 'Jun 08 2015 ~ Apr 01 2016',
new Date(2014, 3, 17), 'Mar 17 2014 ~ Aug 15 2015',
'Oct 01 2013 ~ Nov 01 2013; actual due date is Nov 1 2014',
'Mar 01 2013 ~ Dec 01 2013; no fixed due date',
'Nov 10 2013 ~ Jun 01 2014; actual start date is Feb 1 2013',
'Jan 01 2020 ~ Dec 01 2020; no fixed start date',
]; ];
sortEpics(epics, 'start_date_desc'); sortEpics(epics, 'start_date_desc');
...@@ -376,17 +383,21 @@ describe('sortEpics', () => { ...@@ -376,17 +383,21 @@ describe('sortEpics', () => {
expect(epics).toHaveLength(mockUnsortedEpics.length); expect(epics).toHaveLength(mockUnsortedEpics.length);
epics.forEach((epic, index) => { epics.forEach((epic, index) => {
expect(epic.startDate.getTime()).toBe(sortedOrder[index].getTime()); expect(epic.title).toEqual(sortedOrder[index]);
}); });
}); });
it('sorts epics list by endDate in ascending order when `sortedBy` param is `end_date_asc`', () => { it('sorts epics list by endDate in ascending order when `sortedBy` param is `end_date_asc`', () => {
const epics = mockUnsortedEpics.slice(); const epics = mockUnsortedEpics.slice();
const sortedOrder = [ const sortedOrder = [
new Date(2015, 7, 15), 'Nov 10 2013 ~ Jun 01 2014; actual start date is Feb 1 2013',
new Date(2016, 3, 1), 'Oct 01 2013 ~ Nov 01 2013; actual due date is Nov 1 2014',
new Date(2017, 7, 20), 'Mar 17 2014 ~ Aug 15 2015',
new Date(2019, 7, 30), 'Jun 08 2015 ~ Apr 01 2016',
'Mar 12 2017 ~ Aug 20 2017',
'Apr 12 2019 ~ Aug 30 2019',
'Jan 01 2020 ~ Dec 01 2020; no fixed start date',
'Mar 01 2013 ~ Dec 01 2013; no fixed due date',
]; ];
sortEpics(epics, 'end_date_asc'); sortEpics(epics, 'end_date_asc');
...@@ -394,17 +405,21 @@ describe('sortEpics', () => { ...@@ -394,17 +405,21 @@ describe('sortEpics', () => {
expect(epics).toHaveLength(mockUnsortedEpics.length); expect(epics).toHaveLength(mockUnsortedEpics.length);
epics.forEach((epic, index) => { epics.forEach((epic, index) => {
expect(epic.endDate.getTime()).toBe(sortedOrder[index].getTime()); expect(epic.title).toEqual(sortedOrder[index]);
}); });
}); });
it('sorts epics list by endDate in descending order when `sortedBy` param is `end_date_desc`', () => { it('sorts epics list by endDate in descending order when `sortedBy` param is `end_date_desc`', () => {
const epics = mockUnsortedEpics.slice(); const epics = mockUnsortedEpics.slice();
const sortedOrder = [ const sortedOrder = [
new Date(2019, 7, 30), 'Mar 01 2013 ~ Dec 01 2013; no fixed due date',
new Date(2017, 7, 20), 'Jan 01 2020 ~ Dec 01 2020; no fixed start date',
new Date(2016, 3, 1), 'Apr 12 2019 ~ Aug 30 2019',
new Date(2015, 7, 15), 'Mar 12 2017 ~ Aug 20 2017',
'Jun 08 2015 ~ Apr 01 2016',
'Mar 17 2014 ~ Aug 15 2015',
'Oct 01 2013 ~ Nov 01 2013; actual due date is Nov 1 2014',
'Nov 10 2013 ~ Jun 01 2014; actual start date is Feb 1 2013',
]; ];
sortEpics(epics, 'end_date_desc'); sortEpics(epics, 'end_date_desc');
...@@ -412,118 +427,7 @@ describe('sortEpics', () => { ...@@ -412,118 +427,7 @@ describe('sortEpics', () => {
expect(epics).toHaveLength(mockUnsortedEpics.length); expect(epics).toHaveLength(mockUnsortedEpics.length);
epics.forEach((epic, index) => { epics.forEach((epic, index) => {
expect(epic.endDate.getTime()).toBe(sortedOrder[index].getTime()); expect(epic.title).toEqual(sortedOrder[index]);
}); });
}); });
}); });
describe('assignDates', () => {
const startDateProps = {
dateUndefined: 'startDateUndefined',
outOfRange: 'startDateOutOfRange',
originalDate: 'originalStartDate',
date: 'startDate',
proxyDate: new Date('1900'),
};
const endDateProps = {
dateUndefined: 'endDateUndefined',
outOfRange: 'endDateOutOfRange',
originalDate: 'originalEndDate',
date: 'endDate',
proxyDate: new Date('2200'),
};
it('returns proxyDate if startDate is undefined', () => {
const epic1 = { startDateUndefined: true };
const epic2 = { startDateUndefined: false };
let [aDate, bDate] = assignDates(epic1, epic2, startDateProps);
expect(aDate).toEqual(startDateProps.proxyDate);
expect(bDate).not.toEqual(startDateProps.proxyDate);
epic1.startDateUndefined = false;
epic2.startDateUndefined = true;
[aDate, bDate] = assignDates(epic1, epic2, startDateProps);
expect(aDate).not.toEqual(startDateProps.proxyDate);
expect(bDate).toEqual(startDateProps.proxyDate);
});
it('returns proxyDate if endDate is undefined', () => {
const epic1 = { endDateUndefined: true };
const epic2 = { endDateUndefined: false };
let [aDate, bDate] = assignDates(epic1, epic2, endDateProps);
expect(aDate).toEqual(endDateProps.proxyDate);
expect(bDate).not.toEqual(endDateProps.proxyDate);
epic1.endDateUndefined = false;
epic2.endDateUndefined = true;
[aDate, bDate] = assignDates(epic1, epic2, endDateProps);
expect(aDate).not.toEqual(endDateProps.proxyDate);
expect(bDate).toEqual(endDateProps.proxyDate);
});
it('assigns originalStartDate if date is out of range', () => {
const epic1 = {
startDateUndefined: false,
originalStartDate: new Date('2000'),
startDate: new Date('2010'),
startDateOutOfRange: true,
};
const epic2 = { ...epic1, originalStartDate: new Date('2005') };
const [aDate, bDate] = assignDates(epic1, epic2, startDateProps);
expect(aDate).toEqual(epic1.originalStartDate);
expect(bDate).toEqual(epic2.originalStartDate);
});
it('assigns originalEndDate if date is out of range', () => {
const epic1 = {
endDateUndefined: false,
originalEndDate: new Date('2000'),
endDate: new Date('2010'),
endDateOutOfRange: true,
};
const epic2 = { ...epic1, originalEndDate: new Date('2005') };
const [aDate, bDate] = assignDates(epic1, epic2, endDateProps);
expect(aDate).toEqual(epic1.originalEndDate);
expect(bDate).toEqual(epic2.originalEndDate);
});
it('assigns startDate if date is in the range', () => {
const epic1 = {
startDateUndefined: false,
originalStartDate: new Date('2000'),
startDate: new Date('2010'),
startDateOutOfRange: false,
};
const epic2 = { ...epic1, startDate: new Date('2005') };
const [aDate, bDate] = assignDates(epic1, epic2, startDateProps);
expect(aDate).toEqual(epic1.startDate);
expect(bDate).toEqual(epic2.startDate);
});
it('assigns endDate if date is in the range', () => {
const epic1 = {
endDateUndefined: false,
originalEndDate: new Date('2000'),
endDate: new Date('2010'),
endDateOutOfRange: false,
};
const epic2 = { ...epic1, endDate: new Date('2005') };
const [aDate, bDate] = assignDates(epic1, epic2, endDateProps);
expect(aDate).toEqual(epic1.endDate);
expect(bDate).toEqual(epic2.endDate);
});
});
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