Skip to content

Commit

Permalink
Add mr discussion creation
Browse files Browse the repository at this point in the history
  • Loading branch information
marwatk committed Sep 15, 2020
1 parent df5a8ca commit fd4bc29
Show file tree
Hide file tree
Showing 3 changed files with 229 additions and 0 deletions.
122 changes: 122 additions & 0 deletions cmd/mr_discussion.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package cmd

import (
"bytes"
"fmt"
"io/ioutil"
"log"
"runtime"
"strconv"
"strings"
"text/template"

"github.com/rsteube/carapace"
"github.com/spf13/cobra"
gitlab "github.com/xanzy/go-gitlab"
"github.com/zaquestion/lab/internal/action"
"github.com/zaquestion/lab/internal/git"
lab "github.com/zaquestion/lab/internal/gitlab"
)

var mrCreateDiscussionCmd = &cobra.Command{
Use: "discussion [remote] <id>",
Aliases: []string{"comment"},
Short: "Start a discussion on an MR on GitLab",
Long: ``,
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
rn, mrNum, err := parseArgs(args)
if err != nil {
log.Fatal(err)
}

msgs, err := cmd.Flags().GetStringSlice("message")
if err != nil {
log.Fatal(err)
}

filename, err := cmd.Flags().GetString("file")
if err != nil {
log.Fatal(err)
}

body := ""
if filename != "" {
content, err := ioutil.ReadFile(filename)
if err != nil {
log.Fatal(err)
}
body = string(content)
} else {
body, err = mrDiscussionMsg(msgs)
if err != nil {
_, f, l, _ := runtime.Caller(0)
log.Fatal(f+":"+strconv.Itoa(l)+" ", err)
}
}

if body == "" {
log.Fatal("aborting discussion due to empty discussion msg")
}

discussionURL, err := lab.MRCreateDiscussion(rn, int(mrNum), &gitlab.CreateMergeRequestDiscussionOptions{
Body: &body,
})
if err != nil {
log.Fatal(err)
}
fmt.Println(discussionURL)
},
}

func mrDiscussionMsg(msgs []string) (string, error) {
if len(msgs) > 0 {
return strings.Join(msgs[0:], "\n\n"), nil
}

text, err := mrDiscussionText()
if err != nil {
return "", err
}
return git.EditFile("MR_NOTE", text)
}

func mrDiscussionText() (string, error) {
const tmpl = `{{.InitMsg}}
{{.CommentChar}} Write a message for this discussion. Commented lines are discarded.`

initMsg := "\n"
commentChar := git.CommentChar()

t, err := template.New("tmpl").Parse(tmpl)
if err != nil {
return "", err
}

msg := &struct {
InitMsg string
CommentChar string
}{
InitMsg: initMsg,
CommentChar: commentChar,
}

var b bytes.Buffer
err = t.Execute(&b, msg)
if err != nil {
return "", err
}

return b.String(), nil
}

func init() {
mrCreateDiscussionCmd.Flags().StringSliceP("message", "m", []string{}, "Use the given <msg>; multiple -m are concatenated as separate paragraphs")
mrCreateDiscussionCmd.Flags().StringP("file", "F", "", "Use the given file as the message")

mrCmd.AddCommand(mrCreateDiscussionCmd)
carapace.Gen(mrCreateDiscussionCmd).PositionalCompletion(
action.Remotes(),
action.MergeRequests(mrList),
)
}
89 changes: 89 additions & 0 deletions cmd/mr_discussion_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package cmd

import (
"io/ioutil"
"os/exec"
"path/filepath"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func Test_mrCreateNote(t *testing.T) {
repo := copyTestRepo(t)
cmd := exec.Command(labBinaryPath, "mr", "note", "lab-testing", "1",
"-m", "note text")
cmd.Dir = repo

b, err := cmd.CombinedOutput()
if err != nil {
t.Log(string(b))
t.Fatal(err)
}

require.Contains(t, string(b), "https://gitlab.com/lab-testing/test/merge_requests/1#note_")
}

func Test_mrCreateNote_file(t *testing.T) {
repo := copyTestRepo(t)

err := ioutil.WriteFile(filepath.Join(repo, "hellolab.txt"), []byte("hello\nlab\n"), 0644)
if err != nil {
t.Fatal(err)
}

cmd := exec.Command(labBinaryPath, "mr", "note", "lab-testing", "1",
"-F", "hellolab.txt")
cmd.Dir = repo

b, err := cmd.CombinedOutput()
if err != nil {
t.Log(string(b))
t.Fatal(err)
}

require.Contains(t, string(b), "https://gitlab.com/lab-testing/test/merge_requests/1#note_")
}

func Test_mrNoteMsg(t *testing.T) {
tests := []struct {
Name string
Msgs []string
ExpectedBody string
}{
{
Name: "Using messages",
Msgs: []string{"note paragraph 1", "note paragraph 2"},
ExpectedBody: "note paragraph 1\n\nnote paragraph 2",
},
{
Name: "From Editor",
Msgs: nil,
ExpectedBody: "", // this is not a great test
},
}
for _, test := range tests {
t.Run(test.Name, func(t *testing.T) {
test := test
t.Parallel()
body, err := mrNoteMsg(test.Msgs)
if err != nil {
t.Fatal(err)
}
assert.Equal(t, test.ExpectedBody, body)
})
}
}

func Test_mrNoteText(t *testing.T) {
t.Parallel()
text, err := mrNoteText()
if err != nil {
t.Fatal(err)
}
require.Equal(t, `
# Write a message for this note. Commented lines are discarded.`, text)

}
18 changes: 18 additions & 0 deletions internal/gitlab/gitlab.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,24 @@ func MRCreate(project string, opts *gitlab.CreateMergeRequestOptions) (string, e
return mr.WebURL, nil
}

// MRCreateDiscussion creates a discussion on a merge request on GitLab
func MRCreateDiscussion(project string, mrNum int, opts *gitlab.CreateMergeRequestDiscussionOptions) (string, error) {
p, err := FindProject(project)
if err != nil {
return "", err
}

discussion, _, err := lab.Discussions.CreateMergeRequestDiscussion(p.ID, mrNum, opts)
if err != nil {
return "", err
}

// Unlike MR, Note has no WebURL property, so we have to create it
// ourselves from the project, noteable id and note id
note := discussion.Notes[0]
return fmt.Sprintf("%s/merge_requests/%d#note_%d", p.WebURL, note.NoteableIID, note.ID), nil
}

// MRCreateNote adds a note to a merge request on GitLab
func MRCreateNote(project string, mrNum int, opts *gitlab.CreateMergeRequestNoteOptions) (string, error) {
p, err := FindProject(project)
Expand Down

0 comments on commit fd4bc29

Please sign in to comment.