WIP: Expose a preview API for rotation params
What does this MR do?
Implements a mutation to generate a preview of the shifts for a rotation.
This is for #259172 (closed), which will allow users to visualize the rotation that they are creating/updating before saving it.
Changes in this MR:
- Add new mutation for previewing shifts
- Change nullable option for
OncallRotation
andOncallParticipant
GraphQL types* - Refactor
OncallRotation::Base
mutation to better reflect shared functionality - Aligns fields required mutation arguments for destroying/editing rotations
- Updates the
OncallShifts::ReadService
to accept an unsaved rotation & use that as an indicator not to attempt to pull historical shifts from the DB
The mutation is meant to be essentially identical to the OncallRotationCreate
mutation, including nearly all the same validations.
Differences between creation mutation:
- The rotation name will not be validated for uniqueness.
- Participants are not individually validated. Some checks take place (permissions, duplicates, quantity), but data visualization options are not checked.
- Rotation and participant ids will not be available, as the rotation has not been saved.
Sample GraphQL details for new mutation
Sample mutation:
mutation PreviewRotationEMEA($rotationPreviewEMEA: OncallRotationPreviewInput!) {
oncallRotationPreview(input: $rotationPreviewEMEA) {
oncallRotation {
id
name
startsAt
length
lengthUnit
activePeriod {
endTime
startTime
}
shifts(startTime: "2021-03-14 17:23:13 -0500" , endTime: "2021-04-08 17:23:13 -0500") {
nodes {
endsAt
startsAt
participant {
id
user {
username
}
}
}
}
}
errors
}
}
{
"rotationPreviewEMEA": {
"name": "EMEA",
"projectPath": "h5bp/html5-boilerplate",
"scheduleIid": "1",
"startsAt": {
"date": "2021-02-01",
"time": "08:00"
},
"rotationLength": {
"length": 12,
"unit": "HOURS"
},
"participants": [
{ "username": "colton", "colorPalette": "BLUE", "colorWeight": "WEIGHT_200" },
{ "username": "pauletta_paucek", "colorPalette": "AQUA", "colorWeight": "WEIGHT_400" },
{ "username": "nubia.hartmann", "colorPalette": "GREEN", "colorWeight": "WEIGHT_700" },
{ "username": "juliane.labadie", "colorPalette": "ORANGE", "colorWeight": "WEIGHT_900" }
]
}
}
Sample output:
{
"data": {
"oncallRotationPreview": {
"oncallRotation": {
"id": null,
"name": "GitLab Preview 1564768962608780951",
"startsAt": "2021-02-01T08:00:00Z",
"length": 12,
"lengthUnit": "HOURS",
"activePeriod": {
"endTime": null,
"startTime": null
},
"shifts": {
"nodes": [
{
"endsAt": "2021-03-15T08:00:00Z",
"startsAt": "2021-03-14T20:00:00Z",
"participant": {
"id": null,
"user": {
"username": "juliane.labadie"
}
}
},
{
"endsAt": "2021-03-15T20:00:00Z",
"startsAt": "2021-03-15T08:00:00Z",
"participant": {
"id": null,
"user": {
"username": "colton"
}
}
},
{
"endsAt": "2021-03-16T08:00:00Z",
"startsAt": "2021-03-15T20:00:00Z",
"participant": {
"id": null,
"user": {
"username": "pauletta_paucek"
}
}
},
{
"endsAt": "2021-03-16T20:00:00Z",
"startsAt": "2021-03-16T08:00:00Z",
"participant": {
"id": null,
"user": {
"username": "nubia.hartmann"
}
}
},
{
"endsAt": "2021-03-17T08:00:00Z",
"startsAt": "2021-03-16T20:00:00Z",
"participant": {
"id": null,
"user": {
"username": "juliane.labadie"
}
}
},
{
"endsAt": "2021-03-17T20:00:00Z",
"startsAt": "2021-03-17T08:00:00Z",
"participant": {
"id": null,
"user": {
"username": "colton"
}
}
},
{
"endsAt": "2021-03-18T08:00:00Z",
"startsAt": "2021-03-17T20:00:00Z",
"participant": {
"id": null,
"user": {
"username": "pauletta_paucek"
}
}
},
{
"endsAt": "2021-03-18T20:00:00Z",
"startsAt": "2021-03-18T08:00:00Z",
"participant": {
"id": null,
"user": {
"username": "nubia.hartmann"
}
}
},
{
"endsAt": "2021-03-19T08:00:00Z",
"startsAt": "2021-03-18T20:00:00Z",
"participant": {
"id": null,
"user": {
"username": "juliane.labadie"
}
}
},
{
"endsAt": "2021-03-19T20:00:00Z",
"startsAt": "2021-03-19T08:00:00Z",
"participant": {
"id": null,
"user": {
"username": "colton"
}
}
},
{
"endsAt": "2021-03-20T08:00:00Z",
"startsAt": "2021-03-19T20:00:00Z",
"participant": {
"id": null,
"user": {
"username": "pauletta_paucek"
}
}
},
{
"endsAt": "2021-03-20T20:00:00Z",
"startsAt": "2021-03-20T08:00:00Z",
"participant": {
"id": null,
"user": {
"username": "nubia.hartmann"
}
}
},
{
"endsAt": "2021-03-21T08:00:00Z",
"startsAt": "2021-03-20T20:00:00Z",
"participant": {
"id": null,
"user": {
"username": "juliane.labadie"
}
}
},
{
"endsAt": "2021-03-21T20:00:00Z",
"startsAt": "2021-03-21T08:00:00Z",
"participant": {
"id": null,
"user": {
"username": "colton"
}
}
},
{
"endsAt": "2021-03-22T08:00:00Z",
"startsAt": "2021-03-21T20:00:00Z",
"participant": {
"id": null,
"user": {
"username": "pauletta_paucek"
}
}
},
{
"endsAt": "2021-03-22T20:00:00Z",
"startsAt": "2021-03-22T08:00:00Z",
"participant": {
"id": null,
"user": {
"username": "nubia.hartmann"
}
}
},
{
"endsAt": "2021-03-23T08:00:00Z",
"startsAt": "2021-03-22T20:00:00Z",
"participant": {
"id": null,
"user": {
"username": "juliane.labadie"
}
}
},
{
"endsAt": "2021-03-23T20:00:00Z",
"startsAt": "2021-03-23T08:00:00Z",
"participant": {
"id": null,
"user": {
"username": "colton"
}
}
},
{
"endsAt": "2021-03-24T08:00:00Z",
"startsAt": "2021-03-23T20:00:00Z",
"participant": {
"id": null,
"user": {
"username": "pauletta_paucek"
}
}
},
{
"endsAt": "2021-03-24T20:00:00Z",
"startsAt": "2021-03-24T08:00:00Z",
"participant": {
"id": null,
"user": {
"username": "nubia.hartmann"
}
}
},
{
"endsAt": "2021-03-25T08:00:00Z",
"startsAt": "2021-03-24T20:00:00Z",
"participant": {
"id": null,
"user": {
"username": "juliane.labadie"
}
}
},
{
"endsAt": "2021-03-25T20:00:00Z",
"startsAt": "2021-03-25T08:00:00Z",
"participant": {
"id": null,
"user": {
"username": "colton"
}
}
},
{
"endsAt": "2021-03-26T08:00:00Z",
"startsAt": "2021-03-25T20:00:00Z",
"participant": {
"id": null,
"user": {
"username": "pauletta_paucek"
}
}
},
{
"endsAt": "2021-03-26T20:00:00Z",
"startsAt": "2021-03-26T08:00:00Z",
"participant": {
"id": null,
"user": {
"username": "nubia.hartmann"
}
}
},
{
"endsAt": "2021-03-27T08:00:00Z",
"startsAt": "2021-03-26T20:00:00Z",
"participant": {
"id": null,
"user": {
"username": "juliane.labadie"
}
}
},
{
"endsAt": "2021-03-27T20:00:00Z",
"startsAt": "2021-03-27T08:00:00Z",
"participant": {
"id": null,
"user": {
"username": "colton"
}
}
},
{
"endsAt": "2021-03-28T08:00:00Z",
"startsAt": "2021-03-27T20:00:00Z",
"participant": {
"id": null,
"user": {
"username": "pauletta_paucek"
}
}
},
{
"endsAt": "2021-03-28T20:00:00Z",
"startsAt": "2021-03-28T08:00:00Z",
"participant": {
"id": null,
"user": {
"username": "nubia.hartmann"
}
}
},
{
"endsAt": "2021-03-29T08:00:00Z",
"startsAt": "2021-03-28T20:00:00Z",
"participant": {
"id": null,
"user": {
"username": "juliane.labadie"
}
}
},
{
"endsAt": "2021-03-29T20:00:00Z",
"startsAt": "2021-03-29T08:00:00Z",
"participant": {
"id": null,
"user": {
"username": "colton"
}
}
},
{
"endsAt": "2021-03-30T08:00:00Z",
"startsAt": "2021-03-29T20:00:00Z",
"participant": {
"id": null,
"user": {
"username": "pauletta_paucek"
}
}
},
{
"endsAt": "2021-03-30T20:00:00Z",
"startsAt": "2021-03-30T08:00:00Z",
"participant": {
"id": null,
"user": {
"username": "nubia.hartmann"
}
}
},
{
"endsAt": "2021-03-31T08:00:00Z",
"startsAt": "2021-03-30T20:00:00Z",
"participant": {
"id": null,
"user": {
"username": "juliane.labadie"
}
}
},
{
"endsAt": "2021-03-31T20:00:00Z",
"startsAt": "2021-03-31T08:00:00Z",
"participant": {
"id": null,
"user": {
"username": "colton"
}
}
},
{
"endsAt": "2021-04-01T08:00:00Z",
"startsAt": "2021-03-31T20:00:00Z",
"participant": {
"id": null,
"user": {
"username": "pauletta_paucek"
}
}
},
{
"endsAt": "2021-04-01T20:00:00Z",
"startsAt": "2021-04-01T08:00:00Z",
"participant": {
"id": null,
"user": {
"username": "nubia.hartmann"
}
}
},
{
"endsAt": "2021-04-02T08:00:00Z",
"startsAt": "2021-04-01T20:00:00Z",
"participant": {
"id": null,
"user": {
"username": "juliane.labadie"
}
}
},
{
"endsAt": "2021-04-02T20:00:00Z",
"startsAt": "2021-04-02T08:00:00Z",
"participant": {
"id": null,
"user": {
"username": "colton"
}
}
},
{
"endsAt": "2021-04-03T08:00:00Z",
"startsAt": "2021-04-02T20:00:00Z",
"participant": {
"id": null,
"user": {
"username": "pauletta_paucek"
}
}
},
{
"endsAt": "2021-04-03T20:00:00Z",
"startsAt": "2021-04-03T08:00:00Z",
"participant": {
"id": null,
"user": {
"username": "nubia.hartmann"
}
}
},
{
"endsAt": "2021-04-04T08:00:00Z",
"startsAt": "2021-04-03T20:00:00Z",
"participant": {
"id": null,
"user": {
"username": "juliane.labadie"
}
}
},
{
"endsAt": "2021-04-04T20:00:00Z",
"startsAt": "2021-04-04T08:00:00Z",
"participant": {
"id": null,
"user": {
"username": "colton"
}
}
},
{
"endsAt": "2021-04-05T08:00:00Z",
"startsAt": "2021-04-04T20:00:00Z",
"participant": {
"id": null,
"user": {
"username": "pauletta_paucek"
}
}
},
{
"endsAt": "2021-04-05T20:00:00Z",
"startsAt": "2021-04-05T08:00:00Z",
"participant": {
"id": null,
"user": {
"username": "nubia.hartmann"
}
}
},
{
"endsAt": "2021-04-06T08:00:00Z",
"startsAt": "2021-04-05T20:00:00Z",
"participant": {
"id": null,
"user": {
"username": "juliane.labadie"
}
}
},
{
"endsAt": "2021-04-06T20:00:00Z",
"startsAt": "2021-04-06T08:00:00Z",
"participant": {
"id": null,
"user": {
"username": "colton"
}
}
},
{
"endsAt": "2021-04-07T08:00:00Z",
"startsAt": "2021-04-06T20:00:00Z",
"participant": {
"id": null,
"user": {
"username": "pauletta_paucek"
}
}
},
{
"endsAt": "2021-04-07T20:00:00Z",
"startsAt": "2021-04-07T08:00:00Z",
"participant": {
"id": null,
"user": {
"username": "nubia.hartmann"
}
}
},
{
"endsAt": "2021-04-08T08:00:00Z",
"startsAt": "2021-04-07T20:00:00Z",
"participant": {
"id": null,
"user": {
"username": "juliane.labadie"
}
}
},
{
"endsAt": "2021-04-08T20:00:00Z",
"startsAt": "2021-04-08T08:00:00Z",
"participant": {
"id": null,
"user": {
"username": "colton"
}
}
},
{
"endsAt": "2021-04-09T08:00:00Z",
"startsAt": "2021-04-08T20:00:00Z",
"participant": {
"id": null,
"user": {
"username": "pauletta_paucek"
}
}
}
]
}
},
"errors": []
}
}
}
*Noteworthy:
- This MR swaps two fields from non-nullable to nullable, which technically constitutes a breaking change. However, Oncall Schedules has been feature-flagged through %13.11. So this GraphQL change will need to be merged within %13.11 in order to avoid the 6-release-long deprecation cycle.
Does this MR meet the acceptance criteria?
Conformity
-
📋 Does this MR need a changelog?-
I have included a changelog entry. -
I have not included a changelog entry because _____.
-
-
Documentation (if required) -
Code review guidelines -
Merge request performance guidelines -
Style guides -
Database guides -
Separation of EE specific content
Availability and Testing
-
Review and add/update tests for this feature/bug. Consider all test levels. See the Test Planning Process. -
Tested in all supported browsers -
Informed Infrastructure department of a default or new setting change, if applicable per definition of done
Security
If this MR contains changes to processing or storing of credentials or tokens, authorization and authentication methods and other items described in the security review guidelines:
-
Label as security and @ mention @gitlab-com/gl-security/appsec
-
The MR includes necessary changes to maintain consistency between UI, API, email, or other methods -
Security reports checked/validated by a reviewer from the AppSec team
Edited by Sarah Yasonik