Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
85b9536a
Commit
85b9536a
authored
Apr 29, 2021
by
David O'Regan
Committed by
Natalia Tepluhina
Apr 29, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Multiple Schedules - load one rotation set at a time
parent
199760c8
Changes
5
Show whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
129 additions
and
58 deletions
+129
-58
ee/app/assets/javascripts/oncall_schedules/components/oncall_schedule.vue
...vascripts/oncall_schedules/components/oncall_schedule.vue
+30
-25
ee/app/assets/javascripts/oncall_schedules/components/oncall_schedules_wrapper.vue
.../oncall_schedules/components/oncall_schedules_wrapper.vue
+6
-1
ee/spec/frontend/oncall_schedule/mocks/apollo_mock.js
ee/spec/frontend/oncall_schedule/mocks/apollo_mock.js
+1
-1
ee/spec/frontend/oncall_schedule/mocks/mock_rotation.json
ee/spec/frontend/oncall_schedule/mocks/mock_rotation.json
+14
-10
ee/spec/frontend/oncall_schedule/oncall_schedule_spec.js
ee/spec/frontend/oncall_schedule/oncall_schedule_spec.js
+78
-21
No files found.
ee/app/assets/javascripts/oncall_schedules/components/oncall_schedule.vue
View file @
85b9536a
...
@@ -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
getShiftsForRotations
Query
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
:
t
rue
,
scheduleVisible
:
t
his
.
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>
ee/app/assets/javascripts/oncall_schedules/components/oncall_schedules_wrapper.vue
View file @
85b9536a
...
@@ -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
...
...
ee/spec/frontend/oncall_schedule/mocks/apollo_mock.js
View file @
85b9536a
...
@@ -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
},
},
},
],
],
},
},
...
...
ee/spec/frontend/oncall_schedule/mocks/mock_rotation.json
View file @
85b9536a
...
@@ -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"
}
}
...
...
ee/spec/frontend/oncall_schedule/oncall_schedule_spec.js
View file @
85b9536a
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
:
mockRotation
s
,
rotations
:
schedule
.
rotations
.
node
s
,
};
};
},
},
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
([]);
});
});
});
});
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment