* Feat: Enable sorting discussions by original comment (#422)
* Feat: Improve popup UX (#426)
* Feat: Automatically update MR summary details (#427)
* Feat: Show update progress in winbar (#432)
* Feat: Abbreviate winbar (#439)
* Fix: Note Creation Bug (#441)
* Fix: Checking whether comment can be created (#434)
* Fix: Syntax in discussion tree (#433)
* fix: improve indication of resolved threads and drafts (#442)
* Docs: Various minor improvements (#445)

---------

Co-authored-by: Jakub F. Bortlík <jakub.bortlik@proton.me>
This commit is contained in:
Harrison (Harry) Cramer
2024-12-11 14:21:50 -05:00
committed by GitHub
parent be027331e1
commit 495e64c8bc
32 changed files with 880 additions and 564 deletions

View File

@@ -4,6 +4,7 @@ import (
"net/http"
"sort"
"sync"
"time"
"encoding/json"
@@ -19,8 +20,16 @@ func Contains[T comparable](elems []T, v T) bool {
return false
}
type SortBy string
const (
SortByLatestReply SortBy = "latest_reply"
SortByOriginalComment SortBy = "original_comment"
)
type DiscussionsRequest struct {
Blacklist []string `json:"blacklist" validate:"required"`
SortBy SortBy `json:"sort_by"`
}
type DiscussionsResponse struct {
@@ -30,20 +39,30 @@ type DiscussionsResponse struct {
Emojis map[int][]*gitlab.AwardEmoji `json:"emojis"`
}
type SortableDiscussions []*gitlab.Discussion
func (n SortableDiscussions) Len() int {
return len(n)
type SortableDiscussions struct {
Discussions []*gitlab.Discussion
SortBy SortBy
}
func (d SortableDiscussions) Less(i int, j int) bool {
iTime := d[i].Notes[len(d[i].Notes)-1].CreatedAt
jTime := d[j].Notes[len(d[j].Notes)-1].CreatedAt
return iTime.After(*jTime)
func (d SortableDiscussions) Len() int {
return len(d.Discussions)
}
func (n SortableDiscussions) Swap(i, j int) {
n[i], n[j] = n[j], n[i]
func (d SortableDiscussions) Less(i, j int) bool {
var iTime, jTime *time.Time
if d.SortBy == SortByOriginalComment {
iTime = d.Discussions[i].Notes[0].CreatedAt
jTime = d.Discussions[j].Notes[0].CreatedAt
return iTime.Before(*jTime)
} else { // SortByLatestReply
iTime = d.Discussions[i].Notes[len(d.Discussions[i].Notes)-1].CreatedAt
jTime = d.Discussions[j].Notes[len(d.Discussions[j].Notes)-1].CreatedAt
return iTime.After(*jTime)
}
}
func (d SortableDiscussions) Swap(i, j int) {
d.Discussions[i], d.Discussions[j] = d.Discussions[j], d.Discussions[i]
}
type DiscussionsLister interface {
@@ -115,8 +134,14 @@ func (a discussionsListerService) ServeHTTP(w http.ResponseWriter, r *http.Reque
return
}
sortedLinkedDiscussions := SortableDiscussions(linkedDiscussions)
sortedUnlinkedDiscussions := SortableDiscussions(unlinkedDiscussions)
sortedLinkedDiscussions := SortableDiscussions{
Discussions: linkedDiscussions,
SortBy: request.SortBy,
}
sortedUnlinkedDiscussions := SortableDiscussions{
Discussions: unlinkedDiscussions,
SortBy: request.SortBy,
}
sort.Sort(sortedLinkedDiscussions)
sort.Sort(sortedUnlinkedDiscussions)

View File

@@ -21,8 +21,14 @@ func (f fakeDiscussionsLister) ListMergeRequestDiscussions(pid interface{}, merg
if err != nil {
return nil, nil, err
}
now := time.Now()
newer := now.Add(time.Second * 100)
timePointers := make([]*time.Time, 6)
timePointers[0] = new(time.Time)
*timePointers[0] = time.Now()
for i := 1; i < len(timePointers); i++ {
timePointers[i] = new(time.Time)
*timePointers[i] = timePointers[i-1].Add(time.Second * 100)
}
type Author struct {
ID int `json:"id"`
@@ -35,8 +41,18 @@ func (f fakeDiscussionsLister) ListMergeRequestDiscussions(pid interface{}, merg
}
testListDiscussionsResponse := []*gitlab.Discussion{
{Notes: []*gitlab.Note{{CreatedAt: &now, Type: "DiffNote", Author: Author{Username: "hcramer"}}}},
{Notes: []*gitlab.Note{{CreatedAt: &newer, Type: "DiffNote", Author: Author{Username: "hcramer2"}}}},
{Notes: []*gitlab.Note{
{CreatedAt: timePointers[0], Type: "DiffNote", Author: Author{Username: "hcramer0"}},
{CreatedAt: timePointers[4], Type: "DiffNote", Author: Author{Username: "hcramer1"}},
}},
{Notes: []*gitlab.Note{
{CreatedAt: timePointers[2], Type: "DiffNote", Author: Author{Username: "hcramer2"}},
{CreatedAt: timePointers[3], Type: "DiffNote", Author: Author{Username: "hcramer3"}},
}},
{Notes: []*gitlab.Note{
{CreatedAt: timePointers[1], Type: "DiffNote", Author: Author{Username: "hcramer4"}},
{CreatedAt: timePointers[5], Type: "DiffNote", Author: Author{Username: "hcramer5"}},
}},
}
return testListDiscussionsResponse, resp, err
}
@@ -66,8 +82,8 @@ func getDiscussionsList(t *testing.T, svc http.Handler, request *http.Request) D
}
func TestListDiscussions(t *testing.T) {
t.Run("Returns sorted discussions", func(t *testing.T) {
request := makeRequest(t, http.MethodPost, "/mr/discussions/list", DiscussionsRequest{Blacklist: []string{}})
t.Run("Returns discussions sorted by latest reply", func(t *testing.T) {
request := makeRequest(t, http.MethodPost, "/mr/discussions/list", DiscussionsRequest{Blacklist: []string{}, SortBy: "latest_reply"})
svc := middleware(
discussionsListerService{testProjectData, fakeDiscussionsLister{}},
withMr(testProjectData, fakeMergeRequestLister{}),
@@ -76,12 +92,28 @@ func TestListDiscussions(t *testing.T) {
)
data := getDiscussionsList(t, svc, request)
assert(t, data.Message, "Discussions retrieved")
assert(t, data.Discussions[0].Notes[0].Author.Username, "hcramer2") /* Sorting applied */
assert(t, data.Discussions[1].Notes[0].Author.Username, "hcramer")
assert(t, data.Discussions[0].Notes[0].Author.Username, "hcramer4") /* Sorting applied */
assert(t, data.Discussions[1].Notes[0].Author.Username, "hcramer0")
assert(t, data.Discussions[2].Notes[0].Author.Username, "hcramer2")
})
t.Run("Returns discussions sorted by original comment", func(t *testing.T) {
request := makeRequest(t, http.MethodPost, "/mr/discussions/list", DiscussionsRequest{Blacklist: []string{}, SortBy: "original_comment"})
svc := middleware(
discussionsListerService{testProjectData, fakeDiscussionsLister{}},
withMr(testProjectData, fakeMergeRequestLister{}),
withPayloadValidation(methodToPayload{http.MethodPost: newPayload[DiscussionsRequest]}),
withMethodCheck(http.MethodPost),
)
data := getDiscussionsList(t, svc, request)
assert(t, data.Message, "Discussions retrieved")
assert(t, data.Discussions[0].Notes[0].Author.Username, "hcramer0") /* Sorting applied */
assert(t, data.Discussions[1].Notes[0].Author.Username, "hcramer4")
assert(t, data.Discussions[2].Notes[0].Author.Username, "hcramer2")
})
t.Run("Uses blacklist to filter unwanted authors", func(t *testing.T) {
request := makeRequest(t, http.MethodPost, "/mr/discussions/list", DiscussionsRequest{Blacklist: []string{"hcramer"}})
request := makeRequest(t, http.MethodPost, "/mr/discussions/list", DiscussionsRequest{Blacklist: []string{"hcramer0"}, SortBy: "latest_reply"})
svc := middleware(
discussionsListerService{testProjectData, fakeDiscussionsLister{}},
withMr(testProjectData, fakeMergeRequestLister{}),
@@ -90,8 +122,9 @@ func TestListDiscussions(t *testing.T) {
)
data := getDiscussionsList(t, svc, request)
assert(t, data.SuccessResponse.Message, "Discussions retrieved")
assert(t, len(data.Discussions), 1)
assert(t, data.Discussions[0].Notes[0].Author.Username, "hcramer2")
assert(t, len(data.Discussions), 2)
assert(t, data.Discussions[0].Notes[0].Author.Username, "hcramer4")
assert(t, data.Discussions[1].Notes[0].Author.Username, "hcramer2")
})
t.Run("Handles errors from Gitlab client", func(t *testing.T) {
request := makeRequest(t, http.MethodPost, "/mr/discussions/list", DiscussionsRequest{Blacklist: []string{}})