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)
}
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 != "" {
genValue = underlying.GenericValue + "[]"
genValue = "(readonly " + underlying.GenericValue + "[])"
}
return TypescriptType{
ValueType: underlying.ValueType + "[]",
ValueType: "(readonly " + underlying.ValueType + "[])",
GenericValue: genValue,
AboveTypeLine: underlying.AboveTypeLine,
GenericTypes: underlying.GenericTypes,

View File

@ -7,6 +7,7 @@
package main
import (
"flag"
"os"
"path/filepath"
"strings"
@ -15,6 +16,9 @@ import (
"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) {
t.Parallel()
files, err := os.ReadDir("testdata")
@ -37,7 +41,13 @@ func TestGeneration(t *testing.T) {
require.NoErrorf(t, err, "read file %s", golden)
expectedString := strings.TrimSpace(string(expected))
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
type (
Enum string
Enums []Enum
Enum string
EnumSliceType []Enum
)
const (

View File

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

View File

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

View File

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

View File

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

View File

@ -4,7 +4,7 @@ import { TimelineDateRow } from "components/Timeline/TimelineDateRow";
type GetDateFn<TData> = (data: TData) => Date;
const groupByDate = <TData,>(
items: TData[],
items: readonly TData[],
getDate: GetDateFn<TData>,
): Record<string, TData[]> => {
const itemsByDate: Record<string, TData[]> = {};
@ -23,7 +23,7 @@ const groupByDate = <TData,>(
};
export interface TimelineProps<TData> {
items: TData[];
items: readonly TData[];
getDate: GetDateFn<TData>;
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
// more information about the proxy and the status. More information includes the error message if
// the proxy is unhealthy.
proxies?: Region[] | WorkspaceProxy[];
proxies?: readonly Region[] | readonly WorkspaceProxy[];
// isFetched is true when the 'proxies' api call is complete.
isFetched: boolean;
isLoading: boolean;
@ -117,7 +117,7 @@ export const ProxyProvider: FC<PropsWithChildren> = ({ children }) => {
});
const { permissions } = useAuthenticated();
const query = async (): Promise<Region[]> => {
const query = async (): Promise<readonly Region[]> => {
const endpoint = permissions.editWorkspaceProxies
? getWorkspaceProxies
: getWorkspaceProxyRegions;
@ -218,7 +218,7 @@ export const useProxy = (): ProxyContextValue => {
* If not, `primary` is always the best default.
*/
export const getPreferredProxy = (
proxies: Region[],
proxies: readonly Region[],
selectedProxy?: Region,
latencies?: Record<string, ProxyLatencyReport>,
autoSelectBasedOnLatency = true,
@ -245,7 +245,7 @@ export const getPreferredProxy = (
};
const selectByLatency = (
proxies: Region[],
proxies: readonly Region[],
latencies?: Record<string, ProxyLatencyReport>,
): Region | undefined => {
if (!latencies) {

View File

@ -37,7 +37,7 @@ const proxyLatenciesReducer = (
};
export const useProxyLatency = (
proxies?: Region[],
proxies?: readonly Region[],
): {
// Refetch can be called to refetch the proxy latencies.
// 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
// that no longer exist. This is intended to keep the size of local storage down.
const garbageCollectStoredLatencies = (
regions: Region[],
regions: readonly Region[],
maxStored: number,
): void => {
const latencies = loadStoredLatencies();
@ -282,7 +282,7 @@ const garbageCollectStoredLatencies = (
const cleanupLatencies = (
stored: Record<string, ProxyLatencyReport[]>,
regions: Region[],
regions: readonly Region[],
now: Date,
maxStored: number,
): Record<string, ProxyLatencyReport[]> => {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -17,8 +17,8 @@ import {
import { optionValue } from "./optionValue";
interface OptionsTableProps {
options: SerpentOption[];
additionalValues?: string[];
options: readonly SerpentOption[];
additionalValues?: readonly string[];
}
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
export function optionValue(
option: SerpentOption,
additionalValues?: string[],
additionalValues?: readonly string[],
) {
// If option annotations are present, use them to format the value.
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 }) => {
const theme = useTheme();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -18,7 +18,7 @@ import { Stack } from "components/Stack/Stack";
dayjs.extend(relativeTime);
type BatchUpdateConfirmationProps = {
checkedWorkspaces: Workspace[];
checkedWorkspaces: readonly Workspace[];
open: boolean;
isLoading: boolean;
onClose: () => void;
@ -27,7 +27,7 @@ type BatchUpdateConfirmationProps = {
export interface Update extends TemplateVersion {
template_display_name: string;
affected_workspaces: Workspace[];
affected_workspaces: readonly Workspace[];
}
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
// show update messages and such.
const newVersions = useMemo(() => {
const newVersions = new Map<
string,
Pick<Update, "id" | "template_display_name" | "affected_workspaces">
>();
type MutableUpdateInfo = {
id: string;
template_display_name: string;
affected_workspaces: Workspace[];
};
const newVersions = new Map<string, MutableUpdateInfo>();
for (const it of workspacesToUpdate) {
const versionId = it.template_active_version_id;
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]);
// 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 {
workspaces: Workspace[];
workspaces: readonly Workspace[];
}
const UsedBy: FC<UsedByProps> = ({ workspaces }) => {

View File

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

View File

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

View File

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

View File

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