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

Update to support presetType

parent 806f78fc
<script> <script>
import { s__, sprintf } from '~/locale'; import { s__, sprintf } from '~/locale';
import { dateInWords } from '~/lib/utils/datetime_utility'; 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 { import NewEpic from '../../epics/new_epic/components/new_epic.vue';
components: {
NewEpic, 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: { newEpicEndpoint: {
timeframeStart: { type: String,
type: Date, required: true,
required: true,
},
timeframeEnd: {
type: Date,
required: true,
},
hasFiltersApplied: {
type: Boolean,
required: true,
},
newEpicEndpoint: {
type: String,
required: true,
},
emptyStateIllustrationPath: {
type: String,
required: true,
},
}, },
computed: { emptyStateIllustrationPath: {
timeframeRange() { type: String,
const startDate = dateInWords( 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, this.timeframeStart,
true, true,
this.timeframeStart.getFullYear() === this.timeframeEnd.getFullYear(), 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 = dateInWords(
startDate, this.timeframeStart,
endDate, true,
}; this.timeframeStart.getFullYear() === end.getFullYear(),
}, );
message() { endDate = dateInWords(end, true);
if (this.hasFiltersApplied) { }
return s__('GroupRoadmap|Sorry, no epics matched your search');
} return {
return s__('GroupRoadmap|The roadmap shows the progress of your epics along a timeline'); startDate,
}, endDate,
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}.'), { message() {
startDate: this.timeframeRange.startDate, if (this.hasFiltersApplied) {
endDate: this.timeframeRange.endDate, return s__('GroupRoadmap|Sorry, no epics matched your search');
}); }
} return s__('GroupRoadmap|The roadmap shows the progress of your epics along a timeline');
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}.'), { },
subMessage() {
if (this.hasFiltersApplied) {
return sprintf(PRESET_DEFAULTS[this.presetType].emptyStateWithFilters, {
startDate: this.timeframeRange.startDate, startDate: this.timeframeRange.startDate,
endDate: this.timeframeRange.endDate, endDate: this.timeframeRange.endDate,
}); });
}, }
return sprintf(PRESET_DEFAULTS[this.presetType].emptyStateDefault, {
startDate: this.timeframeRange.startDate,
endDate: this.timeframeRange.endDate,
});
}, },
}; },
};
</script> </script>
<template> <template>
......
<script> <script>
import eventHub from '../event_hub'; import eventHub from '../event_hub';
import { PRESET_TYPES } from '../constants';
import SectionMixin from '../mixins/section_mixin'; 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 { export default {
components: { components: {
timelineHeaderItem, QuartersHeaderItem,
MonthsHeaderItem,
WeeksHeaderItem,
}, },
mixins: [ mixins: [
SectionMixin, SectionMixin,
], ],
props: { props: {
presetType: {
type: String,
required: true,
},
epics: { epics: {
type: Array, type: Array,
required: true, required: true,
...@@ -35,6 +45,18 @@ ...@@ -35,6 +45,18 @@
scrolledHeaderClass: '', 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() { mounted() {
eventHub.$on('epicsListScrolled', this.handleEpicsListScroll); eventHub.$on('epicsListScrolled', this.handleEpicsListScroll);
}, },
...@@ -57,7 +79,8 @@ ...@@ -57,7 +79,8 @@
:style="sectionContainerStyles" :style="sectionContainerStyles"
> >
<span class="timeline-header-blank"></span> <span class="timeline-header-blank"></span>
<timeline-header-item <component
:is="headerItemComponentForPreset"
v-for="(timeframeItem, index) in timeframe" v-for="(timeframeItem, index) in timeframe"
:key="index" :key="index"
:timeframe-index="index" :timeframe-index="index"
......
<script> <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 { import eventHub from '../event_hub';
props: {
currentDate: { export default {
type: Date, props: {
required: true, presetType: {
}, type: String,
timeframeItem: { required: true,
type: Date,
required: true,
},
}, },
data() { currentDate: {
return { type: Date,
todayBarStyles: '', required: true,
todayBarReady: false,
};
}, },
mounted() { timeframeItem: {
eventHub.$on('epicsListRendered', this.handleEpicsListRender); 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: { handleEpicsListScroll() {
/** const indicatorX = this.$el.getBoundingClientRect().x;
* This method takes height of current shell const rootOffsetLeft = this.$root.$el.offsetLeft;
* 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);
// We add 20 to height to ensure that // 3px to compensate size of bubble on top of Indicator
// today indicator goes past the bottom this.todayBarReady = (indicatorX - rootOffsetLeft) >= (EPIC_DETAILS_CELL_WIDTH + 3);
// edge of the browser even when
// scrollbar is present
this.todayBarStyles = {
height: `${height + 20}px`,
left: `${left}%`,
};
this.todayBarReady = true;
},
}, },
}; },
};
</script> </script>
<template> <template>
<span <span
v-if="todayBarReady"
class="today-bar" class="today-bar"
:class="{ 'invisible': !todayBarReady }"
:style="todayBarStyles" :style="todayBarStyles"
> >
</span> </span>
......
...@@ -2,15 +2,29 @@ import Vue from 'vue'; ...@@ -2,15 +2,29 @@ import Vue from 'vue';
import epicsListEmptyComponent from 'ee/roadmap/components/epics_list_empty.vue'; import epicsListEmptyComponent from 'ee/roadmap/components/epics_list_empty.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper'; import { PRESET_TYPES } from 'ee/roadmap/constants';
import { mockTimeframe, mockSvgPath, mockNewEpicEndpoint } from '../mock_data';
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); const Component = Vue.extend(epicsListEmptyComponent);
return mountComponent(Component, { return mountComponent(Component, {
timeframeStart: mockTimeframe[0], presetType,
timeframeEnd: mockTimeframe[mockTimeframe.length - 1], timeframeStart,
timeframeEnd,
emptyStateIllustrationPath: mockSvgPath, emptyStateIllustrationPath: mockSvgPath,
newEpicEndpoint: mockNewEpicEndpoint, newEpicEndpoint: mockNewEpicEndpoint,
hasFiltersApplied, hasFiltersApplied,
...@@ -21,7 +35,7 @@ describe('EpicsListEmptyComponent', () => { ...@@ -21,7 +35,7 @@ describe('EpicsListEmptyComponent', () => {
let vm; let vm;
beforeEach(() => { beforeEach(() => {
vm = createComponent(); vm = createComponent({});
}); });
afterEach(() => { afterEach(() => {
...@@ -46,25 +60,93 @@ describe('EpicsListEmptyComponent', () => { ...@@ -46,25 +60,93 @@ describe('EpicsListEmptyComponent', () => {
}); });
describe('subMessage', () => { describe('subMessage', () => {
it('returns default empty state sub-message', () => { describe('with presetType `QUARTERS`', () => {
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.'); 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 => { describe('with presetType `MONTHS`', () => {
vm.hasFiltersApplied = true; beforeEach(() => {
Vue.nextTick() vm.presetType = PRESET_TYPES.MONTHS;
.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.');
}) it('returns default empty state sub-message when `hasFiltersApplied` props is false', done => {
.then(done) Vue.nextTick()
.catch(done.fail); .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', () => { describe('timeframeRange', () => {
it('returns correct timeframe startDate and endDate in words', () => { it('returns correct timeframe startDate and endDate in words', () => {
expect(vm.timeframeRange.startDate).toBe('Nov 1, 2017'); expect(vm.timeframeRange.startDate).toBe('Dec 1, 2017');
expect(vm.timeframeRange.endDate).toBe('Apr 30, 2018'); expect(vm.timeframeRange.endDate).toBe('Jun 30, 2018');
}); });
}); });
}); });
......
...@@ -3,18 +3,22 @@ import Vue from 'vue'; ...@@ -3,18 +3,22 @@ import Vue from 'vue';
import roadmapTimelineSectionComponent from 'ee/roadmap/components/roadmap_timeline_section.vue'; import roadmapTimelineSectionComponent from 'ee/roadmap/components/roadmap_timeline_section.vue';
import eventHub from 'ee/roadmap/event_hub'; import eventHub from 'ee/roadmap/event_hub';
import { PRESET_TYPES } from 'ee/roadmap/constants';
import mountComponent from 'spec/helpers/vue_mount_component_helper'; 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 = ({ const createComponent = ({
presetType = PRESET_TYPES.MONTHS,
epics = [mockEpic], epics = [mockEpic],
timeframe = mockTimeframe, timeframe = mockTimeframeMonths,
shellWidth = mockShellWidth, shellWidth = mockShellWidth,
listScrollable = false, listScrollable = false,
}) => { }) => {
const Component = Vue.extend(roadmapTimelineSectionComponent); const Component = Vue.extend(roadmapTimelineSectionComponent);
return mountComponent(Component, { return mountComponent(Component, {
presetType,
epics, epics,
timeframe, timeframe,
shellWidth, shellWidth,
......
...@@ -2,23 +2,26 @@ import Vue from 'vue'; ...@@ -2,23 +2,26 @@ import Vue from 'vue';
import timelineTodayIndicatorComponent from 'ee/roadmap/components/timeline_today_indicator.vue'; import timelineTodayIndicatorComponent from 'ee/roadmap/components/timeline_today_indicator.vue';
import eventHub from 'ee/roadmap/event_hub'; import eventHub from 'ee/roadmap/event_hub';
import { PRESET_TYPES } from 'ee/roadmap/constants';
import mountComponent from 'spec/helpers/vue_mount_component_helper'; import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { mockTimeframe } from '../mock_data'; import { mockTimeframeMonths } from '../mock_data';
const mockCurrentDate = new Date( const mockCurrentDate = new Date(
mockTimeframe[0].getFullYear(), mockTimeframeMonths[0].getFullYear(),
mockTimeframe[0].getMonth(), mockTimeframeMonths[0].getMonth(),
15, 15,
); );
const createComponent = ({ const createComponent = ({
presetType = PRESET_TYPES.MONTHS,
currentDate = mockCurrentDate, currentDate = mockCurrentDate,
timeframeItem = mockTimeframe[0], timeframeItem = mockTimeframeMonths[0],
}) => { }) => {
const Component = Vue.extend(timelineTodayIndicatorComponent); const Component = Vue.extend(timelineTodayIndicatorComponent);
return mountComponent(Component, { return mountComponent(Component, {
presetType,
currentDate, currentDate,
timeframeItem, timeframeItem,
}); });
...@@ -48,7 +51,7 @@ describe('TimelineTodayIndicatorComponent', () => { ...@@ -48,7 +51,7 @@ describe('TimelineTodayIndicatorComponent', () => {
}); });
const stylesObj = vm.todayBarStyles; const stylesObj = vm.todayBarStyles;
expect(stylesObj.height).toBe('120px'); expect(stylesObj.height).toBe('120px');
expect(stylesObj.left).toBe('50%'); expect(stylesObj.left).toBe('48%');
expect(vm.todayBarReady).toBe(true); 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