Skip to content

Commit 3b57759

Browse files
rebasing to main
1 parent be02733 commit 3b57759

35 files changed

+920
-576
lines changed

.github/CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Thank you for taking time to contribute to this plugin! Please follow these step
66

77
It's possible that the feature you want is already implemented, or does not belong in `gitlab.nvim` at all. By creating an issue first you can have a conversation with the maintainers about the functionality first. While this is not strictly necessary, it greatly increases the likelihood that your merge request will be accepted.
88

9-
2. Fork the repository, and create a new feature branch for your desired functionality. Make your changes.
9+
2. Fork the repository, and create a new feature branch off the `develop` branch for your desired functionality. Make your changes.
1010

1111
If you are using Lazy as a plugin manager, the easiest way to work on changes is by setting a specific path for the plugin that points to your repository locally. This is what I do:
1212

.github/workflows/go.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ on:
44
branches:
55
- main
66
- develop
7+
paths:
8+
- 'cmd/**' # Ignore changes to the Lua code
9+
- 'go.sum'
10+
- 'go.mod'
711
jobs:
812
go_lint:
913
name: Lint Go 💅

.github/workflows/lua.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ on:
44
branches:
55
- main
66
- develop
7+
paths:
8+
- 'lua/**' # Ignore changes to the Go code
79
jobs:
810
lua_lint:
911
name: Lint Lua 💅

README.md

Lines changed: 25 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@ This Neovim plugin is designed to make it easy to review Gitlab MRs from within
1010
- View and manage pipeline Jobs
1111
- Upload files, jump to the browser, and a lot more!
1212

13-
![Screenshot 2024-01-13 at 10 43 32 AM](https://github.com/harrisoncramer/gitlab.nvim/assets/32515581/8dd8b961-a6b5-4e09-b87f-dc4a17b14149)
14-
![Screenshot 2024-01-13 at 10 43 17 AM](https://github.com/harrisoncramer/gitlab.nvim/assets/32515581/079842de-e8a4-45c5-98c2-dcafc799c904)
13+
![Screenshot 2024-12-08 at 5 43 53 PM](https://github.com/user-attachments/assets/cb9e94e3-3817-4846-ba44-16ec06ea7654)
1514

1615
https://github.com/harrisoncramer/gitlab.nvim/assets/32515581/dc5c07de-4ae6-4335-afe1-d554e3804372
1716

@@ -36,47 +35,48 @@ For more detailed information about the Lua APIs please run `:h gitlab.nvim.api`
3635
With <a href="https://github.com/folke/lazy.nvim">Lazy</a>:
3736

3837
```lua
39-
return {
38+
{
4039
"harrisoncramer/gitlab.nvim",
4140
dependencies = {
4241
"MunifTanjim/nui.nvim",
4342
"nvim-lua/plenary.nvim",
4443
"sindrets/diffview.nvim",
4544
"stevearc/dressing.nvim", -- Recommended but not required. Better UI for pickers.
46-
"nvim-tree/nvim-web-devicons" -- Recommended but not required. Icons in discussion tree.
45+
"nvim-tree/nvim-web-devicons", -- Recommended but not required. Icons in discussion tree.
4746
},
48-
enabled = true,
4947
build = function () require("gitlab.server").build(true) end, -- Builds the Go binary
5048
config = function()
5149
require("gitlab").setup()
5250
end,
5351
}
5452
```
5553

56-
And with Packer:
54+
And with <a href="https://github.com/lewis6991/pckr.nvim">pckr.nvim</a>:
5755

5856
```lua
59-
use {
60-
"harrisoncramer/gitlab.nvim",
61-
requires = {
62-
"MunifTanjim/nui.nvim",
63-
"nvim-lua/plenary.nvim",
64-
"sindrets/diffview.nvim"
65-
"stevearc/dressing.nvim", -- Recommended but not required. Better UI for pickers.
66-
"nvim-tree/nvim-web-devicons", -- Recommended but not required. Icons in discussion tree.
67-
},
68-
build = function()
69-
require("gitlab.server").build()
70-
end,
71-
branch = "develop",
72-
config = function()
73-
require("diffview") -- We require some global state from diffview
74-
local gitlab = require("gitlab")
75-
gitlab.setup()
76-
end,
77-
}
57+
{
58+
"harrisoncramer/gitlab.nvim",
59+
requires = {
60+
"MunifTanjim/nui.nvim",
61+
"nvim-lua/plenary.nvim",
62+
"sindrets/diffview.nvim",
63+
"stevearc/dressing.nvim", -- Recommended but not required. Better UI for pickers.
64+
"nvim-tree/nvim-web-devicons", -- Recommended but not required. Icons in discussion tree.
65+
},
66+
run = function() require("gitlab.server").build() end, -- Builds the Go binary
67+
config = function()
68+
require("diffview") -- We require some global state from diffview
69+
require("gitlab").setup()
70+
end,
71+
}
7872
```
7973

74+
Add `branch = "develop",` to your configuration if you want to use the (possibly unstable) development version of `gitlab.nvim`.
75+
76+
## Contributing
77+
78+
Contributions to the plugin are welcome. Please read [.github/CONTRIBUTING.md](.github/CONTRIBUTING.md) before you start working on a pull request.
79+
8080
## Connecting to Gitlab
8181

8282
This plugin requires an <a href="https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html#create-a-personal-access-token">auth token</a> to connect to Gitlab. The token can be set in the root directory of the project in a `.gitlab.nvim` environment file, or can be set via a shell environment variable called `GITLAB_TOKEN` instead. If both are present, the `.gitlab.nvim` file will take precedence.
@@ -122,7 +122,3 @@ For a list of all these settings please run `:h gitlab.nvim.configuring-the-plug
122122
The plugin sets up a number of useful keybindings in the special buffers it creates, and some global keybindings as well. Refer to the relevant section of the manual `:h gitlab.nvim.keybindings` for more details.
123123

124124
For more information about each of these commands, and about the APIs in general, run `:h gitlab.nvim.api`
125-
126-
## Contributing
127-
128-
Contributions to the plugin are welcome. Please read [.github/CONTRIBUTING.md](.github/CONTRIBUTING.md) before you start working on a pull request.

after/syntax/gitlab.vim

Lines changed: 21 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,26 @@ if filereadable($VIMRUNTIME . '/syntax/markdown.vim')
22
source $VIMRUNTIME/syntax/markdown.vim
33
endif
44

5-
syntax match Date "\v\d+\s+\w+\s+ago"
6-
highlight link Date GitlabDate
7-
8-
execute 'syntax match Unresolved /\s' . g:gitlab_discussion_tree_unresolved . '\s\?/'
9-
highlight link Unresolved GitlabUnresolved
10-
11-
execute 'syntax match Resolved /\s' . g:gitlab_discussion_tree_resolved . '\s\?/'
12-
highlight link Resolved GitlabResolved
13-
14-
execute 'syntax match GitlabDiscussionOpen /^\s*' . g:gitlab_discussion_tree_expander_open . '/'
15-
highlight link GitlabDiscussionOpen GitlabExpander
16-
17-
execute 'syntax match GitlabDiscussionClosed /^\s*' . g:gitlab_discussion_tree_expander_closed . '/'
18-
highlight link GitlabDiscussionClosed GitlabExpander
19-
20-
execute 'syntax match Draft /' . g:gitlab_discussion_tree_draft . '/'
21-
highlight link Draft GitlabDraft
22-
23-
execute 'syntax match Username "@[a-zA-Z0-9.]\+"'
24-
highlight link Username GitlabUsername
25-
26-
execute 'syntax match Mention "\%(' . g:gitlab_discussion_tree_expander_open . '\|'
27-
\ . g:gitlab_discussion_tree_expander_closed . '\)\@<!@[a-zA-Z0-9.]*"'
28-
highlight link Mention GitlabMention
5+
let expanders = '^\s*\%(' . g:gitlab_discussion_tree_expander_open . '\|' . g:gitlab_discussion_tree_expander_closed . '\)'
6+
let username = '@[a-zA-Z0-9.]\+'
7+
8+
" Covers times like '14 days ago', 'just now', as well as 'October 3, 2024'
9+
let time_ago = '\d\+ \w\+ ago'
10+
let formatted_date = '\w\+ \{1,2}\d\{1,2}, \d\{4}'
11+
let date = '\%(' . time_ago . '\|' . formatted_date . '\|just now\)'
12+
13+
let published = date . ' \%(' . g:gitlab_discussion_tree_resolved . '\|' . g:gitlab_discussion_tree_unresolved . '\|' . g:gitlab_discussion_tree_unlinked . '\)\?'
14+
let state = ' \%(' . published . '\|' . g:gitlab_discussion_tree_draft . '\)'
15+
16+
execute 'syntax match GitlabNoteHeader "' . expanders . username . state . '" contains=GitlabDate,GitlabUnresolved,GitlabUnlinked,GitlabResolved,GitlabExpander,GitlabDraft,GitlabUsername'
17+
18+
execute 'syntax match GitlabDate "' . date . '" contained'
19+
execute 'syntax match GitlabUnresolved "' . g:gitlab_discussion_tree_unresolved . '" contained'
20+
execute 'syntax match GitlabUnlinked "' . g:gitlab_discussion_tree_unlinked . '" contained'
21+
execute 'syntax match GitlabResolved "' . g:gitlab_discussion_tree_resolved . '" contained'
22+
execute 'syntax match GitlabExpander "' . expanders . '" contained'
23+
execute 'syntax match GitlabDraft "' . g:gitlab_discussion_tree_draft . '" contained'
24+
execute 'syntax match GitlabUsername "' . username . '" contained'
25+
execute 'syntax match GitlabMention "' . username . '"'
2926

3027
let b:current_syntax = 'gitlab'

cmd/app/client.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ func NewClient() (*Client, error) {
6868

6969
retryClient := retryablehttp.NewClient()
7070
retryClient.HTTPClient.Transport = tr
71+
retryClient.RetryMax = 0
7172
gitlabOptions = append(gitlabOptions, gitlab.WithHTTPClient(retryClient.HTTPClient))
7273

7374
client, err := gitlab.NewClient(pluginOptions.AuthToken, gitlabOptions...)

cmd/app/list_discussions.go

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"net/http"
55
"sort"
66
"sync"
7+
"time"
78

89
"encoding/json"
910

@@ -19,8 +20,16 @@ func Contains[T comparable](elems []T, v T) bool {
1920
return false
2021
}
2122

23+
type SortBy string
24+
25+
const (
26+
SortByLatestReply SortBy = "latest_reply"
27+
SortByOriginalComment SortBy = "original_comment"
28+
)
29+
2230
type DiscussionsRequest struct {
2331
Blacklist []string `json:"blacklist" validate:"required"`
32+
SortBy SortBy `json:"sort_by"`
2433
}
2534

2635
type DiscussionsResponse struct {
@@ -30,20 +39,30 @@ type DiscussionsResponse struct {
3039
Emojis map[int][]*gitlab.AwardEmoji `json:"emojis"`
3140
}
3241

33-
type SortableDiscussions []*gitlab.Discussion
42+
type SortableDiscussions struct {
43+
Discussions []*gitlab.Discussion
44+
SortBy SortBy
45+
}
3446

35-
func (n SortableDiscussions) Len() int {
36-
return len(n)
47+
func (d SortableDiscussions) Len() int {
48+
return len(d.Discussions)
3749
}
3850

39-
func (d SortableDiscussions) Less(i int, j int) bool {
40-
iTime := d[i].Notes[len(d[i].Notes)-1].CreatedAt
41-
jTime := d[j].Notes[len(d[j].Notes)-1].CreatedAt
42-
return iTime.After(*jTime)
51+
func (d SortableDiscussions) Less(i, j int) bool {
52+
var iTime, jTime *time.Time
53+
if d.SortBy == SortByOriginalComment {
54+
iTime = d.Discussions[i].Notes[0].CreatedAt
55+
jTime = d.Discussions[j].Notes[0].CreatedAt
56+
return iTime.Before(*jTime)
57+
} else { // SortByLatestReply
58+
iTime = d.Discussions[i].Notes[len(d.Discussions[i].Notes)-1].CreatedAt
59+
jTime = d.Discussions[j].Notes[len(d.Discussions[j].Notes)-1].CreatedAt
60+
return iTime.After(*jTime)
61+
}
4362
}
4463

45-
func (n SortableDiscussions) Swap(i, j int) {
46-
n[i], n[j] = n[j], n[i]
64+
func (d SortableDiscussions) Swap(i, j int) {
65+
d.Discussions[i], d.Discussions[j] = d.Discussions[j], d.Discussions[i]
4766
}
4867

4968
type DiscussionsLister interface {
@@ -115,8 +134,14 @@ func (a discussionsListerService) ServeHTTP(w http.ResponseWriter, r *http.Reque
115134
return
116135
}
117136

118-
sortedLinkedDiscussions := SortableDiscussions(linkedDiscussions)
119-
sortedUnlinkedDiscussions := SortableDiscussions(unlinkedDiscussions)
137+
sortedLinkedDiscussions := SortableDiscussions{
138+
Discussions: linkedDiscussions,
139+
SortBy: request.SortBy,
140+
}
141+
sortedUnlinkedDiscussions := SortableDiscussions{
142+
Discussions: unlinkedDiscussions,
143+
SortBy: request.SortBy,
144+
}
120145

121146
sort.Sort(sortedLinkedDiscussions)
122147
sort.Sort(sortedUnlinkedDiscussions)

cmd/app/list_discussions_test.go

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,14 @@ func (f fakeDiscussionsLister) ListMergeRequestDiscussions(pid interface{}, merg
2121
if err != nil {
2222
return nil, nil, err
2323
}
24-
now := time.Now()
25-
newer := now.Add(time.Second * 100)
24+
25+
timePointers := make([]*time.Time, 6)
26+
timePointers[0] = new(time.Time)
27+
*timePointers[0] = time.Now()
28+
for i := 1; i < len(timePointers); i++ {
29+
timePointers[i] = new(time.Time)
30+
*timePointers[i] = timePointers[i-1].Add(time.Second * 100)
31+
}
2632

2733
type Author struct {
2834
ID int `json:"id"`
@@ -35,8 +41,18 @@ func (f fakeDiscussionsLister) ListMergeRequestDiscussions(pid interface{}, merg
3541
}
3642

3743
testListDiscussionsResponse := []*gitlab.Discussion{
38-
{Notes: []*gitlab.Note{{CreatedAt: &now, Type: "DiffNote", Author: Author{Username: "hcramer"}}}},
39-
{Notes: []*gitlab.Note{{CreatedAt: &newer, Type: "DiffNote", Author: Author{Username: "hcramer2"}}}},
44+
{Notes: []*gitlab.Note{
45+
{CreatedAt: timePointers[0], Type: "DiffNote", Author: Author{Username: "hcramer0"}},
46+
{CreatedAt: timePointers[4], Type: "DiffNote", Author: Author{Username: "hcramer1"}},
47+
}},
48+
{Notes: []*gitlab.Note{
49+
{CreatedAt: timePointers[2], Type: "DiffNote", Author: Author{Username: "hcramer2"}},
50+
{CreatedAt: timePointers[3], Type: "DiffNote", Author: Author{Username: "hcramer3"}},
51+
}},
52+
{Notes: []*gitlab.Note{
53+
{CreatedAt: timePointers[1], Type: "DiffNote", Author: Author{Username: "hcramer4"}},
54+
{CreatedAt: timePointers[5], Type: "DiffNote", Author: Author{Username: "hcramer5"}},
55+
}},
4056
}
4157
return testListDiscussionsResponse, resp, err
4258
}
@@ -66,8 +82,23 @@ func getDiscussionsList(t *testing.T, svc http.Handler, request *http.Request) D
6682
}
6783

6884
func TestListDiscussions(t *testing.T) {
69-
t.Run("Returns sorted discussions", func(t *testing.T) {
70-
request := makeRequest(t, http.MethodPost, "/mr/discussions/list", DiscussionsRequest{Blacklist: []string{}})
85+
t.Run("Returns discussions sorted by latest reply", func(t *testing.T) {
86+
request := makeRequest(t, http.MethodPost, "/mr/discussions/list", DiscussionsRequest{Blacklist: []string{}, SortBy: "latest_reply"})
87+
svc := middleware(
88+
discussionsListerService{testProjectData, fakeDiscussionsLister{}},
89+
withMr(testProjectData, fakeMergeRequestLister{}),
90+
withPayloadValidation(methodToPayload{http.MethodPost: newPayload[DiscussionsRequest]}),
91+
withMethodCheck(http.MethodPost),
92+
)
93+
data := getDiscussionsList(t, svc, request)
94+
assert(t, data.Message, "Discussions retrieved")
95+
assert(t, data.Discussions[0].Notes[0].Author.Username, "hcramer4") /* Sorting applied */
96+
assert(t, data.Discussions[1].Notes[0].Author.Username, "hcramer0")
97+
assert(t, data.Discussions[2].Notes[0].Author.Username, "hcramer2")
98+
})
99+
100+
t.Run("Returns discussions sorted by original comment", func(t *testing.T) {
101+
request := makeRequest(t, http.MethodPost, "/mr/discussions/list", DiscussionsRequest{Blacklist: []string{}, SortBy: "original_comment"})
71102
svc := middleware(
72103
discussionsListerService{testProjectData, fakeDiscussionsLister{}},
73104
withMr(testProjectData, fakeMergeRequestLister{}),
@@ -76,12 +107,13 @@ func TestListDiscussions(t *testing.T) {
76107
)
77108
data := getDiscussionsList(t, svc, request)
78109
assert(t, data.Message, "Discussions retrieved")
79-
assert(t, data.Discussions[0].Notes[0].Author.Username, "hcramer2") /* Sorting applied */
80-
assert(t, data.Discussions[1].Notes[0].Author.Username, "hcramer")
110+
assert(t, data.Discussions[0].Notes[0].Author.Username, "hcramer0") /* Sorting applied */
111+
assert(t, data.Discussions[1].Notes[0].Author.Username, "hcramer4")
112+
assert(t, data.Discussions[2].Notes[0].Author.Username, "hcramer2")
81113
})
82114

83115
t.Run("Uses blacklist to filter unwanted authors", func(t *testing.T) {
84-
request := makeRequest(t, http.MethodPost, "/mr/discussions/list", DiscussionsRequest{Blacklist: []string{"hcramer"}})
116+
request := makeRequest(t, http.MethodPost, "/mr/discussions/list", DiscussionsRequest{Blacklist: []string{"hcramer0"}, SortBy: "latest_reply"})
85117
svc := middleware(
86118
discussionsListerService{testProjectData, fakeDiscussionsLister{}},
87119
withMr(testProjectData, fakeMergeRequestLister{}),
@@ -90,8 +122,9 @@ func TestListDiscussions(t *testing.T) {
90122
)
91123
data := getDiscussionsList(t, svc, request)
92124
assert(t, data.SuccessResponse.Message, "Discussions retrieved")
93-
assert(t, len(data.Discussions), 1)
94-
assert(t, data.Discussions[0].Notes[0].Author.Username, "hcramer2")
125+
assert(t, len(data.Discussions), 2)
126+
assert(t, data.Discussions[0].Notes[0].Author.Username, "hcramer4")
127+
assert(t, data.Discussions[1].Notes[0].Author.Username, "hcramer2")
95128
})
96129
t.Run("Handles errors from Gitlab client", func(t *testing.T) {
97130
request := makeRequest(t, http.MethodPost, "/mr/discussions/list", DiscussionsRequest{Blacklist: []string{}})

0 commit comments

Comments
 (0)