Feat: Upload Files (#59)

This MR adds the ability to add files to comments, notes, replys, and MR descriptions via a picker.

The file will get uploaded to Gitlab and the filepath will be automatically added into the current popup buffer at the current line. You can then save the changes with the normal save functionality.
This commit is contained in:
Harrison (Harry) Cramer
2023-09-08 10:02:01 -04:00
committed by GitHub
parent 45329f4d69
commit 4e473dab7e
11 changed files with 166 additions and 11 deletions

View File

@@ -15,7 +15,7 @@ https://github.com/harrisoncramer/gitlab.nvim/assets/32515581/ab5a8597-32fa-4a28
## Requirements
- <a href="https://go.dev/">Go >= v1.19</a>
- <a href="https://go.dev/">Go</a> >= v1.19
## Quick Start
@@ -87,6 +87,7 @@ require("gitlab").setup({
port = 21036, -- The port of the Go server, which runs in the background
log_path = vim.fn.stdpath("cache") .. "/gitlab.nvim.log", -- Log path for the Go server
reviewer = "delta", -- The reviewer type ("delta" or "diffview")
attachment_dir = nil, -- The local directory for files (see the "summary" section)
popup = { -- The popup for comment creation, editing, and replying
exit = "<Esc>",
perform_action = "<leader>s", -- Once in normal mode, does action (like saving comment or editing description, etc)
@@ -144,12 +145,16 @@ git checkout feature-branch
Then open Neovim. The `project_id` you specify in your configuration file must match the project_id of the Gitlab project your terminal is inside of.
### Summary
The `summary` action will pull down the MR description into a buffer so that you can read it. To edit the description, use the `settings.popup.perform_action` keybinding.
```lua
require("gitlab").summary()
```
### Reviewing Diffs
The `review` action will open a diff of the changes. You can leave comments using the `create_comment` action.
```lua
@@ -157,6 +162,8 @@ require("gitlab").review()
require("gitlab").create_comment()
```
The reviewer is Delta by default, but you can configure the plugin to use Diffview instead.
### Discussions and Notes
Gitlab groups threads of comments together into "discussions."
@@ -177,6 +184,14 @@ If you'd like to create a note in an MR (like a comment, but not linked to a spe
require("gitlab").create_note()
```
### Uploading Files
To attach a file to an MR description, reply, comment, and so forth use the `settings.popup.perform_linewise_action` keybinding when the the popup is open. This will open a picker that will look in the directory you specify in the `settings.attachment_dir` folder (this must be an absolute path) for files.
When you have picked the file, it will be added to the current buffer at the current line.
Use the `settings.popup.perform_action` to send the changes to Gitlab.
### MR Approvals
You can approve or revoke approval for an MR with the `approve` and `revoke` actions respectively.

71
cmd/attachment.go Normal file
View File

@@ -0,0 +1,71 @@
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
"os"
)
type AttachmentRequest struct {
FilePath string `json:"file_path"`
FileName string `json:"file_name"`
}
type AttachmentResponse struct {
SuccessResponse
Markdown string `json:"markdown"`
Alt string `json:"alt"`
Url string `json:"url"`
}
func AttachmentHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
c := r.Context().Value("client").(Client)
w.Header().Set("Content-Type", "application/json")
var attachmentRequest AttachmentRequest
body, err := io.ReadAll(r.Body)
if err != nil {
c.handleError(w, err, "Could not read request body", http.StatusBadRequest)
return
}
defer r.Body.Close()
err = json.Unmarshal(body, &attachmentRequest)
if err != nil {
c.handleError(w, err, "Could not unmarshal JSON", http.StatusBadRequest)
return
}
file, err := os.Open(attachmentRequest.FilePath)
if err != nil {
c.handleError(w, err, fmt.Sprintf("Could not read %s", attachmentRequest.FilePath), http.StatusBadRequest)
return
}
defer file.Close()
projectFile, res, err := c.git.Projects.UploadFile(c.projectId, file, attachmentRequest.FileName)
if err != nil {
c.handleError(w, err, fmt.Sprintf("Could not upload %s to Gitlab", attachmentRequest.FilePath), res.StatusCode)
return
}
fileResponse := AttachmentResponse{
SuccessResponse: SuccessResponse{
Status: http.StatusOK,
Message: "File uploaded successfully",
},
Markdown: projectFile.Markdown,
Alt: projectFile.Alt,
Url: projectFile.URL,
}
json.NewEncoder(w).Encode(fileResponse)
}

View File

@@ -30,6 +30,7 @@ func main() {
m := http.NewServeMux()
m.Handle("/mr/description", withGitlabContext(http.HandlerFunc(DescriptionHandler), c))
m.Handle("/mr/attachment", withGitlabContext(http.HandlerFunc(AttachmentHandler), 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))

View File

@@ -6,6 +6,7 @@ local state = require("gitlab.state")
local job = require("gitlab.job")
local u = require("gitlab.utils")
local discussions = require("gitlab.actions.discussions")
local miscellaneous = require("gitlab.actions.miscellaneous")
local reviewer = require("gitlab.reviewer")
local M = {}
@@ -17,14 +18,14 @@ M.create_comment = function()
comment_popup:mount()
state.set_popup_keymaps(comment_popup, function(text)
M.confirm_create_comment(text)
end)
end, miscellaneous.attach_file)
end
M.create_note = function()
note_popup:mount()
state.set_popup_keymaps(note_popup, function(text)
M.confirm_create_comment(text, true)
end)
end, miscellaneous.attach_file)
end
-- This function (settings.popup.perform_action) will send the comment to the Go server

View File

@@ -10,6 +10,7 @@ local job = require("gitlab.job")
local u = require("gitlab.utils")
local state = require("gitlab.state")
local reviewer = require("gitlab.reviewer")
local miscellaneous = require("gitlab.actions.miscellaneous")
local edit_popup = Popup(u.create_popup_state("Edit Comment", "80%", "80%"))
local reply_popup = Popup(u.create_popup_state("Reply", "80%", "80%"))
@@ -72,7 +73,7 @@ M.reply = function(tree)
local discussion_node = M.get_root_node(tree, node)
local id = tostring(discussion_node.id)
reply_popup:mount()
state.set_popup_keymaps(reply_popup, M.send_reply(tree, id))
state.set_popup_keymaps(reply_popup, M.send_reply(tree, id), miscellaneous.attach_file)
end
-- This function will send the reply to the Go API

View File

@@ -1,4 +1,6 @@
local state = require("gitlab.state")
local u = require("gitlab.utils")
local job = require("gitlab.job")
local M = {}
M.open_in_browser = function()
@@ -16,4 +18,33 @@ M.open_in_browser = function()
end
end
M.attach_file = function()
local attachment_dir = state.settings.attachment_dir
if not attachment_dir or attachment_dir == '' then
vim.notify("Must provide valid attachment_dir in plugin setup", vim.log.levels.ERROR)
return
end
local files = u.list_files_in_folder(attachment_dir)
if files == nil then
vim.notify(string.format("Could not list files in %s", attachment_dir), vim.log.levels.ERROR)
return
end
vim.ui.select(files, {
prompt = 'Choose attachment',
}, function(choice)
if not choice then return end
local full_path = attachment_dir .. (u.is_windows() and "\\" or "/") .. choice
local body = { file_path = full_path, file_name = choice }
job.run_job("/mr/attachment", "POST", body, function(data)
local markdown = data.markdown
local current_line = u.get_current_line_number()
local bufnr = vim.api.nvim_get_current_buf()
vim.api.nvim_buf_set_lines(bufnr, current_line - 1, current_line, false, { markdown })
end)
end)
end
return M

View File

@@ -4,6 +4,7 @@
local Popup = require("nui.popup")
local job = require("gitlab.job")
local state = require("gitlab.state")
local miscellaneous = require("gitlab.actions.miscellaneous")
local u = require("gitlab.utils")
local M = {}
@@ -23,7 +24,7 @@ M.summary = function()
vim.schedule(function()
vim.api.nvim_buf_set_lines(currentBuffer, 0, -1, false, lines)
descriptionPopup.border:set_text("top", title, "center")
state.set_popup_keymaps(descriptionPopup, M.edit_description)
state.set_popup_keymaps(descriptionPopup, M.edit_description, miscellaneous.attach_file)
end)
end

View File

@@ -59,7 +59,6 @@ M.build = function(override)
local command = string.format(cmd, state.settings.bin_path)
local null = u.is_windows() and " >NUL" or " > /dev/null"
print(command .. null)
local installCode = os.execute(command .. null)
if installCode ~= 0 then
vim.notify("Could not install gitlab.nvim!", vim.log.levels.ERROR)

View File

@@ -11,6 +11,7 @@ M.settings = {
port = 21036,
log_path = (vim.fn.stdpath("cache") .. "/gitlab.nvim.log"),
reviewer = "delta",
attachment_dir = '',
popup = {
exit = "<Esc>",
perform_action = "<leader>s",

View File

@@ -281,6 +281,37 @@ M.switch_can_edit_buf = function(buf, bool)
vim.api.nvim_buf_set_option(buf, "readonly", not bool)
end
M.list_files_in_folder = function(folder_path)
if vim.fn.isdirectory(folder_path) == 0 then
return nil
end
local folder_ok, folder = pcall(vim.fn.readdir, folder_path)
if not folder_ok then return nil end
local files = {}
if folder ~= nil then
for _, file in ipairs(folder) do
local file_path = folder_path .. (M.is_windows() and "\\" or '/') .. file
local timestamp = vim.fn.getftime(file_path)
table.insert(files, { name = file, timestamp = timestamp })
end
end
-- Sort the table by timestamp in descending order (newest first)
table.sort(files, function(a, b)
return a.timestamp > b.timestamp
end)
local result = {}
for _, file in ipairs(files) do
table.insert(result, file.name)
end
return result
end
M.reverse = function(list)
local rev = {}
for i = #list, 1, -1 do

View File

@@ -1,5 +1,8 @@
## Todo
- [ ] Fix the u.merge function to avoid overwriting settings
- [ ] Finish the Reply functionality
- [ ] Auto-Pick buffer when Cycling Through Comments
- Screenshot folder in config (where the images will be kept)
- Within the Summary view, you can call the add_summary_image() command
- This command will open a UI picker to choose the file
- When you choose the file, we pass that file path to an API endpoint which uploads
the file and returns the JSON in the API here (https://docs.gitlab.com/ee/api/projects.html#upload-a-file)
- Then we write that into the Summary buffer at the current cursor