Commit 97811a03 authored by Kushal Pandya's avatar Kushal Pandya

Merge branch 'only-render-epic-item-timeline-once-every-row' into 'master'

Render EpicItemTimeline only once every row in Roadmap

See merge request gitlab-org/gitlab!50652
parents f9988144 0a2e6cc7
...@@ -5,15 +5,21 @@ import EpicItemDetails from './epic_item_details.vue'; ...@@ -5,15 +5,21 @@ import EpicItemDetails from './epic_item_details.vue';
import EpicItemTimeline from './epic_item_timeline.vue'; import EpicItemTimeline from './epic_item_timeline.vue';
import CommonMixin from '../mixins/common_mixin'; import CommonMixin from '../mixins/common_mixin';
import QuartersPresetMixin from '../mixins/quarters_preset_mixin';
import MonthsPresetMixin from '../mixins/months_preset_mixin';
import WeeksPresetMixin from '../mixins/weeks_preset_mixin';
import CurrentDayIndicator from './current_day_indicator.vue';
import { EPIC_HIGHLIGHT_REMOVE_AFTER } from '../constants'; import { EPIC_HIGHLIGHT_REMOVE_AFTER } from '../constants';
export default { export default {
components: { components: {
CurrentDayIndicator,
EpicItemDetails, EpicItemDetails,
EpicItemTimeline, EpicItemTimeline,
}, },
mixins: [CommonMixin], mixins: [CommonMixin, QuartersPresetMixin, MonthsPresetMixin, WeeksPresetMixin],
props: { props: {
presetType: { presetType: {
type: String, type: String,
...@@ -53,6 +59,14 @@ export default { ...@@ -53,6 +59,14 @@ export default {
required: true, required: true,
}, },
}, },
data() {
const currentDate = new Date();
currentDate.setHours(0, 0, 0, 0);
return {
currentDate,
};
},
computed: { computed: {
/** /**
* In case Epic start date is out of range * In case Epic start date is out of range
...@@ -122,15 +136,33 @@ export default { ...@@ -122,15 +136,33 @@ export default {
:has-filters-applied="hasFiltersApplied" :has-filters-applied="hasFiltersApplied"
:is-children-empty="isChildrenEmpty" :is-children-empty="isChildrenEmpty"
/> />
<epic-item-timeline <span
v-for="(timeframeItem, index) in timeframe" v-for="(timeframeItem, index) in timeframe"
:key="index" :key="index"
:preset-type="presetType" class="epic-timeline-cell"
:timeframe="timeframe" data-qa-selector="epic_timeline_cell"
:timeframe-item="timeframeItem" >
:epic="epic" <!--
:client-width="clientWidth" CurrentDayIndicator internally checks if a given timeframeItem is for today.
/> However, we are doing a duplicate check (index === todaysIndex) here -
so that the the indicator is rendered once.
This optimization is only done in this component(EpicItem). -->
<current-day-indicator
v-if="index === todaysIndex"
:preset-type="presetType"
:timeframe-item="timeframeItem"
/>
<epic-item-timeline
v-if="index === roadmapItemIndex"
:preset-type="presetType"
:timeframe="timeframe"
:timeframe-item="timeframeItem"
:epic="epic"
:start-date="startDate"
:end-date="endDate"
:client-width="clientWidth"
/>
</span>
</div> </div>
<epic-item-container <epic-item-container
v-if="hasChildrenToShow" v-if="hasChildrenToShow"
......
...@@ -8,8 +8,6 @@ import QuartersPresetMixin from '../mixins/quarters_preset_mixin'; ...@@ -8,8 +8,6 @@ import QuartersPresetMixin from '../mixins/quarters_preset_mixin';
import MonthsPresetMixin from '../mixins/months_preset_mixin'; import MonthsPresetMixin from '../mixins/months_preset_mixin';
import WeeksPresetMixin from '../mixins/weeks_preset_mixin'; import WeeksPresetMixin from '../mixins/weeks_preset_mixin';
import CurrentDayIndicator from './current_day_indicator.vue';
import { import {
EPIC_DETAILS_CELL_WIDTH, EPIC_DETAILS_CELL_WIDTH,
PERCENTAGE, PERCENTAGE,
...@@ -21,7 +19,6 @@ import { ...@@ -21,7 +19,6 @@ import {
export default { export default {
cellWidth: TIMELINE_CELL_MIN_WIDTH, cellWidth: TIMELINE_CELL_MIN_WIDTH,
components: { components: {
CurrentDayIndicator,
GlIcon, GlIcon,
GlPopover, GlPopover,
GlProgressBar, GlProgressBar,
...@@ -44,6 +41,14 @@ export default { ...@@ -44,6 +41,14 @@ export default {
type: Object, type: Object,
required: true, required: true,
}, },
startDate: {
type: Date,
required: true,
},
endDate: {
type: Date,
required: true,
},
clientWidth: { clientWidth: {
type: Number, type: Number,
required: false, required: false,
...@@ -51,75 +56,19 @@ export default { ...@@ -51,75 +56,19 @@ export default {
}, },
}, },
computed: { computed: {
startDateValues() {
const { startDate } = this.epic;
return {
day: startDate.getDay(),
date: startDate.getDate(),
month: startDate.getMonth(),
year: startDate.getFullYear(),
time: startDate.getTime(),
};
},
endDateValues() {
const { endDate } = this.epic;
return {
day: endDate.getDay(),
date: endDate.getDate(),
month: endDate.getMonth(),
year: endDate.getFullYear(),
time: endDate.getTime(),
};
},
/**
* In case Epic start date is out of range
* we need to use original date instead of proxy date
*/
startDate() {
if (this.epic.startDateOutOfRange) {
return this.epic.originalStartDate;
}
return this.epic.startDate;
},
/**
* In case Epic end date is out of range
* we need to use original date instead of proxy date
*/
endDate() {
if (this.epic.endDateOutOfRange) {
return this.epic.originalEndDate;
}
return this.epic.endDate;
},
hasStartDate() {
if (this.presetTypeQuarters) {
return this.hasStartDateForQuarter(this.timeframeItem);
} else if (this.presetTypeMonths) {
return this.hasStartDateForMonth(this.timeframeItem);
} else if (this.presetTypeWeeks) {
return this.hasStartDateForWeek(this.timeframeItem);
}
return false;
},
timelineBarInnerStyle() { timelineBarInnerStyle() {
return { return {
maxWidth: `${this.clientWidth - EPIC_DETAILS_CELL_WIDTH}px`, maxWidth: `${this.clientWidth - EPIC_DETAILS_CELL_WIDTH}px`,
}; };
}, },
timelineBarWidth() { timelineBarWidth() {
if (this.hasStartDate) { if (this.presetType === PRESET_TYPES.QUARTERS) {
if (this.presetType === PRESET_TYPES.QUARTERS) { return this.getTimelineBarWidthForQuarters(this.epic);
return this.getTimelineBarWidthForQuarters(this.epic); } else if (this.presetType === PRESET_TYPES.MONTHS) {
} else if (this.presetType === PRESET_TYPES.MONTHS) { return this.getTimelineBarWidthForMonths();
return this.getTimelineBarWidthForMonths();
} else if (this.presetType === PRESET_TYPES.WEEKS) {
return this.getTimelineBarWidthForWeeks();
}
} }
return Infinity;
return this.getTimelineBarWidthForWeeks();
}, },
isTimelineBarSmall() { isTimelineBarSmall() {
return this.timelineBarWidth < SMALL_TIMELINE_BAR; return this.timelineBarWidth < SMALL_TIMELINE_BAR;
...@@ -163,37 +112,33 @@ export default { ...@@ -163,37 +112,33 @@ export default {
</script> </script>
<template> <template>
<span class="epic-timeline-cell" data-qa-selector="epic_timeline_cell"> <div class="gl-relative">
<current-day-indicator :preset-type="presetType" :timeframe-item="timeframeItem" /> <a
<div class="gl-relative"> :id="generateKey(epic)"
<a :href="epic.webUrl"
v-if="hasStartDate" :style="timelineBarStyles(epic)"
:id="generateKey(epic)" :class="{ 'epic-bar-child-epic': epic.isChildEpic }"
:href="epic.webUrl" class="epic-bar rounded"
:style="timelineBarStyles(epic)" >
:class="{ 'epic-bar-child-epic': epic.isChildEpic }" <div class="epic-bar-inner gl-px-3 gl-py-2" :style="timelineBarInnerStyle">
class="epic-bar rounded" <p class="epic-bar-title gl-text-truncate gl-m-0">{{ timelineBarTitle }}</p>
>
<div class="epic-bar-inner px-2 py-1" :style="timelineBarInnerStyle">
<p class="epic-bar-title text-nowrap text-truncate m-0">{{ timelineBarTitle }}</p>
<div v-if="!isTimelineBarSmall" class="d-flex align-items-center"> <div v-if="!isTimelineBarSmall" class="gl-display-flex gl-align-items-center">
<gl-progress-bar <gl-progress-bar
class="epic-bar-progress flex-grow-1 mr-1" class="epic-bar-progress gl-flex-grow-1 gl-mr-2"
:value="epicWeightPercentage" :value="epicWeightPercentage"
aria-hidden="true" aria-hidden="true"
/> />
<div class="gl-font-sm d-flex align-items-center text-nowrap"> <div class="gl-font-sm gl-display-flex gl-align-items-center gl-white-space-nowrap">
<gl-icon class="gl-mr-1" :size="12" name="weight" /> <gl-icon class="gl-mr-1" :size="12" name="weight" />
<p class="m-0" :aria-label="epicWeightPercentageText">{{ epicWeightPercentage }}%</p> <p class="gl-m-0" :aria-label="epicWeightPercentageText">{{ epicWeightPercentage }}%</p>
</div>
</div> </div>
</div> </div>
</a> </div>
<gl-popover :target="generateKey(epic)" :title="epic.title" triggers="hover" placement="left"> </a>
<p class="text-secondary m-0">{{ timeframeString(epic) }}</p> <gl-popover :target="generateKey(epic)" :title="epic.title" triggers="hover" placement="left">
<p class="m-0">{{ popoverWeightText }}</p> <p class="gl-text-gray-500 gl-m-0">{{ timeframeString(epic) }}</p>
</gl-popover> <p class="gl-m-0">{{ popoverWeightText }}</p>
</div> </gl-popover>
</span> </div>
</template> </template>
...@@ -38,38 +38,6 @@ export default { ...@@ -38,38 +38,6 @@ export default {
}; };
}, },
computed: { computed: {
startDateValues() {
const { startDate } = this.milestone;
return {
day: startDate.getDay(),
date: startDate.getDate(),
month: startDate.getMonth(),
year: startDate.getFullYear(),
time: startDate.getTime(),
};
},
endDateValues() {
const { endDate } = this.milestone;
return {
day: endDate.getDay(),
date: endDate.getDate(),
month: endDate.getMonth(),
year: endDate.getFullYear(),
time: endDate.getTime(),
};
},
hasStartDate() {
if (this.presetTypeQuarters) {
return this.hasStartDateForQuarter(this.timeframeItem);
} else if (this.presetTypeMonths) {
return this.hasStartDateForMonth(this.timeframeItem);
} else if (this.presetTypeWeeks) {
return this.hasStartDateForWeek(this.timeframeItem);
}
return false;
},
startDate() { startDate() {
return this.milestone.startDateOutOfRange return this.milestone.startDateOutOfRange
? this.milestone.originalStartDate ? this.milestone.originalStartDate
......
...@@ -10,6 +10,31 @@ import { PRESET_TYPES, DAYS_IN_WEEK } from '../constants'; ...@@ -10,6 +10,31 @@ import { PRESET_TYPES, DAYS_IN_WEEK } from '../constants';
export default { export default {
computed: { computed: {
roadmapItem() {
return this.epic ? this.epic : this.milestone;
},
startDateValues() {
const { startDate } = this.roadmapItem;
return {
day: startDate.getDay(),
date: startDate.getDate(),
month: startDate.getMonth(),
year: startDate.getFullYear(),
time: startDate.getTime(),
};
},
endDateValues() {
const { endDate } = this.roadmapItem;
return {
day: endDate.getDay(),
date: endDate.getDate(),
month: endDate.getMonth(),
year: endDate.getFullYear(),
time: endDate.getTime(),
};
},
presetTypeQuarters() { presetTypeQuarters() {
return this.presetType === PRESET_TYPES.QUARTERS; return this.presetType === PRESET_TYPES.QUARTERS;
}, },
...@@ -20,27 +45,51 @@ export default { ...@@ -20,27 +45,51 @@ export default {
return this.presetType === PRESET_TYPES.WEEKS; return this.presetType === PRESET_TYPES.WEEKS;
}, },
hasToday() { hasToday() {
return this.isTimeframeForToday(this.timeframeItem);
},
hasStartDate() {
if (this.presetTypeQuarters) {
return this.hasStartDateForQuarter(this.timeframeItem);
} else if (this.presetTypeMonths) {
return this.hasStartDateForMonth(this.timeframeItem);
} else if (this.presetTypeWeeks) {
return this.hasStartDateForWeek(this.timeframeItem);
}
return false;
},
todaysIndex() {
return this.timeframe.findIndex((item) => this.isTimeframeForToday(item));
},
roadmapItemIndex() {
return this.timeframe.findIndex((item) => {
if (this.presetTypeQuarters) {
return this.hasStartDateForQuarter(item);
} else if (this.presetTypeMonths) {
return this.hasStartDateForMonth(item);
} else if (this.presetTypeWeeks) {
return this.hasStartDateForWeek(item);
}
return false;
});
},
},
methods: {
isTimeframeForToday(timeframeItem) {
if (this.presetTypeQuarters) { if (this.presetTypeQuarters) {
return ( return (
this.currentDate >= this.timeframeItem.range[0] && this.currentDate >= timeframeItem.range[0] && this.currentDate <= timeframeItem.range[2]
this.currentDate <= this.timeframeItem.range[2]
); );
} else if (this.presetTypeMonths) { } else if (this.presetTypeMonths) {
return ( return (
this.currentDate.getMonth() === this.timeframeItem.getMonth() && this.currentDate.getMonth() === timeframeItem.getMonth() &&
this.currentDate.getFullYear() === this.timeframeItem.getFullYear() this.currentDate.getFullYear() === timeframeItem.getFullYear()
); );
} }
const timeframeItem = new Date(this.timeframeItem.getTime()); const itemTime = new Date(timeframeItem.getTime());
const headerSubItems = new Array(7) const headerSubItems = new Array(7)
.fill() .fill()
.map( .map(
(val, i) => (_, i) => new Date(itemTime.getFullYear(), itemTime.getMonth(), itemTime.getDate() + i),
new Date(
timeframeItem.getFullYear(),
timeframeItem.getMonth(),
timeframeItem.getDate() + i,
),
); );
return ( return (
...@@ -48,8 +97,6 @@ export default { ...@@ -48,8 +97,6 @@ export default {
this.currentDate.getTime() <= headerSubItems[headerSubItems.length - 1].getTime() this.currentDate.getTime() <= headerSubItems[headerSubItems.length - 1].getTime()
); );
}, },
},
methods: {
getIndicatorStyles() { getIndicatorStyles() {
let left; let left;
...@@ -104,25 +151,24 @@ export default { ...@@ -104,25 +151,24 @@ export default {
timelineBarStyles(roadmapItem) { timelineBarStyles(roadmapItem) {
let barStyles = {}; let barStyles = {};
if (this.hasStartDate) { if (this.presetTypeQuarters) {
if (this.presetTypeQuarters) { // CSS properties are a false positive: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/24
// CSS properties are a false positive: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/24 // eslint-disable-next-line @gitlab/require-i18n-strings
// eslint-disable-next-line @gitlab/require-i18n-strings barStyles = `width: ${this.getTimelineBarWidthForQuarters(
barStyles = `width: ${this.getTimelineBarWidthForQuarters( roadmapItem,
roadmapItem, )}px; ${this.getTimelineBarStartOffsetForQuarters(roadmapItem)}`;
)}px; ${this.getTimelineBarStartOffsetForQuarters(roadmapItem)}`; } else if (this.presetTypeMonths) {
} else if (this.presetTypeMonths) { // eslint-disable-next-line @gitlab/require-i18n-strings
// eslint-disable-next-line @gitlab/require-i18n-strings barStyles = `width: ${this.getTimelineBarWidthForMonths()}px; ${this.getTimelineBarStartOffsetForMonths(
barStyles = `width: ${this.getTimelineBarWidthForMonths()}px; ${this.getTimelineBarStartOffsetForMonths( roadmapItem,
roadmapItem, )}`;
)}`; } else if (this.presetTypeWeeks) {
} else if (this.presetTypeWeeks) { // eslint-disable-next-line @gitlab/require-i18n-strings
// eslint-disable-next-line @gitlab/require-i18n-strings barStyles = `width: ${this.getTimelineBarWidthForWeeks()}px; ${this.getTimelineBarStartOffsetForWeeks(
barStyles = `width: ${this.getTimelineBarWidthForWeeks()}px; ${this.getTimelineBarStartOffsetForWeeks( roadmapItem,
roadmapItem, )}`;
)}`;
}
} }
return barStyles; return barStyles;
}, },
}, },
......
...@@ -9,6 +9,8 @@ import { PRESET_TYPES } from 'ee/roadmap/constants'; ...@@ -9,6 +9,8 @@ import { PRESET_TYPES } from 'ee/roadmap/constants';
import createStore from 'ee/roadmap/store'; import createStore from 'ee/roadmap/store';
import { getTimeframeForMonthsView } from 'ee/roadmap/utils/roadmap_utils'; import { getTimeframeForMonthsView } from 'ee/roadmap/utils/roadmap_utils';
import CurrentDayIndicator from 'ee/roadmap/components/current_day_indicator.vue';
import { import {
mockTimeframeInitialDate, mockTimeframeInitialDate,
mockEpic, mockEpic,
...@@ -54,6 +56,12 @@ const createComponent = ({ ...@@ -54,6 +56,12 @@ const createComponent = ({
childrenFlags, childrenFlags,
hasFiltersApplied, hasFiltersApplied,
}, },
data() {
return {
// Arbitrarily set the current date to be in timeframe[1] (2017-12-01)
currentDate: timeframe[1],
};
},
}); });
}; };
...@@ -179,5 +187,9 @@ describe('EpicItemComponent', () => { ...@@ -179,5 +187,9 @@ describe('EpicItemComponent', () => {
}); });
expect(wrapper.find('.epic-list-item-container').exists()).toBe(true); expect(wrapper.find('.epic-list-item-container').exists()).toBe(true);
}); });
it('renders current day indicator element', () => {
expect(wrapper.find(CurrentDayIndicator).exists()).toBe(true);
});
}); });
}); });
import { GlPopover, GlProgressBar } from '@gitlab/ui'; import { GlPopover, GlProgressBar } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import CurrentDayIndicator from 'ee/roadmap/components/current_day_indicator.vue';
import EpicItemTimeline from 'ee/roadmap/components/epic_item_timeline.vue'; import EpicItemTimeline from 'ee/roadmap/components/epic_item_timeline.vue';
import { PRESET_TYPES } from 'ee/roadmap/constants'; import { PRESET_TYPES } from 'ee/roadmap/constants';
import { getTimeframeForMonthsView } from 'ee/roadmap/utils/roadmap_utils'; import { getTimeframeForMonthsView } from 'ee/roadmap/utils/roadmap_utils';
...@@ -18,6 +17,8 @@ const createComponent = ({ ...@@ -18,6 +17,8 @@ const createComponent = ({
return shallowMount(EpicItemTimeline, { return shallowMount(EpicItemTimeline, {
propsData: { propsData: {
epic, epic,
startDate: epic.originalStartDate,
endDate: epic.originalEndDate,
presetType, presetType,
timeframe, timeframe,
timeframeItem, timeframeItem,
...@@ -82,10 +83,4 @@ describe('EpicItemTimelineComponent', () => { ...@@ -82,10 +83,4 @@ describe('EpicItemTimelineComponent', () => {
expect(wrapper.find(GlPopover).text()).toContain('- of - weight completed'); expect(wrapper.find(GlPopover).text()).toContain('- of - weight completed');
}); });
}); });
it('shows current day indicator element', () => {
wrapper = createComponent();
expect(wrapper.find(CurrentDayIndicator).exists()).toBe(true);
});
}); });
...@@ -22,6 +22,8 @@ describe('MonthsPresetMixin', () => { ...@@ -22,6 +22,8 @@ describe('MonthsPresetMixin', () => {
timeframe, timeframe,
timeframeItem, timeframeItem,
epic, epic,
startDate: epic.startDate,
endDate: epic.endDate,
}, },
}); });
}; };
......
...@@ -22,6 +22,8 @@ describe('QuartersPresetMixin', () => { ...@@ -22,6 +22,8 @@ describe('QuartersPresetMixin', () => {
timeframe, timeframe,
timeframeItem, timeframeItem,
epic, epic,
startDate: epic.startDate,
endDate: epic.endDate,
}, },
}); });
}; };
......
...@@ -22,6 +22,8 @@ describe('WeeksPresetMixin', () => { ...@@ -22,6 +22,8 @@ describe('WeeksPresetMixin', () => {
timeframe, timeframe,
timeframeItem, timeframeItem,
epic, epic,
startDate: epic.startDate,
endDate: epic.endDate,
}, },
}); });
}; };
......
...@@ -9,7 +9,7 @@ module QA ...@@ -9,7 +9,7 @@ module QA
element :epic_details_cell element :epic_details_cell
end end
view 'ee/app/assets/javascripts/roadmap/components/epic_item_timeline.vue' do view 'ee/app/assets/javascripts/roadmap/components/epic_item.vue' do
element :epic_timeline_cell element :epic_timeline_cell
end end
......
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