8000 feat: handle update build for dynamic params (#18226) · coder/coder@b7a0476 · GitHub
[go: up one dir, main page]

Skip to content

Commit b7a0476

Browse files
jaaydenhblink-so[bot]
authored andcommitted
feat: handle update build for dynamic params (#18226)
resolves coder/preview#110 --------- Co-authored-by: blink-so[bot] <211532188+blink-so[bot]@users.noreply.github.com>
1 parent 1ef4796 commit b7a0476

File tree

15 files changed

+431
-132
lines changed
  • WorkspacesPage
  • 15 files changed

    +431
    -132
    lines changed

    site/src/api/api.ts

    Lines changed: 82 additions & 10 deletions
    Original file line numberDiff line numberDiff line change
    @@ -24,7 +24,10 @@ import type dayjs from "dayjs";
    2424
    import userAgentParser from "ua-parser-js";
    2525
    import { OneWayWebSocket } from "../utils/OneWayWebSocket";
    2626
    import { delay } from "../utils/delay";
    27-
    import type { PostWorkspaceUsageRequest } from "./typesGenerated";
    27+
    import type {
    28+
    DynamicParametersRequest,
    29+
    PostWorkspaceUsageRequest,
    30+
    } from "./typesGenerated";
    2831
    import * as TypesGen from "./typesGenerated";
    2932

    3033
    const getMissingParameters = (
    @@ -73,8 +76,10 @@ const getMissingParameters = (
    7376
    if (templateParameter.options.length === 0) {
    7477
    continue;
    7578
    }
    76-
    77-
    // Check if there is a new value
    79+
    // For multi-select, extra steps are necessary to JSON parse the value.
    80+
    if (templateParameter.form_type === "multi-select") {
    81+
    continue;
    82+
    }
    7883
    let buildParameter = newBuildParameters.find(
    7984
    (p) => p.name === templateParameter.name,
    8085
    );
    @@ -231,7 +236,7 @@ export const watchWorkspaceAgentLogs = (
    231236
    /**
    232237
    * WebSocket compression in Safari (confirmed in 16.5) is broken when
    233238
    * the server sends large messages. The following error is seen:
    234-
    * WebSocket connection to 'wss://...' failed: The operation couldnt be completed.
    239+
    * WebSocket connection to 'wss://...' failed: The operation couldn't be completed.
    235240
    */
    236241
    if (userAgentParser(navigator.userAgent).browser.name === "Safari") {
    237242
    searchParams.set("no_compression", "");
    @@ -990,6 +995,17 @@ class ApiMethods {
    990995
    return response.data;
    991996
    };
    992997

    998+
    getTemplateVersionDynamicParameters = async (
    999+
    versionId: string,
    1000+
    data: TypesGen.DynamicParametersRequest,
    1001+
    ): Promise<TypesGen.DynamicParametersResponse> => {
    1002+
    const response = await this.axios.post(
    1003+
    `/api/v2/templateversions/${versionId}/dynamic-parameters/evaluate`,
    1004+
    data,
    1005+
    );
    1006+
    return response.data;
    1007+
    };
    1008+
    9931009
    getTemplateVersionRichParameters = async (
    9941010
    versionId: string,
    9951011
    ): Promise<TypesGen.TemplateVersionParameter[]> => {
    @@ -2132,6 +2148,38 @@ class ApiMethods {
    21322148
    await this.axios.delete(`/api/v2/licenses/${licenseId}`);
    21332149
    };
    21342150

    2151+
    getDynamicParameters = async (
    2152+
    templateVersionId: string,
    2153+
    ownerId: string,
    2154+
    oldBuildParameters: TypesGen.WorkspaceBuildParameter[],
    2155+
    ) => {
    2156+
    const request: DynamicParametersRequest = {
    2157+
    id: 1,
    2158+
    owner_id: ownerId,
    2159+
    inputs: Object.fromEntries(
    2160+
    new Map(oldBuildParameters.map((param) => [param.name, param.value])),
    2161+
    ),
    2162+
    };
    2163+
    2164+
    const dynamicParametersResponse =
    2165+
    await this.getTemplateVersionDynamicParameters(
    2166+
    templateVersionId,
    2167+
    request,
    2168+
    );
    2169+
    2170+
    return dynamicParametersResponse.parameters.map((p) => ({
    2171+
    ...p,
    2172+
    description_plaintext: p.description || "",
    2173+
    default_value: p.default_value?.valid ? p.default_value.value : "",
    2174+
    options: p.options
    2175+
    ? p.options.map((opt) => ({
    2176+
    ...opt,
    2177+
    value: opt.value?.valid ? opt.value.value : "",
    2178+
    }))
    2179+
    : [],
    2180+
    }));
    2181+
    };
    2182+
    21352183
    /** Steps to change the workspace version
    21362184
    * - Get the latest template to access the latest active version
    21372185
    * - Get the current build parameters
    @@ -2145,11 +2193,23 @@ class ApiMethods {
    21452193
    workspace: TypesGen.Workspace,
    21462194
    templateVersionId: string,
    21472195
    newBuildParameters: TypesGen.WorkspaceBuildParameter[] = [],
    2196+
    isDynamicParametersEnabled = false,
    21482197
    ): Promise<TypesGen.WorkspaceBuild> => {
    2149-
    const [currentBuildParameters, templateParameters] = await Promise.all([
    2150-
    this.getWorkspaceBuildParameters(workspace.latest_build.id),
    2151-
    this.getTemplateVersionRichParameters(templateVersionId),
    2152-
    ]);
    2198+
    const currentBuildParameters = await this.getWorkspaceBuildParameters(
    2199+
    workspace.latest_build.id,
    2200+
    );
    2201+
    2202+
    let templateParameters: TypesGen.TemplateVersionParameter[] = [];
    2203+
    if (isDynamicParametersEnabled) {
    2204+
    templateParameters = await this.getDynamicParameters(
    2205+
    templateVersionId,
    2206+
    workspace.owner_id,
    2207+
    currentBuildParameters,
    2208+
    );
    2209+
    } else {
    2210+
    templateParameters =
    2211+
    await this.getTemplateVersionRichParameters(templateVersionId);
    2212+
    }
    21532213

    21542214
    const missingParameters = getMissingParameters(
    21552215
    currentBuildParameters,
    @@ -2180,15 +2240,27 @@ class ApiMethods {
    21802240
    updateWorkspace = async (
    21812241
    workspace: TypesGen.Workspace,
    21822242
    newBuildParameters: TypesGen.WorkspaceBuildParameter[] = [],
    2243+
    isDynamicParametersEnabled = false,
    21832244
    ): Promise<TypesGen.WorkspaceBuild> => {
    21842245
    const [template, oldBuildParameters] = await Promise.all([
    21852246
    this.getTemplate(workspace.template_id),
    21862247
    this.getWorkspaceBuildParameters(workspace.latest_build.id),
    21872248
    ]);
    21882249

    21892250
    const activeVersionId = template.active_version_id;
    2190-
    const templateParameters =
    2191-
    await this.getTemplateVersionRichParameters(activeVersionId);
    2251+
    2252+
    let templateParameters: TypesGen.TemplateVersionParameter[] = [];
    2253+
    2254+
    if (isDynamicParametersEnabled) {
    2255+
    templateParameters = await this.getDynamicParameters(
    2256+
    activeVersionId,
    2257+
    workspace.owner_id,
    2258+
    oldBuildParameters,
    2259+
    );
    2260+
    } else {
    2261+
    templateParameters =
    2262+
    await this.getTemplateVersionRichParameters(activeVersionId);
    2263+
    }
    21922264

    21932265
    const missingParameters = getMissingParameters(
    21942266
    oldBuildParameters,

    site/src/api/queries/workspaces.ts

    Lines changed: 19 additions & 3 deletions
    Original file line numberDiff line numberDiff line change
    @@ -163,6 +163,7 @@ export const updateDeadline = (
    163163
    export const changeVersion = (
    164164
    workspace: Workspace,
    165165
    queryClient: QueryClient,
    166+
    isDynamicParametersEnabled: boolean,
    166167
    ) => {
    167168
    return {
    168169
    mutationFn: ({
    @@ -172,7 +173,12 @@ export const changeVersion = (
    172173
    versionId: string;
    173174
    buildParameters?: WorkspaceBuildParameter[];
    174175
    }) => {
    175-
    return API.changeWorkspaceVersion(workspace, versionId, buildParameters);
    176+
    return API.changeWorkspaceVersion(
    177+
    workspace,
    178+
    versionId,
    179+
    buildParameters,
    180+
    isDynamicParametersEnabled,
    181+
    );
    176182
    },
    177183
    onSuccess: async (build: WorkspaceBuild) => {
    178184
    await updateWorkspaceBuild(build, queryClient);
    @@ -185,8 +191,18 @@ export const updateWorkspace = (
    185191
    queryClient: QueryClient,
    186192
    ) => {
    187193
    return {
    188-
    mutationFn: (buildParameters?: WorkspaceBuildParameter[]) => {
    189-
    return API.updateWorkspace(workspace, buildParameters);
    194+
    mutationFn: ({
    195+
    buildParameters,
    196+
    isDynamicParametersEnabled,
    197+
    }: {
    198+
    buildParameters?: WorkspaceBuildParameter[];
    199+
    isDynamicParametersEnabled: boolean;
    200+
    }) => {
    201+
    return API.updateWorkspace(
    202+
    workspace,
    203+
    buildParameters,
    204+
    isDynamicParametersEnabled,
    205+
    );
    190206
    },
    191207
    onSuccess: async (build: WorkspaceBuild) => {
    192208
    await updateWorkspaceBuild(build, queryClient);

    site/src/components/Dialog/Dialog.tsx

    Lines changed: 3 additions & 3 deletions
    Original file line numberDiff line numberDiff line change
    @@ -45,7 +45,7 @@ export const DialogContent = forwardRef<
    4545
    <DialogPrimitive.Content
    4646
    ref={ref}
    4747
    className={cn(
    48-
    `fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg gap-4
    48+
    `fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg gap-6
    4949
    border border-solid border-border bg-surface-primary p-8 shadow-lg duration-200 sm:rounded-lg
    5050
    translate-x-[-50%] translate-y-[-50%]
    5151
    data-[state=open]:animate-in data-[state=closed]:animate-out
    @@ -68,7 +68,7 @@ export const DialogHeader: FC<HTMLAttributes<HTMLDivElement>> = ({
    6868
    }) => (
    6969
    <div
    7070
    className={cn(
    71-
    "flex flex-col space-y-1.5 text-center sm:text-left",
    71+
    "flex flex-col space-y-5 text-center sm:text-left",
    7272
    className,
    7373
    )}
    7474
    {...props}
    @@ -108,7 +108,7 @@ export const DialogDescription = forwardRef<
    108108
    >(({ className, ...props }, ref) => (
    109109
    <DialogPrimitive.Description
    110110
    ref={ref}
    111-
    className={cn("text-sm text-content-secondary", className)}
    111+
    className={cn("text-sm text-content-secondary font-medium", className)}
    112112
    {...props}
    113113
    />
    114114
    ));
    Lines changed: 42 additions & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -0,0 +1,42 @@
    1+
    import { useQuery } from "react-query";
    2+
    3+
    export const optOutKey = (id: string): string => `parameters.${id}.optOut`;
    4+
    5+
    interface UseDynamicParametersOptOutOptions {
    6+
    templateId: string | undefined;
    7+
    templateUsesClassicParameters: boolean | undefined;
    8+
    enabled: boolean;
    9+
    }
    10+
    11+
    export const useDynamicParametersOptOut = ({
    12+
    templateId,
    13+
    templateUsesClassicParameters,
    14+
    enabled,
    15+
    }: UseDynamicParametersOptOutOptions) => {
    16+
    return useQuery({
    17+
    enabled: !!templateId && enabled,
    18+
    queryKey: ["dynamicParametersOptOut", templateId],
    19+
    queryFn: () => {
    20+
    if (!templateId) {
    21+
    // This should not happen if enabled is working correctly,
    22+
    // but as a type guard and sanity check.
    23+
    throw new Error("templateId is required");
    24+
    }
    25+
    const localStorageKey = optOutKey(templateId);
    26+
    const storedOptOutString = localStorage.getItem(localStorageKey);
    27+
    28+
    let optedOut: boolean;
    29+
    30+
    if (storedOptOutString !== null) {
    31+
    optedOut = storedOptOutString === "true";
    32+
    } else {
    33+
    optedOut = Boolean(templateUsesClassicParameters);
    34+
    }
    35+
    36+
    return {
    37+
    templateId,
    38+
    optedOut,
    39+
    };
    40+
    },
    41+
    });
    42+
    };
    Lines changed: 71 additions & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -0,0 +1,71 @@
    1+
    import type { TemplateVersionParameter } from "api/typesGenerated";
    2+
    import { Button } from "components/Button/Button";
    3+
    import {
    4+
    Dialog,
    5+
    DialogContent,
    6+
    DialogDescription,
    7+
    DialogFooter,
    8+
    DialogHeader,
    9+
    DialogTitle,
    10+
    } from "components/Dialog/Dialog";
    11+
    import type { FC } from "react";
    12+
    import { useNavigate } from "react-router-dom";
    13+
    14+
    type UpdateBuildParametersDialogExperimentalProps = {
    15+
    open: boolean;
    16+
    onClose: () => void;
    17+
    missedParameters: TemplateVersionParameter[];
    18+
    workspaceOwnerName: string;
    19+
    workspaceName: string;
    20+
    templateVersionId: string | undefined;
    21+
    };
    22+
    23+
    export const UpdateBuildParametersDialogExperimental: FC<
    24+
    UpdateBuildParametersDialogExperimentalProps
    25+
    > = ({
    26+
    missedParameters,
    27+
    open,
    28+
    onClose,
    29+
    workspaceOwnerName,
    30+
    workspaceName,
    31+
    templateVersionId,
    32+
    }) => {
    33+
    const navigate = useNavigate();
    34+
    35+
    const handleGoToParameters = () => {
    36+
    onClose();
    37+
    navigate(
    38+
    `/@${workspaceOwnerName}/${workspaceName}/settings/parameters?templateVersionId=${templateVersionId}`,
    39+
    );
    40+
    };
    41+
    42+
    return (
    43+
    <Dialog open={open} onOpenChange={(isOpen) => !isOpen && onClose()}>
    44+
    <DialogContent>
    45+
    <DialogHeader>
    46+
    <DialogTitle>Update workspace parameters</DialogTitle>
    47+
    <DialogDescription>
    48+
    This template has{" "}
    49+
    <strong className="text-content-primary">
    50+
    {missedParameters.length} new parameter
    51+
    {missedParameters.length === 1 ? "" : "s"}
    52+
    </strong>{" "}
    53+
    that must be configured to complete the update.
    54+
    </DialogDescription>
    55+
    <DialogDescription>
    56+
    Would you like to go to the workspace parameters page to review and
    57+
    update these parameters before continuing?
    58+
    </DialogDescription>
    59+
    </DialogHeader>
    60+
    <DialogFooter>
    61+
    <Button onClick={onClose} variant="outline">
    62+
    Cancel
    63+
    </Button>
    64+
    <Button onClick={handleGoToParameters}>
    65+
    Go to workspace parameters
    66+
    </Button>
    67+
    </DialogFooter>
    68+
    </DialogContent>
    69+
    </Dialog>
    70+
    );
    71+
    };

    0 commit comments

    Comments
     (0)
    0