mirror of
https://gitea.com/gitea/tea.git
synced 2026-02-22 06:13:32 +01:00
Add tea actions runs and workflows commands (#880)
Implements comprehensive workflow execution tracking for Gitea Actions using tea CLI ## Features ### tea actions runs list - List workflow runs with filtering (status, branch, event, actor, time) - Time filters: relative (24h, 7d) and absolute dates - Status symbols: ✓ success, ✘ failure, ⭮ pending, ⊘ skipped/cancelled, ⚠ blocked - Multiple output formats: table, json, yaml, csv, tsv ### tea actions runs view - View run details with metadata (ID, status, workflow, branch, event, trigger info) - Shows jobs table with status, runner, duration - Optional --jobs flag to toggle jobs display ### tea actions runs delete - Delete/cancel workflow runs with confirmation prompt - Supports --confirm/-y to skip prompt ### tea actions runs logs - View job logs for all jobs or specific job (--job <id>) - **New: --follow/-f flag for real-time log following** (like tail -f) - Polls API every 2 seconds, only shows new content - Auto-detects completion and exits ### tea actions workflows list - List workflow files (.yml and .yaml) in repository - Searches in .gitea/workflows and .github/workflows - Shows active (✓) or inactive (✗) status based on recent runs - Displays workflow name, path, and file size ## Commands `tea actions runs list --status success --since 24h` `tea actions runs view 123` `tea actions runs delete 123 --confirm` `tea actions runs logs 123 --job 456 --follow` `tea actions workflows list` ## Tests - 19 unit tests across all commands - Full test suite passing - Manual testing successful --------- Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com> Co-authored-by: techknowlogick <techknowlogick@gitea.com> Reviewed-on: https://gitea.com/gitea/tea/pulls/880 Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com> Co-authored-by: yousfi saad <yousfi.saad@gmail.com> Co-committed-by: yousfi saad <yousfi.saad@gmail.com>
This commit is contained in:
174
modules/print/actions_runs_test.go
Normal file
174
modules/print/actions_runs_test.go
Normal file
@@ -0,0 +1,174 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package print
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
)
|
||||
|
||||
func TestActionRunsListEmpty(t *testing.T) {
|
||||
// Test with empty runs - should not panic
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
t.Errorf("ActionRunsList panicked with empty list: %v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
ActionRunsList([]*gitea.ActionWorkflowRun{}, "")
|
||||
}
|
||||
|
||||
func TestActionRunsListWithData(t *testing.T) {
|
||||
runs := []*gitea.ActionWorkflowRun{
|
||||
{
|
||||
ID: 1,
|
||||
Status: "success",
|
||||
DisplayTitle: "Test Workflow",
|
||||
HeadBranch: "main",
|
||||
Event: "push",
|
||||
StartedAt: time.Now().Add(-1 * time.Hour),
|
||||
CompletedAt: time.Now().Add(-30 * time.Minute),
|
||||
},
|
||||
{
|
||||
ID: 2,
|
||||
Status: "in_progress",
|
||||
Path: ".gitea/workflows/test.yml",
|
||||
HeadBranch: "feature",
|
||||
Event: "pull_request",
|
||||
StartedAt: time.Now().Add(-10 * time.Minute),
|
||||
},
|
||||
}
|
||||
|
||||
// Test that it doesn't panic with real data
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
t.Errorf("ActionRunsList panicked with data: %v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
ActionRunsList(runs, "")
|
||||
}
|
||||
|
||||
func TestActionRunDetails(t *testing.T) {
|
||||
run := &gitea.ActionWorkflowRun{
|
||||
ID: 123,
|
||||
RunNumber: 42,
|
||||
Status: "success",
|
||||
Conclusion: "success",
|
||||
DisplayTitle: "Build and Test",
|
||||
Path: ".gitea/workflows/ci.yml",
|
||||
HeadBranch: "main",
|
||||
Event: "push",
|
||||
HeadSha: "abc123def456",
|
||||
StartedAt: time.Now().Add(-2 * time.Hour),
|
||||
CompletedAt: time.Now().Add(-1 * time.Hour),
|
||||
RunAttempt: 1,
|
||||
Actor: &gitea.User{
|
||||
UserName: "testuser",
|
||||
},
|
||||
HTMLURL: "https://gitea.example.com/owner/repo/actions/runs/123",
|
||||
}
|
||||
|
||||
// Test that it doesn't panic
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
t.Errorf("ActionRunDetails panicked: %v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
ActionRunDetails(run)
|
||||
}
|
||||
|
||||
func TestActionWorkflowJobsListEmpty(t *testing.T) {
|
||||
// Test with empty jobs - should not panic
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
t.Errorf("ActionWorkflowJobsList panicked with empty list: %v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
ActionWorkflowJobsList([]*gitea.ActionWorkflowJob{}, "")
|
||||
}
|
||||
|
||||
func TestActionWorkflowJobsListWithData(t *testing.T) {
|
||||
jobs := []*gitea.ActionWorkflowJob{
|
||||
{
|
||||
ID: 1,
|
||||
Name: "build",
|
||||
Status: "success",
|
||||
RunnerName: "runner-1",
|
||||
StartedAt: time.Now().Add(-30 * time.Minute),
|
||||
CompletedAt: time.Now().Add(-20 * time.Minute),
|
||||
},
|
||||
{
|
||||
ID: 2,
|
||||
Name: "test",
|
||||
Status: "in_progress",
|
||||
RunnerName: "runner-2",
|
||||
StartedAt: time.Now().Add(-5 * time.Minute),
|
||||
},
|
||||
}
|
||||
|
||||
// Test that it doesn't panic with real data
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
t.Errorf("ActionWorkflowJobsList panicked with data: %v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
ActionWorkflowJobsList(jobs, "")
|
||||
}
|
||||
|
||||
func TestFormatDurationMinutes(t *testing.T) {
|
||||
now := time.Now()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
started time.Time
|
||||
completed time.Time
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "zero started",
|
||||
started: time.Time{},
|
||||
completed: now,
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "30 seconds",
|
||||
started: now.Add(-30 * time.Second),
|
||||
completed: now,
|
||||
expected: "30s",
|
||||
},
|
||||
{
|
||||
name: "5 minutes",
|
||||
started: now.Add(-5 * time.Minute),
|
||||
completed: now,
|
||||
expected: "5m",
|
||||
},
|
||||
{
|
||||
name: "in progress (no completed)",
|
||||
started: now.Add(-1 * time.Hour),
|
||||
completed: time.Time{},
|
||||
expected: "1h0m",
|
||||
},
|
||||
{
|
||||
name: "2 hours 30 minutes",
|
||||
started: now.Add(-150 * time.Minute),
|
||||
completed: now,
|
||||
expected: "2h30m",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
result := formatDurationMinutes(test.started, test.completed)
|
||||
if result != test.expected {
|
||||
t.Errorf("formatDurationMinutes() = %q, want %q", result, test.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user