mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
feat(coderd): add support for provisioner job id and tag filter (#16556)
This change adds to new filters to the provisionerjobs endpoint, id (array) and tags (map). Updates #15084 Updates #15192 Related #16532
This commit is contained in:
committed by
GitHub
parent
ade0a53ddb
commit
e38bd27183
Generated
+16
@@ -3055,6 +3055,16 @@ const docTemplate = `{
|
||||
"name": "limit",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"format": "uuid",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Filter results by job IDs",
|
||||
"name": "ids",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"enum": [
|
||||
"pending",
|
||||
@@ -3075,6 +3085,12 @@ const docTemplate = `{
|
||||
"description": "Filter results by status",
|
||||
"name": "status",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"description": "Provisioner tags to filter by (JSON of the form {'tag1':'value1','tag2':'value2'})",
|
||||
"name": "tags",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
|
||||
Generated
+16
@@ -2683,6 +2683,16 @@
|
||||
"name": "limit",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"format": "uuid",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Filter results by job IDs",
|
||||
"name": "ids",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"enum": [
|
||||
"pending",
|
||||
@@ -2703,6 +2713,12 @@
|
||||
"description": "Filter results by status",
|
||||
"name": "status",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"description": "Provisioner tags to filter by (JSON of the form {'tag1':'value1','tag2':'value2'})",
|
||||
"name": "tags",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
|
||||
@@ -4170,6 +4170,9 @@ func (q *FakeQuerier) GetProvisionerJobsByOrganizationAndStatusWithQueuePosition
|
||||
if len(arg.IDs) > 0 && !slices.Contains(arg.IDs, job.ID) {
|
||||
continue
|
||||
}
|
||||
if len(arg.Tags) > 0 && !tagsSubset(job.Tags, arg.Tags) {
|
||||
continue
|
||||
}
|
||||
|
||||
row := database.GetProvisionerJobsByOrganizationAndStatusWithQueuePositionAndProvisionerRow{
|
||||
ProvisionerJob: rowQP.ProvisionerJob,
|
||||
|
||||
@@ -6472,6 +6472,7 @@ WHERE
|
||||
($1::uuid IS NULL OR pj.organization_id = $1)
|
||||
AND (COALESCE(array_length($2::uuid[], 1), 0) = 0 OR pj.id = ANY($2::uuid[]))
|
||||
AND (COALESCE(array_length($3::provisioner_job_status[], 1), 0) = 0 OR pj.job_status = ANY($3::provisioner_job_status[]))
|
||||
AND ($4::tagset = 'null'::tagset OR provisioner_tagset_contains(pj.tags::tagset, $4::tagset))
|
||||
GROUP BY
|
||||
pj.id,
|
||||
qp.queue_position,
|
||||
@@ -6486,13 +6487,14 @@ GROUP BY
|
||||
ORDER BY
|
||||
pj.created_at DESC
|
||||
LIMIT
|
||||
$4::int
|
||||
$5::int
|
||||
`
|
||||
|
||||
type GetProvisionerJobsByOrganizationAndStatusWithQueuePositionAndProvisionerParams struct {
|
||||
OrganizationID uuid.NullUUID `db:"organization_id" json:"organization_id"`
|
||||
IDs []uuid.UUID `db:"ids" json:"ids"`
|
||||
Status []ProvisionerJobStatus `db:"status" json:"status"`
|
||||
Tags StringMap `db:"tags" json:"tags"`
|
||||
Limit sql.NullInt32 `db:"limit" json:"limit"`
|
||||
}
|
||||
|
||||
@@ -6515,6 +6517,7 @@ func (q *sqlQuerier) GetProvisionerJobsByOrganizationAndStatusWithQueuePositionA
|
||||
arg.OrganizationID,
|
||||
pq.Array(arg.IDs),
|
||||
pq.Array(arg.Status),
|
||||
arg.Tags,
|
||||
arg.Limit,
|
||||
)
|
||||
if err != nil {
|
||||
|
||||
@@ -158,6 +158,7 @@ WHERE
|
||||
(sqlc.narg('organization_id')::uuid IS NULL OR pj.organization_id = @organization_id)
|
||||
AND (COALESCE(array_length(@ids::uuid[], 1), 0) = 0 OR pj.id = ANY(@ids::uuid[]))
|
||||
AND (COALESCE(array_length(@status::provisioner_job_status[], 1), 0) = 0 OR pj.job_status = ANY(@status::provisioner_job_status[]))
|
||||
AND (@tags::tagset = 'null'::tagset OR provisioner_tagset_contains(pj.tags::tagset, @tags::tagset))
|
||||
GROUP BY
|
||||
pj.id,
|
||||
qp.queue_position,
|
||||
|
||||
@@ -72,7 +72,9 @@ func (api *API) provisionerJob(rw http.ResponseWriter, r *http.Request) {
|
||||
// @Tags Organizations
|
||||
// @Param organization path string true "Organization ID" format(uuid)
|
||||
// @Param limit query int false "Page limit"
|
||||
// @Param ids query []string false "Filter results by job IDs" format(uuid)
|
||||
// @Param status query codersdk.ProvisionerJobStatus false "Filter results by status" enums(pending,running,succeeded,canceling,canceled,failed)
|
||||
// @Param tags query object false "Provisioner tags to filter by (JSON of the form {'tag1':'value1','tag2':'value2'})"
|
||||
// @Success 200 {array} codersdk.ProvisionerJob
|
||||
// @Router /organizations/{organization}/provisionerjobs [get]
|
||||
func (api *API) provisionerJobs(rw http.ResponseWriter, r *http.Request) {
|
||||
@@ -103,6 +105,10 @@ func (api *API) handleAuthAndFetchProvisionerJobs(rw http.ResponseWriter, r *htt
|
||||
p := httpapi.NewQueryParamParser()
|
||||
limit := p.PositiveInt32(qp, 50, "limit")
|
||||
status := p.Strings(qp, nil, "status")
|
||||
if ids == nil {
|
||||
ids = p.UUIDs(qp, nil, "ids")
|
||||
}
|
||||
tagsRaw := p.String(qp, "", "tags")
|
||||
p.ErrorExcessParams(qp)
|
||||
if len(p.Errors) > 0 {
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
@@ -112,11 +118,23 @@ func (api *API) handleAuthAndFetchProvisionerJobs(rw http.ResponseWriter, r *htt
|
||||
return nil, false
|
||||
}
|
||||
|
||||
tags := database.StringMap{}
|
||||
if tagsRaw != "" {
|
||||
if err := tags.Scan([]byte(tagsRaw)); err != nil {
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: "Invalid tags query parameter",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
|
||||
jobs, err := api.Database.GetProvisionerJobsByOrganizationAndStatusWithQueuePositionAndProvisioner(ctx, database.GetProvisionerJobsByOrganizationAndStatusWithQueuePositionAndProvisionerParams{
|
||||
OrganizationID: uuid.NullUUID{UUID: org.ID, Valid: true},
|
||||
Status: slice.StringEnums[database.ProvisionerJobStatus](status),
|
||||
Limit: sql.NullInt32{Int32: limit, Valid: limit > 0},
|
||||
IDs: ids,
|
||||
Tags: tags,
|
||||
})
|
||||
if err != nil {
|
||||
if httpapi.Is404Error(err) {
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -65,9 +66,10 @@ func TestProvisionerJobs(t *testing.T) {
|
||||
})
|
||||
|
||||
// Add more jobs than the default limit.
|
||||
for range 60 {
|
||||
for i := range 60 {
|
||||
dbgen.ProvisionerJob(t, db, nil, database.ProvisionerJob{
|
||||
OrganizationID: owner.OrganizationID,
|
||||
Tags: database.StringMap{"count": strconv.Itoa(i)},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -132,6 +134,16 @@ func TestProvisionerJobs(t *testing.T) {
|
||||
require.Len(t, jobs, 50)
|
||||
})
|
||||
|
||||
t.Run("IDs", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := testutil.Context(t, testutil.WaitMedium)
|
||||
jobs, err := templateAdminClient.OrganizationProvisionerJobs(ctx, owner.OrganizationID, &codersdk.OrganizationProvisionerJobsOptions{
|
||||
IDs: []uuid.UUID{workspace.LatestBuild.Job.ID, version.Job.ID},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, jobs, 2)
|
||||
})
|
||||
|
||||
t.Run("Status", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := testutil.Context(t, testutil.WaitMedium)
|
||||
@@ -142,6 +154,16 @@ func TestProvisionerJobs(t *testing.T) {
|
||||
require.Len(t, jobs, 1)
|
||||
})
|
||||
|
||||
t.Run("Tags", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := testutil.Context(t, testutil.WaitMedium)
|
||||
jobs, err := templateAdminClient.OrganizationProvisionerJobs(ctx, owner.OrganizationID, &codersdk.OrganizationProvisionerJobsOptions{
|
||||
Tags: map[string]string{"count": "1"},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, jobs, 1)
|
||||
})
|
||||
|
||||
t.Run("Limit", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := testutil.Context(t, testutil.WaitMedium)
|
||||
|
||||
@@ -346,7 +346,9 @@ func (c *Client) OrganizationProvisionerDaemons(ctx context.Context, organizatio
|
||||
|
||||
type OrganizationProvisionerJobsOptions struct {
|
||||
Limit int
|
||||
IDs []uuid.UUID
|
||||
Status []ProvisionerJobStatus
|
||||
Tags map[string]string
|
||||
}
|
||||
|
||||
func (c *Client) OrganizationProvisionerJobs(ctx context.Context, organizationID uuid.UUID, opts *OrganizationProvisionerJobsOptions) ([]ProvisionerJob, error) {
|
||||
@@ -355,9 +357,19 @@ func (c *Client) OrganizationProvisionerJobs(ctx context.Context, organizationID
|
||||
if opts.Limit > 0 {
|
||||
qp.Add("limit", strconv.Itoa(opts.Limit))
|
||||
}
|
||||
if len(opts.IDs) > 0 {
|
||||
qp.Add("ids", joinSliceStringer(opts.IDs))
|
||||
}
|
||||
if len(opts.Status) > 0 {
|
||||
qp.Add("status", joinSlice(opts.Status))
|
||||
}
|
||||
if len(opts.Tags) > 0 {
|
||||
tagsRaw, err := json.Marshal(opts.Tags)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("marshal tags: %w", err)
|
||||
}
|
||||
qp.Add("tags", string(tagsRaw))
|
||||
}
|
||||
}
|
||||
|
||||
res, err := c.Request(ctx, http.MethodGet,
|
||||
@@ -401,6 +413,14 @@ func joinSlice[T ~string](s []T) string {
|
||||
return strings.Join(ss, ",")
|
||||
}
|
||||
|
||||
func joinSliceStringer[T fmt.Stringer](s []T) string {
|
||||
var ss []string
|
||||
for _, v := range s {
|
||||
ss = append(ss, v.String())
|
||||
}
|
||||
return strings.Join(ss, ",")
|
||||
}
|
||||
|
||||
// CreateTemplateVersion processes source-code and optionally associates the version with a template.
|
||||
// Executing without a template is useful for validating source-code.
|
||||
func (c *Client) CreateTemplateVersion(ctx context.Context, organizationID uuid.UUID, req CreateTemplateVersionRequest) (TemplateVersion, error) {
|
||||
|
||||
Generated
+7
-5
@@ -359,11 +359,13 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/provisi
|
||||
|
||||
### Parameters
|
||||
|
||||
| Name | In | Type | Required | Description |
|
||||
|----------------|-------|--------------|----------|--------------------------|
|
||||
| `organization` | path | string(uuid) | true | Organization ID |
|
||||
| `limit` | query | integer | false | Page limit |
|
||||
| `status` | query | string | false | Filter results by status |
|
||||
| Name | In | Type | Required | Description |
|
||||
|----------------|-------|--------------|----------|------------------------------------------------------------------------------------|
|
||||
| `organization` | path | string(uuid) | true | Organization ID |
|
||||
| `limit` | query | integer | false | Page limit |
|
||||
| `ids` | query | array(uuid) | false | Filter results by job IDs |
|
||||
| `status` | query | string | false | Filter results by status |
|
||||
| `tags` | query | object | false | Provisioner tags to filter by (JSON of the form {'tag1':'value1','tag2':'value2'}) |
|
||||
|
||||
#### Enumerated Values
|
||||
|
||||
|
||||
Generated
+2
@@ -1438,7 +1438,9 @@ export interface OrganizationMemberWithUserData extends OrganizationMember {
|
||||
// From codersdk/organizations.go
|
||||
export interface OrganizationProvisionerJobsOptions {
|
||||
readonly Limit: number;
|
||||
readonly IDs: readonly string[];
|
||||
readonly Status: readonly ProvisionerJobStatus[];
|
||||
readonly Tags: Record<string, string>;
|
||||
}
|
||||
|
||||
// From codersdk/idpsync.go
|
||||
|
||||
Reference in New Issue
Block a user