mirror of https://github.com/matrix-org/go-neb.git
Browse Source
Merge pull request #20 from matrix-org/kegan/jira-commands
Merge pull request #20 from matrix-org/kegan/jira-commands
Implement processing of !jira create commandskegan/create-with-json
Kegsay
8 years ago
committed by
GitHub
6 changed files with 320 additions and 31 deletions
-
11src/github.com/matrix-org/go-neb/database/db.go
-
29src/github.com/matrix-org/go-neb/database/schema.go
-
1src/github.com/matrix-org/go-neb/goneb.go
-
5src/github.com/matrix-org/go-neb/plugin/plugin.go
-
124src/github.com/matrix-org/go-neb/realms/jira/jira.go
-
181src/github.com/matrix-org/go-neb/services/jira/jira.go
@ -0,0 +1,181 @@ |
|||||
|
package services |
||||
|
|
||||
|
import ( |
||||
|
"database/sql" |
||||
|
"errors" |
||||
|
"fmt" |
||||
|
log "github.com/Sirupsen/logrus" |
||||
|
"github.com/andygrunwald/go-jira" |
||||
|
"github.com/matrix-org/go-neb/database" |
||||
|
"github.com/matrix-org/go-neb/matrix" |
||||
|
"github.com/matrix-org/go-neb/plugin" |
||||
|
"github.com/matrix-org/go-neb/realms/jira" |
||||
|
"github.com/matrix-org/go-neb/types" |
||||
|
"net/http" |
||||
|
"regexp" |
||||
|
"strings" |
||||
|
) |
||||
|
|
||||
|
// Matches alphas then a -, then a number. E.g "FOO-123"
|
||||
|
var issueKeyRegex = regexp.MustCompile("([A-z]+)-([0-9]+)") |
||||
|
var projectKeyRegex = regexp.MustCompile("^[A-z]+$") |
||||
|
|
||||
|
type jiraService struct { |
||||
|
id string |
||||
|
UserID string |
||||
|
Rooms []string |
||||
|
} |
||||
|
|
||||
|
func (s *jiraService) ServiceUserID() string { return s.UserID } |
||||
|
func (s *jiraService) ServiceID() string { return s.id } |
||||
|
func (s *jiraService) ServiceType() string { return "jira" } |
||||
|
func (s *jiraService) RoomIDs() []string { return s.Rooms } |
||||
|
func (s *jiraService) Register() error { return nil } |
||||
|
func (s *jiraService) PostRegister(old types.Service) {} |
||||
|
func (s *jiraService) Plugin(roomID string) plugin.Plugin { |
||||
|
return plugin.Plugin{ |
||||
|
Commands: []plugin.Command{ |
||||
|
plugin.Command{ |
||||
|
Path: []string{"jira", "create"}, |
||||
|
Command: func(roomID, userID string, args []string) (interface{}, error) { |
||||
|
// E.g jira create PROJ "Issue title" "Issue desc"
|
||||
|
if len(args) <= 1 { |
||||
|
return nil, errors.New("Missing project key (e.g 'ABC') and/or title") |
||||
|
} |
||||
|
|
||||
|
if !projectKeyRegex.MatchString(args[0]) { |
||||
|
return nil, errors.New("Project key must only contain A-Z.") |
||||
|
} |
||||
|
|
||||
|
pkey := strings.ToUpper(args[0]) // REST API complains if they are not ALL CAPS
|
||||
|
|
||||
|
title := args[1] |
||||
|
desc := "" |
||||
|
if len(args) == 3 { |
||||
|
desc = args[2] |
||||
|
} else if len(args) > 3 { // > 3 args is probably a title without quote marks
|
||||
|
joinedTitle := strings.Join(args[1:], " ") |
||||
|
title = joinedTitle |
||||
|
} |
||||
|
|
||||
|
r, err := s.projectToRealm(userID, pkey) |
||||
|
if err != nil { |
||||
|
log.WithError(err).Print("Failed to map project key to realm") |
||||
|
return nil, errors.New("Failed to map project key to a JIRA endpoint.") |
||||
|
} |
||||
|
if r == nil { |
||||
|
return nil, errors.New("No known project exists with that project key.") |
||||
|
} |
||||
|
|
||||
|
iss := jira.Issue{ |
||||
|
Fields: &jira.IssueFields{ |
||||
|
Summary: title, |
||||
|
Description: desc, |
||||
|
Project: jira.Project{ |
||||
|
Key: pkey, |
||||
|
}, |
||||
|
// FIXME: This may vary depending on the JIRA install!
|
||||
|
Type: jira.IssueType{ |
||||
|
Name: "Bug", |
||||
|
}, |
||||
|
}, |
||||
|
} |
||||
|
cli, err := r.JIRAClient(userID, false) |
||||
|
if err != nil { |
||||
|
return nil, err |
||||
|
} |
||||
|
i, res, err := cli.Issue.Create(&iss) |
||||
|
if err != nil { |
||||
|
log.WithFields(log.Fields{ |
||||
|
log.ErrorKey: err, |
||||
|
"user_id": userID, |
||||
|
"project": pkey, |
||||
|
"realm_id": r.ID(), |
||||
|
}).Print("Failed to create issue") |
||||
|
return nil, errors.New("Failed to create issue") |
||||
|
} |
||||
|
if res.StatusCode < 200 || res.StatusCode >= 300 { |
||||
|
return nil, fmt.Errorf("Failed to create issue: JIRA returned %d", res.StatusCode) |
||||
|
} |
||||
|
|
||||
|
return &matrix.TextMessage{ |
||||
|
"m.notice", |
||||
|
fmt.Sprintf("Created issue: %s", i.Key), |
||||
|
}, nil |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
} |
||||
|
} |
||||
|
func (s *jiraService) OnReceiveWebhook(w http.ResponseWriter, req *http.Request, cli *matrix.Client) { |
||||
|
w.WriteHeader(200) // Do nothing
|
||||
|
} |
||||
|
|
||||
|
func (s *jiraService) projectToRealm(userID, pkey string) (*realms.JIRARealm, error) { |
||||
|
// We don't know which JIRA installation this project maps to, so:
|
||||
|
// - Get all known JIRA realms and f.e query their endpoints with the
|
||||
|
// given user ID's credentials (so if it is a private project they
|
||||
|
// can see it will succeed.)
|
||||
|
// - If there is a matching project with that key, return that realm.
|
||||
|
// We search installations which the user has already OAuthed with first as most likely
|
||||
|
// the project key will be on a JIRA they have access to.
|
||||
|
// TODO: Return whether they have authed or not so they know if they need to make a starter link
|
||||
|
logger := log.WithFields(log.Fields{ |
||||
|
"user_id": userID, |
||||
|
"project": pkey, |
||||
|
}) |
||||
|
knownRealms, err := database.GetServiceDB().LoadAuthRealmsByType("jira") |
||||
|
if err != nil { |
||||
|
logger.WithError(err).Print("Failed to load jira auth realms") |
||||
|
return nil, err |
||||
|
} |
||||
|
// typecast and move ones which the user has authed with to the front of the queue
|
||||
|
var queue []*realms.JIRARealm |
||||
|
var unauthRealms []*realms.JIRARealm |
||||
|
for _, r := range knownRealms { |
||||
|
jrealm, ok := r.(*realms.JIRARealm) |
||||
|
if !ok { |
||||
|
logger.WithField("realm_id", r.ID()).Print( |
||||
|
"Failed to type-cast 'jira' type realm into JIRARealm", |
||||
|
) |
||||
|
continue |
||||
|
} |
||||
|
|
||||
|
_, err := database.GetServiceDB().LoadAuthSessionByUser(r.ID(), userID) |
||||
|
if err != nil { |
||||
|
if err == sql.ErrNoRows { |
||||
|
unauthRealms = append(unauthRealms, jrealm) |
||||
|
} else { |
||||
|
logger.WithError(err).WithField("realm_id", r.ID()).Print( |
||||
|
"Failed to load auth sessions for user", |
||||
|
) |
||||
|
} |
||||
|
continue // this may not have been the match anyway so don't give up!
|
||||
|
} |
||||
|
queue = append(queue, jrealm) |
||||
|
} |
||||
|
|
||||
|
// push unauthed realms to the back
|
||||
|
queue = append(queue, unauthRealms...) |
||||
|
|
||||
|
for _, jr := range queue { |
||||
|
exists, err := jr.ProjectKeyExists(userID, pkey) |
||||
|
if err != nil { |
||||
|
logger.WithError(err).WithField("realm_id", jr.ID()).Print( |
||||
|
"Failed to check if project key exists on this realm.", |
||||
|
) |
||||
|
continue // may not have been found anyway so keep searching!
|
||||
|
} |
||||
|
if exists { |
||||
|
logger.Info("Project exists on ", jr.ID()) |
||||
|
return jr, nil |
||||
|
} |
||||
|
} |
||||
|
return nil, nil |
||||
|
} |
||||
|
|
||||
|
func init() { |
||||
|
types.RegisterService(func(serviceID, webhookEndpointURL string) types.Service { |
||||
|
return &jiraService{id: serviceID} |
||||
|
}) |
||||
|
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue