feat: add opt-out option to new parameters form (#17456)

Closes https://github.com/coder/preview/issues/62
This commit is contained in:
ケイラ
2025-04-21 10:44:44 -06:00
committed by GitHub
parent c0ca47d015
commit f6364a2f6e
4 changed files with 112 additions and 14 deletions
@@ -1,18 +1,76 @@
import { templateByName } from "api/queries/templates";
import { ErrorAlert } from "components/Alert/ErrorAlert";
import { Loader } from "components/Loader/Loader";
import { useDashboard } from "modules/dashboard/useDashboard";
import type { FC } from "react";
import { type FC, createContext } from "react";
import { useQuery } from "react-query";
import { useParams } from "react-router-dom";
import CreateWorkspacePage from "./CreateWorkspacePage";
import CreateWorkspacePageExperimental from "./CreateWorkspacePageExperimental";
const CreateWorkspaceExperimentRouter: FC = () => {
const { experiments } = useDashboard();
const dynamicParametersEnabled = experiments.includes("dynamic-parameters");
const { organization: organizationName = "default", template: templateName } =
useParams() as { organization?: string; template: string };
const templateQuery = useQuery(
dynamicParametersEnabled
? templateByName(organizationName, templateName)
: { enabled: false },
);
const optOutQuery = useQuery(
templateQuery.data
? {
queryKey: [
organizationName,
"template",
templateQuery.data.id,
"optOut",
],
queryFn: () => ({
templateId: templateQuery.data.id,
optedOut:
localStorage.getItem(optOutKey(templateQuery.data.id)) === "true",
}),
}
: { enabled: false },
);
if (dynamicParametersEnabled) {
return <CreateWorkspacePageExperimental />;
if (optOutQuery.isLoading) {
return <Loader />;
}
if (!optOutQuery.data) {
return <ErrorAlert error={optOutQuery.error} />;
}
const toggleOptedOut = () => {
const key = optOutKey(optOutQuery.data.templateId);
const current = localStorage.getItem(key) === "true";
localStorage.setItem(key, (!current).toString());
optOutQuery.refetch();
};
return (
<ExperimentalFormContext.Provider value={{ toggleOptedOut }}>
{optOutQuery.data.optedOut ? (
<CreateWorkspacePage />
) : (
<CreateWorkspacePageExperimental />
)}
</ExperimentalFormContext.Provider>
);
}
return <CreateWorkspacePage />;
};
export default CreateWorkspaceExperimentRouter;
const optOutKey = (id: string) => `parameters.${id}.optOut`;
export const ExperimentalFormContext = createContext<
{ toggleOptedOut: () => void } | undefined
>(undefined);
@@ -69,10 +69,11 @@ const CreateWorkspacePageExperimental: FC = () => {
const templateQuery = useQuery(
templateByName(organizationName, templateName),
);
const templateVersionPresetsQuery = useQuery({
...templateVersionPresets(templateQuery.data?.active_version_id ?? ""),
enabled: templateQuery.data !== undefined,
});
const templateVersionPresetsQuery = useQuery(
templateQuery.data
? templateVersionPresets(templateQuery.data.active_version_id)
: { enabled: false },
);
const permissionsQuery = useQuery(
templateQuery.data
? checkAuthorization({
@@ -1,5 +1,4 @@
import type { Interpolation, Theme } from "@emotion/react";
import FormControlLabel from "@mui/material/FormControlLabel";
import FormHelperText from "@mui/material/FormHelperText";
import TextField from "@mui/material/TextField";
import type * as TypesGen from "api/typesGenerated";
@@ -29,7 +28,14 @@ import { Switch } from "components/Switch/Switch";
import { UserAutocomplete } from "components/UserAutocomplete/UserAutocomplete";
import { type FormikContextType, useFormik } from "formik";
import { generateWorkspaceName } from "modules/workspaces/generateWorkspaceName";
import { type FC, useCallback, useEffect, useMemo, useState } from "react";
import {
type FC,
useCallback,
useContext,
useEffect,
useMemo,
useState,
} from "react";
import {
getFormHelpers,
nameValidator,
@@ -41,12 +47,14 @@ import {
useValidationSchemaForRichParameters,
} from "utils/richParameters";
import * as Yup from "yup";
import { ExperimentalFormContext } from "./CreateWorkspaceExperimentRouter";
import type {
CreateWorkspaceMode,
ExternalAuthPollingState,
} from "./CreateWorkspacePage";
import { ExternalAuthButton } from "./ExternalAuthButton";
import type { CreateWorkspacePermissions } from "./permissions";
export const Language = {
duplicationWarning:
"Duplicating a workspace only copies its parameters. No state from the old workspace is copied over.",
@@ -98,6 +106,7 @@ export const CreateWorkspacePageView: FC<CreateWorkspacePageViewProps> = ({
onSubmit,
onCancel,
}) => {
const experimentalFormContext = useContext(ExperimentalFormContext);
const [owner, setOwner] = useState(defaultOwner);
const [suggestedName, setSuggestedName] = useState(() =>
generateWorkspaceName(),
@@ -211,9 +220,20 @@ export const CreateWorkspacePageView: FC<CreateWorkspacePageViewProps> = ({
<Margins size="medium">
<PageHeader
actions={
<Button size="sm" variant="outline" onClick={onCancel}>
Cancel
</Button>
<>
{experimentalFormContext && (
<Button
size="sm"
variant="outline"
onClick={experimentalFormContext.toggleOptedOut}
>
Try out the new workspace creation flow
</Button>
)}
<Button size="sm" variant="outline" onClick={onCancel}>
Cancel
</Button>
</>
}
>
<Stack direction="row">
@@ -22,10 +22,18 @@ import {
useValidationSchemaForDynamicParameters,
} from "modules/workspaces/DynamicParameter/DynamicParameter";
import { generateWorkspaceName } from "modules/workspaces/generateWorkspaceName";
import { type FC, useCallback, useEffect, useId, useState } from "react";
import {
type FC,
useCallback,
useContext,
useEffect,
useId,
useState,
} from "react";
import { getFormHelpers, nameValidator } from "utils/formUtils";
import type { AutofillBuildParameter } from "utils/richParameters";
import * as Yup from "yup";
import { ExperimentalFormContext } from "./CreateWorkspaceExperimentRouter";
import type {
CreateWorkspaceMode,
ExternalAuthPollingState,
@@ -89,6 +97,7 @@ export const CreateWorkspacePageViewExperimental: FC<
owner,
setOwner,
}) => {
const experimentalFormContext = useContext(ExperimentalFormContext);
const [suggestedName, setSuggestedName] = useState(() =>
generateWorkspaceName(),
);
@@ -251,7 +260,7 @@ export const CreateWorkspacePageViewExperimental: FC<
</button>
</div>
<div className="flex flex-col gap-6 max-w-screen-sm mx-auto">
<header className="flex flex-col gap-2 mt-10">
<header className="flex flex-col items-start gap-2 mt-10">
<div className="flex items-center gap-2">
<Avatar
variant="icon"
@@ -268,6 +277,16 @@ export const CreateWorkspacePageViewExperimental: FC<
<h1 className="text-3xl font-semibold m-0">New workspace</h1>
{template.deprecated && <Pill type="warning">Deprecated</Pill>}
{experimentalFormContext && (
<Button
size="sm"
variant="subtle"
onClick={experimentalFormContext.toggleOptedOut}
>
Go back to the classic workspace creation flow
</Button>
)}
</header>
<form