Cleanup of comment creation (#42)

Refactor of comment creation code
This commit is contained in:
Harrison (Harry) Cramer
2023-08-19 00:27:11 -04:00
committed by GitHub
parent ce5dd1aaa2
commit 27c54b4739
8 changed files with 124 additions and 195 deletions

View File

@@ -3,21 +3,9 @@ package main
import (
"encoding/json"
"errors"
"fmt"
"net/http"
)
func (c *Client) Approve() (string, int, error) {
_, res, err := c.git.MergeRequestApprovals.ApproveMergeRequest(c.projectId, c.mergeId, nil, nil)
if err != nil {
return "", res.Response.StatusCode, fmt.Errorf("Approving MR failed: %w", err)
}
return "Success! Approved MR.", http.StatusOK, nil
}
func ApproveHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
c := r.Context().Value("client").(Client)
@@ -26,7 +14,8 @@ func ApproveHandler(w http.ResponseWriter, r *http.Request) {
c.handleError(w, errors.New("Invalid request type"), "That request type is not allowed", http.StatusMethodNotAllowed)
return
}
msg, status, err := c.Approve()
_, res, err := c.git.MergeRequestApprovals.ApproveMergeRequest(c.projectId, c.mergeId, nil, nil)
if err != nil {
c.handleError(w, err, "Could not approve MR", http.StatusBadRequest)
@@ -34,9 +23,9 @@ func ApproveHandler(w http.ResponseWriter, r *http.Request) {
}
/* TODO: Check for non-200 status codes */
w.WriteHeader(status)
w.WriteHeader(res.StatusCode)
response := SuccessResponse{
Message: msg,
Message: "Success! Approved MR.",
Status: http.StatusOK,
}

View File

@@ -1,36 +1,24 @@
package main
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"mime/multipart"
"net/http"
"time"
"github.com/xanzy/go-gitlab"
)
const mrVersionsUrl = "%s/api/v4/projects/%s/merge_requests/%d/versions"
type MRVersion struct {
ID int `json:"id"`
HeadCommitSHA string `json:"head_commit_sha"`
BaseCommitSHA string `json:"base_commit_sha"`
StartCommitSHA string `json:"start_commit_sha"`
CreatedAt time.Time `json:"created_at"`
MergeRequestID int `json:"merge_request_id"`
State string `json:"state"`
RealSize string `json:"real_size"`
}
type PostCommentRequest struct {
LineNumber int `json:"line_number"`
FileName string `json:"file_name"`
Comment string `json:"comment"`
Comment string `json:"comment"`
FileName string `json:"file_name"`
LineNumber int `json:"line_number"`
HeadCommitSHA string `json:"head_commit_sha"`
BaseCommitSHA string `json:"base_commit_sha"`
StartCommitSHA string `json:"start_commit_sha"`
Type string `json:"type"`
}
type DeleteCommentRequest struct {
@@ -91,7 +79,6 @@ func DeleteComment(w http.ResponseWriter, r *http.Request) {
/* TODO: Check status code */
w.WriteHeader(http.StatusOK)
response := SuccessResponse{
Message: "Comment deleted succesfully",
Status: http.StatusOK,
@@ -119,22 +106,46 @@ func PostComment(w http.ResponseWriter, r *http.Request) {
return
}
res, err := c.PostComment(postCommentRequest)
for k, v := range res.Header {
w.Header().Set(k, v[0])
position := &gitlab.NotePosition{
PositionType: "text",
StartSHA: postCommentRequest.StartCommitSHA,
HeadSHA: postCommentRequest.HeadCommitSHA,
BaseSHA: postCommentRequest.BaseCommitSHA,
NewPath: postCommentRequest.FileName,
OldPath: postCommentRequest.FileName,
}
/* TODO: This switch statement relates to #25, for now we are just sending both
the old line and new line but we will eventually have to fix this */
switch postCommentRequest.Type {
case "addition":
position.NewLine = postCommentRequest.LineNumber
case "subtraction":
position.OldLine = postCommentRequest.LineNumber
case "modification":
position.NewLine = postCommentRequest.LineNumber
position.OldLine = postCommentRequest.LineNumber
}
discussion, _, err := c.git.Discussions.CreateMergeRequestDiscussion(
c.projectId,
c.mergeId,
&gitlab.CreateMergeRequestDiscussionOptions{
Body: &postCommentRequest.Comment,
Position: position,
})
if err != nil {
c.handleError(w, err, "Could not post comment", res.StatusCode)
c.handleError(w, err, "Could not create comment", http.StatusBadRequest)
return
}
w.WriteHeader(res.StatusCode)
/* TODO: Check for bad status codes */
response := SuccessResponse{
Message: "Comment created succesfully",
Status: http.StatusOK,
response := CommentResponse{
SuccessResponse: SuccessResponse{
Message: "Comment updated succesfully",
Status: http.StatusOK,
},
Comment: discussion.Notes[0],
}
json.NewEncoder(w).Encode(response)
@@ -194,124 +205,3 @@ func EditComment(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(response)
}
func (c *Client) PostComment(cr PostCommentRequest) (*http.Response, error) {
err, response := getMRVersions(c.gitlabInstance, c.projectId, c.mergeId, c.authToken)
if err != nil {
return nil, fmt.Errorf("Error making diff thread: %e", err)
}
defer response.Body.Close()
body, err := ioutil.ReadAll(response.Body)
var diffVersionInfo []MRVersion
err = json.Unmarshal(body, &diffVersionInfo)
if err != nil {
return nil, fmt.Errorf("Error unmarshalling version info JSON: %w", err)
}
/* This is necessary since we do not know whether the comment is on a line that
has been changed or not. Making all three of these API calls will let us leave
the comment regardless. I ran these in sequence vai a Sync.WaitGroup, but
it was successfully posting a comment to a modified twice, so now I'm running
them in sequence.
To clean this up we might try to detect more information about the change in our
Lua code and pass it to the Go code.
See the Gitlab documentation: https://docs.gitlab.com/ee/api/discussions.html#create-a-new-thread-in-the-merge-request-diff */
for i := 0; i < 3; i++ {
ii := i
res, err := c.CommentOnDeletion(cr.LineNumber, cr.FileName, cr.Comment, diffVersionInfo[0], ii)
if err == nil && res.StatusCode >= 200 && res.StatusCode <= 299 {
return res, nil
}
}
return nil, fmt.Errorf("Could not leave comment")
}
func min(a int, b int) int {
if a < b {
return a
}
return b
}
/* Gets the latest merge request revision data */
func getMRVersions(gitlabInstance string, projectId string, mergeId int, authToken string) (e error, response *http.Response) {
url := fmt.Sprintf(mrVersionsUrl, gitlabInstance, projectId, mergeId)
req, err := http.NewRequest(http.MethodGet, url, nil)
req.Header.Add("PRIVATE-TOKEN", authToken)
if err != nil {
return err, nil
}
client := &http.Client{}
response, err = client.Do(req)
if err != nil {
return err, nil
}
if response.StatusCode != 200 {
return errors.New("Non-200 status code: " + response.Status), nil
}
return nil, response
}
/*
Creates a new merge request discussion https://docs.gitlab.com/ee/api/discussions.html#create-new-merge-request-thread
The go-gitlab client was not working for this API specifically 😢
*/
func (c *Client) CommentOnDeletion(lineNumber int, fileName string, comment string, diffVersionInfo MRVersion, i int) (*http.Response, error) {
deletionDiscussionUrl := fmt.Sprintf(c.gitlabInstance+"/api/v4/projects/%s/merge_requests/%d/discussions", c.projectId, c.mergeId)
payload := &bytes.Buffer{}
writer := multipart.NewWriter(payload)
_ = writer.WriteField("body", comment)
_ = writer.WriteField("position[base_sha]", diffVersionInfo.BaseCommitSHA)
_ = writer.WriteField("position[start_sha]", diffVersionInfo.StartCommitSHA)
_ = writer.WriteField("position[head_sha]", diffVersionInfo.HeadCommitSHA)
_ = writer.WriteField("position[position_type]", "text")
/* We need to set these properties differently depending on whether we're commenting on a deleted line,
a modified line, an added line, or an unmodified line */
_ = writer.WriteField("position[old_path]", fileName)
_ = writer.WriteField("position[new_path]", fileName)
if i == 0 {
_ = writer.WriteField("position[old_line]", fmt.Sprintf("%d", lineNumber))
} else if i == 1 {
_ = writer.WriteField("position[new_line]", fmt.Sprintf("%d", lineNumber))
} else {
_ = writer.WriteField("position[old_line]", fmt.Sprintf("%d", lineNumber))
_ = writer.WriteField("position[new_line]", fmt.Sprintf("%d", lineNumber))
}
err := writer.Close()
if err != nil {
return nil, fmt.Errorf("Error making form data: %w", err)
}
client := &http.Client{}
req, err := http.NewRequest(http.MethodPost, deletionDiscussionUrl, payload)
if err != nil {
return nil, fmt.Errorf("Error building request: %w", err)
}
req.Header.Add("PRIVATE-TOKEN", c.authToken)
req.Header.Set("Content-Type", writer.FormDataContentType())
res, err := client.Do(req)
return res, err
}

View File

@@ -31,6 +31,7 @@ func main() {
m := http.NewServeMux()
m.Handle("/mr/description", withGitlabContext(http.HandlerFunc(DescriptionHandler), c))
m.Handle("/mr/reviewer", withGitlabContext(http.HandlerFunc(ReviewersHandler), c))
m.Handle("/mr/revisions", withGitlabContext(http.HandlerFunc(RevisionsHandler), c))
m.Handle("/mr/assignee", withGitlabContext(http.HandlerFunc(AssigneesHandler), c))
m.Handle("/approve", withGitlabContext(http.HandlerFunc(ApproveHandler), c))
m.Handle("/revoke", withGitlabContext(http.HandlerFunc(RevokeHandler), c))

42
cmd/revisions.go Normal file
View File

@@ -0,0 +1,42 @@
package main
import (
"encoding/json"
"errors"
"net/http"
"github.com/xanzy/go-gitlab"
)
type RevisionsResponse struct {
SuccessResponse
Revisions []*gitlab.MergeRequestDiffVersion
}
func RevisionsHandler(w http.ResponseWriter, r *http.Request) {
c := r.Context().Value("client").(Client)
w.Header().Set("Content-Type", "application/json")
if r.Method != http.MethodGet {
c.handleError(w, errors.New("Invalid request type"), "That request type is not allowed", http.StatusMethodNotAllowed)
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
versionInfo, _, err := c.git.MergeRequests.GetMergeRequestDiffVersions(c.projectId, c.mergeId, &gitlab.GetMergeRequestDiffVersionsOptions{})
if err != nil {
c.handleError(w, err, "Could not get diff version info", http.StatusBadRequest)
}
w.WriteHeader(http.StatusOK)
response := RevisionsResponse{
SuccessResponse: SuccessResponse{
Message: "Revisions fetched successfully",
Status: http.StatusOK,
},
Revisions: versionInfo,
}
json.NewEncoder(w).Encode(response)
}

View File

@@ -3,22 +3,9 @@ package main
import (
"encoding/json"
"errors"
"fmt"
"net/http"
)
func (c *Client) Revoke() (string, int, error) {
res, err := c.git.MergeRequestApprovals.UnapproveMergeRequest(c.projectId, c.mergeId, nil, nil)
if err != nil {
return "", res.Response.StatusCode, fmt.Errorf("Revoking approval failed: %w", err)
}
return "Success! Revoked MR approval.", http.StatusOK, nil
}
func RevokeHandler(w http.ResponseWriter, r *http.Request) {
c := r.Context().Value("client").(Client)
w.Header().Set("Content-Type", "application/json")
@@ -29,16 +16,17 @@ func RevokeHandler(w http.ResponseWriter, r *http.Request) {
return
}
msg, status, err := c.Revoke()
res, err := c.git.MergeRequestApprovals.UnapproveMergeRequest(c.projectId, c.mergeId, nil, nil)
if err != nil {
c.handleError(w, err, "Could not revoke approval", http.StatusBadRequest)
return
}
w.WriteHeader(status)
/* TODO: Check for non-200 status codes */
w.WriteHeader(res.StatusCode)
response := SuccessResponse{
Message: msg,
Message: "Success! Revoked MR approval.",
Status: http.StatusOK,
}