feat: add deleted_at field to workspace model (#7475)

* added impending_deletion workspace field

* gen docs

* update golden files

* added test

* PR comments
This commit is contained in:
Kira Pilot 2023-05-11 08:47:53 -07:00 committed by GitHub
parent fe0e94ece9
commit ae3473dc1b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 149 additions and 21 deletions

View File

@ -20,6 +20,8 @@
"codersdk",
"cronstrue",
"databasefake",
"dbfake",
"dbgen",
"dbtype",
"DERP",
"derphttp",

View File

@ -48,6 +48,7 @@
"name": "test-workspace",
"autostart_schedule": "CRON_TZ=US/Central 30 9 * * 1-5",
"ttl_ms": 28800000,
"last_used_at": "[timestamp]"
"last_used_at": "[timestamp]",
"deleting_at": null
}
]

5
coderd/apidoc/docs.go generated
View File

@ -9455,6 +9455,11 @@ const docTemplate = `{
"type": "string",
"format": "date-time"
},
"deleting_at": {
"description": "DeletingAt indicates the time of the upcoming workspace deletion, if applicable; otherwise it is nil.\nWorkspaces may have impending deletions if Template.InactivityTTL feature is turned on and the workspace is inactive.",
"type": "string",
"format": "date-time"
},
"id": {
"type": "string",
"format": "uuid"

View File

@ -8499,6 +8499,11 @@
"type": "string",
"format": "date-time"
},
"deleting_at": {
"description": "DeletingAt indicates the time of the upcoming workspace deletion, if applicable; otherwise it is nil.\nWorkspaces may have impending deletions if Template.InactivityTTL feature is turned on and the workspace is inactive.",
"type": "string",
"format": "date-time"
},
"id": {
"type": "string",
"format": "uuid"

View File

@ -1169,7 +1169,10 @@ func convertWorkspace(
autostartSchedule = &workspace.AutostartSchedule.String
}
ttlMillis := convertWorkspaceTTLMillis(workspace.Ttl)
var (
ttlMillis = convertWorkspaceTTLMillis(workspace.Ttl)
deletingAt = calculateDeletingAt(workspace, template)
)
return codersdk.Workspace{
ID: workspace.ID,
CreatedAt: workspace.CreatedAt,
@ -1188,6 +1191,7 @@ func convertWorkspace(
AutostartSchedule: autostartSchedule,
TTLMillis: ttlMillis,
LastUsedAt: workspace.LastUsedAt,
DeletingAt: deletingAt,
}
}
@ -1200,6 +1204,22 @@ func convertWorkspaceTTLMillis(i sql.NullInt64) *int64 {
return &millis
}
// Calculate the time of the upcoming workspace deletion, if applicable; otherwise, return nil.
// Workspaces may have impending deletions if InactivityTTL feature is turned on and the workspace is inactive.
func calculateDeletingAt(workspace database.Workspace, template database.Template) *time.Time {
var (
year, month, day = time.Now().Date()
beginningOfToday = time.Date(year, month, day, 0, 0, 0, 0, time.Now().Location())
)
// If InactivityTTL is turned off (set to 0), if the workspace has already been deleted,
// or if the workspace was used sometime within the last day, there is no impending deletion
if template.InactivityTTL == 0 || workspace.Deleted || workspace.LastUsedAt.After(beginningOfToday) {
return nil
}
return ptr.Ref(workspace.LastUsedAt.Add(time.Duration(template.InactivityTTL) * time.Nanosecond))
}
func validWorkspaceTTLMillis(millis *int64, templateDefault, templateMax time.Duration) (sql.NullInt64, error) {
if templateDefault == 0 && templateMax != 0 || (templateMax > 0 && templateDefault > templateMax) {
templateDefault = templateMax

View File

@ -0,0 +1,82 @@
package coderd
import (
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/coder/coder/coderd/database"
"github.com/coder/coder/coderd/util/ptr"
)
func Test_calculateDeletingAt(t *testing.T) {
t.Parallel()
testCases := []struct {
name string
workspace database.Workspace
template database.Template
expected *time.Time
}{
{
name: "DeletingAt",
workspace: database.Workspace{
Deleted: false,
LastUsedAt: time.Now().Add(time.Duration(-10) * time.Hour * 24), // 10 days ago
},
template: database.Template{
InactivityTTL: int64(9 * 24 * time.Hour), // 9 days
},
expected: ptr.Ref(time.Now().Add(time.Duration(-1) * time.Hour * 24)), // yesterday
},
{
name: "InactivityTTLUnset",
workspace: database.Workspace{
Deleted: false,
LastUsedAt: time.Now().Add(time.Duration(-10) * time.Hour * 24),
},
template: database.Template{
InactivityTTL: 0,
},
expected: nil,
},
{
name: "DeletedWorkspace",
workspace: database.Workspace{
Deleted: true,
LastUsedAt: time.Now().Add(time.Duration(-10) * time.Hour * 24),
},
template: database.Template{
InactivityTTL: int64(9 * 24 * time.Hour),
},
expected: nil,
},
{
name: "ActiveWorkspace",
workspace: database.Workspace{
Deleted: true,
LastUsedAt: time.Now().Add(time.Duration(-5) * time.Hour), // 5 hours ago
},
template: database.Template{
InactivityTTL: int64(1 * 24 * time.Hour), // 1 day
},
expected: nil,
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
found := calculateDeletingAt(tc.workspace, tc.template)
if tc.expected == nil {
require.Nil(t, found, "impending deletion should be nil")
} else {
require.NotNil(t, found)
require.WithinDuration(t, *tc.expected, *found, time.Second, "incorrect impending deletion")
}
})
}
}

View File

@ -34,6 +34,10 @@ type Workspace struct {
AutostartSchedule *string `json:"autostart_schedule,omitempty"`
TTLMillis *int64 `json:"ttl_ms,omitempty"`
LastUsedAt time.Time `json:"last_used_at" format:"date-time"`
// DeletingAt indicates the time of the upcoming workspace deletion, if applicable; otherwise it is nil.
// Workspaces may have impending deletions if Template.InactivityTTL feature is turned on and the workspace is inactive.
DeletingAt *time.Time `json:"deleting_at" format:"date-time"`
}
type WorkspacesRequest struct {

View File

@ -4594,6 +4594,7 @@ Parameter represents a set value for the scope.
{
"autostart_schedule": "string",
"created_at": "2019-08-24T14:15:22Z",
"deleting_at": "2019-08-24T14:15:22Z",
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"last_used_at": "2019-08-24T14:15:22Z",
"latest_build": {
@ -4731,25 +4732,26 @@ Parameter represents a set value for the scope.
### Properties
| Name | Type | Required | Restrictions | Description |
| ------------------------------------------- | -------------------------------------------------- | -------- | ------------ | ----------- |
| `autostart_schedule` | string | false | | |
| `created_at` | string | false | | |
| `id` | string | false | | |
| `last_used_at` | string | false | | |
| `latest_build` | [codersdk.WorkspaceBuild](#codersdkworkspacebuild) | false | | |
| `name` | string | false | | |
| `organization_id` | string | false | | |
| `outdated` | boolean | false | | |
| `owner_id` | string | false | | |
| `owner_name` | string | false | | |
| `template_allow_user_cancel_workspace_jobs` | boolean | false | | |
| `template_display_name` | string | false | | |
| `template_icon` | string | false | | |
| `template_id` | string | false | | |
| `template_name` | string | false | | |
| `ttl_ms` | integer | false | | |
| `updated_at` | string | false | | |
| Name | Type | Required | Restrictions | Description |
| ------------------------------------------- | -------------------------------------------------- | -------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `autostart_schedule` | string | false | | |
| `created_at` | string | false | | |
| `deleting_at` | string | false | | Deleting at indicates the time of the upcoming workspace deletion, if applicable; otherwise it is nil. Workspaces may have impending deletions if Template.InactivityTTL feature is turned on and the workspace is inactive. |
| `id` | string | false | | |
| `last_used_at` | string | false | | |
| `latest_build` | [codersdk.WorkspaceBuild](#codersdkworkspacebuild) | false | | |
| `name` | string | false | | |
| `organization_id` | string | false | | |
| `outdated` | boolean | false | | |
| `owner_id` | string | false | | |
| `owner_name` | string | false | | |
| `template_allow_user_cancel_workspace_jobs` | boolean | false | | |
| `template_display_name` | string | false | | |
| `template_icon` | string | false | | |
| `template_id` | string | false | | |
| `template_name` | string | false | | |
| `ttl_ms` | integer | false | | |
| `updated_at` | string | false | | |
## codersdk.WorkspaceAgent
@ -5596,6 +5598,7 @@ Parameter represents a set value for the scope.
{
"autostart_schedule": "string",
"created_at": "2019-08-24T14:15:22Z",
"deleting_at": "2019-08-24T14:15:22Z",
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"last_used_at": "2019-08-24T14:15:22Z",
"latest_build": {

View File

@ -56,6 +56,7 @@ curl -X POST http://coder-server:8080/api/v2/organizations/{organization}/member
{
"autostart_schedule": "string",
"created_at": "2019-08-24T14:15:22Z",
"deleting_at": "2019-08-24T14:15:22Z",
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"last_used_at": "2019-08-24T14:15:22Z",
"latest_build": {
@ -228,6 +229,7 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/workspace/{workspacenam
{
"autostart_schedule": "string",
"created_at": "2019-08-24T14:15:22Z",
"deleting_at": "2019-08-24T14:15:22Z",
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"last_used_at": "2019-08-24T14:15:22Z",
"latest_build": {
@ -423,6 +425,7 @@ curl -X GET http://coder-server:8080/api/v2/workspaces \
{
"autostart_schedule": "string",
"created_at": "2019-08-24T14:15:22Z",
"deleting_at": "2019-08-24T14:15:22Z",
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"last_used_at": "2019-08-24T14:15:22Z",
"latest_build": {
@ -592,6 +595,7 @@ curl -X GET http://coder-server:8080/api/v2/workspaces/{workspace} \
{
"autostart_schedule": "string",
"created_at": "2019-08-24T14:15:22Z",
"deleting_at": "2019-08-24T14:15:22Z",
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"last_used_at": "2019-08-24T14:15:22Z",
"latest_build": {

View File

@ -1113,6 +1113,7 @@ export interface Workspace {
readonly autostart_schedule?: string
readonly ttl_ms?: number
readonly last_used_at: string
readonly deleting_at?: string
}
// From codersdk/workspaceagents.go

View File

@ -721,6 +721,7 @@ export const MockWorkspace: TypesGen.Workspace = {
ttl_ms: 2 * 60 * 60 * 1000,
latest_build: MockWorkspaceBuild,
last_used_at: "2022-05-16T15:29:10.302441433Z",
deleting_at: "0001-01-01T00:00:00Z",
}
export const MockStoppedWorkspace: TypesGen.Workspace = {