From 8a7fc815809a216bd72d5dbb432bc09738f36e83 Mon Sep 17 00:00:00 2001 From: Andrew Scibek Date: Wed, 13 Feb 2019 23:53:01 -0800 Subject: [PATCH] Initial Rollbar support --- main.go | 3 + rollbar/client.go | 80 ++++++++++++++++++ rollbar/rollbar.go | 17 ++++ rollbar/widget.go | 203 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 303 insertions(+) create mode 100644 rollbar/client.go create mode 100644 rollbar/rollbar.go create mode 100644 rollbar/widget.go diff --git a/main.go b/main.go index 3dcc2612..0755e522 100644 --- a/main.go +++ b/main.go @@ -46,6 +46,7 @@ import ( "github.com/wtfutil/wtf/pagerduty" "github.com/wtfutil/wtf/power" "github.com/wtfutil/wtf/resourceusage" + "github.com/wtfutil/wtf/rollbar" "github.com/wtfutil/wtf/security" "github.com/wtfutil/wtf/spotify" "github.com/wtfutil/wtf/spotifyweb" @@ -248,6 +249,8 @@ func addWidget(app *tview.Application, pages *tview.Pages, widgetName string) { widgets = append(widgets, todoist.NewWidget(app, pages)) case "travisci": widgets = append(widgets, travisci.NewWidget(app, pages)) + case "rollbar": + widgets = append(widgets, rollbar.NewWidget(app, pages)) case "trello": widgets = append(widgets, trello.NewWidget(app)) case "twitter": diff --git a/rollbar/client.go b/rollbar/client.go new file mode 100644 index 00000000..751f79f7 --- /dev/null +++ b/rollbar/client.go @@ -0,0 +1,80 @@ +package rollbar + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "net/http" + "net/url" + + "github.com/wtfutil/wtf/wtf" +) + +func CurrentActiveItems() (*ActiveItems, error) { + items := &ActiveItems{} + + accessToken := wtf.Config.UString("wtf.mods.rollbar.accessToken", "") + rollbarAPIURL.Host = "api.rollbar.com" + rollbarAPIURL.Path = "/api/1/items" + resp, err := rollbarItemRequest(accessToken) + if err != nil { + return items, err + } + + parseJSON(&items, resp.Body) + + return items, nil +} + +/* -------------------- Unexported Functions -------------------- */ + +var ( + rollbarAPIURL = &url.URL{Scheme: "https"} +) + +func rollbarItemRequest(accessToken string) (*http.Response, error) { + params := url.Values{} + params.Add("access_token", accessToken) + userName := wtf.Config.UString("wtf.mods.rollbar.assignedToName", "") + params.Add("assigned_user", userName) + active := wtf.Config.UBool("wtf.mods.rollbar.activeOnly", false) + if active { + params.Add("status", "active") + } + + requestURL := rollbarAPIURL.ResolveReference(&url.URL{RawQuery: params.Encode()}) + req, err := http.NewRequest("GET", requestURL.String(), nil) + req.Header.Add("Accept", "application/json") + req.Header.Add("Content-Type", "application/json") + + httpClient := &http.Client{} + resp, err := httpClient.Do(req) + if err != nil { + return nil, err + } + + if resp.StatusCode < 200 || resp.StatusCode > 299 { + return nil, fmt.Errorf(resp.Status) + } + + return resp, nil +} + +func parseJSON(obj interface{}, text io.Reader) { + jsonStream, err := ioutil.ReadAll(text) + if err != nil { + panic(err) + } + + decoder := json.NewDecoder(bytes.NewReader(jsonStream)) + + for { + if err := decoder.Decode(obj); err == io.EOF { + break + } else if err != nil { + panic(err) + } + } +} diff --git a/rollbar/rollbar.go b/rollbar/rollbar.go new file mode 100644 index 00000000..0499d543 --- /dev/null +++ b/rollbar/rollbar.go @@ -0,0 +1,17 @@ +package rollbar + +type ActiveItems struct { + Results Result `json:"result"` +} +type Item struct { + Environment string `json:"environment"` + Title string `json:"title"` + Platform string `json:"platform"` + Status string `json:"status"` + TotalOccurrences int `json:"total_occurrences"` + Level string `json:"level"` + ID int `json:"counter"` +} +type Result struct { + Items []Item `json:"items"` +} diff --git a/rollbar/widget.go b/rollbar/widget.go new file mode 100644 index 00000000..0e836408 --- /dev/null +++ b/rollbar/widget.go @@ -0,0 +1,203 @@ +package rollbar + +import ( + "fmt" + + "github.com/gdamore/tcell" + "github.com/rivo/tview" + "github.com/wtfutil/wtf/wtf" +) + +const HelpText = ` + Keyboard commands for Rollbar: + + /: Show/hide this help window + j: Select the next item in the list + k: Select the previous item in the list + r: Refresh the data + u: unselect the current item(removes item being perma highlighted) + + arrow down: Select the next item in the list + arrow up: Select the previous item in the list + + return: Open the selected item in a browser +` + +type Widget struct { + wtf.HelpfulWidget + wtf.TextWidget + + items *Result + selected int +} + +func NewWidget(app *tview.Application, pages *tview.Pages) *Widget { + widget := Widget{ + HelpfulWidget: wtf.NewHelpfulWidget(app, pages, HelpText), + TextWidget: wtf.NewTextWidget(app, "Rollbar", "rollbar", true), + } + widget.HelpfulWidget.SetView(widget.View) + widget.unselect() + + widget.View.SetInputCapture(widget.keyboardIntercept) + + return &widget +} + +/* -------------------- Exported Functions -------------------- */ + +func (widget *Widget) Refresh() { + if widget.Disabled() { + return + } + + items, err := CurrentActiveItems() + + if err != nil { + widget.View.SetWrap(true) + widget.View.SetTitle(widget.Name) + widget.View.SetText(err.Error()) + } else { + widget.items = &items.Results + } + + widget.display() +} + +/* -------------------- Unexported Functions -------------------- */ + +func (widget *Widget) display() { + if widget.items == nil { + return + } + + widget.View.SetWrap(false) + projectName := wtf.Config.UString("wtf.mods.rollbar.projectName", "Items") + widget.View.SetTitle(widget.ContextualTitle(fmt.Sprintf("%s - %s", widget.Name, projectName))) + widget.View.SetText(widget.contentFrom(widget.items)) +} + +func (widget *Widget) contentFrom(result *Result) string { + var str string + count := wtf.Config.UInt("wtf.mods.rollbar.count", 10) + if len(result.Items) > count { + result.Items = result.Items[:count] + } + for idx, item := range result.Items { + + str = str + fmt.Sprintf( + "[%s] [%s] %s [%s] %s [%s]count: %d [%s]%s\n", + widget.rowColor(idx), + levelColor(&item), + item.Level, + statusColor(&item), + item.Title, + widget.rowColor(idx), + item.TotalOccurrences, + "blue", + item.Environment, + ) + } + + return str +} + +func (widget *Widget) rowColor(idx int) string { + if widget.View.HasFocus() && (idx == widget.selected) { + return wtf.DefaultFocussedRowColor() + } + return "white" +} + +func statusColor(item *Item) string { + switch item.Status { + case "active": + return "red" + case "resolved": + return "green" + default: + return "red" + } +} +func levelColor(item *Item) string { + switch item.Level { + case "error": + return "red" + case "critical": + return "green" + case "warning": + return "yellow" + default: + return "grey" + } +} + +func (widget *Widget) next() { + widget.selected++ + if widget.items != nil && widget.selected >= len(widget.items.Items) { + widget.selected = 0 + } + + widget.display() +} + +func (widget *Widget) prev() { + widget.selected-- + if widget.selected < 0 && widget.items.Items != nil { + widget.selected = len(widget.items.Items) - 1 + } + + widget.display() +} + +func (widget *Widget) openBuild() { + sel := widget.selected + projectOwner := wtf.Config.UString("wtf.mods.rollbar.projectOwner", "") + projectName := wtf.Config.UString("wtf.mods.rollbar.projectName", "") + if sel >= 0 && widget.items != nil && sel < len(widget.items.Items) { + item := &widget.items.Items[widget.selected] + wtf.OpenFile(fmt.Sprintf("https://rollbar.com/%s/%s/%s/%d", projectOwner, projectName, "items", item.ID)) + } +} + +func (widget *Widget) unselect() { + widget.selected = -1 + widget.display() +} + +func (widget *Widget) keyboardIntercept(event *tcell.EventKey) *tcell.EventKey { + switch string(event.Rune()) { + case "/": + widget.ShowHelp() + case "j": + widget.next() + return nil + case "k": + widget.prev() + return nil + case "r": + widget.Refresh() + return nil + case "u": + widget.unselect() + return nil + } + + switch event.Key() { + case tcell.KeyDown: + widget.next() + return nil + case tcell.KeyEnter: + widget.openBuild() + return nil + case tcell.KeyEsc: + widget.unselect() + return event + case tcell.KeyUp: + widget.prev() + widget.display() + return nil + default: + return event + } +}