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';
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