diff --git a/cli/templatelist.go b/cli/templatelist.go index 6a866ad3f83e5..d014cdd6cef47 100644 --- a/cli/templatelist.go +++ b/cli/templatelist.go @@ -13,7 +13,7 @@ import ( func (r *RootCmd) templateList() *serpent.Command { orgContext := NewOrganizationContext() formatter := cliui.NewOutputFormatter( - cliui.TableFormat([]templateTableRow{}, []string{"name", "last updated", "used by"}), + cliui.TableFormat([]templateTableRow{}, []string{"name", "organization name", "last updated", "used by"}), cliui.JSONFormat(), ) diff --git a/cli/templates.go b/cli/templates.go index cb5d47f901e07..e5e64df8df896 100644 --- a/cli/templates.go +++ b/cli/templates.go @@ -83,14 +83,15 @@ type templateTableRow struct { Template codersdk.Template // Used by table format: - Name string `json:"-" table:"name,default_sort"` - CreatedAt string `json:"-" table:"created at"` - LastUpdated string `json:"-" table:"last updated"` - OrganizationID uuid.UUID `json:"-" table:"organization id"` - Provisioner codersdk.ProvisionerType `json:"-" table:"provisioner"` - ActiveVersionID uuid.UUID `json:"-" table:"active version id"` - UsedBy string `json:"-" table:"used by"` - DefaultTTL time.Duration `json:"-" table:"default ttl"` + Name string `json:"-" table:"name,default_sort"` + CreatedAt string `json:"-" table:"created at"` + LastUpdated string `json:"-" table:"last updated"` + OrganizationID uuid.UUID `json:"-" table:"organization id"` + OrganizationName string `json:"-" table:"organization name"` + Provisioner codersdk.ProvisionerType `json:"-" table:"provisioner"` + ActiveVersionID uuid.UUID `json:"-" table:"active version id"` + UsedBy string `json:"-" table:"used by"` + DefaultTTL time.Duration `json:"-" table:"default ttl"` } // templateToRows converts a list of templates to a list of templateTableRow for @@ -99,15 +100,16 @@ func templatesToRows(templates ...codersdk.Template) []templateTableRow { rows := make([]templateTableRow, len(templates)) for i, template := range templates { rows[i] = templateTableRow{ - Template: template, - Name: template.Name, - CreatedAt: template.CreatedAt.Format("January 2, 2006"), - LastUpdated: template.UpdatedAt.Format("January 2, 2006"), - OrganizationID: template.OrganizationID, - Provisioner: template.Provisioner, - ActiveVersionID: template.ActiveVersionID, - UsedBy: pretty.Sprint(cliui.DefaultStyles.Fuchsia, formatActiveDevelopers(template.ActiveUserCount)), - DefaultTTL: (time.Duration(template.DefaultTTLMillis) * time.Millisecond), + Template: template, + Name: template.Name, + CreatedAt: template.CreatedAt.Format("January 2, 2006"), + LastUpdated: template.UpdatedAt.Format("January 2, 2006"), + OrganizationID: template.OrganizationID, + OrganizationName: template.OrganizationName, + Provisioner: template.Provisioner, + ActiveVersionID: template.ActiveVersionID, + UsedBy: pretty.Sprint(cliui.DefaultStyles.Fuchsia, formatActiveDevelopers(template.ActiveUserCount)), + DefaultTTL: (time.Duration(template.DefaultTTLMillis) * time.Millisecond), } } diff --git a/cli/testdata/coder_templates_list_--help.golden b/cli/testdata/coder_templates_list_--help.golden index 3522902eaa75f..a45c5062ddaae 100644 --- a/cli/testdata/coder_templates_list_--help.golden +++ b/cli/testdata/coder_templates_list_--help.golden @@ -11,10 +11,10 @@ OPTIONS: -O, --org string, $CODER_ORGANIZATION Select which organization (uuid or name) to use. - -c, --column string-array (default: name,last updated,used by) + -c, --column string-array (default: name,organization name,last updated,used by) Columns to display in table output. Available columns: name, created - at, last updated, organization id, provisioner, active version id, - used by, default ttl. + at, last updated, organization id, organization name, provisioner, + active version id, used by, default ttl. -o, --output string (default: table) Output format. Available formats: table, json. diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 0d923db69d8fc..68a3773f0d1e8 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -11260,6 +11260,10 @@ const docTemplate = `{ "type": "string", "format": "uuid" }, + "organization_name": { + "type": "string", + "format": "url" + }, "provisioner": { "type": "string", "enum": [ diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 46caa7d6146da..36bae814a59a8 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -10182,6 +10182,10 @@ "type": "string", "format": "uuid" }, + "organization_name": { + "type": "string", + "format": "url" + }, "provisioner": { "type": "string", "enum": ["terraform"] diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index c37003f7cb96a..d19d218556b8d 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -515,7 +515,7 @@ func (q *FakeQuerier) getLatestWorkspaceBuildByWorkspaceIDNoLock(_ context.Conte func (q *FakeQuerier) getTemplateByIDNoLock(_ context.Context, id uuid.UUID) (database.Template, error) { for _, template := range q.templates { if template.ID == id { - return q.templateWithUserNoLock(template), nil + return q.templateWithNameNoLock(template), nil } } return database.Template{}, sql.ErrNoRows @@ -524,12 +524,12 @@ func (q *FakeQuerier) getTemplateByIDNoLock(_ context.Context, id uuid.UUID) (da func (q *FakeQuerier) templatesWithUserNoLock(tpl []database.TemplateTable) []database.Template { cpy := make([]database.Template, 0, len(tpl)) for _, t := range tpl { - cpy = append(cpy, q.templateWithUserNoLock(t)) + cpy = append(cpy, q.templateWithNameNoLock(t)) } return cpy } -func (q *FakeQuerier) templateWithUserNoLock(tpl database.TemplateTable) database.Template { +func (q *FakeQuerier) templateWithNameNoLock(tpl database.TemplateTable) database.Template { var user database.User for _, _user := range q.users { if _user.ID == tpl.CreatedBy { @@ -537,13 +537,23 @@ func (q *FakeQuerier) templateWithUserNoLock(tpl database.TemplateTable) databas break } } - var withUser database.Template + + var org database.Organization + for _, _org := range q.organizations { + if _org.ID == tpl.OrganizationID { + org = _org + break + } + } + + var withNames database.Template // This is a cheeky way to copy the fields over without explicitly listing them all. d, _ := json.Marshal(tpl) - _ = json.Unmarshal(d, &withUser) - withUser.CreatedByUsername = user.Username - withUser.CreatedByAvatarURL = user.AvatarURL - return withUser + _ = json.Unmarshal(d, &withNames) + withNames.CreatedByUsername = user.Username + withNames.CreatedByAvatarURL = user.AvatarURL + withNames.OrganizationName = org.Name + return withNames } func (q *FakeQuerier) templateVersionWithUserNoLock(tpl database.TemplateVersionTable) database.TemplateVersion { @@ -3675,7 +3685,7 @@ func (q *FakeQuerier) GetTemplateByOrganizationAndName(_ context.Context, arg da if template.Deleted != arg.Deleted { continue } - return q.templateWithUserNoLock(template), nil + return q.templateWithNameNoLock(template), nil } return database.Template{}, sql.ErrNoRows } @@ -9323,7 +9333,7 @@ func (q *FakeQuerier) GetAuthorizedTemplates(ctx context.Context, arg database.G var templates []database.Template for _, templateTable := range q.templates { - template := q.templateWithUserNoLock(templateTable) + template := q.templateWithNameNoLock(templateTable) if prepared != nil && prepared.Authorize(ctx, template.RBACObject()) != nil { continue } diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index 0b51a6c300205..f0b9cb311606f 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -1055,7 +1055,7 @@ COMMENT ON COLUMN templates.autostart_block_days_of_week IS 'A bitmap of days of COMMENT ON COLUMN templates.deprecated IS 'If set to a non empty string, the template will no longer be able to be used. The message will be displayed to the user.'; -CREATE VIEW template_with_users AS +CREATE VIEW template_with_names AS SELECT templates.id, templates.created_at, templates.updated_at, @@ -1085,11 +1085,13 @@ CREATE VIEW template_with_users AS templates.activity_bump, templates.max_port_sharing_level, COALESCE(visible_users.avatar_url, ''::text) AS created_by_avatar_url, - COALESCE(visible_users.username, ''::text) AS created_by_username - FROM (templates - LEFT JOIN visible_users ON ((templates.created_by = visible_users.id))); + COALESCE(visible_users.username, ''::text) AS created_by_username, + COALESCE(organizations.name, ''::text) AS organization_name + FROM ((templates + LEFT JOIN visible_users ON ((templates.created_by = visible_users.id))) + LEFT JOIN organizations ON ((templates.organization_id = organizations.id))); -COMMENT ON VIEW template_with_users IS 'Joins in the username + avatar url of the created by user.'; +COMMENT ON VIEW template_with_names IS 'Joins in the display name information such as username, avatar, and organization name.'; CREATE TABLE user_links ( user_id uuid NOT NULL, diff --git a/coderd/database/gentest/models_test.go b/coderd/database/gentest/models_test.go index 4882c77c17889..c1d2ea4999668 100644 --- a/coderd/database/gentest/models_test.go +++ b/coderd/database/gentest/models_test.go @@ -32,7 +32,7 @@ func TestViewSubsetTemplate(t *testing.T) { tableFields := allFields(table) joinedFields := allFields(joined) if !assert.Subset(t, fieldNames(joinedFields), fieldNames(tableFields), "table is not subset") { - t.Log("Some fields were added to the Template Table without updating the 'template_with_users' view.") + t.Log("Some fields were added to the Template Table without updating the 'template_with_names' view.") t.Log("See migration 000138_join_users.up.sql to create the view.") } } diff --git a/coderd/database/migrations/000222_template_organization_name.down.sql b/coderd/database/migrations/000222_template_organization_name.down.sql new file mode 100644 index 0000000000000..e40fd1a7db075 --- /dev/null +++ b/coderd/database/migrations/000222_template_organization_name.down.sql @@ -0,0 +1,16 @@ +DROP VIEW template_with_names; + +CREATE VIEW + template_with_users +AS +SELECT + templates.*, + coalesce(visible_users.avatar_url, '') AS created_by_avatar_url, + coalesce(visible_users.username, '') AS created_by_username +FROM + templates + LEFT JOIN + visible_users + ON + templates.created_by = visible_users.id; +COMMENT ON VIEW template_with_users IS 'Joins in the username + avatar url of the created by user.'; diff --git a/coderd/database/migrations/000222_template_organization_name.up.sql b/coderd/database/migrations/000222_template_organization_name.up.sql new file mode 100644 index 0000000000000..562f9f3ed0914 --- /dev/null +++ b/coderd/database/migrations/000222_template_organization_name.up.sql @@ -0,0 +1,24 @@ +-- Update the template_with_users view by recreating it. +DROP VIEW template_with_users; + +-- Renaming template_with_users -> template_with_names +CREATE VIEW + template_with_names +AS +SELECT + templates.*, + coalesce(visible_users.avatar_url, '') AS created_by_avatar_url, + coalesce(visible_users.username, '') AS created_by_username, + coalesce(organizations.name, '') AS organization_name +FROM + templates + LEFT JOIN + visible_users + ON + templates.created_by = visible_users.id + LEFT JOIN + organizations + ON templates.organization_id = organizations.id +; + +COMMENT ON VIEW template_with_names IS 'Joins in the display name information such as username, avatar, and organization name.'; diff --git a/coderd/database/modelqueries.go b/coderd/database/modelqueries.go index 9cc5d7792101c..3323ed834b31d 100644 --- a/coderd/database/modelqueries.go +++ b/coderd/database/modelqueries.go @@ -116,6 +116,7 @@ func (q *sqlQuerier) GetAuthorizedTemplates(ctx context.Context, arg GetTemplate &i.MaxPortSharingLevel, &i.CreatedByAvatarURL, &i.CreatedByUsername, + &i.OrganizationName, ); err != nil { return nil, err } diff --git a/coderd/database/models.go b/coderd/database/models.go index d7f1ab9972a61..7f34d7680abf2 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -2243,7 +2243,7 @@ type TailnetTunnel struct { UpdatedAt time.Time `db:"updated_at" json:"updated_at"` } -// Joins in the username + avatar url of the created by user. +// Joins in the display name information such as username, avatar, and organization name. type Template struct { ID uuid.UUID `db:"id" json:"id"` CreatedAt time.Time `db:"created_at" json:"created_at"` @@ -2275,6 +2275,7 @@ type Template struct { MaxPortSharingLevel AppSharingLevel `db:"max_port_sharing_level" json:"max_port_sharing_level"` CreatedByAvatarURL string `db:"created_by_avatar_url" json:"created_by_avatar_url"` CreatedByUsername string `db:"created_by_username" json:"created_by_username"` + OrganizationName string `db:"organization_name" json:"organization_name"` } type TemplateTable struct { diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 55db74634c740..ff7b7f6f955bd 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -7178,9 +7178,9 @@ func (q *sqlQuerier) GetTemplateAverageBuildTime(ctx context.Context, arg GetTem const getTemplateByID = `-- name: GetTemplateByID :one SELECT - id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl, display_name, allow_user_cancel_workspace_jobs, allow_user_autostart, allow_user_autostop, failure_ttl, time_til_dormant, time_til_dormant_autodelete, autostop_requirement_days_of_week, autostop_requirement_weeks, autostart_block_days_of_week, require_active_version, deprecated, activity_bump, max_port_sharing_level, created_by_avatar_url, created_by_username + id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl, display_name, allow_user_cancel_workspace_jobs, allow_user_autostart, allow_user_autostop, failure_ttl, time_til_dormant, time_til_dormant_autodelete, autostop_requirement_days_of_week, autostop_requirement_weeks, autostart_block_days_of_week, require_active_version, deprecated, activity_bump, max_port_sharing_level, created_by_avatar_url, created_by_username, organization_name FROM - template_with_users + template_with_names WHERE id = $1 LIMIT @@ -7221,15 +7221,16 @@ func (q *sqlQuerier) GetTemplateByID(ctx context.Context, id uuid.UUID) (Templat &i.MaxPortSharingLevel, &i.CreatedByAvatarURL, &i.CreatedByUsername, + &i.OrganizationName, ) return i, err } const getTemplateByOrganizationAndName = `-- name: GetTemplateByOrganizationAndName :one SELECT - id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl, display_name, allow_user_cancel_workspace_jobs, allow_user_autostart, allow_user_autostop, failure_ttl, time_til_dormant, time_til_dormant_autodelete, autostop_requirement_days_of_week, autostop_requirement_weeks, autostart_block_days_of_week, require_active_version, deprecated, activity_bump, max_port_sharing_level, created_by_avatar_url, created_by_username + id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl, display_name, allow_user_cancel_workspace_jobs, allow_user_autostart, allow_user_autostop, failure_ttl, time_til_dormant, time_til_dormant_autodelete, autostop_requirement_days_of_week, autostop_requirement_weeks, autostart_block_days_of_week, require_active_version, deprecated, activity_bump, max_port_sharing_level, created_by_avatar_url, created_by_username, organization_name FROM - template_with_users AS templates + template_with_names AS templates WHERE organization_id = $1 AND deleted = $2 @@ -7278,12 +7279,13 @@ func (q *sqlQuerier) GetTemplateByOrganizationAndName(ctx context.Context, arg G &i.MaxPortSharingLevel, &i.CreatedByAvatarURL, &i.CreatedByUsername, + &i.OrganizationName, ) return i, err } const getTemplates = `-- name: GetTemplates :many -SELECT id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl, display_name, allow_user_cancel_workspace_jobs, allow_user_autostart, allow_user_autostop, failure_ttl, time_til_dormant, time_til_dormant_autodelete, autostop_requirement_days_of_week, autostop_requirement_weeks, autostart_block_days_of_week, require_active_version, deprecated, activity_bump, max_port_sharing_level, created_by_avatar_url, created_by_username FROM template_with_users AS templates +SELECT id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl, display_name, allow_user_cancel_workspace_jobs, allow_user_autostart, allow_user_autostop, failure_ttl, time_til_dormant, time_til_dormant_autodelete, autostop_requirement_days_of_week, autostop_requirement_weeks, autostart_block_days_of_week, require_active_version, deprecated, activity_bump, max_port_sharing_level, created_by_avatar_url, created_by_username, organization_name FROM template_with_names AS templates ORDER BY (name, id) ASC ` @@ -7327,6 +7329,7 @@ func (q *sqlQuerier) GetTemplates(ctx context.Context) ([]Template, error) { &i.MaxPortSharingLevel, &i.CreatedByAvatarURL, &i.CreatedByUsername, + &i.OrganizationName, ); err != nil { return nil, err } @@ -7343,9 +7346,9 @@ func (q *sqlQuerier) GetTemplates(ctx context.Context) ([]Template, error) { const getTemplatesWithFilter = `-- name: GetTemplatesWithFilter :many SELECT - id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl, display_name, allow_user_cancel_workspace_jobs, allow_user_autostart, allow_user_autostop, failure_ttl, time_til_dormant, time_til_dormant_autodelete, autostop_requirement_days_of_week, autostop_requirement_weeks, autostart_block_days_of_week, require_active_version, deprecated, activity_bump, max_port_sharing_level, created_by_avatar_url, created_by_username + id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl, display_name, allow_user_cancel_workspace_jobs, allow_user_autostart, allow_user_autostop, failure_ttl, time_til_dormant, time_til_dormant_autodelete, autostop_requirement_days_of_week, autostop_requirement_weeks, autostart_block_days_of_week, require_active_version, deprecated, activity_bump, max_port_sharing_level, created_by_avatar_url, created_by_username, organization_name FROM - template_with_users AS templates + template_with_names AS templates WHERE -- Optionally include deleted templates templates.deleted = $1 @@ -7437,6 +7440,7 @@ func (q *sqlQuerier) GetTemplatesWithFilter(ctx context.Context, arg GetTemplate &i.MaxPortSharingLevel, &i.CreatedByAvatarURL, &i.CreatedByUsername, + &i.OrganizationName, ); err != nil { return nil, err } diff --git a/coderd/database/queries/templates.sql b/coderd/database/queries/templates.sql index d804077319ad5..31beb11b4e1ca 100644 --- a/coderd/database/queries/templates.sql +++ b/coderd/database/queries/templates.sql @@ -2,7 +2,7 @@ SELECT * FROM - template_with_users + template_with_names WHERE id = $1 LIMIT @@ -12,7 +12,7 @@ LIMIT SELECT * FROM - template_with_users AS templates + template_with_names AS templates WHERE -- Optionally include deleted templates templates.deleted = @deleted @@ -54,7 +54,7 @@ ORDER BY (name, id) ASC SELECT * FROM - template_with_users AS templates + template_with_names AS templates WHERE organization_id = @organization_id AND deleted = @deleted @@ -63,7 +63,7 @@ LIMIT 1; -- name: GetTemplates :many -SELECT * FROM template_with_users AS templates +SELECT * FROM template_with_names AS templates ORDER BY (name, id) ASC ; diff --git a/coderd/database/sqlc.yaml b/coderd/database/sqlc.yaml index 5d6f4419d5b8b..fc56cf943dc3b 100644 --- a/coderd/database/sqlc.yaml +++ b/coderd/database/sqlc.yaml @@ -55,10 +55,10 @@ sql: - column: "templates.group_acl" go_type: type: "TemplateACL" - - column: "template_with_users.user_acl" + - column: "template_with_names.user_acl" go_type: type: "TemplateACL" - - column: "template_with_users.group_acl" + - column: "template_with_names.group_acl" go_type: type: "TemplateACL" - column: "template_usage_stats.app_usage_mins" @@ -72,7 +72,7 @@ sql: type: "[]byte" rename: template: TemplateTable - template_with_user: Template + template_with_name: Template workspace_build: WorkspaceBuildTable workspace_build_with_user: WorkspaceBuild template_version: TemplateVersionTable diff --git a/coderd/templates.go b/coderd/templates.go index 3027321fdbba2..ffb45fd2e08e4 100644 --- a/coderd/templates.go +++ b/coderd/templates.go @@ -894,6 +894,7 @@ func (api *API) convertTemplate( CreatedAt: template.CreatedAt, UpdatedAt: template.UpdatedAt, OrganizationID: template.OrganizationID, + OrganizationName: template.OrganizationName, Name: template.Name, DisplayName: template.DisplayName, Provisioner: codersdk.ProvisionerType(template.Provisioner), diff --git a/coderd/templates_test.go b/coderd/templates_test.go index 2813f713f5ea2..9b4c813a263b0 100644 --- a/coderd/templates_test.go +++ b/coderd/templates_test.go @@ -443,6 +443,13 @@ func TestTemplatesByOrganization(t *testing.T) { templates, err = client.Templates(ctx) require.NoError(t, err) require.Len(t, templates, 2) + + org, err := client.Organization(ctx, user.OrganizationID) + require.NoError(t, err) + for _, tmpl := range templates { + require.Equal(t, tmpl.OrganizationID, user.OrganizationID, "organization ID") + require.Equal(t, tmpl.OrganizationName, org.Name, "organization name") + } }) t.Run("MultipleOrganizations", func(t *testing.T) { t.Parallel() @@ -474,6 +481,9 @@ func TestTemplatesByOrganization(t *testing.T) { templates, err = user.Templates(ctx) require.NoError(t, err) require.Len(t, templates, 2) + for _, tmpl := range templates { + require.Equal(t, tmpl.OrganizationName, org2.Name, "organization name on template") + } }) } diff --git a/codersdk/templates.go b/codersdk/templates.go index 2d523cf58e8a6..0a9e26da105be 100644 --- a/codersdk/templates.go +++ b/codersdk/templates.go @@ -15,14 +15,15 @@ import ( // Template is the JSON representation of a Coder template. This type matches the // database object for now, but is abstracted for ease of change later on. type Template struct { - ID uuid.UUID `json:"id" format:"uuid"` - CreatedAt time.Time `json:"created_at" format:"date-time"` - UpdatedAt time.Time `json:"updated_at" format:"date-time"` - OrganizationID uuid.UUID `json:"organization_id" format:"uuid"` - Name string `json:"name"` - DisplayName string `json:"display_name"` - Provisioner ProvisionerType `json:"provisioner" enums:"terraform"` - ActiveVersionID uuid.UUID `json:"active_version_id" format:"uuid"` + ID uuid.UUID `json:"id" format:"uuid"` + CreatedAt time.Time `json:"created_at" format:"date-time"` + UpdatedAt time.Time `json:"updated_at" format:"date-time"` + OrganizationID uuid.UUID `json:"organization_id" format:"uuid"` + OrganizationName string `json:"organization_name" format:"url"` + Name string `json:"name"` + DisplayName string `json:"display_name"` + Provisioner ProvisionerType `json:"provisioner" enums:"terraform"` + ActiveVersionID uuid.UUID `json:"active_version_id" format:"uuid"` // ActiveUserCount is set to -1 when loading. ActiveUserCount int `json:"active_user_count"` BuildTimeStats TemplateBuildTimeStats `json:"build_time_stats"` diff --git a/docs/admin/audit-logs.md b/docs/admin/audit-logs.md index 52ed2d34e1a97..5f34e6bf475c4 100644 --- a/docs/admin/audit-logs.md +++ b/docs/admin/audit-logs.md @@ -8,25 +8,25 @@ We track the following resources: -| Resource | | -| -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| APIKey
login, logout, register, create, delete |
FieldTracked
created_attrue
expires_attrue
hashed_secretfalse
idfalse
ip_addressfalse
last_usedtrue
lifetime_secondsfalse
login_typefalse
scopefalse
token_namefalse
updated_atfalse
user_idtrue
| -| AuditOAuthConvertState
|
FieldTracked
created_attrue
expires_attrue
from_login_typetrue
to_login_typetrue
user_idtrue
| -| Group
create, write, delete |
FieldTracked
avatar_urltrue
display_nametrue
idtrue
memberstrue
nametrue
organization_idfalse
quota_allowancetrue
sourcefalse
| -| AuditableOrganizationMember
|
FieldTracked
created_attrue
organization_idtrue
rolestrue
updated_attrue
user_idtrue
usernametrue
| -| CustomRole
|
FieldTracked
created_atfalse
display_nametrue
idfalse
nametrue
org_permissionstrue
organization_idtrue
site_permissionstrue
updated_atfalse
user_permissionstrue
| -| GitSSHKey
create |
FieldTracked
created_atfalse
private_keytrue
public_keytrue
updated_atfalse
user_idtrue
| -| HealthSettings
|
FieldTracked
dismissed_healthcheckstrue
idfalse
| -| License
create, delete |
FieldTracked
exptrue
idfalse
jwtfalse
uploaded_attrue
uuidtrue
| -| OAuth2ProviderApp
|
FieldTracked
callback_urltrue
created_atfalse
icontrue
idfalse
nametrue
updated_atfalse
| -| OAuth2ProviderAppSecret
|
FieldTracked
app_idfalse
created_atfalse
display_secretfalse
hashed_secretfalse
idfalse
last_used_atfalse
secret_prefixfalse
| -| Organization
|
FieldTracked
created_atfalse
descriptiontrue
display_nametrue
icontrue
idfalse
is_defaulttrue
nametrue
updated_attrue
| -| Template
write, delete |
FieldTracked
active_version_idtrue
activity_bumptrue
allow_user_autostarttrue
allow_user_autostoptrue
allow_user_cancel_workspace_jobstrue
autostart_block_days_of_weektrue
autostop_requirement_days_of_weektrue
autostop_requirement_weekstrue
created_atfalse
created_bytrue
created_by_avatar_urlfalse
created_by_usernamefalse
default_ttltrue
deletedfalse
deprecatedtrue
descriptiontrue
display_nametrue
failure_ttltrue
group_acltrue
icontrue
idtrue
max_port_sharing_leveltrue
nametrue
organization_idfalse
provisionertrue
require_active_versiontrue
time_til_dormanttrue
time_til_dormant_autodeletetrue
updated_atfalse
user_acltrue
| -| TemplateVersion
create, write |
FieldTracked
archivedtrue
created_atfalse
created_bytrue
created_by_avatar_urlfalse
created_by_usernamefalse
external_auth_providersfalse
idtrue
job_idfalse
messagefalse
nametrue
organization_idfalse
readmetrue
template_idtrue
updated_atfalse
| -| User
create, write, delete |
FieldTracked
avatar_urlfalse
created_atfalse
deletedtrue
emailtrue
hashed_passwordtrue
idtrue
last_seen_atfalse
login_typetrue
nametrue
quiet_hours_scheduletrue
rbac_rolestrue
statustrue
theme_preferencefalse
updated_atfalse
usernametrue
| -| Workspace
create, write, delete |
FieldTracked
automatic_updatestrue
autostart_scheduletrue
created_atfalse
deletedfalse
deleting_attrue
dormant_attrue
favoritetrue
idtrue
last_used_atfalse
nametrue
organization_idfalse
owner_idtrue
template_idtrue
ttltrue
updated_atfalse
| -| WorkspaceBuild
start, stop |
FieldTracked
build_numberfalse
created_atfalse
daily_costfalse
deadlinefalse
idfalse
initiator_by_avatar_urlfalse
initiator_by_usernamefalse
initiator_idfalse
job_idfalse
max_deadlinefalse
provisioner_statefalse
reasonfalse
template_version_idtrue
transitionfalse
updated_atfalse
workspace_idfalse
| -| WorkspaceProxy
|
FieldTracked
created_attrue
deletedfalse
derp_enabledtrue
derp_onlytrue
display_nametrue
icontrue
idtrue
nametrue
region_idtrue
token_hashed_secrettrue
updated_atfalse
urltrue
versiontrue
wildcard_hostnametrue
| +| Resource | | +| -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| APIKey
login, logout, register, create, delete |
FieldTracked
created_attrue
expires_attrue
hashed_secretfalse
idfalse
ip_addressfalse
last_usedtrue
lifetime_secondsfalse
login_typefalse
scopefalse
token_namefalse
updated_atfalse
user_idtrue
| +| AuditOAuthConvertState
|
FieldTracked
created_attrue
expires_attrue
from_login_typetrue
to_login_typetrue
user_idtrue
| +| Group
create, write, delete |
FieldTracked
avatar_urltrue
display_nametrue
idtrue
memberstrue
nametrue
organization_idfalse
quota_allowancetrue
sourcefalse
| +| AuditableOrganizationMember
|
FieldTracked
created_attrue
organization_idtrue
rolestrue
updated_attrue
user_idtrue
usernametrue
| +| CustomRole
|
FieldTracked
created_atfalse
display_nametrue
idfalse
nametrue
org_permissionstrue
organization_idtrue
site_permissionstrue
updated_atfalse
user_permissionstrue
| +| GitSSHKey
create |
FieldTracked
created_atfalse
private_keytrue
public_keytrue
updated_atfalse
user_idtrue
| +| HealthSettings
|
FieldTracked
dismissed_healthcheckstrue
idfalse
| +| License
create, delete |
FieldTracked
exptrue
idfalse
jwtfalse
uploaded_attrue
uuidtrue
| +| OAuth2ProviderApp
|
FieldTracked
callback_urltrue
created_atfalse
icontrue
idfalse
nametrue
updated_atfalse
| +| OAuth2ProviderAppSecret
|
FieldTracked
app_idfalse
created_atfalse
display_secretfalse
hashed_secretfalse
idfalse
last_used_atfalse
secret_prefixfalse
| +| Organization
|
FieldTracked
created_atfalse
descriptiontrue
display_nametrue
icontrue
idfalse
is_defaulttrue
nametrue
updated_attrue
| +| Template
write, delete |
FieldTracked
active_version_idtrue
activity_bumptrue
allow_user_autostarttrue
allow_user_autostoptrue
allow_user_cancel_workspace_jobstrue
autostart_block_days_of_weektrue
autostop_requirement_days_of_weektrue
autostop_requirement_weekstrue
created_atfalse
created_bytrue
created_by_avatar_urlfalse
created_by_usernamefalse
default_ttltrue
deletedfalse
deprecatedtrue
descriptiontrue
display_nametrue
failure_ttltrue
group_acltrue
icontrue
idtrue
max_port_sharing_leveltrue
nametrue
organization_idfalse
organization_namefalse
provisionertrue
require_active_versiontrue
time_til_dormanttrue
time_til_dormant_autodeletetrue
updated_atfalse
user_acltrue
| +| TemplateVersion
create, write |
FieldTracked
archivedtrue
created_atfalse
created_bytrue
created_by_avatar_urlfalse
created_by_usernamefalse
external_auth_providersfalse
idtrue
job_idfalse
messagefalse
nametrue
organization_idfalse
readmetrue
template_idtrue
updated_atfalse
| +| User
create, write, delete |
FieldTracked
avatar_urlfalse
created_atfalse
deletedtrue
emailtrue
hashed_passwordtrue
idtrue
last_seen_atfalse
login_typetrue
nametrue
quiet_hours_scheduletrue
rbac_rolestrue
statustrue
theme_preferencefalse
updated_atfalse
usernametrue
| +| Workspace
create, write, delete |
FieldTracked
automatic_updatestrue
autostart_scheduletrue
created_atfalse
deletedfalse
deleting_attrue
dormant_attrue
favoritetrue
idtrue
last_used_atfalse
nametrue
organization_idfalse
owner_idtrue
template_idtrue
ttltrue
updated_atfalse
| +| WorkspaceBuild
start, stop |
FieldTracked
build_numberfalse
created_atfalse
daily_costfalse
deadlinefalse
idfalse
initiator_by_avatar_urlfalse
initiator_by_usernamefalse
initiator_idfalse
job_idfalse
max_deadlinefalse
provisioner_statefalse
reasonfalse
template_version_idtrue
transitionfalse
updated_atfalse
workspace_idfalse
| +| WorkspaceProxy
|
FieldTracked
created_attrue
deletedfalse
derp_enabledtrue
derp_onlytrue
display_nametrue
icontrue
idtrue
nametrue
region_idtrue
token_hashed_secrettrue
updated_atfalse
urltrue
versiontrue
wildcard_hostnametrue
| diff --git a/docs/api/schemas.md b/docs/api/schemas.md index 305b3c0e733f6..a9b3d613be318 100644 --- a/docs/api/schemas.md +++ b/docs/api/schemas.md @@ -4294,6 +4294,7 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o "max_port_share_level": "owner", "name": "string", "organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6", + "organization_name": "string", "provisioner": "terraform", "require_active_version": true, "time_til_dormant_autodelete_ms": 0, @@ -4329,6 +4330,7 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o | `max_port_share_level` | [codersdk.WorkspaceAgentPortShareLevel](#codersdkworkspaceagentportsharelevel) | false | | | | `name` | string | false | | | | `organization_id` | string | false | | | +| `organization_name` | string | false | | | | `provisioner` | string | false | | | | `require_active_version` | boolean | false | | Require active version mandates that workspaces are built with the active template version. | | `time_til_dormant_autodelete_ms` | integer | false | | | diff --git a/docs/api/templates.md b/docs/api/templates.md index b85811f41d0b8..2f713d027482c 100644 --- a/docs/api/templates.md +++ b/docs/api/templates.md @@ -63,6 +63,7 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/templat "max_port_share_level": "owner", "name": "string", "organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6", + "organization_name": "string", "provisioner": "terraform", "require_active_version": true, "time_til_dormant_autodelete_ms": 0, @@ -115,6 +116,7 @@ Status Code **200** | `» max_port_share_level` | [codersdk.WorkspaceAgentPortShareLevel](schemas.md#codersdkworkspaceagentportsharelevel) | false | | | | `» name` | string | false | | | | `» organization_id` | string(uuid) | false | | | +| `» organization_name` | string(url) | false | | | | `» provisioner` | string | false | | | | `» require_active_version` | boolean | false | | Require active version mandates that workspaces are built with the active template version. | | `» time_til_dormant_autodelete_ms` | integer | false | | | @@ -225,6 +227,7 @@ curl -X POST http://coder-server:8080/api/v2/organizations/{organization}/templa "max_port_share_level": "owner", "name": "string", "organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6", + "organization_name": "string", "provisioner": "terraform", "require_active_version": true, "time_til_dormant_autodelete_ms": 0, @@ -364,6 +367,7 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/templat "max_port_share_level": "owner", "name": "string", "organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6", + "organization_name": "string", "provisioner": "terraform", "require_active_version": true, "time_til_dormant_autodelete_ms": 0, @@ -674,6 +678,7 @@ curl -X GET http://coder-server:8080/api/v2/templates \ "max_port_share_level": "owner", "name": "string", "organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6", + "organization_name": "string", "provisioner": "terraform", "require_active_version": true, "time_til_dormant_autodelete_ms": 0, @@ -726,6 +731,7 @@ Status Code **200** | `» max_port_share_level` | [codersdk.WorkspaceAgentPortShareLevel](schemas.md#codersdkworkspaceagentportsharelevel) | false | | | | `» name` | string | false | | | | `» organization_id` | string(uuid) | false | | | +| `» organization_name` | string(url) | false | | | | `» provisioner` | string | false | | | | `» require_active_version` | boolean | false | | Require active version mandates that workspaces are built with the active template version. | | `» time_til_dormant_autodelete_ms` | integer | false | | | @@ -805,6 +811,7 @@ curl -X GET http://coder-server:8080/api/v2/templates/{template} \ "max_port_share_level": "owner", "name": "string", "organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6", + "organization_name": "string", "provisioner": "terraform", "require_active_version": true, "time_til_dormant_autodelete_ms": 0, @@ -927,6 +934,7 @@ curl -X PATCH http://coder-server:8080/api/v2/templates/{template} \ "max_port_share_level": "owner", "name": "string", "organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6", + "organization_name": "string", "provisioner": "terraform", "require_active_version": true, "time_til_dormant_autodelete_ms": 0, diff --git a/docs/cli/templates_list.md b/docs/cli/templates_list.md index 86f5386d3f043..551b03ed9ffc1 100644 --- a/docs/cli/templates_list.md +++ b/docs/cli/templates_list.md @@ -18,12 +18,12 @@ coder templates list [flags] ### -c, --column -| | | -| ------- | -------------------------------------- | -| Type | string-array | -| Default | name,last updated,used by | +| | | +| ------- | -------------------------------------------------------- | +| Type | string-array | +| Default | name,organization name,last updated,used by | -Columns to display in table output. Available columns: name, created at, last updated, organization id, provisioner, active version id, used by, default ttl. +Columns to display in table output. Available columns: name, created at, last updated, organization id, organization name, provisioner, active version id, used by, default ttl. ### -o, --output diff --git a/enterprise/audit/table.go b/enterprise/audit/table.go index 72012bf224167..ed52b5e921560 100644 --- a/enterprise/audit/table.go +++ b/enterprise/audit/table.go @@ -82,6 +82,7 @@ var auditableResourcesTypes = map[any]map[string]Action{ "created_at": ActionIgnore, // Never changes, but is implicit and not helpful in a diff. "updated_at": ActionIgnore, // Changes, but is implicit and not helpful in a diff. "organization_id": ActionIgnore, /// Never changes. + "organization_name": ActionIgnore, // Ignore these changes "deleted": ActionIgnore, // Changes, but is implicit when a delete event is fired. "name": ActionTrack, "display_name": ActionTrack, diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 4f4b4c8333304..219f46da10938 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -1093,6 +1093,7 @@ export interface Template { readonly created_at: string; readonly updated_at: string; readonly organization_id: string; + readonly organization_name: string; readonly name: string; readonly display_name: string; readonly provisioner: ProvisionerType; diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts index e00756051b331..1cd0ff5d76ee4 100644 --- a/site/src/testHelpers/entities.ts +++ b/site/src/testHelpers/entities.ts @@ -480,6 +480,7 @@ export const MockTemplate: TypesGen.Template = { created_at: "2022-05-17T17:39:01.382927298Z", updated_at: "2022-05-18T17:39:01.382927298Z", organization_id: MockOrganization.id, + organization_name: "default", name: "test-template", display_name: "Test Template", provisioner: MockProvisioner.provisioners[0],