chore: update generated array type definitions in TypeScript to be readonly (#12947)

* chore: types generated handling readonly slices

* add -update flag to update goldens

* revert excess gens

* fix: update most UI types to account for readonly modifiers

* fix: remove accidental mutation from NavBarView

* fix: remove mutation warning for BatchUpdateConfirmation stories

* fix: remove mutation warning for BactchUpdateConfirmation

* fix: format ActiveUserChart

* fix: update import to make linter happy

* fix: update fmt issue

* fix: disable file write lint rule from unit test

---------

Co-authored-by: Parkreiner <throwawayclover@gmail.com>
This commit is contained in:
Steven Masley 2024-04-15 08:46:10 -05:00 committed by GitHub
parent 7cf8577f1c
commit d9da054c9d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
40 changed files with 284 additions and 228 deletions

View File

@ -814,11 +814,17 @@ func (g *Generator) typescriptType(ty types.Type) (TypescriptType, error) {
return TypescriptType{}, xerrors.Errorf("array: %w", err) return TypescriptType{}, xerrors.Errorf("array: %w", err)
} }
genValue := "" genValue := ""
// Always wrap in parentheses for proper scoped types.
// Running prettier on this output will remove redundant parenthesis,
// so this makes our decision-making easier.
// The example that breaks without this is:
// readonly readonly string[][]
if underlying.GenericValue != "" { if underlying.GenericValue != "" {
genValue = underlying.GenericValue + "[]" genValue = "(readonly " + underlying.GenericValue + "[])"
} }
return TypescriptType{ return TypescriptType{
ValueType: underlying.ValueType + "[]", ValueType: "(readonly " + underlying.ValueType + "[])",
GenericValue: genValue, GenericValue: genValue,
AboveTypeLine: underlying.AboveTypeLine, AboveTypeLine: underlying.AboveTypeLine,
GenericTypes: underlying.GenericTypes, GenericTypes: underlying.GenericTypes,

View File

@ -7,6 +7,7 @@
package main package main
import ( import (
"flag"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
@ -15,6 +16,9 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
// updateGoldenFiles is a flag that can be set to update golden files.
var updateGoldenFiles = flag.Bool("update", false, "Update golden files")
func TestGeneration(t *testing.T) { func TestGeneration(t *testing.T) {
t.Parallel() t.Parallel()
files, err := os.ReadDir("testdata") files, err := os.ReadDir("testdata")
@ -37,7 +41,13 @@ func TestGeneration(t *testing.T) {
require.NoErrorf(t, err, "read file %s", golden) require.NoErrorf(t, err, "read file %s", golden)
expectedString := strings.TrimSpace(string(expected)) expectedString := strings.TrimSpace(string(expected))
output = strings.TrimSpace(output) output = strings.TrimSpace(output)
require.Equal(t, expectedString, output, "matched output") if *updateGoldenFiles {
// nolint:gosec
err := os.WriteFile(golden, []byte(output), 0o644)
require.NoError(t, err, "write golden file")
} else {
require.Equal(t, expectedString, output, "matched output")
}
}) })
} }
} }

View File

@ -1,8 +1,8 @@
package codersdk package codersdk
type ( type (
Enum string Enum string
Enums []Enum EnumSliceType []Enum
) )
const ( const (

View File

@ -1,5 +1,5 @@
// From codersdk/enums.go // From codersdk/enums.go
export type Enums = Enum[] export type EnumSliceType = (readonly Enum[])
// From codersdk/enums.go // From codersdk/enums.go
export type Enum = "bar" | "baz" | "foo" | "qux" export type Enum = "bar" | "baz" | "foo" | "qux"

View File

@ -1,22 +1,22 @@
package codersdk package codersdk
type Foo struct {
Bar string `json:"bar"`
}
type Buzz struct { type Buzz struct {
Foo `json:"foo"` Foo `json:"foo"`
Bazz string `json:"bazz"` Bazz string `json:"bazz"`
} }
type Custom interface { type Foo struct {
Foo | Buzz Bar string `json:"bar"`
} }
type FooBuzz[R Custom] struct { type FooBuzz[R Custom] struct {
Something []R `json:"something"` Something []R `json:"something"`
} }
type Custom interface {
Foo | Buzz
}
// Not yet supported // Not yet supported
//type FooBuzzMap[R Custom] struct { //type FooBuzzMap[R Custom] struct {
// Something map[string]R `json:"something"` // Something map[string]R `json:"something"`

View File

@ -11,8 +11,8 @@ export interface Foo {
// From codersdk/genericmap.go // From codersdk/genericmap.go
export interface FooBuzz<R extends Custom> { export interface FooBuzz<R extends Custom> {
readonly something: R[] readonly something: (readonly R[])
} }
// From codersdk/genericmap.go // From codersdk/genericmap.go
export type Custom = Foo | Buzz export type Custom = Foo | Buzz

View File

@ -33,9 +33,9 @@ export interface Static {
} }
// From codersdk/generics.go // From codersdk/generics.go
export type Custom = string | boolean | number | string[] | null export type Custom = string | boolean | number | (readonly string[]) | null
// From codersdk/generics.go // From codersdk/generics.go
export type Single = string export type Single = string
export type comparable = boolean | number | string | any export type comparable = boolean | number | string | any

View File

@ -0,0 +1,10 @@
package codersdk
type Bar struct {
Bar string
}
type Foo[R any] struct {
Slice []R
TwoD [][]R
}

View File

@ -0,0 +1,10 @@
// From codersdk/genericslice.go
export interface Bar {
readonly Bar: string
}
// From codersdk/genericslice.go
export interface Foo<R extends any> {
readonly Slice: (readonly R[])
readonly TwoD: (readonly (readonly R[])[])
}

View File

@ -4,8 +4,8 @@
// From codersdk/templates.go // From codersdk/templates.go
export interface ACLAvailable { export interface ACLAvailable {
readonly users: ReducedUser[]; readonly users: readonly ReducedUser[];
readonly groups: Group[]; readonly groups: readonly Group[];
} }
// From codersdk/apikey.go // From codersdk/apikey.go
@ -49,7 +49,7 @@ export interface AppearanceConfig {
readonly application_name: string; readonly application_name: string;
readonly logo_url: string; readonly logo_url: string;
readonly service_banner: ServiceBannerConfig; readonly service_banner: ServiceBannerConfig;
readonly support_links?: LinkConfig[]; readonly support_links?: readonly LinkConfig[];
} }
// From codersdk/templates.go // From codersdk/templates.go
@ -60,7 +60,7 @@ export interface ArchiveTemplateVersionsRequest {
// From codersdk/templates.go // From codersdk/templates.go
export interface ArchiveTemplateVersionsResponse { export interface ArchiveTemplateVersionsResponse {
readonly template_id: string; readonly template_id: string;
readonly archived_ids: string[]; readonly archived_ids: readonly string[];
} }
// From codersdk/roles.go // From codersdk/roles.go
@ -108,7 +108,7 @@ export interface AuditLog {
// From codersdk/audit.go // From codersdk/audit.go
export interface AuditLogResponse { export interface AuditLogResponse {
readonly audit_logs: AuditLog[]; readonly audit_logs: readonly AuditLog[];
readonly count: number; readonly count: number;
} }
@ -153,7 +153,7 @@ export type AuthorizationResponse = Record<string, boolean>;
// From codersdk/deployment.go // From codersdk/deployment.go
export interface AvailableExperiments { export interface AvailableExperiments {
readonly safe: Experiment[]; readonly safe: readonly Experiment[];
} }
// From codersdk/deployment.go // From codersdk/deployment.go
@ -241,8 +241,8 @@ export interface CreateTemplateRequest {
// From codersdk/templateversions.go // From codersdk/templateversions.go
export interface CreateTemplateVersionDryRunRequest { export interface CreateTemplateVersionDryRunRequest {
readonly workspace_name: string; readonly workspace_name: string;
readonly rich_parameter_values: WorkspaceBuildParameter[]; readonly rich_parameter_values: readonly WorkspaceBuildParameter[];
readonly user_variable_values?: VariableValue[]; readonly user_variable_values?: readonly VariableValue[];
} }
// From codersdk/organizations.go // From codersdk/organizations.go
@ -255,7 +255,7 @@ export interface CreateTemplateVersionRequest {
readonly example_id?: string; readonly example_id?: string;
readonly provisioner: ProvisionerType; readonly provisioner: ProvisionerType;
readonly tags: Record<string, string>; readonly tags: Record<string, string>;
readonly user_variable_values?: VariableValue[]; readonly user_variable_values?: readonly VariableValue[];
} }
// From codersdk/audit.go // From codersdk/audit.go
@ -292,7 +292,7 @@ export interface CreateWorkspaceBuildRequest {
readonly dry_run?: boolean; readonly dry_run?: boolean;
readonly state?: string; readonly state?: string;
readonly orphan?: boolean; readonly orphan?: boolean;
readonly rich_parameter_values?: WorkspaceBuildParameter[]; readonly rich_parameter_values?: readonly WorkspaceBuildParameter[];
readonly log_level?: ProvisionerLogLevel; readonly log_level?: ProvisionerLogLevel;
} }
@ -310,7 +310,7 @@ export interface CreateWorkspaceRequest {
readonly name: string; readonly name: string;
readonly autostart_schedule?: string; readonly autostart_schedule?: string;
readonly ttl_ms?: number; readonly ttl_ms?: number;
readonly rich_parameter_values?: WorkspaceBuildParameter[]; readonly rich_parameter_values?: readonly WorkspaceBuildParameter[];
readonly automatic_updates?: AutomaticUpdates; readonly automatic_updates?: AutomaticUpdates;
} }
@ -327,7 +327,7 @@ export interface DAURequest {
// From codersdk/deployment.go // From codersdk/deployment.go
export interface DAUsResponse { export interface DAUsResponse {
readonly entries: DAUEntry[]; readonly entries: readonly DAUEntry[];
readonly tz_hour_offset: number; readonly tz_hour_offset: number;
} }
@ -434,7 +434,7 @@ export interface DeploymentValues {
readonly session_lifetime?: SessionLifetime; readonly session_lifetime?: SessionLifetime;
readonly disable_password_auth?: boolean; readonly disable_password_auth?: boolean;
readonly support?: SupportConfig; readonly support?: SupportConfig;
readonly external_auth?: ExternalAuthConfig[]; readonly external_auth?: readonly ExternalAuthConfig[];
readonly config_ssh?: SSHConfig; readonly config_ssh?: SSHConfig;
readonly wgtunnel_host?: string; readonly wgtunnel_host?: string;
readonly disable_owner_workspace_exec?: boolean; readonly disable_owner_workspace_exec?: boolean;
@ -453,8 +453,8 @@ export interface DeploymentValues {
// From codersdk/deployment.go // From codersdk/deployment.go
export interface Entitlements { export interface Entitlements {
readonly features: Record<FeatureName, Feature>; readonly features: Record<FeatureName, Feature>;
readonly warnings: string[]; readonly warnings: readonly string[];
readonly errors: string[]; readonly errors: readonly string[];
readonly has_license: boolean; readonly has_license: boolean;
readonly trial: boolean; readonly trial: boolean;
readonly require_telemetry: boolean; readonly require_telemetry: boolean;
@ -462,7 +462,7 @@ export interface Entitlements {
} }
// From codersdk/deployment.go // From codersdk/deployment.go
export type Experiments = Experiment[]; export type Experiments = readonly Experiment[];
// From codersdk/externalauth.go // From codersdk/externalauth.go
export interface ExternalAuth { export interface ExternalAuth {
@ -471,7 +471,7 @@ export interface ExternalAuth {
readonly display_name: string; readonly display_name: string;
readonly user?: ExternalAuthUser; readonly user?: ExternalAuthUser;
readonly app_installable: boolean; readonly app_installable: boolean;
readonly installations: ExternalAuthAppInstallation[]; readonly installations: readonly ExternalAuthAppInstallation[];
readonly app_install_url: string; readonly app_install_url: string;
} }
@ -493,8 +493,8 @@ export interface ExternalAuthConfig {
readonly app_install_url: string; readonly app_install_url: string;
readonly app_installations_url: string; readonly app_installations_url: string;
readonly no_refresh: boolean; readonly no_refresh: boolean;
readonly scopes: string[]; readonly scopes: readonly string[];
readonly extra_token_keys: string[]; readonly extra_token_keys: readonly string[];
readonly device_flow: boolean; readonly device_flow: boolean;
readonly device_code_url: string; readonly device_code_url: string;
readonly regex: string; readonly regex: string;
@ -561,7 +561,7 @@ export interface GenerateAPIKeyResponse {
// From codersdk/users.go // From codersdk/users.go
export interface GetUsersResponse { export interface GetUsersResponse {
readonly users: User[]; readonly users: readonly User[];
readonly count: number; readonly count: number;
} }
@ -579,7 +579,7 @@ export interface Group {
readonly name: string; readonly name: string;
readonly display_name: string; readonly display_name: string;
readonly organization_id: string; readonly organization_id: string;
readonly members: ReducedUser[]; readonly members: readonly ReducedUser[];
readonly avatar_url: string; readonly avatar_url: string;
readonly quota_allowance: number; readonly quota_allowance: number;
readonly source: GroupSource; readonly source: GroupSource;
@ -638,8 +638,8 @@ export interface LinkConfig {
// From codersdk/externalauth.go // From codersdk/externalauth.go
export interface ListUserExternalAuthResponse { export interface ListUserExternalAuthResponse {
readonly providers: ExternalAuthLinkProvider[]; readonly providers: readonly ExternalAuthLinkProvider[];
readonly links: ExternalAuthLink[]; readonly links: readonly ExternalAuthLink[];
} }
// From codersdk/deployment.go // From codersdk/deployment.go
@ -753,7 +753,7 @@ export interface OIDCConfig {
readonly groups_field: string; readonly groups_field: string;
readonly group_mapping: Record<string, string>; readonly group_mapping: Record<string, string>;
readonly user_role_field: string; readonly user_role_field: string;
readonly user_role_mapping: Record<string, string[]>; readonly user_role_mapping: Record<string, readonly string[]>;
readonly user_roles_default: string[]; readonly user_roles_default: string[];
readonly sign_in_text: string; readonly sign_in_text: string;
readonly icon_url: string; readonly icon_url: string;
@ -775,7 +775,7 @@ export interface OrganizationMember {
readonly organization_id: string; readonly organization_id: string;
readonly created_at: string; readonly created_at: string;
readonly updated_at: string; readonly updated_at: string;
readonly roles: Role[]; readonly roles: readonly Role[];
} }
// From codersdk/pagination.go // From codersdk/pagination.go
@ -787,8 +787,8 @@ export interface Pagination {
// From codersdk/groups.go // From codersdk/groups.go
export interface PatchGroupRequest { export interface PatchGroupRequest {
readonly add_users: string[]; readonly add_users: readonly string[];
readonly remove_users: string[]; readonly remove_users: readonly string[];
readonly name: string; readonly name: string;
readonly display_name?: string; readonly display_name?: string;
readonly avatar_url?: string; readonly avatar_url?: string;
@ -850,7 +850,7 @@ export interface ProvisionerDaemon {
readonly name: string; readonly name: string;
readonly version: string; readonly version: string;
readonly api_version: string; readonly api_version: string;
readonly provisioners: ProvisionerType[]; readonly provisioners: readonly ProvisionerType[];
readonly tags: Record<string, string>; readonly tags: Record<string, string>;
} }
@ -883,8 +883,8 @@ export interface ProvisionerJobLog {
// From codersdk/workspaceproxy.go // From codersdk/workspaceproxy.go
export interface ProxyHealthReport { export interface ProxyHealthReport {
readonly errors: string[]; readonly errors: readonly string[];
readonly warnings: string[]; readonly warnings: readonly string[];
} }
// From codersdk/workspaces.go // From codersdk/workspaces.go
@ -929,7 +929,7 @@ export interface Region {
// From codersdk/workspaceproxy.go // From codersdk/workspaceproxy.go
export interface RegionsResponse<R extends RegionTypes> { export interface RegionsResponse<R extends RegionTypes> {
readonly regions: R[]; readonly regions: readonly R[];
} }
// From codersdk/replicas.go // From codersdk/replicas.go
@ -952,7 +952,7 @@ export interface ResolveAutostartResponse {
export interface Response { export interface Response {
readonly message: string; readonly message: string;
readonly detail?: string; readonly detail?: string;
readonly validations?: ValidationError[]; readonly validations?: readonly ValidationError[];
} }
// From codersdk/roles.go // From codersdk/roles.go
@ -1005,7 +1005,7 @@ export interface SessionLifetime {
// From codersdk/deployment.go // From codersdk/deployment.go
export interface SupportConfig { export interface SupportConfig {
readonly links: LinkConfig[]; readonly links: readonly LinkConfig[];
} }
// From codersdk/deployment.go // From codersdk/deployment.go
@ -1070,13 +1070,13 @@ export interface Template {
// From codersdk/templates.go // From codersdk/templates.go
export interface TemplateACL { export interface TemplateACL {
readonly users: TemplateUser[]; readonly users: readonly TemplateUser[];
readonly group: TemplateGroup[]; readonly group: readonly TemplateGroup[];
} }
// From codersdk/insights.go // From codersdk/insights.go
export interface TemplateAppUsage { export interface TemplateAppUsage {
readonly template_ids: string[]; readonly template_ids: readonly string[];
readonly type: TemplateAppsType; readonly type: TemplateAppsType;
readonly display_name: string; readonly display_name: string;
readonly slug: string; readonly slug: string;
@ -1086,12 +1086,12 @@ export interface TemplateAppUsage {
// From codersdk/templates.go // From codersdk/templates.go
export interface TemplateAutostartRequirement { export interface TemplateAutostartRequirement {
readonly days_of_week: string[]; readonly days_of_week: readonly string[];
} }
// From codersdk/templates.go // From codersdk/templates.go
export interface TemplateAutostopRequirement { export interface TemplateAutostopRequirement {
readonly days_of_week: string[]; readonly days_of_week: readonly string[];
readonly weeks: number; readonly weeks: number;
} }
@ -1108,7 +1108,7 @@ export interface TemplateExample {
readonly name: string; readonly name: string;
readonly description: string; readonly description: string;
readonly icon: string; readonly icon: string;
readonly tags: string[]; readonly tags: readonly string[];
readonly markdown: string; readonly markdown: string;
} }
@ -1121,7 +1121,7 @@ export interface TemplateGroup extends Group {
export interface TemplateInsightsIntervalReport { export interface TemplateInsightsIntervalReport {
readonly start_time: string; readonly start_time: string;
readonly end_time: string; readonly end_time: string;
readonly template_ids: string[]; readonly template_ids: readonly string[];
readonly interval: InsightsReportInterval; readonly interval: InsightsReportInterval;
readonly active_users: number; readonly active_users: number;
} }
@ -1130,36 +1130,36 @@ export interface TemplateInsightsIntervalReport {
export interface TemplateInsightsReport { export interface TemplateInsightsReport {
readonly start_time: string; readonly start_time: string;
readonly end_time: string; readonly end_time: string;
readonly template_ids: string[]; readonly template_ids: readonly string[];
readonly active_users: number; readonly active_users: number;
readonly apps_usage: TemplateAppUsage[]; readonly apps_usage: readonly TemplateAppUsage[];
readonly parameters_usage: TemplateParameterUsage[]; readonly parameters_usage: readonly TemplateParameterUsage[];
} }
// From codersdk/insights.go // From codersdk/insights.go
export interface TemplateInsightsRequest { export interface TemplateInsightsRequest {
readonly start_time: string; readonly start_time: string;
readonly end_time: string; readonly end_time: string;
readonly template_ids: string[]; readonly template_ids: readonly string[];
readonly interval: InsightsReportInterval; readonly interval: InsightsReportInterval;
readonly sections: TemplateInsightsSection[]; readonly sections: readonly TemplateInsightsSection[];
} }
// From codersdk/insights.go // From codersdk/insights.go
export interface TemplateInsightsResponse { export interface TemplateInsightsResponse {
readonly report?: TemplateInsightsReport; readonly report?: TemplateInsightsReport;
readonly interval_reports?: TemplateInsightsIntervalReport[]; readonly interval_reports?: readonly TemplateInsightsIntervalReport[];
} }
// From codersdk/insights.go // From codersdk/insights.go
export interface TemplateParameterUsage { export interface TemplateParameterUsage {
readonly template_ids: string[]; readonly template_ids: readonly string[];
readonly display_name: string; readonly display_name: string;
readonly name: string; readonly name: string;
readonly type: string; readonly type: string;
readonly description: string; readonly description: string;
readonly options?: TemplateVersionParameterOption[]; readonly options?: readonly TemplateVersionParameterOption[];
readonly values: TemplateParameterValue[]; readonly values: readonly TemplateParameterValue[];
} }
// From codersdk/insights.go // From codersdk/insights.go
@ -1186,7 +1186,7 @@ export interface TemplateVersion {
readonly readme: string; readonly readme: string;
readonly created_by: MinimalUser; readonly created_by: MinimalUser;
readonly archived: boolean; readonly archived: boolean;
readonly warnings?: TemplateVersionWarning[]; readonly warnings?: readonly TemplateVersionWarning[];
} }
// From codersdk/templateversions.go // From codersdk/templateversions.go
@ -1210,7 +1210,7 @@ export interface TemplateVersionParameter {
readonly mutable: boolean; readonly mutable: boolean;
readonly default_value: string; readonly default_value: string;
readonly icon: string; readonly icon: string;
readonly options: TemplateVersionParameterOption[]; readonly options: readonly TemplateVersionParameterOption[];
readonly validation_error?: string; readonly validation_error?: string;
readonly validation_regex?: string; readonly validation_regex?: string;
readonly validation_min?: number; readonly validation_min?: number;
@ -1290,7 +1290,7 @@ export interface UpdateCheckResponse {
// From codersdk/users.go // From codersdk/users.go
export interface UpdateRoles { export interface UpdateRoles {
readonly roles: string[]; readonly roles: readonly string[];
} }
// From codersdk/templates.go // From codersdk/templates.go
@ -1391,13 +1391,13 @@ export interface UpsertWorkspaceAgentPortShareRequest {
// From codersdk/users.go // From codersdk/users.go
export interface User extends ReducedUser { export interface User extends ReducedUser {
readonly organization_ids: string[]; readonly organization_ids: readonly string[];
readonly roles: Role[]; readonly roles: readonly Role[];
} }
// From codersdk/insights.go // From codersdk/insights.go
export interface UserActivity { export interface UserActivity {
readonly template_ids: string[]; readonly template_ids: readonly string[];
readonly user_id: string; readonly user_id: string;
readonly username: string; readonly username: string;
readonly avatar_url: string; readonly avatar_url: string;
@ -1408,15 +1408,15 @@ export interface UserActivity {
export interface UserActivityInsightsReport { export interface UserActivityInsightsReport {
readonly start_time: string; readonly start_time: string;
readonly end_time: string; readonly end_time: string;
readonly template_ids: string[]; readonly template_ids: readonly string[];
readonly users: UserActivity[]; readonly users: readonly UserActivity[];
} }
// From codersdk/insights.go // From codersdk/insights.go
export interface UserActivityInsightsRequest { export interface UserActivityInsightsRequest {
readonly start_time: string; readonly start_time: string;
readonly end_time: string; readonly end_time: string;
readonly template_ids: string[]; readonly template_ids: readonly string[];
} }
// From codersdk/insights.go // From codersdk/insights.go
@ -1426,7 +1426,7 @@ export interface UserActivityInsightsResponse {
// From codersdk/insights.go // From codersdk/insights.go
export interface UserLatency { export interface UserLatency {
readonly template_ids: string[]; readonly template_ids: readonly string[];
readonly user_id: string; readonly user_id: string;
readonly username: string; readonly username: string;
readonly avatar_url: string; readonly avatar_url: string;
@ -1437,15 +1437,15 @@ export interface UserLatency {
export interface UserLatencyInsightsReport { export interface UserLatencyInsightsReport {
readonly start_time: string; readonly start_time: string;
readonly end_time: string; readonly end_time: string;
readonly template_ids: string[]; readonly template_ids: readonly string[];
readonly users: UserLatency[]; readonly users: readonly UserLatency[];
} }
// From codersdk/insights.go // From codersdk/insights.go
export interface UserLatencyInsightsRequest { export interface UserLatencyInsightsRequest {
readonly start_time: string; readonly start_time: string;
readonly end_time: string; readonly end_time: string;
readonly template_ids: string[]; readonly template_ids: readonly string[];
} }
// From codersdk/insights.go // From codersdk/insights.go
@ -1482,8 +1482,8 @@ export interface UserQuietHoursScheduleResponse {
// From codersdk/users.go // From codersdk/users.go
export interface UserRoles { export interface UserRoles {
readonly roles: string[]; readonly roles: readonly string[];
readonly organization_roles: Record<string, string[]>; readonly organization_roles: Record<string, readonly string[]>;
} }
// From codersdk/users.go // From codersdk/users.go
@ -1557,15 +1557,15 @@ export interface WorkspaceAgent {
readonly expanded_directory?: string; readonly expanded_directory?: string;
readonly version: string; readonly version: string;
readonly api_version: string; readonly api_version: string;
readonly apps: WorkspaceApp[]; readonly apps: readonly WorkspaceApp[];
readonly latency?: Record<string, DERPRegion>; readonly latency?: Record<string, DERPRegion>;
readonly connection_timeout_seconds: number; readonly connection_timeout_seconds: number;
readonly troubleshooting_url: string; readonly troubleshooting_url: string;
readonly subsystems: AgentSubsystem[]; readonly subsystems: readonly AgentSubsystem[];
readonly health: WorkspaceAgentHealth; readonly health: WorkspaceAgentHealth;
readonly display_apps: DisplayApp[]; readonly display_apps: readonly DisplayApp[];
readonly log_sources: WorkspaceAgentLogSource[]; readonly log_sources: readonly WorkspaceAgentLogSource[];
readonly scripts: WorkspaceAgentScript[]; readonly scripts: readonly WorkspaceAgentScript[];
readonly startup_script_behavior: WorkspaceAgentStartupScriptBehavior; readonly startup_script_behavior: WorkspaceAgentStartupScriptBehavior;
} }
@ -1584,7 +1584,7 @@ export interface WorkspaceAgentListeningPort {
// From codersdk/workspaceagents.go // From codersdk/workspaceagents.go
export interface WorkspaceAgentListeningPortsResponse { export interface WorkspaceAgentListeningPortsResponse {
readonly ports: WorkspaceAgentListeningPort[]; readonly ports: readonly WorkspaceAgentListeningPort[];
} }
// From codersdk/workspaceagents.go // From codersdk/workspaceagents.go
@ -1639,7 +1639,7 @@ export interface WorkspaceAgentPortShare {
// From codersdk/workspaceagentportshare.go // From codersdk/workspaceagentportshare.go
export interface WorkspaceAgentPortShares { export interface WorkspaceAgentPortShares {
readonly shares: WorkspaceAgentPortShare[]; readonly shares: readonly WorkspaceAgentPortShare[];
} }
// From codersdk/workspaceagents.go // From codersdk/workspaceagents.go
@ -1688,7 +1688,7 @@ export interface WorkspaceBuild {
readonly initiator_name: string; readonly initiator_name: string;
readonly job: ProvisionerJob; readonly job: ProvisionerJob;
readonly reason: BuildReason; readonly reason: BuildReason;
readonly resources: WorkspaceResource[]; readonly resources: readonly WorkspaceResource[];
readonly deadline?: string; readonly deadline?: string;
readonly max_deadline?: string; readonly max_deadline?: string;
readonly status: WorkspaceStatus; readonly status: WorkspaceStatus;
@ -1732,7 +1732,7 @@ export interface WorkspaceFilter {
// From codersdk/workspaces.go // From codersdk/workspaces.go
export interface WorkspaceHealth { export interface WorkspaceHealth {
readonly healthy: boolean; readonly healthy: boolean;
readonly failing_agents: string[]; readonly failing_agents: readonly string[];
} }
// From codersdk/workspaces.go // From codersdk/workspaces.go
@ -1780,8 +1780,8 @@ export interface WorkspaceResource {
readonly name: string; readonly name: string;
readonly hide: boolean; readonly hide: boolean;
readonly icon: string; readonly icon: string;
readonly agents?: WorkspaceAgent[]; readonly agents?: readonly WorkspaceAgent[];
readonly metadata?: WorkspaceResourceMetadata[]; readonly metadata?: readonly WorkspaceResourceMetadata[];
readonly daily_cost: number; readonly daily_cost: number;
} }
@ -1799,7 +1799,7 @@ export interface WorkspacesRequest extends Pagination {
// From codersdk/workspaces.go // From codersdk/workspaces.go
export interface WorkspacesResponse { export interface WorkspacesResponse {
readonly workspaces: Workspace[]; readonly workspaces: readonly Workspace[];
readonly count: number; readonly count: number;
} }
@ -2285,7 +2285,7 @@ export type RegionTypes = Region | WorkspaceProxy;
export interface AccessURLReport { export interface AccessURLReport {
readonly healthy: boolean; readonly healthy: boolean;
readonly severity: HealthSeverity; readonly severity: HealthSeverity;
readonly warnings: HealthMessage[]; readonly warnings: readonly HealthMessage[];
readonly dismissed: boolean; readonly dismissed: boolean;
readonly access_url: string; readonly access_url: string;
readonly reachable: boolean; readonly reachable: boolean;
@ -2298,14 +2298,14 @@ export interface AccessURLReport {
export interface DERPHealthReport { export interface DERPHealthReport {
readonly healthy: boolean; readonly healthy: boolean;
readonly severity: HealthSeverity; readonly severity: HealthSeverity;
readonly warnings: HealthMessage[]; readonly warnings: readonly HealthMessage[];
readonly dismissed: boolean; readonly dismissed: boolean;
readonly regions: Record<number, DERPRegionReport>; readonly regions: Record<number, DERPRegionReport>;
// Named type "tailscale.com/net/netcheck.Report" unknown, using "any" // Named type "tailscale.com/net/netcheck.Report" unknown, using "any"
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- External type // eslint-disable-next-line @typescript-eslint/no-explicit-any -- External type
readonly netcheck?: any; readonly netcheck?: any;
readonly netcheck_err?: string; readonly netcheck_err?: string;
readonly netcheck_logs: string[]; readonly netcheck_logs: readonly string[];
readonly error?: string; readonly error?: string;
} }
@ -2313,7 +2313,7 @@ export interface DERPHealthReport {
export interface DERPNodeReport { export interface DERPNodeReport {
readonly healthy: boolean; readonly healthy: boolean;
readonly severity: HealthSeverity; readonly severity: HealthSeverity;
readonly warnings: HealthMessage[]; readonly warnings: readonly HealthMessage[];
// Named type "tailscale.com/tailcfg.DERPNode" unknown, using "any" // Named type "tailscale.com/tailcfg.DERPNode" unknown, using "any"
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- External type // eslint-disable-next-line @typescript-eslint/no-explicit-any -- External type
readonly node?: any; readonly node?: any;
@ -2324,8 +2324,8 @@ export interface DERPNodeReport {
readonly round_trip_ping: string; readonly round_trip_ping: string;
readonly round_trip_ping_ms: number; readonly round_trip_ping_ms: number;
readonly uses_websocket: boolean; readonly uses_websocket: boolean;
readonly client_logs: string[][]; readonly client_logs: readonly (readonly string[])[];
readonly client_errs: string[][]; readonly client_errs: readonly (readonly string[])[];
readonly error?: string; readonly error?: string;
readonly stun: STUNReport; readonly stun: STUNReport;
} }
@ -2334,11 +2334,11 @@ export interface DERPNodeReport {
export interface DERPRegionReport { export interface DERPRegionReport {
readonly healthy: boolean; readonly healthy: boolean;
readonly severity: HealthSeverity; readonly severity: HealthSeverity;
readonly warnings: HealthMessage[]; readonly warnings: readonly HealthMessage[];
// Named type "tailscale.com/tailcfg.DERPRegion" unknown, using "any" // Named type "tailscale.com/tailcfg.DERPRegion" unknown, using "any"
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- External type // eslint-disable-next-line @typescript-eslint/no-explicit-any -- External type
readonly region?: any; readonly region?: any;
readonly node_reports: DERPNodeReport[]; readonly node_reports: readonly DERPNodeReport[];
readonly error?: string; readonly error?: string;
} }
@ -2346,7 +2346,7 @@ export interface DERPRegionReport {
export interface DatabaseReport { export interface DatabaseReport {
readonly healthy: boolean; readonly healthy: boolean;
readonly severity: HealthSeverity; readonly severity: HealthSeverity;
readonly warnings: HealthMessage[]; readonly warnings: readonly HealthMessage[];
readonly dismissed: boolean; readonly dismissed: boolean;
readonly reachable: boolean; readonly reachable: boolean;
readonly latency: string; readonly latency: string;
@ -2357,7 +2357,7 @@ export interface DatabaseReport {
// From healthsdk/healthsdk.go // From healthsdk/healthsdk.go
export interface HealthSettings { export interface HealthSettings {
readonly dismissed_healthchecks: HealthSection[]; readonly dismissed_healthchecks: readonly HealthSection[];
} }
// From healthsdk/healthsdk.go // From healthsdk/healthsdk.go
@ -2365,7 +2365,7 @@ export interface HealthcheckReport {
readonly time: string; readonly time: string;
readonly healthy: boolean; readonly healthy: boolean;
readonly severity: HealthSeverity; readonly severity: HealthSeverity;
readonly failing_sections: HealthSection[]; readonly failing_sections: readonly HealthSection[];
readonly derp: DERPHealthReport; readonly derp: DERPHealthReport;
readonly access_url: AccessURLReport; readonly access_url: AccessURLReport;
readonly websocket: WebsocketReport; readonly websocket: WebsocketReport;
@ -2378,16 +2378,16 @@ export interface HealthcheckReport {
// From healthsdk/healthsdk.go // From healthsdk/healthsdk.go
export interface ProvisionerDaemonsReport { export interface ProvisionerDaemonsReport {
readonly severity: HealthSeverity; readonly severity: HealthSeverity;
readonly warnings: HealthMessage[]; readonly warnings: readonly HealthMessage[];
readonly dismissed: boolean; readonly dismissed: boolean;
readonly error?: string; readonly error?: string;
readonly items: ProvisionerDaemonsReportItem[]; readonly items: readonly ProvisionerDaemonsReportItem[];
} }
// From healthsdk/healthsdk.go // From healthsdk/healthsdk.go
export interface ProvisionerDaemonsReportItem { export interface ProvisionerDaemonsReportItem {
readonly provisioner_daemon: ProvisionerDaemon; readonly provisioner_daemon: ProvisionerDaemon;
readonly warnings: HealthMessage[]; readonly warnings: readonly HealthMessage[];
} }
// From healthsdk/healthsdk.go // From healthsdk/healthsdk.go
@ -2399,14 +2399,14 @@ export interface STUNReport {
// From healthsdk/healthsdk.go // From healthsdk/healthsdk.go
export interface UpdateHealthSettings { export interface UpdateHealthSettings {
readonly dismissed_healthchecks: HealthSection[]; readonly dismissed_healthchecks: readonly HealthSection[];
} }
// From healthsdk/healthsdk.go // From healthsdk/healthsdk.go
export interface WebsocketReport { export interface WebsocketReport {
readonly healthy: boolean; readonly healthy: boolean;
readonly severity: HealthSeverity; readonly severity: HealthSeverity;
readonly warnings: string[]; readonly warnings: readonly string[];
readonly dismissed: boolean; readonly dismissed: boolean;
readonly body: string; readonly body: string;
readonly code: number; readonly code: number;
@ -2417,7 +2417,7 @@ export interface WebsocketReport {
export interface WorkspaceProxyReport { export interface WorkspaceProxyReport {
readonly healthy: boolean; readonly healthy: boolean;
readonly severity: HealthSeverity; readonly severity: HealthSeverity;
readonly warnings: HealthMessage[]; readonly warnings: readonly HealthMessage[];
readonly dismissed: boolean; readonly dismissed: boolean;
readonly error?: string; readonly error?: string;
readonly workspace_proxies: RegionsResponse<WorkspaceProxy>; readonly workspace_proxies: RegionsResponse<WorkspaceProxy>;
@ -2520,13 +2520,13 @@ export interface SerpentOption {
readonly value?: any; readonly value?: any;
readonly annotations?: SerpentAnnotations; readonly annotations?: SerpentAnnotations;
readonly group?: SerpentGroup; readonly group?: SerpentGroup;
readonly use_instead?: SerpentOption[]; readonly use_instead?: readonly SerpentOption[];
readonly hidden?: boolean; readonly hidden?: boolean;
readonly value_source?: SerpentValueSource; readonly value_source?: SerpentValueSource;
} }
// From serpent/option.go // From serpent/option.go
export type SerpentOptionSet = SerpentOption[]; export type SerpentOptionSet = readonly SerpentOption[];
// From serpent/option.go // From serpent/option.go
export type SerpentValueSource = "" | "default" | "env" | "flag" | "yaml"; export type SerpentValueSource = "" | "default" | "env" | "flag" | "yaml";

View File

@ -42,7 +42,7 @@ ChartJS.register(
const USER_LIMIT_DISPLAY_THRESHOLD = 60; const USER_LIMIT_DISPLAY_THRESHOLD = 60;
export interface ActiveUserChartProps { export interface ActiveUserChartProps {
data: Array<{ date: string; amount: number }>; data: readonly { date: string; amount: number }[];
interval: "day" | "week"; interval: "day" | "week";
userLimit: number | undefined; userLimit: number | undefined;
} }

View File

@ -4,7 +4,7 @@ import { TimelineDateRow } from "components/Timeline/TimelineDateRow";
type GetDateFn<TData> = (data: TData) => Date; type GetDateFn<TData> = (data: TData) => Date;
const groupByDate = <TData,>( const groupByDate = <TData,>(
items: TData[], items: readonly TData[],
getDate: GetDateFn<TData>, getDate: GetDateFn<TData>,
): Record<string, TData[]> => { ): Record<string, TData[]> => {
const itemsByDate: Record<string, TData[]> = {}; const itemsByDate: Record<string, TData[]> = {};
@ -23,7 +23,7 @@ const groupByDate = <TData,>(
}; };
export interface TimelineProps<TData> { export interface TimelineProps<TData> {
items: TData[]; items: readonly TData[];
getDate: GetDateFn<TData>; getDate: GetDateFn<TData>;
row: (item: TData) => JSX.Element; row: (item: TData) => JSX.Element;
} }

View File

@ -41,7 +41,7 @@ export interface ProxyContextValue {
// WorkspaceProxy[] is returned if the user is an admin. WorkspaceProxy extends Region with // WorkspaceProxy[] is returned if the user is an admin. WorkspaceProxy extends Region with
// more information about the proxy and the status. More information includes the error message if // more information about the proxy and the status. More information includes the error message if
// the proxy is unhealthy. // the proxy is unhealthy.
proxies?: Region[] | WorkspaceProxy[]; proxies?: readonly Region[] | readonly WorkspaceProxy[];
// isFetched is true when the 'proxies' api call is complete. // isFetched is true when the 'proxies' api call is complete.
isFetched: boolean; isFetched: boolean;
isLoading: boolean; isLoading: boolean;
@ -117,7 +117,7 @@ export const ProxyProvider: FC<PropsWithChildren> = ({ children }) => {
}); });
const { permissions } = useAuthenticated(); const { permissions } = useAuthenticated();
const query = async (): Promise<Region[]> => { const query = async (): Promise<readonly Region[]> => {
const endpoint = permissions.editWorkspaceProxies const endpoint = permissions.editWorkspaceProxies
? getWorkspaceProxies ? getWorkspaceProxies
: getWorkspaceProxyRegions; : getWorkspaceProxyRegions;
@ -218,7 +218,7 @@ export const useProxy = (): ProxyContextValue => {
* If not, `primary` is always the best default. * If not, `primary` is always the best default.
*/ */
export const getPreferredProxy = ( export const getPreferredProxy = (
proxies: Region[], proxies: readonly Region[],
selectedProxy?: Region, selectedProxy?: Region,
latencies?: Record<string, ProxyLatencyReport>, latencies?: Record<string, ProxyLatencyReport>,
autoSelectBasedOnLatency = true, autoSelectBasedOnLatency = true,
@ -245,7 +245,7 @@ export const getPreferredProxy = (
}; };
const selectByLatency = ( const selectByLatency = (
proxies: Region[], proxies: readonly Region[],
latencies?: Record<string, ProxyLatencyReport>, latencies?: Record<string, ProxyLatencyReport>,
): Region | undefined => { ): Region | undefined => {
if (!latencies) { if (!latencies) {

View File

@ -37,7 +37,7 @@ const proxyLatenciesReducer = (
}; };
export const useProxyLatency = ( export const useProxyLatency = (
proxies?: Region[], proxies?: readonly Region[],
): { ): {
// Refetch can be called to refetch the proxy latencies. // Refetch can be called to refetch the proxy latencies.
// Until the new values are loaded, the old values will still be used. // Until the new values are loaded, the old values will still be used.
@ -265,7 +265,7 @@ const updateStoredLatencies = (action: ProxyLatencyAction): void => {
// garbageCollectStoredLatencies will remove any latencies that are older then 1 week or latencies of proxies // garbageCollectStoredLatencies will remove any latencies that are older then 1 week or latencies of proxies
// that no longer exist. This is intended to keep the size of local storage down. // that no longer exist. This is intended to keep the size of local storage down.
const garbageCollectStoredLatencies = ( const garbageCollectStoredLatencies = (
regions: Region[], regions: readonly Region[],
maxStored: number, maxStored: number,
): void => { ): void => {
const latencies = loadStoredLatencies(); const latencies = loadStoredLatencies();
@ -282,7 +282,7 @@ const garbageCollectStoredLatencies = (
const cleanupLatencies = ( const cleanupLatencies = (
stored: Record<string, ProxyLatencyReport[]>, stored: Record<string, ProxyLatencyReport[]>,
regions: Region[], regions: readonly Region[],
now: Date, now: Date,
maxStored: number, maxStored: number,
): Record<string, ProxyLatencyReport[]> => { ): Record<string, ProxyLatencyReport[]> => {

View File

@ -27,8 +27,8 @@ const styles = {
} satisfies Record<string, Interpolation<Theme>>; } satisfies Record<string, Interpolation<Theme>>;
export interface LicenseBannerViewProps { export interface LicenseBannerViewProps {
errors: string[]; errors: readonly string[];
warnings: string[]; warnings: readonly string[];
} }
export const LicenseBannerView: FC<LicenseBannerViewProps> = ({ export const LicenseBannerView: FC<LicenseBannerViewProps> = ({

View File

@ -30,7 +30,7 @@ export interface NavbarViewProps {
logo_url?: string; logo_url?: string;
user?: TypesGen.User; user?: TypesGen.User;
buildInfo?: TypesGen.BuildInfoResponse; buildInfo?: TypesGen.BuildInfoResponse;
supportLinks?: TypesGen.LinkConfig[]; supportLinks?: readonly TypesGen.LinkConfig[];
onSignOut: () => void; onSignOut: () => void;
canViewAuditLog: boolean; canViewAuditLog: boolean;
canViewDeployment: boolean; canViewDeployment: boolean;
@ -342,57 +342,58 @@ const ProxyMenu: FC<ProxyMenuProps> = ({ proxyContextValue }) => {
<Divider css={{ borderColor: theme.palette.divider }} /> <Divider css={{ borderColor: theme.palette.divider }} />
{proxyContextValue.proxies {proxyContextValue.proxies &&
?.sort((a, b) => { [...proxyContextValue.proxies]
const latencyA = latencies?.[a.id]?.latencyMS ?? Infinity; .sort((a, b) => {
const latencyB = latencies?.[b.id]?.latencyMS ?? Infinity; const latencyA = latencies?.[a.id]?.latencyMS ?? Infinity;
return latencyA - latencyB; const latencyB = latencies?.[b.id]?.latencyMS ?? Infinity;
}) return latencyA - latencyB;
.map((proxy) => ( })
<MenuItem .map((proxy) => (
key={proxy.id} <MenuItem
selected={proxy.id === selectedProxy?.id} key={proxy.id}
css={{ fontSize: 14 }} selected={proxy.id === selectedProxy?.id}
onClick={() => { css={{ fontSize: 14 }}
if (!proxy.healthy) { onClick={() => {
displayError("Please select a healthy workspace proxy."); if (!proxy.healthy) {
closeMenu(); displayError("Please select a healthy workspace proxy.");
return; closeMenu();
} return;
}
proxyContextValue.setProxy(proxy); proxyContextValue.setProxy(proxy);
closeMenu(); closeMenu();
}}
>
<div
css={{
display: "flex",
gap: 24,
alignItems: "center",
width: "100%",
}} }}
> >
<div css={{ width: 14, height: 14, lineHeight: 0 }}> <div
<img css={{
src={proxy.icon_url} display: "flex",
alt="" gap: 24,
css={{ alignItems: "center",
objectFit: "contain", width: "100%",
width: "100%", }}
height: "100%", >
}} <div css={{ width: 14, height: 14, lineHeight: 0 }}>
<img
src={proxy.icon_url}
alt=""
css={{
objectFit: "contain",
width: "100%",
height: "100%",
}}
/>
</div>
{proxy.display_name}
<Latency
latency={latencies?.[proxy.id]?.latencyMS}
isLoading={proxyLatencyLoading(proxy)}
/> />
</div> </div>
</MenuItem>
{proxy.display_name} ))}
<Latency
latency={latencies?.[proxy.id]?.latencyMS}
isLoading={proxyLatencyLoading(proxy)}
/>
</div>
</MenuItem>
))}
<Divider css={{ borderColor: theme.palette.divider }} /> <Divider css={{ borderColor: theme.palette.divider }} />

View File

@ -15,7 +15,7 @@ import { UserDropdownContent } from "./UserDropdownContent";
export interface UserDropdownProps { export interface UserDropdownProps {
user: TypesGen.User; user: TypesGen.User;
buildInfo?: TypesGen.BuildInfoResponse; buildInfo?: TypesGen.BuildInfoResponse;
supportLinks?: TypesGen.LinkConfig[]; supportLinks?: readonly TypesGen.LinkConfig[];
onSignOut: () => void; onSignOut: () => void;
children?: ReactNode; children?: ReactNode;
} }

View File

@ -83,7 +83,7 @@ const styles = {
export interface UserDropdownContentProps { export interface UserDropdownContentProps {
user: TypesGen.User; user: TypesGen.User;
buildInfo?: TypesGen.BuildInfoResponse; buildInfo?: TypesGen.BuildInfoResponse;
supportLinks?: TypesGen.LinkConfig[]; supportLinks?: readonly TypesGen.LinkConfig[];
onSignOut: () => void; onSignOut: () => void;
} }

View File

@ -20,8 +20,8 @@ type AgentLogsProps = Omit<
ComponentProps<typeof List>, ComponentProps<typeof List>,
"children" | "itemSize" | "itemCount" "children" | "itemSize" | "itemCount"
> & { > & {
logs: LineWithID[]; logs: readonly LineWithID[];
sources: WorkspaceAgentLogSource[]; sources: readonly WorkspaceAgentLogSource[];
}; };
export const AgentLogs = forwardRef<List, AgentLogsProps>( export const AgentLogs = forwardRef<List, AgentLogsProps>(

View File

@ -116,7 +116,7 @@ const getValidationSchema = (): Yup.AnyObjectSchema =>
}); });
interface PortForwardPopoverViewProps extends PortForwardButtonProps { interface PortForwardPopoverViewProps extends PortForwardButtonProps {
listeningPorts?: WorkspaceAgentListeningPort[]; listeningPorts?: readonly WorkspaceAgentListeningPort[];
portSharingExperimentEnabled: boolean; portSharingExperimentEnabled: boolean;
portSharingControlsEnabled: boolean; portSharingControlsEnabled: boolean;
} }

View File

@ -15,7 +15,7 @@ export interface VSCodeDesktopButtonProps {
workspaceName: string; workspaceName: string;
agentName?: string; agentName?: string;
folderPath?: string; folderPath?: string;
displayApps: DisplayApp[]; displayApps: readonly DisplayApp[];
} }
type VSCodeVariant = "vscode" | "vscode-insiders"; type VSCodeVariant = "vscode" | "vscode-insiders";

View File

@ -32,7 +32,7 @@ export const Language = {
}; };
export interface AuditPageViewProps { export interface AuditPageViewProps {
auditLogs?: AuditLog[]; auditLogs?: readonly AuditLog[];
isNonInitialPage: boolean; isNonInitialPage: boolean;
isAuditLogVisible: boolean; isAuditLogVisible: boolean;
error?: unknown; error?: unknown;

View File

@ -17,8 +17,8 @@ import {
import { optionValue } from "./optionValue"; import { optionValue } from "./optionValue";
interface OptionsTableProps { interface OptionsTableProps {
options: SerpentOption[]; options: readonly SerpentOption[];
additionalValues?: string[]; additionalValues?: readonly string[];
} }
const OptionsTable: FC<OptionsTableProps> = ({ options, additionalValues }) => { const OptionsTable: FC<OptionsTableProps> = ({ options, additionalValues }) => {

View File

@ -4,7 +4,7 @@ import type { SerpentOption } from "api/typesGenerated";
// optionValue is a helper function to format the value of a specific deployment options // optionValue is a helper function to format the value of a specific deployment options
export function optionValue( export function optionValue(
option: SerpentOption, option: SerpentOption,
additionalValues?: string[], additionalValues?: readonly string[],
) { ) {
// If option annotations are present, use them to format the value. // If option annotations are present, use them to format the value.
if (option.annotations) { if (option.annotations) {

View File

@ -214,7 +214,7 @@ export const BooleanPill: FC<BooleanPillProps> = ({
); );
}; };
type LogsProps = { lines: string[] } & HTMLAttributes<HTMLDivElement>; type LogsProps = HTMLAttributes<HTMLDivElement> & { lines: readonly string[] };
export const Logs: FC<LogsProps> = ({ lines, ...divProps }) => { export const Logs: FC<LogsProps> = ({ lines, ...divProps }) => {
const theme = useTheme(); const theme = useTheme();

View File

@ -296,7 +296,7 @@ const UsersLatencyPanel: FC<UsersLatencyPanelProps> = ({
{!data && <Loader css={{ height: "100%" }} />} {!data && <Loader css={{ height: "100%" }} />}
{users && users.length === 0 && <NoDataAvailable />} {users && users.length === 0 && <NoDataAvailable />}
{users && {users &&
users [...users]
.sort((a, b) => b.latency_ms.p50 - a.latency_ms.p50) .sort((a, b) => b.latency_ms.p50 - a.latency_ms.p50)
.map((row) => ( .map((row) => (
<div <div
@ -367,7 +367,7 @@ const UsersActivityPanel: FC<UsersActivityPanelProps> = ({
{!data && <Loader css={{ height: "100%" }} />} {!data && <Loader css={{ height: "100%" }} />}
{users && users.length === 0 && <NoDataAvailable />} {users && users.length === 0 && <NoDataAvailable />}
{users && {users &&
users [...users]
.sort((a, b) => b.seconds - a.seconds) .sort((a, b) => b.seconds - a.seconds)
.map((row) => ( .map((row) => (
<div <div
@ -405,7 +405,7 @@ const UsersActivityPanel: FC<UsersActivityPanelProps> = ({
}; };
interface TemplateUsagePanelProps extends PanelProps { interface TemplateUsagePanelProps extends PanelProps {
data: TemplateAppUsage[] | undefined; data: readonly TemplateAppUsage[] | undefined;
} }
const TemplateUsagePanel: FC<TemplateUsagePanelProps> = ({ const TemplateUsagePanel: FC<TemplateUsagePanelProps> = ({
@ -508,7 +508,7 @@ const TemplateUsagePanel: FC<TemplateUsagePanelProps> = ({
}; };
interface TemplateParametersUsagePanelProps extends PanelProps { interface TemplateParametersUsagePanelProps extends PanelProps {
data: TemplateParameterUsage[] | undefined; data: readonly TemplateParameterUsage[] | undefined;
} }
const TemplateParametersUsagePanel: FC<TemplateParametersUsagePanelProps> = ({ const TemplateParametersUsagePanel: FC<TemplateParametersUsagePanelProps> = ({
@ -579,7 +579,7 @@ const TemplateParametersUsagePanel: FC<TemplateParametersUsagePanelProps> = ({
<div>Count</div> <div>Count</div>
</Tooltip> </Tooltip>
</ParameterUsageRow> </ParameterUsageRow>
{parameter.values {[...parameter.values]
.sort((a, b) => b.count - a.count) .sort((a, b) => b.count - a.count)
.filter((usage) => filterOrphanValues(usage, parameter)) .filter((usage) => filterOrphanValues(usage, parameter))
.map((usage, usageIndex) => ( .map((usage, usageIndex) => (

View File

@ -113,7 +113,7 @@ const ProxyMessagesRow: FC<ProxyMessagesRowProps> = ({ proxy }) => {
interface ProxyMessagesListProps { interface ProxyMessagesListProps {
title: ReactNode; title: ReactNode;
messages?: string[]; messages?: readonly string[];
} }
const ProxyMessagesList: FC<ProxyMessagesListProps> = ({ title, messages }) => { const ProxyMessagesList: FC<ProxyMessagesListProps> = ({ title, messages }) => {

View File

@ -15,7 +15,7 @@ import type { ProxyLatencyReport } from "contexts/useProxyLatency";
import { ProxyRow } from "./WorkspaceProxyRow"; import { ProxyRow } from "./WorkspaceProxyRow";
export interface WorkspaceProxyViewProps { export interface WorkspaceProxyViewProps {
proxies?: Region[]; proxies?: readonly Region[];
proxyLatencies?: Record<string, ProxyLatencyReport>; proxyLatencies?: Record<string, ProxyLatencyReport>;
getWorkspaceProxiesError?: unknown; getWorkspaceProxiesError?: unknown;
isLoading: boolean; isLoading: boolean;

View File

@ -9,7 +9,7 @@ import { UsersFilter } from "./UsersFilter";
import { UsersTable } from "./UsersTable/UsersTable"; import { UsersTable } from "./UsersTable/UsersTable";
export interface UsersPageViewProps { export interface UsersPageViewProps {
users?: TypesGen.User[]; users?: readonly TypesGen.User[];
roles?: TypesGen.AssignableRoles[]; roles?: TypesGen.AssignableRoles[];
isUpdatingUserRoles?: boolean; isUpdatingUserRoles?: boolean;
canEditUsers: boolean; canEditUsers: boolean;

View File

@ -69,7 +69,7 @@ const Option: FC<OptionProps> = ({
export interface EditRolesButtonProps { export interface EditRolesButtonProps {
isLoading: boolean; isLoading: boolean;
roles: Role[]; roles: readonly Role[];
selectedRoleNames: Set<string>; selectedRoleNames: Set<string>;
onChange: (roles: Role["name"][]) => void; onChange: (roles: Role["name"][]) => void;
isDefaultOpen?: boolean; isDefaultOpen?: boolean;

View File

@ -160,7 +160,7 @@ const roleNamesByAccessLevel: readonly string[] = [
"auditor", "auditor",
]; ];
function sortRolesByAccessLevel(roles: Role[]) { function sortRolesByAccessLevel(roles: readonly Role[]): readonly Role[] {
if (roles.length === 0) { if (roles.length === 0) {
return roles; return roles;
} }

View File

@ -21,7 +21,7 @@ export const Language = {
} as const; } as const;
export interface UsersTableProps { export interface UsersTableProps {
users: TypesGen.User[] | undefined; users: readonly TypesGen.User[] | undefined;
roles: TypesGen.AssignableRoles[] | undefined; roles: TypesGen.AssignableRoles[] | undefined;
groupsByUserId: GroupsByUserId | undefined; groupsByUserId: GroupsByUserId | undefined;
isUpdatingUserRoles?: boolean; isUpdatingUserRoles?: boolean;

View File

@ -36,7 +36,7 @@ import { UserRoleCell } from "./UserRoleCell";
dayjs.extend(relativeTime); dayjs.extend(relativeTime);
interface UsersTableBodyProps { interface UsersTableBodyProps {
users: TypesGen.User[] | undefined; users: readonly TypesGen.User[] | undefined;
groupsByUserId: GroupsByUserId | undefined; groupsByUserId: GroupsByUserId | undefined;
authMethods?: TypesGen.AuthMethods; authMethods?: TypesGen.AuthMethods;
roles?: TypesGen.AssignableRoles[]; roles?: TypesGen.AssignableRoles[];

View File

@ -13,7 +13,7 @@ import { getResourceIconPath } from "utils/workspace";
dayjs.extend(relativeTime); dayjs.extend(relativeTime);
type BatchDeleteConfirmationProps = { type BatchDeleteConfirmationProps = {
checkedWorkspaces: Workspace[]; checkedWorkspaces: readonly Workspace[];
open: boolean; open: boolean;
isLoading: boolean; isLoading: boolean;
onClose: () => void; onClose: () => void;
@ -111,7 +111,7 @@ export const BatchDeleteConfirmation: FC<BatchDeleteConfirmationProps> = ({
}; };
interface StageProps { interface StageProps {
workspaces: Workspace[]; workspaces: readonly Workspace[];
} }
const Consequences: FC = () => { const Consequences: FC = () => {

View File

@ -1,6 +1,7 @@
import { action } from "@storybook/addon-actions"; import { action } from "@storybook/addon-actions";
import type { Meta, StoryObj } from "@storybook/react"; import type { Meta, StoryObj } from "@storybook/react";
import { useQueryClient } from "react-query"; import { useQueryClient } from "react-query";
import type { Workspace } from "api/typesGenerated";
import { chromatic } from "testHelpers/chromatic"; import { chromatic } from "testHelpers/chromatic";
import { import {
MockWorkspace, MockWorkspace,
@ -29,23 +30,33 @@ const workspaces = [
}, },
]; ];
const updates = new Map<string, Update>(); function getPopulatedUpdates(): Map<string, Update> {
for (const it of workspaces) { type MutableUpdate = Omit<Update, "affected_workspaces"> & {
const versionId = it.template_active_version_id; affected_workspaces: Workspace[];
const version = updates.get(versionId); };
if (version) { const updates = new Map<string, MutableUpdate>();
version.affected_workspaces.push(it); for (const it of workspaces) {
continue; const versionId = it.template_active_version_id;
const version = updates.get(versionId);
if (version) {
version.affected_workspaces.push(it);
continue;
}
updates.set(versionId, {
...MockTemplateVersion,
template_display_name: it.template_display_name,
affected_workspaces: [it],
});
} }
updates.set(versionId, { return updates as Map<string, Update>;
...MockTemplateVersion,
template_display_name: it.template_display_name,
affected_workspaces: [it],
});
} }
const updates = getPopulatedUpdates();
const meta: Meta<typeof BatchUpdateConfirmation> = { const meta: Meta<typeof BatchUpdateConfirmation> = {
title: "pages/WorkspacesPage/BatchUpdateConfirmation", title: "pages/WorkspacesPage/BatchUpdateConfirmation",
parameters: { chromatic }, parameters: { chromatic },

View File

@ -18,7 +18,7 @@ import { Stack } from "components/Stack/Stack";
dayjs.extend(relativeTime); dayjs.extend(relativeTime);
type BatchUpdateConfirmationProps = { type BatchUpdateConfirmationProps = {
checkedWorkspaces: Workspace[]; checkedWorkspaces: readonly Workspace[];
open: boolean; open: boolean;
isLoading: boolean; isLoading: boolean;
onClose: () => void; onClose: () => void;
@ -27,7 +27,7 @@ type BatchUpdateConfirmationProps = {
export interface Update extends TemplateVersion { export interface Update extends TemplateVersion {
template_display_name: string; template_display_name: string;
affected_workspaces: Workspace[]; affected_workspaces: readonly Workspace[];
} }
export const BatchUpdateConfirmation: FC<BatchUpdateConfirmationProps> = ({ export const BatchUpdateConfirmation: FC<BatchUpdateConfirmationProps> = ({
@ -90,11 +90,13 @@ export const BatchUpdateConfirmation: FC<BatchUpdateConfirmationProps> = ({
// Figure out which new versions everything will be updated to so that we can // Figure out which new versions everything will be updated to so that we can
// show update messages and such. // show update messages and such.
const newVersions = useMemo(() => { const newVersions = useMemo(() => {
const newVersions = new Map< type MutableUpdateInfo = {
string, id: string;
Pick<Update, "id" | "template_display_name" | "affected_workspaces"> template_display_name: string;
>(); affected_workspaces: Workspace[];
};
const newVersions = new Map<string, MutableUpdateInfo>();
for (const it of workspacesToUpdate) { for (const it of workspacesToUpdate) {
const versionId = it.template_active_version_id; const versionId = it.template_active_version_id;
const version = newVersions.get(versionId); const version = newVersions.get(versionId);
@ -111,7 +113,11 @@ export const BatchUpdateConfirmation: FC<BatchUpdateConfirmationProps> = ({
}); });
} }
return newVersions; type ReadonlyUpdateInfo = Readonly<MutableUpdateInfo> & {
affected_workspaces: readonly Workspace[];
};
return newVersions as Map<string, ReadonlyUpdateInfo>;
}, [workspacesToUpdate]); }, [workspacesToUpdate]);
// Not all of the information we want is included in the `Workspace` type, so we // Not all of the information we want is included in the `Workspace` type, so we
@ -401,7 +407,7 @@ const TemplateVersionMessages: FC<TemplateVersionMessagesProps> = ({
}; };
interface UsedByProps { interface UsedByProps {
workspaces: Workspace[]; workspaces: readonly Workspace[];
} }
const UsedBy: FC<UsedByProps> = ({ workspaces }) => { const UsedBy: FC<UsedByProps> = ({ workspaces }) => {

View File

@ -54,7 +54,9 @@ const WorkspacesPage: FC = () => {
}); });
const updateWorkspace = useWorkspaceUpdate(queryKey); const updateWorkspace = useWorkspaceUpdate(queryKey);
const [checkedWorkspaces, setCheckedWorkspaces] = useState<Workspace[]>([]); const [checkedWorkspaces, setCheckedWorkspaces] = useState<
readonly Workspace[]
>([]);
const [confirmingBatchAction, setConfirmingBatchAction] = useState< const [confirmingBatchAction, setConfirmingBatchAction] = useState<
"delete" | "update" | null "delete" | "update" | null
>(null); >(null);

View File

@ -44,15 +44,15 @@ type TemplateQuery = UseQueryResult<Template[]>;
export interface WorkspacesPageViewProps { export interface WorkspacesPageViewProps {
error: unknown; error: unknown;
workspaces?: Workspace[]; workspaces?: readonly Workspace[];
checkedWorkspaces: Workspace[]; checkedWorkspaces: readonly Workspace[];
count?: number; count?: number;
filterProps: ComponentProps<typeof WorkspacesFilter>; filterProps: ComponentProps<typeof WorkspacesFilter>;
page: number; page: number;
limit: number; limit: number;
onPageChange: (page: number) => void; onPageChange: (page: number) => void;
onUpdateWorkspace: (workspace: Workspace) => void; onUpdateWorkspace: (workspace: Workspace) => void;
onCheckChange: (checkedWorkspaces: Workspace[]) => void; onCheckChange: (checkedWorkspaces: readonly Workspace[]) => void;
isRunningBatchAction: boolean; isRunningBatchAction: boolean;
onDeleteAll: () => void; onDeleteAll: () => void;
onUpdateAll: () => void; onUpdateAll: () => void;

View File

@ -30,12 +30,12 @@ import { getDisplayWorkspaceTemplateName } from "utils/workspace";
import { WorkspacesEmpty } from "./WorkspacesEmpty"; import { WorkspacesEmpty } from "./WorkspacesEmpty";
export interface WorkspacesTableProps { export interface WorkspacesTableProps {
workspaces?: Workspace[]; workspaces?: readonly Workspace[];
checkedWorkspaces: Workspace[]; checkedWorkspaces: readonly Workspace[];
error?: unknown; error?: unknown;
isUsingFilter: boolean; isUsingFilter: boolean;
onUpdateWorkspace: (workspace: Workspace) => void; onUpdateWorkspace: (workspace: Workspace) => void;
onCheckChange: (checkedWorkspaces: Workspace[]) => void; onCheckChange: (checkedWorkspaces: readonly Workspace[]) => void;
canCheckWorkspaces: boolean; canCheckWorkspaces: boolean;
templates?: Template[]; templates?: Template[];
canCreateTemplate: boolean; canCreateTemplate: boolean;

View File

@ -18,7 +18,7 @@ export function useBatchActions(options: UseBatchActionsProps) {
const { onSuccess } = options; const { onSuccess } = options;
const startAllMutation = useMutation({ const startAllMutation = useMutation({
mutationFn: (workspaces: Workspace[]) => { mutationFn: (workspaces: readonly Workspace[]) => {
return Promise.all( return Promise.all(
workspaces.map((w) => workspaces.map((w) =>
startWorkspace(w.id, w.latest_build.template_version_id), startWorkspace(w.id, w.latest_build.template_version_id),
@ -32,7 +32,7 @@ export function useBatchActions(options: UseBatchActionsProps) {
}); });
const stopAllMutation = useMutation({ const stopAllMutation = useMutation({
mutationFn: (workspaces: Workspace[]) => { mutationFn: (workspaces: readonly Workspace[]) => {
return Promise.all(workspaces.map((w) => stopWorkspace(w.id))); return Promise.all(workspaces.map((w) => stopWorkspace(w.id)));
}, },
onSuccess, onSuccess,
@ -42,7 +42,7 @@ export function useBatchActions(options: UseBatchActionsProps) {
}); });
const deleteAllMutation = useMutation({ const deleteAllMutation = useMutation({
mutationFn: (workspaces: Workspace[]) => { mutationFn: (workspaces: readonly Workspace[]) => {
return Promise.all(workspaces.map((w) => deleteWorkspace(w.id))); return Promise.all(workspaces.map((w) => deleteWorkspace(w.id)));
}, },
onSuccess, onSuccess,
@ -52,7 +52,7 @@ export function useBatchActions(options: UseBatchActionsProps) {
}); });
const updateAllMutation = useMutation({ const updateAllMutation = useMutation({
mutationFn: (workspaces: Workspace[]) => { mutationFn: (workspaces: readonly Workspace[]) => {
return Promise.all( return Promise.all(
workspaces workspaces
.filter((w) => w.outdated && !w.dormant_at) .filter((w) => w.outdated && !w.dormant_at)
@ -66,7 +66,7 @@ export function useBatchActions(options: UseBatchActionsProps) {
}); });
const favoriteAllMutation = useMutation({ const favoriteAllMutation = useMutation({
mutationFn: (workspaces: Workspace[]) => { mutationFn: (workspaces: readonly Workspace[]) => {
return Promise.all( return Promise.all(
workspaces workspaces
.filter((w) => !w.favorite) .filter((w) => !w.favorite)
@ -80,7 +80,7 @@ export function useBatchActions(options: UseBatchActionsProps) {
}); });
const unfavoriteAllMutation = useMutation({ const unfavoriteAllMutation = useMutation({
mutationFn: (workspaces: Workspace[]) => { mutationFn: (workspaces: readonly Workspace[]) => {
return Promise.all( return Promise.all(
workspaces workspaces
.filter((w) => w.favorite) .filter((w) => w.favorite)