diff --git a/CHANGELOG.md b/CHANGELOG.md index edf23d61a..c52d3f11c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * Adds the `IsUnified` field to `Project`, `Organization` and `Team` by @roncodingenthusiast [#915](https://github.com/hashicorp/go-tfe/pull/915) * Adds Workspace auto-destroy notification types to `NotificationTriggerType` by @notchairmk [#918](https://github.com/hashicorp/go-tfe/pull/918) +* Adds `CreatedAfter` and `CreatedBefore` Date Time filters to `AdminRunsListOptions` by @maed223 [#916](https://github.com/hashicorp/go-tfe/pull/916) # v1.56.0 diff --git a/admin_run.go b/admin_run.go index 4dc391326..adfac0378 100644 --- a/admin_run.go +++ b/admin_run.go @@ -61,8 +61,10 @@ const ( type AdminRunsListOptions struct { ListOptions - RunStatus string `url:"filter[status],omitempty"` - Query string `url:"q,omitempty"` + RunStatus string `url:"filter[status],omitempty"` + CreatedBefore string `url:"filter[to],omitempty"` + CreatedAfter string `url:"filter[from],omitempty"` + Query string `url:"q,omitempty"` // Optional: A list of relations to include. See available resources // https://developer.hashicorp.com/terraform/enterprise/api-docs/admin/runs#available-related-resources Include []AdminRunIncludeOpt `url:"include,omitempty"` @@ -123,6 +125,10 @@ func (o *AdminRunsListOptions) valid() error { return nil } + if err := validateAdminRunDateRanges(o.CreatedBefore, o.CreatedAfter); err != nil { + return err + } + if err := validateAdminRunFilterParams(o.RunStatus); err != nil { return err } @@ -130,6 +136,24 @@ func (o *AdminRunsListOptions) valid() error { return nil } +func validateAdminRunDateRanges(before, after string) error { + if validString(&before) { + _, err := time.Parse(time.RFC3339, before) + if err != nil { + return fmt.Errorf("invalid date format for CreatedBefore: '%s', must be in RFC3339 format", before) + } + } + + if validString(&after) { + _, err := time.Parse(time.RFC3339, after) + if err != nil { + return fmt.Errorf("invalid date format for CreatedAfter: '%s', must be in RFC3339 format", after) + } + } + + return nil +} + func validateAdminRunFilterParams(runStatus string) error { // For the platform, an invalid filter value is a semantically understood query that returns an empty set, no error, no warning. But for go-tfe, an invalid value is good enough reason to error prior to a network call to the platform: if validString(&runStatus) { diff --git a/admin_run_integration_test.go b/admin_run_integration_test.go index b52256099..183e585db 100644 --- a/admin_run_integration_test.go +++ b/admin_run_integration_test.go @@ -227,6 +227,60 @@ func TestAdminRuns_ForceCancel(t *testing.T) { }) } +func TestAdminRuns_ListFilterByDates(t *testing.T) { + skipUnlessEnterprise(t) + + client := testClient(t) + ctx := context.Background() + + org, orgCleanup := createOrganization(t, client) + defer orgCleanup() + + wTest, wTestCleanup := createWorkspace(t, client, org) + defer wTestCleanup() + + timestamp1 := time.Now().Format(time.RFC3339) + rTest1, rCleanup1 := createRun(t, client, wTest) + defer rCleanup1() + + rTest2, rCleanup2 := createRun(t, client, wTest) + defer rCleanup2() + timestamp2 := time.Now().Format(time.RFC3339) + + _, rCleanup3 := createRun(t, client, wTest) + defer rCleanup3() + timestamp3 := time.Now().Format(time.RFC3339) + + t.Run("has valid date ranges", func(t *testing.T) { + rl, err := client.Admin.Runs.List(ctx, &AdminRunsListOptions{ + CreatedAfter: timestamp1, + CreatedBefore: timestamp2, + }) + + require.NoError(t, err) + assert.Equal(t, 2, len(rl.Items)) + assert.Equal(t, adminRunItemsContainsID(rl.Items, rTest1.ID), true) + assert.Equal(t, adminRunItemsContainsID(rl.Items, rTest2.ID), true) + }) + + t.Run("has no items when CreatedAfter and CreatedBefore datetimes has no overlap", func(t *testing.T) { + rl, err := client.Admin.Runs.List(ctx, &AdminRunsListOptions{ + CreatedAfter: timestamp3, + CreatedBefore: timestamp2, + }) + + require.NoError(t, err) + assert.Equal(t, 0, len(rl.Items)) + }) + + t.Run("errors with invalid input for CreatedAfter", func(t *testing.T) { + _, err := client.Admin.Runs.List(ctx, &AdminRunsListOptions{ + CreatedAfter: "invalid", + }) + assert.Error(t, err) + }) +} + func TestAdminRuns_AdminRunsListOptions_valid(t *testing.T) { skipUnlessEnterprise(t)