Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ It's possible that the feature you want is already implemented, or does not belo

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:

```lua
```lua
{
"harrisoncramer/gitlab.nvim",
dependencies = {
Expand Down Expand Up @@ -54,8 +54,8 @@ $ luacheck --globals vim busted --no-max-line-length -- .

4. Make the merge request to the `develop` branch of `.gitlab.nvim`

Please provide a description of the feature, and links to any relevant issues.
Please provide a description of the feature, and links to any relevant issues.

That's it! I'll try to respond to any incoming merge request in a few days. Once we've reviewed it, it will be merged into the develop branch.
That's it! I'll try to respond to any incoming merge request in a few days. Once we've reviewed it, it will be merged into the develop branch.

After some time, if the develop branch is found to be stable, that branch will be merged into `main` and released. When merged into `main` the pipeline will detect whether we're merging in a patch, minor, or major change, and create a new tag (e.g. 1.0.12) and release.
51 changes: 45 additions & 6 deletions cmd/app/pipeline.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,18 @@ type RetriggerPipelineResponse struct {
type PipelineWithJobs struct {
Jobs []*gitlab.Job `json:"jobs"`
LatestPipeline *gitlab.PipelineInfo `json:"latest_pipeline"`
Name string `json:"name"`
}

type GetPipelineAndJobsResponse struct {
SuccessResponse
Pipeline PipelineWithJobs `json:"latest_pipeline"`
Pipelines []PipelineWithJobs `json:"latest_pipeline"`
}

type PipelineManager interface {
ListProjectPipelines(pid interface{}, opt *gitlab.ListProjectPipelinesOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.PipelineInfo, *gitlab.Response, error)
ListPipelineJobs(pid interface{}, pipelineID int, opts *gitlab.ListJobsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.Job, *gitlab.Response, error)
ListPipelineBridges(pid interface{}, pipelineID int, opts *gitlab.ListJobsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.Bridge, *gitlab.Response, error)
RetryPipelineBuild(pid interface{}, pipeline int, options ...gitlab.RequestOptionFunc) (*gitlab.Pipeline, *gitlab.Response, error)
}

Expand Down Expand Up @@ -101,7 +103,6 @@ func (a pipelineService) GetPipelineAndJobs(w http.ResponseWriter, r *http.Reque
}

jobs, res, err := a.client.ListPipelineJobs(a.projectInfo.ProjectId, pipeline.ID, &gitlab.ListJobsOptions{})

if err != nil {
handleError(w, err, "Could not get pipeline jobs", http.StatusInternalServerError)
return
Expand All @@ -112,13 +113,51 @@ func (a pipelineService) GetPipelineAndJobs(w http.ResponseWriter, r *http.Reque
return
}

pipelines := []PipelineWithJobs{}
pipelines = append(pipelines, PipelineWithJobs{
Jobs: jobs,
LatestPipeline: pipeline,
Name: "root",
})

bridges, res, err := a.client.ListPipelineBridges(a.projectInfo.ProjectId, pipeline.ID, &gitlab.ListJobsOptions{})

if err != nil {
handleError(w, err, "Could not get pipeline trigger jobs", http.StatusInternalServerError)
return
}
if res.StatusCode >= 300 {
handleError(w, GenericError{r.URL.Path}, "Could not get pipeline trigger jobs", res.StatusCode)
return
}

for _, bridge := range bridges {
if bridge.DownstreamPipeline == nil {
continue
}

pipelineIdInBridge := bridge.DownstreamPipeline.ID
bridgePipelineJobs, res, err := a.client.ListPipelineJobs(bridge.DownstreamPipeline.ProjectID, pipelineIdInBridge, &gitlab.ListJobsOptions{})
if err != nil {
handleError(w, err, "Could not get jobs for a pipeline from a trigger job", http.StatusInternalServerError)
return
}
if res.StatusCode >= 300 {
handleError(w, GenericError{r.URL.Path}, "Could not get jobs for a pipeline from a trigger job", res.StatusCode)
return
}

pipelines = append(pipelines, PipelineWithJobs{
Jobs: bridgePipelineJobs,
LatestPipeline: bridge.DownstreamPipeline,
Name: bridge.Name,
})
}

w.WriteHeader(http.StatusOK)
response := GetPipelineAndJobsResponse{
SuccessResponse: SuccessResponse{Message: "Pipeline retrieved"},
Pipeline: PipelineWithJobs{
LatestPipeline: pipeline,
Jobs: jobs,
},
Pipelines: pipelines,
}

err = json.NewEncoder(w).Encode(response)
Expand Down
8 changes: 8 additions & 0 deletions cmd/app/pipeline_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ func (f fakePipelineManager) ListPipelineJobs(pid interface{}, pipelineID int, o
return []*gitlab.Job{}, resp, err
}

func (f fakePipelineManager) ListPipelineBridges(pid interface{}, pipelineID int, opts *gitlab.ListJobsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.Bridge, *gitlab.Response, error) {
resp, err := f.handleGitlabError()
if err != nil {
return nil, nil, err
}
return []*gitlab.Bridge{}, resp, err
}

func (f fakePipelineManager) RetryPipelineBuild(pid interface{}, pipeline int, options ...gitlab.RequestOptionFunc) (*gitlab.Pipeline, *gitlab.Response, error) {
resp, err := f.handleGitlabError()
if err != nil {
Expand Down
186 changes: 118 additions & 68 deletions lua/gitlab/actions/pipeline.lua
Original file line number Diff line number Diff line change
Expand Up @@ -12,94 +12,147 @@ local M = {
pipeline_popup = nil,
}

local function get_latest_pipeline()
local pipeline = state.PIPELINE and state.PIPELINE.latest_pipeline
if type(pipeline) ~= "table" or (type(pipeline) == "table" and u.table_size(pipeline) == 0) then
u.notify("Pipeline not found", vim.log.levels.WARN)
return
local function get_latest_pipelines(count)
count = count or 1 -- Default to 1 if count is not provided
local pipelines = {}

if not state.PIPELINE then
u.notify("Pipeline state is not initialized", vim.log.levels.WARN)
return nil
end
return pipeline
end

local function get_pipeline_jobs()
M.latest_pipeline = get_latest_pipeline()
if not M.latest_pipeline then
return
for i = 1, math.max(count, #state.PIPELINE) do
local pipeline = state.PIPELINE[i].latest_pipeline
if type(pipeline) == "table" and u.table_size(pipeline) > 0 then
table.insert(pipelines, pipeline)
end
end

if #pipelines == 0 then
u.notify("No valid pipelines found", vim.log.levels.WARN)
return nil
end
return u.reverse(type(state.PIPELINE.jobs) == "table" and state.PIPELINE.jobs or {})
return pipelines
end

local function get_pipeline_jobs(idx)
return u.reverse(type(state.PIPELINE[idx].jobs) == "table" and state.PIPELINE[idx].jobs or {})
end

-- The function will render the Pipeline state in a popup
M.open = function()
M.pipeline_jobs = get_pipeline_jobs()
M.latest_pipeline = get_latest_pipeline()
if M.latest_pipeline == nil then
M.latest_pipelines = get_latest_pipelines()
if not M.latest_pipelines then
return
end
if not M.latest_pipelines or #M.latest_pipelines == 0 then
return
end

local width = string.len(M.latest_pipeline.web_url) + 10
local height = 6 + #M.pipeline_jobs + 3
local max_width = 0
local total_height = 0
local pipelines_data = {}

for idx, pipeline in ipairs(M.latest_pipelines) do
local width = string.len(pipeline.web_url) + 10
max_width = math.max(max_width, width)
local pipeline_jobs = get_pipeline_jobs(idx)
local pipeline_status = M.get_pipeline_status(idx, false)
local height = 6 + #pipeline_jobs + 3
total_height = total_height + height

table.insert(pipelines_data, {
pipeline = pipeline,
pipeline_status = pipeline_status,
jobs = pipeline_jobs,
width = width,
height = 6 + #pipeline_jobs + 3,
lines = {},
})
end

local pipeline_popup =
Popup(popup.create_popup_state("Loading Pipeline...", state.settings.popup.pipeline, width, height, 60))
Popup(popup.create_popup_state("Loading Pipelines...", state.settings.popup.pipeline, max_width, total_height, 60))
popup.set_up_autocommands(pipeline_popup, nil, vim.api.nvim_get_current_win())
M.pipeline_popup = pipeline_popup
pipeline_popup:mount()

local bufnr = vim.api.nvim_get_current_buf()
vim.opt_local.wrap = false

local lines = {}

u.switch_can_edit_buf(bufnr, true)
table.insert(lines, "Status: " .. M.get_pipeline_status(false))
table.insert(lines, "")
table.insert(lines, string.format("Last Run: %s", u.time_since(M.latest_pipeline.created_at)))
table.insert(lines, string.format("Url: %s", M.latest_pipeline.web_url))
table.insert(lines, string.format("Triggered By: %s", M.latest_pipeline.source))

table.insert(lines, "")
table.insert(lines, "Jobs:")

local longest_title = u.get_longest_string(u.map(M.pipeline_jobs, function(v)
return v.name
end))

local function row_offset(name)
local offset = longest_title - string.len(name)
local res = string.rep(" ", offset + 5)
return res
end

for _, pipeline_job in ipairs(M.pipeline_jobs) do
local offset = row_offset(pipeline_job.name)
local row = string.format(
"%s%s %s (%s)",
pipeline_job.name,
offset,
state.settings.pipeline[pipeline_job.status] or "*",
pipeline_job.status or ""
)

table.insert(lines, row)
local all_lines = {}
for i, data in ipairs(pipelines_data) do
local pipeline = data.pipeline
local lines = data.lines

table.insert(lines, data.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))
table.insert(lines, string.format("Triggered By: %s", pipeline.source))
table.insert(lines, "")
table.insert(lines, "Jobs:")

local longest_title = u.get_longest_string(u.map(data.jobs, function(v)
return v.name
end))

local function row_offset(name)
local offset = longest_title - string.len(name)
local res = string.rep(" ", offset + 5)
return res
end

for _, pipeline_job in ipairs(data.jobs) do
local offset = row_offset(pipeline_job.name)
local row = string.format(
"%s%s %s (%s)",
pipeline_job.name,
offset,
state.settings.pipeline[pipeline_job.status] or "*",
pipeline_job.status or ""
)
table.insert(lines, row)
end

-- Add separator between pipelines
if i < #pipelines_data then
table.insert(lines, "")
table.insert(lines, string.rep("-", max_width))
table.insert(lines, "")
end

for _, line in ipairs(lines) do
table.insert(all_lines, line)
end
end

vim.schedule(function()
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
M.color_status(M.latest_pipeline.status, bufnr, lines[1], 1)
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, all_lines)

local line_offset = 0
for _, data in ipairs(pipelines_data) do
local pipeline = data.pipeline
local lines = data.lines

for i, pipeline_job in ipairs(M.pipeline_jobs) do
M.color_status(pipeline_job.status, bufnr, lines[7 + i], 7 + i)
M.color_status(pipeline.status, bufnr, all_lines[line_offset + 1], line_offset + 1)

for j, pipeline_job in ipairs(data.jobs) do
M.color_status(pipeline_job.status, bufnr, all_lines[line_offset + 7 + j], line_offset + 7 + j)
end

line_offset = line_offset + #lines
end

pipeline_popup.border:set_text("top", "Pipeline Status", "center")
pipeline_popup.border:set_text("top", "Pipelines Status", "center")
popup.set_popup_keymaps(pipeline_popup, M.retrigger, M.see_logs)
u.switch_can_edit_buf(bufnr, false)
end)
end

M.retrigger = function()
M.latest_pipeline = get_latest_pipeline()
M.latest_pipeline = get_latest_pipelines()
if not M.latest_pipeline then
return
end
Expand Down Expand Up @@ -173,12 +226,8 @@ end
---colorize the pipeline icon.
---@param wrap_with_color boolean
---@return string
M.get_pipeline_icon = function(wrap_with_color)
M.latest_pipeline = get_latest_pipeline()
if not M.latest_pipeline then
return ""
end
local symbol = state.settings.pipeline[M.latest_pipeline.status]
M.get_pipeline_icon = function(idx, wrap_with_color)
local symbol = state.settings.pipeline[state.PIPELINE[idx].latest_pipeline.status]
if not wrap_with_color then
return symbol
end
Expand All @@ -196,12 +245,13 @@ end
---colorize the pipeline icon.
---@param wrap_with_color boolean
---@return string
M.get_pipeline_status = function(wrap_with_color)
M.latest_pipeline = get_latest_pipeline()
if not M.latest_pipeline then
return ""
end
return string.format("%s (%s)", M.get_pipeline_icon(wrap_with_color), M.latest_pipeline.status)
M.get_pipeline_status = function(idx, wrap_with_color)
return string.format(
"[%s]: Status: %s (%s)",
state.PIPELINE[idx].name,
M.get_pipeline_icon(idx, wrap_with_color),
state.PIPELINE[idx].latest_pipeline.status
)
end

M.color_status = function(status, bufnr, status_line, linnr)
Expand Down
Loading