Commit 6d4a29e9 authored by Olena Horal-Koretska's avatar Olena Horal-Koretska

Merge branch 'oncall-schedule-timeline-3-code-consolodate' into 'master'

Oncall schedule consolidate day and week code

See merge request gitlab-org/gitlab!58322
parents 5dcc79e3 5ef2aa6c
......@@ -4,8 +4,6 @@ import { isString, mapValues, isNumber, reduce } from 'lodash';
import * as timeago from 'timeago.js';
import { languageCode, s__, __, n__ } from '../../locale';
const MILLISECONDS_IN_HOUR = 60 * 60 * 1000;
const MILLISECONDS_IN_DAY = 24 * MILLISECONDS_IN_HOUR;
const DAYS_IN_WEEK = 7;
window.timeago = timeago;
......@@ -946,49 +944,6 @@ export const format24HourTimeStringFromInt = (time) => {
return formatted24HourString;
};
/**
* A utility function which checks if two date ranges overlap.
*
* @param {Object} givenPeriodLeft - the first period to compare.
* @param {Object} givenPeriodRight - the second period to compare.
* @returns {Object} { daysOverlap: number of days the overlap is present, hoursOverlap: number of hours the overlap is present, overlapStartDate: the start date of the overlap in time format, overlapEndDate: the end date of the overlap in time format }
* @throws {Error} Uncaught Error: Invalid period
*
* @example
* getOverlapDateInPeriods(
* { start: new Date(2021, 0, 11), end: new Date(2021, 0, 13) },
* { start: new Date(2021, 0, 11), end: new Date(2021, 0, 14) }
* ) => { daysOverlap: 2, hoursOverlap: 48, overlapStartDate: 1610323200000, overlapEndDate: 1610496000000 }
*
*/
export const getOverlapDateInPeriods = (givenPeriodLeft = {}, givenPeriodRight = {}) => {
const leftStartTime = new Date(givenPeriodLeft.start).getTime();
const leftEndTime = new Date(givenPeriodLeft.end).getTime();
const rightStartTime = new Date(givenPeriodRight.start).getTime();
const rightEndTime = new Date(givenPeriodRight.end).getTime();
if (!(leftStartTime <= leftEndTime && rightStartTime <= rightEndTime)) {
throw new Error(__('Invalid period'));
}
const isOverlapping = leftStartTime < rightEndTime && rightStartTime < leftEndTime;
if (!isOverlapping) {
return { daysOverlap: 0 };
}
const overlapStartDate = Math.max(leftStartTime, rightStartTime);
const overlapEndDate = rightEndTime > leftEndTime ? leftEndTime : rightEndTime;
const differenceInMs = overlapEndDate - overlapStartDate;
return {
hoursOverlap: Math.ceil(differenceInMs / MILLISECONDS_IN_HOUR),
daysOverlap: Math.ceil(differenceInMs / MILLISECONDS_IN_DAY),
overlapStartDate,
overlapEndDate,
};
};
/**
* A utility function that checks that the date is today
*
......
......@@ -7,8 +7,8 @@ import { __, sprintf } from '~/locale';
export const SHIFT_WIDTHS = {
md: 100,
sm: 50,
xs: 25,
sm: 75,
xs: 20,
};
const ROTATION_CENTER_CLASS = 'gl-display-flex gl-justify-content-center gl-align-items-center';
......@@ -77,7 +77,7 @@ export default {
</script>
<template>
<div class="gl-absolute gl-h-7 gl-mt-3" :style="rotationAssigneeStyle">
<div class="gl-absolute gl-h-7 gl-mt-3 gl-pr-1" :style="rotationAssigneeStyle">
<div
:id="rotationAssigneeUniqueID"
class="gl-h-6"
......
......@@ -13,6 +13,11 @@ export default {
type: [Date, Object],
required: true,
},
timelineWidth: {
type: Number,
required: false,
default: 1,
},
},
computed: {
isVisible() {
......@@ -32,7 +37,7 @@ export default {
<template>
<span
v-if="isVisible"
:style="getIndicatorStyles(presetType, timeframeItem)"
:style="getIndicatorStyles(presetType, timeframeItem, timelineWidth)"
data-testid="current-day-indicator"
class="current-day-indicator"
></span>
......
<script>
import { GlResizeObserverDirective } from '@gitlab/ui';
import { PRESET_TYPES, HOURS_IN_DAY } from 'ee/oncall_schedules/constants';
import updateShiftTimeUnitWidthMutation from 'ee/oncall_schedules/graphql/mutations/update_shift_time_unit_width.mutation.graphql';
import CommonMixin from 'ee/oncall_schedules/mixins/common_mixin';
export default {
PRESET_TYPES,
HOURS_IN_DAY,
directives: {
GlResizeObserver: GlResizeObserverDirective,
},
mixins: [CommonMixin],
props: {
timeframeItem: {
......@@ -17,28 +12,11 @@ export default {
required: true,
},
},
mounted() {
this.updateShiftStyles();
},
methods: {
updateShiftStyles() {
this.$apollo.mutate({
mutation: updateShiftTimeUnitWidthMutation,
variables: {
shiftTimeUnitWidth: this.$refs.dailyHourCell[0].offsetWidth,
},
});
},
},
};
</script>
<template>
<div
v-gl-resize-observer="updateShiftStyles"
class="item-sublabel"
data-testid="day-item-sublabel"
>
<div class="item-sublabel" data-testid="day-item-sublabel">
<span
v-for="hour in $options.HOURS_IN_DAY"
:key="hour"
......
<script>
import { GlResizeObserverDirective } from '@gitlab/ui';
import { PRESET_TYPES } from 'ee/oncall_schedules/constants';
import updateShiftTimeUnitWidthMutation from 'ee/oncall_schedules/graphql/mutations/update_shift_time_unit_width.mutation.graphql';
import CommonMixin from 'ee/oncall_schedules/mixins/common_mixin';
export default {
PRESET_TYPES,
directives: {
GlResizeObserver: GlResizeObserverDirective,
},
mixins: [CommonMixin],
props: {
timeframeItem: {
......@@ -33,9 +28,6 @@ export default {
return headerSubItems;
},
},
mounted() {
this.updateShiftStyles();
},
methods: {
getSubItemValueClass(subItem) {
// Show dark color text only for current & upcoming dates
......@@ -49,24 +41,12 @@ export default {
getSubItemValue(subItem) {
return subItem.getDate();
},
updateShiftStyles() {
this.$apollo.mutate({
mutation: updateShiftTimeUnitWidthMutation,
variables: {
shiftTimeUnitWidth: this.$refs.weeklyDayCell[0].offsetWidth,
},
});
},
},
};
</script>
<template>
<div
v-gl-resize-observer="updateShiftStyles"
class="item-sublabel"
data-testid="week-item-sublabel"
>
<div class="item-sublabel" data-testid="week-item-sublabel">
<span
v-for="(subItem, index) in headerSubItems"
:key="index"
......
......@@ -11,7 +11,6 @@ import ScheduleShiftWrapper from 'ee/oncall_schedules/components/schedule/compon
import {
editRotationModalId,
deleteRotationModalId,
PRESET_TYPES,
TIMELINE_CELL_WIDTH,
} from 'ee/oncall_schedules/constants';
import { s__ } from '~/locale';
......@@ -68,21 +67,9 @@ export default {
};
},
computed: {
presetIsDay() {
return this.presetType === PRESET_TYPES.DAYS;
},
timeframeToDraw() {
if (this.presetIsDay) {
return [this.timeframe[0]];
}
return this.timeframe;
},
timelineStyles() {
const length = this.presetIsDay ? 1 : 2;
return {
width: `calc((${100}% - ${TIMELINE_CELL_WIDTH}px) / ${length})`,
width: `calc(${100}% - ${TIMELINE_CELL_WIDTH}px)`,
};
},
},
......@@ -91,12 +78,6 @@ export default {
this.rotationToUpdate = rotation;
this.$emit('set-rotation-to-update', rotation);
},
cellShouldHideOverflow(index) {
return index + 1 === this.timeframe.length || this.presetIsDay;
},
timeframeItemUniqueKey(timeframeItem) {
return timeframeItem.valueOf();
},
},
};
</script>
......@@ -111,13 +92,15 @@ export default {
<span class="gl-text-truncate">{{ $options.i18n.addRotationLabel }}</span>
</span>
<span
v-for="(timeframeItem, index) in timeframeToDraw"
:key="index"
class="timeline-cell gl-border-b-solid gl-border-b-gray-100 gl-border-b-1"
class="timeline-cell gl-border-b-solid gl-border-b-gray-100 gl-border-b-1 gl-overflow-hidden"
:style="timelineStyles"
data-testid="empty-timeline-cell"
>
<current-day-indicator :preset-type="presetType" :timeframe-item="timeframeItem" />
<current-day-indicator
:preset-type="presetType"
:timeframe-item="timeframe[0]"
:timeline-width="2"
/>
</span>
</div>
<div v-else>
......@@ -154,18 +137,18 @@ export default {
</gl-button-group>
</span>
<span
v-for="(timeframeItem, index) in timeframeToDraw"
:key="timeframeItemUniqueKey(timeframeItem)"
class="timeline-cell gl-border-b-solid gl-border-b-gray-100 gl-border-b-1"
:class="{ 'gl-overflow-hidden': cellShouldHideOverflow(index) }"
class="timeline-cell gl-border-b-solid gl-border-b-gray-100 gl-border-b-1 gl-overflow-hidden"
:style="timelineStyles"
data-testid="timeline-cell"
>
<current-day-indicator :preset-type="presetType" :timeframe-item="timeframeItem" />
<current-day-indicator
:preset-type="presetType"
:timeframe-item="timeframe[0]"
:timeline-width="2"
/>
<schedule-shift-wrapper
v-if="rotation.shifts"
:preset-type="presetType"
:timeframe-item="timeframeItem"
:timeframe="timeframe"
:rotation="rotation"
/>
......
<script>
import RotationAssignee from 'ee/oncall_schedules/components/rotations/components/rotation_assignee.vue';
import { HOURS_IN_DAY, ASSIGNEE_SPACER } from 'ee/oncall_schedules/constants';
import { getOverlapDateInPeriods } from '~/lib/utils/datetime_utility';
import { currentTimeframeEndsAt } from './shift_utils';
export default {
components: {
RotationAssignee,
},
props: {
shift: {
type: Object,
required: true,
},
shiftIndex: {
type: Number,
required: true,
},
timeframeItem: {
type: [Date, Object],
required: true,
},
timeframe: {
type: Array,
required: true,
},
presetType: {
type: String,
required: true,
},
shiftTimeUnitWidth: {
type: Number,
required: true,
},
},
computed: {
currentTimeframeEndsAt() {
return currentTimeframeEndsAt(this.timeframeItem, this.presetType);
},
hoursUntilEndOfTimeFrame() {
return HOURS_IN_DAY - new Date(this.shiftRangeOverlap.overlapStartDate).getHours();
},
rotationAssigneeStyle() {
return {
left: `${this.shiftLeft}px`,
width: `${this.shiftWidth}px`,
};
},
shiftEndsAt() {
return new Date(this.shift.endsAt);
},
shiftLeft() {
const shouldStartAtBeginningOfCell =
this.shiftStartsAt.getHours() === 0 || this.shiftStartHourOutOfRange;
return shouldStartAtBeginningOfCell
? 0
: (HOURS_IN_DAY - this.hoursUntilEndOfTimeFrame) * this.shiftTimeUnitWidth;
},
shiftRangeOverlap() {
try {
return getOverlapDateInPeriods(
{ start: this.timeframeItem, end: this.currentTimeframeEndsAt },
{ start: this.shiftStartsAt, end: this.shiftEndsAt },
);
} catch (error) {
return { hoursOverlap: 0 };
}
},
shiftStartsAt() {
return new Date(this.shift.startsAt);
},
shiftStartHourOutOfRange() {
return this.shiftStartsAt.getTime() < this.timeframeItem.getTime();
},
shiftWidth() {
const baseWidth =
this.shiftEndsAt.getTime() >= this.currentTimeframeEndsAt.getTime()
? HOURS_IN_DAY
: this.shiftRangeOverlap.hoursOverlap + this.shiftOffset;
return this.shiftTimeUnitWidth * baseWidth - ASSIGNEE_SPACER;
},
shiftOffset() {
return (this.shiftStartsAt.getTimezoneOffset() - this.shiftEndsAt.getTimezoneOffset()) / 60;
},
},
};
</script>
<template>
<rotation-assignee
:assignee="shift.participant"
:rotation-assignee-style="rotationAssigneeStyle"
:rotation-assignee-starts-at="shift.startsAt"
:rotation-assignee-ends-at="shift.endsAt"
:shift-width="shiftWidth"
/>
</template>
<script>
import { PRESET_TYPES, SHIFT_WIDTH_CALCULATION_DELAY } from 'ee/oncall_schedules/constants';
import getShiftTimeUnitWidthQuery from 'ee/oncall_schedules/graphql/queries/get_shift_time_unit_width.query.graphql';
import { SHIFT_WIDTH_CALCULATION_DELAY } from 'ee/oncall_schedules/constants';
import getTimelineWidthQuery from 'ee/oncall_schedules/graphql/queries/get_timeline_width.query.graphql';
import DaysScheduleShift from './days_schedule_shift.vue';
import { shiftsToRender } from './shift_utils';
import WeeksScheduleShift from './weeks_schedule_shift.vue';
import ShiftItem from './shift_item.vue';
export default {
components: {
DaysScheduleShift,
WeeksScheduleShift,
ShiftItem,
},
props: {
presetType: {
......@@ -20,10 +16,6 @@ export default {
type: Object,
required: true,
},
timeframeItem: {
type: [Date, Object],
required: true,
},
timeframe: {
type: Array,
required: true,
......@@ -31,41 +23,18 @@ export default {
},
data() {
return {
shiftTimeUnitWidth: 0,
componentByPreset: {
[PRESET_TYPES.DAYS]: DaysScheduleShift,
[PRESET_TYPES.WEEKS]: WeeksScheduleShift,
},
timelineWidth: 0,
};
},
apollo: {
shiftTimeUnitWidth: {
query: getShiftTimeUnitWidthQuery,
debounce: SHIFT_WIDTH_CALCULATION_DELAY,
},
timelineWidth: {
query: getTimelineWidthQuery,
debounce: SHIFT_WIDTH_CALCULATION_DELAY,
},
},
computed: {
rotationLength() {
const { length, lengthUnit } = this.rotation;
return { length, lengthUnit };
},
shiftsToRender() {
return Object.freeze(
shiftsToRender(
this.rotation.shifts.nodes,
this.timeframeItem,
this.presetType,
this.timeframeIndex,
),
);
},
timeframeIndex() {
return this.timeframe.indexOf(this.timeframeItem);
return Object.freeze(this.rotation.shifts.nodes);
},
},
};
......@@ -73,17 +42,12 @@ export default {
<template>
<div>
<component
:is="componentByPreset[presetType]"
v-for="(shift, shiftIndex) in shiftsToRender"
<shift-item
v-for="shift in shiftsToRender"
:key="shift.startAt"
:shift="shift"
:shift-index="shiftIndex"
:preset-type="presetType"
:timeframe-item="timeframeItem"
:timeframe="timeframe"
:shift-time-unit-width="shiftTimeUnitWidth"
:rotation-length="rotationLength"
:timeline-width="timelineWidth"
/>
</div>
......
<script>
import RotationAssignee from 'ee/oncall_schedules/components/rotations/components/rotation_assignee.vue';
import { DAYS_IN_WEEK, HOURS_IN_DAY } from 'ee/oncall_schedules/constants';
import { getOverlapDateInPeriods, nDaysAfter } from '~/lib/utils/datetime_utility';
import { weekDisplayShiftLeft, getPixelWidth } from './shift_utils';
import { getPixelOffset, getPixelWidth } from './shift_utils';
export default {
components: {
......@@ -13,14 +11,6 @@ export default {
type: Object,
required: true,
},
shiftIndex: {
type: Number,
required: true,
},
timeframeItem: {
type: [Date, Object],
required: true,
},
timeframe: {
type: Array,
required: true,
......@@ -29,46 +19,22 @@ export default {
type: String,
required: true,
},
shiftTimeUnitWidth: {
type: Number,
required: true,
},
rotationLength: {
type: Object,
required: true,
},
timelineWidth: {
type: Number,
required: true,
},
},
computed: {
currentTimeFrameEnd() {
return nDaysAfter(this.timeframeEndsAt, DAYS_IN_WEEK);
},
shiftStyles() {
const {
shiftUnitIsHour,
totalShiftRangeOverlap,
shiftStartDateOutOfRange,
shiftTimeUnitWidth,
shiftStartsAt,
timeframeItem,
presetType,
timelineWidth,
shift,
} = this;
const { timeframe, presetType, timelineWidth, shift } = this;
return {
left: weekDisplayShiftLeft(
shiftUnitIsHour,
totalShiftRangeOverlap,
shiftStartDateOutOfRange,
shiftTimeUnitWidth,
shiftStartsAt,
timeframeItem,
left: getPixelOffset({
timeframe,
shift,
timelineWidth,
presetType,
),
}),
width: Math.round(
getPixelWidth({
shift,
......@@ -88,37 +54,6 @@ export default {
width: `${width}px`,
};
},
shiftStartsAt() {
return new Date(this.shift.startsAt);
},
shiftEndsAt() {
return new Date(this.shift.endsAt);
},
shiftStartDateOutOfRange() {
return this.shiftStartsAt.getTime() < this.timeframeItem.getTime();
},
shiftUnitIsHour() {
return (
this.totalShiftRangeOverlap.hoursOverlap <= HOURS_IN_DAY &&
this.rotationLength?.lengthUnit === 'HOURS'
);
},
timeframeEndsAt() {
return this.timeframe[this.timeframe.length - 1];
},
totalShiftRangeOverlap() {
try {
return getOverlapDateInPeriods(
{
start: this.timeframeItem,
end: this.currentTimeFrameEnd,
},
{ start: this.shiftStartsAt, end: this.shiftEndsAt },
);
} catch (error) {
return { hoursOverlap: 0 };
}
},
},
};
</script>
......
import {
PRESET_TYPES,
DAYS_IN_WEEK,
ASSIGNEE_SPACER,
ASSIGNEE_SPACER_SMALL,
HOURS_IN_DAY,
} from 'ee/oncall_schedules/constants';
import {
getOverlapDateInPeriods,
getDayDifference,
nDaysAfter,
} from '~/lib/utils/datetime_utility';
import { __ } from '~/locale';
/**
* This method returns a Date that is
* n days after the start Date provided. This
* is used to calculate the end Date of a time
* frame item.
*
*
* @param {Date} timeframeStart - the current timeframe start Date.
* @param {String} presetType - the current grid type i.e. Week, Day, Hour.
* @returns {Date}
* @throws {Error} Uncaught Error: Invalid date
*
* @example
* currentTimeframeEndsAt(new Date(2021, 01, 07), 'WEEKS') => new Date(2021, 01, 14)
* currentTimeframeEndsAt(new Date(2021, 01, 07), 'DAYS') => new Date(2021, 01, 08)
*
*/
export const currentTimeframeEndsAt = (timeframeStart, presetType) => {
if (!(timeframeStart instanceof Date)) {
throw new Error(__('Invalid date'));
}
return nDaysAfter(timeframeStart, presetType === PRESET_TYPES.DAYS ? 1 : DAYS_IN_WEEK);
};
/**
* This method returns a Boolean
* to decide if a current shift item
* is valid for render by checking if there
* is an hoursOverlap greater than 0
*
*
* @param {Object} shiftRangeOverlap - current shift range overlap object.
* @returns {Boolean}
*
* @example
* shiftShouldRender({ hoursOverlap: 48 })
* => true
*
*/
export const shiftShouldRender = (shiftRangeOverlap) => {
return Boolean(shiftRangeOverlap.hoursOverlap);
};
/**
* This method extends shiftShouldRender for a week item
* by adding a conditional check for if the
* shift occurs after the first timeframe
* item, we need to check if the current shift
* starts on the timeframe start Date
*
*
* @param {Object} shiftRangeOverlap - current shift range overlap object.
* @param {Number} timeframeIndex - current timeframe index.
* @param {Date} shiftStartsAt - current shift start Date.
* @param {Date} timeframeItem - the current timeframe start Date.
* @returns {Boolean}
*
* @example
* weekShiftShouldRender({ overlapStartDate: 1610074800000, hoursOverlap: 3 }, 0, new Date(2021-01-07), new Date(2021-01-08))
* => true
*
*/
export const weekShiftShouldRender = (
shiftRangeOverlap,
timeframeIndex,
shiftStartsAt,
timeframeItem,
) => {
if (timeframeIndex === 0) {
return shiftShouldRender(shiftRangeOverlap);
}
return (
(shiftStartsAt >= timeframeItem ||
new Date(shiftRangeOverlap.overlapStartDate) > timeframeItem) &&
new Date(shiftRangeOverlap.overlapStartDate) <
currentTimeframeEndsAt(timeframeItem, PRESET_TYPES.WEEKS)
);
};
/**
* This method returns array of shifts to render
* against a current timeframe Date i.e.
* return any shifts that have an overlap with the current
* timeframe Date
*
*
* @param {Array} shifts - current array of shifts for a given rotation timeframe.
* @param {Date} timeframeItem - the current timeframe start Date.
* @param {String} presetType - the current grid type i.e. Week, Day, Hour.
* @param {Number} timeframeIndex - the index of the current timeframe.
* @returns {Array}
*
* @example
* shiftsToRender([{ startsAt: '2021-01-07', endsAt: '2021-01-08' }, { startsAt: '2021-01-016', endsAt: '2021-01-19' }], new Date(2021, 01, 07), 'WEEKS')
* => [{ startsAt: '2021-01-07', endsAt: '2021-01-08' }]
*
*/
export const shiftsToRender = (shifts, timeframeItem, presetType, timeframeIndex) => {
try {
const timeframeEndsAt = currentTimeframeEndsAt(timeframeItem, presetType);
const overlap = (startsAt, endsAt) =>
getOverlapDateInPeriods(
{ start: timeframeItem, end: timeframeEndsAt },
{ start: startsAt, end: endsAt },
);
if (presetType === PRESET_TYPES.DAYS) {
return shifts.filter(({ startsAt, endsAt }) => overlap(startsAt, endsAt).hoursOverlap > 0);
}
return shifts.filter(({ startsAt, endsAt }) =>
weekShiftShouldRender(
overlap(startsAt, endsAt),
timeframeIndex,
new Date(startsAt),
timeframeItem,
),
);
} catch (error) {
return [];
}
};
/**
* This method calculates the amount of days until the end of the current
* timeframe from where the current shift overlap begins at, taking
* into account when a timeframe might transition month during render
*
*
* @param {Object} shiftRangeOverlap - current shift range overlap object.
* @param {Date} timeframeItem - the current timeframe start Date.
* @param {String} presetType - the current grid type i.e. Week, Day, Hour.
* @returns {Number}
*
* @example
* daysUntilEndOfTimeFrame({ overlapStartDate: 1612814725387 }, Date Mon Feb 08 2021 15:04:57, 'WEEKS')
* => 7
* Where overlapStartDate is the timestamp equal to Date Mon Feb 08 2021 15:04:57
*
*/
export const daysUntilEndOfTimeFrame = (shiftRangeOverlap, timeframeItem, presetType) => {
const timeframeEndsAt = currentTimeframeEndsAt(timeframeItem, presetType);
const startDate = new Date(shiftRangeOverlap?.overlapStartDate);
if (timeframeEndsAt.getMonth() !== startDate.getMonth()) {
return Math.abs(getDayDifference(timeframeEndsAt, startDate));
}
return timeframeEndsAt.getDate() - startDate.getDate();
};
/**
* This method calculates the total left position of a current week
* rotation cell for less than 24 hours, equal to 24 hours
* or more than 24 hours
*
*
* @param {Boolean} shiftUnitIsHour - true if the current shift length is less than 24 hours.
* @param {Object} shiftRangeOverlap - current shift range overlap object.
* @param {Boolean} shiftStartDateOutOfRange - true if the current shift start date is outside of the current grid range.
* @param {String} shiftTimeUnitWidth - the current grid type i.e. Week, Day, Hour.
* @param {Date} shiftStartsAt - current shift start Date.
* @param {Date} timeframeItem - the current timeframe start Date.
* @param {String} presetType - the current grid type i.e. Week, Day, Hour.
* @returns {Number}
*
* @example
* weekDisplayShiftLeft(false, { daysOverlap: 3 }, false , 50, Date Mon Feb 08 2021 15:04:57, Date Mon Feb 08 2021 15:04:57, 'WEEKS')
* => 148
*
*/
export const weekDisplayShiftLeft = (
shiftUnitIsHour,
shiftRangeOverlap,
shiftStartDateOutOfRange,
shiftTimeUnitWidth,
shiftStartsAt,
timeframeItem,
presetType,
) => {
const startDate = shiftStartsAt.getDate();
const firstDayOfWeek = timeframeItem.getDate();
const shiftStartsEarly = startDate === firstDayOfWeek || shiftStartDateOutOfRange;
const daysUntilEnd = daysUntilEndOfTimeFrame(shiftRangeOverlap, timeframeItem, presetType);
const dayOffSet = (DAYS_IN_WEEK - daysUntilEnd) * shiftTimeUnitWidth;
if (shiftUnitIsHour) {
const hourOffset =
(shiftTimeUnitWidth / HOURS_IN_DAY) * new Date(shiftRangeOverlap.overlapStartDate).getHours();
return dayOffSet + Math.floor(hourOffset);
}
if (shiftStartsEarly) {
return 0;
}
return dayOffSet;
};
/**
* This method calculates the total width of a current week
* rotation cell for less than 24 hours, equal to 24 hours
* or more than 24 hours
*
*
* @param {Boolean} shiftUnitIsHour - true if the current shift length is less than 24 hours.
* @param {Object} shiftRangeOverlap - current shift range overlap object.
* @param {Boolean} shiftStartDateOutOfRange - true if the current shift start date is outside of the current grid range.
* @param {String} shiftTimeUnitWidth - the current grid type i.e. Week, Day, Hour.
* @returns {Number}
*
* @example
* weekDisplayShiftWidth(false, { daysOverlap: 3, hoursOverlap: 72, overlapEndDate: 1610496000000 }, false , 50)
* => 148
*
*/
export const weekDisplayShiftWidth = (
shiftUnitIsHour,
shiftRangeOverlap,
shiftStartDateOutOfRange,
shiftTimeUnitWidth,
) => {
if (shiftUnitIsHour) {
const SPACER = shiftRangeOverlap.hoursOverlap === 1 ? ASSIGNEE_SPACER_SMALL : ASSIGNEE_SPACER;
return (
Math.floor((shiftTimeUnitWidth / HOURS_IN_DAY) * shiftRangeOverlap.hoursOverlap) - SPACER
);
}
const shiftEndsAtMidnight = new Date(shiftRangeOverlap.overlapEndDate).getHours() === 0;
const widthOffset = shiftStartDateOutOfRange && !shiftEndsAtMidnight ? 1 : 0;
return shiftTimeUnitWidth * (shiftRangeOverlap.daysOverlap - widthOffset) - ASSIGNEE_SPACER;
};
import { PRESET_TYPES } from 'ee/oncall_schedules/constants';
// New utils, unused for now. Added as part of the
// https://gitlab.com/gitlab-org/gitlab/-/issues/324608 merge train.
......
......@@ -2,20 +2,12 @@ import produce from 'immer';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
import getShiftTimeUnitWidthQuery from './graphql/queries/get_shift_time_unit_width.query.graphql';
import getTimelineWidthQuery from './graphql/queries/get_timeline_width.query.graphql';
Vue.use(VueApollo);
const resolvers = {
Mutation: {
updateShiftTimeUnitWidth: (_, { shiftTimeUnitWidth = 0 }, { cache }) => {
const sourceData = cache.readQuery({ query: getShiftTimeUnitWidthQuery });
const data = produce(sourceData, (draftData) => {
draftData.shiftTimeUnitWidth = shiftTimeUnitWidth;
});
cache.writeQuery({ query: getShiftTimeUnitWidthQuery, data });
},
updateTimelineWidth: (_, { timelineWidth = 0 }, { cache }) => {
const sourceData = cache.readQuery({ query: getTimelineWidthQuery });
const data = produce(sourceData, (draftData) => {
......
mutation updateShiftTimeUnitWidth($shiftTimeUnitWidth: Int) {
updateShiftTimeUnitWidth(shiftTimeUnitWidth: $shiftTimeUnitWidth) @client
}
......@@ -2,7 +2,6 @@ import Vue from 'vue';
import VueApollo from 'vue-apollo';
import OnCallSchedulesWrapper from './components/oncall_schedules_wrapper.vue';
import apolloProvider from './graphql';
import getShiftTimeUnitWidthQuery from './graphql/queries/get_shift_time_unit_width.query.graphql';
import getTimelineWidthQuery from './graphql/queries/get_timeline_width.query.graphql';
Vue.use(VueApollo);
......@@ -14,13 +13,6 @@ export default () => {
const { projectPath, emptyOncallSchedulesSvgPath, timezones } = el.dataset;
apolloProvider.clients.defaultClient.cache.writeQuery({
query: getShiftTimeUnitWidthQuery,
data: {
shiftTimeUnitWidth: 0,
},
});
apolloProvider.clients.defaultClient.cache.writeQuery({
query: getTimelineWidthQuery,
data: {
......
......@@ -38,12 +38,16 @@ export default {
this.$options.currentDate = currentDate;
},
methods: {
getIndicatorStyles(presetType = PRESET_TYPES.WEEKS, timeframeStartDate = new Date()) {
getIndicatorStyles(
presetType = PRESET_TYPES.WEEKS,
timeframeStartDate = new Date(),
timelineWidth = 1,
) {
if (presetType === PRESET_TYPES.DAYS) {
return this.getDayViewIndicatorStyles();
}
return this.getWeekViewIndicatorStyles(timeframeStartDate);
return this.getWeekViewIndicatorStyles(timeframeStartDate, timelineWidth);
},
getDayViewIndicatorStyles() {
const currentDate = new Date();
......@@ -54,14 +58,14 @@ export default {
left: `${hours + minutes}%`,
};
},
getWeekViewIndicatorStyles(timeframeStartDate) {
getWeekViewIndicatorStyles(timeframeStartDate, timelineWidth) {
const currentDate = new Date();
const hourOffset = oneHourOffsetWeekView * currentDate.getHours();
const daysSinceShiftStart = getDayDifference(timeframeStartDate, currentDate);
const leftOffset = oneDayOffsetWeekView * daysSinceShiftStart + hourOffset;
return {
left: `${Math.round(leftOffset)}%`,
left: `${leftOffset / timelineWidth}%`,
};
},
},
......
......@@ -68,18 +68,19 @@ exports[`RotationsListSectionComponent when the timeframe includes today renders
</span>
<span
class="timeline-cell gl-border-b-solid gl-border-b-gray-100 gl-border-b-1"
class="timeline-cell gl-border-b-solid gl-border-b-gray-100 gl-border-b-1 gl-overflow-hidden"
data-testid="timeline-cell"
style="width: calc(100% - 180px);"
>
<span
class="current-day-indicator"
data-testid="current-day-indicator"
style="left: 29%;"
style="left: 14.285714285714286%;"
/>
<div>
<div
class="gl-absolute gl-h-7 gl-mt-3"
class="gl-absolute gl-h-7 gl-mt-3 gl-pr-1"
style="left: 0px; width: 0px;"
>
<div
......@@ -119,7 +120,7 @@ exports[`RotationsListSectionComponent when the timeframe includes today renders
</div>
</div>
<div
class="gl-absolute gl-h-7 gl-mt-3"
class="gl-absolute gl-h-7 gl-mt-3 gl-pr-1"
style="left: 0px; width: 0px;"
>
<div
......@@ -160,14 +161,6 @@ exports[`RotationsListSectionComponent when the timeframe includes today renders
</div>
</div>
</span>
<span
class="timeline-cell gl-border-b-solid gl-border-b-gray-100 gl-border-b-1 gl-overflow-hidden"
data-testid="timeline-cell"
>
<!---->
<div />
</span>
</div>
</div>
......
......@@ -43,9 +43,10 @@ describe('CurrentDayIndicator', () => {
* and the current day is the following Wednesday.
* This creates a gap of two days so our generated offset should represent:
* DayDiffOffset + weeklyOffset + weeklyHourOffset
* 29 + 0
* Note: We do not round these calculations
* 28.571428571428573 + 0
*/
const leftOffset = '29';
const leftOffset = '28.571428571428573';
expect(wrapper.attributes('style')).toBe(`left: ${leftOffset}%;`);
});
......
import { shallowMount } from '@vue/test-utils';
import DaysHeaderSubItem from 'ee/oncall_schedules/components/schedule/components/preset_days/days_header_sub_item.vue';
import updateShiftTimeUnitWidthMutation from 'ee/oncall_schedules/graphql/mutations/update_shift_time_unit_width.mutation.graphql';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
describe('ee/oncall_schedules/components/schedule/components/preset_days/days_header_sub_item.vue', () => {
......@@ -14,9 +12,6 @@ describe('ee/oncall_schedules/components/schedule/components/preset_days/days_he
propsData: {
timeframeItem,
},
directives: {
GlResizeObserver: createMockDirective(),
},
mocks: {
$apollo: {
mutate: jest.fn(),
......@@ -36,7 +31,6 @@ describe('ee/oncall_schedules/components/schedule/components/preset_days/days_he
}
});
const findDaysHeaderSubItem = () => wrapper.findByTestId('day-item-sublabel');
const findDaysHeaderCurrentIndicator = () =>
wrapper.findByTestId('day-item-sublabel-current-indicator');
......@@ -54,25 +48,4 @@ describe('ee/oncall_schedules/components/schedule/components/preset_days/days_he
expect(findDaysHeaderCurrentIndicator().exists()).toBe(true);
});
});
describe('updateShiftStyles', () => {
it('should store the rendered cell width in Apollo cache via `updateShiftTimeUnitWidthMutation` when mounted', async () => {
wrapper.vm.$apollo.mutate.mockResolvedValueOnce({});
await wrapper.vm.$nextTick();
expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
mutation: updateShiftTimeUnitWidthMutation,
variables: {
shiftTimeUnitWidth: wrapper.vm.$refs.dailyHourCell[0].offsetWidth,
},
});
expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledTimes(1);
});
it('should re-calculate cell width inside Apollo cache on page resize', () => {
expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledTimes(1);
const { value } = getBinding(findDaysHeaderSubItem().element, 'gl-resize-observer');
value();
expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledTimes(2);
});
});
});
import { shallowMount } from '@vue/test-utils';
import WeeksHeaderSubItemComponent from 'ee/oncall_schedules/components/schedule/components/preset_weeks/weeks_header_sub_item.vue';
import { getTimeframeForWeeksView } from 'ee/oncall_schedules/components/schedule/utils';
import updateShiftTimeUnitWidthMutation from 'ee/oncall_schedules/graphql/mutations/update_shift_time_unit_width.mutation.graphql';
import { useFakeDate } from 'helpers/fake_date';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
describe('WeeksHeaderSubItemComponent', () => {
......@@ -19,9 +17,6 @@ describe('WeeksHeaderSubItemComponent', () => {
propsData: {
timeframeItem,
},
directives: {
GlResizeObserver: createMockDirective(),
},
mocks: {
$apollo: {
mutate: jest.fn(),
......@@ -42,7 +37,6 @@ describe('WeeksHeaderSubItemComponent', () => {
});
const findSublabelValues = () => wrapper.findAll('[data-testid="sublabel-value"]');
const findWeeksHeaderSubItemComponent = () => wrapper.findByTestId('week-item-sublabel');
describe('computed', () => {
describe('headerSubItems', () => {
......@@ -84,24 +78,5 @@ describe('WeeksHeaderSubItemComponent', () => {
expect.arrayContaining(['label-dark', 'label-bold']),
);
});
it('should store the rendered cell width in Apollo cache via `updateShiftTimeUnitWidthMutation` when mounted', async () => {
wrapper.vm.$apollo.mutate.mockResolvedValueOnce({});
await wrapper.vm.$nextTick();
expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
mutation: updateShiftTimeUnitWidthMutation,
variables: {
shiftTimeUnitWidth: wrapper.vm.$refs.weeklyDayCell[0].offsetWidth,
},
});
expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledTimes(1);
});
it('should re-calculate cell width inside Apollo cache on page resize', () => {
expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledTimes(1);
const { value } = getBinding(findWeeksHeaderSubItemComponent().element, 'gl-resize-observer');
value();
expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledTimes(2);
});
});
});
......@@ -70,7 +70,8 @@ describe('RotationsListSectionComponent', () => {
});
it('renders timeline cell items based on timeframe data', () => {
expect(findTimelineCells().length).toBe(mockTimeframeWeeks.length);
const mockTimelineCellWidth = 1;
expect(findTimelineCells().length).toBe(mockTimelineCellWidth);
});
it('renders current day indicator in the first timeline cell', () => {
......@@ -78,7 +79,7 @@ describe('RotationsListSectionComponent', () => {
});
it('render the correct amount of rotation assignees with their related information', () => {
expect(findRotationAssignees()).toHaveLength(2);
expect(findRotationAssignees()).toHaveLength(mockRotations[0].shifts.nodes.length);
expect(findRotationAssignees().at(0).props().assignee.user).toEqual(
mockRotations[0].shifts.nodes[0].participant.user,
);
......
import { shallowMount } from '@vue/test-utils';
import RotationsAssignee from 'ee/oncall_schedules/components/rotations/components/rotation_assignee.vue';
import DaysScheduleShift from 'ee/oncall_schedules/components/schedule/components/shifts/components/days_schedule_shift.vue';
import { PRESET_TYPES, DAYS_IN_WEEK } from 'ee/oncall_schedules/constants';
import { nDaysAfter } from '~/lib/utils/datetime_utility';
const shift = {
participant: {
id: '1',
user: {
username: 'nora.schaden',
},
},
startsAt: '2021-01-15T00:04:56.333Z',
endsAt: '2021-01-15T04:22:56.333Z',
};
const CELL_WIDTH = 50;
const timeframeItem = new Date(2021, 0, 15);
const timeframe = [timeframeItem, nDaysAfter(timeframeItem, DAYS_IN_WEEK)];
describe('ee/oncall_schedules/components/schedule/components/shifts/components/days_schedule_shift.vue', () => {
let wrapper;
function createComponent({ props = {} } = {}) {
wrapper = shallowMount(DaysScheduleShift, {
propsData: {
shift,
shiftIndex: 0,
timeframeItem,
timeframe,
presetType: PRESET_TYPES.WEEKS,
shiftTimeUnitWidth: CELL_WIDTH,
...props,
},
});
}
beforeEach(() => {
createComponent();
});
afterEach(() => {
wrapper.destroy();
});
const findRotationAssignee = () => wrapper.findComponent(RotationsAssignee);
describe('shift overlaps inside the current time-frame', () => {
it('should render a rotation assignee child component', () => {
expect(findRotationAssignee().exists()).toBe(true);
});
it('calculates the correct rotation assignee styles when the shift starts at the beginning of the time-frame cell', () => {
/**
* Where left should be 0px i.e. beginning of time-frame cell
* and width should be overlapping hours * CELL_WIDTH(5 * 50)
*/
createComponent({ data: { shiftTimeUnitWidth: CELL_WIDTH } });
expect(findRotationAssignee().props('rotationAssigneeStyle')).toEqual({
left: '0px',
width: '248px',
});
});
it('calculates the correct rotation assignee styles when the shift does not start at the beginning of the time-frame cell', () => {
/**
* Where left should be 500px i.e. ((HOURS_IN_DAY - (HOURS_IN_DAY - overlapStartTime)) * CELL_WIDTH) (((24 - (24 - 10)) * 50))
* and width should be overlapping hours * CELL_WIDTH (12 * 50 - 2)
*/
createComponent({
props: {
shift: {
...shift,
startsAt: '2021-01-15T10:04:56.333Z',
endsAt: '2021-01-15T22:04:56.333Z',
},
},
data: { shiftTimeUnitWidth: CELL_WIDTH },
});
expect(findRotationAssignee().props('rotationAssigneeStyle')).toEqual({
left: '500px',
width: '598px',
});
});
it('handles the offset for timezone changes', () => {
const DLSTimeframeItem = new Date(2021, 2, 14);
const DSLTimeframe = [timeframeItem, nDaysAfter(timeframeItem, DAYS_IN_WEEK)];
/**
* Where left should be: ((HOURS_IN_DAY - (HOURS_IN_DAY - overlapStartTime)) * CELL_WIDTH)
* and width should be: (overlappingHours + timezoneOffset) * CELL_WIDTH
*/
createComponent({
props: {
shift: {
...shift,
startsAt: '2021-03-14T05:00:00Z',
endsAt: '2021-03-14T07:00:00Z',
},
timeframeItem: DLSTimeframeItem,
timeframe: DSLTimeframe,
},
data: { shiftTimeUnitWidth: CELL_WIDTH },
});
expect(findRotationAssignee().props('rotationAssigneeStyle')).toEqual({
left: '250px',
width: '98px',
});
});
});
});
import { shallowMount } from '@vue/test-utils';
import DaysScheduleShift from 'ee/oncall_schedules/components/schedule/components/shifts/components/days_schedule_shift.vue';
import ScheduleShiftWrapper from 'ee/oncall_schedules/components/schedule/components/shifts/components/schedule_shift_wrapper.vue';
import WeeksScheduleShift from 'ee/oncall_schedules/components/schedule/components/shifts/components/weeks_schedule_shift.vue';
import ShiftItem from 'ee/oncall_schedules/components/schedule/components/shifts/components/shift_item.vue';
import { PRESET_TYPES, DAYS_IN_WEEK } from 'ee/oncall_schedules/constants';
import { nDaysAfter } from '~/lib/utils/datetime_utility';
import mockRotations from '../../../../mocks/mock_rotation.json';
......@@ -23,14 +22,14 @@ describe('ee/oncall_schedules/components/schedule/components/shifts/components/s
},
data() {
return {
shiftTimeUnitWidth: 0,
timelineWidth: 0,
...data,
};
},
mocks: {
$apollo: {
queries: {
shiftTimeUnitWidth: 0,
timelineWidth: 0,
},
},
},
......@@ -45,14 +44,13 @@ describe('ee/oncall_schedules/components/schedule/components/shifts/components/s
wrapper.destroy();
});
const findDaysScheduleShifts = () => wrapper.findAllComponents(DaysScheduleShift);
const findWeeksScheduleShifts = () => wrapper.findAllComponents(WeeksScheduleShift);
const findShiftItems = () => wrapper.findAllComponents(ShiftItem);
const updateShifts = (startsAt, endsAt) =>
mockRotations[0].shifts.nodes.map((el) => ({ ...el, startsAt, endsAt }));
describe('when the preset type is WEEKS', () => {
it('should render a selection of week grid shifts inside the rotation', () => {
expect(findWeeksScheduleShifts()).toHaveLength(2);
expect(findShiftItems()).toHaveLength(2);
});
it.each`
......@@ -78,14 +76,14 @@ describe('ee/oncall_schedules/components/schedule/components/shifts/components/s
},
});
expect(findWeeksScheduleShifts().exists()).toBe(false);
expect(findShiftItems().exists()).toBe(false);
});
});
describe('when the preset type is DAYS', () => {
it('should render a selection of day grid shifts inside the rotation', () => {
createComponent({ props: { presetType: PRESET_TYPES.DAYS } });
expect(findDaysScheduleShifts()).toHaveLength(1);
expect(findShiftItems()).toHaveLength(2);
});
it.each`
......@@ -111,7 +109,7 @@ describe('ee/oncall_schedules/components/schedule/components/shifts/components/s
},
});
expect(findDaysScheduleShifts().exists()).toBe(false);
expect(findShiftItems().exists()).toBe(false);
});
});
});
import { shallowMount } from '@vue/test-utils';
import RotationsAssignee from 'ee/oncall_schedules/components/rotations/components/rotation_assignee.vue';
import WeeksScheduleShift from 'ee/oncall_schedules/components/schedule/components/shifts/components/weeks_schedule_shift.vue';
import ShiftItem from 'ee/oncall_schedules/components/schedule/components/shifts/components/shift_item.vue';
import { PRESET_TYPES, DAYS_IN_WEEK } from 'ee/oncall_schedules/constants';
import { nDaysAfter } from '~/lib/utils/datetime_utility';
......@@ -20,19 +20,15 @@ const CELL_WIDTH = 50;
const timeframeItem = new Date(2021, 0, 13);
const timeframe = [timeframeItem, new Date(nDaysAfter(timeframeItem, DAYS_IN_WEEK))];
describe('ee/oncall_schedules/components/schedule/components/shifts/components/weeks_schedule_shift.vue', () => {
describe('ee/oncall_schedules/components/schedule/components/shifts/components/shift_item.vue', () => {
let wrapper;
function createComponent({ props = {} } = {}) {
wrapper = shallowMount(WeeksScheduleShift, {
wrapper = shallowMount(ShiftItem, {
propsData: {
shift,
shiftIndex: 0,
timeframeItem,
timeframe,
presetType: PRESET_TYPES.WEEKS,
shiftTimeUnitWidth: CELL_WIDTH,
rotationLength: { lengthUnit: 'DAYS' },
timelineWidth: CELL_WIDTH * 14,
...props,
},
......@@ -53,39 +49,6 @@ describe('ee/oncall_schedules/components/schedule/components/shifts/components/w
it('should render a rotation assignee child component', () => {
expect(findRotationAssignee().exists()).toBe(true);
});
it('calculates the correct rotation assignee styles when the shift starts at the beginning of the time-frame cell', () => {
/**
* Where left should be 0px i.e. beginning of time-frame cell
* and width should be absolute pixel width (3.5 * CELL_WIDTH)
*/
createComponent({ data: { shiftTimeUnitWidth: CELL_WIDTH } });
expect(findRotationAssignee().props('rotationAssigneeStyle')).toEqual({
left: '0px',
width: '175px',
});
});
it('calculates the correct rotation assignee styles when the shift does not start at the beginning of the time-frame cell', () => {
/**
* Where left should be 52x i.e. ((DAYS_IN_WEEK - (timeframeEndsAt - overlapStartDate)) * CELL_WIDTH)(((7 - (20 - 14)) * 50))
* and width should be absolute pixel width (3.5 * CELL_WIDTH)
*/
createComponent({
props: {
shift: {
...shift,
startsAt: '2021-01-14T10:04:56.333Z',
endsAt: '2021-01-17T22:04:56.333Z',
},
},
data: { shiftTimeUnitWidth: CELL_WIDTH },
});
expect(findRotationAssignee().props('rotationAssigneeStyle')).toEqual({
left: '50px',
width: '175px',
});
});
});
describe('shift overlaps inside the current time-frame with a shift equal to 24 hours', () => {
......@@ -99,17 +62,6 @@ describe('ee/oncall_schedules/components/schedule/components/shifts/components/w
it('should render a rotation assignee child component', () => {
expect(findRotationAssignee().exists()).toBe(true);
});
it('calculates the correct rotation assignee styles when the shift does not start at the beginning of the time-frame cell', () => {
/**
* Where left should be ((DAYS_IN_WEEK - (timeframeEndsAt - overlapStartDate)) * CELL_WIDTH)(((7 - (20 - 14)) * 50))
* and width should be absolute pixel width (1.5 * CELL_WIDTH)
*/
expect(findRotationAssignee().props('rotationAssigneeStyle')).toEqual({
left: '50px',
width: '75px',
});
});
});
describe('shift overlaps inside the current time-frame with a shift less than 24 hours', () => {
......@@ -130,16 +82,5 @@ describe('ee/oncall_schedules/components/schedule/components/shifts/components/w
it('should render a rotation assignee child component', () => {
expect(findRotationAssignee().exists()).toBe(true);
});
it('calculates the correct rotation assignee styles when the shift does not start at the beginning of the time-frame cell', () => {
/**
* Where left should be 70px i.e. ((CELL_WIDTH / HOURS_IN_DAY) * overlapStartDate + dayOffSet)(50 / 24 * 10) + 50;
* and width should be the correct fraction of a day: (hours / 24) * CELL_WIDTH
*/
expect(findRotationAssignee().props('rotationAssigneeStyle')).toEqual({
left: '70px',
width: '4px',
});
});
});
});
......@@ -97,7 +97,7 @@ describe('Schedule Common Mixins', () => {
const leftOffset = oneDayOffsetWeekView * daysSinceShiftStart + hourOffset;
expect(wrapper.vm.getIndicatorStyles(PRESET_TYPES.WEEKS, mockTimeframeInitialDate)).toEqual(
expect.objectContaining({
left: `${Math.round(leftOffset)}%`,
left: `${leftOffset}%`,
}),
);
});
......
......@@ -17027,9 +17027,6 @@ msgstr ""
msgid "Invalid login or password"
msgstr ""
msgid "Invalid period"
msgstr ""
msgid "Invalid pin code"
msgstr ""
......
......@@ -966,62 +966,6 @@ describe('format24HourTimeStringFromInt', () => {
});
});
describe('getOverlapDateInPeriods', () => {
const start = new Date(2021, 0, 11);
const end = new Date(2021, 0, 13);
describe('when date periods overlap', () => {
const givenPeriodLeft = new Date(2021, 0, 11);
const givenPeriodRight = new Date(2021, 0, 14);
it('returns an overlap object that contains the amount of days overlapping, the amount of hours overlapping, start date of overlap and end date of overlap', () => {
expect(
datetimeUtility.getOverlapDateInPeriods(
{ start, end },
{ start: givenPeriodLeft, end: givenPeriodRight },
),
).toEqual({
daysOverlap: 2,
hoursOverlap: 48,
overlapStartDate: givenPeriodLeft.getTime(),
overlapEndDate: end.getTime(),
});
});
});
describe('when date periods do not overlap', () => {
const givenPeriodLeft = new Date(2021, 0, 9);
const givenPeriodRight = new Date(2021, 0, 10);
it('returns an overlap object that contains a 0 value for days overlapping', () => {
expect(
datetimeUtility.getOverlapDateInPeriods(
{ start, end },
{ start: givenPeriodLeft, end: givenPeriodRight },
),
).toEqual({ daysOverlap: 0 });
});
});
describe('when date periods contain an invalid Date', () => {
const startInvalid = new Date(NaN);
const endInvalid = new Date(NaN);
const error = __('Invalid period');
it('throws an exception when the left period contains an invalid date', () => {
expect(() =>
datetimeUtility.getOverlapDateInPeriods({ start, end }, { start: startInvalid, end }),
).toThrow(error);
});
it('throws an exception when the right period contains an invalid date', () => {
expect(() =>
datetimeUtility.getOverlapDateInPeriods({ start, end }, { start, end: endInvalid }),
).toThrow(error);
});
});
});
describe('isToday', () => {
const today = new Date();
it.each`
......
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