From 38ed4841ce137079728803fffefb0e17a8771916 Mon Sep 17 00:00:00 2001 From: Marko Simon Date: Tue, 9 Aug 2022 16:17:21 +0200 Subject: [PATCH 1/5] add schedule schema --- schedule.go | 271 +++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 259 insertions(+), 12 deletions(-) diff --git a/schedule.go b/schedule.go index 305b8fd..9704219 100644 --- a/schedule.go +++ b/schedule.go @@ -10,13 +10,17 @@ import ( // Schedule definition https://api.ilert.com/api-docs/#tag/Schedules type Schedule struct { - ID int64 `json:"id,omitempty"` - Name string `json:"name"` - Timezone string `json:"timezone,omitempty"` - StartsOn string `json:"startsOn,omitempty"` // Date time string in ISO format - CurrentShift *Shift `json:"currentShift,omitempty"` - NextShift *Shift `json:"nextShift,omitempty"` - Teams []TeamShort `json:"teams,omitempty"` + ID int64 `json:"id,omitempty"` + Name string `json:"name"` + Timezone string `json:"timezone,omitempty"` + StartsOn string `json:"startsOn,omitempty"` // Date time string in ISO format, @deprecated + ScheduleLayers []ScheduleLayer `json:"scheduleLayers,omitempty"` + Shifts []Shift `json:"shifts,omitempty"` + ShowGaps bool `json:"showGaps,omitempty"` + DefaultShiftDuration string `json:"defaultShiftDuration,omitempty"` // P7D + CurrentShift *Shift `json:"currentShift,omitempty"` + NextShift *Shift `json:"nextShift,omitempty"` + Teams []TeamShort `json:"teams,omitempty"` } // Shift definition @@ -26,10 +30,105 @@ type Shift struct { End string `json:"end"` // Date time string in ISO format } +// Schedule layer definition +type ScheduleLayer struct { + Name string `json:"name"` + StartsOn string `json:"startsOn"` // Date time string in ISO format + EndsOn string `json:"endsOn,omitempty"` // Date time string in ISO format + Users []User `json:"users"` + Rotation string `json:"rotation"` // P7D + RestrictionType string `json:"restrictionType,omitempty"` + Restrictions []LayerRestriction `json:"restrictions,omitempty"` +} + +type LayerRestriction struct { + From *TimeOfWeek `json:"from"` + To *TimeOfWeek `json:"to"` +} + +type TimeOfWeek struct { + DayOfWeek string `json:"dayOfWeek"` + Time string `json:"time"` // Time string in format <15:00> +} + +var RestrictionType = struct { + TimeOfWeek string + TimeOfDay string +}{ + TimeOfWeek: "TIME_OF_WEEK", + TimeOfDay: "TIME_OF_DAY", +} + +var DayOfWeek = struct { + Monday string + Tuesday string + Wednesday string + Thursday string + Friday string + Saturday string + Sunday string +}{ + Monday: "MONDAY", + Tuesday: "TUESDAY", + Wednesday: "WEDNESDAY", + Thursday: "THURSDAY", + Friday: "FRIDAY", + Saturday: "SATURDAY", + Sunday: "SUNDAY", +} + +// CreateScheduleInput represents the input of a CreateSchedule operation. +type CreateScheduleInput struct { + _ struct{} + Schedule *Schedule + AbortOnGaps *bool +} + +// CreateScheduleOutput represents the output of a CreateSchedule operation. +type CreateScheduleOutput struct { + _ struct{} + Schedule *Schedule +} + +// CreateSchedule creates a new schedule. https://api.ilert.com/api-docs/#tag/Schedules/paths/~1schedules/post +func (c *Client) CreateSchedule(input *CreateScheduleInput) (*CreateScheduleOutput, error) { + if input == nil { + return nil, errors.New("input is required") + } + if input.Schedule == nil { + return nil, errors.New("schedule input is required") + } + + q := url.Values{} + + if input.AbortOnGaps != nil { + q.Add("abort-on-gaps", strconv.FormatBool(*input.AbortOnGaps)) + } + + resp, err := c.httpClient.R().SetBody(input.Schedule).Post(fmt.Sprintf("%s?%s", apiRoutes.schedules, q.Encode())) + if err != nil { + return nil, err + } + if apiErr := getGenericAPIError(resp, 201); apiErr != nil { + return nil, apiErr + } + + schedule := &Schedule{} + err = json.Unmarshal(resp.Body(), schedule) + if err != nil { + return nil, err + } + + return &CreateScheduleOutput{Schedule: schedule}, nil +} + // GetScheduleInput represents the input of a GetSchedule operation. type GetScheduleInput struct { _ struct{} ScheduleID *int64 + + // describes optional properties that should be included in the response + Include []*string } // GetScheduleOutput represents the output of a GetSchedule operation. @@ -47,11 +146,17 @@ func (c *Client) GetSchedule(input *GetScheduleInput) (*GetScheduleOutput, error return nil, errors.New("Schedule id is required") } - resp, err := c.httpClient.R().Get(fmt.Sprintf("%s/%d", apiRoutes.schedules, *input.ScheduleID)) + q := url.Values{} + + for _, include := range input.Include { + q.Add("include", *include) + } + + resp, err := c.httpClient.R().Get(fmt.Sprintf("%s/%d?%s", apiRoutes.schedules, *input.ScheduleID, q.Encode())) if err != nil { return nil, err } - if apiErr := getGenericAPIError(resp, 201); apiErr != nil { + if apiErr := getGenericAPIError(resp, 200); apiErr != nil { return nil, apiErr } @@ -67,6 +172,9 @@ func (c *Client) GetSchedule(input *GetScheduleInput) (*GetScheduleOutput, error // GetSchedulesInput represents the input of a GetSchedules operation. type GetSchedulesInput struct { _ struct{} + + // describes optional properties that should be included in the response + Include []*string } // GetSchedulesOutput represents the output of a GetSchedules operation. @@ -77,7 +185,17 @@ type GetSchedulesOutput struct { // GetSchedules gets list on-call schedules. https://api.ilert.com/api-docs/#tag/Schedules/paths/~1schedules/get func (c *Client) GetSchedules(input *GetSchedulesInput) (*GetSchedulesOutput, error) { - resp, err := c.httpClient.R().Get(apiRoutes.schedules) + if input == nil { + return nil, errors.New("input is required") + } + + q := url.Values{} + + for _, include := range input.Include { + q.Add("include", *include) + } + + resp, err := c.httpClient.R().Get(fmt.Sprintf("%s?%s", apiRoutes.schedules, q.Encode())) if err != nil { return nil, err } @@ -123,7 +241,7 @@ func (c *Client) GetScheduleShifts(input *GetScheduleShiftsInput) (*GetScheduleS q.Add("from", *input.From) } if input.Until != nil { - q.Add("until", *input.From) + q.Add("until", *input.Until) } if input.ExcludeOverrides != nil { q.Add("exclude-overrides", strconv.FormatBool(*input.ExcludeOverrides)) @@ -196,7 +314,7 @@ type GetScheduleUserOnCallOutput struct { Shift *Shift } -// GetScheduleUserOnCall gets overrides for the specified schedule. https://api.ilert.com/api-docs/#tag/Schedules/paths/~1schedules~1{id}~1user-on-call/get +// GetScheduleUserOnCall gets the current user on call for specified schedule. https://api.ilert.com/api-docs/#tag/Schedules/paths/~1schedules~1{id}~1user-on-call/get func (c *Client) GetScheduleUserOnCall(input *GetScheduleUserOnCallInput) (*GetScheduleUserOnCallOutput, error) { if input == nil { return nil, errors.New("input is required") @@ -225,3 +343,132 @@ func (c *Client) GetScheduleUserOnCall(input *GetScheduleUserOnCallInput) (*GetS return &GetScheduleUserOnCallOutput{Shift: shift}, nil } + +// UpdateScheduleInput represents the input of a UpdateSchedule operation. +type UpdateScheduleInput struct { + _ struct{} + ScheduleID *int64 + Schedule *Schedule + AbortOnGaps *bool +} + +// UpdateScheduleOutput represents the output of a UpdateSchedule operation. +type UpdateScheduleOutput struct { + _ struct{} + Schedule *Schedule +} + +// UpdateSchedule updates the specific Schedule. https://api.ilert.com/api-docs/#tag/Schedules/paths/~1schedules~1{id}/put +func (c *Client) UpdateSchedule(input *UpdateScheduleInput) (*UpdateScheduleOutput, error) { + if input == nil { + return nil, errors.New("input is required") + } + if input.ScheduleID == nil { + return nil, errors.New("schedule id is required") + } + if input.Schedule == nil { + return nil, errors.New("schedule input is required") + } + + q := url.Values{} + + if input.AbortOnGaps != nil { + q.Add("abort-on-gaps", strconv.FormatBool(*input.AbortOnGaps)) + } + + url := fmt.Sprintf("%s/%d?%s", apiRoutes.schedules, *input.ScheduleID, q.Encode()) + + resp, err := c.httpClient.R().SetBody(input.Schedule).Put(url) + if err != nil { + return nil, err + } + if apiErr := getGenericAPIError(resp, 200); apiErr != nil { + return nil, apiErr + } + + schedule := &Schedule{} + err = json.Unmarshal(resp.Body(), schedule) + if err != nil { + return nil, err + } + + return &UpdateScheduleOutput{Schedule: schedule}, nil +} + +// AddScheduleShiftOverrideInput represents the input of a AddScheduleShiftOverride operation. +type AddScheduleShiftOverrideInput struct { + _ struct{} + ScheduleID *int64 + Shift *Shift +} + +// AddScheduleShiftOverrideOutput represents the output of a AddScheduleShiftOverride operation. +type AddScheduleShiftOverrideOutput struct { + _ struct{} + Schedule *Schedule +} + +// AddScheduleShiftOverride adds an override to a shift on the schedule. https://api.ilert.com/api-docs/#tag/Schedules/paths/~1schedules~1{id}~1overrides/put +func (c *Client) AddScheduleShiftOverride(input *AddScheduleShiftOverrideInput) (*AddScheduleShiftOverrideOutput, error) { + if input == nil { + return nil, errors.New("input is required") + } + if input.ScheduleID == nil { + return nil, errors.New("schedule id is required") + } + if input.Shift == nil { + return nil, errors.New("shift input is required") + } + + url := fmt.Sprintf("%s/%d/overrides", apiRoutes.schedules, *input.ScheduleID) + + resp, err := c.httpClient.R().SetBody(input.Shift).Post(url) + if err != nil { + return nil, err + } + if apiErr := getGenericAPIError(resp, 200); apiErr != nil { + return nil, apiErr + } + + schedule := &Schedule{} + err = json.Unmarshal(resp.Body(), schedule) + if err != nil { + return nil, err + } + + return &AddScheduleShiftOverrideOutput{Schedule: schedule}, nil +} + +// DeleteScheduleInput represents the input of a DeleteSchedule operation. +type DeleteScheduleInput struct { + _ struct{} + ScheduleID *int64 +} + +// DeleteScheduleOutput represents the output of a DeleteSchedule operation. +type DeleteScheduleOutput struct { + _ struct{} +} + +// DeleteSchedule deletes the specified Schedule. https://api.ilert.com/api-docs/#tag/Schedules/paths/~1schedules~1{id}/delete +func (c *Client) DeleteSchedule(input *DeleteScheduleInput) (*DeleteScheduleOutput, error) { + if input == nil { + return nil, errors.New("input is required") + } + if input.ScheduleID == nil { + return nil, errors.New("schedule id is required") + } + + url := fmt.Sprintf("%s/%d", apiRoutes.schedules, *input.ScheduleID) + + resp, err := c.httpClient.R().Delete(url) + + if err != nil { + return nil, err + } + if apiErr := getGenericAPIError(resp, 204); apiErr != nil { + return nil, apiErr + } + + return &DeleteScheduleOutput{}, nil +} From dc44247b2ac2d0fe5650b7dcc3a7d835f346db47 Mon Sep 17 00:00:00 2001 From: Marko Simon Date: Tue, 9 Aug 2022 18:16:07 +0200 Subject: [PATCH 2/5] fix to schedule update example --- examples/schedules/main.go | 8 ++------ schedule.go | 9 +++++++++ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/examples/schedules/main.go b/examples/schedules/main.go index cb2ea35..6535c23 100644 --- a/examples/schedules/main.go +++ b/examples/schedules/main.go @@ -8,11 +8,8 @@ import ( ) func main() { - // set your environment variables: - // ILERT_ORGANIZATION="your organization" - // ILERT_USERNAME="your username" - // ILERT_PASSWORD="your password" - client := ilert.NewClient() + var apiToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJpbGVydCIsImlsX3QiOiJBUEkiLCJpbF92IjoxLCJpbF9rIjoiZDAxYmY5Njg4ZjRlNGMzYmIxNDY5YWNhNjllNTgxY2IifQ.eDSLCoArmP_Qbh60jly9bza1gmVYx1ROdLsvTMF0bvk" + client := ilert.NewClient(ilert.WithAPIToken(apiToken)) result, err := client.GetSchedules(&ilert.GetSchedulesInput{}) if err != nil { log.Println(result) @@ -22,5 +19,4 @@ func main() { for _, schedule := range result.Schedules { log.Println(fmt.Sprintf("%+v\n", *schedule)) } - } diff --git a/schedule.go b/schedule.go index 9704219..ccfff8c 100644 --- a/schedule.go +++ b/schedule.go @@ -13,6 +13,7 @@ type Schedule struct { ID int64 `json:"id,omitempty"` Name string `json:"name"` Timezone string `json:"timezone,omitempty"` + Type string `json:"type,omitempty"` StartsOn string `json:"startsOn,omitempty"` // Date time string in ISO format, @deprecated ScheduleLayers []ScheduleLayer `json:"scheduleLayers,omitempty"` Shifts []Shift `json:"shifts,omitempty"` @@ -51,6 +52,14 @@ type TimeOfWeek struct { Time string `json:"time"` // Time string in format <15:00> } +var ScheduleType = struct { + Static string + Recurring string +}{ + Static: "STATIC", + Recurring: "RECURRING", +} + var RestrictionType = struct { TimeOfWeek string TimeOfDay string From f2df5bafa0cd9d014b2a339420c3ba22538cfe4c Mon Sep 17 00:00:00 2001 From: Marko Simon Date: Tue, 16 Aug 2022 05:11:22 +0200 Subject: [PATCH 3/5] handle schedule types, fix required fields --- schedule.go | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/schedule.go b/schedule.go index ccfff8c..a665934 100644 --- a/schedule.go +++ b/schedule.go @@ -10,15 +10,15 @@ import ( // Schedule definition https://api.ilert.com/api-docs/#tag/Schedules type Schedule struct { - ID int64 `json:"id,omitempty"` + ID int64 `json:"id"` Name string `json:"name"` - Timezone string `json:"timezone,omitempty"` - Type string `json:"type,omitempty"` + Timezone string `json:"timezone"` + Type string `json:"type"` StartsOn string `json:"startsOn,omitempty"` // Date time string in ISO format, @deprecated ScheduleLayers []ScheduleLayer `json:"scheduleLayers,omitempty"` Shifts []Shift `json:"shifts,omitempty"` ShowGaps bool `json:"showGaps,omitempty"` - DefaultShiftDuration string `json:"defaultShiftDuration,omitempty"` // P7D + DefaultShiftDuration string `json:"defaultShiftDuration,omitempty"` // for ex. P7D (7 Days) or PT8H (8 Hours) CurrentShift *Shift `json:"currentShift,omitempty"` NextShift *Shift `json:"nextShift,omitempty"` Teams []TeamShort `json:"teams,omitempty"` @@ -107,6 +107,12 @@ func (c *Client) CreateSchedule(input *CreateScheduleInput) (*CreateScheduleOutp if input.Schedule == nil { return nil, errors.New("schedule input is required") } + if input.Schedule.Type == ScheduleType.Static && input.Schedule.Shifts == nil { + return nil, errors.New("shifts must be declared on static schedule") + } + if input.Schedule.Type == ScheduleType.Recurring && input.Schedule.ScheduleLayers == nil { + return nil, errors.New("schedule layers must be declared on recurring schedule") + } q := url.Values{} From 8fd36269a019298c79bc9f7a724f410a3655bd56 Mon Sep 17 00:00:00 2001 From: Marko Simon Date: Fri, 26 Aug 2022 11:41:38 +0200 Subject: [PATCH 4/5] fix example --- examples/schedules/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/schedules/main.go b/examples/schedules/main.go index 6535c23..cf0c801 100644 --- a/examples/schedules/main.go +++ b/examples/schedules/main.go @@ -8,7 +8,7 @@ import ( ) func main() { - var apiToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJpbGVydCIsImlsX3QiOiJBUEkiLCJpbF92IjoxLCJpbF9rIjoiZDAxYmY5Njg4ZjRlNGMzYmIxNDY5YWNhNjllNTgxY2IifQ.eDSLCoArmP_Qbh60jly9bza1gmVYx1ROdLsvTMF0bvk" + var apiToken = "your API token" client := ilert.NewClient(ilert.WithAPIToken(apiToken)) result, err := client.GetSchedules(&ilert.GetSchedulesInput{}) if err != nil { From 8a0e4d917dda609662f6346e78e3ce8434b7553f Mon Sep 17 00:00:00 2001 From: Marko Simon Date: Fri, 26 Aug 2022 12:18:33 +0200 Subject: [PATCH 5/5] update changelog and version --- CHANGELOG.md | 43 +++++++++++++++++++++++++++++++++++++++++++ version.go | 2 +- 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2582265..ad3db63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,48 @@ # Changelog +## 26.08.2022, Version 2.1.0 + +- add schedules + +## 12.07.2022, Version 2.0.5 + +- another fix for alert source support hours check + +## 12.07.2022, Version 2.0.4 + +- fix alert source support hours check + +## 12.07.2022, Version 2.0.3 + +- fix error codes on create resources + +## 08.07.2022, Version 2.0.2 + +- add various lists +- fix alert source creation +- add deprecated hints +- fix user alert notification type list + +## 04.07.2022, Version 2.0.1 + +- fix internal version + +## 30.06.2022, Version 2.0.0 + +- add automation rules +- add incident templates +- add services +- add statuspages +- migrating API v1 to versionless + - rename incident v1 to alert and update fields + - add incident + - renaming connection to alert action, therefore deprecating connection + - update alert source fields + - update event fields + - update uptime monitor fields + - update user fields +- update examples + ## 16.04.2022, Version 1.6.5 - add not_found error types diff --git a/version.go b/version.go index cd7d5dd..895cbc7 100644 --- a/version.go +++ b/version.go @@ -1,4 +1,4 @@ package ilert // Version package version -const Version = "v2.0.5" +const Version = "v2.1.0"