From 47bbc4d3c52d89220578f1f2afea11575ac26de2 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Thu, 24 Aug 2023 14:02:55 +0200 Subject: [PATCH 01/17] Restart ephemeral parameters --- site/e2e/helpers.ts | 73 ++++++++++++++++++++++++- site/e2e/parameters.ts | 36 +++++++++--- site/e2e/tests/restartWorkspace.spec.ts | 39 +++++++++++++ 3 files changed, 136 insertions(+), 12 deletions(-) create mode 100644 site/e2e/tests/restartWorkspace.spec.ts diff --git a/site/e2e/helpers.ts b/site/e2e/helpers.ts index 6525fa3b01f9f..a2c3de6f511fe 100644 --- a/site/e2e/helpers.ts +++ b/site/e2e/helpers.ts @@ -77,9 +77,12 @@ export const createWorkspace = async ( await page.getByTestId("form-submit").click() await expect(page).toHaveURL("/@admin/" + name) - await page.waitForSelector("[data-testid='build-status']", { - state: "visible", - }) + await page.waitForSelector( + "span[data-testid='build-status'] >> text=Running", + { + state: "visible", + }, + ) return name } @@ -213,6 +216,70 @@ export const sshIntoWorkspace = async ( }) } +export const restartWorkspace = async ( + page: Page, + workspaceName: string, + richParameters: RichParameter[] = [], + buildParameters: WorkspaceBuildParameter[] = [], +) => { + await page.goto("/@admin/" + workspaceName, { + waitUntil: "domcontentloaded", + }) + await expect(page).toHaveURL("/@admin/" + workspaceName) + + await page.getByTestId("build-parameters-button").click() + + for (const buildParameter of buildParameters) { + const richParameter = richParameters.find( + (richParam) => richParam.name === buildParameter.name, + ) + if (!richParameter) { + throw new Error( + "build parameter is expected to be present in rich parameter schema", + ) + } + + const parameterLabel = await page.waitForSelector( + "[data-testid='parameter-field-" + richParameter.name + "']", + { state: "visible" }, + ) + + if (richParameter.type === "bool") { + const parameterField = await parameterLabel.waitForSelector( + "[data-testid='parameter-field-bool'] .MuiRadio-root input[value='" + + buildParameter.value + + "']", + ) + await parameterField.check() + } else if (richParameter.options.length > 0) { + const parameterField = await parameterLabel.waitForSelector( + "[data-testid='parameter-field-options'] .MuiRadio-root input[value='" + + buildParameter.value + + "']", + ) + await parameterField.check() + } else if (richParameter.type === "list(string)") { + throw new Error("not implemented yet") // FIXME + } else { + // text or number + const parameterField = await parameterLabel.waitForSelector( + "[data-testid='parameter-field-text'] input", + ) + await parameterField.fill(buildParameter.value) + } + } + + await page.getByTestId("build-parameters-submit").click() + await page.getByTestId("confirm-button").click() + + await page.waitForSelector( + "span[data-testid='build-status'] >> text=Running", + { + state: "visible", + }, + ) +} + // startAgent runs the coder agent with the provided token. // It awaits the agent to be ready before returning. export const startAgent = async (page: Page, token: string): Promise => { diff --git a/site/e2e/parameters.ts b/site/e2e/parameters.ts index c575fdcf83162..240eeb0f8566c 100644 --- a/site/e2e/parameters.ts +++ b/site/e2e/parameters.ts @@ -28,7 +28,6 @@ export const firstParameter: RichParameter = { name: "first_parameter", displayName: "First parameter", type: "number", - options: [], description: "This is first parameter.", icon: "/emojis/1f310.png", defaultValue: "123", @@ -43,10 +42,8 @@ export const secondParameter: RichParameter = { name: "second_parameter", displayName: "Second parameter", type: "string", - options: [], description: "This is second parameter.", defaultValue: "abc", - icon: "", order: 2, } @@ -56,7 +53,6 @@ export const thirdParameter: RichParameter = { name: "third_parameter", type: "string", - options: [], description: "This is third parameter.", defaultValue: "", mutable: true, @@ -69,10 +65,8 @@ export const fourthParameter: RichParameter = { name: "fourth_parameter", type: "bool", - options: [], description: "This is fourth parameter.", defaultValue: "true", - icon: "", order: 3, } @@ -105,7 +99,6 @@ export const fifthParameter: RichParameter = { ], description: "This is fifth parameter.", defaultValue: "def", - icon: "", order: 3, } @@ -116,7 +109,6 @@ export const sixthParameter: RichParameter = { name: "sixth_parameter", displayName: "Sixth parameter", type: "number", - options: [], description: "This is sixth parameter.", icon: "/emojis/1f310.png", required: true, @@ -131,8 +123,34 @@ export const seventhParameter: RichParameter = { name: "seventh_parameter", displayName: "Seventh parameter", type: "string", - options: [], description: "This is seventh parameter.", required: true, order: 1, } + +// Build options + +export const firstBuildOption: RichParameter = { + ...emptyParameter, + + name: "first_build_option", + displayName: "First build option", + type: "string", + description: "This is first build option.", + icon: "/emojis/1f310.png", + defaultValue: "ABCDEF", + mutable: true, + ephemeral: true, +} + +export const secondBuildOption: RichParameter = { + ...emptyParameter, + + name: "second_build_option", + displayName: "Second build option", + type: "bool", + description: "This is second build option.", + defaultValue: "false", + mutable: true, + ephemeral: true, +} diff --git a/site/e2e/tests/restartWorkspace.spec.ts b/site/e2e/tests/restartWorkspace.spec.ts new file mode 100644 index 0000000000000..0c910cedf2dac --- /dev/null +++ b/site/e2e/tests/restartWorkspace.spec.ts @@ -0,0 +1,39 @@ +import { test } from "@playwright/test" +import { + createTemplate, + createWorkspace, + echoResponsesWithParameters, + restartWorkspace, + verifyParameters, +} from "../helpers" + +import { firstBuildOption, secondBuildOption } from "../parameters" +import { RichParameter } from "../provisionerGenerated" + +test("restart workspace with ephemeral parameters", async ({ page }) => { + const richParameters: RichParameter[] = [firstBuildOption, secondBuildOption] + const template = await createTemplate( + page, + echoResponsesWithParameters(richParameters), + ) + const workspaceName = await createWorkspace(page, template) + + // Verify that build options are default (not selected). + await verifyParameters(page, workspaceName, richParameters, [ + { name: firstBuildOption.name, value: firstBuildOption.defaultValue }, + { name: secondBuildOption.name, value: secondBuildOption.defaultValue }, + ]) + + // Now, restart the workspace with ephemeral parameters selected. + const buildParameters = [ + { name: firstBuildOption.name, value: "AAAAA" }, + { name: secondBuildOption.name, value: "true" }, + ] + await restartWorkspace(page, workspaceName, richParameters, buildParameters) + + // Verify that build options are default (not selected). + await verifyParameters(page, workspaceName, richParameters, [ + { name: firstBuildOption.name, value: buildParameters[0].value }, + { name: secondBuildOption.name, value: buildParameters[1].value }, + ]) +}) From 0b8d6d8c544481ba8726f36b7b3bf0f105dab9f0 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Fri, 25 Aug 2023 11:49:14 +0200 Subject: [PATCH 02/17] start workspace with ephemerals --- site/e2e/helpers.ts | 27 ++++++++++- site/e2e/tests/restartWorkspace.spec.ts | 7 +-- site/e2e/tests/startWorkspace.spec.ts | 45 +++++++++++++++++++ .../components/WorkspaceActions/Buttons.tsx | 1 + 4 files changed, 75 insertions(+), 5 deletions(-) create mode 100644 site/e2e/tests/startWorkspace.spec.ts diff --git a/site/e2e/helpers.ts b/site/e2e/helpers.ts index 4f30637f5298e..df102715f3d0c 100644 --- a/site/e2e/helpers.ts +++ b/site/e2e/helpers.ts @@ -216,11 +216,31 @@ export const sshIntoWorkspace = async ( }) } -export const restartWorkspace = async ( +export const stopWorkspace = async ( + page: Page, + workspaceName: string, +) => { + await page.goto("/@admin/" + workspaceName, { + waitUntil: "domcontentloaded", + }) + await expect(page).toHaveURL("/@admin/" + workspaceName) + + await page.getByTestId("workspace-stop-button").click() + + await page.waitForSelector( + "span[data-testid='build-status'] >> text=Stopped", + { + state: "visible", + }, + ) +} + +export const buildWorkspaceWithParameters = async ( page: Page, workspaceName: string, richParameters: RichParameter[] = [], buildParameters: WorkspaceBuildParameter[] = [], + confirm: boolean = false, ) => { await page.goto("/@admin/" + workspaceName, { waitUntil: "domcontentloaded", @@ -270,7 +290,10 @@ export const restartWorkspace = async ( } await page.getByTestId("build-parameters-submit").click() - await page.getByTestId("confirm-button").click() + + if (confirm) { + await page.getByTestId("confirm-button").click() + } await page.waitForSelector( "span[data-testid='build-status'] >> text=Running", diff --git a/site/e2e/tests/restartWorkspace.spec.ts b/site/e2e/tests/restartWorkspace.spec.ts index 0c910cedf2dac..157876f7eba52 100644 --- a/site/e2e/tests/restartWorkspace.spec.ts +++ b/site/e2e/tests/restartWorkspace.spec.ts @@ -1,9 +1,9 @@ import { test } from "@playwright/test" import { + buildWorkspaceWithParameters, createTemplate, createWorkspace, echoResponsesWithParameters, - restartWorkspace, verifyParameters, } from "../helpers" @@ -29,9 +29,10 @@ test("restart workspace with ephemeral parameters", async ({ page }) => { { name: firstBuildOption.name, value: "AAAAA" }, { name: secondBuildOption.name, value: "true" }, ] - await restartWorkspace(page, workspaceName, richParameters, buildParameters) + await buildWorkspaceWithParameters(page, workspaceName, richParameters, buildParameters, true) - // Verify that build options are default (not selected). + // FIXME: verify that build options are default (not selected). + // It is the opposite now until the site logic is corrected. await verifyParameters(page, workspaceName, richParameters, [ { name: firstBuildOption.name, value: buildParameters[0].value }, { name: secondBuildOption.name, value: buildParameters[1].value }, diff --git a/site/e2e/tests/startWorkspace.spec.ts b/site/e2e/tests/startWorkspace.spec.ts new file mode 100644 index 0000000000000..2620c1d644767 --- /dev/null +++ b/site/e2e/tests/startWorkspace.spec.ts @@ -0,0 +1,45 @@ +import { test } from "@playwright/test" +import { + buildWorkspaceWithParameters, + createTemplate, + createWorkspace, + echoResponsesWithParameters, + stopWorkspace, + verifyParameters, +} from "../helpers" + +import { firstBuildOption, secondBuildOption } from "../parameters" +import { RichParameter } from "../provisionerGenerated" + +test("start workspace with ephemeral parameters", async ({ page }) => { + const richParameters: RichParameter[] = [firstBuildOption, secondBuildOption] + const template = await createTemplate( + page, + echoResponsesWithParameters(richParameters), + ) + const workspaceName = await createWorkspace(page, template) + + // Verify that build options are default (not selected). + await verifyParameters(page, workspaceName, richParameters, [ + { name: firstBuildOption.name, value: firstBuildOption.defaultValue }, + { name: secondBuildOption.name, value: secondBuildOption.defaultValue }, + ]) + + // Stop the workspace + await stopWorkspace(page, workspaceName) + + // Now, start the workspace with ephemeral parameters selected. + const buildParameters = [ + { name: firstBuildOption.name, value: "AAAAA" }, + { name: secondBuildOption.name, value: "true" }, + ] + + await buildWorkspaceWithParameters(page, workspaceName, richParameters, buildParameters) + + // FIXME: verify that build options are default (not selected). + // It is the opposite now until the site logic is corrected. + await verifyParameters(page, workspaceName, richParameters, [ + { name: firstBuildOption.name, value: buildParameters[0].value }, + { name: secondBuildOption.name, value: buildParameters[1].value }, + ]) +}) diff --git a/site/src/components/WorkspaceActions/Buttons.tsx b/site/src/components/WorkspaceActions/Buttons.tsx index 4d62e0bb9cc32..e2a005929a5a7 100644 --- a/site/src/components/WorkspaceActions/Buttons.tsx +++ b/site/src/components/WorkspaceActions/Buttons.tsx @@ -94,6 +94,7 @@ export const StopButton: FC = ({ handleAction, loading }) => { loadingPosition="start" startIcon={} onClick={handleAction} + data-testid="workspace-stop-button" > Stop From ca7feaa6104960e3d0863405dbabff4bcffbd573 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Fri, 25 Aug 2023 11:58:48 +0200 Subject: [PATCH 03/17] refactor --- site/e2e/helpers.ts | 134 +++++++++--------------- site/e2e/tests/restartWorkspace.spec.ts | 8 +- site/e2e/tests/startWorkspace.spec.ts | 7 +- 3 files changed, 62 insertions(+), 87 deletions(-) diff --git a/site/e2e/helpers.ts b/site/e2e/helpers.ts index df102715f3d0c..8743b13730b08 100644 --- a/site/e2e/helpers.ts +++ b/site/e2e/helpers.ts @@ -34,46 +34,7 @@ export const createWorkspace = async ( const name = randomName() await page.getByLabel("name").fill(name) - for (const buildParameter of buildParameters) { - const richParameter = richParameters.find( - (richParam) => richParam.name === buildParameter.name, - ) - if (!richParameter) { - throw new Error( - "build parameter is expected to be present in rich parameter schema", - ) - } - - const parameterLabel = await page.waitForSelector( - "[data-testid='parameter-field-" + richParameter.name + "']", - { state: "visible" }, - ) - - if (richParameter.type === "bool") { - const parameterField = await parameterLabel.waitForSelector( - "[data-testid='parameter-field-bool'] .MuiRadio-root input[value='" + - buildParameter.value + - "']", - ) - await parameterField.check() - } else if (richParameter.options.length > 0) { - const parameterField = await parameterLabel.waitForSelector( - "[data-testid='parameter-field-options'] .MuiRadio-root input[value='" + - buildParameter.value + - "']", - ) - await parameterField.check() - } else if (richParameter.type === "list(string)") { - throw new Error("not implemented yet") // FIXME - } else { - // text or number - const parameterField = await parameterLabel.waitForSelector( - "[data-testid='parameter-field-text'] input", - ) - await parameterField.fill(buildParameter.value) - } - } - + await fillParameters(page, richParameters, buildParameters) await page.getByTestId("form-submit").click() await expect(page).toHaveURL("/@admin/" + name) @@ -216,10 +177,7 @@ export const sshIntoWorkspace = async ( }) } -export const stopWorkspace = async ( - page: Page, - workspaceName: string, -) => { +export const stopWorkspace = async (page: Page, workspaceName: string) => { await page.goto("/@admin/" + workspaceName, { waitUntil: "domcontentloaded", }) @@ -249,48 +207,8 @@ export const buildWorkspaceWithParameters = async ( await page.getByTestId("build-parameters-button").click() - for (const buildParameter of buildParameters) { - const richParameter = richParameters.find( - (richParam) => richParam.name === buildParameter.name, - ) - if (!richParameter) { - throw new Error( - "build parameter is expected to be present in rich parameter schema", - ) - } - - const parameterLabel = await page.waitForSelector( - "[data-testid='parameter-field-" + richParameter.name + "']", - { state: "visible" }, - ) - - if (richParameter.type === "bool") { - const parameterField = await parameterLabel.waitForSelector( - "[data-testid='parameter-field-bool'] .MuiRadio-root input[value='" + - buildParameter.value + - "']", - ) - await parameterField.check() - } else if (richParameter.options.length > 0) { - const parameterField = await parameterLabel.waitForSelector( - "[data-testid='parameter-field-options'] .MuiRadio-root input[value='" + - buildParameter.value + - "']", - ) - await parameterField.check() - } else if (richParameter.type === "list(string)") { - throw new Error("not implemented yet") // FIXME - } else { - // text or number - const parameterField = await parameterLabel.waitForSelector( - "[data-testid='parameter-field-text'] input", - ) - await parameterField.fill(buildParameter.value) - } - } - + await fillParameters(page, richParameters, buildParameters) await page.getByTestId("build-parameters-submit").click() - if (confirm) { await page.getByTestId("confirm-button").click() } @@ -651,3 +569,49 @@ export const echoResponsesWithParameters = ( ], } } + +export const fillParameters = async ( + page: Page, + richParameters: RichParameter[] = [], + buildParameters: WorkspaceBuildParameter[] = [], +) => { + for (const buildParameter of buildParameters) { + const richParameter = richParameters.find( + (richParam) => richParam.name === buildParameter.name, + ) + if (!richParameter) { + throw new Error( + "build parameter is expected to be present in rich parameter schema", + ) + } + + const parameterLabel = await page.waitForSelector( + "[data-testid='parameter-field-" + richParameter.name + "']", + { state: "visible" }, + ) + + if (richParameter.type === "bool") { + const parameterField = await parameterLabel.waitForSelector( + "[data-testid='parameter-field-bool'] .MuiRadio-root input[value='" + + buildParameter.value + + "']", + ) + await parameterField.check() + } else if (richParameter.options.length > 0) { + const parameterField = await parameterLabel.waitForSelector( + "[data-testid='parameter-field-options'] .MuiRadio-root input[value='" + + buildParameter.value + + "']", + ) + await parameterField.check() + } else if (richParameter.type === "list(string)") { + throw new Error("not implemented yet") // FIXME + } else { + // text or number + const parameterField = await parameterLabel.waitForSelector( + "[data-testid='parameter-field-text'] input", + ) + await parameterField.fill(buildParameter.value) + } + } +} diff --git a/site/e2e/tests/restartWorkspace.spec.ts b/site/e2e/tests/restartWorkspace.spec.ts index 157876f7eba52..0cae848172490 100644 --- a/site/e2e/tests/restartWorkspace.spec.ts +++ b/site/e2e/tests/restartWorkspace.spec.ts @@ -29,7 +29,13 @@ test("restart workspace with ephemeral parameters", async ({ page }) => { { name: firstBuildOption.name, value: "AAAAA" }, { name: secondBuildOption.name, value: "true" }, ] - await buildWorkspaceWithParameters(page, workspaceName, richParameters, buildParameters, true) + await buildWorkspaceWithParameters( + page, + workspaceName, + richParameters, + buildParameters, + true, + ) // FIXME: verify that build options are default (not selected). // It is the opposite now until the site logic is corrected. diff --git a/site/e2e/tests/startWorkspace.spec.ts b/site/e2e/tests/startWorkspace.spec.ts index 2620c1d644767..7c580ec1f288d 100644 --- a/site/e2e/tests/startWorkspace.spec.ts +++ b/site/e2e/tests/startWorkspace.spec.ts @@ -34,7 +34,12 @@ test("start workspace with ephemeral parameters", async ({ page }) => { { name: secondBuildOption.name, value: "true" }, ] - await buildWorkspaceWithParameters(page, workspaceName, richParameters, buildParameters) + await buildWorkspaceWithParameters( + page, + workspaceName, + richParameters, + buildParameters, + ) // FIXME: verify that build options are default (not selected). // It is the opposite now until the site logic is corrected. From 135b1e4f20baf81945a283f03c775b18008b56a1 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Fri, 25 Aug 2023 12:29:39 +0200 Subject: [PATCH 04/17] FIXMEs --- site/e2e/tests/restartWorkspace.spec.ts | 7 +++---- site/e2e/tests/startWorkspace.spec.ts | 7 +++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/site/e2e/tests/restartWorkspace.spec.ts b/site/e2e/tests/restartWorkspace.spec.ts index 0cae848172490..eb5d0c99c0217 100644 --- a/site/e2e/tests/restartWorkspace.spec.ts +++ b/site/e2e/tests/restartWorkspace.spec.ts @@ -37,10 +37,9 @@ test("restart workspace with ephemeral parameters", async ({ page }) => { true, ) - // FIXME: verify that build options are default (not selected). - // It is the opposite now until the site logic is corrected. + // Verify that build options are default (not selected). await verifyParameters(page, workspaceName, richParameters, [ - { name: firstBuildOption.name, value: buildParameters[0].value }, - { name: secondBuildOption.name, value: buildParameters[1].value }, + { name: firstBuildOption.name, value: firstBuildOption.defaultValue }, + { name: secondBuildOption.name, value: secondBuildOption.defaultValue }, ]) }) diff --git a/site/e2e/tests/startWorkspace.spec.ts b/site/e2e/tests/startWorkspace.spec.ts index 7c580ec1f288d..232ac27299849 100644 --- a/site/e2e/tests/startWorkspace.spec.ts +++ b/site/e2e/tests/startWorkspace.spec.ts @@ -41,10 +41,9 @@ test("start workspace with ephemeral parameters", async ({ page }) => { buildParameters, ) - // FIXME: verify that build options are default (not selected). - // It is the opposite now until the site logic is corrected. + // Verify that build options are default (not selected). await verifyParameters(page, workspaceName, richParameters, [ - { name: firstBuildOption.name, value: buildParameters[0].value }, - { name: secondBuildOption.name, value: buildParameters[1].value }, + { name: firstBuildOption.name, value: firstBuildOption.defaultValue }, + { name: secondBuildOption.name, value: secondBuildOption.defaultValue }, ]) }) From 679e205444ec93a8498c77e48441a375a3260ab1 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Fri, 25 Aug 2023 16:38:44 +0200 Subject: [PATCH 05/17] WIP --- site/e2e/helpers.ts | 22 ++++++++++++ site/e2e/tests/createWorkspace.spec.ts | 1 + site/e2e/tests/updateWorkspace.spec.ts | 49 ++++++++++++++++++++++++++ 3 files changed, 72 insertions(+) create mode 100644 site/e2e/tests/updateWorkspace.spec.ts diff --git a/site/e2e/helpers.ts b/site/e2e/helpers.ts index 8743b13730b08..fd8f5d209c353 100644 --- a/site/e2e/helpers.ts +++ b/site/e2e/helpers.ts @@ -19,6 +19,7 @@ import { port } from "./playwright.config" import * as ssh from "ssh2" import { Duplex } from "stream" import { WorkspaceBuildParameter } from "api/typesGenerated" +import { createTemplateVersion, uploadTemplateFile } from "api/api" // createWorkspace creates a workspace for a template. // It does not wait for it to be running, but it does navigate to the page. @@ -615,3 +616,24 @@ export const fillParameters = async ( } } } + +export const uploadTemplateVersion = async ( + templateName: string, + responses?: EchoProvisionerResponses, +): Promise => { + const tarball = await createTemplateVersionTar(responses) + const file = new File([tarball], "version.tar", { type: "application/x-tar" }) + const uploadResponse = await uploadTemplateFile(file) + + // FIXME find: + const organizationId = "" + const templateId = "" + const templateVersion = await createTemplateVersion(organizationId, { + template_id: templateId, + storage_method: "file", + file_id: uploadResponse.hash, + provisioner: "echo", + tags: {}, + }) + return templateVersion.id +} diff --git a/site/e2e/tests/createWorkspace.spec.ts b/site/e2e/tests/createWorkspace.spec.ts index 1effc01976651..cb2d204a2e50e 100644 --- a/site/e2e/tests/createWorkspace.spec.ts +++ b/site/e2e/tests/createWorkspace.spec.ts @@ -107,6 +107,7 @@ test("create workspace and overwrite default parameters", async ({ page }) => { page, echoResponsesWithParameters(richParameters), ) + const workspaceName = await createWorkspace( page, template, diff --git a/site/e2e/tests/updateWorkspace.spec.ts b/site/e2e/tests/updateWorkspace.spec.ts new file mode 100644 index 0000000000000..89bb54db734af --- /dev/null +++ b/site/e2e/tests/updateWorkspace.spec.ts @@ -0,0 +1,49 @@ +import { test } from "@playwright/test" +import { + createTemplate, + createWorkspace, + echoResponsesWithParameters, + uploadTemplateVersion, + verifyParameters, +} from "../helpers" + +import { fifthParameter, firstParameter, secondParameter } from "../parameters" +import { RichParameter } from "../provisionerGenerated" + +test("update workspace, new optional, mutable parameter added", async ({ + page, +}) => { + const richParameters: RichParameter[] = [firstParameter, secondParameter] + const template = await createTemplate( + page, + echoResponsesWithParameters(richParameters), + ) + + const workspaceName = await createWorkspace(page, template) + + // Verify that parameter values are default. + await verifyParameters(page, workspaceName, richParameters, [ + { name: firstParameter.name, value: firstParameter.defaultValue }, + { name: secondParameter.name, value: secondParameter.defaultValue }, + ]) + + // Upload new template version with extra parameter. + const updatedRichParameters = [...richParameters, fifthParameter] + const templateVersion = await uploadTemplateVersion( + template, + echoResponsesWithParameters(updatedRichParameters), + ) + + // TODO Activate the template version + // Go to Versions -> Promote version + + // Now, update the workspace. + // TODO Update workspace + + // Verify that parameter values are default. + await verifyParameters(page, workspaceName, updatedRichParameters, [ + { name: firstParameter.name, value: firstParameter.defaultValue }, + { name: secondParameter.name, value: secondParameter.defaultValue }, + { name: fifthParameter.name, value: fifthParameter.defaultValue }, + ]) +}) From d43615534e2f9314a58eaa664e2a6d019d0186d1 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Mon, 28 Aug 2023 14:12:35 +0200 Subject: [PATCH 06/17] Update --- site/e2e/helpers.ts | 83 +++++++++++++++---- site/e2e/tests/updateWorkspace.spec.ts | 21 ++--- .../UpdateBuildParametersDialog.tsx | 8 +- 3 files changed, 85 insertions(+), 27 deletions(-) diff --git a/site/e2e/helpers.ts b/site/e2e/helpers.ts index fd8f5d209c353..098c2cf468418 100644 --- a/site/e2e/helpers.ts +++ b/site/e2e/helpers.ts @@ -19,7 +19,6 @@ import { port } from "./playwright.config" import * as ssh from "ssh2" import { Duplex } from "stream" import { WorkspaceBuildParameter } from "api/typesGenerated" -import { createTemplateVersion, uploadTemplateFile } from "api/api" // createWorkspace creates a workspace for a template. // It does not wait for it to be running, but it does navigate to the page. @@ -117,6 +116,7 @@ export const createTemplate = async ( await page.addInitScript({ content: "window.playwright = true", }) + await page.goto("/templates/new", { waitUntil: "networkidle" }) await page.getByTestId("file-upload").setInputFiles({ buffer: await createTemplateVersionTar(responses), @@ -617,23 +617,74 @@ export const fillParameters = async ( } } -export const uploadTemplateVersion = async ( +export const updateTemplate = async ( + page: Page, templateName: string, responses?: EchoProvisionerResponses, -): Promise => { +) => { const tarball = await createTemplateVersionTar(responses) - const file = new File([tarball], "version.tar", { type: "application/x-tar" }) - const uploadResponse = await uploadTemplateFile(file) - - // FIXME find: - const organizationId = "" - const templateId = "" - const templateVersion = await createTemplateVersion(organizationId, { - template_id: templateId, - storage_method: "file", - file_id: uploadResponse.hash, - provisioner: "echo", - tags: {}, + + const sessionToken = await findSessionToken(page) + const child = spawn( + "go", + [ + "run", + coderMainPath(), + "templates", + "push", + "--test.provisioner", + "echo", + "-y", + "-d", + "-", + templateName, + ], + { + env: { + ...process.env, + CODER_SESSION_TOKEN: sessionToken, + CODER_URL: "http://localhost:3000", + }, + }, + ) + + const uploaded = new Awaiter() + child.on("exit", (code) => { + if (code === 0) { + uploaded.done() + return + } + + throw new Error(`coder templates push failed with code ${code}`) + }) + + child.stdin.write(tarball) + child.stdin.end() + + await uploaded.wait() +} + +export const updateWorkspace = async ( + page: Page, + workspaceName: string, + richParameters: RichParameter[] = [], + buildParameters: WorkspaceBuildParameter[] = [], +) => { + await page.goto("/@admin/" + workspaceName, { + waitUntil: "domcontentloaded", }) - return templateVersion.id + await expect(page).toHaveURL("/@admin/" + workspaceName) + + await page.getByTestId("workspace-update-button").click() + await page.getByTestId("confirm-button").click() + + await fillParameters(page, richParameters, buildParameters) + await page.getByTestId("form-submit").click() + + await page.waitForSelector( + "span[data-testid='build-status'] >> text=Running", + { + state: "visible", + }, + ) } diff --git a/site/e2e/tests/updateWorkspace.spec.ts b/site/e2e/tests/updateWorkspace.spec.ts index 89bb54db734af..2303feb479e33 100644 --- a/site/e2e/tests/updateWorkspace.spec.ts +++ b/site/e2e/tests/updateWorkspace.spec.ts @@ -3,14 +3,15 @@ import { createTemplate, createWorkspace, echoResponsesWithParameters, - uploadTemplateVersion, + updateTemplate, + updateWorkspace, verifyParameters, } from "../helpers" import { fifthParameter, firstParameter, secondParameter } from "../parameters" import { RichParameter } from "../provisionerGenerated" -test("update workspace, new optional, mutable parameter added", async ({ +test("update workspace, new optional, immutable parameter added", async ({ page, }) => { const richParameters: RichParameter[] = [firstParameter, secondParameter] @@ -27,23 +28,23 @@ test("update workspace, new optional, mutable parameter added", async ({ { name: secondParameter.name, value: secondParameter.defaultValue }, ]) - // Upload new template version with extra parameter. + // Push updated template. const updatedRichParameters = [...richParameters, fifthParameter] - const templateVersion = await uploadTemplateVersion( + await updateTemplate( + page, template, echoResponsesWithParameters(updatedRichParameters), ) - // TODO Activate the template version - // Go to Versions -> Promote version - - // Now, update the workspace. - // TODO Update workspace + // Now, update the workspace, and select the value for immutable parameter. + await updateWorkspace(page, workspaceName, updatedRichParameters, [ + { name: fifthParameter.name, value: fifthParameter.options[0].value }, + ]) // Verify that parameter values are default. await verifyParameters(page, workspaceName, updatedRichParameters, [ { name: firstParameter.name, value: firstParameter.defaultValue }, { name: secondParameter.name, value: secondParameter.defaultValue }, - { name: fifthParameter.name, value: fifthParameter.defaultValue }, + { name: fifthParameter.name, value: fifthParameter.options[0].value }, ]) }) diff --git a/site/src/pages/WorkspacePage/UpdateBuildParametersDialog.tsx b/site/src/pages/WorkspacePage/UpdateBuildParametersDialog.tsx index 860969267fb7e..2293fb530ffb3 100644 --- a/site/src/pages/WorkspacePage/UpdateBuildParametersDialog.tsx +++ b/site/src/pages/WorkspacePage/UpdateBuildParametersDialog.tsx @@ -111,7 +111,13 @@ export const UpdateBuildParametersDialog: FC< - From b7fe39d47cb524de5982e71d5f4dec32b917b1ae Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Tue, 29 Aug 2023 11:03:39 +0200 Subject: [PATCH 07/17] Update tests --- site/e2e/helpers.ts | 26 ++++++++ site/e2e/tests/updateWorkspace.spec.ts | 84 +++++++++++++++++++++++++- 2 files changed, 108 insertions(+), 2 deletions(-) diff --git a/site/e2e/helpers.ts b/site/e2e/helpers.ts index b447210b59162..7ce33e9308f28 100644 --- a/site/e2e/helpers.ts +++ b/site/e2e/helpers.ts @@ -31,6 +31,8 @@ export const createWorkspace = async ( await page.goto("/templates/" + templateName + "/workspace", { waitUntil: "networkidle", }) + await expect(page).toHaveURL("/templates/" + templateName + "/workspace") + const name = randomName() await page.getByLabel("name").fill(name) @@ -689,3 +691,27 @@ export const updateWorkspace = async ( }, ) } + +export const updateWorkspaceParameters = async ( + page: Page, + workspaceName: string, + richParameters: RichParameter[] = [], + buildParameters: WorkspaceBuildParameter[] = [], +) => { + await page.goto("/@admin/" + workspaceName + "/settings/parameters", { + waitUntil: "domcontentloaded", + }) + await expect(page).toHaveURL( + "/@admin/" + workspaceName + "/settings/parameters", + ) + + await fillParameters(page, richParameters, buildParameters) + await page.getByTestId("form-submit").click() + + await page.waitForSelector( + "span[data-testid='build-status'] >> text=Running", + { + state: "visible", + }, + ) +} diff --git a/site/e2e/tests/updateWorkspace.spec.ts b/site/e2e/tests/updateWorkspace.spec.ts index 2303feb479e33..2582e380efe0d 100644 --- a/site/e2e/tests/updateWorkspace.spec.ts +++ b/site/e2e/tests/updateWorkspace.spec.ts @@ -5,10 +5,17 @@ import { echoResponsesWithParameters, updateTemplate, updateWorkspace, + updateWorkspaceParameters, verifyParameters, } from "../helpers" -import { fifthParameter, firstParameter, secondParameter } from "../parameters" +import { + fifthParameter, + firstParameter, + secondParameter, + sixthParameter, + secondBuildOption, +} from "../parameters" import { RichParameter } from "../provisionerGenerated" test("update workspace, new optional, immutable parameter added", async ({ @@ -41,10 +48,83 @@ test("update workspace, new optional, immutable parameter added", async ({ { name: fifthParameter.name, value: fifthParameter.options[0].value }, ]) - // Verify that parameter values are default. + // Verify parameter values. await verifyParameters(page, workspaceName, updatedRichParameters, [ { name: firstParameter.name, value: firstParameter.defaultValue }, { name: secondParameter.name, value: secondParameter.defaultValue }, { name: fifthParameter.name, value: fifthParameter.options[0].value }, ]) }) + +test("update workspace, new required, mutable parameter added", async ({ + page, +}) => { + const richParameters: RichParameter[] = [firstParameter, secondParameter] + const template = await createTemplate( + page, + echoResponsesWithParameters(richParameters), + ) + + const workspaceName = await createWorkspace(page, template) + + // Verify that parameter values are default. + await verifyParameters(page, workspaceName, richParameters, [ + { name: firstParameter.name, value: firstParameter.defaultValue }, + { name: secondParameter.name, value: secondParameter.defaultValue }, + ]) + + // Push updated template. + const updatedRichParameters = [...richParameters, sixthParameter] + await updateTemplate( + page, + template, + echoResponsesWithParameters(updatedRichParameters), + ) + + // Now, update the workspace, and provide the parameter value. + const buildParameters = [{ name: sixthParameter.name, value: "99" }] + await updateWorkspace( + page, + workspaceName, + updatedRichParameters, + buildParameters, + ) + + // Verify parameter values. + await verifyParameters(page, workspaceName, updatedRichParameters, [ + { name: firstParameter.name, value: firstParameter.defaultValue }, + { name: secondParameter.name, value: secondParameter.defaultValue }, + ...buildParameters, + ]) +}) + +test("update workspace with ephemeral parameter enabled", async ({ page }) => { + const richParameters: RichParameter[] = [firstParameter, secondBuildOption] + const template = await createTemplate( + page, + echoResponsesWithParameters(richParameters), + ) + + const workspaceName = await createWorkspace(page, template) + + // Verify that parameter values are default. + await verifyParameters(page, workspaceName, richParameters, [ + { name: firstParameter.name, value: firstParameter.defaultValue }, + { name: secondBuildOption.name, value: secondBuildOption.defaultValue }, + ]) + + // Now, update the workspace, and select the value for ephemeral parameter. + const buildParameters = [{ name: secondBuildOption.name, value: "true" }] + await updateWorkspaceParameters( + page, + workspaceName, + richParameters, + buildParameters, + ) + + // Verify that parameter values are default. + await verifyParameters(page, workspaceName, richParameters, [ + { name: firstParameter.name, value: firstParameter.defaultValue }, + { name: secondBuildOption.name, value: secondBuildOption.defaultValue }, + ]) +}) From 5a141fbafa41290488b6bc781d7653a15fd0a4f8 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Tue, 29 Aug 2023 11:16:51 +0200 Subject: [PATCH 08/17] expect --- site/e2e/helpers.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/site/e2e/helpers.ts b/site/e2e/helpers.ts index 7ce33e9308f28..f655b14c192e3 100644 --- a/site/e2e/helpers.ts +++ b/site/e2e/helpers.ts @@ -121,6 +121,8 @@ export const createTemplate = async ( }) await page.goto("/templates/new", { waitUntil: "networkidle" }) + await expect(page).toHaveURL("/templates/new") + await page.getByTestId("file-upload").setInputFiles({ buffer: await createTemplateVersionTar(responses), mimeType: "application/x-tar", From 2973893685068d3ab651d4d7e4cab1fcbf9d51e7 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Tue, 29 Aug 2023 11:39:26 +0200 Subject: [PATCH 09/17] WIP --- site/e2e/tests/updateWorkspace.spec.ts | 2 ++ site/src/xServices/createTemplate/createTemplateXService.ts | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/site/e2e/tests/updateWorkspace.spec.ts b/site/e2e/tests/updateWorkspace.spec.ts index 2582e380efe0d..062260e6b1661 100644 --- a/site/e2e/tests/updateWorkspace.spec.ts +++ b/site/e2e/tests/updateWorkspace.spec.ts @@ -99,6 +99,8 @@ test("update workspace, new required, mutable parameter added", async ({ }) test("update workspace with ephemeral parameter enabled", async ({ page }) => { + page.on('console', msg => console.log(msg.text())); + const richParameters: RichParameter[] = [firstParameter, secondBuildOption] const template = await createTemplate( page, diff --git a/site/src/xServices/createTemplate/createTemplateXService.ts b/site/src/xServices/createTemplate/createTemplateXService.ts index bfff04549fe17..b968118fb9113 100644 --- a/site/src/xServices/createTemplate/createTemplateXService.ts +++ b/site/src/xServices/createTemplate/createTemplateXService.ts @@ -487,7 +487,7 @@ export const createTemplateMachine = assignError: assign({ error: (_, { data }) => data }), assignJobError: assign({ jobError: (_, { data }) => data.job.error }), displayUploadError: () => { - displayError("Error on upload the file.") + displayError("Error on upload the file") }, assignStarterTemplate: assign({ starterTemplate: (_, { data }) => data, From 5f06496ca7bef6ab10dbc6e5bf7a5175a5e31377 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Tue, 29 Aug 2023 11:43:26 +0200 Subject: [PATCH 10/17] WIP: console --- site/e2e/tests/updateWorkspace.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/e2e/tests/updateWorkspace.spec.ts b/site/e2e/tests/updateWorkspace.spec.ts index 062260e6b1661..1c599b2844521 100644 --- a/site/e2e/tests/updateWorkspace.spec.ts +++ b/site/e2e/tests/updateWorkspace.spec.ts @@ -99,7 +99,7 @@ test("update workspace, new required, mutable parameter added", async ({ }) test("update workspace with ephemeral parameter enabled", async ({ page }) => { - page.on('console', msg => console.log(msg.text())); + page.on("console", (msg) => console.log(msg.text())) const richParameters: RichParameter[] = [firstParameter, secondBuildOption] const template = await createTemplate( From ae71f23bc4f9e21307696b01d2548b7682b1785d Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Tue, 29 Aug 2023 11:56:45 +0200 Subject: [PATCH 11/17] WIP: console --- site/e2e/tests/createWorkspace.spec.ts | 6 +++++- site/e2e/tests/updateWorkspace.spec.ts | 9 ++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/site/e2e/tests/createWorkspace.spec.ts b/site/e2e/tests/createWorkspace.spec.ts index cb2d204a2e50e..8ac547a48b376 100644 --- a/site/e2e/tests/createWorkspace.spec.ts +++ b/site/e2e/tests/createWorkspace.spec.ts @@ -1,4 +1,4 @@ -import { test } from "@playwright/test" +import { test, Page } from "@playwright/test" import { createTemplate, createWorkspace, @@ -17,6 +17,10 @@ import { } from "../parameters" import { RichParameter } from "../provisionerGenerated" +test.beforeEach(async ({ page }: { page: Page }) => { + page.on("console", (msg) => console.log(msg.text())) +}) + test("create workspace", async ({ page }) => { const template = await createTemplate(page, { apply: [ diff --git a/site/e2e/tests/updateWorkspace.spec.ts b/site/e2e/tests/updateWorkspace.spec.ts index 1c599b2844521..a5783099f1ef5 100644 --- a/site/e2e/tests/updateWorkspace.spec.ts +++ b/site/e2e/tests/updateWorkspace.spec.ts @@ -1,4 +1,5 @@ -import { test } from "@playwright/test" +import { test, Page } from "@playwright/test" + import { createTemplate, createWorkspace, @@ -18,6 +19,10 @@ import { } from "../parameters" import { RichParameter } from "../provisionerGenerated" +test.beforeEach(async ({ page }: { page: Page }) => { + page.on("console", (msg) => console.log(msg.text())) +}) + test("update workspace, new optional, immutable parameter added", async ({ page, }) => { @@ -99,8 +104,6 @@ test("update workspace, new required, mutable parameter added", async ({ }) test("update workspace with ephemeral parameter enabled", async ({ page }) => { - page.on("console", (msg) => console.log(msg.text())) - const richParameters: RichParameter[] = [firstParameter, secondBuildOption] const template = await createTemplate( page, From b77cd9bc23603ea3751de5eb46263cef2955f3d9 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Tue, 29 Aug 2023 12:05:53 +0200 Subject: [PATCH 12/17] Console fix --- site/e2e/tests/createWorkspace.spec.ts | 2 +- site/e2e/tests/updateWorkspace.spec.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/site/e2e/tests/createWorkspace.spec.ts b/site/e2e/tests/createWorkspace.spec.ts index 8ac547a48b376..d6bbad9af1b7f 100644 --- a/site/e2e/tests/createWorkspace.spec.ts +++ b/site/e2e/tests/createWorkspace.spec.ts @@ -18,7 +18,7 @@ import { import { RichParameter } from "../provisionerGenerated" test.beforeEach(async ({ page }: { page: Page }) => { - page.on("console", (msg) => console.log(msg.text())) + page.on("console", (msg) => console.log("Console: " + msg.text())) }) test("create workspace", async ({ page }) => { diff --git a/site/e2e/tests/updateWorkspace.spec.ts b/site/e2e/tests/updateWorkspace.spec.ts index a5783099f1ef5..987dabf12877d 100644 --- a/site/e2e/tests/updateWorkspace.spec.ts +++ b/site/e2e/tests/updateWorkspace.spec.ts @@ -20,7 +20,7 @@ import { import { RichParameter } from "../provisionerGenerated" test.beforeEach(async ({ page }: { page: Page }) => { - page.on("console", (msg) => console.log(msg.text())) + page.on("console", (msg) => console.log("Console: " + msg.text())) }) test("update workspace, new optional, immutable parameter added", async ({ From 55ed6878fe1362584df2bb2712d28ea64d6f67a0 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Tue, 29 Aug 2023 12:15:39 +0200 Subject: [PATCH 13/17] api-rate-limit --- site/e2e/playwright.config.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/site/e2e/playwright.config.ts b/site/e2e/playwright.config.ts index 78eb4495ce371..7838f663bcc7a 100644 --- a/site/e2e/playwright.config.ts +++ b/site/e2e/playwright.config.ts @@ -41,6 +41,7 @@ export default defineConfig({ `--access-url=http://localhost:${port} ` + `--http-address=localhost:${port} ` + `--in-memory --telemetry=false ` + + `--api-rate-limit=-1` + `--provisioner-daemons 10 ` + `--provisioner-daemons-echo ` + `--provisioner-daemon-poll-interval 50ms`, From d833e9ed73633fea5591e6c6d602bf1cd6d80524 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Tue, 29 Aug 2023 12:21:56 +0200 Subject: [PATCH 14/17] fix --- site/e2e/playwright.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/e2e/playwright.config.ts b/site/e2e/playwright.config.ts index 7838f663bcc7a..51735f32f42a4 100644 --- a/site/e2e/playwright.config.ts +++ b/site/e2e/playwright.config.ts @@ -41,7 +41,7 @@ export default defineConfig({ `--access-url=http://localhost:${port} ` + `--http-address=localhost:${port} ` + `--in-memory --telemetry=false ` + - `--api-rate-limit=-1` + + `--api-rate-limit=-1 ` + `--provisioner-daemons 10 ` + `--provisioner-daemons-echo ` + `--provisioner-daemon-poll-interval 50ms`, From 5128187cba775d4bac8ab94996174a7e9b2c21da Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Tue, 29 Aug 2023 12:38:34 +0200 Subject: [PATCH 15/17] dangerous-disable-rate-limits --- site/e2e/playwright.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/e2e/playwright.config.ts b/site/e2e/playwright.config.ts index 51735f32f42a4..b25393e529e4a 100644 --- a/site/e2e/playwright.config.ts +++ b/site/e2e/playwright.config.ts @@ -41,7 +41,7 @@ export default defineConfig({ `--access-url=http://localhost:${port} ` + `--http-address=localhost:${port} ` + `--in-memory --telemetry=false ` + - `--api-rate-limit=-1 ` + + `--dangerous-disable-rate-limits ` + `--provisioner-daemons 10 ` + `--provisioner-daemons-echo ` + `--provisioner-daemon-poll-interval 50ms`, From 63cb14533eaf79bce48b41afdaee85aaaefcdadd Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Tue, 29 Aug 2023 12:49:02 +0200 Subject: [PATCH 16/17] lint fix --- site/e2e/tests/createWorkspace.spec.ts | 1 + site/e2e/tests/updateWorkspace.spec.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/site/e2e/tests/createWorkspace.spec.ts b/site/e2e/tests/createWorkspace.spec.ts index d6bbad9af1b7f..8a4937ee019ea 100644 --- a/site/e2e/tests/createWorkspace.spec.ts +++ b/site/e2e/tests/createWorkspace.spec.ts @@ -18,6 +18,7 @@ import { import { RichParameter } from "../provisionerGenerated" test.beforeEach(async ({ page }: { page: Page }) => { + // eslint-disable-next-line no-console -- For debugging purposes page.on("console", (msg) => console.log("Console: " + msg.text())) }) diff --git a/site/e2e/tests/updateWorkspace.spec.ts b/site/e2e/tests/updateWorkspace.spec.ts index 987dabf12877d..ef84f19ba4ee0 100644 --- a/site/e2e/tests/updateWorkspace.spec.ts +++ b/site/e2e/tests/updateWorkspace.spec.ts @@ -20,6 +20,7 @@ import { import { RichParameter } from "../provisionerGenerated" test.beforeEach(async ({ page }: { page: Page }) => { + // eslint-disable-next-line no-console -- For debugging purposes page.on("console", (msg) => console.log("Console: " + msg.text())) }) From 55e7b8f58572d9146ad41b80c6810c81c3352d30 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Tue, 29 Aug 2023 12:57:16 +0200 Subject: [PATCH 17/17] fix --- site/src/xServices/createTemplate/createTemplateXService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/src/xServices/createTemplate/createTemplateXService.ts b/site/src/xServices/createTemplate/createTemplateXService.ts index b968118fb9113..bfff04549fe17 100644 --- a/site/src/xServices/createTemplate/createTemplateXService.ts +++ b/site/src/xServices/createTemplate/createTemplateXService.ts @@ -487,7 +487,7 @@ export const createTemplateMachine = assignError: assign({ error: (_, { data }) => data }), assignJobError: assign({ jobError: (_, { data }) => data.job.error }), displayUploadError: () => { - displayError("Error on upload the file") + displayError("Error on upload the file.") }, assignStarterTemplate: assign({ starterTemplate: (_, { data }) => data,