Skip to content

Commit 1d32dbe

Browse files
committed
feat: create dynamic parameter component
1 parent 39b9d23 commit 1d32dbe

File tree

8 files changed

+832
-91
lines changed

8 files changed

+832
-91
lines changed

site/src/api/typesParameter.ts

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
// Code generated by 'guts'. DO NOT EDIT.
2+
3+
// From types/diagnostics.go
4+
export type DiagnosticSeverityString = "error" | "warning";
5+
6+
export const DiagnosticSeverityStrings: DiagnosticSeverityString[] = [
7+
"error",
8+
"warning",
9+
];
10+
11+
// From types/diagnostics.go
12+
export type Diagnostics = readonly FriendlyDiagnostic[];
13+
14+
// From types/diagnostics.go
15+
export interface FriendlyDiagnostic {
16+
readonly severity: DiagnosticSeverityString;
17+
readonly summary: string;
18+
readonly detail: string;
19+
}
20+
21+
// From types/value.go
22+
export interface NullHCLString {
23+
readonly value: string;
24+
readonly valid: boolean;
25+
}
26+
27+
// From types/parameter.go
28+
export interface Parameter extends ParameterData {
29+
readonly value: NullHCLString;
30+
readonly diagnostics: Diagnostics;
31+
}
32+
33+
// From types/parameter.go
34+
export interface ParameterData {
35+
readonly name: string;
36+
readonly display_name: string;
37+
readonly description: string;
38+
readonly type: ParameterType;
39+
// this is likely an enum in an external package "github.com/coder/terraform-provider-coder/v2/provider.ParameterFormType"
40+
readonly form_type: string;
41+
// empty interface{} type, falling back to unknown
42+
readonly styling: unknown;
43+
readonly mutable: boolean;
44+
readonly default_value: NullHCLString;
45+
readonly icon: string;
46+
readonly options: readonly ParameterOption[];
47+
readonly validations: readonly ParameterValidation[];
48+
readonly required: boolean;
49+
readonly order: number;
50+
readonly ephemeral: boolean;
51+
}
52+
53+
// From types/parameter.go
54+
export interface ParameterOption {
55+
readonly name: string;
56+
readonly description: string;
57+
readonly value: NullHCLString;
58+
readonly icon: string;
59+
}
60+
61+
// From types/enum.go
62+
export type ParameterType = "bool" | "list(string)" | "number" | "string";
63+
64+
export const ParameterTypes: ParameterType[] = [
65+
"bool",
66+
"list(string)",
67+
"number",
68+
"string",
69+
];
70+
71+
// From types/parameter.go
72+
export interface ParameterValidation {
73+
readonly validation_error: string;
74+
readonly validation_regex: string | null;
75+
readonly validation_min: number | null;
76+
readonly validation_max: number | null;
77+
readonly validation_monotonic: string | null;
78+
readonly validation_invalid: boolean | null;
79+
}
80+
81+
// From web/session.go
82+
export interface Request {
83+
readonly id: number;
84+
readonly inputs: Record<string, string>;
85+
}
86+
87+
// From web/session.go
88+
export interface Response {
89+
readonly id: number;
90+
readonly diagnostics: Diagnostics;
91+
readonly parameters: readonly Parameter[];
92+
}
93+
94+
// From web/session.go
95+
export interface SessionInputs {
96+
readonly PlanPath: string;
97+
readonly User: WorkspaceOwner;
98+
}
99+
100+
// From types/parameter.go
101+
export const ValidationMonotonicDecreasing = "decreasing";
102+
103+
// From types/parameter.go
104+
export const ValidationMonotonicIncreasing = "increasing";
105+
106+
// From types/owner.go
107+
export interface WorkspaceOwner {
108+
readonly id: string;
109+
readonly name: string;
110+
readonly full_name: string;
111+
readonly email: string;
112+
readonly ssh_public_key: string;
113+
readonly groups: readonly string[];
114+
readonly session_token: string;
115+
readonly oidc_access_token: string;
116+
readonly login_type: string;
117+
readonly rbac_roles: readonly WorkspaceOwnerRBACRole[];
118+
}
119+
120+
// From types/owner.go
121+
export interface WorkspaceOwnerRBACRole {
122+
readonly name: string;
123+
readonly org_id: string;
124+
}

site/src/components/Checkbox/Checkbox.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ import * as React from "react";
88

99
import { cn } from "utils/cn";
1010

11+
/**
12+
* To allow for an indeterminate state the checkbox must be controlled, otherwise the checked prop would remain undefined
13+
*/
1114
export const Checkbox = React.forwardRef<
1215
React.ElementRef<typeof CheckboxPrimitive.Root>,
1316
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>

site/src/components/MultiSelectCombobox/MultiSelectCombobox.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,9 +203,18 @@ export const MultiSelectCombobox = forwardRef<
203203
const [open, setOpen] = useState(false);
204204
const [onScrollbar, setOnScrollbar] = useState(false);
205205
const [isLoading, setIsLoading] = useState(false);
206-
const dropdownRef = useRef<HTMLDivElement>(null); // Added this
206+
const dropdownRef = useRef<HTMLDivElement>(null);
207207

208-
const [selected, setSelected] = useState<Option[]>(value || []);
208+
const getInitialSelectedOptions = () => {
209+
if (arrayDefaultOptions && arrayDefaultOptions.length > 0) {
210+
return arrayDefaultOptions;
211+
}
212+
return [];
213+
};
214+
215+
const [selected, setSelected] = useState<Option[]>(
216+
getInitialSelectedOptions,
217+
);
209218
const [options, setOptions] = useState<GroupOption>(
210219
transitionToGroupOption(arrayDefaultOptions, groupBy),
211220
);

site/src/components/RadioGroup/RadioGroup.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export const RadioGroupItem = React.forwardRef<
3434
focus:outline-none focus-visible:ring-2 focus-visible:ring-content-link
3535
focus-visible:ring-offset-4 focus-visible:ring-offset-surface-primary
3636
disabled:cursor-not-allowed disabled:opacity-25 disabled:border-surface-invert-primary
37-
hover:border-border-hover`,
37+
hover:border-border-hover data-[state=checked]:border-border-hover`,
3838
className,
3939
)}
4040
{...props}

site/src/hooks/useWebsocket.ts

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// This file is temporary until we have a proper websocket implementation for dynamic parameters
2+
import { useCallback, useEffect, useRef, useState } from "react";
3+
4+
export function useWebSocket<T>(
5+
url: string,
6+
testdata: string,
7+
user: string,
8+
plan: string,
9+
) {
10+
const [message, setMessage] = useState<T | null>(null);
11+
const [connectionStatus, setConnectionStatus] = useState<
12+
"connecting" | "connected" | "disconnected"
13+
>("connecting");
14+
const wsRef = useRef<WebSocket | null>(null);
15+
const urlRef = useRef(url);
16+
17+
const connectWebSocket = useCallback(() => {
18+
try {
19+
const ws = new WebSocket(urlRef.current);
20+
wsRef.current = ws;
21+
setConnectionStatus("connecting");
22+
23+
ws.onopen = () => {
24+
// console.log("Connected to WebSocket");
25+
setConnectionStatus("connected");
26+
ws.send(JSON.stringify({}));
27+
};
28+
29+
ws.onmessage = (event) => {
30+
try {
31+
const data: T = JSON.parse(event.data);
32+
// console.log("Received message:", data);
33+
setMessage(data);
34+
} catch (err) {
35+
console.error("Invalid JSON from server: ", event.data);
36+
console.error("Error: ", err);
37+
}
38+
};
39+
40+
ws.onerror = (event) => {
41+
console.error("WebSocket error:", event);
42+
};
43+
44+
ws.onclose = (event) => {
45+
// console.log(
46+
// `WebSocket closed with code ${event.code}. Reason: ${event.reason}`,
47+
// );
48+
setConnectionStatus("disconnected");
49+
};
50+
} catch (error) {
51+
console.error("Failed to create WebSocket connection:", error);
52+
setConnectionStatus("disconnected");
53+
}
54+
}, []);
55+
56+
useEffect(() => {
57+
if (!testdata) {
58+
return;
59+
}
60+
61+
setMessage(null);
62+
setConnectionStatus("connecting");
63+
64+
const createConnection = () => {
65+
urlRef.current = url;
66+
connectWebSocket();
67+
};
68+
69+
if (wsRef.current) {
70+
wsRef.current.close();
71+
wsRef.current = null;
72+
}
73+
74+
const timeoutId = setTimeout(createConnection, 100);
75+
76+
return () => {
77+
clearTimeout(timeoutId);
78+
if (wsRef.current) {
79+
wsRef.current.close();
80+
wsRef.current = null;
81+
}
82+
};
83+
}, [testdata, connectWebSocket, url]);
84+
85+
const sendMessage = (data: unknown) => {
86+
if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) {
87+
wsRef.current.send(JSON.stringify(data));
88+
} else {
89+
console.warn("Cannot send message: WebSocket is not connected");
90+
}
91+
};
92+
93+
return { message, sendMessage, connectionStatus };
94+
}

0 commit comments

Comments
 (0)