diff --git a/manager/api/endpoint.go b/manager/api/endpoint.go index 3f68a577..c3025df8 100644 --- a/manager/api/endpoint.go +++ b/manager/api/endpoint.go @@ -20,7 +20,7 @@ func listPropletsEndpoint(svc manager.Service) endpoint.Endpoint { return listpropletResponse{}, errors.Join(apiutil.ErrValidation, err) } - proplets, err := svc.ListProplets(ctx, req.offset, req.limit) + proplets, err := svc.ListProplets(ctx, req.offset, req.limit, req.status) if err != nil { return listpropletResponse{}, err } @@ -169,7 +169,7 @@ func listJobsEndpoint(svc manager.Service) endpoint.Endpoint { return listJobResponse{}, errors.Join(apiutil.ErrValidation, err) } - jobs, err := svc.ListJobs(ctx, req.offset, req.limit) + jobs, err := svc.ListJobs(ctx, req.offset, req.limit, req.status) if err != nil { return listJobResponse{}, err } diff --git a/manager/api/requests.go b/manager/api/requests.go index 3be90bbe..9d3f7f2e 100644 --- a/manager/api/requests.go +++ b/manager/api/requests.go @@ -94,6 +94,7 @@ func (e *entityReq) validate() error { type listEntityReq struct { offset, limit uint64 + status string } func (e *listEntityReq) validate() error { diff --git a/manager/api/transport.go b/manager/api/transport.go index faf0e260..1e49cc0c 100644 --- a/manager/api/transport.go +++ b/manager/api/transport.go @@ -309,6 +309,7 @@ func decodeListEntityReq(_ context.Context, r *http.Request) (any, error) { return listEntityReq{ offset: o, limit: l, + status: r.URL.Query().Get("status"), }, nil } diff --git a/manager/manager.go b/manager/manager.go index e38fc7af..6fbd6639 100644 --- a/manager/manager.go +++ b/manager/manager.go @@ -9,7 +9,7 @@ import ( type Service interface { GetProplet(ctx context.Context, propletID string) (proplet.Proplet, error) - ListProplets(ctx context.Context, offset, limit uint64) (proplet.PropletPage, error) + ListProplets(ctx context.Context, offset, limit uint64, status string) (proplet.PropletPage, error) SelectProplet(ctx context.Context, task task.Task) (proplet.Proplet, error) DeleteProplet(ctx context.Context, propletID string) error @@ -18,7 +18,7 @@ type Service interface { CreateJob(ctx context.Context, name string, tasks []task.Task, executionMode string) (string, []task.Task, error) GetTask(ctx context.Context, taskID string) (task.Task, error) GetJob(ctx context.Context, jobID string) ([]task.Task, error) - ListJobs(ctx context.Context, offset, limit uint64) (JobPage, error) + ListJobs(ctx context.Context, offset, limit uint64, status string) (JobPage, error) StartJob(ctx context.Context, jobID string) error StopJob(ctx context.Context, jobID string) error ListTasks(ctx context.Context, offset, limit uint64) (task.TaskPage, error) diff --git a/manager/middleware/logging.go b/manager/middleware/logging.go index 9ef334a3..0cc5ccde 100644 --- a/manager/middleware/logging.go +++ b/manager/middleware/logging.go @@ -42,12 +42,13 @@ func (lm *loggingMiddleware) GetProplet(ctx context.Context, id string) (resp pr return lm.svc.GetProplet(ctx, id) } -func (lm *loggingMiddleware) ListProplets(ctx context.Context, offset, limit uint64) (resp proplet.PropletPage, err error) { +func (lm *loggingMiddleware) ListProplets(ctx context.Context, offset, limit uint64, status string) (resp proplet.PropletPage, err error) { defer func(begin time.Time) { args := []any{ slog.String("duration", time.Since(begin).String()), slog.Uint64("offset", offset), slog.Uint64("limit", limit), + slog.String("status", status), } if err != nil { args = append(args, slog.Any("error", err)) @@ -58,7 +59,7 @@ func (lm *loggingMiddleware) ListProplets(ctx context.Context, offset, limit uin lm.logger.Info("List proplets completed successfully", args...) }(time.Now()) - return lm.svc.ListProplets(ctx, offset, limit) + return lm.svc.ListProplets(ctx, offset, limit, status) } func (lm *loggingMiddleware) SelectProplet(ctx context.Context, t task.Task) (w proplet.Proplet, err error) { @@ -348,12 +349,13 @@ func (lm *loggingMiddleware) GetJob(ctx context.Context, jobID string) (resp []t return lm.svc.GetJob(ctx, jobID) } -func (lm *loggingMiddleware) ListJobs(ctx context.Context, offset, limit uint64) (resp manager.JobPage, err error) { +func (lm *loggingMiddleware) ListJobs(ctx context.Context, offset, limit uint64, status string) (resp manager.JobPage, err error) { defer func(begin time.Time) { args := []any{ slog.String("duration", time.Since(begin).String()), slog.Uint64("offset", offset), slog.Uint64("limit", limit), + slog.String("status", status), } if err != nil { args = append(args, slog.Any("error", err)) @@ -365,7 +367,7 @@ func (lm *loggingMiddleware) ListJobs(ctx context.Context, offset, limit uint64) lm.logger.Info("List jobs completed successfully", args...) }(time.Now()) - return lm.svc.ListJobs(ctx, offset, limit) + return lm.svc.ListJobs(ctx, offset, limit, status) } func (lm *loggingMiddleware) StartJob(ctx context.Context, jobID string) (err error) { diff --git a/manager/middleware/metrics.go b/manager/middleware/metrics.go index c4ae24db..6c8972e8 100644 --- a/manager/middleware/metrics.go +++ b/manager/middleware/metrics.go @@ -35,13 +35,13 @@ func (mm *metricsMiddleware) GetProplet(ctx context.Context, id string) (proplet return mm.svc.GetProplet(ctx, id) } -func (mm *metricsMiddleware) ListProplets(ctx context.Context, offset, limit uint64) (proplet.PropletPage, error) { +func (mm *metricsMiddleware) ListProplets(ctx context.Context, offset, limit uint64, status string) (proplet.PropletPage, error) { defer func(begin time.Time) { mm.counter.With("method", "list-proplets").Add(1) mm.latency.With("method", "list-proplets").Observe(time.Since(begin).Seconds()) }(time.Now()) - return mm.svc.ListProplets(ctx, offset, limit) + return mm.svc.ListProplets(ctx, offset, limit, status) } func (mm *metricsMiddleware) SelectProplet(ctx context.Context, t task.Task) (proplet.Proplet, error) { @@ -170,13 +170,13 @@ func (mm *metricsMiddleware) GetJob(ctx context.Context, jobID string) ([]task.T return mm.svc.GetJob(ctx, jobID) } -func (mm *metricsMiddleware) ListJobs(ctx context.Context, offset, limit uint64) (manager.JobPage, error) { +func (mm *metricsMiddleware) ListJobs(ctx context.Context, offset, limit uint64, status string) (manager.JobPage, error) { defer func(begin time.Time) { mm.counter.With("method", "list-jobs").Add(1) mm.latency.With("method", "list-jobs").Observe(time.Since(begin).Seconds()) }(time.Now()) - return mm.svc.ListJobs(ctx, offset, limit) + return mm.svc.ListJobs(ctx, offset, limit, status) } func (mm *metricsMiddleware) StartJob(ctx context.Context, jobID string) error { diff --git a/manager/middleware/tracing.go b/manager/middleware/tracing.go index 3000a4f0..daa754b8 100644 --- a/manager/middleware/tracing.go +++ b/manager/middleware/tracing.go @@ -30,14 +30,15 @@ func (tm *tracing) GetProplet(ctx context.Context, id string) (resp proplet.Prop return tm.svc.GetProplet(ctx, id) } -func (tm *tracing) ListProplets(ctx context.Context, offset, limit uint64) (resp proplet.PropletPage, err error) { +func (tm *tracing) ListProplets(ctx context.Context, offset, limit uint64, status string) (resp proplet.PropletPage, err error) { ctx, span := tm.tracer.Start(ctx, "list-proplets", trace.WithAttributes( attribute.Int64("offset", int64(offset)), attribute.Int64("limit", int64(limit)), + attribute.String("status", status), )) defer span.End() - return tm.svc.ListProplets(ctx, offset, limit) + return tm.svc.ListProplets(ctx, offset, limit, status) } func (tm *tracing) SelectProplet(ctx context.Context, t task.Task) (resp proplet.Proplet, err error) { @@ -178,14 +179,15 @@ func (tm *tracing) GetJob(ctx context.Context, jobID string) (resp []task.Task, return tm.svc.GetJob(ctx, jobID) } -func (tm *tracing) ListJobs(ctx context.Context, offset, limit uint64) (resp manager.JobPage, err error) { +func (tm *tracing) ListJobs(ctx context.Context, offset, limit uint64, status string) (resp manager.JobPage, err error) { ctx, span := tm.tracer.Start(ctx, "list-jobs", trace.WithAttributes( attribute.Int64("offset", int64(offset)), attribute.Int64("limit", int64(limit)), + attribute.String("status", status), )) defer span.End() - return tm.svc.ListJobs(ctx, offset, limit) + return tm.svc.ListJobs(ctx, offset, limit, status) } func (tm *tracing) StartJob(ctx context.Context, jobID string) (err error) { diff --git a/manager/mocks/service.go b/manager/mocks/service.go index 77f39d3f..2eba3883 100644 --- a/manager/mocks/service.go +++ b/manager/mocks/service.go @@ -1062,8 +1062,8 @@ func (_c *MockService_GetTaskResults_Call) RunAndReturn(run func(ctx context.Con } // ListJobs provides a mock function for the type MockService -func (_mock *MockService) ListJobs(ctx context.Context, offset uint64, limit uint64) (manager.JobPage, error) { - ret := _mock.Called(ctx, offset, limit) +func (_mock *MockService) ListJobs(ctx context.Context, offset uint64, limit uint64, status string) (manager.JobPage, error) { + ret := _mock.Called(ctx, offset, limit, status) if len(ret) == 0 { panic("no return value specified for ListJobs") @@ -1071,16 +1071,16 @@ func (_mock *MockService) ListJobs(ctx context.Context, offset uint64, limit uin var r0 manager.JobPage var r1 error - if returnFunc, ok := ret.Get(0).(func(context.Context, uint64, uint64) (manager.JobPage, error)); ok { - return returnFunc(ctx, offset, limit) + if returnFunc, ok := ret.Get(0).(func(context.Context, uint64, uint64, string) (manager.JobPage, error)); ok { + return returnFunc(ctx, offset, limit, status) } - if returnFunc, ok := ret.Get(0).(func(context.Context, uint64, uint64) manager.JobPage); ok { - r0 = returnFunc(ctx, offset, limit) + if returnFunc, ok := ret.Get(0).(func(context.Context, uint64, uint64, string) manager.JobPage); ok { + r0 = returnFunc(ctx, offset, limit, status) } else { r0 = ret.Get(0).(manager.JobPage) } - if returnFunc, ok := ret.Get(1).(func(context.Context, uint64, uint64) error); ok { - r1 = returnFunc(ctx, offset, limit) + if returnFunc, ok := ret.Get(1).(func(context.Context, uint64, uint64, string) error); ok { + r1 = returnFunc(ctx, offset, limit, status) } else { r1 = ret.Error(1) } @@ -1096,11 +1096,12 @@ type MockService_ListJobs_Call struct { // - ctx context.Context // - offset uint64 // - limit uint64 -func (_e *MockService_Expecter) ListJobs(ctx interface{}, offset interface{}, limit interface{}) *MockService_ListJobs_Call { - return &MockService_ListJobs_Call{Call: _e.mock.On("ListJobs", ctx, offset, limit)} +// - status string +func (_e *MockService_Expecter) ListJobs(ctx interface{}, offset interface{}, limit interface{}, status interface{}) *MockService_ListJobs_Call { + return &MockService_ListJobs_Call{Call: _e.mock.On("ListJobs", ctx, offset, limit, status)} } -func (_c *MockService_ListJobs_Call) Run(run func(ctx context.Context, offset uint64, limit uint64)) *MockService_ListJobs_Call { +func (_c *MockService_ListJobs_Call) Run(run func(ctx context.Context, offset uint64, limit uint64, status string)) *MockService_ListJobs_Call { _c.Call.Run(func(args mock.Arguments) { var arg0 context.Context if args[0] != nil { @@ -1114,10 +1115,15 @@ func (_c *MockService_ListJobs_Call) Run(run func(ctx context.Context, offset ui if args[2] != nil { arg2 = args[2].(uint64) } + var arg3 string + if args[3] != nil { + arg3 = args[3].(string) + } run( arg0, arg1, arg2, + arg3, ) }) return _c @@ -1128,14 +1134,14 @@ func (_c *MockService_ListJobs_Call) Return(jobPage manager.JobPage, err error) return _c } -func (_c *MockService_ListJobs_Call) RunAndReturn(run func(ctx context.Context, offset uint64, limit uint64) (manager.JobPage, error)) *MockService_ListJobs_Call { +func (_c *MockService_ListJobs_Call) RunAndReturn(run func(ctx context.Context, offset uint64, limit uint64, status string) (manager.JobPage, error)) *MockService_ListJobs_Call { _c.Call.Return(run) return _c } // ListProplets provides a mock function for the type MockService -func (_mock *MockService) ListProplets(ctx context.Context, offset uint64, limit uint64) (proplet.PropletPage, error) { - ret := _mock.Called(ctx, offset, limit) +func (_mock *MockService) ListProplets(ctx context.Context, offset uint64, limit uint64, status string) (proplet.PropletPage, error) { + ret := _mock.Called(ctx, offset, limit, status) if len(ret) == 0 { panic("no return value specified for ListProplets") @@ -1143,16 +1149,16 @@ func (_mock *MockService) ListProplets(ctx context.Context, offset uint64, limit var r0 proplet.PropletPage var r1 error - if returnFunc, ok := ret.Get(0).(func(context.Context, uint64, uint64) (proplet.PropletPage, error)); ok { - return returnFunc(ctx, offset, limit) + if returnFunc, ok := ret.Get(0).(func(context.Context, uint64, uint64, string) (proplet.PropletPage, error)); ok { + return returnFunc(ctx, offset, limit, status) } - if returnFunc, ok := ret.Get(0).(func(context.Context, uint64, uint64) proplet.PropletPage); ok { - r0 = returnFunc(ctx, offset, limit) + if returnFunc, ok := ret.Get(0).(func(context.Context, uint64, uint64, string) proplet.PropletPage); ok { + r0 = returnFunc(ctx, offset, limit, status) } else { r0 = ret.Get(0).(proplet.PropletPage) } - if returnFunc, ok := ret.Get(1).(func(context.Context, uint64, uint64) error); ok { - r1 = returnFunc(ctx, offset, limit) + if returnFunc, ok := ret.Get(1).(func(context.Context, uint64, uint64, string) error); ok { + r1 = returnFunc(ctx, offset, limit, status) } else { r1 = ret.Error(1) } @@ -1168,11 +1174,12 @@ type MockService_ListProplets_Call struct { // - ctx context.Context // - offset uint64 // - limit uint64 -func (_e *MockService_Expecter) ListProplets(ctx interface{}, offset interface{}, limit interface{}) *MockService_ListProplets_Call { - return &MockService_ListProplets_Call{Call: _e.mock.On("ListProplets", ctx, offset, limit)} +// - status string +func (_e *MockService_Expecter) ListProplets(ctx interface{}, offset interface{}, limit interface{}, status interface{}) *MockService_ListProplets_Call { + return &MockService_ListProplets_Call{Call: _e.mock.On("ListProplets", ctx, offset, limit, status)} } -func (_c *MockService_ListProplets_Call) Run(run func(ctx context.Context, offset uint64, limit uint64)) *MockService_ListProplets_Call { +func (_c *MockService_ListProplets_Call) Run(run func(ctx context.Context, offset uint64, limit uint64, status string)) *MockService_ListProplets_Call { _c.Call.Run(func(args mock.Arguments) { var arg0 context.Context if args[0] != nil { @@ -1186,10 +1193,15 @@ func (_c *MockService_ListProplets_Call) Run(run func(ctx context.Context, offse if args[2] != nil { arg2 = args[2].(uint64) } + var arg3 string + if args[3] != nil { + arg3 = args[3].(string) + } run( arg0, arg1, arg2, + arg3, ) }) return _c @@ -1200,7 +1212,7 @@ func (_c *MockService_ListProplets_Call) Return(propletPage proplet.PropletPage, return _c } -func (_c *MockService_ListProplets_Call) RunAndReturn(run func(ctx context.Context, offset uint64, limit uint64) (proplet.PropletPage, error)) *MockService_ListProplets_Call { +func (_c *MockService_ListProplets_Call) RunAndReturn(run func(ctx context.Context, offset uint64, limit uint64, status string) (proplet.PropletPage, error)) *MockService_ListProplets_Call { _c.Call.Return(run) return _c } diff --git a/manager/service.go b/manager/service.go index c06d40f1..8b3b60d8 100644 --- a/manager/service.go +++ b/manager/service.go @@ -39,6 +39,14 @@ const ( ExecutionModeConfigurable = "configurable" EnvJobExecutionMode = "JOB_EXECUTION_MODE" shutdownTaskStopWait = 200 * time.Millisecond + + PropletStatusActive = "active" + PropletStatusInactive = "inactive" + + JobStatusPending = "pending" + JobStatusRunning = "running" + JobStatusCompleted = "completed" + JobStatusFailed = "failed" ) var ( @@ -112,25 +120,65 @@ func (svc *service) GetProplet(ctx context.Context, propletID string) (proplet.P return w, nil } -func (svc *service) ListProplets(ctx context.Context, offset, limit uint64) (proplet.PropletPage, error) { - proplets, total, err := svc.propletRepo.List(ctx, offset, limit) +func (svc *service) ListProplets(ctx context.Context, offset, limit uint64, status string) (proplet.PropletPage, error) { + if status != "" && status != PropletStatusActive && status != PropletStatusInactive { + return proplet.PropletPage{}, fmt.Errorf("invalid proplet status filter %q: must be %q, %q, or empty", status, PropletStatusActive, PropletStatusInactive) + } + + if status == "" { + proplets, total, err := svc.propletRepo.List(ctx, offset, limit) + if err != nil { + return proplet.PropletPage{}, err + } + for i := range proplets { + proplets[i].SetAlive() + } + + return proplet.PropletPage{ + Offset: offset, + Limit: limit, + Total: total, + Proplets: proplets, + }, nil + } + + all, err := svc.listAllProplets(ctx) if err != nil { return proplet.PropletPage{}, err } - for i := range proplets { - proplets[i].SetAlive() + + filtered := make([]proplet.Proplet, 0, len(all)) + for i := range all { + all[i].SetAlive() + match := (status == PropletStatusActive && all[i].Alive) || + (status == PropletStatusInactive && !all[i].Alive) + if match { + filtered = append(filtered, all[i]) + } } + total := uint64(len(filtered)) + if offset >= total { + return proplet.PropletPage{ + Offset: offset, + Limit: limit, + Total: total, + Proplets: []proplet.Proplet{}, + }, nil + } + + end := min(offset+limit, total) + return proplet.PropletPage{ Offset: offset, Limit: limit, Total: total, - Proplets: proplets, + Proplets: filtered[offset:end], }, nil } func (svc *service) SelectProplet(ctx context.Context, t task.Task) (proplet.Proplet, error) { - proplets, err := svc.ListProplets(ctx, defOffset, defLimit) + proplets, err := svc.ListProplets(ctx, defOffset, defLimit, "") if err != nil { return proplet.Proplet{}, err } @@ -338,7 +386,11 @@ func (svc *service) GetJob(ctx context.Context, jobID string) ([]task.Task, erro return svc.getJobTasks(ctx, jobID) } -func (svc *service) ListJobs(ctx context.Context, offset, limit uint64) (JobPage, error) { +func (svc *service) ListJobs(ctx context.Context, offset, limit uint64, status string) (JobPage, error) { + if status != "" && status != JobStatusPending && status != JobStatusRunning && status != JobStatusCompleted && status != JobStatusFailed { + return JobPage{}, fmt.Errorf("invalid job status filter %q: must be %q, %q, %q, %q, or empty", status, JobStatusPending, JobStatusRunning, JobStatusCompleted, JobStatusFailed) + } + jobs := make([]JobSummary, 0) seen := make(map[string]struct{}) @@ -393,6 +445,23 @@ func (svc *service) ListJobs(ctx context.Context, offset, limit uint64) (JobPage } }) + if status != "" { + statusStateMap := map[string]task.State{ + JobStatusPending: task.Pending, + JobStatusRunning: task.Running, + JobStatusCompleted: task.Completed, + JobStatusFailed: task.Failed, + } + targetState := statusStateMap[status] + filtered := make([]JobSummary, 0, len(jobs)) + for i := range jobs { + if jobs[i].State == targetState { + filtered = append(filtered, jobs[i]) + } + } + jobs = filtered + } + total := uint64(len(jobs)) if offset >= total { return JobPage{ @@ -1663,6 +1732,26 @@ func (svc *service) listAllTasks(ctx context.Context) ([]task.Task, error) { return allTasks, nil } +func (svc *service) listAllProplets(ctx context.Context) ([]proplet.Proplet, error) { + const pageSize uint64 = 100 + var allProplets []proplet.Proplet + var offset uint64 + + for { + proplets, total, err := svc.propletRepo.List(ctx, offset, pageSize) + if err != nil { + return nil, err + } + allProplets = append(allProplets, proplets...) + offset += uint64(len(proplets)) + if offset >= total || len(proplets) == 0 { + break + } + } + + return allProplets, nil +} + func (svc *service) pinTaskToProplet(ctx context.Context, taskID, propletID string) error { return svc.taskPropletRepo.Create(ctx, taskID, propletID) } diff --git a/manager/service_job_test.go b/manager/service_job_test.go index 9b01ab55..546ad0e0 100644 --- a/manager/service_job_test.go +++ b/manager/service_job_test.go @@ -66,7 +66,7 @@ func TestListJobs(t *testing.T) { }, "sequential") require.NoError(t, err) - page, err := svc.ListJobs(context.Background(), 0, 100) + page, err := svc.ListJobs(context.Background(), 0, 100, "") require.NoError(t, err) assert.Equal(t, uint64(2), page.Total) assert.Len(t, page.Jobs, 2) @@ -88,7 +88,7 @@ func TestListJobsIncludesLegacyTaskOnlyJob(t *testing.T) { }) require.NoError(t, err) - page, err := svc.ListJobs(context.Background(), 0, 100) + page, err := svc.ListJobs(context.Background(), 0, 100, "") require.NoError(t, err) assert.Equal(t, uint64(2), page.Total) @@ -198,12 +198,12 @@ func TestListJobsPagination(t *testing.T) { require.NoError(t, err) } - page, err := svc.ListJobs(context.Background(), 0, 3) + page, err := svc.ListJobs(context.Background(), 0, 3, "") require.NoError(t, err) assert.Equal(t, uint64(5), page.Total) assert.Len(t, page.Jobs, 3) - page2, err := svc.ListJobs(context.Background(), 3, 3) + page2, err := svc.ListJobs(context.Background(), 3, 3, "") require.NoError(t, err) assert.Len(t, page2.Jobs, 2) } @@ -265,3 +265,60 @@ func TestComputeJobState(t *testing.T) { }) } } + +func TestListJobsFilterByStatus(t *testing.T) { + t.Parallel() + svc := newService(t) + _, _, err := svc.CreateJob(context.Background(), "pending-job", []task.Task{ + {Name: "p1", State: task.Pending}, + }, "parallel") + require.NoError(t, err) + + _, _, err = svc.CreateJob(context.Background(), "running-job", []task.Task{ + {Name: "r1", State: task.Running}, + }, "parallel") + require.NoError(t, err) + + _, _, err = svc.CreateJob(context.Background(), "completed-job", []task.Task{ + {Name: "c1", State: task.Completed}, + }, "parallel") + require.NoError(t, err) + + _, _, err = svc.CreateJob(context.Background(), "failed-job", []task.Task{ + {Name: "f1", State: task.Failed}, + }, "parallel") + require.NoError(t, err) + + all, err := svc.ListJobs(context.Background(), 0, 100, "") + require.NoError(t, err) + assert.Equal(t, uint64(4), all.Total) + + page, err := svc.ListJobs(context.Background(), 0, 100, "pending") + require.NoError(t, err) + assert.Equal(t, uint64(1), page.Total) + assert.Equal(t, task.Pending, page.Jobs[0].State) + + page, err = svc.ListJobs(context.Background(), 0, 100, "running") + require.NoError(t, err) + assert.Equal(t, uint64(1), page.Total) + assert.Equal(t, task.Running, page.Jobs[0].State) + + page, err = svc.ListJobs(context.Background(), 0, 100, "completed") + require.NoError(t, err) + assert.Equal(t, uint64(1), page.Total) + assert.Equal(t, task.Completed, page.Jobs[0].State) + + page, err = svc.ListJobs(context.Background(), 0, 100, "failed") + require.NoError(t, err) + assert.Equal(t, uint64(1), page.Total) + assert.Equal(t, task.Failed, page.Jobs[0].State) +} + +func TestListJobsInvalidStatusFilter(t *testing.T) { + t.Parallel() + svc := newService(t) + + _, err := svc.ListJobs(context.Background(), 0, 100, "invalid") + require.Error(t, err) + assert.Contains(t, err.Error(), "invalid job status filter") +} diff --git a/pkg/sdk/sdk.go b/pkg/sdk/sdk.go index c3cb6b28..d870d26c 100644 --- a/pkg/sdk/sdk.go +++ b/pkg/sdk/sdk.go @@ -88,11 +88,13 @@ type SDK interface { // job, _ := sdk.GetJob("b1d10738-c5d7-4ff1-8f4d-b9328ce6f040") GetJob(jobID string) (JobResponse, error) - // ListJobs lists jobs. + // ListJobs lists jobs with optional status filter. + // Status can be "pending", "running", "completed", "failed", or "" (all). // // example: - // jobPage, _ := sdk.ListJobs(0, 10) - ListJobs(offset uint64, limit uint64) (JobPage, error) + // jobPage, _ := sdk.ListJobs(0, 10, "") + // jobPage, _ := sdk.ListJobs(0, 10, "running") + ListJobs(offset uint64, limit uint64, status string) (JobPage, error) // StartJob starts a job. // diff --git a/pkg/sdk/task.go b/pkg/sdk/task.go index 47ab7321..ced78498 100644 --- a/pkg/sdk/task.go +++ b/pkg/sdk/task.go @@ -215,7 +215,7 @@ func (sdk *propSDK) GetJob(jobID string) (JobResponse, error) { return jr, nil } -func (sdk *propSDK) ListJobs(offset, limit uint64) (JobPage, error) { +func (sdk *propSDK) ListJobs(offset, limit uint64, status string) (JobPage, error) { queries := make([]string, 0) if offset > 0 { queries = append(queries, fmt.Sprintf("offset=%d", offset)) @@ -223,6 +223,9 @@ func (sdk *propSDK) ListJobs(offset, limit uint64) (JobPage, error) { if limit > 0 { queries = append(queries, fmt.Sprintf("limit=%d", limit)) } + if status != "" { + queries = append(queries, "status="+status) + } query := "" if len(queries) > 0 { query = "?" + strings.Join(queries, "&")