diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 6844d166d8f1d..ec11a67fcf0be 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -12043,10 +12043,6 @@ const docTemplate = `{ "dry_run": { "type": "boolean" }, - "enable_dynamic_parameters": { - "description": "EnableDynamicParameters skips some of the static parameter checking.\nIt will default to whatever the template has marked as the default experience.\nRequires the \"dynamic-experiment\" to be used.", - "type": "boolean" - }, "log_level": { "description": "Log level changes the default logging verbosity of a provider (\"info\" if empty).", "enum": [ @@ -12128,9 +12124,6 @@ const docTemplate = `{ "autostart_schedule": { "type": "string" }, - "enable_dynamic_parameters": { - "type": "boolean" - }, "name": { "type": "string" }, diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 35d39cfbe3839..d346b166580f7 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -10753,10 +10753,6 @@ "dry_run": { "type": "boolean" }, - "enable_dynamic_parameters": { - "description": "EnableDynamicParameters skips some of the static parameter checking.\nIt will default to whatever the template has marked as the default experience.\nRequires the \"dynamic-experiment\" to be used.", - "type": "boolean" - }, "log_level": { "description": "Log level changes the default logging verbosity of a provider (\"info\" if empty).", "enum": ["debug"], @@ -10828,9 +10824,6 @@ "autostart_schedule": { "type": "string" }, - "enable_dynamic_parameters": { - "type": "boolean" - }, "name": { "type": "string" }, diff --git a/coderd/dynamicparameters/resolver.go b/coderd/dynamicparameters/resolver.go index bda422ad4b840..f71b630858013 100644 --- a/coderd/dynamicparameters/resolver.go +++ b/coderd/dynamicparameters/resolver.go @@ -26,6 +26,45 @@ type parameterValue struct { Source parameterValueSource } +type ResolverError struct { + Diagnostics hcl.Diagnostics + Parameter map[string]hcl.Diagnostics +} + +// Error is a pretty bad format for these errors. Try to avoid using this. +func (e *ResolverError) Error() string { + var diags hcl.Diagnostics + diags = diags.Extend(e.Diagnostics) + for _, d := range e.Parameter { + diags = diags.Extend(d) + } + + return diags.Error() +} + +func (e *ResolverError) HasError() bool { + if e.Diagnostics.HasErrors() { + return true + } + + for _, diags := range e.Parameter { + if diags.HasErrors() { + return true + } + } + return false +} + +func (e *ResolverError) Extend(parameterName string, diag hcl.Diagnostics) { + if e.Parameter == nil { + e.Parameter = make(map[string]hcl.Diagnostics) + } + if _, ok := e.Parameter[parameterName]; !ok { + e.Parameter[parameterName] = hcl.Diagnostics{} + } + e.Parameter[parameterName] = e.Parameter[parameterName].Extend(diag) +} + //nolint:revive // firstbuild is a control flag to turn on immutable validation func ResolveParameters( ctx context.Context, @@ -35,7 +74,7 @@ func ResolveParameters( previousValues []database.WorkspaceBuildParameter, buildValues []codersdk.WorkspaceBuildParameter, presetValues []database.TemplateVersionPresetParameter, -) (map[string]string, hcl.Diagnostics) { +) (map[string]string, error) { previousValuesMap := slice.ToMapFunc(previousValues, func(p database.WorkspaceBuildParameter) (string, string) { return p.Name, p.Value }) @@ -73,7 +112,10 @@ func ResolveParameters( // always be valid. If there is a case where this is not true, then this has to // be changed to allow the build to continue with a different set of values. - return nil, diags + return nil, &ResolverError{ + Diagnostics: diags, + Parameter: nil, + } } // The user's input now needs to be validated against the parameters. @@ -113,12 +155,16 @@ func ResolveParameters( // are fatal. Additional validation for immutability has to be done manually. output, diags = renderer.Render(ctx, ownerID, values.ValuesMap()) if diags.HasErrors() { - return nil, diags + return nil, &ResolverError{ + Diagnostics: diags, + Parameter: nil, + } } // parameterNames is going to be used to remove any excess values that were left // around without a parameter. parameterNames := make(map[string]struct{}, len(output.Parameters)) + parameterError := &ResolverError{} for _, parameter := range output.Parameters { parameterNames[parameter.Name] = struct{}{} @@ -132,20 +178,22 @@ func ResolveParameters( } // An immutable parameter was changed, which is not allowed. - // Add the failed diagnostic to the output. - diags = diags.Append(&hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Immutable parameter changed", - Detail: fmt.Sprintf("Parameter %q is not mutable, so it can't be updated after creating a workspace.", parameter.Name), - Subject: src, + // Add a failed diagnostic to the output. + parameterError.Extend(parameter.Name, hcl.Diagnostics{ + &hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Immutable parameter changed", + Detail: fmt.Sprintf("Parameter %q is not mutable, so it can't be updated after creating a workspace.", parameter.Name), + Subject: src, + }, }) } } // TODO: Fix the `hcl.Diagnostics(...)` type casting. It should not be needed. if hcl.Diagnostics(parameter.Diagnostics).HasErrors() { - // All validation errors are raised here. - diags = diags.Extend(hcl.Diagnostics(parameter.Diagnostics)) + // All validation errors are raised here for each parameter. + parameterError.Extend(parameter.Name, hcl.Diagnostics(parameter.Diagnostics)) } // If the parameter has a value, but it was not set explicitly by the user at any @@ -174,8 +222,13 @@ func ResolveParameters( } } + if parameterError.HasError() { + // If there are any errors, return them. + return nil, parameterError + } + // Return the values to be saved for the build. - return values.ValuesMap(), diags + return values.ValuesMap(), nil } type parameterValueMap map[string]parameterValue diff --git a/coderd/httpapi/httperror/doc.go b/coderd/httpapi/httperror/doc.go new file mode 100644 index 0000000000000..01a0b3956e3e7 --- /dev/null +++ b/coderd/httpapi/httperror/doc.go @@ -0,0 +1,4 @@ +// Package httperror handles formatting and writing some sentinel errors returned +// within coder to the API. +// This package exists outside httpapi to avoid some cyclic dependencies +package httperror diff --git a/coderd/httpapi/httperror/wsbuild.go b/coderd/httpapi/httperror/wsbuild.go new file mode 100644 index 0000000000000..17436b55d5ae0 --- /dev/null +++ b/coderd/httpapi/httperror/wsbuild.go @@ -0,0 +1,91 @@ +package httperror + +import ( + "context" + "errors" + "fmt" + "net/http" + "sort" + + "github.com/hashicorp/hcl/v2" + + "github.com/coder/coder/v2/coderd/dynamicparameters" + "github.com/coder/coder/v2/coderd/httpapi" + "github.com/coder/coder/v2/coderd/wsbuilder" + "github.com/coder/coder/v2/codersdk" +) + +func WriteWorkspaceBuildError(ctx context.Context, rw http.ResponseWriter, err error) { + var buildErr wsbuilder.BuildError + if errors.As(err, &buildErr) { + if httpapi.IsUnauthorizedError(err) { + buildErr.Status = http.StatusForbidden + } + + httpapi.Write(ctx, rw, buildErr.Status, codersdk.Response{ + Message: buildErr.Message, + Detail: buildErr.Error(), + }) + return + } + + var parameterErr *dynamicparameters.ResolverError + if errors.As(err, ¶meterErr) { + resp := codersdk.Response{ + Message: "Unable to validate parameters", + Validations: nil, + } + + // Sort the parameter names so that the order is consistent. + sortedNames := make([]string, 0, len(parameterErr.Parameter)) + for name := range parameterErr.Parameter { + sortedNames = append(sortedNames, name) + } + sort.Strings(sortedNames) + + for _, name := range sortedNames { + diag := parameterErr.Parameter[name] + resp.Validations = append(resp.Validations, codersdk.ValidationError{ + Field: name, + Detail: DiagnosticsErrorString(diag), + }) + } + + if parameterErr.Diagnostics.HasErrors() { + resp.Detail = DiagnosticsErrorString(parameterErr.Diagnostics) + } + + httpapi.Write(ctx, rw, http.StatusBadRequest, resp) + return + } + + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ + Message: "Internal error creating workspace build.", + Detail: err.Error(), + }) +} + +func DiagnosticError(d *hcl.Diagnostic) string { + return fmt.Sprintf("%s; %s", d.Summary, d.Detail) +} + +func DiagnosticsErrorString(d hcl.Diagnostics) string { + count := len(d) + switch { + case count == 0: + return "no diagnostics" + case count == 1: + return DiagnosticError(d[0]) + default: + for _, d := range d { + // Render the first error diag. + // If there are warnings, do not priority them over errors. + if d.Severity == hcl.DiagError { + return fmt.Sprintf("%s, and %d other diagnostic(s)", DiagnosticError(d), count-1) + } + } + + // All warnings? ok... + return fmt.Sprintf("%s, and %d other diagnostic(s)", DiagnosticError(d[0]), count-1) + } +} diff --git a/coderd/parameters_test.go b/coderd/parameters_test.go index a18000673b50e..3a5cae7adbe5b 100644 --- a/coderd/parameters_test.go +++ b/coderd/parameters_test.go @@ -15,7 +15,6 @@ import ( "github.com/coder/coder/v2/coderd/database/dbtestutil" "github.com/coder/coder/v2/coderd/database/pubsub" "github.com/coder/coder/v2/coderd/rbac" - "github.com/coder/coder/v2/coderd/util/ptr" "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/codersdk/wsjson" "github.com/coder/coder/v2/provisioner/echo" @@ -260,7 +259,6 @@ func TestDynamicParametersWithTerraformValues(t *testing.T) { Value: "eu", }, } - request.EnableDynamicParameters = true }) coderdtest.AwaitWorkspaceBuildJobCompleted(t, setup.client, wrk.LatestBuild.ID) @@ -285,7 +283,6 @@ func TestDynamicParametersWithTerraformValues(t *testing.T) { RichParameterValues: []codersdk.WorkspaceBuildParameter{ {Name: "region", Value: regionVal}, }, - EnableDynamicParameters: ptr.Ref(true), }) require.NoError(t, err) coderdtest.AwaitWorkspaceBuildJobCompleted(t, setup.client, bld.ID) diff --git a/coderd/workspacebuilds.go b/coderd/workspacebuilds.go index 526be7e4e4f89..850e9dd457dd8 100644 --- a/coderd/workspacebuilds.go +++ b/coderd/workspacebuilds.go @@ -27,6 +27,7 @@ import ( "github.com/coder/coder/v2/coderd/database/dbtime" "github.com/coder/coder/v2/coderd/database/provisionerjobs" "github.com/coder/coder/v2/coderd/httpapi" + "github.com/coder/coder/v2/coderd/httpapi/httperror" "github.com/coder/coder/v2/coderd/httpmw" "github.com/coder/coder/v2/coderd/notifications" "github.com/coder/coder/v2/coderd/provisionerdserver" @@ -385,10 +386,6 @@ func (api *API) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) { builder = builder.State(createBuild.ProvisionerState) } - if createBuild.EnableDynamicParameters != nil { - builder = builder.DynamicParameters(*createBuild.EnableDynamicParameters) - } - workspaceBuild, provisionerJob, provisionerDaemons, err = builder.Build( ctx, tx, @@ -410,28 +407,8 @@ func (api *API) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) { ) return err }, nil) - var buildErr wsbuilder.BuildError - if xerrors.As(err, &buildErr) { - var authErr dbauthz.NotAuthorizedError - if xerrors.As(err, &authErr) { - buildErr.Status = http.StatusForbidden - } - - if buildErr.Status == http.StatusInternalServerError { - api.Logger.Error(ctx, "workspace build error", slog.Error(buildErr.Wrapped)) - } - - httpapi.Write(ctx, rw, buildErr.Status, codersdk.Response{ - Message: buildErr.Message, - Detail: buildErr.Error(), - }) - return - } if err != nil { - httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ - Message: "Error posting new build", - Detail: err.Error(), - }) + httperror.WriteWorkspaceBuildError(ctx, rw, err) return } diff --git a/coderd/workspaces.go b/coderd/workspaces.go index 58375ae4c4d45..ecb624d1bc09f 100644 --- a/coderd/workspaces.go +++ b/coderd/workspaces.go @@ -27,6 +27,7 @@ import ( "github.com/coder/coder/v2/coderd/database/dbtime" "github.com/coder/coder/v2/coderd/database/provisionerjobs" "github.com/coder/coder/v2/coderd/httpapi" + "github.com/coder/coder/v2/coderd/httpapi/httperror" "github.com/coder/coder/v2/coderd/httpmw" "github.com/coder/coder/v2/coderd/notifications" "github.com/coder/coder/v2/coderd/prebuilds" @@ -717,10 +718,6 @@ func createWorkspace( builder = builder.MarkPrebuiltWorkspaceClaim() } - if req.EnableDynamicParameters { - builder = builder.DynamicParameters(req.EnableDynamicParameters) - } - workspaceBuild, provisionerJob, provisionerDaemons, err = builder.Build( ctx, db, @@ -732,21 +729,11 @@ func createWorkspace( ) return err }, nil) - var bldErr wsbuilder.BuildError - if xerrors.As(err, &bldErr) { - httpapi.Write(ctx, rw, bldErr.Status, codersdk.Response{ - Message: bldErr.Message, - Detail: bldErr.Error(), - }) - return - } if err != nil { - httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ - Message: "Internal error creating workspace.", - Detail: err.Error(), - }) + httperror.WriteWorkspaceBuildError(ctx, rw, err) return } + err = provisionerjobs.PostJob(api.Pubsub, *provisionerJob) if err != nil { // Client probably doesn't care about this error, so just log it. diff --git a/coderd/wsbuilder/wsbuilder.go b/coderd/wsbuilder/wsbuilder.go index aaccd337ee793..f327e071c3946 100644 --- a/coderd/wsbuilder/wsbuilder.go +++ b/coderd/wsbuilder/wsbuilder.go @@ -57,12 +57,10 @@ type Builder struct { deploymentValues *codersdk.DeploymentValues experiments codersdk.Experiments - richParameterValues []codersdk.WorkspaceBuildParameter - // dynamicParametersEnabled is non-nil if set externally - dynamicParametersEnabled *bool - initiator uuid.UUID - reason database.BuildReason - templateVersionPresetID uuid.UUID + richParameterValues []codersdk.WorkspaceBuildParameter + initiator uuid.UUID + reason database.BuildReason + templateVersionPresetID uuid.UUID // used during build, makes function arguments less verbose ctx context.Context @@ -204,12 +202,6 @@ func (b Builder) MarkPrebuiltWorkspaceClaim() Builder { return b } -func (b Builder) DynamicParameters(using bool) Builder { - // nolint: revive - b.dynamicParametersEnabled = ptr.Ref(using) - return b -} - // SetLastWorkspaceBuildInTx prepopulates the Builder's cache with the last workspace build. This allows us // to avoid a repeated database query when the Builder's caller also needs the workspace build, e.g. auto-start & // auto-stop. @@ -760,20 +752,12 @@ func (b *Builder) getDynamicParameters() (names, values []string, err error) { return nil, nil, BuildError{http.StatusInternalServerError, "failed to check if first build", err} } - buildValues, diagnostics := dynamicparameters.ResolveParameters(b.ctx, b.workspace.OwnerID, render, firstBuild, + buildValues, err := dynamicparameters.ResolveParameters(b.ctx, b.workspace.OwnerID, render, firstBuild, lastBuildParameters, b.richParameterValues, presetParameterValues) - - if diagnostics.HasErrors() { - // TODO: Improve the error response. The response should include the validations for each failed - // parameter. The response should also indicate it's a validation error or a more general form failure. - // For now, any error is sufficient. - return nil, nil, BuildError{ - Status: http.StatusBadRequest, - Message: fmt.Sprintf("%d errors occurred while resolving parameters", len(diagnostics)), - Wrapped: diagnostics, - } + if err != nil { + return nil, nil, xerrors.Errorf("resolve parameters: %w", err) } names = make([]string, 0, len(buildValues)) @@ -1211,10 +1195,6 @@ func (b *Builder) checkRunningBuild() error { } func (b *Builder) usingDynamicParameters() bool { - if b.dynamicParametersEnabled != nil { - return *b.dynamicParametersEnabled - } - tpl, err := b.getTemplate() if err != nil { return false // Let another part of the code get this error diff --git a/codersdk/organizations.go b/codersdk/organizations.go index 728540ef2e6e1..08c1fb0034b46 100644 --- a/codersdk/organizations.go +++ b/codersdk/organizations.go @@ -227,7 +227,6 @@ type CreateWorkspaceRequest struct { RichParameterValues []WorkspaceBuildParameter `json:"rich_parameter_values,omitempty"` AutomaticUpdates AutomaticUpdates `json:"automatic_updates,omitempty"` TemplateVersionPresetID uuid.UUID `json:"template_version_preset_id,omitempty" format:"uuid"` - EnableDynamicParameters bool `json:"enable_dynamic_parameters,omitempty"` } func (c *Client) OrganizationByName(ctx context.Context, name string) (Organization, error) { diff --git a/codersdk/workspaces.go b/codersdk/workspaces.go index 2c73d60a2696c..c776f2cf5a473 100644 --- a/codersdk/workspaces.go +++ b/codersdk/workspaces.go @@ -110,10 +110,6 @@ type CreateWorkspaceBuildRequest struct { LogLevel ProvisionerLogLevel `json:"log_level,omitempty" validate:"omitempty,oneof=debug"` // TemplateVersionPresetID is the ID of the template version preset to use for the build. TemplateVersionPresetID uuid.UUID `json:"template_version_preset_id,omitempty" format:"uuid"` - // EnableDynamicParameters skips some of the static parameter checking. - // It will default to whatever the template has marked as the default experience. - // Requires the "dynamic-experiment" to be used. - EnableDynamicParameters *bool `json:"enable_dynamic_parameters,omitempty"` } type WorkspaceOptions struct { diff --git a/docs/reference/api/builds.md b/docs/reference/api/builds.md index c47b89d0bbba1..bb279f5825f6e 100644 --- a/docs/reference/api/builds.md +++ b/docs/reference/api/builds.md @@ -1752,7 +1752,6 @@ curl -X POST http://coder-server:8080/api/v2/workspaces/{workspace}/builds \ ```json { "dry_run": true, - "enable_dynamic_parameters": true, "log_level": "debug", "orphan": true, "rich_parameter_values": [ diff --git a/docs/reference/api/schemas.md b/docs/reference/api/schemas.md index e0999f6bb3778..d1da727a45816 100644 --- a/docs/reference/api/schemas.md +++ b/docs/reference/api/schemas.md @@ -1917,7 +1917,6 @@ This is required on creation to enable a user-flow of validating a template work ```json { "dry_run": true, - "enable_dynamic_parameters": true, "log_level": "debug", "orphan": true, "rich_parameter_values": [ @@ -1940,7 +1939,6 @@ This is required on creation to enable a user-flow of validating a template work | Name | Type | Required | Restrictions | Description | |------------------------------|-------------------------------------------------------------------------------|----------|--------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `dry_run` | boolean | false | | | -| `enable_dynamic_parameters` | boolean | false | | Enable dynamic parameters skips some of the static parameter checking. It will default to whatever the template has marked as the default experience. Requires the "dynamic-experiment" to be used. | | `log_level` | [codersdk.ProvisionerLogLevel](#codersdkprovisionerloglevel) | false | | Log level changes the default logging verbosity of a provider ("info" if empty). | | `orphan` | boolean | false | | Orphan may be set for the Destroy transition. | | `rich_parameter_values` | array of [codersdk.WorkspaceBuildParameter](#codersdkworkspacebuildparameter) | false | | Rich parameter values are optional. It will write params to the 'workspace' scope. This will overwrite any existing parameters with the same name. This will not delete old params not included in this list. | @@ -1982,7 +1980,6 @@ This is required on creation to enable a user-flow of validating a template work { "automatic_updates": "always", "autostart_schedule": "string", - "enable_dynamic_parameters": true, "name": "string", "rich_parameter_values": [ { @@ -2005,7 +2002,6 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o |------------------------------|-------------------------------------------------------------------------------|----------|--------------|---------------------------------------------------------------------------------------------------------| | `automatic_updates` | [codersdk.AutomaticUpdates](#codersdkautomaticupdates) | false | | | | `autostart_schedule` | string | false | | | -| `enable_dynamic_parameters` | boolean | false | | | | `name` | string | true | | | | `rich_parameter_values` | array of [codersdk.WorkspaceBuildParameter](#codersdkworkspacebuildparameter) | false | | Rich parameter values allows for additional parameters to be provided during the initial provision. | | `template_id` | string | false | | Template ID specifies which template should be used for creating the workspace. | diff --git a/docs/reference/api/workspaces.md b/docs/reference/api/workspaces.md index a13c61cc05ee6..a43a5f2c8fe18 100644 --- a/docs/reference/api/workspaces.md +++ b/docs/reference/api/workspaces.md @@ -25,7 +25,6 @@ of the template will be used. { "automatic_updates": "always", "autostart_schedule": "string", - "enable_dynamic_parameters": true, "name": "string", "rich_parameter_values": [ { @@ -624,7 +623,6 @@ of the template will be used. { "automatic_updates": "always", "autostart_schedule": "string", - "enable_dynamic_parameters": true, "name": "string", "rich_parameter_values": [ { diff --git a/enterprise/coderd/workspaces_test.go b/enterprise/coderd/workspaces_test.go index 125647b099282..ef9d1b977ea00 100644 --- a/enterprise/coderd/workspaces_test.go +++ b/enterprise/coderd/workspaces_test.go @@ -1759,7 +1759,6 @@ func TestWorkspaceTemplateParamsChange(t *testing.T) { Value: "7", }, }, - EnableDynamicParameters: true, }) // Then: the build should succeed. The updated value of param_min should be diff --git a/site/package.json b/site/package.json index b099706bd57a3..7f63035231d69 100644 --- a/site/package.json +++ b/site/package.json @@ -64,6 +64,7 @@ "@radix-ui/react-radio-group": "1.2.3", "@radix-ui/react-scroll-area": "1.2.3", "@radix-ui/react-select": "2.1.4", + "@radix-ui/react-separator": "1.1.7", "@radix-ui/react-slider": "1.2.2", "@radix-ui/react-slot": "1.1.1", "@radix-ui/react-switch": "1.1.1", diff --git a/site/pnpm-lock.yaml b/site/pnpm-lock.yaml index 7b332074b32fc..e626209d2c754 100644 --- a/site/pnpm-lock.yaml +++ b/site/pnpm-lock.yaml @@ -106,6 +106,9 @@ importers: '@radix-ui/react-select': specifier: 2.1.4 version: 2.1.4(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-separator': + specifier: 1.1.7 + version: 1.1.7(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-slider': specifier: 1.2.2 version: 1.2.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -1615,6 +1618,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-compose-refs@1.1.2': + resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==, tarball: https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-context@1.1.1': resolution: {integrity: sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==, tarball: https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz} peerDependencies: @@ -1833,6 +1845,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-primitive@2.1.3': + resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==, tarball: https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-radio-group@1.2.3': resolution: {integrity: sha512-xtCsqt8Rp09FK50ItqEqTJ7Sxanz8EM8dnkVIhJrc/wkMMomSmXHvYbhv3E7Zx4oXh98aaLt9W679SUYXg4IDA==, tarball: https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.2.3.tgz} peerDependencies: @@ -1898,6 +1923,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-separator@1.1.7': + resolution: {integrity: sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA==, tarball: https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.7.tgz} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-slider@1.2.2': resolution: {integrity: sha512-sNlU06ii1/ZcbHf8I9En54ZPW0Vil/yPVg4vQMcFNjrIx51jsHbFl1HYHQvCIWJSr1q0ZmA+iIs/ZTv8h7HHSA==, tarball: https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.2.2.tgz} peerDependencies: @@ -1938,6 +1976,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-slot@1.2.3': + resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==, tarball: https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-switch@1.1.1': resolution: {integrity: sha512-diPqDDoBcZPSicYoMWdWx+bCPuTRH4QSp9J+65IvtdS0Kuzt67bI6n32vCj8q6NZmYW/ah+2orOtMwcX5eQwIg==, tarball: https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.1.1.tgz} peerDependencies: @@ -7792,6 +7839,12 @@ snapshots: optionalDependencies: '@types/react': 18.3.12 + '@radix-ui/react-compose-refs@1.1.2(@types/react@18.3.12)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.12 + '@radix-ui/react-context@1.1.1(@types/react@18.3.12)(react@18.3.1)': dependencies: react: 18.3.1 @@ -8014,6 +8067,15 @@ snapshots: '@types/react': 18.3.12 '@types/react-dom': 18.3.1 + '@radix-ui/react-primitive@2.1.3(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-slot': 1.2.3(@types/react@18.3.12)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.12 + '@types/react-dom': 18.3.1 + '@radix-ui/react-radio-group@1.2.3(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.1 @@ -8112,6 +8174,15 @@ snapshots: '@types/react': 18.3.12 '@types/react-dom': 18.3.1 + '@radix-ui/react-separator@1.1.7(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.12 + '@types/react-dom': 18.3.1 + '@radix-ui/react-slider@1.2.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/number': 1.1.0 @@ -8152,6 +8223,13 @@ snapshots: optionalDependencies: '@types/react': 18.3.12 + '@radix-ui/react-slot@1.2.3(@types/react@18.3.12)(react@18.3.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.12)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.12 + '@radix-ui/react-switch@1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.0 diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index d536ac3a0fe5e..9a5acb4fbe569 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -490,7 +490,6 @@ export interface CreateWorkspaceBuildRequest { readonly rich_parameter_values?: readonly WorkspaceBuildParameter[]; readonly log_level?: ProvisionerLogLevel; readonly template_version_preset_id?: string; - readonly enable_dynamic_parameters?: boolean; } // From codersdk/workspaceproxy.go @@ -510,7 +509,6 @@ export interface CreateWorkspaceRequest { readonly rich_parameter_values?: readonly WorkspaceBuildParameter[]; readonly automatic_updates?: AutomaticUpdates; readonly template_version_preset_id?: string; - readonly enable_dynamic_parameters?: boolean; } // From codersdk/deployment.go diff --git a/site/src/components/Separator/Separator.tsx b/site/src/components/Separator/Separator.tsx new file mode 100644 index 0000000000000..e18975eb2da58 --- /dev/null +++ b/site/src/components/Separator/Separator.tsx @@ -0,0 +1,30 @@ +import * as SeparatorPrimitive from "@radix-ui/react-separator"; +/** + * Copied from shadc/ui on 06/20/2025 + * @see {@link https://ui.shadcn.com/docs/components/separator} + */ +import type * as React from "react"; + +import { cn } from "utils/cn"; + +function Separator({ + className, + orientation = "horizontal", + decorative = true, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +export { Separator }; diff --git a/site/src/components/Skeleton/Skeleton.tsx b/site/src/components/Skeleton/Skeleton.tsx new file mode 100644 index 0000000000000..da5d5a7f1ddd0 --- /dev/null +++ b/site/src/components/Skeleton/Skeleton.tsx @@ -0,0 +1,17 @@ +/** + * Copied from shadc/ui on 06/20/2025 + * @see {@link https://ui.shadcn.com/docs/components/skeleton} + */ +import { cn } from "utils/cn"; + +function Skeleton({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ); +} + +export { Skeleton }; diff --git a/site/src/modules/workspaces/DynamicParameter/DynamicParameter.tsx b/site/src/modules/workspaces/DynamicParameter/DynamicParameter.tsx index 9f97d558c8f08..ec04bd7ea8e09 100644 --- a/site/src/modules/workspaces/DynamicParameter/DynamicParameter.tsx +++ b/site/src/modules/workspaces/DynamicParameter/DynamicParameter.tsx @@ -52,7 +52,7 @@ interface DynamicParameterProps { onChange: (value: string) => void; disabled?: boolean; isPreset?: boolean; - autofill: boolean; + autofill?: boolean; } export const DynamicParameter: FC = ({ @@ -873,7 +873,6 @@ interface DiagnosticsProps { diagnostics: PreviewParameter["diagnostics"]; } -// Displays a diagnostic with a border, icon and background color export const Diagnostics: FC = ({ diagnostics }) => { return (
diff --git a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageExperimental.tsx b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageExperimental.tsx index 070576a5e9a99..2e39c5625a6cb 100644 --- a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageExperimental.tsx +++ b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageExperimental.tsx @@ -326,7 +326,6 @@ const CreateWorkspacePageExperimental: FC = () => { const workspace = await createWorkspaceMutation.mutateAsync({ ...workspaceRequest, - enable_dynamic_parameters: true, userId: owner.id, }); onCreateWorkspace(workspace); diff --git a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageViewExperimental.tsx b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageViewExperimental.tsx index 3522d24012445..ef657c3fa297c 100644 --- a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageViewExperimental.tsx +++ b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageViewExperimental.tsx @@ -29,8 +29,8 @@ import { type FormikContextType, useFormik } from "formik"; import type { ExternalAuthPollingState } from "hooks/useExternalAuth"; import { ArrowLeft, CircleHelp } from "lucide-react"; import { useSyncFormParameters } from "modules/hooks/useSyncFormParameters"; -import { Diagnostics } from "modules/workspaces/DynamicParameter/DynamicParameter"; import { + Diagnostics, DynamicParameter, getInitialParameterValues, useValidationSchemaForDynamicParameters, diff --git a/site/src/pages/TaskPage/TaskAppIframe.tsx b/site/src/pages/TaskPage/TaskAppIframe.tsx index 860fc64cbbcec..b995dfec771b6 100644 --- a/site/src/pages/TaskPage/TaskAppIframe.tsx +++ b/site/src/pages/TaskPage/TaskAppIframe.tsx @@ -58,42 +58,44 @@ export const TaskAppIFrame: FC = ({ return (
-
- + {app.slug === "preview" && ( +
+ - {/* Possibly we will put a URL bar here, but for now we cannot due to - * cross-origin restrictions in iframes. */} -
+ {/* Possibly we will put a URL bar here, but for now we cannot due to + * cross-origin restrictions in iframes. */} +
- - - - - - - - - Open app in new tab - - - - -
+ + + + + + + + + Open app in new tab + + + + +
+ )}