From 4fe0a4bca205da1de7e36f6800009b79ba33e817 Mon Sep 17 00:00:00 2001 From: "blink-so[bot]" <211532188+blink-so[bot]@users.noreply.github.com> Date: Fri, 20 Jun 2025 15:04:36 -0400 Subject: [PATCH] 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`). Screenshot 2025-06-18 at 14 35 54 Screenshot 2025-06-18 at 14 35 43 ![Screenshot 2025-06-18 at 14 41 41](https://github.com/user-attachments/assets/52f1ab99-f3bf-4540-91ac-e385c632de8c) ## 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 --- site/src/components/Badge/Badge.tsx | 2 + .../EphemeralParametersDialog.tsx | 86 +++++++++++ .../DynamicParameter.stories.tsx | 9 ++ .../DynamicParameter/DynamicParameter.tsx | 18 +++ .../BuildParametersPopover.tsx | 55 ++++++- .../WorkspacePage/WorkspaceReadyPage.tsx | 142 +++++++++++++++--- ...orkspaceParametersPageViewExperimental.tsx | 42 +----- site/src/testHelpers/entities.ts | 2 +- 8 files changed, 290 insertions(+), 66 deletions(-) create mode 100644 site/src/components/EphemeralParametersDialog/EphemeralParametersDialog.tsx diff --git a/site/src/components/Badge/Badge.tsx b/site/src/components/Badge/Badge.tsx index 3b2a5d5897..0d11c96d30 100644 --- a/site/src/components/Badge/Badge.tsx +++ b/site/src/components/Badge/Badge.tsx @@ -22,6 +22,8 @@ const badgeVariants = cva( "border border-solid border-border-warning bg-surface-orange text-content-warning shadow", destructive: "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: { xs: "text-2xs font-regular h-5 [&_svg]:hidden rounded px-1.5", diff --git a/site/src/components/EphemeralParametersDialog/EphemeralParametersDialog.tsx b/site/src/components/EphemeralParametersDialog/EphemeralParametersDialog.tsx new file mode 100644 index 0000000000..d1713d920f --- /dev/null +++ b/site/src/components/EphemeralParametersDialog/EphemeralParametersDialog.tsx @@ -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 = ({ + open, + onClose, + onContinue, + ephemeralParameters, + workspaceOwner, + workspaceName, + templateVersionId, +}) => { + const navigate = useNavigate(); + + const handleGoToParameters = () => { + onClose(); + navigate( + `/@${workspaceOwner}/${workspaceName}/settings/parameters?templateVersionId=${templateVersionId}`, + ); + }; + + return ( + !isOpen && onClose()}> + + + Ephemeral Parameters Detected + + This workspace template has{" "} + + {ephemeralParameters.length} + {" "} + ephemeral parameters that will be reset to their default values + + +
    + {ephemeralParameters.map((param) => ( +
  • +

    + {param.display_name || param.name} +

    + {param.description && ( +

    + {param.description} +

    + )} +
  • + ))} +
+
+ + Would you like to go to the workspace parameters page to review and + update these parameters before continuing? + +
+ + + + +
+
+ ); +}; diff --git a/site/src/modules/workspaces/DynamicParameter/DynamicParameter.stories.tsx b/site/src/modules/workspaces/DynamicParameter/DynamicParameter.stories.tsx index 4d1e91d9bf..db3fa2f404 100644 --- a/site/src/modules/workspaces/DynamicParameter/DynamicParameter.stories.tsx +++ b/site/src/modules/workspaces/DynamicParameter/DynamicParameter.stories.tsx @@ -211,6 +211,15 @@ export const Immutable: Story = { }, }; +export const Ephemeral: Story = { + args: { + parameter: { + ...MockPreviewParameter, + ephemeral: true, + }, + }, +}; + export const AllBadges: Story = { args: { parameter: { diff --git a/site/src/modules/workspaces/DynamicParameter/DynamicParameter.tsx b/site/src/modules/workspaces/DynamicParameter/DynamicParameter.tsx index c3448ac7d7..9f97d558c8 100644 --- a/site/src/modules/workspaces/DynamicParameter/DynamicParameter.tsx +++ b/site/src/modules/workspaces/DynamicParameter/DynamicParameter.tsx @@ -36,6 +36,7 @@ import { useDebouncedValue } from "hooks/debounce"; import { useEffectEvent } from "hooks/hookPolyfills"; import { CircleAlert, + Hourglass, Info, LinkIcon, Settings, @@ -162,6 +163,23 @@ const ParameterLabel: FC = ({ )} + {parameter.ephemeral && ( + + + + + + + Ephemeral + + + + + This parameter only applies for a single workspace start + + + + )} {isPreset && ( diff --git a/site/src/pages/WorkspacePage/WorkspaceActions/BuildParametersPopover.tsx b/site/src/pages/WorkspacePage/WorkspaceActions/BuildParametersPopover.tsx index d594351d8d..76b100fc96 100644 --- a/site/src/pages/WorkspacePage/WorkspaceActions/BuildParametersPopover.tsx +++ b/site/src/pages/WorkspacePage/WorkspaceActions/BuildParametersPopover.tsx @@ -1,5 +1,4 @@ import { useTheme } from "@emotion/react"; -import Button from "@mui/material/Button"; import visuallyHidden from "@mui/utils/visuallyHidden"; import { API } from "api/api"; import type { @@ -7,6 +6,7 @@ import type { Workspace, WorkspaceBuildParameter, } from "api/typesGenerated"; +import { Button } from "components/Button/Button"; import { FormFields } from "components/Form/Form"; import { TopbarButton } from "components/FullPageLayout/Topbar"; import { @@ -27,6 +27,7 @@ import { useFormik } from "formik"; import { ChevronDownIcon } from "lucide-react"; import type { FC } from "react"; import { useQuery } from "react-query"; +import { useNavigate } from "react-router-dom"; import { docs } from "utils/docs"; import { getFormHelpers } from "utils/formUtils"; import { @@ -72,6 +73,7 @@ export const BuildParametersPopover: FC = ({ css={{ ".MuiPaper-root": { width: 304 } }} > = ({ }; interface BuildParametersPopoverContentProps { + workspace: Workspace; ephemeralParameters?: TemplateVersionParameter[]; buildParameters?: WorkspaceBuildParameter[]; onSubmit: (buildParameters: WorkspaceBuildParameter[]) => void; } const BuildParametersPopoverContent: FC = ({ + workspace, ephemeralParameters, buildParameters, onSubmit, }) => { const theme = useTheme(); 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 ( +
+

+ Ephemeral Parameters +

+

+ This template has ephemeral parameters that must be configured on the + workspace parameters page +

+ +
+
    + {ephemeralParameters.map((param) => ( +
  • +

    + {param.display_name || param.name} +

    + {param.description && ( +

    + {param.description} +

    + )} +
  • + ))} +
+
+ + +
+ ); + } return ( <> @@ -206,8 +257,6 @@ const Form: FC = ({