jirachat

package module
v0.0.0-...-c355a56 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 28, 2018 License: MIT Imports: 11 Imported by: 0

README

jirachat

Golang JIRA Webhook handler that forwards to Hipchat and/or Slack

This work is mostly adapted from https://github.com/tbruyelle/hipchat-go for the Hipchat portions

The Slack integration is adapted from https://github.com/daikikohara/enotify-slack

There are a ton of things that could be done to improve this so feel free to contribute.

If you are using this and I break something, feel free to yell :).

How to work with the Slack service

// Slack Sample Handler
func SendSlack(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)

	svc := jirachat.NewSlackService(r,
		&jirachat.SlackConfig{
			ErrChan:    <YOU_ERROR_CHANNEL>,
			BotName:    <BOT_NAME>,
			WebhookUrl: <SLACK_WEBHOOK_URL>,
		})

	// Parse our event, baby! JIRA event can be touchy,
	// don't consider parse errors fatal
	event, err := jirachat.Parse(r)
	if err != nil {
		c.Errorf("Error parsing JIRA event %v", err)
		//w.WriteHeader(http.StatusInternalServerError)
		//return
	}

	mySvc := &MySlacker{config: svc.Config}
	err = mySvc.IssueCreated(&event)

	if err != nil {
		c.Errorf("Slack Error %v", err)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	// All went well!
	w.WriteHeader(http.StatusOK)
}

type MySlacker struct {
	config *jirachat.SlackConfig
}

func (s *MySlacker) IssueCreated(event *jirachat.JIRAWebevent) error {
	payload := jirachat.SlackMessage{}
	fields := []jirachat.Field{
		jirachat.Field{
			Title: "Summary",
			Value: event.Issue.Fields.Summary,
			Short: false,
		},
	}
	title := fmt.Sprintf("%s created %s", event.GetUserLink(s.config),
		event.GetIssueLink(s.config))
	attachment := jirachat.Attachment{
		Fallback: title,
		Pretext:  title,
		Color:    getPriorityColor(event.Issue.Fields.Priority.Id),
		Fields:   fields,
	}

	payload.Channel = s.config.Channel
	payload.Username = s.config.BotName
	payload.Icon_url = event.User.LargeAvatar()
	payload.Unfurl_links = true
	payload.Text = ""
	payload.Attachments = []jirachat.Attachment{attachment}
	return payload.SendEvent(s.config)
}
func getPriorityColor(id string) string {

	switch {
	case id == "1": 		// Blocker
		return "#990000"
	case id == "2":
		return "#cc0000" 	// Critical
	case id == "3":
		return "#ff0000"
	case id == "6": 		// Normal
		return "#339933"
	case id == "4": 		// Minor
		return "#006600"
	case id == "5": 		// Trivial
		return "#003300"
	case id == "10000": 	// Holding
		return "#000000"
	default:
		return jriachat.ColorGood
	}
}

How to work with the Hipchat service

// Sample Hipchat Handler
func SendHipchat(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)

	client, err := jirachat.NewHipService(r,
		&jirachat.HipConfig{Token: <YOUR_API_TOKEN>})

	if err != nil {
		c.Errorf("Failed to create HipService: %v", err)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	var req jirachat.NotificationRequest
	var sendIt bool

	// Parse our event, baby! JIRA event can be touchy,
	// don't consider parse errors fatal
	event, err := jirachat.Parse(r)
	if err != nil {
		c.Errorf("Error parsing JIRA event %v", err)
		//w.WriteHeader(http.StatusInternalServerError)
		//return
	}

	switch event.WebhookEvent {
	case "jira:issue_created":
		req = jirachat.NotificationRequest{
			Message: getUserAvatar(event.User) + " " +
				getUserLink(event.User) +
				"<i><strong> created </i> &#8594</strong> " +
				getLink(event.Issue),
			Color:         jirachat.ColorGreen,
			MessageFormat: jirachat.FormatHTML,
			Notify:        true,
		}
	}
	sendIt = true
	if sendIt {
		if resp, err := client.Notification(<ROOM_ID>, &req); err != nil {
			c.Errorf(err.Error())
			fmt.Printf("Error during room notification %q\n", err)
			fmt.Printf("Server returns %+v\n", resp)
			w.WriteHeader(http.StatusInternalServerError)
			return
		}

		// Everything went well!
		w.WriteHeader(http.StatusOK)
	}
}

Documentation

Overview

Package hipchat provides a client for using the HipChat API v2.

Index

Examples

Constants

View Source
const (
	ColorYellow = "yellow"
	ColorRed    = "red"
	ColorGreen  = "green"
	ColorPurple = "purple"
	ColorGray   = "gray"
	ColorRandom = "random"
	FormatText  = "text"
	FormatHTML  = "html"
)
View Source
const (
	PARSE_FULL = "full"
	ColorGood  = "good"
)

Variables

View Source
var ErrSlackParse = errors.New("unknown Event Failed Slack Parsing")

Functions

func NewHipService

func NewHipService(r *http.Request, config *HipConfig) (*hipService, error)

NewClient returns a new HipChat API client

func SendErrorNotice

func SendErrorNotice(msg string, config *SlackConfig)

ConstructSlackError constructs an error message sent to Slack.

Types

type Attachment

type Attachment struct {

	// text summary of the attachment that is shown by clients that understand
	// attachments but choose not to show them.
	// Required
	Fallback string `json:"fallback"`

	// text that should appear within the attachment
	// Optional
	Text string `json:"text"`

	// text that should appear above the formatted data",
	// Optional
	Pretext string `json:"pretext"`

	// Can either be one of 'good', 'warning', 'danger', or any hex color code
	// Optional
	Color string `json:"color"`

	// Fields are displayed in a table on the message
	Fields []Field `json:"fields"`
}

Attachment is an attachment to Payload. The format is defined in Slack Api document. See - https://api.slack.com/docs/attachments

type ChangleLogItems

type ChangleLogItems struct {
	ToString   string `json:"toString"`
	To         string `json:"to"`
	FromString string `json:"fromString"`
	From       string `json:"from"`
	FieldType  string `json:"fieldtype"`
	Field      string `json:"field"`
}

type Field

type Field struct {
	// The title may not contain markup and will be escaped for you
	// Required
	Title string `json:"title"`

	// Text value of the field. May contain standard message markup and
	// must be escaped as normal. May be multi-line.",
	Value string `json:"value"`

	// flag indicating whether the `value` is short enough to be
	// displayed side-by-side with other values
	// Optional
	Short bool `json:"short"`
}

Field is a field to Attachment. Like Attachment, the format is defined in Slack Api document. see - https://api.slack.com/docs/attachments

type HipConfig

type HipConfig struct {
	// Hipchat access token
	Token string
	// contains filtered or unexported fields
}

Config manages service resources

func (*HipConfig) IsValid

func (c *HipConfig) IsValid() error

Returns true if the configuration appears valid

type InnerComment

type InnerComment struct {
	StartAt    int           `json:"startAt"`
	MaxResults int           `json:"maxResults"`
	Total      int           `json:"total"`
	Comments   []JIRAComment `json:"comments"`
}

type IssueFieldData

type IssueFieldData struct {
	Summary     string            `json:"summary"`
	Created     string            `json:"created"`
	Description string            `json:"description"`
	Priority    JIRAIssuePriority `json:"priority"`
	Assignee    JIRAIssueAssignee `json:"assignee"`
	Labels      []string          `json:"labels"`
	Status      JIRAIssueStatus   `json:"status"`
	Comment     InnerComment      `json:"comment"`
	IssueType   JIRAIssueType     `json:"issuetype"`
	Project     JIRAProject       `json:"project"`
	// CustomFields is a map of customfield_xxx from your JIRA instance. The key will match whichever
	// custom fields you have created. The contents obviously depend on what you have created. The value
	// the raw string value of whatever your field contains.
	CustomFields map[string]string
}

type JIRAChangelog

type JIRAChangelog struct {
	Items []ChangleLogItems `json:"items,omitempty"`
	Id    string            `json:"id,omitempty"`
}

Some of the ChangeLogItems may through unmarshal errors but they don't seem to cause any major issues

type JIRAComment

type JIRAComment struct {
	Self         string   `json:"self"`
	Id           string   `json:"id"`
	Author       JIRAUser `json:"author"`
	Body         string   `json:"body"`
	UpdateAuthor JIRAUser `json:"updateAuthor"`
	Created      string   `json:"created"`
	Updated      string   `json:"updated"`
}

Describes the JIRAComment object defined in the JIRA 5.1 REST docs

https://docs.atlassian.com/jira/REST/5.1/#id204337

func (e *JIRAComment) GetUserLink(s *SlackConfig) string

Returns a markdown formatted user link with the user name as the link text

type JIRAIssue

type JIRAIssue struct {
	Expand string `json:"expand"`

	// Internal ID of the issue
	Id string `json:"id"`

	Self string `json:"self"`

	// This is the common issue key, e.g. JIRA-100
	Key string `json:"key"`

	// There are quite a few fields and that ones provided in this library
	// are nowhere near exhaustive.
	Fields IssueFieldData `json:"fields"`
}

Decribes the JIRAIssue object defined in the JIRA 5.1 REST docs

https://docs.atlassian.com/jira/REST/5.1/#id204637

type JIRAIssueAssignee

type JIRAIssueAssignee struct {
	Self        string            `json:"self"`
	Name        string            `json:"name"`
	Key         string            `json:"key"`
	Email       string            `json:"emailAddress"`
	AvatarUrls  map[string]string `json:"avatarUrls"`
	DisplayName string            `json:"displayName"`
	Active      bool              `json:"active"`
	Timezone    string            `json:"timeZone"`
}

type JIRAIssuePriority

type JIRAIssuePriority struct {
	Name string `json:"name"`
	Id   string `json:"id"`
}

type JIRAIssueStatus

type JIRAIssueStatus struct {
	Name string `json:"name"`
}

type JIRAIssueType

type JIRAIssueType struct {
	Self        string `json:"self"`
	Id          string `json:"id"`
	Description string `json:"description"`
	IconURL     string `json:"iconUrl"`
	Name        string `json:"name"`
	// contains filtered or unexported fields
}

type JIRAProject

type JIRAProject struct {
	Self       string            `json:"self"`
	Id         string            `json:"id"`
	Key        string            `json:"key"`
	Name       string            `json:"name"`
	IconUrl    string            `json:"iconUrl"`
	Subtask    bool              `json:"subtask"`
	AvatarUrls map[string]string `json:"avatarUrls"`
}

type JIRAUser

type JIRAUser struct {
	Self string `json:"self"`

	// The user's system name, e.g. mmcfly
	Name         string            `json:"name"`
	EmailAddress string            `json:"emailAddress"`
	AvatarUrls   map[string]string `json:"avatarUrls"`

	// Pretty name E.g. Mart McFly
	DisplayName string `json:"displayName"`
	Active      bool   `json:"active"`
}

Describes the JIRAUser object defined in the JIRA 5.1 REST docs

https://docs.atlassian.com/jira/REST/5.1/#id202197

func (*JIRAUser) LargeAvatar

func (j *JIRAUser) LargeAvatar() string

Returns the 48x48 user Avatar Note: If this is not a Gravatar, it will not render in Slack message because uploaded images are private to your JIRA instance.

func (*JIRAUser) SmallAvatar

func (j *JIRAUser) SmallAvatar() string

Returns the 16x16 user Avatar Note: If this is not a Gravatar, it will not render in Slack message because uploaded images are private to your JIRA instance.

type JIRAWebevent

type JIRAWebevent struct {
	// Internal ID of the event
	Id int `json:"id,omitempty"`

	// Unix timestamp
	Timestamp int `json:"timestamp,omitempty"`

	// Object describing issue event relates to
	Issue JIRAIssue `json:"issue"`

	// User who triggered event
	User JIRAUser `json:"user"`

	// An array of change items, with one entry for each field that has
	// been changed. The changelog is only provided for the issue_updated event.
	Changelog JIRAChangelog `json:"changelog"`

	// Set if this event is a jira_updated event and a comment was made
	Comment JIRAComment `json:"comment"`

	// The type of event
	WebhookEvent string `json:"webhookEvent"`
}

This is a json response for a JIRA webhook (more or less) according to https://developer.atlassian.com/jiradev/jira-architecture/webhooks

func Parse

func Parse(r *http.Request) (JIRAWebevent, error)

Parse the request body as a JIRA webhook event Returns a new JiraWebEvent object or error

func (e *JIRAWebevent) GetIssueLink(s *SlackConfig) string

Returns a markdown formatted issue link with the issue key as the link text

func (*JIRAWebevent) GetPriorityColor

func (e *JIRAWebevent) GetPriorityColor() string

Convert priority id to hex color string

func (e *JIRAWebevent) GetUserLink(s *SlackConfig) string

Returns a markdown formatted user link with the user name as the link text

type NotificationRequest

type NotificationRequest struct {
	// Background color for message.
	// Valid values: yellow, green, red, purple, gray, random.
	// Defaults to 'yellow'.
	Color string `json:"color,omitempty"`

	// The message body
	// Valid length range: 1 - 10000.
	Message string `json:"message,omitempty"`

	// Whether this message should trigger a user notification (change the tab
	// color, play a sound, notify mobile phones, etc). Each recipient's
	// notification preferences are taken into account.
	Notify bool `json:"notify,omitempty"`

	// Determines how the message is treated by our server and rendered inside
	// HipChat applications
	MessageFormat string `json:"message_format,omitempty"`
}

NotificationRequest represents a HipChat room notification request.

type Response

type Response map[string]interface{}

Convenience interface for printing anonymous JSON objects

Example
package main

import (
	"encoding/json"
	"fmt"
	"net/http"

	"github.com/corytodd/jirachat"
)

func main(r *http.Request) {
	config := HipConfig{
		Token: "<YOUR-ACCESS-TOKEN>",
	}
	svc, err := NewHipService(r, &config)
}

func main(r *http.Request) {
	svc := jirachat.NewSlackService(r,
		&jirachat.SlackConfig{
			ErrChan:    "https://hooks.slack.com/services/<random_webhook_url>",
			BotName:    "Animus",
			WebhookUrl: "https://hooks.slack.com/services/<random_webhook_url>",
			Domain:     "example", // Your JIRA domain, e.g. example.atlassian.net
		})
}

func main() {

	resp := &Response{
		"string": "Hello",
		"int":    42,
	}
	d, _ := json.Marshal(resp)
	fmt.Print(string(d))
}
Output:

{"int":42,"string":"Hello"}

func (Response) String

func (r Response) String() (s string)

type SlackConfig

type SlackConfig struct {
	// Optional channel to post error reports to
	ErrChan string

	// Receiver channel for JIRA events
	Channel string

	// Bot name reported to Slack
	BotName string

	// Simple Slack Webhook URI
	WebhookUrl string

	// JIRA domain name
	Domain string
	// contains filtered or unexported fields
}

Configuration used by SlackService to communicate with your Slack instance.

type SlackMessage

type SlackMessage struct {
	Channel      string       `json:"channel"`
	Username     string       `json:"username"`
	Text         string       `json:"text"`
	Icon_emoji   string       `json:"icon_emoji"`
	Icon_url     string       `json:"icon_url"`
	Unfurl_links bool         `json:"unfurl_links"`
	Attachments  []Attachment `json:"attachments"`
}

SlackMessage represents a payload sent to Slack. The values are sent to Slack via incoming-webhook. See - https://my.slack.com/services/new/incoming-webhook

func (*SlackMessage) SendEvent

func (p *SlackMessage) SendEvent(config *SlackConfig) error

SendEvent sends SlackMessage which contains JIRA data to Slack.

type SlackService

type SlackService struct {
	Config *SlackConfig
}

SlackService handles HTTP communication with Slack Chat

func NewSlackService

func NewSlackService(r *http.Request, config *SlackConfig) *SlackService

Create a new slack service with the given config. A Slack service provides default JIRAWebEvent parser and notification functions.

func (*SlackService) CommentCreated

func (s *SlackService) CommentCreated(event *JIRAWebevent) error

func (*SlackService) IssueCreated

func (s *SlackService) IssueCreated(event *JIRAWebevent) error

Default construct SlackMessage for issue_created type

func (*SlackService) IssueDeleted

func (s *SlackService) IssueDeleted(event *JIRAWebevent) error

Default construct SlackMessage for issue_deleted type

func (*SlackService) IssueUpdated

func (s *SlackService) IssueUpdated(event *JIRAWebevent) error

Default constructSlackMessage for issue_updated type. Unfortunately this includes everything that isn't worklog or ticket create/delete

func (*SlackService) WorklogUpdated

func (s *SlackService) WorklogUpdated(event *JIRAWebevent) error

Default construct SlackMessage for issue_deleted type

type Slacker

type Slacker interface {
	IssueCreated(*JIRAWebevent) error
	IssueDeleted(JIRAWebevent) error
	IssueUpdated(JIRAWebevent) error
	WorklogUpdated(JIRAWebevent) error
	CommentCreated(JIRAWebevent) error
	SendErrorNotice(string, *SlackConfig)
}

Slacker is the interface implmented by types that can parse their own webevents.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL