mirror of
https://github.com/coder/coder.git
synced 2025-07-03 16:13:58 +00:00
chore: add 'classic_parameter_flow' column setting to templates (#17828)
We are forcing users to try the dynamic parameter experience first. Currently this setting only comes into effect if an experiment is enabled.
This commit is contained in:
3
coderd/apidoc/docs.go
generated
3
coderd/apidoc/docs.go
generated
@ -15573,6 +15573,9 @@ const docTemplate = `{
|
||||
"updated_at": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"use_classic_parameter_flow": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
3
coderd/apidoc/swagger.json
generated
3
coderd/apidoc/swagger.json
generated
@ -14167,6 +14167,9 @@
|
||||
"updated_at": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"use_classic_parameter_flow": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -11084,6 +11084,7 @@ func (q *FakeQuerier) UpdateTemplateMetaByID(_ context.Context, arg database.Upd
|
||||
tpl.GroupACL = arg.GroupACL
|
||||
tpl.AllowUserCancelWorkspaceJobs = arg.AllowUserCancelWorkspaceJobs
|
||||
tpl.MaxPortSharingLevel = arg.MaxPortSharingLevel
|
||||
tpl.UseClassicParameterFlow = arg.UseClassicParameterFlow
|
||||
q.templates[idx] = tpl
|
||||
return nil
|
||||
}
|
||||
|
6
coderd/database/dump.sql
generated
6
coderd/database/dump.sql
generated
@ -1560,7 +1560,8 @@ CREATE TABLE templates (
|
||||
require_active_version boolean DEFAULT false NOT NULL,
|
||||
deprecated text DEFAULT ''::text NOT NULL,
|
||||
activity_bump bigint DEFAULT '3600000000000'::bigint NOT NULL,
|
||||
max_port_sharing_level app_sharing_level DEFAULT 'owner'::app_sharing_level NOT NULL
|
||||
max_port_sharing_level app_sharing_level DEFAULT 'owner'::app_sharing_level NOT NULL,
|
||||
use_classic_parameter_flow boolean DEFAULT false NOT NULL
|
||||
);
|
||||
|
||||
COMMENT ON COLUMN templates.default_ttl IS 'The default duration for autostop for workspaces created from this template.';
|
||||
@ -1581,6 +1582,8 @@ 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.';
|
||||
|
||||
COMMENT ON COLUMN templates.use_classic_parameter_flow IS 'Determines whether to default to the dynamic parameter creation flow for this template or continue using the legacy classic parameter creation flow.This is a template wide setting, the template admin can revert to the classic flow if there are any issues. An escape hatch is required, as workspace creation is a core workflow and cannot break. This column will be removed when the dynamic parameter creation flow is stable.';
|
||||
|
||||
CREATE VIEW template_with_names AS
|
||||
SELECT templates.id,
|
||||
templates.created_at,
|
||||
@ -1610,6 +1613,7 @@ CREATE VIEW template_with_names AS
|
||||
templates.deprecated,
|
||||
templates.activity_bump,
|
||||
templates.max_port_sharing_level,
|
||||
templates.use_classic_parameter_flow,
|
||||
COALESCE(visible_users.avatar_url, ''::text) AS created_by_avatar_url,
|
||||
COALESCE(visible_users.username, ''::text) AS created_by_username,
|
||||
COALESCE(organizations.name, ''::text) AS organization_name,
|
||||
|
@ -0,0 +1,28 @@
|
||||
DROP VIEW template_with_names;
|
||||
|
||||
-- Drop the column
|
||||
ALTER TABLE templates DROP COLUMN use_classic_parameter_flow;
|
||||
|
||||
|
||||
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,
|
||||
coalesce(organizations.display_name, '') AS organization_display_name,
|
||||
coalesce(organizations.icon, '') AS organization_icon
|
||||
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.';
|
@ -0,0 +1,36 @@
|
||||
-- Default to `false`. Users will have to manually opt back into the classic parameter flow.
|
||||
-- We want the new experience to be tried first.
|
||||
ALTER TABLE templates ADD COLUMN use_classic_parameter_flow BOOL NOT NULL DEFAULT false;
|
||||
|
||||
COMMENT ON COLUMN templates.use_classic_parameter_flow IS
|
||||
'Determines whether to default to the dynamic parameter creation flow for this template '
|
||||
'or continue using the legacy classic parameter creation flow.'
|
||||
'This is a template wide setting, the template admin can revert to the classic flow if there are any issues. '
|
||||
'An escape hatch is required, as workspace creation is a core workflow and cannot break. '
|
||||
'This column will be removed when the dynamic parameter creation flow is stable.';
|
||||
|
||||
|
||||
-- Update the template_with_names view by recreating it.
|
||||
DROP VIEW 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,
|
||||
coalesce(organizations.display_name, '') AS organization_display_name,
|
||||
coalesce(organizations.icon, '') AS organization_icon
|
||||
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.';
|
@ -117,6 +117,7 @@ func (q *sqlQuerier) GetAuthorizedTemplates(ctx context.Context, arg GetTemplate
|
||||
&i.Deprecated,
|
||||
&i.ActivityBump,
|
||||
&i.MaxPortSharingLevel,
|
||||
&i.UseClassicParameterFlow,
|
||||
&i.CreatedByAvatarURL,
|
||||
&i.CreatedByUsername,
|
||||
&i.OrganizationName,
|
||||
|
@ -3114,6 +3114,7 @@ type Template struct {
|
||||
Deprecated string `db:"deprecated" json:"deprecated"`
|
||||
ActivityBump int64 `db:"activity_bump" json:"activity_bump"`
|
||||
MaxPortSharingLevel AppSharingLevel `db:"max_port_sharing_level" json:"max_port_sharing_level"`
|
||||
UseClassicParameterFlow bool `db:"use_classic_parameter_flow" json:"use_classic_parameter_flow"`
|
||||
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"`
|
||||
@ -3159,6 +3160,8 @@ type TemplateTable struct {
|
||||
Deprecated string `db:"deprecated" json:"deprecated"`
|
||||
ActivityBump int64 `db:"activity_bump" json:"activity_bump"`
|
||||
MaxPortSharingLevel AppSharingLevel `db:"max_port_sharing_level" json:"max_port_sharing_level"`
|
||||
// Determines whether to default to the dynamic parameter creation flow for this template or continue using the legacy classic parameter creation flow.This is a template wide setting, the template admin can revert to the classic flow if there are any issues. An escape hatch is required, as workspace creation is a core workflow and cannot break. This column will be removed when the dynamic parameter creation flow is stable.
|
||||
UseClassicParameterFlow bool `db:"use_classic_parameter_flow" json:"use_classic_parameter_flow"`
|
||||
}
|
||||
|
||||
// Records aggregated usage statistics for templates/users. All usage is rounded up to the nearest minute.
|
||||
|
@ -10427,7 +10427,7 @@ 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, organization_name, organization_display_name, organization_icon
|
||||
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, use_classic_parameter_flow, created_by_avatar_url, created_by_username, organization_name, organization_display_name, organization_icon
|
||||
FROM
|
||||
template_with_names
|
||||
WHERE
|
||||
@ -10468,6 +10468,7 @@ func (q *sqlQuerier) GetTemplateByID(ctx context.Context, id uuid.UUID) (Templat
|
||||
&i.Deprecated,
|
||||
&i.ActivityBump,
|
||||
&i.MaxPortSharingLevel,
|
||||
&i.UseClassicParameterFlow,
|
||||
&i.CreatedByAvatarURL,
|
||||
&i.CreatedByUsername,
|
||||
&i.OrganizationName,
|
||||
@ -10479,7 +10480,7 @@ func (q *sqlQuerier) GetTemplateByID(ctx context.Context, id uuid.UUID) (Templat
|
||||
|
||||
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, organization_name, organization_display_name, organization_icon
|
||||
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, use_classic_parameter_flow, created_by_avatar_url, created_by_username, organization_name, organization_display_name, organization_icon
|
||||
FROM
|
||||
template_with_names AS templates
|
||||
WHERE
|
||||
@ -10528,6 +10529,7 @@ func (q *sqlQuerier) GetTemplateByOrganizationAndName(ctx context.Context, arg G
|
||||
&i.Deprecated,
|
||||
&i.ActivityBump,
|
||||
&i.MaxPortSharingLevel,
|
||||
&i.UseClassicParameterFlow,
|
||||
&i.CreatedByAvatarURL,
|
||||
&i.CreatedByUsername,
|
||||
&i.OrganizationName,
|
||||
@ -10538,7 +10540,7 @@ func (q *sqlQuerier) GetTemplateByOrganizationAndName(ctx context.Context, arg G
|
||||
}
|
||||
|
||||
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, organization_name, organization_display_name, organization_icon FROM template_with_names 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, use_classic_parameter_flow, created_by_avatar_url, created_by_username, organization_name, organization_display_name, organization_icon FROM template_with_names AS templates
|
||||
ORDER BY (name, id) ASC
|
||||
`
|
||||
|
||||
@ -10580,6 +10582,7 @@ func (q *sqlQuerier) GetTemplates(ctx context.Context) ([]Template, error) {
|
||||
&i.Deprecated,
|
||||
&i.ActivityBump,
|
||||
&i.MaxPortSharingLevel,
|
||||
&i.UseClassicParameterFlow,
|
||||
&i.CreatedByAvatarURL,
|
||||
&i.CreatedByUsername,
|
||||
&i.OrganizationName,
|
||||
@ -10601,7 +10604,7 @@ 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, organization_name, organization_display_name, organization_icon
|
||||
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, use_classic_parameter_flow, created_by_avatar_url, created_by_username, organization_name, organization_display_name, organization_icon
|
||||
FROM
|
||||
template_with_names AS templates
|
||||
WHERE
|
||||
@ -10701,6 +10704,7 @@ func (q *sqlQuerier) GetTemplatesWithFilter(ctx context.Context, arg GetTemplate
|
||||
&i.Deprecated,
|
||||
&i.ActivityBump,
|
||||
&i.MaxPortSharingLevel,
|
||||
&i.UseClassicParameterFlow,
|
||||
&i.CreatedByAvatarURL,
|
||||
&i.CreatedByUsername,
|
||||
&i.OrganizationName,
|
||||
@ -10877,7 +10881,8 @@ SET
|
||||
display_name = $6,
|
||||
allow_user_cancel_workspace_jobs = $7,
|
||||
group_acl = $8,
|
||||
max_port_sharing_level = $9
|
||||
max_port_sharing_level = $9,
|
||||
use_classic_parameter_flow = $10
|
||||
WHERE
|
||||
id = $1
|
||||
`
|
||||
@ -10892,6 +10897,7 @@ type UpdateTemplateMetaByIDParams struct {
|
||||
AllowUserCancelWorkspaceJobs bool `db:"allow_user_cancel_workspace_jobs" json:"allow_user_cancel_workspace_jobs"`
|
||||
GroupACL TemplateACL `db:"group_acl" json:"group_acl"`
|
||||
MaxPortSharingLevel AppSharingLevel `db:"max_port_sharing_level" json:"max_port_sharing_level"`
|
||||
UseClassicParameterFlow bool `db:"use_classic_parameter_flow" json:"use_classic_parameter_flow"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) UpdateTemplateMetaByID(ctx context.Context, arg UpdateTemplateMetaByIDParams) error {
|
||||
@ -10905,6 +10911,7 @@ func (q *sqlQuerier) UpdateTemplateMetaByID(ctx context.Context, arg UpdateTempl
|
||||
arg.AllowUserCancelWorkspaceJobs,
|
||||
arg.GroupACL,
|
||||
arg.MaxPortSharingLevel,
|
||||
arg.UseClassicParameterFlow,
|
||||
)
|
||||
return err
|
||||
}
|
||||
@ -18197,7 +18204,7 @@ LEFT JOIN LATERAL (
|
||||
) latest_build ON TRUE
|
||||
LEFT JOIN LATERAL (
|
||||
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
|
||||
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, use_classic_parameter_flow
|
||||
FROM
|
||||
templates
|
||||
WHERE
|
||||
|
@ -124,7 +124,8 @@ SET
|
||||
display_name = $6,
|
||||
allow_user_cancel_workspace_jobs = $7,
|
||||
group_acl = $8,
|
||||
max_port_sharing_level = $9
|
||||
max_port_sharing_level = $9,
|
||||
use_classic_parameter_flow = $10
|
||||
WHERE
|
||||
id = $1
|
||||
;
|
||||
|
@ -728,6 +728,12 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// Defaults to the existing.
|
||||
classicTemplateFlow := template.UseClassicParameterFlow
|
||||
if req.UseClassicParameterFlow != nil {
|
||||
classicTemplateFlow = *req.UseClassicParameterFlow
|
||||
}
|
||||
|
||||
var updated database.Template
|
||||
err = api.Database.InTx(func(tx database.Store) error {
|
||||
if req.Name == template.Name &&
|
||||
@ -747,6 +753,7 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) {
|
||||
req.TimeTilDormantAutoDeleteMillis == time.Duration(template.TimeTilDormantAutoDelete).Milliseconds() &&
|
||||
req.RequireActiveVersion == template.RequireActiveVersion &&
|
||||
(deprecationMessage == template.Deprecated) &&
|
||||
(classicTemplateFlow == template.UseClassicParameterFlow) &&
|
||||
maxPortShareLevel == template.MaxPortSharingLevel {
|
||||
return nil
|
||||
}
|
||||
@ -788,6 +795,7 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) {
|
||||
AllowUserCancelWorkspaceJobs: req.AllowUserCancelWorkspaceJobs,
|
||||
GroupACL: groupACL,
|
||||
MaxPortSharingLevel: maxPortShareLevel,
|
||||
UseClassicParameterFlow: classicTemplateFlow,
|
||||
})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("update template metadata: %w", err)
|
||||
@ -1066,10 +1074,11 @@ func (api *API) convertTemplate(
|
||||
DaysOfWeek: codersdk.BitmapToWeekdays(template.AutostartAllowedDays()),
|
||||
},
|
||||
// These values depend on entitlements and come from the templateAccessControl
|
||||
RequireActiveVersion: templateAccessControl.RequireActiveVersion,
|
||||
Deprecated: templateAccessControl.IsDeprecated(),
|
||||
DeprecationMessage: templateAccessControl.Deprecated,
|
||||
MaxPortShareLevel: maxPortShareLevel,
|
||||
RequireActiveVersion: templateAccessControl.RequireActiveVersion,
|
||||
Deprecated: templateAccessControl.IsDeprecated(),
|
||||
DeprecationMessage: templateAccessControl.Deprecated,
|
||||
MaxPortShareLevel: maxPortShareLevel,
|
||||
UseClassicParameterFlow: template.UseClassicParameterFlow,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1540,6 +1540,41 @@ func TestPatchTemplateMeta(t *testing.T) {
|
||||
require.False(t, template.Deprecated)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("ClassicParameterFlow", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client := coderdtest.New(t, nil)
|
||||
user := coderdtest.CreateFirstUser(t, client)
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
|
||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||
require.False(t, template.UseClassicParameterFlow, "default is false")
|
||||
|
||||
bTrue := true
|
||||
bFalse := false
|
||||
req := codersdk.UpdateTemplateMeta{
|
||||
UseClassicParameterFlow: &bTrue,
|
||||
}
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
|
||||
// set to true
|
||||
updated, err := client.UpdateTemplateMeta(ctx, template.ID, req)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, updated.UseClassicParameterFlow, "expected true")
|
||||
|
||||
// noop
|
||||
req.UseClassicParameterFlow = nil
|
||||
updated, err = client.UpdateTemplateMeta(ctx, template.ID, req)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, updated.UseClassicParameterFlow, "expected true")
|
||||
|
||||
// back to false
|
||||
req.UseClassicParameterFlow = &bFalse
|
||||
updated, err = client.UpdateTemplateMeta(ctx, template.ID, req)
|
||||
require.NoError(t, err)
|
||||
assert.False(t, updated.UseClassicParameterFlow, "expected false")
|
||||
})
|
||||
}
|
||||
|
||||
func TestDeleteTemplate(t *testing.T) {
|
||||
|
Reference in New Issue
Block a user