8000 feat: create dynamic parameter component by jaaydenh · Pull Request #17351 · coder/coder · GitHub
[go: up one dir, main page]

Skip to content

feat: create dynamic parameter component #17351

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Apr 16, 2025
Merged
Next Next commit
feat: create dynamic parameter component
  • Loading branch information
jaaydenh committed Apr 11, 2025
commit 1d32dbe8595579be32144e7543e55eaafdab9be2
124 changes: 124 additions & 0 deletions site/src/api/typesParameter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// Code generated by 'guts'. DO NOT EDIT.

// From types/diagnostics.go
export type DiagnosticSeverityString = "error" | "warning";

export const DiagnosticSeverityStrings: DiagnosticSeverityString[] = [
"error",
"warning",
];

// From types/diagnostics.go
export type Diagnostics = readonly FriendlyDiagnostic[];

// From types/diagnostics.go
export interface FriendlyDiagnostic {
readonly severity: DiagnosticSeverityString;
readonly summary: string;
readonly detail: string;
}

// From types/value.go
export interface NullHCLString {
readonly value: string;
readonly valid: boolean;
}

// From types/parameter.go
export interface Parameter extends ParameterData {
readonly value: NullHCLString;
readonly diagnostics: Diagnostics;
}

// From types/parameter.go
export interface ParameterData {
readonly name: string;
readonly display_name: string;
readonly description: string;
readonly type: ParameterType;
// this is likely an enum in an external package "github.com/coder/terraform-provider-coder/v2/provider.ParameterFormType"
readonly form_type: string;
// empty interface{} type, falling back to unknown
readonly styling: unknown;
readonly mutable: boolean;
readonly default_value: NullHCLString;
readonly icon: string;
readonly options: readonly ParameterOption[];
readonly validations: readonly ParameterValidation[];
readonly required: boolean;
readonly order: number;
readonly ephemeral: boolean;
}

// From types/parameter.go
export interface ParameterOption {
readonly name: string;
readonly description: string;
readonly value: NullHCLString;
readonly icon: string;
}

// From types/enum.go
export type ParameterType = "bool" | "list(string)" | "number" | "string";

export const ParameterTypes: ParameterType[] = [
"bool",
"list(string)",
"number",
"string",
];

// From types/parameter.go
export interface ParameterValidation {
readonly validation_error: string;
readonly validation_regex: string | null;
readonly validation_min: number | null;
readonly validation_max: number | null;
readonly validation_monotonic: string | null;
readonly validation_invalid: boolean | null;
}

// From web/session.go
export interface Request {
readonly id: number;
readonly inputs: Record<string, string>;
}

// From web/session.go
export interface Response {
readonly id: number;
readonly diagnostics: Diagnostics;
readonly parameters: readonly Parameter[];
}

// From web/session.go
export interface SessionInputs {
readonly PlanPath: string;
readonly User: WorkspaceOwner;
}

// From types/parameter.go
export const ValidationMonotonicDecreasing = "decreasing";

// From types/parameter.go
export const ValidationMonotonicIncreasing = "increasing";

// From types/owner.go
export interface WorkspaceOwner {
readonly id: string;
readonly name: string;
readonly full_name: string;
readonly email: string;
readonly ssh_public_key: string;
readonly groups: readonly string[];
readonly session_token: string;
readonly oidc_access_token: string;
readonly login_type: string;
readonly rbac_roles: readonly WorkspaceOwnerRBACRole[];
}

// From types/owner.go
export interface WorkspaceOwnerRBACRole {
readonly name: string;
readonly org_id: string;
}
3 changes: 3 additions & 0 deletions site/src/components/Checkbox/Checkbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import * as React from "react";

import { cn } from "utils/cn";

/**
* To allow for an indeterminate state the checkbox must be controlled, otherwise the checked prop would remain undefined
*/
export const Checkbox = React.forwardRef<
React.ElementRef<typeof CheckboxPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
Expand Down
13 changes: 11 additions & 2 deletions site/src/components/MultiSelectCombobox/MultiSelectCombobox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -203,9 +203,18 @@ export const MultiSelectCombobox = forwardRef<
const [open, setOpen] = useState(false);
const [onScrollbar, setOnScrollbar] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const dropdownRef = useRef<HTMLDivElement>(null); // Added this
const dropdownRef = useRef<HTMLDivElement>(null);

const [selected, setSelected] = useState<Option[]>(value || []);
const getInitialSelectedOptions = () => {
if (arrayDefaultOptions && arrayDefaultOptions.length > 0) {
return arrayDefaultOptions;
}
return [];
};

const [selected, setSelected] = useState<Option[]>(
getInitialSelectedOptions,
);
const [options, setOptions] = useState<GroupOption>(
transitionToGroupOption(arrayDefaultOptions, groupBy),
);
Expand Down
2 changes: 1 addition & 1 deletion site/src/components/RadioGroup/RadioGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export const RadioGroupItem = React.forwardRef<
focus:outline-none focus-visible:ring-2 focus-visible:ring-content-link
focus-visible:ring-offset-4 focus-visible:ring-offset-surface-primary
disabled:cursor-not-allowed disabled:opacity-25 disabled:border-surface-invert-primary
hover:border-border-hover`,
hover:border-border-hover data-[state=checked]:border-border-hover`,
className,
)}
{...props}
Expand Down
94 changes: 94 additions & 0 deletions site/src/hooks/useWebsocket.ts
B41A
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// This file is temporary until we have a proper websocket implementation for dynamic parameters
import { useCallback, useEffect, useRef, useState } from "react";

export function useWebSocket<T>(
url: string,
testdata: string,
user: string,
plan: string,
) {
const [message, setMessage] = useState<T | null>(null);
const [connectionStatus, setConnectionStatus] = useState<
"connecting" | "connected" | "disconnected"
>("connecting");
const wsRef = useRef<WebSocket | null>(null);
const urlRef = useRef(url);

const connectWebSocket = useCallback(() => {
try {
const ws = new WebSocket(urlRef.current);
wsRef.current = ws;
setConnectionStatus("connecting");

ws.onopen = () => {
// console.log("Connected to WebSocket");
setConnectionStatus("connected");
ws.send(JSON.stringify({}));
};

ws.onmessage = (event) => {
try {
const data: T = JSON.parse(event.data);
// console.log("Received message:", data);
setMessage(data);
} catch (err) {
console.error("Invalid JSON from server: ", event.data);
console.error("Error: ", err);
}
};

ws.onerror = (event) => {
console.error("WebSocket error:", event);
};

ws.onclose = (event) => {
// console.log(
// `WebSocket closed with code ${event.code}. Reason: ${event.reason}`,
// );
setConnectionStatus("disconnected");
};
} catch (error) {
console.error("Failed to create WebSocket connection:", error);
setConnectionStatus("disconnected");
}
}, []);

useEffect(() => {
if (!testdata) {
return;
}

setMessage(null);
setConnectionStatus("connecting");

const createConnection = () => {
urlRef.current = url;
connectWebSocket();
};

if (wsRef.current) {
wsRef.current.close();
wsRef.current = null;
}

const timeoutId = setTimeout(createConnection, 100);

return () => {
clearTimeout(timeoutId);
if (wsRef.current) {
wsRef.current.close();
wsRef.current = null;
}
};
}, [testdata, connectWebSocket, url]);

const sendMessage = (data: unknown) => {
if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) {
wsRef.current.send(JSON.stringify(data));
} else {
console.warn("Cannot send message: WebSocket is not connected");
}
};

return { message, sendMessage, connectionStatus };
}
Loading
0