Feat: Adds More Information to Summary Panel (#100)
This MR adds more information into the summary view, including the MR author, created at date, merge status, draft status, conflicts, and pipeline status, among other things. This is configurable via the setup function.
This commit is contained in:
committed by
GitHub
parent
88b9196a2e
commit
c4a3229f16
@@ -20,6 +20,14 @@ local function get_pipeline()
|
||||
return pipeline
|
||||
end
|
||||
|
||||
M.get_pipeline_status = function()
|
||||
local pipeline = get_pipeline()
|
||||
if pipeline == nil then
|
||||
return nil
|
||||
end
|
||||
return string.format("%s (%s)", state.settings.pipeline[pipeline.status], pipeline.status)
|
||||
end
|
||||
|
||||
-- The function will render the Pipeline state in a popup
|
||||
M.open = function()
|
||||
local pipeline = get_pipeline()
|
||||
@@ -44,7 +52,7 @@ M.open = function()
|
||||
local lines = {}
|
||||
|
||||
u.switch_can_edit_buf(bufnr, true)
|
||||
table.insert(lines, string.format("Status: %s (%s)", state.settings.pipeline[pipeline.status], pipeline.status))
|
||||
table.insert(lines, "Status: " .. M.get_pipeline_status())
|
||||
table.insert(lines, "")
|
||||
table.insert(lines, string.format("Last Run: %s", u.time_since(pipeline.created_at)))
|
||||
table.insert(lines, string.format("Url: %s", pipeline.web_url))
|
||||
|
||||
@@ -7,6 +7,8 @@ local job = require("gitlab.job")
|
||||
local u = require("gitlab.utils")
|
||||
local state = require("gitlab.state")
|
||||
local miscellaneous = require("gitlab.actions.miscellaneous")
|
||||
local pipeline = require("gitlab.actions.pipeline")
|
||||
|
||||
local M = {
|
||||
layout_visible = false,
|
||||
layout = nil,
|
||||
@@ -15,7 +17,46 @@ local M = {
|
||||
description_bufnr = nil,
|
||||
}
|
||||
|
||||
-- The function will render the MR description in a popup
|
||||
local title_popup_settings = {
|
||||
buf_options = {
|
||||
filetype = "markdown",
|
||||
},
|
||||
focusable = true,
|
||||
border = {
|
||||
style = "rounded",
|
||||
},
|
||||
}
|
||||
|
||||
local details_popup_settings = {
|
||||
buf_options = {
|
||||
filetype = "markdown",
|
||||
},
|
||||
focusable = true,
|
||||
border = {
|
||||
style = "rounded",
|
||||
text = {
|
||||
top = "Details",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
local description_popup_settings = {
|
||||
buf_options = {
|
||||
filetype = "markdown",
|
||||
},
|
||||
enter = true,
|
||||
focusable = true,
|
||||
border = {
|
||||
style = "rounded",
|
||||
text = {
|
||||
top = "Description",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
-- The function will render a popup containing the MR title and MR description, and optionally,
|
||||
-- any additional metadata that the user wants. The title and description are editable and
|
||||
-- can be changed via the local action keybinding, which also closes the popup
|
||||
M.summary = function()
|
||||
if M.layout_visible then
|
||||
M.layout:unmount()
|
||||
@@ -23,7 +64,11 @@ M.summary = function()
|
||||
return
|
||||
end
|
||||
|
||||
local layout, title_popup, description_popup = M.create_layout()
|
||||
local title = state.INFO.title
|
||||
local description_lines = M.build_description_lines()
|
||||
local info_lines = state.settings.info.enabled and M.build_info_lines() or nil
|
||||
|
||||
local layout, title_popup, description_popup, info_popup = M.create_layout(info_lines)
|
||||
|
||||
M.layout = layout
|
||||
M.layout_buf = layout.bufnr
|
||||
@@ -34,19 +79,16 @@ M.summary = function()
|
||||
M.layout_visible = false
|
||||
end
|
||||
|
||||
local currentBuffer = vim.api.nvim_get_current_buf()
|
||||
local title = state.INFO.title
|
||||
local description = state.INFO.description
|
||||
local lines = {}
|
||||
|
||||
for line in description:gmatch("[^\n]+") do
|
||||
table.insert(lines, line)
|
||||
table.insert(lines, "")
|
||||
end
|
||||
|
||||
vim.schedule(function()
|
||||
vim.api.nvim_buf_set_lines(currentBuffer, 0, -1, false, lines)
|
||||
vim.api.nvim_buf_set_lines(description_popup.bufnr, 0, -1, false, description_lines)
|
||||
vim.api.nvim_buf_set_lines(title_popup.bufnr, 0, -1, false, { title })
|
||||
|
||||
if info_popup then
|
||||
vim.api.nvim_buf_set_lines(info_popup.bufnr, 0, -1, false, info_lines)
|
||||
vim.api.nvim_set_option_value("modifiable", false, { buf = info_popup.bufnr })
|
||||
vim.api.nvim_set_option_value("readonly", false, { buf = info_popup.bufnr })
|
||||
end
|
||||
|
||||
state.set_popup_keymaps(
|
||||
description_popup,
|
||||
M.edit_summary,
|
||||
@@ -54,9 +96,76 @@ M.summary = function()
|
||||
{ cb = exit, action_before_close = true }
|
||||
)
|
||||
state.set_popup_keymaps(title_popup, M.edit_summary, nil, { cb = exit, action_before_close = true })
|
||||
|
||||
vim.api.nvim_set_current_buf(description_popup.bufnr)
|
||||
end)
|
||||
end
|
||||
|
||||
-- Builds a lua list of strings that contain the MR description
|
||||
M.build_description_lines = function()
|
||||
local description_lines = {}
|
||||
|
||||
local description = state.INFO.description
|
||||
for line in description:gmatch("[^\n]+") do
|
||||
table.insert(description_lines, line)
|
||||
table.insert(description_lines, "")
|
||||
end
|
||||
|
||||
return description_lines
|
||||
end
|
||||
|
||||
-- Builds a lua list of strings that contain metadata about the current MR. Only builds the
|
||||
-- lines that users include in their state.settings.info.fields list.
|
||||
M.build_info_lines = function()
|
||||
local info = state.INFO
|
||||
local options = {
|
||||
author = { title = "Author", content = "@" .. info.author.username .. " (" .. info.author.name .. ")" },
|
||||
created_at = { title = "Created", content = u.format_to_local(info.created_at, vim.fn.strftime("%z")) },
|
||||
updated_at = { title = "Updated", content = u.format_to_local(info.updated_at, vim.fn.strftime("%z")) },
|
||||
merge_status = { title = "Status", content = info.detailed_merge_status },
|
||||
draft = { title = "Draft", content = (info.draft and "Yes" or "No") },
|
||||
conflicts = { title = "Merge Conflicts", content = (info.has_conflicts and "Yes" or "No") },
|
||||
assignees = { title = "Assignees", content = u.make_readable_list(info.assignees, "name") },
|
||||
branch = { title = "Branch", content = info.source_branch },
|
||||
pipeline = {
|
||||
title = "Pipeline Status:",
|
||||
content = function()
|
||||
return pipeline.get_pipeline_status()
|
||||
end,
|
||||
},
|
||||
}
|
||||
|
||||
local longest_used = ""
|
||||
for _, v in ipairs(state.settings.info.fields) do
|
||||
local title = options[v].title
|
||||
if string.len(title) > string.len(longest_used) then
|
||||
longest_used = title
|
||||
end
|
||||
end
|
||||
|
||||
local function row_offset(row)
|
||||
local offset = string.len(longest_used) - string.len(row)
|
||||
return string.rep(" ", offset + 3)
|
||||
end
|
||||
|
||||
local lines = {}
|
||||
for _, v in ipairs(state.settings.info.fields) do
|
||||
local row = options[v]
|
||||
local line = "* " .. row.title .. row_offset(row.title)
|
||||
if type(row.content) == "function" then
|
||||
local content = row.content()
|
||||
if content ~= nil then
|
||||
line = line .. row.content()
|
||||
end
|
||||
else
|
||||
line = line .. row.content
|
||||
end
|
||||
table.insert(lines, line)
|
||||
end
|
||||
|
||||
return lines
|
||||
end
|
||||
|
||||
-- This function will PUT the new description to the Go server
|
||||
M.edit_summary = function()
|
||||
local description = u.get_buffer_text(M.description_bufnr)
|
||||
@@ -71,54 +180,52 @@ M.edit_summary = function()
|
||||
end)
|
||||
end
|
||||
|
||||
local top_popup = {
|
||||
buf_options = {
|
||||
filetype = "markdown",
|
||||
},
|
||||
focusable = true,
|
||||
border = {
|
||||
style = "rounded",
|
||||
text = {
|
||||
top = "Merge Request",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
local bottom_popup = {
|
||||
buf_options = {
|
||||
filetype = "markdown",
|
||||
},
|
||||
enter = true,
|
||||
focusable = true,
|
||||
border = {
|
||||
style = "rounded",
|
||||
},
|
||||
}
|
||||
|
||||
M.create_layout = function()
|
||||
local title_popup = Popup(top_popup)
|
||||
M.create_layout = function(info_lines)
|
||||
local title_popup = Popup(title_popup_settings)
|
||||
M.title_bufnr = title_popup.bufnr
|
||||
local description_popup = Popup(bottom_popup)
|
||||
local description_popup = Popup(description_popup_settings)
|
||||
M.description_bufnr = description_popup.bufnr
|
||||
local details_popup
|
||||
|
||||
local layout = Layout(
|
||||
{
|
||||
position = "50%",
|
||||
relative = "editor",
|
||||
size = {
|
||||
width = "90%",
|
||||
height = "70%",
|
||||
},
|
||||
},
|
||||
Layout.Box({
|
||||
Layout.Box(title_popup, { size = { height = 3 } }),
|
||||
Layout.Box(description_popup, { size = "100%" }),
|
||||
local internal_layout
|
||||
if state.settings.info.enabled then
|
||||
details_popup = Popup(details_popup_settings)
|
||||
if state.settings.info.horizontal then
|
||||
local longest_line = u.get_longest_string(info_lines)
|
||||
print(longest_line)
|
||||
internal_layout = Layout.Box({
|
||||
Layout.Box(title_popup, { size = 3 }),
|
||||
Layout.Box({
|
||||
Layout.Box(details_popup, { size = longest_line + 3 }),
|
||||
Layout.Box(description_popup, { grow = 1 }),
|
||||
}, { dir = "row", size = "100%" }),
|
||||
}, { dir = "col" })
|
||||
else
|
||||
internal_layout = Layout.Box({
|
||||
Layout.Box(title_popup, { size = 3 }),
|
||||
Layout.Box(description_popup, { grow = 1 }),
|
||||
Layout.Box(details_popup, { size = #info_lines + 3 }),
|
||||
}, { dir = "col" })
|
||||
end
|
||||
else
|
||||
internal_layout = Layout.Box({
|
||||
Layout.Box(title_popup, { size = 3 }),
|
||||
Layout.Box(description_popup, { grow = 1 }),
|
||||
}, { dir = "col" })
|
||||
)
|
||||
end
|
||||
|
||||
local layout = Layout({
|
||||
position = "50%",
|
||||
relative = "editor",
|
||||
size = {
|
||||
width = "95%",
|
||||
height = "95%",
|
||||
},
|
||||
}, internal_layout)
|
||||
|
||||
layout:mount()
|
||||
|
||||
return layout, title_popup, description_popup
|
||||
return layout, title_popup, description_popup, details_popup
|
||||
end
|
||||
|
||||
return M
|
||||
|
||||
@@ -178,4 +178,38 @@ describe("utils/init.lua", function()
|
||||
assert.are.same(got, want)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("offset_to_seconds", function()
|
||||
local tests = {
|
||||
est = { "-0500", -18000 },
|
||||
pst = { "-0800", -28800 },
|
||||
gmt = { "+0000", 0 },
|
||||
cet = { "+0100", 360 },
|
||||
jst = { "+0900", 32400 },
|
||||
ist = { "+0530", 19800 },
|
||||
art = { "-0300", -10800 },
|
||||
aest = { "+1100", 39600 },
|
||||
mmt = { "+0630", 23400 },
|
||||
}
|
||||
|
||||
for _, val in ipairs(tests) do
|
||||
local got = u.offset_to_seconds(val[1])
|
||||
local want = val[2]
|
||||
assert.are.same(got, want)
|
||||
end
|
||||
end)
|
||||
|
||||
describe("format_to_local", function()
|
||||
local tests = {
|
||||
{ "2023-10-28T16:25:09.482Z", "-0500", "10/28/2023 at 11:25" },
|
||||
{ "2016-11-22T1:25:09.482Z", "-0500", "11/21/2016 at 20:25" },
|
||||
{ "2016-11-22T1:25:09.482Z", "-0000", "11/22/2016 at 01:25" },
|
||||
{ "2017-3-22T13:25:09.482Z", "+0700", "03/22/2017 at 20:25" },
|
||||
}
|
||||
for _, val in ipairs(tests) do
|
||||
local got = u.format_to_local(val[1], val[2])
|
||||
local want = val[3]
|
||||
assert.are.same(got, want)
|
||||
end
|
||||
end)
|
||||
end)
|
||||
|
||||
@@ -34,6 +34,21 @@ M.settings = {
|
||||
resolved = "✓",
|
||||
unresolved = "",
|
||||
},
|
||||
info = {
|
||||
enabled = true,
|
||||
horizontal = false,
|
||||
fields = {
|
||||
"author",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"merge_status",
|
||||
"draft",
|
||||
"conflicts",
|
||||
"assignees",
|
||||
"branch",
|
||||
"pipeline",
|
||||
},
|
||||
},
|
||||
discussion_sign_and_diagnostic = {
|
||||
skip_resolved_discussion = false,
|
||||
skip_old_revision_discussion = false,
|
||||
|
||||
@@ -151,6 +151,67 @@ M.reverse = function(list)
|
||||
return rev
|
||||
end
|
||||
|
||||
---Returns the difference between a time offset and UTC time, in seconds
|
||||
---@param offset string The offset to compare, e.g. -0500 for EST
|
||||
---@return number
|
||||
M.offset_to_seconds = function(offset)
|
||||
local sign, hours, minutes = offset:match("([%+%-])(%d%d)(%d%d)")
|
||||
local offset_in_seconds = tonumber(hours) * 3600 + tonumber(minutes) * 60
|
||||
if sign == "-" then
|
||||
offset_in_seconds = -offset_in_seconds
|
||||
end
|
||||
return offset_in_seconds
|
||||
end
|
||||
|
||||
---Converts a UTC timestamp and offset to a human readable datestring
|
||||
---@param date_string string The time stamp
|
||||
---@param offset string The offset of the user's local time zone, e.g. -0500 for EST
|
||||
---@return string
|
||||
M.format_to_local = function(date_string, offset)
|
||||
local year, month, day, hour, min, sec, _, tzOffset = date_string:match("(%d+)-(%d+)-(%d+)T(%d+):(%d+):(%d+).(%d+)Z")
|
||||
local localTime = os.time({
|
||||
year = year,
|
||||
month = month,
|
||||
day = day,
|
||||
hour = hour,
|
||||
min = min,
|
||||
sec = sec,
|
||||
tzOffset = tzOffset,
|
||||
})
|
||||
|
||||
local localTimestamp = localTime + M.offset_to_seconds(offset)
|
||||
|
||||
return tostring(os.date("%m/%d/%Y at %H:%M", localTimestamp))
|
||||
end
|
||||
|
||||
-- Returns a comma separated (human readable) list of values from a list of associative tables
|
||||
---@param list_of_tables table The list to traverse
|
||||
---@param key string The key of the values to pull from the tables
|
||||
---@return string
|
||||
M.make_readable_list = function(list_of_tables, key)
|
||||
local res = ""
|
||||
for i, t in ipairs(list_of_tables) do
|
||||
res = res .. t[key]
|
||||
if i < #list_of_tables then
|
||||
res = res .. ", "
|
||||
end
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
-- Returns the length of the longest string in a list of strings
|
||||
---@param list table The list of strings
|
||||
---@return number
|
||||
M.get_longest_string = function(list)
|
||||
local longest = 0
|
||||
for _, v in pairs(list) do
|
||||
if string.len(v) > longest then
|
||||
longest = string.len(v)
|
||||
end
|
||||
end
|
||||
return longest
|
||||
end
|
||||
|
||||
M.notify = function(msg, lvl)
|
||||
vim.notify("gitlab.nvim: " .. msg, lvl)
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user