Tree Refresh; Draft Note Replies (#289)

* fix: always refresh discussion tree data after choosing a new branch 
* fix: rebuild discussion tree without collapsing nodes after all edit/delete/create actions
* feat: add command to refresh discussion tree
* feat: Add support for draft note replies, e.g. replies to existing notes and comments in draft form
* fix: allow backticks in comment suggestions

This is a #MINOR release
This commit is contained in:
Harrison (Harry) Cramer
2024-04-25 19:15:08 -04:00
committed by GitHub
parent cf6ccddce3
commit 0d0ed1639a
15 changed files with 444 additions and 454 deletions

View File

@@ -2,8 +2,6 @@
-- That includes things like editing existing draft notes in the tree, and
-- and deleting them. Normal notes and comments are managed separately,
-- under lua/gitlab/actions/discussions/init.lua
local winbar = require("gitlab.actions.discussions.winbar")
local diagnostics = require("gitlab.indicators.diagnostics")
local common = require("gitlab.actions.common")
local discussion_tree = require("gitlab.actions.discussions.tree")
local job = require("gitlab.job")
@@ -14,76 +12,31 @@ local state = require("gitlab.state")
local M = {}
---@class AddDraftNoteOpts table
---@field draft_note DraftNote
---@field unlinked boolean
---Adds a draft note to the draft notes state, then rebuilds the view
---@param opts AddDraftNoteOpts
M.add_draft_note = function(opts)
local new_draft_notes = u.ensure_table(state.DRAFT_NOTES)
table.insert(new_draft_notes, opts.draft_note)
state.DRAFT_NOTES = new_draft_notes
local discussions = require("gitlab.actions.discussions")
if opts.unlinked then
discussions.rebuild_unlinked_discussion_tree()
else
discussions.rebuild_discussion_tree()
end
winbar.update_winbar()
end
---Tells whether a draft note was left on a particular diff or is an unlinked note
---@param note DraftNote
M.has_position = function(note)
return note.position.new_path ~= nil or note.position.old_path ~= nil
end
---Returns a list of nodes to add to the discussion tree. Can filter and return only unlinked (note) nodes.
---Re-fetches all draft notes (and non-draft notes) and re-renders the relevant views
---@param unlinked boolean
---@return NuiTree.Node[]
M.add_draft_notes_to_table = function(unlinked)
local draft_notes = List.new(state.DRAFT_NOTES)
local draft_note_nodes = draft_notes
---@param note DraftNote
:filter(function(note)
if unlinked then
return not M.has_position(note)
end
return M.has_position(note)
end)
---@param note DraftNote
:map(function(note)
local _, root_text, root_text_nodes = discussion_tree.build_note(note)
return NuiTree.Node({
range = (type(note.position) == "table" and note.position.line_range or nil),
text = root_text,
type = "note",
is_root = true,
is_draft = true,
id = note.id,
root_note_id = note.id,
file_name = (type(note.position) == "table" and note.position.new_path or nil),
new_line = (type(note.position) == "table" and note.position.new_line or nil),
old_line = (type(note.position) == "table" and note.position.old_line or nil),
resolvable = false,
resolved = false,
url = state.INFO.web_url .. "#note_" .. note.id,
}, root_text_nodes)
end)
return draft_note_nodes
-- TODO: Combine draft_notes and normal discussion nodes in the complex discussion
-- tree. The code for that feature is a clusterfuck so this is difficult
-- if state.settings.discussion_tree.tree_type == "simple" then
-- return draft_note_nodes
-- end
---@param all boolean|nil
M.rebuild_view = function(unlinked, all)
M.load_draft_notes(function()
local discussions = require("gitlab.actions.discussions")
discussions.rebuild_view(unlinked, all)
end)
end
---Send edits will actually send the edits to Gitlab and refresh the draft_notes tree
M.send_edits = function(note_id)
---Makes API call to get the discussion data, stores it in the state, and calls the callback
---@param callback function|nil
M.load_draft_notes = function(callback)
state.load_new_state("draft_notes", function()
if callback ~= nil then
callback()
end
end)
end
---Will actually send the edits to Gitlab and refresh the draft_notes tree
---@param note_id integer
---@param unlinked boolean
---@return function
M.confirm_edit_draft_note = function(note_id, unlinked)
return function(text)
local all_notes = List.new(state.DRAFT_NOTES)
local the_note = all_notes:find(function(note)
@@ -92,67 +45,18 @@ M.send_edits = function(note_id)
local body = { note = text, position = the_note.position }
job.run_job(string.format("/mr/draft_notes/%d", note_id), "PATCH", body, function(data)
u.notify(data.message, vim.log.levels.INFO)
local has_position = false
local new_draft_notes = all_notes:map(function(note)
if note.id == note_id then
has_position = M.has_position(note)
note.note = text
end
return note
end)
state.DRAFT_NOTES = new_draft_notes
local discussions = require("gitlab.actions.discussions")
if has_position then
discussions.rebuild_discussion_tree()
else
discussions.rebuild_unlinked_discussion_tree()
end
winbar.update_winbar()
M.rebuild_view(unlinked)
end)
end
end
-- This function will actually send the deletion to Gitlab when you make a selection, and re-render the tree
M.send_deletion = function(tree)
local current_node = tree:get_node()
local note_node = common.get_note_node(tree, current_node)
local root_node = common.get_root_node(tree, current_node)
if note_node == nil or root_node == nil then
u.notify("Could not get note or root node", vim.log.levels.ERROR)
return
end
---@type integer
local note_id = note_node.is_root and root_node.id or note_node.id
---This function will actually send the deletion to Gitlab when you make a selection, and re-render the tree
---@param note_id integer
---@param unlinked boolean
M.confirm_delete_draft_note = function(note_id, unlinked)
job.run_job(string.format("/mr/draft_notes/%d", note_id), "DELETE", nil, function(data)
u.notify(data.message, vim.log.levels.INFO)
local has_position = false
local new_draft_notes = List.new(state.DRAFT_NOTES):filter(function(note)
if note.id ~= note_id then
return true
else
has_position = M.has_position(note)
return false
end
end)
state.DRAFT_NOTES = new_draft_notes
local discussions = require("gitlab.actions.discussions")
if has_position then
discussions.rebuild_discussion_tree()
else
discussions.rebuild_unlinked_discussion_tree()
end
if state.settings.discussion_signs.enabled and state.DISCUSSION_DATA then
diagnostics.refresh_diagnostics()
end
winbar.update_winbar()
common.add_empty_titles()
M.rebuild_view(unlinked)
end)
end
@@ -185,11 +89,7 @@ M.confirm_publish_all_drafts = function()
u.notify(data.message, vim.log.levels.INFO)
state.DRAFT_NOTES = {}
local discussions = require("gitlab.actions.discussions")
discussions.refresh(function()
discussions.rebuild_discussion_tree()
discussions.rebuild_unlinked_discussion_tree()
winbar.update_winbar()
end)
discussions.rebuild_view(false, true)
end)
end
@@ -213,27 +113,61 @@ M.confirm_publish_draft = function(tree)
job.run_job("/mr/draft_notes/publish", "POST", body, function(data)
u.notify(data.message, vim.log.levels.INFO)
local has_position = false
local new_draft_notes = List.new(state.DRAFT_NOTES):filter(function(note)
if note.id ~= note_id then
return true
else
has_position = M.has_position(note)
return false
end
end)
state.DRAFT_NOTES = new_draft_notes
local discussions = require("gitlab.actions.discussions")
discussions.refresh(function()
if has_position then
discussions.rebuild_discussion_tree()
else
discussions.rebuild_unlinked_discussion_tree()
end
winbar.update_winbar()
end)
local unlinked = tree.bufnr == discussions.unlinked_bufnr
M.rebuild_view(unlinked)
end)
end
--- Helper functions
---Tells whether a draft note was left on a particular diff or is an unlinked note
---@param note DraftNote
M.has_position = function(note)
return note.position.new_path ~= nil or note.position.old_path ~= nil
end
---Builds a note for the discussion tree for draft notes that are roots
---of their own discussions, e.g. not replies
---@param note DraftNote
---@return NuiTree.Node
M.build_root_draft_note = function(note)
local _, root_text, root_text_nodes = discussion_tree.build_note(note)
return NuiTree.Node({
range = (type(note.position) == "table" and note.position.line_range or nil),
text = root_text,
type = "note",
is_root = true,
is_draft = true,
id = note.id,
root_note_id = note.id,
file_name = (type(note.position) == "table" and note.position.new_path or nil),
new_line = (type(note.position) == "table" and note.position.new_line or nil),
old_line = (type(note.position) == "table" and note.position.old_line or nil),
resolvable = false,
resolved = false,
url = state.INFO.web_url .. "#note_" .. note.id,
}, root_text_nodes)
end
---Returns a list of nodes to add to the discussion tree. Can filter and return only unlinked (note) nodes.
---@param unlinked boolean
---@return NuiTree.Node[]
M.add_draft_notes_to_table = function(unlinked)
local draft_notes = List.new(state.DRAFT_NOTES)
local draft_note_nodes = draft_notes
---@param note DraftNote
:filter(function(note)
if unlinked then
return not M.has_position(note)
end
return M.has_position(note)
end)
:filter(function(note)
return note.discussion_id == "" -- Do not include draft replies
end)
:map(M.build_root_draft_note)
return draft_note_nodes
end
return M