mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
feat: add ephemeral parameter dialog for workspace start/restart (#18413)
resolves #17709 FYI, blink created a first draft which was heavily modified. ## Summary This PR implements ephemeral parameter handling for workspace start/restart operations when templates use dynamic parameters (`use_classic_parameter_flow = false`). <img width="522" alt="Screenshot 2025-06-18 at 14 35 54" src="https://github.com/user-attachments/assets/450527c0-cc88-4fc3-b0fa-170bdeb5ea51" /> <img width="327" alt="Screenshot 2025-06-18 at 14 35 43" src="https://github.com/user-attachments/assets/ea74bf8e-d127-489d-b406-edfc5ec1e9a8" />  ## Changes ### 1. EphemeralParametersDialog Component - **New**: `site/src/components/EphemeralParametersDialog/` - Shows a dialog when starting/restarting workspaces with ephemeral parameters - Lists ephemeral parameters with names and descriptions - Provides options to continue without setting values or navigate to parameters page ### 2. WorkspaceReadyPage Updates - Added `checkEphemeralParameters()` function using `API.getDynamicParameters` - Modified `handleStart` and `handleRestart` to check for ephemeral parameters - Only triggers for templates with `use_classic_parameter_flow = false` - Shows dialog if ephemeral parameters exist, otherwise proceeds normally ### 3. BuildParametersPopover Updates - Added special UI for non-classic parameter flow templates with ephemeral parameters - Lists ephemeral parameters with descriptions - Explains that users must use the workspace parameters page - Provides direct link to `WorkspaceParametersPageExperimental` --------- Co-authored-by: blink-so[bot] <211532188+blink-so[bot]@users.noreply.github.com> Co-authored-by: jaaydenh <1858163+jaaydenh@users.noreply.github.com> Co-authored-by: Jaayden Halko <jaayden@coder.com>
This commit is contained in:
@@ -22,6 +22,8 @@ const badgeVariants = cva(
|
|||||||
"border border-solid border-border-warning bg-surface-orange text-content-warning shadow",
|
"border border-solid border-border-warning bg-surface-orange text-content-warning shadow",
|
||||||
destructive:
|
destructive:
|
||||||
"border border-solid border-border-destructive bg-surface-red text-highlight-red shadow",
|
"border border-solid border-border-destructive bg-surface-red text-highlight-red shadow",
|
||||||
|
green:
|
||||||
|
"border border-solid border-surface-green bg-surface-green text-highlight-green shadow",
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
xs: "text-2xs font-regular h-5 [&_svg]:hidden rounded px-1.5",
|
xs: "text-2xs font-regular h-5 [&_svg]:hidden rounded px-1.5",
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
import type { TemplateVersionParameter } from "api/typesGenerated";
|
||||||
|
import { Button } from "components/Button/Button";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from "components/Dialog/Dialog";
|
||||||
|
import type { FC } from "react";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
|
interface EphemeralParametersDialogProps {
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
onContinue: () => void;
|
||||||
|
ephemeralParameters: TemplateVersionParameter[];
|
||||||
|
workspaceOwner: string;
|
||||||
|
workspaceName: string;
|
||||||
|
templateVersionId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EphemeralParametersDialog: FC<EphemeralParametersDialogProps> = ({
|
||||||
|
open,
|
||||||
|
onClose,
|
||||||
|
onContinue,
|
||||||
|
ephemeralParameters,
|
||||||
|
workspaceOwner,
|
||||||
|
workspaceName,
|
||||||
|
templateVersionId,
|
||||||
|
}) => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const handleGoToParameters = () => {
|
||||||
|
onClose();
|
||||||
|
navigate(
|
||||||
|
`/@${workspaceOwner}/${workspaceName}/settings/parameters?templateVersionId=${templateVersionId}`,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onOpenChange={(isOpen) => !isOpen && onClose()}>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Ephemeral Parameters Detected</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
This workspace template has{" "}
|
||||||
|
<strong className="text-content-primary">
|
||||||
|
{ephemeralParameters.length}
|
||||||
|
</strong>{" "}
|
||||||
|
ephemeral parameters that will be reset to their default values
|
||||||
|
</DialogDescription>
|
||||||
|
<DialogDescription>
|
||||||
|
<ul className="list-none pl-6 space-y-2">
|
||||||
|
{ephemeralParameters.map((param) => (
|
||||||
|
<li key={param.name}>
|
||||||
|
<p className="text-content-primary m-0 font-bold">
|
||||||
|
{param.display_name || param.name}
|
||||||
|
</p>
|
||||||
|
{param.description && (
|
||||||
|
<p className="m-0 text-sm text-content-secondary">
|
||||||
|
{param.description}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</DialogDescription>
|
||||||
|
<DialogDescription>
|
||||||
|
Would you like to go to the workspace parameters page to review and
|
||||||
|
update these parameters before continuing?
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<DialogFooter>
|
||||||
|
<Button onClick={onContinue} variant="outline">
|
||||||
|
Continue
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleGoToParameters}>
|
||||||
|
Go to workspace parameters
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -211,6 +211,15 @@ export const Immutable: Story = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const Ephemeral: Story = {
|
||||||
|
args: {
|
||||||
|
parameter: {
|
||||||
|
...MockPreviewParameter,
|
||||||
|
ephemeral: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export const AllBadges: Story = {
|
export const AllBadges: Story = {
|
||||||
args: {
|
args: {
|
||||||
parameter: {
|
parameter: {
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import { useDebouncedValue } from "hooks/debounce";
|
|||||||
import { useEffectEvent } from "hooks/hookPolyfills";
|
import { useEffectEvent } from "hooks/hookPolyfills";
|
||||||
import {
|
import {
|
||||||
CircleAlert,
|
CircleAlert,
|
||||||
|
Hourglass,
|
||||||
Info,
|
Info,
|
||||||
LinkIcon,
|
LinkIcon,
|
||||||
Settings,
|
Settings,
|
||||||
@@ -162,6 +163,23 @@ const ParameterLabel: FC<ParameterLabelProps> = ({
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
)}
|
)}
|
||||||
|
{parameter.ephemeral && (
|
||||||
|
<TooltipProvider delayDuration={100}>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<span className="flex items-center">
|
||||||
|
<Badge size="sm" variant="green" border="none">
|
||||||
|
<Hourglass />
|
||||||
|
Ephemeral
|
||||||
|
</Badge>
|
||||||
|
</span>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent className="max-w-xs">
|
||||||
|
This parameter only applies for a single workspace start
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
)}
|
||||||
{isPreset && (
|
{isPreset && (
|
||||||
<TooltipProvider delayDuration={100}>
|
<TooltipProvider delayDuration={100}>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { useTheme } from "@emotion/react";
|
import { useTheme } from "@emotion/react";
|
||||||
import Button from "@mui/material/Button";
|
|
||||||
import visuallyHidden from "@mui/utils/visuallyHidden";
|
import visuallyHidden from "@mui/utils/visuallyHidden";
|
||||||
import { API } from "api/api";
|
import { API } from "api/api";
|
||||||
import type {
|
import type {
|
||||||
@@ -7,6 +6,7 @@ import type {
|
|||||||
Workspace,
|
Workspace,
|
||||||
WorkspaceBuildParameter,
|
WorkspaceBuildParameter,
|
||||||
} from "api/typesGenerated";
|
} from "api/typesGenerated";
|
||||||
|
import { Button } from "components/Button/Button";
|
||||||
import { FormFields } from "components/Form/Form";
|
import { FormFields } from "components/Form/Form";
|
||||||
import { TopbarButton } from "components/FullPageLayout/Topbar";
|
import { TopbarButton } from "components/FullPageLayout/Topbar";
|
||||||
import {
|
import {
|
||||||
@@ -27,6 +27,7 @@ import { useFormik } from "formik";
|
|||||||
import { ChevronDownIcon } from "lucide-react";
|
import { ChevronDownIcon } from "lucide-react";
|
||||||
import type { FC } from "react";
|
import type { FC } from "react";
|
||||||
import { useQuery } from "react-query";
|
import { useQuery } from "react-query";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
import { docs } from "utils/docs";
|
import { docs } from "utils/docs";
|
||||||
import { getFormHelpers } from "utils/formUtils";
|
import { getFormHelpers } from "utils/formUtils";
|
||||||
import {
|
import {
|
||||||
@@ -72,6 +73,7 @@ export const BuildParametersPopover: FC<BuildParametersPopoverProps> = ({
|
|||||||
css={{ ".MuiPaper-root": { width: 304 } }}
|
css={{ ".MuiPaper-root": { width: 304 } }}
|
||||||
>
|
>
|
||||||
<BuildParametersPopoverContent
|
<BuildParametersPopoverContent
|
||||||
|
workspace={workspace}
|
||||||
ephemeralParameters={ephemeralParameters}
|
ephemeralParameters={ephemeralParameters}
|
||||||
buildParameters={parameters?.buildParameters}
|
buildParameters={parameters?.buildParameters}
|
||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
@@ -82,18 +84,67 @@ export const BuildParametersPopover: FC<BuildParametersPopoverProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
interface BuildParametersPopoverContentProps {
|
interface BuildParametersPopoverContentProps {
|
||||||
|
workspace: Workspace;
|
||||||
ephemeralParameters?: TemplateVersionParameter[];
|
ephemeralParameters?: TemplateVersionParameter[];
|
||||||
buildParameters?: WorkspaceBuildParameter[];
|
buildParameters?: WorkspaceBuildParameter[];
|
||||||
onSubmit: (buildParameters: WorkspaceBuildParameter[]) => void;
|
onSubmit: (buildParameters: WorkspaceBuildParameter[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const BuildParametersPopoverContent: FC<BuildParametersPopoverContentProps> = ({
|
const BuildParametersPopoverContent: FC<BuildParametersPopoverContentProps> = ({
|
||||||
|
workspace,
|
||||||
ephemeralParameters,
|
ephemeralParameters,
|
||||||
buildParameters,
|
buildParameters,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
}) => {
|
}) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const popover = usePopover();
|
const popover = usePopover();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
if (
|
||||||
|
!workspace.template_use_classic_parameter_flow &&
|
||||||
|
ephemeralParameters &&
|
||||||
|
ephemeralParameters.length > 0
|
||||||
|
) {
|
||||||
|
const handleGoToParameters = () => {
|
||||||
|
popover.setOpen(false);
|
||||||
|
navigate(
|
||||||
|
`/@${workspace.owner_name}/${workspace.name}/settings/parameters`,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-4 p-5">
|
||||||
|
<h1 className="text-xl m-0 text-content-primary font-semibold leading-none ">
|
||||||
|
Ephemeral Parameters
|
||||||
|
</h1>
|
||||||
|
<p className="m-0 text-sm text-content-secondary">
|
||||||
|
This template has ephemeral parameters that must be configured on the
|
||||||
|
workspace parameters page
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<ul className="list-none pl-3 space-y-2">
|
||||||
|
{ephemeralParameters.map((param) => (
|
||||||
|
<li key={param.name}>
|
||||||
|
<p className="text-content-primary m-0 font-bold">
|
||||||
|
{param.display_name || param.name}
|
||||||
|
</p>
|
||||||
|
{param.description && (
|
||||||
|
<p className="m-0 text-sm text-content-secondary">
|
||||||
|
{param.description}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button className="w-full" onClick={handleGoToParameters}>
|
||||||
|
Go to workspace parameters
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -206,8 +257,6 @@ const Form: FC<FormProps> = ({
|
|||||||
<Button
|
<Button
|
||||||
data-testid="build-parameters-submit"
|
data-testid="build-parameters-submit"
|
||||||
type="submit"
|
type="submit"
|
||||||
variant="contained"
|
|
||||||
color="primary"
|
|
||||||
css={{ width: "100%" }}
|
css={{ width: "100%" }}
|
||||||
>
|
>
|
||||||
Build workspace
|
Build workspace
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
ConfirmDialog,
|
ConfirmDialog,
|
||||||
type ConfirmDialogProps,
|
type ConfirmDialogProps,
|
||||||
} from "components/Dialogs/ConfirmDialog/ConfirmDialog";
|
} from "components/Dialogs/ConfirmDialog/ConfirmDialog";
|
||||||
|
import { EphemeralParametersDialog } from "components/EphemeralParametersDialog/EphemeralParametersDialog";
|
||||||
import { displayError } from "components/GlobalSnackbar/utils";
|
import { displayError } from "components/GlobalSnackbar/utils";
|
||||||
import { useWorkspaceBuildLogs } from "hooks/useWorkspaceBuildLogs";
|
import { useWorkspaceBuildLogs } from "hooks/useWorkspaceBuildLogs";
|
||||||
import {
|
import {
|
||||||
@@ -53,6 +54,13 @@ export const WorkspaceReadyPage: FC<WorkspaceReadyPageProps> = ({
|
|||||||
open: boolean;
|
open: boolean;
|
||||||
buildParameters?: TypesGen.WorkspaceBuildParameter[];
|
buildParameters?: TypesGen.WorkspaceBuildParameter[];
|
||||||
}>({ open: false });
|
}>({ open: false });
|
||||||
|
|
||||||
|
const [ephemeralParametersDialog, setEphemeralParametersDialog] = useState<{
|
||||||
|
open: boolean;
|
||||||
|
action: "start" | "restart";
|
||||||
|
buildParameters?: TypesGen.WorkspaceBuildParameter[];
|
||||||
|
ephemeralParameters: TypesGen.TemplateVersionParameter[];
|
||||||
|
}>({ open: false, action: "start", ephemeralParameters: [] });
|
||||||
const { mutate: mutateRestartWorkspace, isPending: isRestarting } =
|
const { mutate: mutateRestartWorkspace, isPending: isRestarting } =
|
||||||
useMutation({
|
useMutation({
|
||||||
mutationFn: API.restartWorkspace,
|
mutationFn: API.restartWorkspace,
|
||||||
@@ -137,12 +145,49 @@ export const WorkspaceReadyPage: FC<WorkspaceReadyPageProps> = ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const runLastBuild = (
|
const checkEphemeralParameters = async (
|
||||||
|
buildParameters?: TypesGen.WorkspaceBuildParameter[],
|
||||||
|
) => {
|
||||||
|
if (workspace.template_use_classic_parameter_flow) {
|
||||||
|
return { hasEphemeral: false, ephemeralParameters: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const dynamicParameters = await API.getDynamicParameters(
|
||||||
|
workspace.latest_build.template_version_id,
|
||||||
|
workspace.owner_id,
|
||||||
|
buildParameters || [],
|
||||||
|
);
|
||||||
|
|
||||||
|
const ephemeralParameters = dynamicParameters.filter(
|
||||||
|
(param) => param.ephemeral,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
hasEphemeral: ephemeralParameters.length > 0,
|
||||||
|
ephemeralParameters,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return { hasEphemeral: false, ephemeralParameters: [] };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const runLastBuild = async (
|
||||||
buildParameters: TypesGen.WorkspaceBuildParameter[] | undefined,
|
buildParameters: TypesGen.WorkspaceBuildParameter[] | undefined,
|
||||||
debug: boolean,
|
debug: boolean,
|
||||||
) => {
|
) => {
|
||||||
const logLevel = debug ? "debug" : undefined;
|
const logLevel = debug ? "debug" : undefined;
|
||||||
|
|
||||||
|
const { hasEphemeral, ephemeralParameters } =
|
||||||
|
await checkEphemeralParameters(buildParameters);
|
||||||
|
if (hasEphemeral) {
|
||||||
|
setEphemeralParametersDialog({
|
||||||
|
open: true,
|
||||||
|
action: "start",
|
||||||
|
buildParameters,
|
||||||
|
ephemeralParameters,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
switch (workspace.latest_build.transition) {
|
switch (workspace.latest_build.transition) {
|
||||||
case "start":
|
case "start":
|
||||||
startWorkspaceMutation.mutate({
|
startWorkspaceMutation.mutate({
|
||||||
@@ -157,18 +202,19 @@ export const WorkspaceReadyPage: FC<WorkspaceReadyPageProps> = ({
|
|||||||
deleteWorkspaceMutation.mutate({ log_level: logLevel });
|
deleteWorkspaceMutation.mutate({ log_level: logLevel });
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRetry = (
|
const handleRetry = async (
|
||||||
buildParameters?: TypesGen.WorkspaceBuildParameter[],
|
buildParameters?: TypesGen.WorkspaceBuildParameter[],
|
||||||
) => {
|
) => {
|
||||||
runLastBuild(buildParameters, false);
|
await runLastBuild(buildParameters, false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDebug = (
|
const handleDebug = async (
|
||||||
buildParameters?: TypesGen.WorkspaceBuildParameter[],
|
buildParameters?: TypesGen.WorkspaceBuildParameter[],
|
||||||
) => {
|
) => {
|
||||||
runLastBuild(buildParameters, true);
|
await runLastBuild(buildParameters, true);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -196,14 +242,36 @@ export const WorkspaceReadyPage: FC<WorkspaceReadyPageProps> = ({
|
|||||||
template={template}
|
template={template}
|
||||||
buildLogs={buildLogs}
|
buildLogs={buildLogs}
|
||||||
timings={timingsQuery.data}
|
timings={timingsQuery.data}
|
||||||
handleStart={(buildParameters) => {
|
handleStart={async (buildParameters) => {
|
||||||
|
const { hasEphemeral, ephemeralParameters } =
|
||||||
|
await checkEphemeralParameters(buildParameters);
|
||||||
|
if (hasEphemeral) {
|
||||||
|
setEphemeralParametersDialog({
|
||||||
|
open: true,
|
||||||
|
action: "start",
|
||||||
|
buildParameters,
|
||||||
|
ephemeralParameters,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
startWorkspaceMutation.mutate({ buildParameters });
|
startWorkspaceMutation.mutate({ buildParameters });
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
handleStop={() => {
|
handleStop={() => {
|
||||||
stopWorkspaceMutation.mutate({});
|
stopWorkspaceMutation.mutate({});
|
||||||
}}
|
}}
|
||||||
handleRestart={(buildParameters) => {
|
handleRestart={async (buildParameters) => {
|
||||||
|
const { hasEphemeral, ephemeralParameters } =
|
||||||
|
await checkEphemeralParameters(buildParameters);
|
||||||
|
if (hasEphemeral) {
|
||||||
|
setEphemeralParametersDialog({
|
||||||
|
open: true,
|
||||||
|
action: "restart",
|
||||||
|
buildParameters,
|
||||||
|
ephemeralParameters,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
setConfirmingRestart({ open: true, buildParameters });
|
setConfirmingRestart({ open: true, buildParameters });
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
handleUpdate={workspaceUpdate.update}
|
handleUpdate={workspaceUpdate.update}
|
||||||
handleCancel={cancelBuildMutation.mutate}
|
handleCancel={cancelBuildMutation.mutate}
|
||||||
@@ -242,6 +310,36 @@ export const WorkspaceReadyPage: FC<WorkspaceReadyPageProps> = ({
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<EphemeralParametersDialog
|
||||||
|
open={ephemeralParametersDialog.open}
|
||||||
|
onClose={() =>
|
||||||
|
setEphemeralParametersDialog({
|
||||||
|
...ephemeralParametersDialog,
|
||||||
|
open: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
onContinue={() => {
|
||||||
|
if (ephemeralParametersDialog.action === "start") {
|
||||||
|
startWorkspaceMutation.mutate({
|
||||||
|
buildParameters: ephemeralParametersDialog.buildParameters,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setConfirmingRestart({
|
||||||
|
open: true,
|
||||||
|
buildParameters: ephemeralParametersDialog.buildParameters,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setEphemeralParametersDialog({
|
||||||
|
...ephemeralParametersDialog,
|
||||||
|
open: false,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
ephemeralParameters={ephemeralParametersDialog.ephemeralParameters}
|
||||||
|
workspaceOwner={workspace.owner_name}
|
||||||
|
workspaceName={workspace.name}
|
||||||
|
templateVersionId={workspace.latest_build.template_version_id}
|
||||||
|
/>
|
||||||
|
|
||||||
<WorkspaceUpdateDialogs {...workspaceUpdate.dialogs} />
|
<WorkspaceUpdateDialogs {...workspaceUpdate.dialogs} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
+2
-40
@@ -74,9 +74,6 @@ export const WorkspaceParametersPageViewExperimental: FC<
|
|||||||
validateOnChange: true,
|
validateOnChange: true,
|
||||||
validateOnBlur: true,
|
validateOnBlur: true,
|
||||||
});
|
});
|
||||||
// Group parameters by ephemeral status
|
|
||||||
const ephemeralParameters = parameters.filter((p) => p.ephemeral);
|
|
||||||
const standardParameters = parameters.filter((p) => !p.ephemeral);
|
|
||||||
|
|
||||||
const disabled =
|
const disabled =
|
||||||
workspace.outdated &&
|
workspace.outdated &&
|
||||||
@@ -204,7 +201,7 @@ export const WorkspaceParametersPageViewExperimental: FC<
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<form onSubmit={form.handleSubmit} className="flex flex-col gap-8">
|
<form onSubmit={form.handleSubmit} className="flex flex-col gap-8">
|
||||||
{standardParameters.length > 0 && (
|
{parameters.length > 0 && (
|
||||||
<section className="flex flex-col gap-9">
|
<section className="flex flex-col gap-9">
|
||||||
<hgroup>
|
<hgroup>
|
||||||
<h2 className="text-xl font-medium mb-0">Parameters</h2>
|
<h2 className="text-xl font-medium mb-0">Parameters</h2>
|
||||||
@@ -220,7 +217,7 @@ export const WorkspaceParametersPageViewExperimental: FC<
|
|||||||
</Link>
|
</Link>
|
||||||
</p>
|
</p>
|
||||||
</hgroup>
|
</hgroup>
|
||||||
{standardParameters.map((parameter, index) => {
|
{parameters.map((parameter, index) => {
|
||||||
const currentParameterValueIndex =
|
const currentParameterValueIndex =
|
||||||
form.values.rich_parameter_values?.findIndex(
|
form.values.rich_parameter_values?.findIndex(
|
||||||
(p) => p.name === parameter.name,
|
(p) => p.name === parameter.name,
|
||||||
@@ -260,41 +257,6 @@ export const WorkspaceParametersPageViewExperimental: FC<
|
|||||||
</section>
|
</section>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{ephemeralParameters.length > 0 && (
|
|
||||||
<section className="flex flex-col gap-6">
|
|
||||||
<hgroup>
|
|
||||||
<h2 className="text-xl font-medium mb-1">Ephemeral Parameters</h2>
|
|
||||||
<p className="text-sm text-content-secondary m-0">
|
|
||||||
These parameters only apply for a single workspace start
|
|
||||||
</p>
|
|
||||||
</hgroup>
|
|
||||||
|
|
||||||
<div className="flex flex-col gap-9">
|
|
||||||
{ephemeralParameters.map((parameter, index) => {
|
|
||||||
const actualIndex = standardParameters.length + index;
|
|
||||||
const parameterField = `rich_parameter_values.${actualIndex}`;
|
|
||||||
const isDisabled =
|
|
||||||
disabled || parameter.styling?.disabled || isSubmitting;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<DynamicParameter
|
|
||||||
key={parameter.name}
|
|
||||||
parameter={parameter}
|
|
||||||
onChange={(value) =>
|
|
||||||
handleChange(parameter, parameterField, value)
|
|
||||||
}
|
|
||||||
autofill={false}
|
|
||||||
disabled={isDisabled}
|
|
||||||
value={
|
|
||||||
form.values?.rich_parameter_values?.[index]?.value || ""
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="flex justify-end gap-2">
|
<div className="flex justify-end gap-2">
|
||||||
<Button onClick={onCancel} variant="outline">
|
<Button onClick={onCancel} variant="outline">
|
||||||
Cancel
|
Cancel
|
||||||
|
|||||||
@@ -3017,7 +3017,7 @@ export const MockPreviewParameter: TypesGen.PreviewParameter = {
|
|||||||
value: { valid: true, value: "" },
|
value: { valid: true, value: "" },
|
||||||
diagnostics: [],
|
diagnostics: [],
|
||||||
options: [],
|
options: [],
|
||||||
ephemeral: true,
|
ephemeral: false,
|
||||||
required: true,
|
required: true,
|
||||||
icon: "",
|
icon: "",
|
||||||
styling: {},
|
styling: {},
|
||||||
|
|||||||
Reference in New Issue
Block a user