diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index c8c09b06b0..32eab3d45c 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -18919,6 +18919,9 @@ const docTemplate = `{ "default_ttl_ms": { "type": "integer" }, + "deleted": { + "type": "boolean" + }, "deprecated": { "type": "boolean" }, diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index eb70c06c0b..156c5a8a42 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -17306,6 +17306,9 @@ "default_ttl_ms": { "type": "integer" }, + "deleted": { + "type": "boolean" + }, "deprecated": { "type": "boolean" }, diff --git a/coderd/templates.go b/coderd/templates.go index adb32ce065..2bcaf2099f 100644 --- a/coderd/templates.go +++ b/coderd/templates.go @@ -1131,6 +1131,7 @@ func (api *API) convertTemplate( RequireActiveVersion: templateAccessControl.RequireActiveVersion, Deprecated: templateAccessControl.IsDeprecated(), DeprecationMessage: templateAccessControl.Deprecated, + Deleted: template.Deleted, MaxPortShareLevel: maxPortShareLevel, UseClassicParameterFlow: template.UseClassicParameterFlow, CORSBehavior: codersdk.CORSBehavior(template.CorsBehavior), diff --git a/coderd/templates_test.go b/coderd/templates_test.go index 511d6e1dcc..d53ecf80d2 100644 --- a/coderd/templates_test.go +++ b/coderd/templates_test.go @@ -1801,6 +1801,49 @@ func TestDeleteTemplate(t *testing.T) { require.ErrorAs(t, err, &apiErr) require.Equal(t, http.StatusForbidden, apiErr.StatusCode()) }) + + t.Run("DeletedIsSet", func(t *testing.T) { + t.Parallel() + client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true}) + user := coderdtest.CreateFirstUser(t, client) + version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil) + template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) + coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID) + + ctx := testutil.Context(t, testutil.WaitLong) + + // Verify the deleted field is exposed in the SDK and set to false for active templates + got, err := client.Template(ctx, template.ID) + require.NoError(t, err) + require.False(t, got.Deleted) + }) + + t.Run("DeletedIsTrue", func(t *testing.T) { + t.Parallel() + client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true}) + user := coderdtest.CreateFirstUser(t, client) + version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil) + template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) + + coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID) + + ctx := testutil.Context(t, testutil.WaitLong) + + err := client.DeleteTemplate(ctx, template.ID) + require.NoError(t, err) + + // Verify the deleted field is set to true by listing templates with + // deleted:true filter. + templates, err := client.Templates(ctx, codersdk.TemplateFilter{ + OrganizationID: user.OrganizationID, + SearchQuery: "deleted:true", + }) + require.NoError(t, err) + + require.Len(t, templates, 1) + require.Equal(t, template.ID, templates[0].ID) + require.True(t, templates[0].Deleted) + }) } func TestTemplateMetrics(t *testing.T) { diff --git a/codersdk/templates.go b/codersdk/templates.go index d94dbbeed6..21c922025d 100644 --- a/codersdk/templates.go +++ b/codersdk/templates.go @@ -32,6 +32,7 @@ type Template struct { Description string `json:"description"` Deprecated bool `json:"deprecated"` DeprecationMessage string `json:"deprecation_message"` + Deleted bool `json:"deleted"` Icon string `json:"icon"` DefaultTTLMillis int64 `json:"default_ttl_ms"` ActivityBumpMillis int64 `json:"activity_bump_ms"` diff --git a/docs/reference/api/schemas.md b/docs/reference/api/schemas.md index 16f769d069..5045861feb 100644 --- a/docs/reference/api/schemas.md +++ b/docs/reference/api/schemas.md @@ -8443,6 +8443,7 @@ Only certain features set these fields: - FeatureManagedAgentLimit| "created_by_id": "9377d689-01fb-4abf-8450-3368d2c1924f", "created_by_name": "string", "default_ttl_ms": 0, + "deleted": true, "deprecated": true, "deprecation_message": "string", "description": "string", @@ -8484,6 +8485,7 @@ Only certain features set these fields: - FeatureManagedAgentLimit| | `created_by_id` | string | false | | | | `created_by_name` | string | false | | | | `default_ttl_ms` | integer | false | | | +| `deleted` | boolean | false | | | | `deprecated` | boolean | false | | | | `deprecation_message` | string | false | | | | `description` | string | false | | | diff --git a/docs/reference/api/templates.md b/docs/reference/api/templates.md index cfc51f8fbf..3a8c871cc7 100644 --- a/docs/reference/api/templates.md +++ b/docs/reference/api/templates.md @@ -62,6 +62,7 @@ To include deprecated templates, specify `deprecated:true` in the search query. "created_by_id": "9377d689-01fb-4abf-8450-3368d2c1924f", "created_by_name": "string", "default_ttl_ms": 0, + "deleted": true, "deprecated": true, "deprecation_message": "string", "description": "string", @@ -120,6 +121,7 @@ Restarts will only happen on weekdays in this list on weeks which line up with W |`» created_by_id`|string(uuid)|false||| |`» created_by_name`|string|false||| |`» default_ttl_ms`|integer|false||| +|`» deleted`|boolean|false||| |`» deprecated`|boolean|false||| |`» deprecation_message`|string|false||| |`» description`|string|false||| @@ -246,6 +248,7 @@ curl -X POST http://coder-server:8080/api/v2/organizations/{organization}/templa "created_by_id": "9377d689-01fb-4abf-8450-3368d2c1924f", "created_by_name": "string", "default_ttl_ms": 0, + "deleted": true, "deprecated": true, "deprecation_message": "string", "description": "string", @@ -397,6 +400,7 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/templat "created_by_id": "9377d689-01fb-4abf-8450-3368d2c1924f", "created_by_name": "string", "default_ttl_ms": 0, + "deleted": true, "deprecated": true, "deprecation_message": "string", "description": "string", @@ -814,6 +818,7 @@ To include deprecated templates, specify `deprecated:true` in the search query. "created_by_id": "9377d689-01fb-4abf-8450-3368d2c1924f", "created_by_name": "string", "default_ttl_ms": 0, + "deleted": true, "deprecated": true, "deprecation_message": "string", "description": "string", @@ -872,6 +877,7 @@ Restarts will only happen on weekdays in this list on weeks which line up with W |`» created_by_id`|string(uuid)|false||| |`» created_by_name`|string|false||| |`» default_ttl_ms`|integer|false||| +|`» deleted`|boolean|false||| |`» deprecated`|boolean|false||| |`» deprecation_message`|string|false||| |`» description`|string|false||| @@ -1016,6 +1022,7 @@ curl -X GET http://coder-server:8080/api/v2/templates/{template} \ "created_by_id": "9377d689-01fb-4abf-8450-3368d2c1924f", "created_by_name": "string", "default_ttl_ms": 0, + "deleted": true, "deprecated": true, "deprecation_message": "string", "description": "string", @@ -1189,6 +1196,7 @@ curl -X PATCH http://coder-server:8080/api/v2/templates/{template} \ "created_by_id": "9377d689-01fb-4abf-8450-3368d2c1924f", "created_by_name": "string", "default_ttl_ms": 0, + "deleted": true, "deprecated": true, "deprecation_message": "string", "description": "string", diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 273254428f..cbf279194a 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -5155,6 +5155,7 @@ export interface Template { readonly description: string; readonly deprecated: boolean; readonly deprecation_message: string; + readonly deleted: boolean; readonly icon: string; readonly default_ttl_ms: number; readonly activity_bump_ms: number; diff --git a/site/src/pages/TemplatesPage/TemplatesPageView.stories.tsx b/site/src/pages/TemplatesPage/TemplatesPageView.stories.tsx index 9f6897033d..202397d55f 100644 --- a/site/src/pages/TemplatesPage/TemplatesPageView.stories.tsx +++ b/site/src/pages/TemplatesPage/TemplatesPageView.stories.tsx @@ -82,6 +82,13 @@ export const WithTemplates: Story = { display_name: "Deprecated", description: "Template is incompatible", }, + { + ...MockTemplate, + name: "deleted-template", + display_name: "Deleted", + description: "Template has been deleted", + deleted: true, + }, ], examples: [], workspacePermissions: { diff --git a/site/src/pages/TemplatesPage/TemplatesPageView.tsx b/site/src/pages/TemplatesPage/TemplatesPageView.tsx index 60667e62b3..d6a9d65470 100644 --- a/site/src/pages/TemplatesPage/TemplatesPageView.tsx +++ b/site/src/pages/TemplatesPage/TemplatesPageView.tsx @@ -85,6 +85,49 @@ const TemplateHelpTooltip: FC = () => { ); }; +interface TemplateActionsProps { + template: Template; + workspacePermissions: Record | undefined; + templatePageLink: string; +} + +const TemplateActions: FC = ({ + template, + workspacePermissions, + templatePageLink, +}) => { + if (template.deleted) { + return null; + } + + if (template.deprecated) { + return ; + } + + if ( + !workspacePermissions?.[template.organization_id]?.createWorkspaceForUserID + ) { + return null; + } + + return ( + + ); +}; + interface TemplateRowProps { showOrganizations: boolean; template: Template; @@ -149,25 +192,11 @@ const TemplateRow: FC = ({ - {template.deprecated ? ( - - ) : workspacePermissions?.[template.organization_id] - ?.createWorkspaceForUserID ? ( - - ) : null} + ); diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts index 6533fa83e2..c27af26cf1 100644 --- a/site/src/testHelpers/entities.ts +++ b/site/src/testHelpers/entities.ts @@ -852,6 +852,7 @@ export const MockTemplate: TypesGen.Template = { require_active_version: false, deprecated: false, deprecation_message: "", + deleted: false, max_port_share_level: "public", use_classic_parameter_flow: false, cors_behavior: "simple",