Commit 96283344 authored by Kushal Pandya's avatar Kushal Pandya

Quarters Preset Mixin

parent aeff2f4f
import { totalDaysInQuarter, dayInQuarter } from '~/lib/utils/datetime_utility';
import { TIMELINE_END_OFFSET_HALF } from '../constants';
export default {
methods: {
/**
* Check if current epic starts within current quarter (timeline cell)
*/
hasStartDateForQuarter() {
const quarterStart = this.timeframeItem.range[0];
const quarterEnd = this.timeframeItem.range[2];
return this.epic.startDate >= quarterStart && this.epic.startDate <= quarterEnd;
},
/**
* Check if current epic ends within current quarter (timeline cell)
*/
isTimeframeUnderEndDateForQuarter(timeframeItem, epicEndDate) {
const quarterEnd = timeframeItem.range[2];
return epicEndDate <= quarterEnd;
},
/**
* Return timeline bar width for current quarter (timeline cell) based on
* cellWidth, days in quarter and day of the quarter
*/
getBarWidthForSingleQuarter(cellWidth, daysInQuarter, day) {
const dayWidth = cellWidth / daysInQuarter;
const barWidth = day === daysInQuarter ? cellWidth : dayWidth * day;
return Math.min(cellWidth, barWidth);
},
/**
* In case startDate for any epic is undefined or is out of range
* for current timeframe, we have to provide specific offset while
* positioning it to ensure that;
*
* 1. Timeline bar starts at correct position based on start date.
* 2. Bar starts exactly at the start of cell in case start date is `1`.
* 3. A "triangle" shape is shown at the beginning of timeline bar
* when startDate is out of range.
*
* Implementation of this method is identical to
* MonthsPresetMixin#getTimelineBarStartOffsetForMonths
*/
getTimelineBarStartOffsetForQuarters() {
const daysInQuarter = totalDaysInQuarter(this.timeframeItem.range);
const startDay = dayInQuarter(this.epic.startDate, this.timeframeItem.range);
if (
this.epic.startDateOutOfRange ||
(this.epic.startDateUndefined && this.epic.endDateOutOfRange)
) {
return '';
} else if (startDay === 1) {
return 'left: 0;';
}
const lastTimeframeItem = this.timeframe[this.timeframe.length - 1].range[2];
if (
this.epic.startDate >= this.timeframe[this.timeframe.length - 1].range[0] &&
this.epic.startDate <= lastTimeframeItem
) {
return `right: ${TIMELINE_END_OFFSET_HALF}px;`;
}
return `left: ${startDay / daysInQuarter * 100}%;`;
},
/**
* This method is externally only called when current timeframe cell has timeline
* bar to show. So when this method is called, we iterate over entire timeframe
* array starting from current timeframeItem.
*
* For eg;
* If timeframe range for 6 quarters is;
* [2017 Oct Nov Dec], [2018 Jan Feb Mar], [Apr May Jun],
* [Jul Aug Sep], [Oct Nov Dec], [2019 Jan Feb Mar]
*
* And if Epic starts in 2017 Dec and ends in 2018 May.
*
* Then this method will iterate over timeframe as;
* [2017 Oct Nov Dec] => [2018 Apr May Jun]
* And will add up width(see 1.) for timeline bar for each quarter in iteration
* based on provided start and end dates.
*
* 1. Width from date is calculated by totalWidthCell / totalDaysInQuarter = widthOfSingleDay
* and then dayOfQuarter x widthOfSingleDay = totalBarWidth
*
* Implementation of this method is identical to
* MonthsPresetMixin#getTimelineBarWidthForMonths
*/
getTimelineBarWidthForQuarters() {
let timelineBarWidth = 0;
const indexOfCurrentQuarter = this.timeframe.indexOf(this.timeframeItem);
const cellWidth = this.getCellWidth();
const offsetEnd = this.getTimelineBarEndOffset();
const epicStartDate = this.epic.startDate;
const epicEndDate = this.epic.endDate;
for (let i = indexOfCurrentQuarter; i < this.timeframe.length; i += 1) {
const currentQuarter = this.timeframe[i].range;
if (i === indexOfCurrentQuarter) {
if (this.isTimeframeUnderEndDateForQuarter(this.timeframe[i], epicEndDate)) {
timelineBarWidth += this.getBarWidthForSingleQuarter(
cellWidth,
totalDaysInQuarter(currentQuarter),
dayInQuarter(epicEndDate, currentQuarter) -
dayInQuarter(epicStartDate, currentQuarter) +
1,
);
break;
} else {
const daysInQuarter = totalDaysInQuarter(currentQuarter);
const day = dayInQuarter(epicStartDate, currentQuarter);
const date = day === 1 ? daysInQuarter : daysInQuarter - day;
timelineBarWidth += this.getBarWidthForSingleQuarter(
cellWidth,
totalDaysInQuarter(currentQuarter),
date,
);
}
} else if (this.isTimeframeUnderEndDateForQuarter(this.timeframe[i], epicEndDate)) {
timelineBarWidth += this.getBarWidthForSingleQuarter(
cellWidth,
totalDaysInQuarter(currentQuarter),
dayInQuarter(epicEndDate, currentQuarter),
);
break;
} else {
const daysInQuarter = totalDaysInQuarter(currentQuarter);
timelineBarWidth += this.getBarWidthForSingleQuarter(
cellWidth,
daysInQuarter,
daysInQuarter,
);
}
}
return timelineBarWidth - offsetEnd;
},
},
};
import Vue from 'vue';
import EpicItemTimelineComponent from 'ee/roadmap/components/epic_item_timeline.vue';
import { PRESET_TYPES } from 'ee/roadmap/constants';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { mockTimeframeQuarters, mockEpic, mockShellWidth, mockItemWidth } from '../mock_data';
const createComponent = ({
presetType = PRESET_TYPES.QUARTERS,
timeframe = mockTimeframeQuarters,
timeframeItem = mockTimeframeQuarters[0],
epic = mockEpic,
shellWidth = mockShellWidth,
itemWidth = mockItemWidth,
}) => {
const Component = Vue.extend(EpicItemTimelineComponent);
return mountComponent(Component, {
presetType,
timeframe,
timeframeItem,
epic,
shellWidth,
itemWidth,
});
};
describe('QuartersPresetMixin', () => {
let vm;
afterEach(() => {
vm.$destroy();
});
describe('methods', () => {
describe('hasStartDateForQuarter', () => {
it('returns true when Epic.startDate falls within timeframeItem', () => {
vm = createComponent({
epic: Object.assign({}, mockEpic, { startDate: mockTimeframeQuarters[1].range[0] }),
timeframeItem: mockTimeframeQuarters[1],
});
expect(vm.hasStartDateForQuarter()).toBe(true);
});
it('returns false when Epic.startDate does not fall within timeframeItem', () => {
vm = createComponent({
epic: Object.assign({}, mockEpic, { startDate: mockTimeframeQuarters[0].range[0] }),
timeframeItem: mockTimeframeQuarters[1],
});
expect(vm.hasStartDateForQuarter()).toBe(false);
});
});
describe('isTimeframeUnderEndDateForQuarter', () => {
beforeEach(() => {
vm = createComponent({});
});
it('returns true if provided timeframeItem is under epicEndDate', () => {
const timeframeItem = mockTimeframeQuarters[1];
const epicEndDate = mockTimeframeQuarters[1].range[2];
expect(vm.isTimeframeUnderEndDateForQuarter(timeframeItem, epicEndDate)).toBe(true);
});
it('returns false if provided timeframeItem is NOT under epicEndDate', () => {
const timeframeItem = mockTimeframeQuarters[1];
const epicEndDate = mockTimeframeQuarters[2].range[1];
expect(vm.isTimeframeUnderEndDateForQuarter(timeframeItem, epicEndDate)).toBe(false);
});
});
describe('getBarWidthForSingleQuarter', () => {
it('returns calculated bar width based on provided cellWidth, daysInQuarter and day of quarter', () => {
vm = createComponent({});
expect(Math.floor(vm.getBarWidthForSingleQuarter(300, 91, 1))).toBe(3); // 10% size
expect(Math.floor(vm.getBarWidthForSingleQuarter(300, 91, 45))).toBe(148); // 50% size
expect(vm.getBarWidthForSingleQuarter(300, 91, 91)).toBe(300); // Full size
});
});
describe('getTimelineBarStartOffsetForQuarters', () => {
it('returns empty string when Epic startDate is out of range', () => {
vm = createComponent({
epic: Object.assign({}, mockEpic, { startDateOutOfRange: true }),
});
expect(vm.getTimelineBarStartOffsetForQuarters()).toBe('');
});
it('returns empty string when Epic startDate is undefined and endDate is out of range', () => {
vm = createComponent({
epic: Object.assign({}, mockEpic, {
startDateUndefined: true,
endDateOutOfRange: true,
}),
});
expect(vm.getTimelineBarStartOffsetForQuarters()).toBe('');
});
it('return `left: 0;` when Epic startDate is first day of the quarter', () => {
vm = createComponent({
epic: Object.assign({}, mockEpic, {
startDate: mockTimeframeQuarters[0].range[0],
}),
});
expect(vm.getTimelineBarStartOffsetForQuarters()).toBe('left: 0;');
});
it('returns `right: 8px;` when Epic startDate is in last timeframe month and endDate is out of range', () => {
vm = createComponent({
epic: Object.assign({}, mockEpic, {
startDate: mockTimeframeQuarters[mockTimeframeQuarters.length - 1].range[1],
endDateOutOfRange: true,
}),
});
expect(vm.getTimelineBarStartOffsetForQuarters()).toBe('right: 8px;');
});
it('returns proportional `left` value based on Epic startDate and days in the quarter', () => {
vm = createComponent({
epic: Object.assign({}, mockEpic, {
startDate: mockTimeframeQuarters[0].range[1],
}),
});
expect(vm.getTimelineBarStartOffsetForQuarters()).toContain('left: 34');
});
});
describe('getTimelineBarWidthForQuarters', () => {
it('returns calculated width value based on Epic.startDate and Epic.endDate', () => {
vm = createComponent({
shellWidth: 2000,
timeframeItem: mockTimeframeQuarters[0],
epic: Object.assign({}, mockEpic, {
startDate: mockTimeframeQuarters[0].range[1],
endDate: mockTimeframeQuarters[1].range[1],
}),
});
expect(Math.floor(vm.getTimelineBarWidthForQuarters())).toBe(282);
});
});
});
});
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