Commit 64c40b6c authored by Kushal Pandya's avatar Kushal Pandya

Update to support presetType

parent 806f78fc
<script>
import { s__, sprintf } from '~/locale';
import { dateInWords } from '~/lib/utils/datetime_utility';
import { s__, sprintf } from '~/locale';
import { dateInWords } from '~/lib/utils/datetime_utility';
import NewEpic from '../../epics/new_epic/components/new_epic.vue';
import { PRESET_TYPES, PRESET_DEFAULTS } from '../constants';
export default {
components: {
NewEpic,
import NewEpic from '../../epics/new_epic/components/new_epic.vue';
export default {
components: {
NewEpic,
},
props: {
presetType: {
type: String,
required: true,
},
timeframeStart: {
type: [Date, Object],
required: true,
},
timeframeEnd: {
type: [Date, Object],
required: true,
},
hasFiltersApplied: {
type: Boolean,
required: true,
},
props: {
timeframeStart: {
type: Date,
required: true,
},
timeframeEnd: {
type: Date,
required: true,
},
hasFiltersApplied: {
type: Boolean,
required: true,
},
newEpicEndpoint: {
type: String,
required: true,
},
emptyStateIllustrationPath: {
type: String,
required: true,
},
newEpicEndpoint: {
type: String,
required: true,
},
computed: {
timeframeRange() {
const startDate = dateInWords(
emptyStateIllustrationPath: {
type: String,
required: true,
},
},
computed: {
timeframeRange() {
let startDate;
let endDate;
if (this.presetType === PRESET_TYPES.QUARTERS) {
const quarterStart = this.timeframeStart.range[0];
const quarterEnd = this.timeframeEnd.range[2];
startDate = dateInWords(
quarterStart,
true,
quarterStart.getFullYear() === quarterEnd.getFullYear(),
);
endDate = dateInWords(quarterEnd, true);
} else if (this.presetType === PRESET_TYPES.MONTHS) {
startDate = dateInWords(
this.timeframeStart,
true,
this.timeframeStart.getFullYear() === this.timeframeEnd.getFullYear(),
);
const endDate = dateInWords(this.timeframeEnd, true);
endDate = dateInWords(this.timeframeEnd, true);
} else if (this.presetType === PRESET_TYPES.WEEKS) {
const end = new Date(this.timeframeEnd.getTime());
end.setDate(end.getDate() + 6);
return {
startDate,
endDate,
};
},
message() {
if (this.hasFiltersApplied) {
return s__('GroupRoadmap|Sorry, no epics matched your search');
}
return s__('GroupRoadmap|The roadmap shows the progress of your epics along a timeline');
},
subMessage() {
if (this.hasFiltersApplied) {
return sprintf(s__('GroupRoadmap|To widen your search, change or remove filters. Only epics in the past 3 months and the next 3 months are shown &ndash; from %{startDate} to %{endDate}.'), {
startDate: this.timeframeRange.startDate,
endDate: this.timeframeRange.endDate,
});
}
return sprintf(s__('GroupRoadmap|To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown &ndash; from %{startDate} to %{endDate}.'), {
startDate = dateInWords(
this.timeframeStart,
true,
this.timeframeStart.getFullYear() === end.getFullYear(),
);
endDate = dateInWords(end, true);
}
return {
startDate,
endDate,
};
},
message() {
if (this.hasFiltersApplied) {
return s__('GroupRoadmap|Sorry, no epics matched your search');
}
return s__('GroupRoadmap|The roadmap shows the progress of your epics along a timeline');
},
subMessage() {
if (this.hasFiltersApplied) {
return sprintf(PRESET_DEFAULTS[this.presetType].emptyStateWithFilters, {
startDate: this.timeframeRange.startDate,
endDate: this.timeframeRange.endDate,
});
},
}
return sprintf(PRESET_DEFAULTS[this.presetType].emptyStateDefault, {
startDate: this.timeframeRange.startDate,
endDate: this.timeframeRange.endDate,
});
},
};
},
};
</script>
<template>
......
<script>
import eventHub from '../event_hub';
import { PRESET_TYPES } from '../constants';
import SectionMixin from '../mixins/section_mixin';
import timelineHeaderItem from './timeline_header_item.vue';
import QuartersHeaderItem from './preset_quarters/quarters_header_item.vue';
import MonthsHeaderItem from './preset_months/months_header_item.vue';
import WeeksHeaderItem from './preset_weeks/weeks_header_item.vue';
export default {
components: {
timelineHeaderItem,
QuartersHeaderItem,
MonthsHeaderItem,
WeeksHeaderItem,
},
mixins: [
SectionMixin,
],
props: {
presetType: {
type: String,
required: true,
},
epics: {
type: Array,
required: true,
......@@ -35,6 +45,18 @@
scrolledHeaderClass: '',
};
},
computed: {
headerItemComponentForPreset() {
if (this.presetType === PRESET_TYPES.QUARTERS) {
return 'quarters-header-item';
} else if (this.presetType === PRESET_TYPES.MONTHS) {
return 'months-header-item';
} else if (this.presetType === PRESET_TYPES.WEEKS) {
return 'weeks-header-item';
}
return '';
},
},
mounted() {
eventHub.$on('epicsListScrolled', this.handleEpicsListScroll);
},
......@@ -57,7 +79,8 @@
:style="sectionContainerStyles"
>
<span class="timeline-header-blank"></span>
<timeline-header-item
<component
:is="headerItemComponentForPreset"
v-for="(timeframeItem, index) in timeframe"
:key="index"
:timeframe-index="index"
......
<script>
import { totalDaysInMonth } from '~/lib/utils/datetime_utility';
import { totalDaysInMonth, dayInQuarter, totalDaysInQuarter } from '~/lib/utils/datetime_utility';
import eventHub from '../event_hub';
import { EPIC_DETAILS_CELL_WIDTH, PRESET_TYPES } from '../constants';
export default {
props: {
currentDate: {
type: Date,
required: true,
},
timeframeItem: {
type: Date,
required: true,
},
import eventHub from '../event_hub';
export default {
props: {
presetType: {
type: String,
required: true,
},
data() {
return {
todayBarStyles: '',
todayBarReady: false,
};
currentDate: {
type: Date,
required: true,
},
mounted() {
eventHub.$on('epicsListRendered', this.handleEpicsListRender);
timeframeItem: {
type: [Date, Object],
required: true,
},
beforeDestroy() {
eventHub.$off('epicsListRendered', this.handleEpicsListRender);
},
data() {
return {
todayBarStyles: '',
todayBarReady: false,
};
},
mounted() {
eventHub.$on('epicsListRendered', this.handleEpicsListRender);
eventHub.$on('epicsListScrolled', this.handleEpicsListScroll);
},
beforeDestroy() {
eventHub.$off('epicsListRendered', this.handleEpicsListRender);
eventHub.$off('epicsListScrolled', this.handleEpicsListScroll);
},
methods: {
/**
* This method takes height of current shell
* and renders vertical line over the area where
* today falls in current timeline
*/
handleEpicsListRender({ height }) {
let left = 0;
// Get total days of current timeframe Item and then
// get size in % from current date and days in range
// based on the current presetType
if (this.presetType === PRESET_TYPES.QUARTERS) {
left = Math.floor(
dayInQuarter(this.currentDate, this.timeframeItem.range) /
totalDaysInQuarter(this.timeframeItem.range) *
100,
);
} else if (this.presetType === PRESET_TYPES.MONTHS) {
left = Math.floor(this.currentDate.getDate() / totalDaysInMonth(this.timeframeItem) * 100);
} else if (this.presetType === PRESET_TYPES.WEEKS) {
left = Math.floor(((this.currentDate.getDay() + 1) / 7 * 100) - 7);
}
// We add 20 to height to ensure that
// today indicator goes past the bottom
// edge of the browser even when
// scrollbar is present
this.todayBarStyles = {
height: `${height + 20}px`,
left: `${left}%`,
};
this.todayBarReady = true;
},
methods: {
/**
* This method takes height of current shell
* and renders vertical line over the area where
* today falls in current timeline
*/
handleEpicsListRender({ height }) {
// Get total days of current timeframe Item
const daysInMonth = totalDaysInMonth(this.timeframeItem);
// Get size in % from current date and days in month.
const left = Math.floor((this.currentDate.getDate() / daysInMonth) * 100);
handleEpicsListScroll() {
const indicatorX = this.$el.getBoundingClientRect().x;
const rootOffsetLeft = this.$root.$el.offsetLeft;
// We add 20 to height to ensure that
// today indicator goes past the bottom
// edge of the browser even when
// scrollbar is present
this.todayBarStyles = {
height: `${height + 20}px`,
left: `${left}%`,
};
this.todayBarReady = true;
},
// 3px to compensate size of bubble on top of Indicator
this.todayBarReady = (indicatorX - rootOffsetLeft) >= (EPIC_DETAILS_CELL_WIDTH + 3);
},
};
},
};
</script>
<template>
<span
v-if="todayBarReady"
class="today-bar"
:class="{ 'invisible': !todayBarReady }"
:style="todayBarStyles"
>
</span>
......
......@@ -2,15 +2,29 @@ import Vue from 'vue';
import epicsListEmptyComponent from 'ee/roadmap/components/epics_list_empty.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { mockTimeframe, mockSvgPath, mockNewEpicEndpoint } from '../mock_data';
import { PRESET_TYPES } from 'ee/roadmap/constants';
const createComponent = (hasFiltersApplied = false) => {
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import {
mockTimeframeQuarters,
mockTimeframeMonths,
mockTimeframeWeeks,
mockSvgPath,
mockNewEpicEndpoint,
} from '../mock_data';
const createComponent = ({
hasFiltersApplied = false,
presetType = PRESET_TYPES.MONTHS,
timeframeStart = mockTimeframeMonths[0],
timeframeEnd = mockTimeframeMonths[mockTimeframeMonths.length - 1],
}) => {
const Component = Vue.extend(epicsListEmptyComponent);
return mountComponent(Component, {
timeframeStart: mockTimeframe[0],
timeframeEnd: mockTimeframe[mockTimeframe.length - 1],
presetType,
timeframeStart,
timeframeEnd,
emptyStateIllustrationPath: mockSvgPath,
newEpicEndpoint: mockNewEpicEndpoint,
hasFiltersApplied,
......@@ -21,7 +35,7 @@ describe('EpicsListEmptyComponent', () => {
let vm;
beforeEach(() => {
vm = createComponent();
vm = createComponent({});
});
afterEach(() => {
......@@ -46,25 +60,93 @@ describe('EpicsListEmptyComponent', () => {
});
describe('subMessage', () => {
it('returns default empty state sub-message', () => {
expect(vm.subMessage).toBe('To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown &ndash; from Nov 1, 2017 to Apr 30, 2018.');
describe('with presetType `QUARTERS`', () => {
beforeEach(() => {
vm.presetType = PRESET_TYPES.QUARTERS;
vm.timeframeStart = mockTimeframeQuarters[0];
vm.timeframeEnd = mockTimeframeQuarters[mockTimeframeQuarters.length - 1];
});
it('returns default empty state sub-message when `hasFiltersApplied` props is false', done => {
Vue.nextTick()
.then(() => {
expect(vm.subMessage).toBe('To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. In the quarters view, only epics in the past quarter, current quarter, and next 4 quarters are shown &ndash; from Oct 1, 2017 to Mar 31, 2019.');
})
.then(done)
.catch(done.fail);
});
it('returns empty state sub-message when `hasFiltersApplied` prop is true', done => {
vm.hasFiltersApplied = true;
Vue.nextTick()
.then(() => {
expect(vm.subMessage).toBe('To widen your search, change or remove filters. In the quarters view, only epics in the past quarter, current quarter, and next 4 quarters are shown &ndash; from Oct 1, 2017 to Mar 31, 2019.');
})
.then(done)
.catch(done.fail);
});
});
it('returns empty state sub-message when `hasFiltersApplied` prop is true', done => {
vm.hasFiltersApplied = true;
Vue.nextTick()
.then(() => {
expect(vm.subMessage).toBe('To widen your search, change or remove filters. Only epics in the past 3 months and the next 3 months are shown &ndash; from Nov 1, 2017 to Apr 30, 2018.');
})
.then(done)
.catch(done.fail);
describe('with presetType `MONTHS`', () => {
beforeEach(() => {
vm.presetType = PRESET_TYPES.MONTHS;
});
it('returns default empty state sub-message when `hasFiltersApplied` props is false', done => {
Vue.nextTick()
.then(() => {
expect(vm.subMessage).toBe('To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. In the months view, only epics in the past month, current month, and next 5 months are shown &ndash; from Dec 1, 2017 to Jun 30, 2018.');
})
.then(done)
.catch(done.fail);
});
it('returns empty state sub-message when `hasFiltersApplied` prop is true', done => {
vm.hasFiltersApplied = true;
Vue.nextTick()
.then(() => {
expect(vm.subMessage).toBe('To widen your search, change or remove filters. In the months view, only epics in the past month, current month, and next 5 months are shown &ndash; from Dec 1, 2017 to Jun 30, 2018.');
})
.then(done)
.catch(done.fail);
});
});
describe('with presetType `WEEKS`', () => {
beforeEach(() => {
const timeframeEnd = mockTimeframeWeeks[mockTimeframeWeeks.length - 1];
timeframeEnd.setDate(timeframeEnd.getDate() + 6);
vm.presetType = PRESET_TYPES.WEEKS;
vm.timeframeStart = mockTimeframeWeeks[0];
vm.timeframeEnd = timeframeEnd;
});
it('returns default empty state sub-message when `hasFiltersApplied` props is false', done => {
Vue.nextTick()
.then(() => {
expect(vm.subMessage).toBe('To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. In the weeks view, only epics in the past week, current week, and next 4 weeks are shown &ndash; from Dec 24, 2017 to Feb 9, 2018.');
})
.then(done)
.catch(done.fail);
});
it('returns empty state sub-message when `hasFiltersApplied` prop is true', done => {
vm.hasFiltersApplied = true;
Vue.nextTick()
.then(() => {
expect(vm.subMessage).toBe('To widen your search, change or remove filters. In the weeks view, only epics in the past week, current week, and next 4 weeks are shown &ndash; from Dec 24, 2017 to Feb 15, 2018.');
})
.then(done)
.catch(done.fail);
});
});
});
describe('timeframeRange', () => {
it('returns correct timeframe startDate and endDate in words', () => {
expect(vm.timeframeRange.startDate).toBe('Nov 1, 2017');
expect(vm.timeframeRange.endDate).toBe('Apr 30, 2018');
expect(vm.timeframeRange.startDate).toBe('Dec 1, 2017');
expect(vm.timeframeRange.endDate).toBe('Jun 30, 2018');
});
});
});
......
......@@ -3,18 +3,22 @@ import Vue from 'vue';
import roadmapTimelineSectionComponent from 'ee/roadmap/components/roadmap_timeline_section.vue';
import eventHub from 'ee/roadmap/event_hub';
import { PRESET_TYPES } from 'ee/roadmap/constants';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { mockEpic, mockTimeframe, mockShellWidth } from '../mock_data';
import { mockEpic, mockTimeframeMonths, mockShellWidth } from '../mock_data';
const createComponent = ({
presetType = PRESET_TYPES.MONTHS,
epics = [mockEpic],
timeframe = mockTimeframe,
timeframe = mockTimeframeMonths,
shellWidth = mockShellWidth,
listScrollable = false,
}) => {
const Component = Vue.extend(roadmapTimelineSectionComponent);
return mountComponent(Component, {
presetType,
epics,
timeframe,
shellWidth,
......
......@@ -2,23 +2,26 @@ import Vue from 'vue';
import timelineTodayIndicatorComponent from 'ee/roadmap/components/timeline_today_indicator.vue';
import eventHub from 'ee/roadmap/event_hub';
import { PRESET_TYPES } from 'ee/roadmap/constants';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { mockTimeframe } from '../mock_data';
import { mockTimeframeMonths } from '../mock_data';
const mockCurrentDate = new Date(
mockTimeframe[0].getFullYear(),
mockTimeframe[0].getMonth(),
mockTimeframeMonths[0].getFullYear(),
mockTimeframeMonths[0].getMonth(),
15,
);
const createComponent = ({
presetType = PRESET_TYPES.MONTHS,
currentDate = mockCurrentDate,
timeframeItem = mockTimeframe[0],
timeframeItem = mockTimeframeMonths[0],
}) => {
const Component = Vue.extend(timelineTodayIndicatorComponent);
return mountComponent(Component, {
presetType,
currentDate,
timeframeItem,
});
......@@ -48,7 +51,7 @@ describe('TimelineTodayIndicatorComponent', () => {
});
const stylesObj = vm.todayBarStyles;
expect(stylesObj.height).toBe('120px');
expect(stylesObj.left).toBe('50%');
expect(stylesObj.left).toBe('48%');
expect(vm.todayBarReady).toBe(true);
});
});
......
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