mirror of
https://github.com/taigrr/wtf
synced 2025-01-18 04:03:14 -08:00
74
modules/zendesk/client.go
Normal file
74
modules/zendesk/client.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package zendesk
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/wtfutil/wtf/wtf"
|
||||
)
|
||||
|
||||
type Resource struct {
|
||||
Response interface{}
|
||||
Raw string
|
||||
}
|
||||
|
||||
func apiKey() string {
|
||||
return wtf.Config.UString(
|
||||
"wtf.mods.zendesk.apiKey",
|
||||
os.Getenv("ZENDESK_API"),
|
||||
)
|
||||
}
|
||||
|
||||
func subdomain() string {
|
||||
return wtf.Config.UString(
|
||||
"wtf.mods.zendesk.subdomain",
|
||||
os.Getenv("ZENDESK_SUBDOMAIN"),
|
||||
)
|
||||
}
|
||||
|
||||
func errHandler(err error) {
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
}
|
||||
}
|
||||
|
||||
func api(key string, meth string, path string, params string) (*Resource, error) {
|
||||
trn := &http.Transport{}
|
||||
|
||||
client := &http.Client{
|
||||
Transport: trn,
|
||||
}
|
||||
|
||||
baseURL := fmt.Sprintf("https://%v.zendesk.com/api/v2", subdomain())
|
||||
URL := baseURL + "/tickets.json?sort_by=status"
|
||||
|
||||
req, err := http.NewRequest(meth, URL, bytes.NewBufferString(params))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
|
||||
username := wtf.Config.UString("wtf.mods.zendesk.username")
|
||||
apiUser := fmt.Sprintf("%v/token", username)
|
||||
req.SetBasicAuth(apiUser, key)
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Resource{Response: &resp, Raw: string(data)}, nil
|
||||
|
||||
}
|
||||
82
modules/zendesk/tickets.go
Normal file
82
modules/zendesk/tickets.go
Normal file
@@ -0,0 +1,82 @@
|
||||
package zendesk
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
)
|
||||
|
||||
type TicketArray struct {
|
||||
Count int `json:"count"`
|
||||
Created string `json:"created"`
|
||||
Next_page string `json:"next_page"`
|
||||
Previous_page string `json:"previous_page"`
|
||||
Tickets []Ticket
|
||||
}
|
||||
|
||||
type Ticket struct {
|
||||
Id uint64 `json:"id"`
|
||||
URL string `json:"url"`
|
||||
ExternalId string `json:"external_id"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
Type string `json:"type"`
|
||||
Subject string `json:"subject"`
|
||||
RawSubject string `json:"raw_subject"`
|
||||
Description string `json:"description"`
|
||||
Priority string `json:"priority"`
|
||||
Status string `json:"status"`
|
||||
Recipient string `json:"recipient"`
|
||||
RequesterId uint32 `json:"requester_id"`
|
||||
SubmitterId uint32 `json:"submitter_id"`
|
||||
AssigneeId uint32 `json:"assignee_id"`
|
||||
OrganizationId uint32 `json:"organization_id"`
|
||||
GroupId uint32 `json:"group_id"`
|
||||
CollaboratorIds []int32 `json:"collaborator_ids"`
|
||||
ForumTopicId uint32 `json:"forum_topic_id"`
|
||||
ProblemId uint32 `json:"problem_id"`
|
||||
HasIncidents bool `json:"has_incidents"`
|
||||
DueAt string `json:"due_at"`
|
||||
Tags []string `json:"tags"`
|
||||
Satisfaction_rating string `json:"satisfaction_rating"`
|
||||
Ticket_form_id uint32 `json:"ticket_form_id"`
|
||||
Sharing_agreement_ids interface{} `json:"sharing_agreement_ids"`
|
||||
Via interface{} `json:"via"`
|
||||
Custom_Fields interface{} `json:"custom_fields"`
|
||||
Fields interface{} `json:"fields"`
|
||||
}
|
||||
|
||||
func listTickets(pag ...string) (*TicketArray, error) {
|
||||
|
||||
TicketStruct := &TicketArray{}
|
||||
|
||||
var path string
|
||||
if len(pag) < 1 {
|
||||
path = "/tickets.json"
|
||||
} else {
|
||||
path = pag[0]
|
||||
}
|
||||
resource, err := api(apiKey(), "GET", path, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
json.Unmarshal([]byte(resource.Raw), TicketStruct)
|
||||
|
||||
return TicketStruct, err
|
||||
|
||||
}
|
||||
|
||||
func newTickets(ticketStatus string) (*TicketArray, error) {
|
||||
newTicketArray := &TicketArray{}
|
||||
tickets, err := listTickets()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
for _, Ticket := range tickets.Tickets {
|
||||
if Ticket.Status == ticketStatus && Ticket.Status != "closed" && Ticket.Status != "solved" {
|
||||
newTicketArray.Tickets = append(newTicketArray.Tickets, Ticket)
|
||||
}
|
||||
}
|
||||
|
||||
return newTicketArray, nil
|
||||
}
|
||||
150
modules/zendesk/widget.go
Normal file
150
modules/zendesk/widget.go
Normal file
@@ -0,0 +1,150 @@
|
||||
package zendesk
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/gdamore/tcell"
|
||||
"github.com/rivo/tview"
|
||||
"github.com/wtfutil/wtf/wtf"
|
||||
)
|
||||
|
||||
type Widget struct {
|
||||
wtf.TextWidget
|
||||
|
||||
result *TicketArray
|
||||
selected int
|
||||
}
|
||||
|
||||
func NewWidget(app *tview.Application) *Widget {
|
||||
widget := Widget{
|
||||
TextWidget: wtf.NewTextWidget(app, "Zendesk", "zendesk", true),
|
||||
}
|
||||
|
||||
widget.View.SetInputCapture(widget.keyboardIntercept)
|
||||
|
||||
return &widget
|
||||
}
|
||||
|
||||
/* -------------------- Exported Functions -------------------- */
|
||||
func (widget *Widget) Refresh() {
|
||||
ticketStatus := wtf.Config.UString("wtf.mods.zendesk.status")
|
||||
ticketArray, err := newTickets(ticketStatus)
|
||||
ticketArray.Count = len(ticketArray.Tickets)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
} else {
|
||||
widget.result = ticketArray
|
||||
}
|
||||
|
||||
widget.display()
|
||||
}
|
||||
|
||||
/* -------------------- Unexported Functions -------------------- */
|
||||
|
||||
func (widget *Widget) display() {
|
||||
widget.View.SetTitle(fmt.Sprintf("%s (%d)", widget.Name, widget.result.Count))
|
||||
widget.View.SetText(widget.textContent(widget.result.Tickets))
|
||||
}
|
||||
|
||||
func (widget *Widget) textContent(items []Ticket) string {
|
||||
if len(items) == 0 {
|
||||
return fmt.Sprintf("No unassigned tickets in queue - woop!!")
|
||||
}
|
||||
|
||||
str := ""
|
||||
for idx, data := range items {
|
||||
str = str + widget.format(data, idx)
|
||||
}
|
||||
|
||||
return str
|
||||
}
|
||||
|
||||
func (widget *Widget) format(ticket Ticket, idx int) string {
|
||||
var str string
|
||||
requesterName := widget.parseRequester(ticket)
|
||||
textColor := wtf.Config.UString("wtf.colors.background", "green")
|
||||
if idx == widget.selected {
|
||||
textColor = wtf.Config.UString("wtf.colors.background", "orange")
|
||||
}
|
||||
str = fmt.Sprintf(" [%s:]%d - %s\n %s\n\n", textColor, ticket.Id, requesterName, ticket.Subject)
|
||||
return str
|
||||
}
|
||||
|
||||
// this is a nasty means of extracting the actual name of the requester from the Via interface of the Ticket.
|
||||
// very very open to improvements on this
|
||||
func (widget *Widget) parseRequester(ticket Ticket) interface{} {
|
||||
viaMap := ticket.Via
|
||||
via := viaMap.(map[string]interface{})
|
||||
source := via["source"]
|
||||
fromMap, _ := source.(map[string]interface{})
|
||||
from := fromMap["from"]
|
||||
fromValMap := from.(map[string]interface{})
|
||||
fromName := fromValMap["name"]
|
||||
return fromName
|
||||
}
|
||||
|
||||
func (widget *Widget) next() {
|
||||
widget.selected++
|
||||
if widget.result != nil && widget.selected >= len(widget.result.Tickets) {
|
||||
widget.selected = 0
|
||||
}
|
||||
}
|
||||
|
||||
func (widget *Widget) prev() {
|
||||
widget.selected--
|
||||
if widget.selected < 0 && widget.result != nil {
|
||||
widget.selected = len(widget.result.Tickets) - 1
|
||||
}
|
||||
}
|
||||
|
||||
func (widget *Widget) openTicket() {
|
||||
sel := widget.selected
|
||||
if sel >= 0 && widget.result != nil && sel < len(widget.result.Tickets) {
|
||||
issue := &widget.result.Tickets[widget.selected]
|
||||
ticketUrl := fmt.Sprintf("https://%s.zendesk.com/agent/tickets/%d", subdomain(), issue.Id)
|
||||
wtf.OpenFile(ticketUrl)
|
||||
}
|
||||
}
|
||||
|
||||
func (widget *Widget) unselect() {
|
||||
widget.selected = -1
|
||||
}
|
||||
|
||||
func (widget *Widget) keyboardIntercept(event *tcell.EventKey) *tcell.EventKey {
|
||||
switch string(event.Rune()) {
|
||||
case "j":
|
||||
// Select the next item down
|
||||
widget.next()
|
||||
widget.display()
|
||||
return nil
|
||||
case "k":
|
||||
// Select the next item up
|
||||
widget.prev()
|
||||
widget.display()
|
||||
return nil
|
||||
}
|
||||
switch event.Key() {
|
||||
case tcell.KeyDown:
|
||||
// Select the next item down
|
||||
widget.next()
|
||||
widget.display()
|
||||
return nil
|
||||
case tcell.KeyUp:
|
||||
// Select the next item up
|
||||
widget.prev()
|
||||
widget.display()
|
||||
return nil
|
||||
case tcell.KeyEnter:
|
||||
widget.openTicket()
|
||||
return nil
|
||||
case tcell.KeyEsc:
|
||||
// Unselect the current row
|
||||
widget.unselect()
|
||||
widget.display()
|
||||
return event
|
||||
default:
|
||||
// Pass it along
|
||||
return event
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user