mirror of
https://gitea.com/gitea/tea.git
synced 2026-10-26 19:11:25 +01:00
fix(run): filter runs by creation time and report unstarted times as null
run list --since/--until compared the start time, which queued runs report as the Unix epoch, so every queued run was dropped. Filter on created_at, stop scanning at the first older run, and report a run's missing start or completion time as null instead of 1970-01-01. Signed-off-by: bircni <bircni@icloud.com>
This commit is contained in:
3 files changed
+56
-19
No files matched your search
@@ -158,7 +158,7 @@ func (a *application) runCommand() *cobra.Command {
|
|||||||
return writeList(a, cmd, items, runRow)
|
return writeList(a, cmd, items, runRow)
|
||||||
}}
|
}}
|
||||||
pageFlags(list, &filters.Limit, &filters.Page)
|
pageFlags(list, &filters.Limit, &filters.Page)
|
||||||
started.register(list, "since", "Runs started at or after a date, RFC3339 timestamp, or duration ago (24h)", "until", "Runs started at or before a date, RFC3339 timestamp, or duration ago")
|
started.register(list, "since", "Runs created at or after a date, RFC3339 timestamp, or duration ago (24h)", "until", "Runs created at or before a date, RFC3339 timestamp, or duration ago")
|
||||||
list.Flags().StringVar(&filters.Branch, "branch", "", "Filter by branch name")
|
list.Flags().StringVar(&filters.Branch, "branch", "", "Filter by branch name")
|
||||||
list.Flags().StringVar(&filters.Event, "event", "", "Filter by triggering event")
|
list.Flags().StringVar(&filters.Event, "event", "", "Filter by triggering event")
|
||||||
list.Flags().StringVar(&filters.Actor, "actor", "", "Filter by triggering username")
|
list.Flags().StringVar(&filters.Actor, "actor", "", "Filter by triggering username")
|
||||||
|
|||||||
@@ -28,12 +28,13 @@ func workflowFrom(workflow *gitea.ActionsWorkflow) Workflow {
|
|||||||
return Workflow{ID: workflow.ID, Name: workflow.Name, Path: workflow.Path, State: workflow.State}
|
return Workflow{ID: workflow.ID, Name: workflow.Name, Path: workflow.Path, State: workflow.State}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run is the stable command representation of an Actions run.
|
// Run is the stable command representation of an Actions run. StartedAt and CompletedAt are nil
|
||||||
|
// until the run starts and completes.
|
||||||
type Run struct {
|
type Run struct {
|
||||||
CreatedAt string `json:"created_at"`
|
CreatedAt string `json:"created_at"`
|
||||||
UpdatedAt string `json:"updated_at"`
|
UpdatedAt string `json:"updated_at"`
|
||||||
StartedAt string `json:"started_at"`
|
StartedAt *string `json:"started_at"`
|
||||||
CompletedAt string `json:"completed_at"`
|
CompletedAt *string `json:"completed_at"`
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
@@ -67,11 +68,21 @@ type runResponse struct {
|
|||||||
func (r runResponse) result() Run {
|
func (r runResponse) result() Run {
|
||||||
return Run{
|
return Run{
|
||||||
ID: r.ID, Title: r.DisplayTitle, Status: r.Status, Conclusion: r.Conclusion, URL: r.HTMLURL, Branch: r.HeadBranch,
|
ID: r.ID, Title: r.DisplayTitle, Status: r.Status, Conclusion: r.Conclusion, URL: r.HTMLURL, Branch: r.HeadBranch,
|
||||||
CreatedAt: r.CreatedAt, UpdatedAt: r.UpdatedAt, StartedAt: r.StartedAt, CompletedAt: r.CompletedAt,
|
CreatedAt: r.CreatedAt, UpdatedAt: r.UpdatedAt, StartedAt: runTime(r.StartedAt), CompletedAt: runTime(r.CompletedAt),
|
||||||
Event: r.Event, Actor: r.Actor.Login, Commit: r.HeadSHA, Attempt: r.RunAttempt,
|
Event: r.Event, Actor: r.Actor.Login, Commit: r.HeadSHA, Attempt: r.RunAttempt,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// runTime returns an Actions timestamp, or nil for the zero or Unix epoch time Gitea reports
|
||||||
|
// for a run or job that has not started or completed.
|
||||||
|
func runTime(raw string) *string {
|
||||||
|
t, err := time.Parse(time.RFC3339Nano, raw)
|
||||||
|
if raw == "" || (err == nil && t.Unix() <= 0) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &raw
|
||||||
|
}
|
||||||
|
|
||||||
// DispatchResult identifies a workflow run created by dispatch when the server returns details.
|
// DispatchResult identifies a workflow run created by dispatch when the server returns details.
|
||||||
type DispatchResult struct {
|
type DispatchResult struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
@@ -171,14 +182,15 @@ func (s Actions) Runs(ctx context.Context, opts RunListOptions) ([]Run, error) {
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
for _, item := range items {
|
for _, item := range items {
|
||||||
if item.StartedAt == "" {
|
created, err := time.Parse(time.RFC3339Nano, item.CreatedAt)
|
||||||
continue
|
|
||||||
}
|
|
||||||
started, err := time.Parse(time.RFC3339Nano, item.StartedAt)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("server returned an invalid run start timestamp")
|
return nil, errors.New("server returned an invalid run creation timestamp")
|
||||||
}
|
}
|
||||||
if opts.Since != nil && started.Before(*opts.Since) || opts.Until != nil && started.After(*opts.Until) {
|
// Runs are listed newest first, so the rest were created earlier still.
|
||||||
|
if opts.Since != nil && created.Before(*opts.Since) {
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
if opts.Until != nil && created.After(*opts.Until) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if matched >= first {
|
if matched >= first {
|
||||||
|
|||||||
@@ -205,35 +205,60 @@ func TestRunFiltersEncodeServerQueriesAndRejectInvalidStatus(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRunTimeFilteringPaginatesMatchingResults(t *testing.T) {
|
func TestRunTimeFilteringPaginatesMatchingResults(t *testing.T) {
|
||||||
since := time.Date(2028, 1, 1, 0, 0, 0, 0, time.UTC)
|
until := time.Date(2028, 1, 2, 0, 0, 0, 0, time.UTC)
|
||||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
switch r.URL.Query().Get("page") {
|
switch r.URL.Query().Get("page") {
|
||||||
case "1":
|
case "1":
|
||||||
_, _ = w.Write([]byte(`{"workflow_runs":[{"id":1,"started_at":"2027-12-31T00:00:00Z"},{"id":2,"started_at":"2028-01-01T00:00:00Z"}]}`))
|
_, _ = w.Write([]byte(`{"workflow_runs":[{"id":4,"created_at":"2028-01-03T00:00:00Z"},{"id":3,"created_at":"2028-01-01T12:00:00Z"}]}`))
|
||||||
case "2":
|
case "2":
|
||||||
_, _ = w.Write([]byte(`{"workflow_runs":[{"id":3,"started_at":"2028-01-02T00:00:00Z"}]}`))
|
_, _ = w.Write([]byte(`{"workflow_runs":[{"id":2,"created_at":"2028-01-01T00:00:00Z"}]}`))
|
||||||
default:
|
default:
|
||||||
_, _ = w.Write([]byte(`{"workflow_runs":[]}`))
|
_, _ = w.Write([]byte(`{"workflow_runs":[]}`))
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
service := Actions{Client: transport.New(server.URL, "", false), Repository: "owner/repo"}
|
service := Actions{Client: transport.New(server.URL, "", false), Repository: "owner/repo"}
|
||||||
items, err := service.Runs(context.Background(), RunListOptions{Limit: 1, Page: 2, Since: &since})
|
items, err := service.Runs(context.Background(), RunListOptions{Limit: 1, Page: 2, Until: &until})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, items, 1)
|
require.Len(t, items, 1)
|
||||||
require.EqualValues(t, 3, items[0].ID)
|
require.EqualValues(t, 2, items[0].ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRunTimeFilteringStopsAtScanCap(t *testing.T) {
|
func TestRunTimeFilteringUsesCreationTimeAndStopsAtOlderRuns(t *testing.T) {
|
||||||
since := time.Date(2028, 1, 1, 0, 0, 0, 0, time.UTC)
|
since := time.Date(2028, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||||
pages := 0
|
pages := 0
|
||||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
pages++
|
pages++
|
||||||
_, _ = w.Write([]byte(`{"workflow_runs":[{"id":1,"started_at":"2027-12-31T00:00:00Z"}]}`))
|
_, _ = w.Write([]byte(`{"workflow_runs":[
|
||||||
|
{"id":3,"status":"queued","created_at":"2028-01-02T00:00:00Z","started_at":"1970-01-01T00:00:00Z","completed_at":"1970-01-01T00:00:00Z"},
|
||||||
|
{"id":2,"status":"completed","created_at":"2028-01-01T00:00:00Z","started_at":"2028-01-01T00:00:05Z","completed_at":"2028-01-01T00:01:00Z"},
|
||||||
|
{"id":1,"status":"completed","created_at":"2027-12-31T00:00:00Z","started_at":"2027-12-31T00:00:05Z"}]}`))
|
||||||
}))
|
}))
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
service := Actions{Client: transport.New(server.URL, "", false), Repository: "owner/repo"}
|
service := Actions{Client: transport.New(server.URL, "", false), Repository: "owner/repo"}
|
||||||
_, err := service.Runs(context.Background(), RunListOptions{Limit: 1, Page: 1, Since: &since})
|
items, err := service.Runs(context.Background(), RunListOptions{Limit: 30, Page: 1, Since: &since})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 1, pages)
|
||||||
|
require.Len(t, items, 2)
|
||||||
|
require.EqualValues(t, 3, items[0].ID)
|
||||||
|
require.Nil(t, items[0].StartedAt)
|
||||||
|
require.Nil(t, items[0].CompletedAt)
|
||||||
|
require.Equal(t, "2028-01-01T00:00:05Z", *items[1].StartedAt)
|
||||||
|
data, err := json.Marshal(items[0])
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Contains(t, string(data), `"started_at":null,"completed_at":null`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunTimeFilteringStopsAtScanCap(t *testing.T) {
|
||||||
|
until := time.Date(2028, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
pages := 0
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
pages++
|
||||||
|
_, _ = w.Write([]byte(`{"workflow_runs":[{"id":1,"created_at":"2028-12-31T00:00:00Z"}]}`))
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
service := Actions{Client: transport.New(server.URL, "", false), Repository: "owner/repo"}
|
||||||
|
_, err := service.Runs(context.Background(), RunListOptions{Limit: 1, Page: 1, Until: &until})
|
||||||
require.ErrorContains(t, err, "narrow the search")
|
require.ErrorContains(t, err, "narrow the search")
|
||||||
require.Equal(t, maxRunScanPages, pages)
|
require.Equal(t, maxRunScanPages, pages)
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user