From 7a335905420c7a4c3c60ad20c16f2e4ff6b0be66 Mon Sep 17 00:00:00 2001 From: McKayla Washburn Date: Mon, 11 Aug 2025 16:07:44 +0000 Subject: [PATCH 1/2] idk, I wonder --- site/src/api/api.ts | 97 +++++++------------ .../WorkspacePage/WorkspacePage.test.tsx | 4 +- 2 files changed, 38 insertions(+), 63 deletions(-) diff --git a/site/src/api/api.ts b/site/src/api/api.ts index 2b21ddf1e8a08..39094fc58b3fd 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -2240,63 +2240,11 @@ class ApiMethods { const currentBuildParameters = await this.getWorkspaceBuildParameters( workspace.latest_build.id, ); - - let templateParameters: TypesGen.TemplateVersionParameter[] = []; - if (isDynamicParametersEnabled) { - templateParameters = await this.getDynamicParameters( - templateVersionId, - workspace.owner_id, - currentBuildParameters, - ); - } else { - templateParameters = - await this.getTemplateVersionRichParameters(templateVersionId); - } - - const missingParameters = getMissingParameters( - currentBuildParameters, - newBuildParameters, - templateParameters, - ); - - if (missingParameters.length > 0) { - throw new MissingBuildParameters(missingParameters, templateVersionId); - } - - return this.postWorkspaceBuild(workspace.id, { - transition: "start", - template_version_id: templateVersionId, - rich_parameter_values: newBuildParameters, - }); - }; - - /** Steps to update the workspace - * - Get the latest template to access the latest active version - * - Get the current build parameters - * - Get the template parameters - * - Update the build parameters and check if there are missed parameters for - * the newest version - * - If there are missing parameters raise an error - * - Stop the workspace with the current template version if it is already running - * - Create a build with the latest version and updated build parameters - */ - updateWorkspace = async ( - workspace: TypesGen.Workspace, - newBuildParameters: TypesGen.WorkspaceBuildParameter[] = [], - isDynamicParametersEnabled = false, - ): Promise => { - const [template, oldBuildParameters] = await Promise.all([ - this.getTemplate(workspace.template_id), - this.getWorkspaceBuildParameters(workspace.latest_build.id), - ]); - - const activeVersionId = template.active_version_id; - if (isDynamicParametersEnabled) { try { return await this.postWorkspaceBuild(workspace.id, { transition: "start", - template_version_id: activeVersionId, + template_version_id: templateVersionId, rich_parameter_values: newBuildParameters, }); } catch (error) { @@ -2309,7 +2257,7 @@ class ApiMethods { error.response.data.validations.length > 0 ) { throw new ParameterValidationError( - activeVersionId, + templateVersionId, error.response.data.validations, ); } @@ -2318,38 +2266,63 @@ class ApiMethods { } const templateParameters = - await this.getTemplateVersionRichParameters(activeVersionId); + await this.getTemplateVersionRichParameters(templateVersionId); const missingParameters = getMissingParameters( - oldBuildParameters, + currentBuildParameters, newBuildParameters, templateParameters, ); if (missingParameters.length > 0) { - throw new MissingBuildParameters(missingParameters, activeVersionId); + throw new MissingBuildParameters(missingParameters, templateVersionId); } // Stop the workspace if it is already running. if (workspace.latest_build.status === "running") { const stopBuild = await this.stopWorkspace(workspace.id); const awaitedStopBuild = await this.waitForBuild(stopBuild); - // If the stop is canceled halfway through, we bail. - // This is the same behaviour as restartWorkspace. + // If the stop is canceled halfway through, we bail. This is the same + // behaviour as restartWorkspace. if (awaitedStopBuild?.status === "canceled") { - return Promise.reject( - new Error("Workspace stop was canceled, not proceeding with update."), + throw new Error( + "Workspace stop was canceled, not proceeding with update.", ); } } return this.postWorkspaceBuild(workspace.id, { transition: "start", - template_version_id: activeVersionId, + template_version_id: templateVersionId, rich_parameter_values: newBuildParameters, }); }; + /** Steps to update the workspace + * - Get the latest template to access the latest active version + * - Get the current build parameters + * - Get the template parameters + * - Update the build parameters and check if there are missed parameters for + * the newest version + * - If there are missing parameters raise an error + * - Stop the workspace with the current template version if it is already running + * - Create a build with the latest version and updated build parameters + */ + updateWorkspace = async ( + workspace: TypesGen.Workspace, + newBuildParameters: TypesGen.WorkspaceBuildParameter[] = [], + isDynamicParametersEnabled = false, + ): Promise => { + const template = await this.getTemplate(workspace.template_id); + + return this.changeWorkspaceVersion( + workspace, + template.active_version_id, + newBuildParameters, + isDynamicParametersEnabled, + ); + }; + getWorkspaceResolveAutostart = async ( workspaceId: string, ): Promise => { diff --git a/site/src/pages/WorkspacePage/WorkspacePage.test.tsx b/site/src/pages/WorkspacePage/WorkspacePage.test.tsx index 288e80127af4a..37aa8abebaf80 100644 --- a/site/src/pages/WorkspacePage/WorkspacePage.test.tsx +++ b/site/src/pages/WorkspacePage/WorkspacePage.test.tsx @@ -337,7 +337,9 @@ describe("WorkspacePage", () => { // After trying to update, a new dialog asking for missed parameters should // be displayed and filled - const dialog = await waitFor(() => screen.findByTestId("dialog")); + const dialog = await waitFor(() => screen.findByTestId("dialog"), { + timeout: 2000, + }); const firstParameterInput = within(dialog).getByLabelText( MockTemplateVersionParameter1.name, { exact: false }, From df06d534edd093c4ed6e4d21ad18cb7ecbe5b5c6 Mon Sep 17 00:00:00 2001 From: McKayla Washburn Date: Mon, 11 Aug 2025 16:38:54 +0000 Subject: [PATCH 2/2] tryin --- site/src/api/api.ts | 12 +--- site/src/api/errors.ts | 9 +++ .../WorkspaceMoreActions.tsx | 4 +- .../workspaces/WorkspaceUpdateDialogs.tsx | 3 +- .../WorkspacePage/WorkspacePage.test.tsx | 69 +++++-------------- 5 files changed, 31 insertions(+), 66 deletions(-) diff --git a/site/src/api/api.ts b/site/src/api/api.ts index 39094fc58b3fd..dc6e8894a68cc 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -24,7 +24,7 @@ import type dayjs from "dayjs"; import userAgentParser from "ua-parser-js"; import { OneWayWebSocket } from "../utils/OneWayWebSocket"; import { delay } from "../utils/delay"; -import { type FieldError, isApiError } from "./errors"; +import { ParameterValidationError, isApiError } from "./errors"; import type { DynamicParametersRequest, PostWorkspaceUsageRequest, @@ -398,15 +398,6 @@ export class MissingBuildParameters extends Error { } } -export class ParameterValidationError extends Error { - constructor( - public readonly versionId: string, - public readonly validations: FieldError[], - ) { - super("Parameters are not valid for new template version"); - } -} - export type GetProvisionerJobsParams = { status?: string; limit?: number; @@ -2256,6 +2247,7 @@ class ApiMethods { error.response.data.validations && error.response.data.validations.length > 0 ) { + console.log(error.response.data.validations); throw new ParameterValidationError( templateVersionId, error.response.data.validations, diff --git a/site/src/api/errors.ts b/site/src/api/errors.ts index 9705a08ff057c..a3f458c405242 100644 --- a/site/src/api/errors.ts +++ b/site/src/api/errors.ts @@ -149,3 +149,12 @@ export class DetailedError extends Error { super(message); } } + +export class ParameterValidationError extends Error { + constructor( + public readonly versionId: string, + public readonly validations: FieldError[], + ) { + super("Parameters are not valid for new template version"); + } +} diff --git a/site/src/modules/workspaces/WorkspaceMoreActions/WorkspaceMoreActions.tsx b/site/src/modules/workspaces/WorkspaceMoreActions/WorkspaceMoreActions.tsx index 8e7b9e33b93d2..e7653b53111b3 100644 --- a/site/src/modules/workspaces/WorkspaceMoreActions/WorkspaceMoreActions.tsx +++ b/site/src/modules/workspaces/WorkspaceMoreActions/WorkspaceMoreActions.tsx @@ -1,5 +1,5 @@ -import { MissingBuildParameters, ParameterValidationError } from "api/api"; -import { isApiError } from "api/errors"; +import { MissingBuildParameters } from "api/api"; +import { ParameterValidationError, isApiError } from "api/errors"; import { type ApiError, getErrorMessage } from "api/errors"; import { changeVersion, diff --git a/site/src/modules/workspaces/WorkspaceUpdateDialogs.tsx b/site/src/modules/workspaces/WorkspaceUpdateDialogs.tsx index 2fad94de2da73..74e7a4dcf3b55 100644 --- a/site/src/modules/workspaces/WorkspaceUpdateDialogs.tsx +++ b/site/src/modules/workspaces/WorkspaceUpdateDialogs.tsx @@ -1,4 +1,5 @@ -import { MissingBuildParameters, ParameterValidationError } from "api/api"; +import { MissingBuildParameters } from "api/api"; +import { ParameterValidationError } from "api/errors"; import { updateWorkspace } from "api/queries/workspaces"; import type { TemplateVersion, diff --git a/site/src/pages/WorkspacePage/WorkspacePage.test.tsx b/site/src/pages/WorkspacePage/WorkspacePage.test.tsx index 37aa8abebaf80..cc882a5d5f6f1 100644 --- a/site/src/pages/WorkspacePage/WorkspacePage.test.tsx +++ b/site/src/pages/WorkspacePage/WorkspacePage.test.tsx @@ -1,6 +1,7 @@ import { screen, waitFor, within } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import * as apiModule from "api/api"; +import { ParameterValidationError } from "api/errors"; import type { TemplateVersionParameter, Workspace } from "api/typesGenerated"; import MockServerSocket from "jest-websocket-mock"; import { @@ -282,11 +283,9 @@ describe("WorkspacePage", () => { }); it("requests an update when the user presses Update", async () => { - // Mocks jest .spyOn(API, "getWorkspaceByOwnerAndName") .mockResolvedValueOnce(MockOutdatedWorkspace); - const updateWorkspaceMock = jest .spyOn(API, "updateWorkspace") .mockResolvedValueOnce(MockWorkspaceBuild); @@ -306,73 +305,37 @@ describe("WorkspacePage", () => { }); }); - it("updates the parameters when they are missing during update", async () => { - // Mocks + it("requires invalid parameters to be updated", async () => { jest .spyOn(API, "getWorkspaceByOwnerAndName") .mockResolvedValueOnce(MockOutdatedWorkspace); - const updateWorkspaceSpy = jest + const updateWorkspaceMock = jest .spyOn(API, "updateWorkspace") .mockRejectedValueOnce( - new MissingBuildParameters( - [MockTemplateVersionParameter1, MockTemplateVersionParameter2], + new ParameterValidationError( MockOutdatedWorkspace.template_active_version_id, + [ + { + field: MockTemplateVersionParameter1.name, + detail: + "Required parameter not provided; parameter value is null", + }, + ], ), ); - // Render await renderWorkspacePage(MockWorkspace); - // Actions + // Start workspace update const user = userEvent.setup(); await user.click(screen.getByTestId("workspace-update-button")); const confirmButton = await screen.findByTestId("confirm-button"); await user.click(confirmButton); - // The update was called - await waitFor(() => { - expect(API.updateWorkspace).toBeCalled(); - updateWorkspaceSpy.mockClear(); - }); - - // After trying to update, a new dialog asking for missed parameters should - // be displayed and filled - const dialog = await waitFor(() => screen.findByTestId("dialog"), { - timeout: 2000, - }); - const firstParameterInput = within(dialog).getByLabelText( - MockTemplateVersionParameter1.name, - { exact: false }, - ); - await user.clear(firstParameterInput); - await user.type(firstParameterInput, "some-value"); - const secondParameterInput = within(dialog).getByLabelText( - MockTemplateVersionParameter2.name, - { exact: false }, - ); - await user.clear(secondParameterInput); - await user.type(secondParameterInput, "2"); - await user.click( - within(dialog).getByRole("button", { name: /update parameters/i }), - ); - - // Check if the update was called using the values from the form - await waitFor(() => { - expect(API.updateWorkspace).toHaveBeenCalledWith( - MockOutdatedWorkspace, - [ - { - name: MockTemplateVersionParameter1.name, - value: "some-value", - }, - { - name: MockTemplateVersionParameter2.name, - value: "2", - }, - ], - false, - ); - }); + // Dialog should warn the parameters need to be updated + const dialog = await screen.findByTestId("dialog"); + await within(dialog).findByText("Update workspace parameters"); + await screen.findByText(/go to the workspace parameters page to review/); }); it("restart the workspace with one time parameters when having the confirmation dialog", async () => {