Commit 3b28f1cc authored by Phil Hughes's avatar Phil Hughes

Merge branch '262858-integrate-end-date-for-rotations' into 'master'

Create rotation with endsAt date - integration

See merge request gitlab-org/gitlab!55404
parents 33600264 80c02ae6
......@@ -40,9 +40,10 @@ export const i18n = {
title: __('Starts on'),
error: s__('OnCallSchedules|Rotation start date cannot be empty'),
},
endsOn: {
endsAt: {
enableToggle: s__('OnCallSchedules|Enable end date'),
title: __('Ends on'),
error: s__('OnCallSchedules|Rotation end date/time must come after start date/time'),
},
restrictToTime: {
enableToggle: s__('OnCallSchedules|Restrict to time intervals'),
......@@ -234,7 +235,7 @@ export default {
<div class="gl-display-inline-block">
<gl-toggle
v-model="endDateEnabled"
:label="$options.i18n.fields.endsOn.enableToggle"
:label="$options.i18n.fields.endsAt.enableToggle"
label-position="left"
class="gl-mb-5"
/>
......@@ -245,28 +246,43 @@ export default {
class="gl-border-gray-400 gl-bg-gray-10"
>
<gl-form-group
:label="$options.i18n.fields.endsOn.title"
:label="$options.i18n.fields.endsAt.title"
label-size="sm"
:invalid-feedback="$options.i18n.fields.endsOn.error"
:state="validationState.endsAt"
:invalid-feedback="$options.i18n.fields.endsAt.error"
class="gl-mb-0"
>
<div class="gl-display-flex gl-align-items-center">
<gl-datepicker
class="gl-mr-3"
@input="$emit('update-rotation-form', { type: 'endsOn.date', value: $event })"
/>
@input="$emit('update-rotation-form', { type: 'endsAt.date', value: $event })"
>
<template #default="{ formattedDate }">
<gl-form-input
class="gl-w-full"
:value="formattedDate"
:placeholder="__(`YYYY-MM-DD`)"
@blur="
$emit('update-rotation-form', {
type: 'endsAt.date',
value: $event.target.value,
})
"
/>
</template>
</gl-datepicker>
<span> {{ __('at') }} </span>
<gl-dropdown
data-testid="rotation-end-time"
:text="format24HourTimeStringFromInt(form.endsOn.time)"
:text="format24HourTimeStringFromInt(form.endsAt.time)"
class="gl-px-3"
>
<gl-dropdown-item
v-for="time in $options.HOURS_IN_DAY"
:key="time"
:is-checked="form.endsOn.time === time"
:is-checked="form.endsAt.time === time"
is-check-item
@click="$emit('update-rotation-form', { type: 'endsOn.time', value: time })"
@click="$emit('update-rotation-form', { type: 'endsAt.time', value: time })"
>
<span class="gl-white-space-nowrap">
{{ format24HourTimeStringFromInt(time) }}</span
......@@ -294,7 +310,7 @@ export default {
<gl-form-group
:label="$options.i18n.fields.restrictToTime.title"
label-size="sm"
:invalid-feedback="$options.i18n.fields.endsOn.error"
:invalid-feedback="$options.i18n.fields.endsAt.error"
class="gl-mb-0"
>
<div class="gl-display-flex gl-align-items-center">
......
......@@ -78,7 +78,7 @@ export default {
date: null,
time: 0,
},
endsOn: {
endsAt: {
date: null,
time: 0,
},
......@@ -92,6 +92,7 @@ export default {
name: true,
participants: true,
startsAt: true,
endsAt: true,
},
};
},
......@@ -129,7 +130,8 @@ export default {
name,
rotationLength,
participants,
startsAt: { date, time },
startsAt: { date: startDate, time: startTime },
endsAt: { date: endDate, time: endTime },
} = this.form;
return {
......@@ -137,9 +139,15 @@ export default {
scheduleIid: this.schedule.iid,
name,
startsAt: {
date: formatDate(date, 'yyyy-mm-dd'),
time: format24HourTimeStringFromInt(time),
date: formatDate(startDate, 'yyyy-mm-dd'),
time: format24HourTimeStringFromInt(startTime),
},
endsAt: endDate
? {
date: formatDate(endDate, 'yyyy-mm-dd'),
time: format24HourTimeStringFromInt(endTime),
}
: null,
rotationLength: {
...rotationLength,
length: parseInt(rotationLength.length, 10),
......@@ -150,6 +158,20 @@ export default {
title() {
return this.isEditMode ? this.$options.i18n.editRotation : this.$options.i18n.addRotation;
},
isEndDateValid() {
const startsAt = this.form.startsAt.date?.getTime();
const endsAt = this.form.endsAt.date?.getTime();
if (!startsAt || !endsAt) {
// If start or end is not present, we consider the end date valid
return true;
} else if (startsAt < endsAt) {
return true;
} else if (startsAt === endsAt) {
return this.form.startsAt.time < this.form.endsAt.time;
}
return false;
},
},
methods: {
createRotation() {
......@@ -244,8 +266,11 @@ export default {
this.validationState.name = isNameFieldValid(this.form.name);
} else if (key === 'participants') {
this.validationState.participants = this.form.participants.length > 0;
} else if (key === 'startsAt.date') {
} else if (key === 'startsAt.date' || key === 'startsAt.time') {
this.validationState.startsAt = Boolean(this.form.startsAt.date);
this.validationState.endsAt = this.isEndDateValid;
} else if (key === 'endsAt.date' || key === 'endsAt.time') {
this.validationState.endsAt = this.isEndDateValid;
}
},
},
......
......@@ -4,6 +4,7 @@ fragment OnCallRotation on IncidentManagementOncallRotation {
id
name
startsAt
endsAt
length
lengthUnit
participants {
......
......@@ -138,7 +138,8 @@ export const createRotationResponse = {
oncallRotation: {
id: '44',
name: 'Test',
startsAt: '2020-12-17T12:00:00Z',
startsAt: '2020-12-20T12:00:00Z',
endsAt: '2021-03-17T12:00:00Z',
length: 5,
lengthUnit: 'WEEKS',
participants: {
......@@ -171,7 +172,8 @@ export const createRotationResponseWithErrors = {
oncallRotation: {
id: '44',
name: 'Test',
startsAt: '2020-12-17T12:00:00Z',
startsAt: '2020-12-20T12:00:00Z',
endsAt: '2021-03-17T12:00:00Z',
length: 5,
lengthUnit: 'WEEKS',
participants: {
......
......@@ -2,6 +2,7 @@
"id": "gid://gitlab/IncidentManagement::OncallRotation/2",
"name": "Rotation 242",
"startsAt": "2021-01-13T10:04:56.333Z",
"endsAt": "2021-03-13T10:04:56.333Z",
"length": 1,
"lengthUnit": "WEEKS",
"participants": {
......@@ -54,6 +55,7 @@
"id": "gid://gitlab/IncidentManagement::OncallRotation/55",
"name": "Rotation 242",
"startsAt": "2021-01-13T10:04:56.333Z",
"endsAt": "2021-03-13T10:04:56.333Z",
"length": 1,
"lengthUnit": "WEEKS",
"participants": {
......@@ -102,6 +104,7 @@
"id": "gid://gitlab/IncidentManagement::OncallRotation/3",
"name": "Rotation 244",
"startsAt": "2021-01-06T10:04:56.333Z",
"endsAt": "2021-01-10T10:04:56.333Z",
"length": 1,
"lengthUnit": "WEEKS",
"participants": {
......@@ -150,6 +153,7 @@
"id": "gid://gitlab/IncidentManagement::OncallRotation/5",
"name": "Rotation 247",
"startsAt": "2021-01-06T10:04:56.333Z",
"endsAt": "2021-01-11T10:04:56.333Z",
"length": 1,
"lengthUnit": "WEEKS",
"participants": {
......
......@@ -40,7 +40,7 @@ describe('AddEditRotationForm', () => {
date: null,
time: 0,
},
endsOn: {
endsAt: {
date: null,
time: 0,
},
......@@ -160,7 +160,7 @@ describe('AddEditRotationForm', () => {
await wrapper.vm.$nextTick();
const emittedEvent = wrapper.emitted('update-rotation-form');
expect(emittedEvent).toHaveLength(1);
expect(emittedEvent[0][0]).toEqual({ type: 'endsOn.time', value: option + 1 });
expect(emittedEvent[0][0]).toEqual({ type: 'endsAt.time', value: option + 1 });
});
it('should add a checkmark to a selected end time', async () => {
......@@ -168,7 +168,7 @@ describe('AddEditRotationForm', () => {
const time = 5;
wrapper.setProps({
form: {
endsOn: {
endsAt: {
time,
},
startsAt: {
......@@ -221,7 +221,7 @@ describe('AddEditRotationForm', () => {
wrapper.setProps({
form: {
endsOn: {
endsAt: {
time: 0,
},
startsAt: {
......
import { GlModal, GlAlert } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
import AddEditRotationForm from 'ee/oncall_schedules/components/rotations/components/add_edit_rotation_form.vue';
import AddEditRotationModal, {
i18n,
} from 'ee/oncall_schedules/components/rotations/components/add_edit_rotation_modal.vue';
......@@ -129,8 +130,9 @@ describe('AddEditRotationModal', () => {
wrapper = null;
});
const findModal = () => wrapper.find(GlModal);
const findAlert = () => wrapper.find(GlAlert);
const findModal = () => wrapper.findComponent(GlModal);
const findAlert = () => wrapper.findComponent(GlAlert);
const findForm = () => wrapper.findComponent(AddEditRotationForm);
it('renders rotation modal layout', () => {
expect(wrapper.element).toMatchSnapshot();
......@@ -155,6 +157,149 @@ describe('AddEditRotationModal', () => {
expect(findAlert().exists()).toBe(true);
expect(findAlert().text()).toContain(error);
});
describe('Validation', () => {
describe('name', () => {
it('is valid when name is NOT empty', () => {
const form = findForm();
form.vm.$emit('update-rotation-form', { type: 'name', value: '' });
expect(form.props('validationState').name).toBe(false);
});
it('is NOT valid when name is empty', () => {
const form = findForm();
form.vm.$emit('update-rotation-form', { type: 'name', value: 'Some value' });
expect(form.props('validationState').name).toBe(true);
});
});
describe('participants', () => {
it('is valid when participants array is NOT empty', () => {
const form = findForm();
form.vm.$emit('update-rotation-form', {
type: 'participants',
value: ['user1', 'user2'],
});
expect(form.props('validationState').participants).toBe(true);
});
it('is NOT valid when participants array is empty', () => {
const form = findForm();
form.vm.$emit('update-rotation-form', { type: 'participants', value: [] });
expect(form.props('validationState').participants).toBe(false);
});
});
describe('startsAt date', () => {
it('is valid when date is NOT empty', () => {
const form = findForm();
form.vm.$emit('update-rotation-form', {
type: 'startsAt.date',
value: new Date('10/12/2021'),
});
expect(form.props('validationState').startsAt).toBe(true);
});
it('is NOT valid when date is empty', () => {
const form = findForm();
form.vm.$emit('update-rotation-form', { type: 'startsAt.time', value: null });
expect(form.props('validationState').startsAt).toBe(false);
});
});
describe('endsAt date', () => {
it('is valid when date is empty', () => {
const form = findForm();
form.vm.$emit('update-rotation-form', { type: 'endsAt.date', value: null });
expect(form.props('validationState').endsAt).toBe(true);
});
it('is valid when start date is smaller then end date', () => {
const form = findForm();
form.vm.$emit('update-rotation-form', {
type: 'startsAt.date',
value: new Date('9/11/2021'),
});
form.vm.$emit('update-rotation-form', {
type: 'endsAt.date',
value: new Date('10/11/2021'),
});
expect(form.props('validationState').endsAt).toBe(true);
});
it('is invalid when start date is larger then end date', () => {
const form = findForm();
form.vm.$emit('update-rotation-form', {
type: 'startsAt.date',
value: new Date('11/11/2021'),
});
form.vm.$emit('update-rotation-form', {
type: 'endsAt.date',
value: new Date('10/11/2021'),
});
expect(form.props('validationState').endsAt).toBe(false);
});
it('is valid when start and end dates are equal but time is smaller on start date', () => {
const form = findForm();
form.vm.$emit('update-rotation-form', {
type: 'startsAt.date',
value: new Date('11/11/2021'),
});
form.vm.$emit('update-rotation-form', { type: 'startsAt.time', value: 10 });
form.vm.$emit('update-rotation-form', {
type: 'endsAt.date',
value: new Date('11/11/2021'),
});
form.vm.$emit('update-rotation-form', { type: 'endsAt.time', value: 22 });
expect(form.props('validationState').endsAt).toBe(true);
});
it('is invalid when start and end dates are equal but time is larger on start date', () => {
const form = findForm();
form.vm.$emit('update-rotation-form', {
type: 'startsAt.date',
value: new Date('11/11/2021'),
});
form.vm.$emit('update-rotation-form', { type: 'startsAt.time', value: 10 });
form.vm.$emit('update-rotation-form', {
type: 'endsAt.date',
value: new Date('11/11/2021'),
});
form.vm.$emit('update-rotation-form', { type: 'endsAt.time', value: 8 });
expect(form.props('validationState').endsAt).toBe(false);
});
});
describe('Toggle primary button state', () => {
it('should disable primary button when any of the fields is invalid', async () => {
const form = findForm();
form.vm.$emit('update-rotation-form', { type: 'name', value: 'lalal' });
await wrapper.vm.$nextTick();
expect(findModal().props('actionPrimary').attributes).toEqual(
expect.arrayContaining([{ disabled: true }]),
);
});
it('should enable primary button when all fields are valid', async () => {
const form = findForm();
form.vm.$emit('update-rotation-form', { type: 'name', value: 'Value' });
form.vm.$emit('update-rotation-form', { type: 'participants', value: [1, 2, 3] });
form.vm.$emit('update-rotation-form', {
type: 'startsAt.date',
value: new Date('11/10/2021'),
});
form.vm.$emit('update-rotation-form', {
type: 'endsAt.date',
value: new Date('12/10/2021'),
});
await wrapper.vm.$nextTick();
expect(findModal().props('actionPrimary').attributes).toEqual(
expect.arrayContaining([{ disabled: false }]),
);
});
});
});
});
describe('with mocked Apollo client', () => {
......
......@@ -21028,6 +21028,9 @@ msgstr ""
msgid "OnCallSchedules|Restrict to time intervals"
msgstr ""
msgid "OnCallSchedules|Rotation end date/time must come after start date/time"
msgstr ""
msgid "OnCallSchedules|Rotation length"
msgstr ""
......
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