Commit 0a2e6cc7 authored by Eulyeon Ko's avatar Eulyeon Ko Committed by Kushal Pandya

Render EpicItemTimeline component conditionally

EpicItemTimeline should only render once for every row
in roadmap view.
parent 34d2b34a
......@@ -5,15 +5,21 @@ import EpicItemDetails from './epic_item_details.vue';
import EpicItemTimeline from './epic_item_timeline.vue';
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';
export default {
components: {
CurrentDayIndicator,
EpicItemDetails,
EpicItemTimeline,
},
mixins: [CommonMixin],
mixins: [CommonMixin, QuartersPresetMixin, MonthsPresetMixin, WeeksPresetMixin],
props: {
presetType: {
type: String,
......@@ -53,6 +59,14 @@ export default {
required: true,
},
},
data() {
const currentDate = new Date();
currentDate.setHours(0, 0, 0, 0);
return {
currentDate,
};
},
computed: {
/**
* In case Epic start date is out of range
......@@ -122,15 +136,33 @@ export default {
:has-filters-applied="hasFiltersApplied"
:is-children-empty="isChildrenEmpty"
/>
<epic-item-timeline
<span
v-for="(timeframeItem, index) in timeframe"
:key="index"
:preset-type="presetType"
:timeframe="timeframe"
:timeframe-item="timeframeItem"
:epic="epic"
:client-width="clientWidth"
/>
class="epic-timeline-cell"
data-qa-selector="epic_timeline_cell"
>
<!--
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>
<epic-item-container
v-if="hasChildrenToShow"
......
......@@ -8,8 +8,6 @@ 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_DETAILS_CELL_WIDTH,
PERCENTAGE,
......@@ -21,7 +19,6 @@ import {
export default {
cellWidth: TIMELINE_CELL_MIN_WIDTH,
components: {
CurrentDayIndicator,
GlIcon,
GlPopover,
GlProgressBar,
......@@ -44,6 +41,14 @@ export default {
type: Object,
required: true,
},
startDate: {
type: Date,
required: true,
},
endDate: {
type: Date,
required: true,
},
clientWidth: {
type: Number,
required: false,
......@@ -51,75 +56,19 @@ export default {
},
},
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() {
return {
maxWidth: `${this.clientWidth - EPIC_DETAILS_CELL_WIDTH}px`,
};
},
timelineBarWidth() {
if (this.hasStartDate) {
if (this.presetType === PRESET_TYPES.QUARTERS) {
return this.getTimelineBarWidthForQuarters(this.epic);
} else if (this.presetType === PRESET_TYPES.MONTHS) {
return this.getTimelineBarWidthForMonths();
} else if (this.presetType === PRESET_TYPES.WEEKS) {
return this.getTimelineBarWidthForWeeks();
}
if (this.presetType === PRESET_TYPES.QUARTERS) {
return this.getTimelineBarWidthForQuarters(this.epic);
} else if (this.presetType === PRESET_TYPES.MONTHS) {
return this.getTimelineBarWidthForMonths();
}
return Infinity;
return this.getTimelineBarWidthForWeeks();
},
isTimelineBarSmall() {
return this.timelineBarWidth < SMALL_TIMELINE_BAR;
......@@ -163,37 +112,33 @@ export default {
</script>
<template>
<span class="epic-timeline-cell" data-qa-selector="epic_timeline_cell">
<current-day-indicator :preset-type="presetType" :timeframe-item="timeframeItem" />
<div class="gl-relative">
<a
v-if="hasStartDate"
:id="generateKey(epic)"
:href="epic.webUrl"
:style="timelineBarStyles(epic)"
:class="{ 'epic-bar-child-epic': epic.isChildEpic }"
class="epic-bar rounded"
>
<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 class="gl-relative">
<a
:id="generateKey(epic)"
:href="epic.webUrl"
:style="timelineBarStyles(epic)"
:class="{ 'epic-bar-child-epic': epic.isChildEpic }"
class="epic-bar rounded"
>
<div class="epic-bar-inner gl-px-3 gl-py-2" :style="timelineBarInnerStyle">
<p class="epic-bar-title gl-text-truncate gl-m-0">{{ timelineBarTitle }}</p>
<div v-if="!isTimelineBarSmall" class="d-flex align-items-center">
<gl-progress-bar
class="epic-bar-progress flex-grow-1 mr-1"
:value="epicWeightPercentage"
aria-hidden="true"
/>
<div class="gl-font-sm d-flex align-items-center text-nowrap">
<gl-icon class="gl-mr-1" :size="12" name="weight" />
<p class="m-0" :aria-label="epicWeightPercentageText">{{ epicWeightPercentage }}%</p>
</div>
<div v-if="!isTimelineBarSmall" class="gl-display-flex gl-align-items-center">
<gl-progress-bar
class="epic-bar-progress gl-flex-grow-1 gl-mr-2"
:value="epicWeightPercentage"
aria-hidden="true"
/>
<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" />
<p class="gl-m-0" :aria-label="epicWeightPercentageText">{{ epicWeightPercentage }}%</p>
</div>
</div>
</a>
<gl-popover :target="generateKey(epic)" :title="epic.title" triggers="hover" placement="left">
<p class="text-secondary m-0">{{ timeframeString(epic) }}</p>
<p class="m-0">{{ popoverWeightText }}</p>
</gl-popover>
</div>
</span>
</div>
</a>
<gl-popover :target="generateKey(epic)" :title="epic.title" triggers="hover" placement="left">
<p class="gl-text-gray-500 gl-m-0">{{ timeframeString(epic) }}</p>
<p class="gl-m-0">{{ popoverWeightText }}</p>
</gl-popover>
</div>
</template>
......@@ -38,38 +38,6 @@ export default {
};
},
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() {
return this.milestone.startDateOutOfRange
? this.milestone.originalStartDate
......
......@@ -10,6 +10,31 @@ import { PRESET_TYPES, DAYS_IN_WEEK } from '../constants';
export default {
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() {
return this.presetType === PRESET_TYPES.QUARTERS;
},
......@@ -20,27 +45,51 @@ export default {
return this.presetType === PRESET_TYPES.WEEKS;
},
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) {
return (
this.currentDate >= this.timeframeItem.range[0] &&
this.currentDate <= this.timeframeItem.range[2]
this.currentDate >= timeframeItem.range[0] && this.currentDate <= timeframeItem.range[2]
);
} else if (this.presetTypeMonths) {
return (
this.currentDate.getMonth() === this.timeframeItem.getMonth() &&
this.currentDate.getFullYear() === this.timeframeItem.getFullYear()
this.currentDate.getMonth() === timeframeItem.getMonth() &&
this.currentDate.getFullYear() === timeframeItem.getFullYear()
);
}
const timeframeItem = new Date(this.timeframeItem.getTime());
const itemTime = new Date(timeframeItem.getTime());
const headerSubItems = new Array(7)
.fill()
.map(
(val, i) =>
new Date(
timeframeItem.getFullYear(),
timeframeItem.getMonth(),
timeframeItem.getDate() + i,
),
(_, i) => new Date(itemTime.getFullYear(), itemTime.getMonth(), itemTime.getDate() + i),
);
return (
......@@ -48,8 +97,6 @@ export default {
this.currentDate.getTime() <= headerSubItems[headerSubItems.length - 1].getTime()
);
},
},
methods: {
getIndicatorStyles() {
let left;
......@@ -104,25 +151,24 @@ export default {
timelineBarStyles(roadmapItem) {
let barStyles = {};
if (this.hasStartDate) {
if (this.presetTypeQuarters) {
// 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
barStyles = `width: ${this.getTimelineBarWidthForQuarters(
roadmapItem,
)}px; ${this.getTimelineBarStartOffsetForQuarters(roadmapItem)}`;
} else if (this.presetTypeMonths) {
// eslint-disable-next-line @gitlab/require-i18n-strings
barStyles = `width: ${this.getTimelineBarWidthForMonths()}px; ${this.getTimelineBarStartOffsetForMonths(
roadmapItem,
)}`;
} else if (this.presetTypeWeeks) {
// eslint-disable-next-line @gitlab/require-i18n-strings
barStyles = `width: ${this.getTimelineBarWidthForWeeks()}px; ${this.getTimelineBarStartOffsetForWeeks(
roadmapItem,
)}`;
}
if (this.presetTypeQuarters) {
// 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
barStyles = `width: ${this.getTimelineBarWidthForQuarters(
roadmapItem,
)}px; ${this.getTimelineBarStartOffsetForQuarters(roadmapItem)}`;
} else if (this.presetTypeMonths) {
// eslint-disable-next-line @gitlab/require-i18n-strings
barStyles = `width: ${this.getTimelineBarWidthForMonths()}px; ${this.getTimelineBarStartOffsetForMonths(
roadmapItem,
)}`;
} else if (this.presetTypeWeeks) {
// eslint-disable-next-line @gitlab/require-i18n-strings
barStyles = `width: ${this.getTimelineBarWidthForWeeks()}px; ${this.getTimelineBarStartOffsetForWeeks(
roadmapItem,
)}`;
}
return barStyles;
},
},
......
......@@ -9,6 +9,8 @@ import { PRESET_TYPES } from 'ee/roadmap/constants';
import createStore from 'ee/roadmap/store';
import { getTimeframeForMonthsView } from 'ee/roadmap/utils/roadmap_utils';
import CurrentDayIndicator from 'ee/roadmap/components/current_day_indicator.vue';
import {
mockTimeframeInitialDate,
mockEpic,
......@@ -54,6 +56,12 @@ const createComponent = ({
childrenFlags,
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', () => {
});
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 { 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 { PRESET_TYPES } from 'ee/roadmap/constants';
import { getTimeframeForMonthsView } from 'ee/roadmap/utils/roadmap_utils';
......@@ -18,6 +17,8 @@ const createComponent = ({
return shallowMount(EpicItemTimeline, {
propsData: {
epic,
startDate: epic.originalStartDate,
endDate: epic.originalEndDate,
presetType,
timeframe,
timeframeItem,
......@@ -82,10 +83,4 @@ describe('EpicItemTimelineComponent', () => {
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', () => {
timeframe,
timeframeItem,
epic,
startDate: epic.startDate,
endDate: epic.endDate,
},
});
};
......
......@@ -22,6 +22,8 @@ describe('QuartersPresetMixin', () => {
timeframe,
timeframeItem,
epic,
startDate: epic.startDate,
endDate: epic.endDate,
},
});
};
......
......@@ -22,6 +22,8 @@ describe('WeeksPresetMixin', () => {
timeframe,
timeframeItem,
epic,
startDate: epic.startDate,
endDate: epic.endDate,
},
});
};
......
......@@ -9,7 +9,7 @@ module QA
element :epic_details_cell
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
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