Commit 85b9536a authored by David O'Regan's avatar David O'Regan Committed by Natalia Tepluhina

Multiple Schedules - load one rotation set at a time

parent 199760c8
...@@ -24,7 +24,7 @@ import { ...@@ -24,7 +24,7 @@ import {
editRotationModalId, editRotationModalId,
PRESET_TYPES, PRESET_TYPES,
} from '../constants'; } from '../constants';
import getShiftsForRotations from '../graphql/queries/get_oncall_schedules_with_rotations_shifts.query.graphql'; import getShiftsForRotationsQuery from '../graphql/queries/get_oncall_schedules_with_rotations_shifts.query.graphql';
import EditScheduleModal from './add_edit_schedule_modal.vue'; import EditScheduleModal from './add_edit_schedule_modal.vue';
import DeleteScheduleModal from './delete_schedule_modal.vue'; import DeleteScheduleModal from './delete_schedule_modal.vue';
import AddEditRotationModal from './rotations/components/add_edit_rotation_modal.vue'; import AddEditRotationModal from './rotations/components/add_edit_rotation_modal.vue';
...@@ -80,10 +80,17 @@ export default { ...@@ -80,10 +80,17 @@ export default {
type: Object, type: Object,
required: true, required: true,
}, },
scheduleIndex: {
type: Number,
required: true,
},
}, },
apollo: { apollo: {
rotations: { rotations: {
query: getShiftsForRotations, query: getShiftsForRotationsQuery,
skip() {
return !this.scheduleVisible;
},
variables() { variables() {
this.timeframeStartDate.setHours(0, 0, 0, 0); this.timeframeStartDate.setHours(0, 0, 0, 0);
const startsAt = this.timeframeStartDate; const startsAt = this.timeframeStartDate;
...@@ -115,7 +122,7 @@ export default { ...@@ -115,7 +122,7 @@ export default {
timeframeStartDate: getStartOfWeek(new Date()), timeframeStartDate: getStartOfWeek(new Date()),
rotations: this.schedule.rotations.nodes, rotations: this.schedule.rotations.nodes,
rotationToUpdate: {}, rotationToUpdate: {},
scheduleVisible: true, scheduleVisible: this.scheduleIndex === 0,
}; };
}, },
computed: { computed: {
...@@ -318,7 +325,6 @@ export default { ...@@ -318,7 +325,6 @@ export default {
</gl-card> </gl-card>
</gl-collapse> </gl-collapse>
</gl-card> </gl-card>
<div v-if="scheduleVisible">
<delete-schedule-modal :schedule="schedule" :modal-id="deleteScheduleModalId" /> <delete-schedule-modal :schedule="schedule" :modal-id="deleteScheduleModalId" />
<edit-schedule-modal :schedule="schedule" :modal-id="editScheduleModalId" is-edit-mode /> <edit-schedule-modal :schedule="schedule" :modal-id="editScheduleModalId" is-edit-mode />
<add-edit-rotation-modal <add-edit-rotation-modal
...@@ -340,5 +346,4 @@ export default { ...@@ -340,5 +346,4 @@ export default {
@fetch-rotation-shifts="fetchRotationShifts" @fetch-rotation-shifts="fetchRotationShifts"
/> />
</div> </div>
</div>
</template> </template>
...@@ -118,7 +118,12 @@ export default { ...@@ -118,7 +118,12 @@ export default {
> >
{{ $options.i18n.successNotification.description }} {{ $options.i18n.successNotification.description }}
</gl-alert> </gl-alert>
<oncall-schedule v-for="schedule in schedules" :key="schedule.iid" :schedule="schedule" /> <oncall-schedule
v-for="(schedule, scheduleIndex) in schedules"
:key="schedule.iid"
:schedule="schedule"
:schedule-index="scheduleIndex"
/>
</template> </template>
<gl-empty-state <gl-empty-state
......
...@@ -35,7 +35,7 @@ export const getOncallSchedulesQueryResponse = { ...@@ -35,7 +35,7 @@ export const getOncallSchedulesQueryResponse = {
name: 'Test schedule from query', name: 'Test schedule from query',
description: 'Description 1 lives here', description: 'Description 1 lives here',
timezone: 'Pacific/Honolulu', timezone: 'Pacific/Honolulu',
rotations: { nodes: [mockRotations] }, rotations: { nodes: mockRotations },
}, },
], ],
}, },
......
...@@ -27,11 +27,10 @@ ...@@ -27,11 +27,10 @@
"nodes": [ "nodes": [
{ {
"participant": { "participant": {
"id": "gid://gitlab/IncidentManagement::OncallParticipant/49",
"colorWeight": "500", "colorWeight": "500",
"colorPalette": "blue", "colorPalette": "blue",
"user": { "user": {
"id": "1", "id": "gid://gitlab/User/1",
"username": "nora.schaden", "username": "nora.schaden",
"avatarUrl": "/url", "avatarUrl": "/url",
"name": "nora" "name": "nora"
...@@ -42,11 +41,10 @@ ...@@ -42,11 +41,10 @@
}, },
{ {
"participant": { "participant": {
"id": "gid://gitlab/IncidentManagement::OncallParticipant/232",
"colorWeight": "500", "colorWeight": "500",
"colorPalette": "orange", "colorPalette": "orange",
"user": { "user": {
"id": "2", "id": "gid://gitlab/User/2",
"username": "racheal.loving", "username": "racheal.loving",
"avatarUrl": "/url", "avatarUrl": "/url",
"name": "racheal" "name": "racheal"
...@@ -87,10 +85,11 @@ ...@@ -87,10 +85,11 @@
"nodes": [ "nodes": [
{ {
"participant": { "participant": {
"id": "gid://gitlab/IncidentManagement::OncallParticipant/99",
"colorWeight": "500", "colorWeight": "500",
"colorPalette": "aqua", "colorPalette": "aqua",
"user": { "user": {
"id": "gid://gitlab/User/38",
"avatarUrl": "url",
"username": "david.oregan", "username": "david.oregan",
"name": "david" "name": "david"
} }
...@@ -100,10 +99,11 @@ ...@@ -100,10 +99,11 @@
}, },
{ {
"participant": { "participant": {
"id": "gid://gitlab/IncidentManagement::OncallParticipant/300",
"colorWeight": "500", "colorWeight": "500",
"colorPalette": "green", "colorPalette": "green",
"user": { "user": {
"id": "gid://gitlab/User/39",
"avatarUrl": "url",
"username": "david.keagan", "username": "david.keagan",
"name": "david k" "name": "david k"
} }
...@@ -143,10 +143,11 @@ ...@@ -143,10 +143,11 @@
"nodes": [ "nodes": [
{ {
"participant": { "participant": {
"id": "gid://gitlab/IncidentManagement::OncallParticipant/100",
"colorWeight": "500", "colorWeight": "500",
"colorPalette": "magenta", "colorPalette": "magenta",
"user": { "user": {
"id": "gid://gitlab/User/40",
"avatarUrl": "url",
"username": "root", "username": "root",
"name": "Administrator" "name": "Administrator"
} }
...@@ -156,10 +157,11 @@ ...@@ -156,10 +157,11 @@
}, },
{ {
"participant": { "participant": {
"id": "gid://gitlab/IncidentManagement::OncallParticipant/109",
"colorWeight": "600", "colorWeight": "600",
"colorPalette": "blue", "colorPalette": "blue",
"user": { "user": {
"id": "gid://gitlab/User/41",
"avatarUrl": "url",
"username": "root2", "username": "root2",
"name": "Administrator 2" "name": "Administrator 2"
} }
...@@ -199,10 +201,11 @@ ...@@ -199,10 +201,11 @@
"nodes": [ "nodes": [
{ {
"participant": { "participant": {
"id": "gid://gitlab/IncidentManagement::OncallParticipant/52",
"colorWeight": "600", "colorWeight": "600",
"colorPalette": "orange", "colorPalette": "orange",
"user": { "user": {
"id": "gid://gitlab/User/43",
"avatarUrl": "url",
"username": "oregand", "username": "oregand",
"name": "david" "name": "david"
} }
...@@ -212,10 +215,11 @@ ...@@ -212,10 +215,11 @@
}, },
{ {
"participant": { "participant": {
"id": "gid://gitlab/IncidentManagement::OncallParticipant/77",
"colorWeight": "600", "colorWeight": "600",
"colorPalette": "aqua", "colorPalette": "aqua",
"user": { "user": {
"id": "gid://gitlab/User/44",
"avatarUrl": "url",
"username": "sarah.w", "username": "sarah.w",
"name": "sarah" "name": "sarah"
} }
......
import { GlCard, GlButton } from '@gitlab/ui'; import { GlButton, GlCard, GlCollapse } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils'; import { shallowMount, createLocalVue } from '@vue/test-utils';
import { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import OnCallSchedule, { i18n } from 'ee/oncall_schedules/components/oncall_schedule.vue'; import OnCallSchedule, { i18n } from 'ee/oncall_schedules/components/oncall_schedule.vue';
import RotationsListSection from 'ee/oncall_schedules/components/schedule/components/rotations_list_section.vue'; import RotationsListSection from 'ee/oncall_schedules/components/schedule/components/rotations_list_section.vue';
import ScheduleTimelineSection from 'ee/oncall_schedules/components/schedule/components/schedule_timeline_section.vue'; import ScheduleTimelineSection from 'ee/oncall_schedules/components/schedule/components/schedule_timeline_section.vue';
import * as utils from 'ee/oncall_schedules/components/schedule/utils'; import * as utils from 'ee/oncall_schedules/components/schedule/utils';
import { PRESET_TYPES } from 'ee/oncall_schedules/constants'; import { PRESET_TYPES } from 'ee/oncall_schedules/constants';
import getShiftsForRotationsQuery from 'ee/oncall_schedules/graphql/queries/get_oncall_schedules_with_rotations_shifts.query.graphql';
import * as commonUtils from 'ee/oncall_schedules/utils/common_utils'; import * as commonUtils from 'ee/oncall_schedules/utils/common_utils';
import createMockApollo from 'helpers/mock_apollo_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import * as dateTimeUtility from '~/lib/utils/datetime_utility'; import * as dateTimeUtility from '~/lib/utils/datetime_utility';
import { getOncallSchedulesQueryResponse } from './mocks/apollo_mock';
import mockTimezones from './mocks/mock_timezones.json'; import mockTimezones from './mocks/mock_timezones.json';
const localVue = createLocalVue();
localVue.use(VueApollo);
describe('On-call schedule', () => { describe('On-call schedule', () => {
let wrapper; let wrapper;
let fakeApollo;
const lastTz = mockTimezones[mockTimezones.length - 1]; const lastTz = mockTimezones[mockTimezones.length - 1];
const mockRotations = [{ name: 'rotation1' }, { name: 'rotation2' }];
const mockSchedule = { const mockSchedule = {
description: 'monitor description', description: 'monitor description',
iid: '3', iid: '3',
name: 'monitor schedule', name: 'monitor schedule',
timezone: lastTz.identifier, timezone: lastTz.identifier,
rotations: { rotations: {
nodes: mockRotations, nodes: [],
}, },
}; };
...@@ -32,44 +42,54 @@ describe('On-call schedule', () => { ...@@ -32,44 +42,54 @@ describe('On-call schedule', () => {
]; ];
const formattedTimezone = '(UTC-09:00) AKST Alaska'; const formattedTimezone = '(UTC-09:00) AKST Alaska';
function createComponent({ schedule, loading } = {}) { const createComponent = ({
const $apollo = { schedule = mockSchedule,
queries: { scheduleIndex = 0,
rotations: { getShiftsForRotationsQueryHandler = jest
loading, .fn()
}, .mockResolvedValue(getOncallSchedulesQueryResponse),
}, props = {},
}; provide = {},
} = {}) => {
fakeApollo = createMockApollo([
[getShiftsForRotationsQuery, getShiftsForRotationsQueryHandler],
]);
wrapper = extendedWrapper( wrapper = extendedWrapper(
shallowMount(OnCallSchedule, { shallowMount(OnCallSchedule, {
localVue,
apolloProvider: fakeApollo,
propsData: { propsData: {
schedule, schedule,
}, scheduleIndex,
provide: { ...props,
timezones: mockTimezones,
projectPath,
}, },
data() { data() {
return { return {
rotations: mockRotations, rotations: schedule.rotations.nodes,
}; };
}, },
provide: {
timezones: mockTimezones,
projectPath,
...provide,
},
stubs: { stubs: {
GlCard, GlCard,
}, },
mocks: { $apollo },
}), }),
); );
} };
beforeEach(() => { beforeEach(() => {
jest.spyOn(utils, 'getTimeframeForWeeksView').mockReturnValue(mockWeeksTimeFrame); jest.spyOn(utils, 'getTimeframeForWeeksView').mockReturnValue(mockWeeksTimeFrame);
jest.spyOn(commonUtils, 'getFormattedTimezone').mockReturnValue(formattedTimezone); jest.spyOn(commonUtils, 'getFormattedTimezone').mockReturnValue(formattedTimezone);
createComponent({ schedule: mockSchedule, loading: false }); createComponent();
}); });
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
fakeApollo = null;
}); });
const findScheduleHeader = () => wrapper.findByTestId('scheduleHeader'); const findScheduleHeader = () => wrapper.findByTestId('scheduleHeader');
...@@ -83,6 +103,7 @@ describe('On-call schedule', () => { ...@@ -83,6 +103,7 @@ describe('On-call schedule', () => {
const findRotationsList = () => findRotations().find(RotationsListSection); const findRotationsList = () => findRotations().find(RotationsListSection);
const findLoadPreviousTimeframeBtn = () => wrapper.findByTestId('previous-timeframe-btn'); const findLoadPreviousTimeframeBtn = () => wrapper.findByTestId('previous-timeframe-btn');
const findLoadNextTimeframeBtn = () => wrapper.findByTestId('next-timeframe-btn'); const findLoadNextTimeframeBtn = () => wrapper.findByTestId('next-timeframe-btn');
const findCollapsible = () => wrapper.findComponent(GlCollapse);
it('shows schedule title', () => { it('shows schedule title', () => {
expect(findScheduleHeader().text()).toBe(mockSchedule.name); expect(findScheduleHeader().text()).toBe(mockSchedule.name);
...@@ -102,7 +123,11 @@ describe('On-call schedule', () => { ...@@ -102,7 +123,11 @@ describe('On-call schedule', () => {
}); });
it('does not show schedule description if none present', () => { it('does not show schedule description if none present', () => {
createComponent({ schedule: { ...mockSchedule, description: null }, loading: false }); createComponent({
schedule: { ...mockSchedule, description: null },
loading: false,
scheduleIndex: 0,
});
expect(findScheduleDescription()).not.toContain(mockSchedule.description); expect(findScheduleDescription()).not.toContain(mockSchedule.description);
}); });
}); });
...@@ -133,6 +158,15 @@ describe('On-call schedule', () => { ...@@ -133,6 +158,15 @@ describe('On-call schedule', () => {
}); });
}); });
it('renders a open card for the first in the list by default', () => {
expect(findCollapsible().attributes('visible')).toBe('true');
});
it('renders a collapsed card if not the first in the list by default', () => {
createComponent({ scheduleIndex: 1 });
expect(findCollapsible().attributes('visible')).toBeUndefined();
});
describe('Timeframe shift preset type', () => { describe('Timeframe shift preset type', () => {
it('renders rotation shift preset type buttons', () => { it('renders rotation shift preset type buttons', () => {
expect(findRotationsShiftPreset().exists()).toBe(true); expect(findRotationsShiftPreset().exists()).toBe(true);
...@@ -196,4 +230,27 @@ describe('On-call schedule', () => { ...@@ -196,4 +230,27 @@ describe('On-call schedule', () => {
}); });
}); });
}); });
describe('with Apollo mock', () => {
it('renders rotations list from API response when resolved', async () => {
createComponent();
await waitForPromises();
expect(findRotationsList().props('rotations')).toHaveLength(4);
expect(findRotationsList().props('rotations')).toEqual(
getOncallSchedulesQueryResponse.data.project.incidentManagementOncallSchedules.nodes[0]
.rotations.nodes,
);
});
it('does not renders rotations list from API response when skipped', async () => {
createComponent({ scheduleIndex: 1 });
await nextTick();
await waitForPromises();
expect(findRotationsList().props('rotations')).toHaveLength(0);
expect(findRotationsList().props('rotations')).toEqual([]);
});
});
}); });
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