diff --git a/main.go b/main.go index 6651cc41..2535058b 100644 --- a/main.go +++ b/main.go @@ -51,6 +51,7 @@ import ( "github.com/wtfutil/wtf/travisci" "github.com/wtfutil/wtf/trello" "github.com/wtfutil/wtf/twitter" + "github.com/wtfutil/wtf/victorops" "github.com/wtfutil/wtf/weatherservices/prettyweather" "github.com/wtfutil/wtf/weatherservices/weather" "github.com/wtfutil/wtf/wtf" @@ -243,6 +244,8 @@ func addWidget(app *tview.Application, pages *tview.Pages, widgetName string) { widgets = append(widgets, trello.NewWidget(app)) case "twitter": widgets = append(widgets, twitter.NewWidget(app, pages)) + case "victorops": + widgets = append(widgets, victorops.NewWidget(app)) case "weather": widgets = append(widgets, weather.NewWidget(app, pages)) case "zendesk": diff --git a/victorops/client.go b/victorops/client.go new file mode 100644 index 00000000..8c952e28 --- /dev/null +++ b/victorops/client.go @@ -0,0 +1,86 @@ +package victorops + +import ( + "encoding/json" + "fmt" + "net/http" + "os" + "strings" + + "github.com/wtfutil/wtf/logger" + "github.com/wtfutil/wtf/wtf" +) + +// Fetch gets the current oncall users +func Fetch() ([]OnCallTeam, error) { + scheduleURL := "https://api.victorops.com/api-public/v1/oncall/current" + response, err := victorOpsRequest(scheduleURL, apiID(), apiKey()) + return response, err +} + +/* ---------------- Unexported Functions ---------------- */ +func apiID() string { + return wtf.Config.UString( + "wtf.mods.victorops.apiID", + os.Getenv("WTF_VICTOROPS_API_ID"), + ) +} + +func apiKey() string { + return wtf.Config.UString( + "wtf.mods.victorops.apiKey", + os.Getenv("WTF_VICTOROPS_API_KEY"), + ) +} + +func victorOpsRequest(url string, apiID string, apiKey string) ([]OnCallTeam, error) { + req, err := http.NewRequest("GET", url, nil) + if err != nil { + logger.Log(fmt.Sprintf("Failed to initialize sessions to VictorOps. ERROR: %s", err)) + return nil, err + } + + req.Header.Set("X-VO-Api-Id", apiID) + req.Header.Set("X-VO-Api-Key", apiKey) + client := &http.Client{} + + resp, err := client.Do(req) + if err != nil { + logger.Log(fmt.Sprintf("Failed to make request to VictorOps. ERROR: %s", err)) + return nil, err + } + if resp.StatusCode != 200 { + return nil, fmt.Errorf("%s", resp.Status) + } + defer resp.Body.Close() + + response := &OnCallResponse{} + if err := json.NewDecoder(resp.Body).Decode(response); err != nil { + logger.Log(fmt.Sprintf("Failed to decode JSON response. ERROR: %s", err)) + return nil, err + } + + teams := parseTeams(response) + return teams, nil +} + +func parseTeams(input *OnCallResponse) []OnCallTeam { + var teamResults []OnCallTeam + + for _, data := range input.TeamsOnCall { + var team OnCallTeam + team.Name = data.Team.Name + team.Slug = data.Team.Slug + var userList []string + for _, userData := range data.OnCallNow { + escalationPolicy := userData.EscalationPolicy.Name + for _, user := range userData.Users { + userList = append(userList, user.OnCallUser.Username) + } + team.OnCall = append(team.OnCall, OnCall{escalationPolicy, strings.Join(userList, ", ")}) + } + teamResults = append(teamResults, team) + } + + return teamResults +} diff --git a/victorops/oncallresponse.go b/victorops/oncallresponse.go new file mode 100644 index 00000000..418a407c --- /dev/null +++ b/victorops/oncallresponse.go @@ -0,0 +1,22 @@ +package victorops + +// OnCallResponse object +type OnCallResponse struct { + TeamsOnCall []struct { + Team struct { + Name string `json:"name"` + Slug string `json:"slug"` + } `json:"team"` + OnCallNow []struct { + EscalationPolicy struct { + Name string `json:"name"` + Slug string `json:"slug"` + } `json:"escalationPolicy"` + Users []struct { + OnCallUser struct { + Username string `json:"username"` + } `json:"onCalluser"` + } `json:"users"` + } `json:"oncallNow"` + } `json:"teamsOnCall"` +} diff --git a/victorops/oncallteam.go b/victorops/oncallteam.go new file mode 100644 index 00000000..a12fcce3 --- /dev/null +++ b/victorops/oncallteam.go @@ -0,0 +1,16 @@ +package victorops + +// OnCallTeam object to make +// managing objects easier +type OnCallTeam struct { + Name string + Slug string + OnCall []OnCall +} + +// OnCall object to handle +// different on call policies +type OnCall struct { + Policy string + Userlist string +} diff --git a/victorops/widget.go b/victorops/widget.go new file mode 100644 index 00000000..0a9dac06 --- /dev/null +++ b/victorops/widget.go @@ -0,0 +1,89 @@ +package victorops + +import ( + "fmt" + + "github.com/rivo/tview" + "github.com/wtfutil/wtf/wtf" +) + +// HelpText to display to users +const HelpText = ` + Keyboard commands for VictorOps + + /: Show/hide this help window + arrow down: Scroll down the list + arrow up: Scroll up the list +` + +// Widget contains text info +type Widget struct { + wtf.TextWidget + teams []OnCallTeam +} + +// NewWidget creates a new widget +func NewWidget(app *tview.Application) *Widget { + widget := Widget{ + TextWidget: wtf.NewTextWidget(app, "VictorOps - OnCall", "victorops", true), + } + + widget.View.SetScrollable(true) + widget.View.SetRegions(true) + + return &widget +} + +// Refresh gets latest content for the widget +func (widget *Widget) Refresh() { + if widget.Disabled() { + return + } + + teams, err := Fetch() + widget.View.SetTitle(widget.ContextualTitle(widget.Name)) + + if err != nil { + widget.View.SetWrap(true) + widget.View.SetText(err.Error()) + } else { + widget.teams = teams + } + + widget.display() +} + +func (widget *Widget) display() { + if widget.teams == nil { + return + } + + widget.View.SetWrap(false) + widget.View.Clear() + widget.View.SetText(widget.contentFrom(widget.teams)) +} + +func (widget *Widget) contentFrom(teams []OnCallTeam) string { + teamToDisplay := wtf.Config.UString("wtf.mods.victorops.team") + var str string + for _, team := range teams { + if len(teamToDisplay) > 0 && teamToDisplay != team.Slug { + continue + } + + str = fmt.Sprintf("%s[green]%s\n", str, team.Name) + if len(team.OnCall) == 0 { + str = fmt.Sprintf("%s[grey]no one\n", str) + } + for _, onCall := range team.OnCall { + str = fmt.Sprintf("%s[white]%s - %s\n", str, onCall.Policy, onCall.Userlist) + } + + str = fmt.Sprintf("%s\n", str) + } + + if len(str) == 0 { + str = "Could not find any teams to display" + } + return str +}