1package pagerduty
2
3import (
4	"fmt"
5	"net/http"
6
7	"github.com/google/go-querystring/query"
8)
9
10// Restriction limits on-call responsibility for a layer to certain times of the day or week.
11type Restriction struct {
12	Type            string `json:"type,omitempty"`
13	StartTimeOfDay  string `json:"start_time_of_day,omitempty"`
14	StartDayOfWeek  uint   `json:"start_day_of_week,omitempty"`
15	DurationSeconds uint   `json:"duration_seconds,omitempty"`
16}
17
18// RenderedScheduleEntry represents the computed set of schedule layer entries that put users on call for a schedule, and cannot be modified directly.
19type RenderedScheduleEntry struct {
20	Start string    `json:"start,omitempty"`
21	End   string    `json:"end,omitempty"`
22	User  APIObject `json:"user,omitempty"`
23}
24
25// ScheduleLayer is an entry that puts users on call for a schedule.
26type ScheduleLayer struct {
27	APIObject
28	Name                       string                  `json:"name,omitempty"`
29	Start                      string                  `json:"start,omitempty"`
30	End                        string                  `json:"end,omitempty"`
31	RotationVirtualStart       string                  `json:"rotation_virtual_start,omitempty"`
32	RotationTurnLengthSeconds  uint                    `json:"rotation_turn_length_seconds,omitempty"`
33	Users                      []UserReference         `json:"users,omitempty"`
34	Restrictions               []Restriction           `json:"restrictions,omitempty"`
35	RenderedScheduleEntries    []RenderedScheduleEntry `json:"rendered_schedule_entries,omitempty"`
36	RenderedCoveragePercentage float64                 `json:"rendered_coverage_percentage,omitempty"`
37}
38
39// Schedule determines the time periods that users are on call.
40type Schedule struct {
41	APIObject
42	Name                string          `json:"name,omitempty"`
43	TimeZone            string          `json:"time_zone,omitempty"`
44	Description         string          `json:"description,omitempty"`
45	EscalationPolicies  []APIObject     `json:"escalation_policies,omitempty"`
46	Users               []APIObject     `json:"users,omitempty"`
47	ScheduleLayers      []ScheduleLayer `json:"schedule_layers,omitempty"`
48	OverrideSubschedule ScheduleLayer   `json:"override_subschedule,omitempty"`
49	FinalSchedule       ScheduleLayer   `json:"final_schedule,omitempty"`
50}
51
52// ListSchedulesOptions is the data structure used when calling the ListSchedules API endpoint.
53type ListSchedulesOptions struct {
54	APIListObject
55	Query string `url:"query,omitempty"`
56}
57
58// ListSchedulesResponse is the data structure returned from calling the ListSchedules API endpoint.
59type ListSchedulesResponse struct {
60	APIListObject
61	Schedules []Schedule `json:"schedules"`
62}
63
64// UserReference is a reference to an authorized PagerDuty user.
65type UserReference struct {
66	User APIObject `json:"user"`
67}
68
69// ListSchedules lists the on-call schedules.
70func (c *Client) ListSchedules(o ListSchedulesOptions) (*ListSchedulesResponse, error) {
71	v, err := query.Values(o)
72	if err != nil {
73		return nil, err
74	}
75	resp, err := c.get("/schedules?" + v.Encode())
76	if err != nil {
77		return nil, err
78	}
79	var result ListSchedulesResponse
80	return &result, c.decodeJSON(resp, &result)
81}
82
83// CreateSchedule creates a new on-call schedule.
84func (c *Client) CreateSchedule(s Schedule) (*Schedule, error) {
85	data := make(map[string]Schedule)
86	data["schedule"] = s
87	resp, err := c.post("/schedules", data, nil)
88	if err != nil {
89		return nil, err
90	}
91	return getScheduleFromResponse(c, resp)
92}
93
94// PreviewScheduleOptions is the data structure used when calling the PreviewSchedule API endpoint.
95type PreviewScheduleOptions struct {
96	APIListObject
97	Since    string `url:"since,omitempty"`
98	Until    string `url:"until,omitempty"`
99	Overflow bool   `url:"overflow,omitempty"`
100}
101
102// PreviewSchedule previews what an on-call schedule would look like without saving it.
103func (c *Client) PreviewSchedule(s Schedule, o PreviewScheduleOptions) error {
104	v, err := query.Values(o)
105	if err != nil {
106		return err
107	}
108	var data map[string]Schedule
109	data["schedule"] = s
110	_, e := c.post("/schedules/preview?"+v.Encode(), data, nil)
111	return e
112}
113
114// DeleteSchedule deletes an on-call schedule.
115func (c *Client) DeleteSchedule(id string) error {
116	_, err := c.delete("/schedules/" + id)
117	return err
118}
119
120// GetScheduleOptions is the data structure used when calling the GetSchedule API endpoint.
121type GetScheduleOptions struct {
122	APIListObject
123	TimeZone string `url:"time_zone,omitempty"`
124	Since    string `url:"since,omitempty"`
125	Until    string `url:"until,omitempty"`
126}
127
128// GetSchedule shows detailed information about a schedule, including entries for each layer and sub-schedule.
129func (c *Client) GetSchedule(id string, o GetScheduleOptions) (*Schedule, error) {
130	v, err := query.Values(o)
131	if err != nil {
132		return nil, fmt.Errorf("Could not parse values for query: %v", err)
133	}
134	resp, err := c.get("/schedules/" + id + "?" + v.Encode())
135	if err != nil {
136		return nil, err
137	}
138	return getScheduleFromResponse(c, resp)
139}
140
141// UpdateScheduleOptions is the data structure used when calling the UpdateSchedule API endpoint.
142type UpdateScheduleOptions struct {
143	Overflow bool `url:"overflow,omitempty"`
144}
145
146// UpdateSchedule updates an existing on-call schedule.
147func (c *Client) UpdateSchedule(id string, s Schedule) (*Schedule, error) {
148	v := make(map[string]Schedule)
149	v["schedule"] = s
150	resp, err := c.put("/schedules/"+id, v, nil)
151	if err != nil {
152		return nil, err
153	}
154	return getScheduleFromResponse(c, resp)
155}
156
157// ListOverridesOptions is the data structure used when calling the ListOverrides API endpoint.
158type ListOverridesOptions struct {
159	APIListObject
160	Since    string `url:"since,omitempty"`
161	Until    string `url:"until,omitempty"`
162	Editable bool   `url:"editable,omitempty"`
163	Overflow bool   `url:"overflow,omitempty"`
164}
165
166// Overrides are any schedule layers from the override layer.
167type Override struct {
168	ID    string    `json:"id,omitempty"`
169	Start string    `json:"start,omitempty"`
170	End   string    `json:"end,omitempty"`
171	User  APIObject `json:"user,omitempty"`
172}
173
174// ListOverrides lists overrides for a given time range.
175func (c *Client) ListOverrides(id string, o ListOverridesOptions) ([]Override, error) {
176	v, err := query.Values(o)
177	if err != nil {
178		return nil, err
179	}
180	resp, err := c.get("/schedules/" + id + "/overrides?" + v.Encode())
181	if err != nil {
182		return nil, err
183	}
184	var result map[string][]Override
185	if err := c.decodeJSON(resp, &result); err != nil {
186		return nil, err
187	}
188	overrides, ok := result["overrides"]
189	if !ok {
190		return nil, fmt.Errorf("JSON response does not have overrides field")
191	}
192	return overrides, nil
193}
194
195// CreateOverride creates an override for a specific user covering the specified time range.
196func (c *Client) CreateOverride(id string, o Override) (*Override, error) {
197	data := make(map[string]Override)
198	data["override"] = o
199	resp, err := c.post("/schedules/"+id+"/overrides", data, nil)
200	if err != nil {
201		return nil, err
202	}
203	return getOverrideFromResponse(c, resp)
204}
205
206// DeleteOverride removes an override.
207func (c *Client) DeleteOverride(scheduleID, overrideID string) error {
208	_, err := c.delete("/schedules/" + scheduleID + "/overrides/" + overrideID)
209	return err
210}
211
212// ListOnCallUsersOptions is the data structure used when calling the ListOnCallUsers API endpoint.
213type ListOnCallUsersOptions struct {
214	APIListObject
215	Since string `url:"since,omitempty"`
216	Until string `url:"until,omitempty"`
217}
218
219// ListOnCallUsers lists all of the users on call in a given schedule for a given time range.
220func (c *Client) ListOnCallUsers(id string, o ListOnCallUsersOptions) ([]User, error) {
221	v, err := query.Values(o)
222	if err != nil {
223		return nil, err
224	}
225	resp, err := c.get("/schedules/" + id + "/users?" + v.Encode())
226	if err != nil {
227		return nil, err
228	}
229	var result map[string][]User
230	if err := c.decodeJSON(resp, &result); err != nil {
231		return nil, err
232	}
233	u, ok := result["users"]
234	if !ok {
235		return nil, fmt.Errorf("JSON response does not have users field")
236	}
237	return u, nil
238}
239
240func getScheduleFromResponse(c *Client, resp *http.Response) (*Schedule, error) {
241	var target map[string]Schedule
242	if dErr := c.decodeJSON(resp, &target); dErr != nil {
243		return nil, fmt.Errorf("Could not decode JSON response: %v", dErr)
244	}
245	rootNode := "schedule"
246	t, nodeOK := target[rootNode]
247	if !nodeOK {
248		return nil, fmt.Errorf("JSON response does not have %s field", rootNode)
249	}
250	return &t, nil
251}
252
253func getOverrideFromResponse(c *Client, resp *http.Response) (*Override, error) {
254	var target map[string]Override
255	if dErr := c.decodeJSON(resp, &target); dErr != nil {
256		return nil, fmt.Errorf("Could not decode JSON response: %v", dErr)
257	}
258	rootNode := "override"
259	o, nodeOK := target[rootNode]
260	if !nodeOK {
261		return nil, fmt.Errorf("JSON response does not have %s field", rootNode)
262	}
263	return &o, nil
264}
265