diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 0e79fa39e..4db8112b4 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -104,10 +104,10 @@ pnpm -F webapp test The webapp tests rely on `cypress` and the test suite is located in the `webapp/cypress` folder. -Note that you can also run test interactively in the browser of your choice. To do so, run +Note that you can also run test interactively in the browser of your choice. To do so, run from the `webapp` folder ``` -VITE_SERVER_URL=http://server npx -w webapp start-server-and-test start http://localhost:8081 'cypress open --e2e' +pnpm run test:e2e-interactive ``` which should open the Cypress UI and let you choose the browser you wand to use and which tests to run. More information on [the Cypress docs](https://docs.cypress.io/app/get-started/open-the-app). diff --git a/webapp/cypress/e2e/datasetInput.cy.ts b/webapp/cypress/e2e/datasetInput.cy.ts index d26a83eed..50aeafbe6 100644 --- a/webapp/cypress/e2e/datasetInput.cy.ts +++ b/webapp/cypress/e2e/datasetInput.cy.ts @@ -39,11 +39,14 @@ describe("image dataset input by group", () => { goToDatasetInputStep(); cy.get("button").contains("group").click(); - cy.contains("select images").selectFile([ - { fileName: "first.png", contents: new Uint8Array() }, - { fileName: "second.png", contents: new Uint8Array() }, - { fileName: "third.png", contents: new Uint8Array() }, - ]); + cy.contains("Drop images"); + cy.get('[data-testid="select-image-button"]') + .first() + .selectFile([ + { fileName: "first.png", contents: new Uint8Array() }, + { fileName: "second.png", contents: new Uint8Array() }, + { fileName: "third.png", contents: new Uint8Array() }, + ]); cy.contains("Number of selected files: 3").should("exist"); }); @@ -61,21 +64,27 @@ describe("image dataset input by csv", () => { goToDatasetInputStep(); cy.get("button").contains("csv").click(); - cy.contains("select CSV").selectFile({ - fileName: "csv", - contents: new TextEncoder().encode( - "filename,label\n" + - "first,first\n" + - "second,second\n" + - "third,third\n", - ), - }); + cy.contains("Drop CSV"); + cy.get('[data-testid="select-tabular-button"]') + .first() + .selectFile({ + fileName: "csv", + contents: new TextEncoder().encode( + "filename,label\n" + + "first,first\n" + + "second,second\n" + + "third,third\n", + ), + }); - cy.contains("select images").selectFile([ - { fileName: "first.png", contents: new Uint8Array() }, - { fileName: "second.png", contents: new Uint8Array() }, - { fileName: "third.png", contents: new Uint8Array() }, - ]); + cy.contains("Drop images"); + cy.get('[data-testid="select-image-button"]') + .first() + .selectFile([ + { fileName: "first.png", contents: new Uint8Array() }, + { fileName: "second.png", contents: new Uint8Array() }, + { fileName: "third.png", contents: new Uint8Array() }, + ]); cy.contains("Number of selected files: 3").should("exist"); }); @@ -91,10 +100,13 @@ describe("tabular dataset input", () => { ); goToDatasetInputStep(); - cy.contains("select CSV").selectFile({ - fileName: "filename", - contents: new TextEncoder().encode("a,b,c\n1,2,3\n"), - }); + cy.contains("Drop CSV"); + cy.get('[data-testid="select-tabular-button"]') + .first() + .selectFile({ + fileName: "filename", + contents: new TextEncoder().encode("a,b,c\n1,2,3\n"), + }); cy.contains("filename").should("exist"); }); diff --git a/webapp/cypress/e2e/testing.cy.ts b/webapp/cypress/e2e/testing.cy.ts index 6c1862c74..b001c0cc4 100644 --- a/webapp/cypress/e2e/testing.cy.ts +++ b/webapp/cypress/e2e/testing.cy.ts @@ -9,14 +9,15 @@ it("can test titanic", () => { cy.contains("button", "download").click(); cy.contains("button", "test").click(); - cy.contains("label", "select CSV").selectFile( - "../datasets/titanic_train.csv", - ); + cy.contains("Drop CSV"); + cy.get('[data-testid="select-tabular-button"]') + .first() + .selectFile("../datasets/titanic_train.csv"); cy.contains("button", "next").click(); cy.contains("Validate your model") - .parent() - .parent() + .parents() + .eq(1) .contains("button", "test") .click(); @@ -31,13 +32,13 @@ it("can test lus_covid", () => { cy.contains("button", "test").click(); cy.task("readdir", "../datasets/lus_covid/COVID+/").then((files) => - cy.contains("label", "select images").selectFile(files), + cy.get('[data-testid="select-image-button"]').first().selectFile(files), ); cy.contains("button", "next").click(); cy.contains("Validate your model") - .parent() - .parent() + .parents() + .eq(1) .contains("button", "test") .click(); @@ -51,14 +52,14 @@ it("can start and stop testing of wikitext", () => { cy.contains("button", "download").click(); cy.contains("button", "test").click(); - cy.contains("label", "select text").selectFile( - "../datasets/wikitext/wiki.test.tokens", - ); + cy.get('[data-testid="select-text-button"]') + .first() + .selectFile("../datasets/wikitext/wiki.test.tokens"); cy.contains("button", "next").click(); cy.contains("Validate your model") - .parent() - .parent() + .parents() + .eq(1) .contains("button", "test") .click(); cy.contains("button", "stop testing").click(); diff --git a/webapp/cypress/e2e/training.cy.ts b/webapp/cypress/e2e/training.cy.ts index 35d72e3e0..54d517422 100644 --- a/webapp/cypress/e2e/training.cy.ts +++ b/webapp/cypress/e2e/training.cy.ts @@ -30,9 +30,10 @@ describe("training page", () => { goToTaskOverview(); cy.contains("button", "next").click(); - cy.contains("label", "select CSV").selectFile( - "../datasets/titanic_train.csv", - ); + cy.contains("Drop CSV"); + cy.get('[data-testid="select-tabular-button"]') + .first() + .selectFile("../datasets/titanic_train.csv"); cy.contains("button", "next").click(); cy.contains("button", "locally").click(); @@ -61,7 +62,8 @@ describe("training page", () => { cy .contains("h4", "COVID-Positive") .parents() - .contains("select images") + .eq(1) + .find('[data-testid="select-image-button"]') .selectFile(files), ); cy.task("readdir", "../datasets/lus_covid/COVID-/").then( @@ -69,7 +71,8 @@ describe("training page", () => { cy .contains("h4", "COVID-Negative") .parents() - .contains("select images") + .eq(1) + .find('[data-testid="select-image-button"]') .selectFile(files), ); cy.contains("button", "next").click(); diff --git a/webapp/package.json b/webapp/package.json index 2945ad5c8..f62bacee9 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -7,7 +7,8 @@ "build": "vue-tsc --build && vite build", "test": "pnpm run test:unit && pnpm run test:e2e", "test:unit": "vitest --run", - "test:e2e": "VITE_SERVER_URL=http://server start-server-and-test \"pnpm start\" http://localhost:1351 'pnpm exec cypress run --e2e'" + "test:e2e": "VITE_SERVER_URL=http://server start-server-and-test \"pnpm start\" http://localhost:1351 'pnpm exec cypress run --e2e'", + "test:e2e-interactive": "VITE_SERVER_URL=http://server start-server-and-test \"pnpm start\" http://localhost:1351 'pnpm exec cypress open --e2e'" }, "dependencies": { "@epfml/discojs": "workspace:", diff --git a/webapp/src/components/dataset_input/DatasetInput.vue b/webapp/src/components/dataset_input/DatasetInput.vue index 1527493c0..efe213c61 100644 --- a/webapp/src/components/dataset_input/DatasetInput.vue +++ b/webapp/src/components/dataset_input/DatasetInput.vue @@ -3,17 +3,6 @@ -
- Disco needs to know where your data is located on your device in order to - read it (not upload it!) and train models. Models are trained on - your local data and are periodically aggregated with other users' models - if any. -
-
- - Your data stays on your device and data is never uploaded anywhere. - -
diff --git a/webapp/src/components/dataset_input/FileSelection.vue b/webapp/src/components/dataset_input/FileSelection.vue index 0aeeb04b7..03a55b9cf 100644 --- a/webapp/src/components/dataset_input/FileSelection.vue +++ b/webapp/src/components/dataset_input/FileSelection.vue @@ -8,6 +8,18 @@ >
+
+ + + Stays on your device + +

- Drag and drop the {{ fileType }} or + Drop {{ fileType }} here or

-
- -
- - - -
- Number of selected files: - {{ files.size }} - {{ files.first()?.name ?? "none" }} + + + Number of selected files: + {{ files.size }} + +
+
+ {{ name }}
@@ -101,11 +103,11 @@ const props = withDefaults( defineProps<{ type: "image" | "json" | "tabular" | "text"; multiple?: boolean; // accept one or multiple files - lockIcon?: boolean; + noUpload?: boolean; }>(), { multiple: false, - lockIcon: false, + noUpload: false, }, ); @@ -139,6 +141,7 @@ const fileType = computed(() => { return `${name}${props.multiple ? "s" : ""}`; }); + const acceptFilter = computed(() => { switch (props.type) { case "image": @@ -156,6 +159,16 @@ const acceptFilter = computed(() => { throw new TypeError("invalid value"); }); +const fileNamesDisplay = computed(() => { + if (!files.value) return ""; + const arr = files.value.map((f) => f.name); + if (arr.size < 5) { + return arr; + } else { + return [...arr.slice(0, 3), "...", arr.last()]; + } +}); + // we use an event counter to test whether the user is dragging a file over the field // because events are triggered multiple times when hovering of children elements (such as button or text) const dragEventCount = ref(0); diff --git a/webapp/src/components/dataset_input/ImageDatasetInput.vue b/webapp/src/components/dataset_input/ImageDatasetInput.vue index 2ae99f9e6..df9dd6a75 100644 --- a/webapp/src/components/dataset_input/ImageDatasetInput.vue +++ b/webapp/src/components/dataset_input/ImageDatasetInput.vue @@ -1,6 +1,6 @@ diff --git a/webapp/src/components/dataset_input/LabeledImageDatasetInput/ByCSV.vue b/webapp/src/components/dataset_input/LabeledImageDatasetInput/ByCSV.vue index cfed2c998..13c546dd2 100644 --- a/webapp/src/components/dataset_input/LabeledImageDatasetInput/ByCSV.vue +++ b/webapp/src/components/dataset_input/LabeledImageDatasetInput/ByCSV.vue @@ -6,7 +6,7 @@ Connect the CSV file containing a mapping between images and labels - + The CSV file must contain a header with only two columns (filename, label) - + Drag and drop or browse for the images referenced in the connected CSV file.
- {{ browsingTip }}
@@ -64,7 +63,6 @@ import { useToaster } from "@/composables/toaster"; import FileSelection from "../FileSelection.vue"; import type { NamedLabeledImageDataset } from "../types.js"; -import { browsingTip } from "./strings.js"; const toaster = useToaster(); diff --git a/webapp/src/components/dataset_input/LabeledImageDatasetInput/ByGroup.vue b/webapp/src/components/dataset_input/LabeledImageDatasetInput/ByGroup.vue index d0d81d6fb..f592b9385 100644 --- a/webapp/src/components/dataset_input/LabeledImageDatasetInput/ByGroup.vue +++ b/webapp/src/components/dataset_input/LabeledImageDatasetInput/ByGroup.vue @@ -2,11 +2,9 @@
- + - - {{ browsingTip }} - +
@@ -25,7 +23,6 @@ import IconCard from "@/components/containers/IconCard.vue"; import FileSelection from "../FileSelection.vue"; import type { NamedLabeledImageDataset } from "../types.js"; -import { browsingTip } from "./strings.js"; const props = defineProps<{ labels: Set; diff --git a/webapp/src/components/dataset_input/LabeledImageDatasetInput/strings.ts b/webapp/src/components/dataset_input/LabeledImageDatasetInput/strings.ts deleted file mode 100644 index ebb4fbd1f..000000000 --- a/webapp/src/components/dataset_input/LabeledImageDatasetInput/strings.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const browsingTip = - "Tip: when browsing files you can use the keyboard shortcut Ctrl + A to select all images."; diff --git a/webapp/src/components/dataset_input/TabularDatasetInput.vue b/webapp/src/components/dataset_input/TabularDatasetInput.vue index 389330e13..379ff527d 100644 --- a/webapp/src/components/dataset_input/TabularDatasetInput.vue +++ b/webapp/src/components/dataset_input/TabularDatasetInput.vue @@ -1,7 +1,7 @@ diff --git a/webapp/src/components/dataset_input/TextDatasetInput.vue b/webapp/src/components/dataset_input/TextDatasetInput.vue index dd846021d..8638b3b64 100644 --- a/webapp/src/components/dataset_input/TextDatasetInput.vue +++ b/webapp/src/components/dataset_input/TextDatasetInput.vue @@ -1,7 +1,7 @@