Browse Source

Merge pull request #358 from matrix-org/github-new_issue_labels

Add support for GitHub labels when creating a new issue
pull/365/head
Michael Telatynski 3 years ago
committed by GitHub
parent
commit
0260eacfb4
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      .github/workflows/ci.yaml
  2. 11
      clients/clients.go
  3. 1
      go.mod
  4. 2
      go.sum
  5. 71
      services/github/github.go
  6. 11
      types/service.go

2
.github/workflows/ci.yaml

@ -27,7 +27,7 @@ jobs:
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v2 uses: actions/setup-go@v2
with: with:
go-version: '~1.16.4'
go-version: '~1.18.0'
- name: Install libolm - name: Install libolm
run: sudo apt-get -y install libolm3 libolm-dev run: sudo apt-get -y install libolm3 libolm-dev
- name: Install linters - name: Install linters

11
clients/clients.go

@ -287,11 +287,12 @@ func (c *Clients) onBotOptionsEvent(client *mautrix.Client, event *mevt.Event) {
return return
} }
// these options fully clobber what was there previously. // these options fully clobber what was there previously.
opts := types.BotOptions{ opts := types.BotOptions{
UserID: client.UserID, UserID: client.UserID,
RoomID: event.RoomID, RoomID: event.RoomID,
SetByUserID: event.Sender, SetByUserID: event.Sender,
Options: event.Content.Raw,
Options: event.Content.Parsed.(*types.BotOptionsContent),
} }
if _, err := c.db.StoreBotOptions(opts); err != nil { if _, err := c.db.StoreBotOptions(opts); err != nil {
log.WithFields(log.Fields{ log.WithFields(log.Fields{
@ -328,6 +329,8 @@ func (c *Clients) onRoomMemberEvent(client *mautrix.Client, event *mevt.Event) {
} }
} }
var StateBotOptionsEvent = mevt.Type{Type: "m.room.bot.options", Class: mevt.StateEventType}
func (c *Clients) initClient(botClient *BotClient) error { func (c *Clients) initClient(botClient *BotClient) error {
config := botClient.config config := botClient.config
client, err := mautrix.NewClient(config.HomeserverURL, config.UserID, config.AccessToken) client, err := mautrix.NewClient(config.HomeserverURL, config.UserID, config.AccessToken)
@ -344,6 +347,10 @@ func (c *Clients) initClient(botClient *BotClient) error {
botClient.verificationSAS = &sync.Map{} botClient.verificationSAS = &sync.Map{}
syncer := client.Syncer.(*mautrix.DefaultSyncer) syncer := client.Syncer.(*mautrix.DefaultSyncer)
syncer.ParseEventContent = true
// Add m.room.bot.options to mautrix's TypeMap so that it parses it as a valid event
mevt.TypeMap[StateBotOptionsEvent] = reflect.TypeOf(types.BotOptionsContent{})
nebStore := &matrix.NEBStore{ nebStore := &matrix.NEBStore{
InMemoryStore: *mautrix.NewInMemoryStore(), InMemoryStore: *mautrix.NewInMemoryStore(),
@ -366,7 +373,7 @@ func (c *Clients) initClient(botClient *BotClient) error {
c.onMessageEvent(botClient, event) c.onMessageEvent(botClient, event)
}) })
syncer.OnEventType(mevt.Type{Type: "m.room.bot.options", Class: mevt.UnknownEventType}, func(_ mautrix.EventSource, event *mevt.Event) {
syncer.OnEventType(StateBotOptionsEvent, func(_ mautrix.EventSource, event *mevt.Event) {
c.onBotOptionsEvent(botClient.Client, event) c.onBotOptionsEvent(botClient.Client, event)
}) })

1
go.mod

@ -33,6 +33,7 @@ require (
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f // indirect
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
gopkg.in/yaml.v2 v2.3.0 gopkg.in/yaml.v2 v2.3.0
maunium.net/go/mautrix v0.9.12 maunium.net/go/mautrix v0.9.12

2
go.sum

@ -143,6 +143,8 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f h1:8w7RhxzTVgUzw/AH/9mUV5q0vMgy40SQRursCcfmkCw=
golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

71
services/github/github.go

@ -7,6 +7,7 @@ package github
import ( import (
"context" "context"
"database/sql" "database/sql"
"errors"
"fmt" "fmt"
"regexp" "regexp"
"strconv" "strconv"
@ -50,17 +51,20 @@ var ownerRepoRegex = regexp.MustCompile(`^([A-z0-9-_.]+)/([A-z0-9-_.]+)$`)
// //
// Before you can set up a Github Service, you need to set up a Github Realm. // Before you can set up a Github Service, you need to set up a Github Realm.
// //
// You can set a "default repository" for a Matrix room by sending a `m.room.bot.options` state event
// You can set optional config for a Matrix room by sending a `m.room.bot.options` state event
// which has the following `content`: // which has the following `content`:
// //
// { // {
// "github": { // "github": {
// "default_repo": "owner/repo"
// // The default repository to use for this room; this allows "owner/repo" to be omitted
// // when creating/expanding issues.
// "default_repo": "owner/repo",
//
// // Array of Github labels to attach to any issue created by this bot in this room.
// "new_issue_labels": ["bot-label-1", "bot-label-2"]
// } // }
// } // }
// //
// This will allow the "owner/repo" to be omitted when creating/expanding issues.
//
// Example request: // Example request:
// { // {
// "RealmID": "github-realm-id" // "RealmID": "github-realm-id"
@ -167,9 +171,18 @@ func (s *Service) cmdGithubCreate(roomID id.RoomID, userID id.UserID, args []str
// Look for a default if the first arg doesn't look like an owner/repo // Look for a default if the first arg doesn't look like an owner/repo
ownerRepoGroups := ownerRepoRegex.FindStringSubmatch(args[0]) ownerRepoGroups := ownerRepoRegex.FindStringSubmatch(args[0])
logger := log.WithFields(log.Fields{
"room_id": roomID,
"bot_user_id": s.ServiceUserID(),
})
options, err := s.loadBotOptions(roomID, logger)
if err != nil {
return nil, err
}
if len(ownerRepoGroups) == 0 { if len(ownerRepoGroups) == 0 {
// look for a default repo // look for a default repo
defaultRepo := s.defaultRepo(roomID)
defaultRepo := options.DefaultRepo
if defaultRepo == "" { if defaultRepo == "" {
return &mevt.MessageEventContent{ return &mevt.MessageEventContent{
MsgType: mevt.MsgNotice, MsgType: mevt.MsgNotice,
@ -206,6 +219,7 @@ func (s *Service) cmdGithubCreate(roomID id.RoomID, userID id.UserID, args []str
issue, res, err := cli.Issues.Create(context.Background(), ownerRepoGroups[1], ownerRepoGroups[2], &gogithub.IssueRequest{ issue, res, err := cli.Issues.Create(context.Background(), ownerRepoGroups[1], ownerRepoGroups[2], &gogithub.IssueRequest{
Title: title, Title: title,
Body: desc, Body: desc,
Labels: &options.NewIssueLabels,
}) })
if err != nil { if err != nil {
log.WithField("err", err).Print("Failed to create issue") log.WithField("err", err).Print("Failed to create issue")
@ -735,34 +749,37 @@ func (s *Service) Register(oldService types.Service, client types.MatrixClient)
return nil return nil
} }
// defaultRepo returns the default repo for the given room, or an empty string.
func (s *Service) defaultRepo(roomID id.RoomID) string {
logger := log.WithFields(log.Fields{
"room_id": roomID,
"bot_user_id": s.ServiceUserID(),
})
func (s *Service) loadBotOptions(roomID id.RoomID, logger *log.Entry) (result types.GithubOptions, err error) {
opts, err := database.GetServiceDB().LoadBotOptions(s.ServiceUserID(), roomID) opts, err := database.GetServiceDB().LoadBotOptions(s.ServiceUserID(), roomID)
if err != nil { if err != nil {
if err != sql.ErrNoRows {
logger.WithError(err).Error("Failed to load bot options")
if err == sql.ErrNoRows {
logger.Info("no bot options specified - using defaults")
return types.GithubOptions{}, nil
} else {
err := errors.New("Failed to load bot options")
logger.WithError(err).Error(err)
return types.GithubOptions{}, err
} }
return ""
} }
// Expect opts to look like: // Expect opts to look like:
// { github: { default_repo: $OWNER_REPO } }
ghOpts, ok := opts.Options["github"].(map[string]interface{})
if !ok {
logger.WithField("options", opts.Options).Error("Failed to cast bot options as github options")
return ""
}
defaultRepo, ok := ghOpts["default_repo"].(string)
if !ok {
logger.WithField("default_repo", ghOpts["default_repo"]).Error(
"Failed to cast default repo as a string",
)
return ""
// {
// github: {
// default_repo: $OWNER_REPO,
// new_issue_labels: [ "label1", .. ]
// }
// }
return opts.Options.Github, nil
} }
return defaultRepo
// defaultRepo returns the default repo for the given room, or an empty string.
func (s *Service) defaultRepo(roomID id.RoomID) string {
logger := log.WithFields(log.Fields{
"room_id": roomID,
"bot_user_id": s.ServiceUserID(),
})
// ignore any errors, we treat it the same as no options and log inside the method
ghOpts, _ := s.loadBotOptions(roomID, logger)
return ghOpts.DefaultRepo
} }
func (s *Service) githubClientFor(userID id.UserID, allowUnauth bool) *gogithub.Client { func (s *Service) githubClientFor(userID id.UserID, allowUnauth bool) *gogithub.Client {

11
types/service.go

@ -13,12 +13,21 @@ import (
"maunium.net/go/mautrix/id" "maunium.net/go/mautrix/id"
) )
type GithubOptions struct {
DefaultRepo string `json:"default_repo,omitempty"`
NewIssueLabels []string `json:"new_issue_labels,omitempty"`
}
type BotOptionsContent struct {
Github GithubOptions `json:"github"`
}
// BotOptions for a given bot user in a given room // BotOptions for a given bot user in a given room
type BotOptions struct { type BotOptions struct {
RoomID id.RoomID RoomID id.RoomID
UserID id.UserID UserID id.UserID
SetByUserID id.UserID SetByUserID id.UserID
Options map[string]interface{}
Options *BotOptionsContent
} }
// Poller represents a thing which can poll. Services should implement this method signature to support polling. // Poller represents a thing which can poll. Services should implement this method signature to support polling.

Loading…
Cancel
Save