Commit cec7133f authored by David O'Regan's avatar David O'Regan

Merge branch '262858-end-datetime-for-rotations' into 'master'

Enable end date/time for rotations

See merge request gitlab-org/gitlab!50327
parents 851161ad 7afd4ad1
......@@ -29,6 +29,18 @@
}
}
.rotations-modal {
.gl-card {
min-width: 75%;
width: fit-content;
@include gl-bg-gray-10;
}
&.gl-modal .modal-md {
max-width: 640px;
}
}
//// Copied from roadmaps.scss - adapted for on-call schedules
$header-item-height: 72px;
$item-height: 40px;
......
......@@ -9,6 +9,8 @@ import {
GlTokenSelector,
GlAvatar,
GlAvatarLabeled,
GlToggle,
GlCard,
} from '@gitlab/ui';
import {
LENGTH_ENUM,
......@@ -33,6 +35,10 @@ export const i18n = {
title: __('Starts on'),
error: s__('OnCallSchedules|Rotation start date cannot be empty'),
},
endsOn: {
enableToggle: s__('OnCallSchedules|Enable end date'),
title: __('Ends on'),
},
},
};
......@@ -55,6 +61,8 @@ export default {
GlTokenSelector,
GlAvatar,
GlAvatarLabeled,
GlToggle,
GlCard,
},
props: {
form: {
......@@ -82,6 +90,7 @@ export default {
data() {
return {
participantsArr: [],
endDateEnabled: false,
};
},
methods: {
......@@ -150,7 +159,7 @@ export default {
:value="1"
@input="$emit('update-rotation-form', { type: 'rotationLength.length', value: $event })"
/>
<gl-dropdown id="rotation-length" :text="form.rotationLength.unit.toLowerCase()">
<gl-dropdown :text="form.rotationLength.unit.toLowerCase()">
<gl-dropdown-item
v-for="unit in $options.LENGTH_ENUM"
:key="unit"
......@@ -167,7 +176,7 @@ export default {
<gl-form-group
:label="$options.i18n.fields.startsAt.title"
label-size="sm"
label-for="rotation-time"
label-for="rotation-start-time"
:invalid-feedback="$options.i18n.fields.startsAt.error"
:state="validationState.startsAt"
>
......@@ -189,7 +198,7 @@ export default {
</gl-datepicker>
<span> {{ __('at') }} </span>
<gl-dropdown
id="rotation-time"
id="rotation-start-time"
:text="format24HourTimeStringFromInt(form.startsAt.time)"
class="gl-w-12 gl-pl-3"
>
......@@ -206,5 +215,45 @@ export default {
<span class="gl-pl-5"> {{ schedule.timezone }} </span>
</div>
</gl-form-group>
<gl-toggle
v-model="endDateEnabled"
:label="$options.i18n.fields.endsOn.enableToggle"
label-position="left"
class="gl-mb-5"
/>
<gl-card v-if="endDateEnabled" class="gl-min-w-fit-content" data-testid="rotation-ends-on">
<gl-form-group
:label="$options.i18n.fields.endsOn.title"
label-size="sm"
label-for="rotation-end-time"
:invalid-feedback="$options.i18n.fields.endsOn.error"
>
<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 })"
/>
<span> {{ __('at') }} </span>
<gl-dropdown
id="rotation-end-time"
:text="format24HourTimeStringFromInt(form.endsOn.time)"
class="gl-w-12 gl-pl-3"
>
<gl-dropdown-item
v-for="time in $options.HOURS_IN_DAY"
:key="time"
:is-checked="form.endsOn.time === time"
is-check-item
@click="$emit('update-rotation-form', { type: 'endsOn.time', value: time })"
>
<span class="gl-white-space-nowrap"> {{ format24HourTimeStringFromInt(time) }}</span>
</gl-dropdown-item>
</gl-dropdown>
<div class="gl-mx-5">{{ schedule.timezone }}</div>
</div>
</gl-form-group>
</gl-card>
</gl-form>
</template>
......@@ -80,6 +80,10 @@ export default {
date: null,
time: 0,
},
endsOn: {
date: null,
time: 0,
},
},
error: '',
validationState: {
......@@ -241,10 +245,10 @@ export default {
<gl-modal
ref="addEditScheduleRotationModal"
:modal-id="modalId"
size="sm"
:title="title"
:action-primary="actionsProps.primary"
:action-cancel="actionsProps.cancel"
modal-class="rotations-modal"
@primary.prevent="isEditMode ? editRotation() : createRotation()"
>
<gl-alert v-if="error" variant="danger" @dismiss="error = ''">
......
......@@ -5,9 +5,9 @@ exports[`AddEditRotationModal renders rotation modal layout 1`] = `
actioncancel="[object Object]"
actionprimary="[object Object]"
dismisslabel="Close"
modalclass=""
modalclass="rotations-modal"
modalid="addRotationModal"
size="sm"
size="md"
title="Add rotation"
titletag="h4"
>
......
import { shallowMount } from '@vue/test-utils';
import waitForPromises from 'helpers/wait_for_promises';
import { GlDropdownItem, GlTokenSelector, GlFormGroup } from '@gitlab/ui';
import { GlDropdownItem, GlTokenSelector, GlFormGroup, GlToggle } from '@gitlab/ui';
import AddEditRotationForm from 'ee/oncall_schedules/components/rotations/components/add_edit_rotation_form.vue';
import { LENGTH_ENUM } from 'ee/oncall_schedules/constants';
import { participants, getOncallSchedulesQueryResponse } from '../../mocks/apollo_mock';
......@@ -40,6 +40,10 @@ describe('AddEditRotationForm', () => {
date: null,
time: 0,
},
endsOn: {
date: null,
time: 0,
},
},
},
provide: {
......@@ -53,14 +57,20 @@ describe('AddEditRotationForm', () => {
});
afterEach(() => {
wrapper.destroy();
if (wrapper) {
wrapper.destroy();
}
});
const findRotationLength = () => wrapper.find('[id = "rotation-length"]');
const findRotationStartsOn = () => wrapper.find('[id = "rotation-time"]');
const findRotationLength = () => wrapper.find('[id="rotation-length"]');
const findRotationStartTime = () => wrapper.find('[id="rotation-start-time"]');
const findRotationEndsContainer = () => wrapper.find('[data-testid="rotation-ends-on"]');
const findEndDateToggle = () => wrapper.find(GlToggle);
const findRotationEndTime = () => wrapper.find('[id="rotation-end-time"]');
const findUserSelector = () => wrapper.find(GlTokenSelector);
const findDropdownOptions = () => wrapper.findAllComponents(GlDropdownItem);
const findRotationFormGroups = () => wrapper.findAllComponents(GlFormGroup);
const findStartsOnTimeOptions = () => findRotationStartTime().findAllComponents(GlDropdownItem);
const findEndsOnTimeOptions = () => findRotationEndTime().findAllComponents(GlDropdownItem);
describe('Rotation form validation', () => {
it.each`
......@@ -85,19 +95,87 @@ describe('AddEditRotationForm', () => {
});
it('renders the rotation starts on datepicker', async () => {
const startsOn = findRotationStartsOn();
const startsOn = findRotationStartTime();
expect(startsOn.exists()).toBe(true);
expect(startsOn.attributes('text')).toBe('00:00');
expect(startsOn.attributes('headertext')).toBe('');
});
it('should add a check for a rotation length type selected', async () => {
const selectedLengthType1 = findDropdownOptions().at(0);
const selectedLengthType2 = findDropdownOptions().at(1);
selectedLengthType1.vm.$emit('click');
it('should emit an event with selected value on time selection', async () => {
findStartsOnTimeOptions().at(3).vm.$emit('click');
await wrapper.vm.$nextTick();
const emittedEvent = wrapper.emitted('update-rotation-form');
expect(emittedEvent).toHaveLength(1);
expect(emittedEvent[0][0]).toEqual({ type: 'startsAt.time', value: 4 });
});
it('should add a checkmark to a selected start time', async () => {
const time = 7;
wrapper.setProps({
form: {
startsAt: {
time,
},
rotationLength: {
length: 1,
unit: LENGTH_ENUM.hours,
},
},
});
await wrapper.vm.$nextTick();
expect(
findStartsOnTimeOptions()
.at(time - 1)
.props('isChecked'),
).toBe(true);
});
});
describe('Rotation end time', () => {
it('toggles end time visibility', async () => {
const toggle = findEndDateToggle().vm;
toggle.$emit('change', false);
await wrapper.vm.$nextTick();
expect(findRotationEndsContainer().exists()).toBe(false);
toggle.$emit('change', true);
await wrapper.vm.$nextTick();
expect(findRotationEndsContainer().exists()).toBe(true);
});
it('should emit an event with selected value on time selection', async () => {
findEndDateToggle().vm.$emit('change', true);
await wrapper.vm.$nextTick();
const option = 3;
findEndsOnTimeOptions().at(option).vm.$emit('click');
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 });
});
it('should add a checkmark to a selected end time', async () => {
findEndDateToggle().vm.$emit('change', true);
const time = 5;
wrapper.setProps({
form: {
endsOn: {
time,
},
startsAt: {
time: 0,
},
rotationLength: {
length: 1,
unit: LENGTH_ENUM.hours,
},
},
});
await wrapper.vm.$nextTick();
expect(selectedLengthType1.props('isChecked')).toBe(true);
expect(selectedLengthType2.props('isChecked')).toBe(false);
expect(
findEndsOnTimeOptions()
.at(time - 1)
.props('isChecked'),
).toBe(true);
});
});
......
......@@ -10758,6 +10758,9 @@ msgstr ""
msgid "Ends at (UTC)"
msgstr ""
msgid "Ends on"
msgstr ""
msgid "Enforce DNS rebinding attack protection"
msgstr ""
......@@ -19591,6 +19594,9 @@ msgstr ""
msgid "OnCallSchedules|Edit schedule"
msgstr ""
msgid "OnCallSchedules|Enable end date"
msgstr ""
msgid "OnCallSchedules|Failed to add rotation"
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