From 6e3cf2a37fae3e27566d868609105a44e4ab9591 Mon Sep 17 00:00:00 2001 From: tharvik Date: Thu, 14 Nov 2024 22:04:45 +0100 Subject: [PATCH] webapp/task-creation: revamp --- package-lock.json | 31 +- webapp/cypress/e2e/task-creation.cy.ts | 119 +++ webapp/cypress/support/e2e.ts | 2 + webapp/package.json | 2 +- .../task_creation_form/FormField.vue | 23 + .../task_creation_form/FormLabel.vue | 32 + .../task_creation_form/TaskForm.vue | 813 ++++++++++++------ .../containers/ArrayContainer.vue | 145 ---- .../containers/CheckboxContainer.vue | 45 - .../containers/FileContainer.vue | 166 ---- .../containers/FloatContainer.vue | 13 - .../containers/NumberContainer.vue | 16 - .../containers/ObjectArrayContainer.vue | 174 ---- .../containers/SelectContainer.vue | 38 - .../containers/TextContainer.vue | 27 - .../task_creation_form/simple/CustomField.vue | 44 - .../src/components/training/Description.vue | 4 +- webapp/src/task_creation_form.ts | 379 -------- 18 files changed, 744 insertions(+), 1329 deletions(-) create mode 100644 webapp/cypress/e2e/task-creation.cy.ts create mode 100644 webapp/src/components/task_creation_form/FormField.vue create mode 100644 webapp/src/components/task_creation_form/FormLabel.vue delete mode 100644 webapp/src/components/task_creation_form/containers/ArrayContainer.vue delete mode 100644 webapp/src/components/task_creation_form/containers/CheckboxContainer.vue delete mode 100644 webapp/src/components/task_creation_form/containers/FileContainer.vue delete mode 100644 webapp/src/components/task_creation_form/containers/FloatContainer.vue delete mode 100644 webapp/src/components/task_creation_form/containers/NumberContainer.vue delete mode 100644 webapp/src/components/task_creation_form/containers/ObjectArrayContainer.vue delete mode 100644 webapp/src/components/task_creation_form/containers/SelectContainer.vue delete mode 100644 webapp/src/components/task_creation_form/containers/TextContainer.vue delete mode 100644 webapp/src/components/task_creation_form/simple/CustomField.vue delete mode 100644 webapp/src/task_creation_form.ts diff --git a/package-lock.json b/package-lock.json index ffb241a66..4f52ef01f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3063,6 +3063,29 @@ "dev": true, "license": "ISC" }, + "node_modules/@vee-validate/zod": { + "version": "4.14.7", + "resolved": "https://registry.npmjs.org/@vee-validate/zod/-/zod-4.14.7.tgz", + "integrity": "sha512-UD0Tfyz1cKKd7BinnUztqKL+oeMjg/T4ZEguN/uZV4DsR9z7gdrD0lOuOU7aVl9UpVK6NM7MhDka35Lj7b/DTw==", + "license": "MIT", + "dependencies": { + "type-fest": "^4.8.3", + "vee-validate": "4.14.7", + "zod": "^3.22.4" + } + }, + "node_modules/@vee-validate/zod/node_modules/type-fest": { + "version": "4.30.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.30.0.tgz", + "integrity": "sha512-G6zXWS1dLj6eagy6sVhOMQiLtJdxQBHIA9Z6HFUNLOlr6MFOgzV8wvmidtPONfPtEUv0uZsy77XJNzTAfwPDaA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@vitejs/plugin-vue": { "version": "5.1.4", "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.1.4.tgz", @@ -12885,9 +12908,9 @@ } }, "node_modules/vee-validate": { - "version": "4.14.4", - "resolved": "https://registry.npmjs.org/vee-validate/-/vee-validate-4.14.4.tgz", - "integrity": "sha512-Eg1FK2IqxqmujjN8Oehvqfx+QynyrIVc6Su2h/HQI/m3VEyUXW4wlB0h0sLkNizsEWlTj7Kwxn5Q2UWCr7RTQg==", + "version": "4.14.7", + "resolved": "https://registry.npmjs.org/vee-validate/-/vee-validate-4.14.7.tgz", + "integrity": "sha512-XVb1gBFJR57equ11HEI8uxNqFJkwvCP/b+p+saDPQYaW7k45cdF5jsYPEJud1o29GD6h2y7Awm7Qfm89yKi74A==", "license": "MIT", "dependencies": { "@vue/devtools-api": "^7.5.2", @@ -13742,13 +13765,13 @@ "@epfml/discojs": "*", "@epfml/discojs-web": "*", "@msgpack/msgpack": "^3.0.0-beta2", + "@vee-validate/zod": "4", "apexcharts": "3", "cypress": "13", "d3": "7", "immutable": "4", "pinia": "<2.2.3", "pinia-plugin-persistedstate-2": "2", - "vee-validate": "4", "vue": "3", "vue-router": "4", "vue-tippy": "6", diff --git a/webapp/cypress/e2e/task-creation.cy.ts b/webapp/cypress/e2e/task-creation.cy.ts new file mode 100644 index 000000000..20fd63892 --- /dev/null +++ b/webapp/cypress/e2e/task-creation.cy.ts @@ -0,0 +1,119 @@ +import type { Task } from "@epfml/discojs"; + +import * as tf from "@tensorflow/tfjs"; + +it("submits with tabular task", () => { + cy.intercept( + { hostname: "server", pathname: "tasks", method: "POST" }, + { statusCode: 200 }, + ).as("posted"); + + cy.visit("/#/create"); + + cy.get("input[name='id']").type("id"); + cy.get("select[name='dataType']").select("tabular"); + + cy.get("input[name='displayInformation.title']").type("simple"); + cy.get("input[name='displayInformation.summary.preview']").type("preview"); + cy.get("textarea[name='displayInformation.summary.overview']").type( + "overview", + ); + cy.contains("Example tabular data").within(() => { + cy.contains("add example").click(); + cy.get("input[name='displayInformation.dataExample[0].name']").type("name"); + cy.get("input[name='displayInformation.dataExample[0].data']").type("data"); + }); + + cy.get("select[name='trainingInformation.scheme']").select("federated"); + cy.get("input[name='trainingInformation.epochs']").type("10"); + cy.get("input[name='trainingInformation.batchSize']").type("5"); + cy.get("input[name='trainingInformation.roundDuration']").type("2"); + cy.get("input[name='trainingInformation.validationSplit']").type("0"); + cy.get("input[name='trainingInformation.minNbOfParticipants']").type("2"); + cy.contains("Input columns names").within(() => { + cy.contains("add column").click(); + cy.get("input[name='trainingInformation.inputColumns[0]']").type("input"); + }); + cy.get("input[name='trainingInformation.outputColumn']").type("output"); + + const model = tf.sequential({ + layers: [ + tf.layers.conv2d({ + inputShape: [32, 32, 3], + kernelSize: 3, + filters: 16, + activation: "relu", + }), + ], + }); + model.compile({ loss: "hinge", optimizer: "sgd" }); + cy.wrap(getArtifacts(model)) + .then((artifacts) => JSON.stringify(artifacts)) + .then((json) => new TextEncoder().encode(json)) + .then((raw) => + cy.get("input[type='file']").selectFile(new Uint8Array(raw), { + force: true, // input is hidden + }), + ); + cy.get("input[name='model.loss']").type("hinge"); + cy.get("input[name='model.optimizer']").type("sgd"); + + cy.get("button[type='submit']").click(); + + cy.wait("@posted") + .its("request.body") + .then((body) => JSON.parse(body)) + .its("task") + .should("deep.equal", { + id: "id", + dataType: "tabular", + displayInformation: { + title: "simple", + summary: { + preview: "preview", + overview: "overview", + }, + dataExample: [{ name: "name", data: "data" }], + }, + trainingInformation: { + scheme: "federated", + epochs: 10, + batchSize: 5, + roundDuration: 2, + validationSplit: 0, + minNbOfParticipants: 2, + inputColumns: ["input"], + outputColumn: "output", + tensorBackend: "tfjs", + }, + } satisfies Task<"tabular">); +}); + +async function getArtifacts( + model: tf.LayersModel, +): Promise { + let resolveArtifacts: (_: tf.io.ModelArtifacts) => void; + const ret = new Promise((resolve) => { + resolveArtifacts = resolve; + }); + + await model.save( + { + save: (artifacts) => { + resolveArtifacts(artifacts); + return Promise.resolve({ + modelArtifactsInfo: { + dateSaved: new Date(), + modelTopologyType: "JSON", + }, + }); + }, + }, + { includeOptimizer: true }, + ); + + return { + ...(await ret), + weightsManifest: [], // required by tf.loadLayersModel + }; +} diff --git a/webapp/cypress/support/e2e.ts b/webapp/cypress/support/e2e.ts index 309caa30a..41b05b8d9 100644 --- a/webapp/cypress/support/e2e.ts +++ b/webapp/cypress/support/e2e.ts @@ -84,3 +84,5 @@ beforeEach(() => .getDirectory() .then((root) => root.removeEntry("models", { recursive: true })), ); + +before(() => (localStorage.debug = "discojs*,webapp*")); diff --git a/webapp/package.json b/webapp/package.json index 342144ea7..a1030b20e 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -14,13 +14,13 @@ "@epfml/discojs": "*", "@epfml/discojs-web": "*", "@msgpack/msgpack": "^3.0.0-beta2", + "@vee-validate/zod": "4", "apexcharts": "3", "cypress": "13", "d3": "7", "immutable": "4", "pinia": "<2.2.3", "pinia-plugin-persistedstate-2": "2", - "vee-validate": "4", "vue": "3", "vue-router": "4", "vue-tippy": "6", diff --git a/webapp/src/components/task_creation_form/FormField.vue b/webapp/src/components/task_creation_form/FormField.vue new file mode 100644 index 000000000..97da19aa8 --- /dev/null +++ b/webapp/src/components/task_creation_form/FormField.vue @@ -0,0 +1,23 @@ + + + diff --git a/webapp/src/components/task_creation_form/FormLabel.vue b/webapp/src/components/task_creation_form/FormLabel.vue new file mode 100644 index 000000000..fb63a3f34 --- /dev/null +++ b/webapp/src/components/task_creation_form/FormLabel.vue @@ -0,0 +1,32 @@ + + + + + diff --git a/webapp/src/components/task_creation_form/TaskForm.vue b/webapp/src/components/task_creation_form/TaskForm.vue index 8e184bd70..57f67aaa7 100644 --- a/webapp/src/components/task_creation_form/TaskForm.vue +++ b/webapp/src/components/task_creation_form/TaskForm.vue @@ -1,328 +1,589 @@ + + diff --git a/webapp/src/components/task_creation_form/containers/ArrayContainer.vue b/webapp/src/components/task_creation_form/containers/ArrayContainer.vue deleted file mode 100644 index 79bb2b795..000000000 --- a/webapp/src/components/task_creation_form/containers/ArrayContainer.vue +++ /dev/null @@ -1,145 +0,0 @@ - - - diff --git a/webapp/src/components/task_creation_form/containers/CheckboxContainer.vue b/webapp/src/components/task_creation_form/containers/CheckboxContainer.vue deleted file mode 100644 index 196c13044..000000000 --- a/webapp/src/components/task_creation_form/containers/CheckboxContainer.vue +++ /dev/null @@ -1,45 +0,0 @@ - - - diff --git a/webapp/src/components/task_creation_form/containers/FileContainer.vue b/webapp/src/components/task_creation_form/containers/FileContainer.vue deleted file mode 100644 index ae9e65965..000000000 --- a/webapp/src/components/task_creation_form/containers/FileContainer.vue +++ /dev/null @@ -1,166 +0,0 @@ - - - diff --git a/webapp/src/components/task_creation_form/containers/FloatContainer.vue b/webapp/src/components/task_creation_form/containers/FloatContainer.vue deleted file mode 100644 index ce214ca61..000000000 --- a/webapp/src/components/task_creation_form/containers/FloatContainer.vue +++ /dev/null @@ -1,13 +0,0 @@ - - - diff --git a/webapp/src/components/task_creation_form/containers/NumberContainer.vue b/webapp/src/components/task_creation_form/containers/NumberContainer.vue deleted file mode 100644 index 3d21d87dd..000000000 --- a/webapp/src/components/task_creation_form/containers/NumberContainer.vue +++ /dev/null @@ -1,16 +0,0 @@ - - - diff --git a/webapp/src/components/task_creation_form/containers/ObjectArrayContainer.vue b/webapp/src/components/task_creation_form/containers/ObjectArrayContainer.vue deleted file mode 100644 index 0e0febb90..000000000 --- a/webapp/src/components/task_creation_form/containers/ObjectArrayContainer.vue +++ /dev/null @@ -1,174 +0,0 @@ - - - diff --git a/webapp/src/components/task_creation_form/containers/SelectContainer.vue b/webapp/src/components/task_creation_form/containers/SelectContainer.vue deleted file mode 100644 index 44784dad5..000000000 --- a/webapp/src/components/task_creation_form/containers/SelectContainer.vue +++ /dev/null @@ -1,38 +0,0 @@ - - - diff --git a/webapp/src/components/task_creation_form/containers/TextContainer.vue b/webapp/src/components/task_creation_form/containers/TextContainer.vue deleted file mode 100644 index f41874011..000000000 --- a/webapp/src/components/task_creation_form/containers/TextContainer.vue +++ /dev/null @@ -1,27 +0,0 @@ - - - diff --git a/webapp/src/components/task_creation_form/simple/CustomField.vue b/webapp/src/components/task_creation_form/simple/CustomField.vue deleted file mode 100644 index a2fd46199..000000000 --- a/webapp/src/components/task_creation_form/simple/CustomField.vue +++ /dev/null @@ -1,44 +0,0 @@ - - - diff --git a/webapp/src/components/training/Description.vue b/webapp/src/components/training/Description.vue index 35ee5f40e..1ab1920ea 100644 --- a/webapp/src/components/training/Description.vue +++ b/webapp/src/components/training/Description.vue @@ -1,7 +1,9 @@