8000 feat: setup connection to dynamic parameters websocket by jaaydenh · Pull Request #17393 · coder/coder · GitHub
[go: up one dir, main page]

Skip to content

feat: setup connection to dynamic parameters websocket #17393

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 20 commits into from
Apr 16, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
feat: create dynamic parameter component
  • Loading branch information
jaaydenh committed Apr 16, 2025
commit 7b6148570c6c6f9d2410e80a3e9f275731111b00
124 changes: 124 additions & 0 deletions site/src/api/typesParameter.ts
8000
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;
}
94 changes: 94 additions & 0 deletions site/src/hooks/useWebsocket.ts
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 };
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,20 @@ import type { AutofillBuildParameter } from "utils/richParameters";
import { CreateWorkspacePageViewExperimental } from "./CreateWorkspacePageViewExperimental";
export const createWorkspaceModes = ["form", "auto", "duplicate"] as const;
export type CreateWorkspaceMode = (typeof createWorkspaceModes)[number];
import type {
Response,
} from "api/typesParameter";
import { useWebSocket } from "hooks/useWebsocket";
import {
type CreateWorkspacePermissions,
createWorkspaceChecks,
} from "./permissions";
export type ExternalAuthPollingState = "idle" | "polling" | "abandoned";

const serverAddress = "localhost:8100";
const urlTestdata = "demo";
const wsUrl = `ws://${serverAddress}/ws/${encodeURIComponent(urlTestdata)}`;

const CreateWorkspacePageExperimental: FC = () => {
const { organization: organizationName = "default", template: templateName } =
useParams() as { organization?: string; template: string };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,44 @@ export interface CreateWorkspacePageViewExperimentalProps {
startPollingExternalAuth: () => void;
}

// const getInitialParameterValues = (
// params: Parameter[],
// autofillParams?: AutofillBuildParameter[],
// ): WorkspaceBuildParameter[] => {
// return params.map((parameter) => {
// // Short-circuit for ephemeral parameters, which are always reset to
// // the template-defined default.
// if (parameter.ephemeral) {
// return {
// name: parameter.name,
// value: parameter.default_value,
// };
// }

// const autofillParam = autofillParams?.find(
// ({ name }) => name === parameter.name,
// );

// return {
// name: parameter.name,
// value:
// autofillParam &&
// // isValidValue(parameter, autofillParam) &&
// autofillParam.source !== "user_history"
// ? autofillParam.value
// : parameter.default_value,
// };
// });
// };

const getInitialParameterValues = (parameters: Parameter[]) => {
return parameters.map((parameter) => {
return {
name: parameter.name,
value: parameter.default_value.valid ? parameter.default_value.value : "",
};
});
};
export const CreateWorkspacePageViewExperimental: FC<
CreateWorkspacePageViewExperimentalProps
> = ({
Expand Down
0