feat(obs-integration): add package's build status view (#284)
* feat(obs-integration): add package's build status view * i18n(obs-integration): new translation for build status * chore(obs-integration): update plugin to v1.1.0 * docs(obs-integration): update README * i18n(obs-integration): fix duplicates and missing comma in translation --------- Co-authored-by: neyfua <ogkhang.05@gmail.com>
This commit is contained in:
@@ -64,8 +64,8 @@ its actions:
|
|||||||
- **Service Remote Run** — runs `osc service rr` on the OBS server for the
|
- **Service Remote Run** — runs `osc service rr` on the OBS server for the
|
||||||
package on the remote project. No local code executes; the request triggers
|
package on the remote project. No local code executes; the request triggers
|
||||||
server-side service runs, and a confirmation is not required.
|
server-side service runs, and a confirmation is not required.
|
||||||
- **Rebuild package** — pick an architecture (or **All**) and trigger
|
- **Rebuild package** — pick a repository and architecture (or **All**) and trigger `osc rebuild`. Confirmation is required before it runs.
|
||||||
`osc rebuild`. Confirmation is required before it runs.
|
- **Build Status** — Shows real-time build results for the package across all repositories and architectures. Click **Refresh status** to pull latest results.
|
||||||
- **Edit package meta** — `osc meta pkg -e` in a terminal.
|
- **Edit package meta** — `osc meta pkg -e` in a terminal.
|
||||||
- **Remove package** — deletes the checkout from disk only (never touches the
|
- **Remove package** — deletes the checkout from disk only (never touches the
|
||||||
OBS project); confirm before it runs. If it was the last package in the
|
OBS project); confirm before it runs. If it was the last package in the
|
||||||
@@ -93,3 +93,7 @@ operation opens a terminal only when `osc status` reports pending changes;
|
|||||||
otherwise, it runs asynchronously in the panel. Confirmation prompts precede
|
otherwise, it runs asynchronously in the panel. Confirmation prompts precede
|
||||||
destructive and side-effecting operations: Rebuild package, Remove package,
|
destructive and side-effecting operations: Rebuild package, Remove package,
|
||||||
and Service Run All.
|
and Service Run All.
|
||||||
|
|
||||||
|
Build status data is fetched from `osc results` and cached for the session. The
|
||||||
|
status display shares the same underlying `osc results` call used for rebuild
|
||||||
|
architecture selection, so enabling a package view pulls both in one query.
|
||||||
|
|||||||
+110
-2
@@ -21,6 +21,7 @@ local rebuildRepoIndex = 0
|
|||||||
local rebuildConfirm = false
|
local rebuildConfirm = false
|
||||||
local runAllConfirm = false
|
local runAllConfirm = false
|
||||||
local repoMap = {}
|
local repoMap = {}
|
||||||
|
local statusMap = {}
|
||||||
local savedRebuildRepo = nil
|
local savedRebuildRepo = nil
|
||||||
local savedRebuildArch = nil
|
local savedRebuildArch = nil
|
||||||
local rebuildLoaded = false
|
local rebuildLoaded = false
|
||||||
@@ -275,17 +276,25 @@ loadPackageFiles = function(pkgDir, pkg)
|
|||||||
end, 2000)
|
end, 2000)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function loadRebuildArches(project, pkg)
|
local function loadRebuildArches(project, pkg)
|
||||||
rebuildLoaded = false
|
rebuildLoaded = false
|
||||||
|
rebuildRepos = {}
|
||||||
|
repoMap = {}
|
||||||
|
statusMap = {}
|
||||||
noctalia.runAsync("osc results " .. shellQuote(project) .. " " .. shellQuote(pkg), function(result)
|
noctalia.runAsync("osc results " .. shellQuote(project) .. " " .. shellQuote(pkg), function(result)
|
||||||
local map = {}
|
local map = {}
|
||||||
|
local statuses = {}
|
||||||
if result.exitCode == 0 then
|
if result.exitCode == 0 then
|
||||||
for line in tostring(result.stdout):gmatch("[^\n]+") do
|
for line in tostring(result.stdout):gmatch("[^\n]+") do
|
||||||
line = noctalia.string.trim(line)
|
line = noctalia.string.trim(line)
|
||||||
local repo, arch, _, status = line:match("^(%S+)%s+(%S+)%s+(%S+)%s+(%S+)")
|
local repo, arch, _, status = line:match("^(%S+)%s+(%S+)%s+(%S+)%s+(%S+)")
|
||||||
|
if status then
|
||||||
|
status = status:gsub("%*$", "")
|
||||||
|
end
|
||||||
if repo ~= nil and arch ~= nil and status ~= "disabled" then
|
if repo ~= nil and arch ~= nil and status ~= "disabled" then
|
||||||
if not map[repo] then
|
if not map[repo] then
|
||||||
map[repo] = {}
|
map[repo] = {}
|
||||||
|
statuses[repo] = {}
|
||||||
end
|
end
|
||||||
local found = false
|
local found = false
|
||||||
for _, a in ipairs(map[repo]) do
|
for _, a in ipairs(map[repo]) do
|
||||||
@@ -297,6 +306,7 @@ local function loadRebuildArches(project, pkg)
|
|||||||
if not found then
|
if not found then
|
||||||
table.insert(map[repo], arch)
|
table.insert(map[repo], arch)
|
||||||
end
|
end
|
||||||
|
statuses[repo][arch] = status
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -309,6 +319,7 @@ local function loadRebuildArches(project, pkg)
|
|||||||
table.sort(map[repo])
|
table.sort(map[repo])
|
||||||
end
|
end
|
||||||
repoMap = map
|
repoMap = map
|
||||||
|
statusMap = statuses
|
||||||
rebuildRepos = repos
|
rebuildRepos = repos
|
||||||
local repoIndex = 0
|
local repoIndex = 0
|
||||||
if savedRebuildRepo then
|
if savedRebuildRepo then
|
||||||
@@ -336,6 +347,50 @@ local function loadRebuildArches(project, pkg)
|
|||||||
end, 15000)
|
end, 15000)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local STATUS_STYLE = {
|
||||||
|
succeeded = { glyph = "check", color = "secondary", labelKey = "status_succeeded" },
|
||||||
|
failure = { glyph = "x", color = "error", labelKey = "status_failed" },
|
||||||
|
broken = { glyph = "alert-circle", color = "error", labelKey = "status_failed" },
|
||||||
|
unresolvable = { glyph = "alert-circle", color = "error", labelKey = "status_unresolvable" },
|
||||||
|
building = { glyph = "loader", color = "primary", labelKey = "status_building" },
|
||||||
|
dispatching = { glyph = "loader", color = "primary", labelKey = "status_building" },
|
||||||
|
scheduled = { glyph = "clock", color = "on_surface_variant", labelKey = "status_scheduled" },
|
||||||
|
signing = { glyph = "lock", color = "warning", labelKey = "status_signing" },
|
||||||
|
blocked = { glyph = "clock", color = "on_surface_variant", labelKey = "status_blocked" },
|
||||||
|
excluded = { glyph = "ban", color = "on_surface_variant", labelKey = "status_excluded" },
|
||||||
|
unbuildable = { glyph = "ban", color = "on_surface_variant", labelKey = "status_unbuildable" },
|
||||||
|
finished = { glyph = "check", color = "secondary", labelKey = "status_succeeded" },
|
||||||
|
unknown = { glyph = "help-circle", color = "on_surface_variant", labelKey = "status_unknown" },
|
||||||
|
}
|
||||||
|
|
||||||
|
local STATUS_GROUPS = {
|
||||||
|
["succeeded"] = "succeeded",
|
||||||
|
["success"] = "succeeded",
|
||||||
|
["finished"] = "succeeded",
|
||||||
|
["failed"] = "failure",
|
||||||
|
["broken"] = "failure",
|
||||||
|
["unresolvable"] = "unresolvable",
|
||||||
|
["building"] = "building",
|
||||||
|
["dispatching"] = "building",
|
||||||
|
["scheduled"] = "scheduled",
|
||||||
|
["signing"] = "signing",
|
||||||
|
["blocked"] = "blocked",
|
||||||
|
["excluded"] = "excluded",
|
||||||
|
["unbuildable"] = "unbuildable",
|
||||||
|
["unknown"] = "unknown",
|
||||||
|
["disabled"] = "disabled",
|
||||||
|
}
|
||||||
|
|
||||||
|
local function statusPresentation(status)
|
||||||
|
status = status or "unknown"
|
||||||
|
local group = STATUS_GROUPS[status] or status
|
||||||
|
local base = STATUS_STYLE[group]
|
||||||
|
if base then
|
||||||
|
return base.glyph, base.color, noctalia.tr("panel." .. base.labelKey), group
|
||||||
|
end
|
||||||
|
return "help-circle", "on_surface_variant", status, "unknown"
|
||||||
|
end
|
||||||
|
|
||||||
local function removeFileLocal(path, pkgDir, pkg)
|
local function removeFileLocal(path, pkgDir, pkg)
|
||||||
noctalia.runAsync("rm " .. shellQuote(path), function(result)
|
noctalia.runAsync("rm " .. shellQuote(path), function(result)
|
||||||
if result.exitCode == 0 then
|
if result.exitCode == 0 then
|
||||||
@@ -831,6 +886,7 @@ function render()
|
|||||||
rebuildRepos = {}
|
rebuildRepos = {}
|
||||||
rebuildRepoIndex = 0
|
rebuildRepoIndex = 0
|
||||||
repoMap = {}
|
repoMap = {}
|
||||||
|
statusMap = {}
|
||||||
savedRebuildRepo = nil
|
savedRebuildRepo = nil
|
||||||
savedRebuildArch = nil
|
savedRebuildArch = nil
|
||||||
rebuildLoaded = false
|
rebuildLoaded = false
|
||||||
@@ -1262,7 +1318,7 @@ function render()
|
|||||||
}),
|
}),
|
||||||
ui.button({
|
ui.button({
|
||||||
key = "rebuild-btn",
|
key = "rebuild-btn",
|
||||||
glyph = rebuildConfirm and "check" or "refresh",
|
glyph = rebuildConfirm and "check" or "analyze-filled",
|
||||||
tooltip = rebuildConfirm and noctalia.tr("panel.confirm_rebuild") or noctalia.tr("panel.rebuild"),
|
tooltip = rebuildConfirm and noctalia.tr("panel.confirm_rebuild") or noctalia.tr("panel.rebuild"),
|
||||||
variant = rebuildConfirm and "secondary" or "default",
|
variant = rebuildConfirm and "secondary" or "default",
|
||||||
onClick = function()
|
onClick = function()
|
||||||
@@ -1297,6 +1353,58 @@ function render()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
table.insert(body, ui.separator({ thickness = 1, spacing = 6 }))
|
||||||
|
|
||||||
|
local statusChildren = {
|
||||||
|
ui.row({ align = "center", gap = 8 }, {
|
||||||
|
ui.glyph({ name = "activity", color = "primary", size = 16 }),
|
||||||
|
ui.label({ text = noctalia.tr("panel.build_status"), fontWeight = "bold", fontSize = 15, flexGrow = 1 }),
|
||||||
|
ui.button({
|
||||||
|
glyph = "refresh",
|
||||||
|
tooltip = noctalia.tr("panel.status_refresh"),
|
||||||
|
variant = "ghost",
|
||||||
|
controlSize = "sm",
|
||||||
|
onClick = function()
|
||||||
|
loadRebuildArches(selectedProject, selectedPackage)
|
||||||
|
end,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
if not rebuildLoaded then
|
||||||
|
table.insert(statusChildren, ui.row({ align = "center", gap = 8, paddingV = 2 }, {
|
||||||
|
ui.glyph({ name = "loader", color = "primary", size = 14 }),
|
||||||
|
ui.label({ text = noctalia.tr("panel.status_loading"), color = "on_surface_variant" }),
|
||||||
|
}))
|
||||||
|
else
|
||||||
|
local hadResults = false
|
||||||
|
for _, repo in ipairs(rebuildRepos) do
|
||||||
|
local archs = repoMap[repo] or {}
|
||||||
|
if #archs > 0 then
|
||||||
|
hadResults = true
|
||||||
|
table.insert(
|
||||||
|
statusChildren,
|
||||||
|
ui.label({ text = repo, fontWeight = "bold", fontSize = 14, color = "on_surface" })
|
||||||
|
)
|
||||||
|
for _, arch in ipairs(archs) do
|
||||||
|
local status = statusMap[repo] and statusMap[repo][arch] or nil
|
||||||
|
local glyph, color, label = statusPresentation(status)
|
||||||
|
table.insert(
|
||||||
|
statusChildren,
|
||||||
|
ui.row({ align = "center", gap = 8, paddingL = 12 }, {
|
||||||
|
ui.glyph({ name = glyph, color = color, size = 14 }),
|
||||||
|
ui.label({ text = arch, fontSize = 13, color = "on_surface", flexGrow = 1 }),
|
||||||
|
ui.label({ text = label, fontSize = 13, color = color }),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if not hadResults then
|
||||||
|
table.insert(statusChildren, ui.label({ text = noctalia.tr("panel.no_builds"), color = "on_surface_variant" }))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
table.insert(body, ui.column({ gap = 4 }, statusChildren))
|
||||||
|
|
||||||
elseif selectedProject then
|
elseif selectedProject then
|
||||||
table.insert(
|
table.insert(
|
||||||
body,
|
body,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
id = "neyfua/obs-integration"
|
id = "neyfua/obs-integration"
|
||||||
name = "OBS Integration"
|
name = "OBS Integration"
|
||||||
version = "1.0.0"
|
version = "1.1.0"
|
||||||
plugin_api = 9
|
plugin_api = 9
|
||||||
author = "neyfua"
|
author = "neyfua"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|||||||
@@ -26,10 +26,6 @@
|
|||||||
"no_files": "No files found.",
|
"no_files": "No files found.",
|
||||||
"no_packages": "No packages found.",
|
"no_packages": "No packages found.",
|
||||||
"no_projects": "No projects found.",
|
"no_projects": "No projects found.",
|
||||||
"osc_missing": "'osc' not found",
|
|
||||||
"osc_missing_hint": "Install the 'osc' package to start using this plugin.",
|
|
||||||
"osc_not_configured": "OBS account not configured",
|
|
||||||
"osc_not_configured_hint": "Create ~/.config/osc/oscrc before browsing projects.",
|
|
||||||
"rebuild": "Rebuild package",
|
"rebuild": "Rebuild package",
|
||||||
"rebuilding_pkg": "Rebuilding package...",
|
"rebuilding_pkg": "Rebuilding package...",
|
||||||
"reload_projects": "Reload projects",
|
"reload_projects": "Reload projects",
|
||||||
@@ -47,6 +43,24 @@
|
|||||||
"service_remote_run": "Service Remote Run",
|
"service_remote_run": "Service Remote Run",
|
||||||
"service_run": "Service Manual Run",
|
"service_run": "Service Manual Run",
|
||||||
"service_run_all": "Service Run All",
|
"service_run_all": "Service Run All",
|
||||||
|
"osc_missing": "'osc' not found",
|
||||||
|
"osc_missing_hint": "Install the 'osc' package to start using this plugin.",
|
||||||
|
"osc_not_configured": "OBS account not configured",
|
||||||
|
"osc_not_configured_hint": "Create ~/.config/osc/oscrc before browsing projects.",
|
||||||
|
"build_status": "Build Status",
|
||||||
|
"status_refresh": "Refresh status",
|
||||||
|
"status_loading": "Loading status...",
|
||||||
|
"no_builds": "No build results.",
|
||||||
|
"status_succeeded": "Succeeded",
|
||||||
|
"status_failed": "Failed",
|
||||||
|
"status_unresolvable": "Unresolvable",
|
||||||
|
"status_building": "Building",
|
||||||
|
"status_scheduled": "Scheduled",
|
||||||
|
"status_signing": "Signing",
|
||||||
|
"status_blocked": "Blocked",
|
||||||
|
"status_excluded": "Excluded",
|
||||||
|
"status_unbuildable": "Unbuildable",
|
||||||
|
"status_unknown": "Unknown",
|
||||||
"update_changes": "Update {name}.changes",
|
"update_changes": "Update {name}.changes",
|
||||||
"update_dir": "Update directory",
|
"update_dir": "Update directory",
|
||||||
"updating_dir": "Updating directory..."
|
"updating_dir": "Updating directory..."
|
||||||
|
|||||||
Reference in New Issue
Block a user