Skip to content

Commit a18a21c

Browse files
authored
Oncall tools improvements (#88)
1 parent bd9400f commit a18a21c

File tree

4 files changed

+95
-32
lines changed

4 files changed

+95
-32
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ go 1.24.0
44

55
require (
66
github.com/go-openapi/strfmt v0.23.0
7-
github.com/grafana/amixr-api-go-client v0.0.20
7+
github.com/grafana/amixr-api-go-client v0.0.21
88
github.com/grafana/grafana-openapi-client-go v0.0.0-20250108132429-8d7e1f158f65
99
github.com/grafana/incident-go v0.0.0-20250211094540-dc6a98fdae43
1010
github.com/invopop/jsonschema v0.13.0

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@ github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17
4949
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
5050
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
5151
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
52-
github.com/grafana/amixr-api-go-client v0.0.20 h1:/L44PCP0H8y0Z6NSZmJFCUH3Nc5gRPphKrp2Ck7ufz0=
53-
github.com/grafana/amixr-api-go-client v0.0.20/go.mod h1:u53FF0WSBMx6XvZK58fply91KBl6X+OtIu0aJC07amY=
52+
github.com/grafana/amixr-api-go-client v0.0.21 h1:kJBpZATTCifNNLiZ5Ce6djsjZxl7UHPJRczhuavFI/0=
53+
github.com/grafana/amixr-api-go-client v0.0.21/go.mod h1:u53FF0WSBMx6XvZK58fply91KBl6X+OtIu0aJC07amY=
5454
github.com/grafana/grafana-openapi-client-go v0.0.0-20250108132429-8d7e1f158f65 h1:AnfwjPE8TXJO8CX0Q5PvtzGta9Ls3iRASWVV4jHl4KA=
5555
github.com/grafana/grafana-openapi-client-go v0.0.0-20250108132429-8d7e1f158f65/go.mod h1:hiZnMmXc9KXNUlvkV2BKFsiWuIFF/fF4wGgYWEjBitI=
5656
github.com/grafana/incident-go v0.0.0-20250211094540-dc6a98fdae43 h1:+MCsOKi5BJ1wO3PRj3eDNxCScYwE2IcKNW1t8OcTarE=

tools/oncall.go

Lines changed: 85 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,46 @@ func oncallClientFromContext(ctx context.Context) (*aapi.Client, error) {
7575
return client, nil
7676
}
7777

78+
// getUserServiceFromContext creates a new UserService using the OnCall client from the context
79+
func getUserServiceFromContext(ctx context.Context) (*aapi.UserService, error) {
80+
client, err := oncallClientFromContext(ctx)
81+
if err != nil {
82+
return nil, fmt.Errorf("getting OnCall client: %w", err)
83+
}
84+
85+
return aapi.NewUserService(client), nil
86+
}
87+
88+
// getScheduleServiceFromContext creates a new ScheduleService using the OnCall client from the context
89+
func getScheduleServiceFromContext(ctx context.Context) (*aapi.ScheduleService, error) {
90+
client, err := oncallClientFromContext(ctx)
91+
if err != nil {
92+
return nil, fmt.Errorf("getting OnCall client: %w", err)
93+
}
94+
95+
return aapi.NewScheduleService(client), nil
96+
}
97+
98+
// getTeamServiceFromContext creates a new TeamService using the OnCall client from the context
99+
func getTeamServiceFromContext(ctx context.Context) (*aapi.TeamService, error) {
100+
client, err := oncallClientFromContext(ctx)
101+
if err != nil {
102+
return nil, fmt.Errorf("getting OnCall client: %w", err)
103+
}
104+
105+
return aapi.NewTeamService(client), nil
106+
}
107+
108+
// getOnCallShiftServiceFromContext creates a new OnCallShiftService using the OnCall client from the context
109+
func getOnCallShiftServiceFromContext(ctx context.Context) (*aapi.OnCallShiftService, error) {
110+
client, err := oncallClientFromContext(ctx)
111+
if err != nil {
112+
return nil, fmt.Errorf("getting OnCall client: %w", err)
113+
}
114+
115+
return aapi.NewOnCallShiftService(client), nil
116+
}
117+
78118
type ListOnCallSchedulesParams struct {
79119
TeamID string `json:"teamId,omitempty" jsonschema:"description=The ID of the team to list schedules for"`
80120
ScheduleID string `json:"scheduleId,omitempty" jsonschema:"description=The ID of the schedule to get details for. If provided, returns only that schedule's details"`
@@ -91,13 +131,11 @@ type ScheduleSummary struct {
91131
}
92132

93133
func listOnCallSchedules(ctx context.Context, args ListOnCallSchedulesParams) ([]*ScheduleSummary, error) {
94-
client, err := oncallClientFromContext(ctx)
134+
scheduleService, err := getScheduleServiceFromContext(ctx)
95135
if err != nil {
96-
return nil, fmt.Errorf("getting OnCall client: %w", err)
136+
return nil, fmt.Errorf("getting OnCall schedule service: %w", err)
97137
}
98138

99-
scheduleService := aapi.NewScheduleService(client)
100-
101139
if args.ScheduleID != "" {
102140
schedule, _, err := scheduleService.GetSchedule(args.ScheduleID, &aapi.GetScheduleOptions{})
103141
if err != nil {
@@ -119,6 +157,9 @@ func listOnCallSchedules(ctx context.Context, args ListOnCallSchedulesParams) ([
119157
if args.Page > 0 {
120158
listOptions.Page = args.Page
121159
}
160+
if args.TeamID != "" {
161+
listOptions.TeamID = args.TeamID
162+
}
122163

123164
response, _, err := scheduleService.ListSchedules(listOptions)
124165
if err != nil {
@@ -128,11 +169,6 @@ func listOnCallSchedules(ctx context.Context, args ListOnCallSchedulesParams) ([
128169
// Convert schedules to summaries
129170
summaries := make([]*ScheduleSummary, 0, len(response.Schedules))
130171
for _, schedule := range response.Schedules {
131-
// Filter by team ID if provided. Note: We filter here because the API doesn't support
132-
// filtering by team ID directly in the ListSchedules endpoint.
133-
if args.TeamID != "" && schedule.TeamId != args.TeamID {
134-
continue
135-
}
136172
summary := &ScheduleSummary{
137173
ID: schedule.ID,
138174
Name: schedule.Name,
@@ -159,12 +195,11 @@ type GetOnCallShiftParams struct {
159195
}
160196

161197
func getOnCallShift(ctx context.Context, args GetOnCallShiftParams) (*aapi.OnCallShift, error) {
162-
client, err := oncallClientFromContext(ctx)
198+
shiftService, err := getOnCallShiftServiceFromContext(ctx)
163199
if err != nil {
164-
return nil, fmt.Errorf("getting OnCall client: %w", err)
200+
return nil, fmt.Errorf("getting OnCall shift service: %w", err)
165201
}
166202

167-
shiftService := aapi.NewOnCallShiftService(client)
168203
shift, _, err := shiftService.GetOnCallShift(args.ShiftID, &aapi.GetOnCallShiftOptions{})
169204
if err != nil {
170205
return nil, fmt.Errorf("getting OnCall shift %s: %w", args.ShiftID, err)
@@ -181,37 +216,61 @@ var GetOnCallShift = mcpgrafana.MustTool(
181216

182217
// CurrentOnCallUsers represents the currently on-call users for a schedule
183218
type CurrentOnCallUsers struct {
184-
ScheduleID string `json:"scheduleId" jsonschema:"description=The ID of the schedule"`
185-
ScheduleName string `json:"scheduleName" jsonschema:"description=The name of the schedule"`
186-
Users []string `json:"users" jsonschema:"description=List of user IDs currently on call"`
219+
ScheduleID string `json:"scheduleId" jsonschema:"description=The ID of the schedule"`
220+
ScheduleName string `json:"scheduleName" jsonschema:"description=The name of the schedule"`
221+
Users []*aapi.User `json:"users" jsonschema:"description=List of users currently on call"`
187222
}
188223

189224
type GetCurrentOnCallUsersParams struct {
190225
ScheduleID string `json:"scheduleId" jsonschema:"required,description=The ID of the schedule to get current on-call users for"`
191226
}
192227

193228
func getCurrentOnCallUsers(ctx context.Context, args GetCurrentOnCallUsersParams) (*CurrentOnCallUsers, error) {
194-
client, err := oncallClientFromContext(ctx)
229+
scheduleService, err := getScheduleServiceFromContext(ctx)
195230
if err != nil {
196-
return nil, fmt.Errorf("getting OnCall client: %w", err)
231+
return nil, fmt.Errorf("getting OnCall schedule service: %w", err)
197232
}
198233

199-
scheduleService := aapi.NewScheduleService(client)
200234
schedule, _, err := scheduleService.GetSchedule(args.ScheduleID, &aapi.GetScheduleOptions{})
201235
if err != nil {
202236
return nil, fmt.Errorf("getting schedule %s: %w", args.ScheduleID, err)
203237
}
204238

205-
return &CurrentOnCallUsers{
239+
// Create the result with the schedule info
240+
result := &CurrentOnCallUsers{
206241
ScheduleID: schedule.ID,
207242
ScheduleName: schedule.Name,
208-
Users: schedule.OnCallNow,
209-
}, nil
243+
Users: make([]*aapi.User, 0, len(schedule.OnCallNow)),
244+
}
245+
246+
// If there are no users on call, return early
247+
if len(schedule.OnCallNow) == 0 {
248+
return result, nil
249+
}
250+
251+
// Get the user service to fetch user details
252+
userService, err := getUserServiceFromContext(ctx)
253+
if err != nil {
254+
return nil, fmt.Errorf("getting OnCall user service: %w", err)
255+
}
256+
257+
// Fetch details for each user currently on call
258+
for _, userID := range schedule.OnCallNow {
259+
user, _, err := userService.GetUser(userID, &aapi.GetUserOptions{})
260+
if err != nil {
261+
// Log the error but continue with other users
262+
fmt.Printf("Error fetching user %s: %v\n", userID, err)
263+
continue
264+
}
265+
result.Users = append(result.Users, user)
266+
}
267+
268+
return result, nil
210269
}
211270

212271
var GetCurrentOnCallUsers = mcpgrafana.MustTool(
213272
"get_current_oncall_users",
214-
"Get users currently on-call for a specific schedule. A schedule is a calendar-based system defining when team members are on-call",
273+
"Get users currently on-call for a specific schedule. A schedule is a calendar-based system defining when team members are on-call. This tool will return info about all users currently on-call for the schedule, regardless of team.",
215274
getCurrentOnCallUsers,
216275
)
217276

@@ -220,17 +279,16 @@ type ListOnCallTeamsParams struct {
220279
}
221280

222281
func listOnCallTeams(ctx context.Context, args ListOnCallTeamsParams) ([]*aapi.Team, error) {
223-
client, err := oncallClientFromContext(ctx)
282+
teamService, err := getTeamServiceFromContext(ctx)
224283
if err != nil {
225-
return nil, fmt.Errorf("getting OnCall client: %w", err)
284+
return nil, fmt.Errorf("getting OnCall team service: %w", err)
226285
}
227286

228287
listOptions := &aapi.ListTeamOptions{}
229288
if args.Page > 0 {
230289
listOptions.Page = args.Page
231290
}
232291

233-
teamService := aapi.NewTeamService(client)
234292
response, _, err := teamService.ListTeams(listOptions)
235293
if err != nil {
236294
return nil, fmt.Errorf("listing OnCall teams: %w", err)
@@ -252,13 +310,11 @@ type ListOnCallUsersParams struct {
252310
}
253311

254312
func listOnCallUsers(ctx context.Context, args ListOnCallUsersParams) ([]*aapi.User, error) {
255-
client, err := oncallClientFromContext(ctx)
313+
userService, err := getUserServiceFromContext(ctx)
256314
if err != nil {
257-
return nil, fmt.Errorf("getting OnCall client: %w", err)
315+
return nil, fmt.Errorf("getting OnCall user service: %w", err)
258316
}
259317

260-
userService := aapi.NewUserService(client)
261-
262318
if args.UserID != "" {
263319
user, _, err := userService.GetUser(args.UserID, &aapi.GetUserOptions{})
264320
if err != nil {

tools/oncall_cloud_test.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,13 @@ func TestCloudGetCurrentOnCallUsers(t *testing.T) {
153153
assert.Equal(t, scheduleID, result.ScheduleID, "Should return the correct schedule")
154154
assert.NotEmpty(t, result.ScheduleName, "Schedule should have a name")
155155
assert.NotNil(t, result.Users, "Users field should be present")
156+
157+
// Assert that Users is of type []*aapi.User
158+
if len(result.Users) > 0 {
159+
user := result.Users[0]
160+
assert.NotEmpty(t, user.ID, "User should have an ID")
161+
assert.NotEmpty(t, user.Username, "User should have a username")
162+
}
156163
})
157164

158165
t.Run("get current on-call users with invalid schedule ID", func(t *testing.T) {

0 commit comments

Comments
 (0)