Parcourir la source

Merge branch 'release/v0.2.8'

rigwild il y a 4 ans
Parent
commit
d3c84e8252

+ 1 - 0
DOCUMENTATION/00-home.md

@@ -8,3 +8,4 @@
 | 02 | [npm scripts](./02-npm-scripts.md) | Details the available npm scripts |
 | 03 | [npm dependencies](./03-npm-dependencies.md) | Details the npm dependencies |
 | 04 | [Create an experiment](./04-create-an-experiment.md) | Details how to fully create an experiment |
+| 05 | [Experiment mixins API](./05-experiment-mixins-api.md) | Details available data and methods in the experiments mixins |

+ 4 - 9
DOCUMENTATION/01-application-file-tree.md

@@ -51,7 +51,7 @@ The following file tree represents the full application.
 │   ├── favicon.ico                  || Website favicon
 │   └── index.html                   || 
 ├── README.md                        || 
-├── server                           || Server, database and WebSockets server code
+├── server                           || Server and database code
 │   └── ...                          || 
 ├── src                              || Front-end code
 │   └── ...                          || 
@@ -113,14 +113,12 @@ The `/logs` directory contains all the logs of the application.
     ├── extractsRemoverService.error.log || Extracts remover service errors
     ├── extractsRemoverService.log       || Extracts remover service logs
     ├── server.combined.log              || Server combined logs
-    ├── server.error.log                 || Server errors
-    ├── ws.error.log                     || WebSockets server errors
-    └── ws.log                           || WebSockets server logs
+    └── server.error.log                 || Server errors
 ```
 
 ## `/server` directory
 
-The [`/server`](../server) directory contains the Back-end: server, database and WebSockets server code.
+The [`/server`](../server) directory contains the Back-end: server and database code.
 
 ```console
 └── server                    || 
@@ -131,14 +129,11 @@ The [`/server`](../server) directory contains the Back-end: server, database and
     │   └── models            || Database models (Schemas, types)
     │       └── Data.js       || 
     ├── functions.js          || Common utils for the server
-    ├── index.js              || Server module: links server, database and WebSockets server
+    ├── index.js              || Server module: links server and database
     ├── routes                || All API routes
     │   ├── index.js          || Router module to inject all routes
     │   ├── getImage.js       || 
     │   └── ...               || 
-    ├── webSocket             || WebSockets server
-    │   ├── index.js          || Websockets server module: starts server
-    │   └── messageHandler.js || Handle WebSockets messages (JSON)
     └── winston.config.js     || All the server's logging configuration
 ```
 

+ 2 - 2
DOCUMENTATION/02-npm-scripts.md

@@ -2,7 +2,7 @@
 
 This wiki page details the available npm scripts. These scripts are located in the `scripts` category of [`/package.json`](../package.json).
 
-Keep in mind that this documentation uses the `yarn` command but using `npm` instead will have the same effect.
+Keep in mind that this documentation uses the `yarn` command but **using `npm` instead will have the same effect**.
 
 ## Summary
 
@@ -64,7 +64,7 @@ yarn doc
 ```
 
 ## `test`
-Run the API automated tests. It will check for routes/WebSockets server/database errors.
+Run the API automated tests. It will check for routes and database errors.
 
 These tests are automatically ran when using Docker.
 ```sh

+ 3 - 5
DOCUMENTATION/03-npm-dependencies.md

@@ -18,7 +18,7 @@ These dependencies are mostly used by the server.
 | `body-parser` | [npm](https://www.npmjs.com/package/body-parser) | Parse HTTP request body |
 | `compression` | [npm](https://www.npmjs.com/package/compression) | Turn HTTP server's gzip compression on |
 | `core-js` | [npm](https://www.npmjs.com/package/core-js) | Dependency of Babel (in `@vue/cli-service`) |
-| `cors` | [npm](https://www.npmjs.com/package/cors) | Turn Cross-Origin Resource Sharing (CORS) HTTP header on |
+| `cors` | [npm](https://www.npmjs.com/package/cors) | Turn [Cross-Origin Resource Sharing (CORS)](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) HTTP header on |
 | `cron` | [npm](https://www.npmjs.com/package/cron) | Cron integrated in Node.js, used for the extracts remover service |
 | `esm` | [npm](https://www.npmjs.com/package/esm) | Node.js JavaScript module loader. Used for the server |
 | `express` | [npm](https://www.npmjs.com/package/express) | API framework |
@@ -29,12 +29,11 @@ These dependencies are mostly used by the server.
 | `sharp` | [npm](https://www.npmjs.com/package/sharp) | Node.js image processing library |
 | `ua-parser-js` | [npm](https://www.npmjs.com/package/ua-parser-js) | Parse User Agent strings |
 | `winston` | [npm](https://www.npmjs.com/package/winston) | Server logger |
-| `ws` | [npm](https://www.npmjs.com/package/ws) | Node.js WebSockets server |
 
 ## Development dependencies
-These dependencies are here for developers only. They contain the web application setup, optimized builders and API automated tests. It also contains API documentation generator and `ESLint` with its plugins.
+These dependencies are here for **developers only**. They contain the web application setup, optimized builders and API automated tests. It also contains API documentation generator and `ESLint` with its plugins.
 
-When the web application files are builded, `Vue.js` and its plugins are not necessary. In fact, the build output will contain portable HTML, CSS and JavaScript.
+When the web application files are builded, `Vue.js` and its plugins are not necessary. In fact, **the build output will contain portable HTML, CSS and JavaScript**.
 
 | Name | Link | Description |
 | --- | --- | --- |
@@ -55,7 +54,6 @@ When the web application files are builded, `Vue.js` and its plugins are not nec
 | `supertest` | [npm](https://www.npmjs.com/package/supertest) | HTTP requests test library |
 | `vue` | [npm](https://www.npmjs.com/package/vue) | `Vue.js` JavaScript framework |
 | `vue-cli-plugin-vuetify` | [npm](https://www.npmjs.com/package/vue-cli-plugin-vuetify) | Just a plugin to use `Vuetify` in `Vue.js` |
-| `vue-native-websocket` | [npm](https://www.npmjs.com/package/vue-native-websocket) | WebSockets client library, plugged to `Vuex` |
 | `vue-router` | [npm](https://www.npmjs.com/package/vue-router) | Web application routing system |
 | `vue-template-compiler` | [npm](https://www.npmjs.com/package/vue-template-compiler) | Build `Vue.js` `.vue` files |
 | `vuetify` | [npm](https://www.npmjs.com/package/vuetify) | `Vue.js` specific CSS framework |

+ 42 - 72
DOCUMENTATION/04-create-an-experiment.md

@@ -7,17 +7,11 @@ This wiki page details how to fully create an experiment.
  - [Prerequisites](#prerequisites)
  - [Experiment file tree](#experiment-file-tree)
  - [Experiment creation](#experiment-creation)
-   - [Experiment initialization](#experiment-initialization)
-   - [Experiment configuration](#experiment-configuration)
- - [Experiment mixins API](#experiment-mixins-api)
-   - [ExperimentBase](#experimentbase)
-   - [ExperimentBaseAreSameImages](#experimentbasearesameimages)
-   - [ExperimentBaseExtracts](#experimentbaseextracts)
 
 ---
 
 ## Prerequisites
-To learn how to create an experiment, you first need to know how [Vue.js](https://vuejs.org/v2/guide/) works.
+To learn how to create an experiment, **you first need to know how [Vue.js](https://vuejs.org/v2/guide/) works**.
 
 If you want to use the same style as the application, here is the [Vuetify documentation](https://vuetifyjs.com/en/getting-started/quick-start).
 
@@ -42,99 +36,75 @@ The complete `/src` directory file tree can be found here: [Application file tre
     │   └── index.js                                 || Main routing logic
     └── views                                        || Application views (pages)
         └── Experiments                              || Experiments
+            ├── _template_.vue                       || Experiment minimal template
             ├── AreSameImagesRandom.vue              || Two random quality images (versus)
-            ├── AreSameImagesReferenceOneExtract.vue || Two reference images, one has a random quality extract (versus)
             ├── AreSameImagesReference.vue           || One random quality image, one reference image (versus)
-            └── MatchExtractsWithReference.vue       || Lowest quality extracts, match them to reference image
+            ├── AreSameImagesReferenceOneExtract.vue || Two reference images, one has a random quality extract (versus)
+            ├── IsImageCorrect.vue                   || An image cut in half horizontally, tell if it looks normal
+            ├── IsImageCorrectOneExtract.vue         || An image cut in half horizontally, tell if it looks normal
+            ├── MatchExtractsWithReference.vue       || Lowest quality extracts, match them to reference image
+            └── PercentQualityRandom.vue             || Random quality image, tell how high the quality is
 ```
 
 ## Experiment creation
-
-### Experiment initialization
-When creating an experiment
+Follow the following steps to create a new experiment.
 
 ##### 1. Choose an experiment name, it must be in [UpperCamelCase](https://en.wikipedia.org/wiki/Camel_case)
+
 ##### 2. Create a new `<YourExperimentName>.vue` file in the [`/src/views/Experiments`](../src/views/Experiments) directory
 
 ##### 3. Link your experiment to the [Vue.js routing system](https://router.vuejs.org/)
+
 Create a new entry in the [`/src/router/experiments.js`](../src/router/experiments.js) file following this structure:
 
 ```js
 {
-  path: '/experiments/YourExperimentName/:sceneName',
-  name: 'YourExperimentName',
-  component: () => import('@/views/Experiments/YourExperimentName'),
+  path: '/experiments/<YourExperimentName>/:sceneName',
+  name: '<YourExperimentName>',
+  component: () => import('@/views/Experiments/<YourExperimentName>'),
   props: true,
   meta: {
-    fullName: 'The visible name of your experiment'
+    fullName: '<The visible name of your experiment>'
   }
 }
 ```
 
 ##### 4. Choose or create the experiment mixin
-`TODO: finish this part`
-
-An experiment mixin is a subset of functions or data that can be used in a type of experiment.
-
-
-##### 5. Actual experiment creation
-Copy the minimal experiment template [`/src/views/Experiments/_template.vue`](../src/views/Experiments/_template.vue) content to your new experiment.
-
-You are all set! You can now develop your own experiment.
-See [Experiment mixins API](#experiment-mixins-api) to check data and methods available from your selected parent mixins using the `this` keyword in any Vue methods/templates.
-
-### Experiment configuration
-`TODO: finish this part`
 
-## Experiment mixins API
-### ExperimentBase
-File: [`/src/mixins/ExperimentBase.vue`](../src/mixins/ExperimentBase.vue)
+An experiment mixin is a subset of data or methods that can be used in a type of experiment. The [`ExperimentBase`](./05-experiment-mixins-api.md#experimentbase) mixin is the bare minimum to include in your experiment, it is mandatory.
 
-Extends: []
+Keep in mind that if the mixin you are using already extends [`ExperimentBase`](./05-experiment-mixins-api.md#experimentbase), you do not need to import it a second time.
 
-This mixin is mandatory. If you want to create a new mixin, use this mixin in your mixin.
+See [Experiment mixins API](./05-experiment-mixins-api.md) to check data and methods available to you. You need to use the `this` keyword in any Vue methods/templates to use these features.
 
-When drawing the tree from top mixins to the experiment itself, `ExperimentBase` must be the very top parent.
+##### 5. Create the experiment configuration
 
-| Data | Type | Default | Description |
-| ---- | ---- | ------- | ----------- |
-| `loadingMessage` | `String` | `null` | Message to display while loading the application. `null` = not loading
-| `loadingErrorMessage` |  `String` | `null` | Message to display when the application failed to load. `null` = no loading errror
-| `qualities` | `Number[]` | `null` | List of qualities for the current scene
+Create a new entry in the `experiments` object of the [`/experimentConfig.default.js`](../experimentConfig.default.js) file following this structure:
 
-| Method | Return type | Description |
-| ------ | ----------- | ----------- |
-| `loadProgress()` | `void` | Load progress from store into local state |
-| `saveProgress()` | `void` | Save progress from local state into store |
-| `loadConfig()` | `void` | Load a config object into the local state |
-| `finishExperiment()` | `void` | Finish an experiment, sending full data to the server . Don't forget to surcharge this function when using this mixin to add more data! |
-| `getQualitiesList()` | `Promise<void>` | Load qualities list from the API |
-| `getImage(quality: Number)` | `Promise<Object>` | Load an image from the API |
-| `sendMessage({ msgId: String, msg: Object })` | `void` | Send a message using WebSockets, your message will be stored in the database. Message IDs are listed in [`/config.messageId.js`](../config.messageId.js) |
-| `setExperimentFinished([done=true: Boolean])` | `void` | Set the current experiment as finished in the browser's cache |
-
-### ExperimentBaseAreSameImages
-File: [`/src/mixins/ExperimentBaseAreSameImages.vue`](../src/mixins/ExperimentBaseAreSameImages.vue)
-
-Extends: [[ExperimentBase](#experimentbase)]
-
-| Data | Type | Default | Description |
-| ---- | ---- | ------- | ----------- |
-
-| Method | Return type | Description |
-| ------ | ----------- | ----------- |
-
-`TODO: finish this part`
-
-### ExperimentBaseExtracts
-File: [`/src/mixins/ExperimentBaseExtracts.vue`](../src/mixins/ExperimentBaseExtracts.vue)
+```js
+YourExperimentName: {
+  mixins: [mixins.ExperimentBase], // Your used mixins
+  defaultConfig: {                 // Configuration used for all scenes
+    // Data to apply to any scenes of the experiment
+    // maxTestCount: 5
+  },
+  scenesConfig: {                  // Scene-specific configuration
+    // Data to apply to only some scenes
+    // bathroom: {
+    //   maxTestCount: 5
+    // }
+  },
+  availableScenes: {
+    whitelist: null,               // Whitelist available scenes (String[]). If null, takes all scenes
+    blacklist: null                // Remove scenes (String[])
+  }
+}
+```
 
-Extends: [[ExperimentBase](#experimentbase)]
+You can add any data in `defaultConfig` and `scenesConfig` properties, It will get merged with your experiment data (the configuration file has more priority, if you have an experiment data with the same key, it will replace it).
 
-| Data | Type | Default | Description |
-| ---- | ---- | ------- | ----------- |
+##### 6. Actual experiment creation
 
-| Method | Return type | Description |
-| ------ | ----------- | ----------- |
+Copy the minimal experiment template [`/src/views/Experiments/_template.vue`](../src/views/Experiments/_template.vue) content to your new experiment.
 
-`TODO: finish this part`
+You are all set! You can now develop your own experiment.

+ 78 - 0
DOCUMENTATION/05-experiment-mixins-api.md

@@ -0,0 +1,78 @@
+# Experiment mixins API
+
+This wiki page details available data and methods in the experiments mixins.
+
+## Summary
+
+ - [ExperimentBase](#experimentbase)
+ - [ExperimentBaseAreSameImages](#experimentbasearesameimages)
+ - [ExperimentBaseExtracts](#experimentbaseextracts)
+
+---
+
+## ExperimentBase
+File: [`/src/mixins/ExperimentBase.vue`](../src/mixins/ExperimentBase.vue)
+
+Extends: None
+
+**This mixin is mandatory**. If you want to create a new mixin, **use this mixin in your mixin**.
+
+When drawing the tree from top mixins to the experiment itself, **`ExperimentBase` must be the very top parent**.
+
+| Data | Type | Default | Description |
+| ---- | ---- | ------- | ----------- |
+| `loadingMessage` | `String` | `null` | Message to display while loading the application. `null` = not loading
+| `loadingErrorMessage` |  `String` | `null` | Message to display when the application failed to load. `null` = no loading errror
+| `qualities` | `Number[]` | `null` | List of qualities for the current scene
+
+| Method | Return type | Description |
+| ------ | ----------- | ----------- |
+| `loadProgress()` | `void` | Load progress from store into local state |
+| `saveProgress()` | `void` | Save progress from local state into store |
+| `loadConfig()` | `void` | Load a config object into the local state |
+| `finishExperiment()` | `void` | Finish an experiment, sending full data to the server . Don't forget to surcharge this function when using this mixin to add more data! |
+| `getQualitiesList()` | `Promise<void>` | Load qualities list from the API |
+| `getImage(quality: Number)` | `Promise<Object>` | Load an image from the API |
+| `sendMessage({ msgId: String, msg: Object })` | `void` | Send a message using `/experimentCollect` API route, your message will be stored in the database. Message IDs are listed in [`/config.messageId.js`](../config.messageId.js) |
+| `setExperimentFinished([done=true: Boolean])` | `void` | Set the current experiment as finished in the browser's cache |
+
+## ExperimentBaseAreSameImages
+File: [`/src/mixins/ExperimentBaseAreSameImages.vue`](../src/mixins/ExperimentBaseAreSameImages.vue)
+
+Extends: [[`ExperimentBase`](#experimentbase)]
+
+| Data | Type | Default | Description |
+| ---- | ---- | ------- | ----------- |
+| `maxTestCount` | `Number` | `null` | The total number of tests to pass for this scene |
+| `testCount` | `Number` | `1` | Passed tests count |
+| `image1` | `Object` | `null` | One of the two images |
+| `image2` | `Object` | `null` | One of the two images |
+
+| Method | Return type | Description |
+| ------ | ----------- | ----------- |
+| `scrollToChoiceButtons()` | `void` | Scroll the page to the answer buttons |
+| `getTest(leftQuality: Number, rightQuality: Number)` | `Promise<Object>` | Load a test using provided qualities |
+| `areTheSameAction(areTheSame: Boolean, getTestFn: Function, additionalData: any)` | `Promise<Object>` | Answer to the versus. `getTestFn` shoud be an async function that returns an object matching `{ image1: Object, image2: Object }`. additionalData is the experiment-specific content you want to send to the database  |
+| `finishExperiment()` | `void` | Finish an experiment |
+
+## ExperimentBaseExtracts
+File: [`/src/mixins/ExperimentBaseExtracts.vue`](../src/mixins/ExperimentBaseExtracts.vue)
+
+Extends: [[`ExperimentBase`](#experimentbase)]
+
+| Data | Type | Default | Description |
+| ---- | ---- | ------- | ----------- |
+| `extractConfig` | `Object` | `{ x=null: Number, y=null: Number }` | Used configuration to cut the image |
+| `extracts` | `Object[]` | `[]` | List of extracts of the cutted image |
+| `extractsInfos` | `Object` | `null` | Informations on the cutted image |
+| `showHoverBorder` | `Boolean` | `null` | Should the extracts be hoverable (white rectangle around the extract) |
+| `lockConfig` | `Boolean` | `null` | Should the extract configuration be editable |
+
+| Method | Return type | Description |
+| ------ | ----------- | ----------- |
+| `getExtracts([quality='min']: Number|String)` | `Promise<Object>` | Load image extracts using the provided quality and extract configuration |
+| `setExtractConfig(config: Object, [configuratorRef: Object])` | `Promise<Object>` | Change the extract configuration. It loads the new extracts. `config` is the new extracts configuration to use. If `configuratorRef` is provided, it will extract the extracts configurator |
+| `extractAction(event: MouseEvent, extractObj: Object)` | `Promise<void>` | Used when clicking on an extract. `event` is the mouse event (left/right click). `extractObj` is the object corresponding to the clicked extract (from the extracts array) |
+| `getExtractFullObject(extractsApiObj: Object)` | `Promise<Object>` | Takes the API image extracts response and apply more data to the extracts (like next/prec quality, zone, index or full link to image) |
+| `getClickDataObject(event, extractObj, action)` | `Object` | Calculate data on clicked event. Will get the click position on the responsive image, calculate the ratio to the real-sized image. It returns the full message sent when clicking on an extract |
+| `finishExperiment()` | `void` | Finish an experiment sending final experiment data |

+ 0 - 1
README.md

@@ -33,7 +33,6 @@ Configure more deeply the way the app works by modifying *[config.js](config.js)
 | `sceneFileNameBlackList` | `['config', 'seuilExpe', 'extracts']` | Files to ignore in scenes |
 | `deleteExtractsCronTime` | `0 3 * * *` (every day at 03:00 AM) | Cron time for extracts deletion |
 | `logger` | Logs : `logs/server.combined.log` Errors : `logs/server.error.log` | Default application logger |
-| `wsLogger` | Logs : `logs/ws.log` Errors : `logs/ws.error.log` | WebSocket logger configuration |
 | `dbLogger` | Logs : `logs/db.log` Errors : `logs/db.error.log` | Database logger configuration |
 
 ### Configure experiments

+ 2 - 2
config.js

@@ -1,7 +1,7 @@
 'use strict'
 
 import path from 'path'
-import { logger, wsLogger, dbLogger } from './server/winston.config'
+import { logger, dbLogger } from './server/winston.config'
 
 export const PRODUCTION_MODE = process.env.NODE_ENV === 'production'
 export const TEST_MODE = process.env.NODE_ENV === 'test'
@@ -39,4 +39,4 @@ export const sceneFileNameBlackList = ['config', 'seuilExpe', extractsDirName]
 export const deleteExtractsCronTime = '0 3 * * *'
 
 // Logger configurations (Default application, WebSocket, Database)
-export { logger, wsLogger, dbLogger }
+export { logger, dbLogger }

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "expe-web",
-  "version": "0.2.7",
+  "version": "0.2.8",
   "private": true,
   "scripts": {
     "server:start": "node -r esm index.js",

+ 17 - 4
server/routes/dataCollect.js

@@ -20,10 +20,13 @@ const router = express.Router()
  * @apiDescription Collect user's data
  *
  * @apiParam {String} uuid The unique user identifier
+ * @apiParam {String} userId The user ID
+ * @apiParam {String} experimentId The experiment ID
+ * @apiParam {String} uuid The unique user identifier
  * @apiParam {Object} screen Screen data, `window.screen` @see https://developer.mozilla.org/en-US/docs/Web/API/Screen
  *
  * @apiExample Usage example
- * curl -i -L -H "Content-Type: application/json" -X POST "http://diran.univ-littoral.fr/api/dataCollect" -d {"uuid":"test","screen":{"width":1920,"height":1024}}
+ * curl -i -L -H "Content-Type: application/json" -X POST "http://diran.univ-littoral.fr/api/dataCollect" -d {"uuid":"test","userId":"rigwild","experimentId":"expe-test","screen":{"width":1920,"height":1024}}
  *
  * @apiSuccessExample {string} Success response example
  * HTTP/1.1 200 OK /api/dataCollect
@@ -33,7 +36,7 @@ const router = express.Router()
  * @apiErrorExample {json} Missing parameter
  * HTTP/1.1 400 Bad Request
  * {
- *   "message": "Missing parameter(s). Required parameters : uuid, screen."
+ *   "message": "Missing parameter(s). Required parameters : uuid, userId, experimentId, screen."
  * }
  *
  * @apiError (Error 4xx) 400_[2] Invalid query parameter
@@ -43,6 +46,8 @@ const router = express.Router()
  *   "message": "Invalid body parameter(s).",
  *   "data": [
  *     "\"uuid\" must be a string.",
+ *     "\"userId\" must be a string.",
+ *     "\"experimentId\" must be a string.",
  *     "\"screen\" must be a valid object."
  *   ]
  * }
@@ -52,13 +57,19 @@ const router = express.Router()
 router.post('/', asyncMiddleware(async (req, res) => {
   // Check the request contains all the required body parameters
   const b = req.body
-  checkRequiredParameters(['uuid', 'screen'], b)
+  checkRequiredParameters(['uuid', 'screen', 'userId', 'experimentId'], b)
 
   let errorList = []
 
   if (typeof b.uuid !== 'string')
     errorList.push('"uuid" must be a string.')
 
+  if (b.userId && typeof b.userId !== 'string')
+    errorList.push('"userId" must be a string.')
+
+  if (b.experimentId && typeof b.experimentId !== 'string')
+    errorList.push('"experimentId" must be a string.')
+
   if (typeof b.screen !== 'object' || Object.keys(b.screen).length > 30)
     errorList.push('"screen" must be a valid object.')
 
@@ -76,7 +87,9 @@ router.post('/', asyncMiddleware(async (req, res) => {
       screen: b.screen,
       userAgent,
       ip: req.ip
-    }
+    },
+    userid: b.userId || null,
+    experimentId: b.experimentId || null
   }
 
   if (!TEST_MODE) await DataController.add(data)

+ 0 - 16
server/winston.config.js

@@ -18,22 +18,6 @@ export const logger = winston.createLogger({
   exitOnError: false
 })
 
-// WebSocket logger configuration
-export const wsLogger = winston.createLogger({
-  level: 'info',
-  format: winston.format.json(),
-  transports: [
-    new winston.transports.File({ filename: 'logs/ws.log' }),
-    new winston.transports.File({ filename: 'logs/ws.error.log', level: 'error' }),
-    new winston.transports.Console({
-      level: 'debug',
-      handleExceptions: true,
-      format: winston.format.json()
-    })
-  ],
-  exitOnError: false
-})
-
 // Database logger configuration
 export const dbLogger = winston.createLogger({
   level: 'info',

+ 18 - 1
src/App.vue

@@ -24,7 +24,16 @@
             </v-list-tile-content>
           </v-list-tile>
 
-          <v-list-tile @click="loadScenes">
+          <v-list-tile to="/linkGenerator" exact>
+            <v-list-tile-action>
+              <v-icon>share</v-icon>
+            </v-list-tile-action>
+            <v-list-tile-content>
+              <v-list-tile-title>Link generator</v-list-tile-title>
+            </v-list-tile-content>
+          </v-list-tile>
+
+          <v-list-tile @click="loadScenesHard">
             <v-list-tile-action>
               <v-icon>refresh</v-icon>
             </v-list-tile-action>
@@ -89,6 +98,9 @@ export default {
     },
     isHostConfigured(isConfigured) {
       if (isConfigured) this.APP_LOADER()
+    },
+    areScenesLoaded(areLoaded) {
+      if (areLoaded) this.APP_LOADER()
     }
   },
   mounted() {
@@ -108,6 +120,11 @@ export default {
       return this.load(this.loadScenesList, 'Loading scenes list...')
     },
 
+    async loadScenesHard() {
+      await this.loadScenes()
+      this.$router.go()
+    },
+
     async load(fn, loadingMessage) {
       try {
         this.loadingMessage = loadingMessage

+ 3 - 3
src/components/ResetAppButton.vue

@@ -19,7 +19,6 @@
 
         <v-divider />
 
-
         <v-card-actions>
           <v-btn color="secondary" flat @click="showDialog = false">Cancel</v-btn>
           <v-spacer />
@@ -75,7 +74,8 @@ export default {
       items: [
         { text: 'GDPR consent', value: 'gdprConsent' },
         { text: 'Host configuration and User/Experiment ID', value: 'hostConfig' },
-        { text: 'Progression', value: 'progression' }
+        { text: 'Progression', value: 'progression' },
+        { text: 'Scenes list', value: 'scenesList' }
       ]
     }
   },
@@ -93,6 +93,7 @@ export default {
     }
   },
   methods: {
+    ...mapActions(['resetApp']),
     toggle() {
       this.$nextTick(() => {
         if (this.selectAll) {
@@ -104,7 +105,6 @@ export default {
       })
     },
 
-    ...mapActions(['resetApp']),
     reset() {
       const toReset = this.selectedItems.reduce((acc, x) => {
         acc[x.value] = true

+ 1 - 1
src/mixins/ExperimentBase.vue

@@ -20,7 +20,7 @@ export default {
   },
   data() {
     return {
-      experimentName: null, // Must be redefined in parent component
+      experimentName: this.$route.name,
 
       loadingMessage: null,
       loadingErrorMessage: null,

+ 3 - 25
src/mixins/ExperimentBaseAreSameImages.vue

@@ -8,7 +8,6 @@
 import ExperimentBase from '@/mixins/ExperimentBase'
 
 import { mapGetters } from 'vuex'
-import { rand } from '@/functions'
 import { EXPERIMENT as experimentMsgId } from '@/../config.messagesId'
 
 export default {
@@ -38,27 +37,6 @@ export default {
       return { image1, image2 }
     },
 
-    // Get a test with random qualities
-    getRandomTest() {
-      return this.getTest(
-        this.qualities[rand(0, this.qualities.length - 1)],
-        this.qualities[rand(0, this.qualities.length - 1)]
-      )
-    },
-
-    // Get a test with random qualities
-    getReferenceTest() {
-      // Randomly choose which is the reference image (0 = left, 1 = right)
-      const isReferenceLeft = rand(0, 1) === 0
-      // Randomly choose a quality for the other image
-      const randomQuality = this.qualities[rand(0, this.qualities.length - 1)]
-
-      const res = [this.qualities[this.qualities.length - 1], randomQuality]
-      this.referenceImagePosition = isReferenceLeft ? 'left' : 'right'
-      const table = isReferenceLeft ? res : res.reverse()
-      return this.getTest(table[0], table[1])
-    },
-
     /** An action was triggered, load a new test and save progression
      * @param {Boolean} areTheSame Are the images the same
      * @param {Function} getTestFn Function to be called to get the next tests
@@ -81,12 +59,12 @@ export default {
         }, additionalData || {})
         this.sendMessage({ msgId: experimentMsgId.DATA, msg: obj })
 
+        // Experiment end
+        if (this.testCount > this.maxTestCount) return this.finishExperiment()
+
         const { image1, image2 } = await getTestFn()
         this.image1 = image1
         this.image2 = image2
-
-        // Experiment end
-        if (this.testCount > this.maxTestCount) return this.finishExperiment()
       }
       catch (err) {
         console.error('Failed to load new test', err)

+ 8 - 2
src/store/actions.js

@@ -13,7 +13,11 @@ export default {
     if (!state.uuid) commit('setAppUniqueId')
   },
 
-  resetApp({ commit }, { gdprConsent = false, hostConfig = false, progression = false }) {
+  async resetApp({ dispatch, commit }, { gdprConsent = false, hostConfig = false, progression = false, scenesList = false }) {
+    if (!gdprConsent && !hostConfig && scenesList) {
+      await dispatch('loadScenesList')
+      router.go()
+    }
     commit('resetApp', { gdprConsent, hostConfig, progression })
   },
 
@@ -42,7 +46,7 @@ export default {
       })
   },
 
-  setUserExperimentId({ commit }, { userId, experimentId }) {
+  setUserExperimentId({ commit }, { userId = null, experimentId = null }) {
     commit('setUserExperimentId', { userId, experimentId })
   },
 
@@ -57,6 +61,8 @@ export default {
       },
       body: JSON.stringify({
         uuid: state.uuid,
+        userId: state.userId,
+        experimentId: state.experimentId,
         screen
       })
     })

+ 5 - 5
src/store/mutations.js

@@ -42,11 +42,11 @@ export default {
     const defaultStateObj = defaultState()
     if (gdprConsent) {
       state.gdprConsent = false
-      delete state.userId
-      delete state.experimentId
-      delete state.hostConfig
-      delete state.progression
-      delete state.scenesList
+      state.hostConfig = defaultStateObj.hostConfig
+      state.userId = defaultStateObj.userId
+      state.experimentId = defaultStateObj.experimentId
+      state.progression = defaultStateObj.progression
+      state.scenesList = defaultStateObj.scenesList
       return
     }
 

+ 13 - 7
src/views/Experiments/AreSameImagesRandom.vue

@@ -58,19 +58,17 @@
 
 <script>
 import ExperimentBlock from '@/components/ExperimentBlock.vue'
-import ExperimentBaseAreTheSame from '@/mixins/ExperimentBaseAreSameImages'
+import ExperimentBaseAreSameImages from '@/mixins/ExperimentBaseAreSameImages'
+import { rand } from '@/functions'
 
 export default {
-  name: 'AreSameImagesRandom',
   components: {
     ExperimentBlock
   },
-  mixins: [ExperimentBaseAreTheSame],
+  mixins: [ExperimentBaseAreSameImages],
 
   data() {
-    return {
-      experimentName: 'AreSameImagesRandom'
-    }
+    return {}
   },
 
   async mounted() {
@@ -94,6 +92,14 @@ export default {
   },
 
   methods: {
+    // Get a test with random qualities
+    getRandomTest() {
+      return this.getTest(
+        this.qualities[rand(0, this.qualities.length - 1)],
+        this.qualities[rand(0, this.qualities.length - 1)]
+      )
+    },
+
     // generate next action and save data
     async nextAction(same) {
       let additionalData = {
@@ -101,7 +107,7 @@ export default {
         maxStepCount: this.maxTestCount
       }
 
-      this.areTheSameAction(same, this.getReferenceTest, additionalData)
+      this.areTheSameAction(same, this.getRandomTest, additionalData)
     }
   }
 }

+ 14 - 2
src/views/Experiments/AreSameImagesReference.vue

@@ -59,9 +59,9 @@
 <script>
 import ExperimentBlock from '@/components/ExperimentBlock.vue'
 import ExperimentBaseAreSameImages from '@/mixins/ExperimentBaseAreSameImages'
+import { rand } from '@/functions'
 
 export default {
-  name: 'AreSameImagesReference',
   components: {
     ExperimentBlock
   },
@@ -69,7 +69,6 @@ export default {
 
   data() {
     return {
-      experimentName: 'AreSameImagesReference',
       referenceImagePosition: null
     }
   },
@@ -95,6 +94,19 @@ export default {
   },
 
   methods: {
+    // Get a test with random qualities
+    getReferenceTest() {
+      // Randomly choose which is the reference image (0 = left, 1 = right)
+      const isReferenceLeft = rand(0, 1) === 0
+      // Randomly choose a quality for the other image
+      const randomQuality = this.qualities[rand(0, this.qualities.length - 1)]
+
+      const res = [this.qualities[this.qualities.length - 1], randomQuality]
+      this.referenceImagePosition = isReferenceLeft ? 'left' : 'right'
+      const table = isReferenceLeft ? res : res.reverse()
+      return this.getTest(table[0], table[1])
+    },
+
     // generate next action and save data
     async nextAction(same) {
       let additionalData = {

+ 0 - 3
src/views/Experiments/AreSameImagesReferenceOneExtract.vue

@@ -69,7 +69,6 @@ import ExtractsToImage from '@/components/ExperimentsComponents/ExtractsToImage'
 import { rand } from '@/functions'
 
 export default {
-  name: 'AreSameImagesReferenceOneExtract',
   components: {
     ExperimentBlock,
     ExtractsToImage
@@ -81,8 +80,6 @@ export default {
 
   data() {
     return {
-      experimentName: 'AreSameImagesReferenceOneExtract',
-
       imageOneExtractPosition: null,
       randomZoneIndex: null,
       randomZoneQuality: null

+ 13 - 20
src/views/Experiments/IsImageCorrect.vue

@@ -9,7 +9,7 @@
       <!-- ## Template to place in the header (example: Extract-configurator) ## -->
     </template>
 
-    <template v-if="reconstructedImage" v-slot:content>
+    <template v-if="extracts" v-slot:content>
       <!-- ## Actual experiment template ## -->
 
       <!-- Image -->
@@ -17,7 +17,7 @@
         <v-card dark color="primary">
           <v-card-text class="px-0">Reconstructed image</v-card-text>
           <v-container class="pa-1">
-            <ExtractsToImage :extracts="reconstructedImage" :extract-config="extractConfig" />
+            <ExtractsToImage :extracts="extracts" :extract-config="extractConfig" />
           </v-container>
         </v-card>
       </v-flex>
@@ -52,7 +52,6 @@ import { EXPERIMENT as experimentMsgId } from '@/../config.messagesId'
 import { rand } from '@/functions'
 
 export default {
-  name: 'IsImageCorrect', // experiment filename
   components: {
     ExperimentBlock,
     ExtractsToImage
@@ -62,11 +61,6 @@ export default {
   ],
   data() {
     return {
-      experimentName: 'IsImageCorrect', // experiment filename
-      refImage: null,
-      randImage: null,
-      reconstructedImage: null,
-      selectedQuality: 50,
       testCount: 1,
       maxTestCount: 10
     }
@@ -106,18 +100,17 @@ export default {
     // load reconstructed image
     async getReconstructedImage() {
       const randomQuality = this.qualities[rand(0, this.qualities.length - 1)]
-      const maxQuality = this.qualities[this.qualities.length - 1]
 
       // Get the reference image, extracts of reference image and random quality extracts
-      const [maxExtracts, randomExtracts, maxImage, randImage] = await Promise.all([
+      const [maxExtracts, randomExtracts] = await Promise.all([
         this.getExtracts('max'),
-        this.getExtracts(randomQuality),
-        this.getImage(maxQuality),
-        this.getImage(randomQuality)
+        this.getExtracts(randomQuality)
       ])
 
-      this.refImage = maxImage
-      this.randImage = randImage
+      this.extractsInfos = {
+        refImage: maxExtracts.info,
+        randImage: randomExtracts.info
+      }
 
       // get part to keep into refImage
       let position = rand(0, 1)
@@ -125,7 +118,7 @@ export default {
 
       // construct new image with two different parts
       maxExtracts.extracts[position] = randomExtracts.extracts[position]
-      this.reconstructedImage = maxExtracts.extracts
+      this.extracts = maxExtracts.extracts
     },
 
     // get next reconstructed image
@@ -136,8 +129,8 @@ export default {
         this.testCount++
 
         const experimentalData = {
-          refImage: this.refImage,
-          randImage: this.randImage,
+          refImage: this.extractsInfos.refImage,
+          randImage: this.extractsInfos.randImage,
           refPosition: this.refPosition,
           imageCorrect: correct,
           stepCounter: this.testCount,
@@ -147,10 +140,10 @@ export default {
         }
         this.sendMessage({ msgId: experimentMsgId.DATA, msg: experimentalData })
 
-        await this.getReconstructedImage()
-
         // Experiment end
         if (this.testCount > this.maxTestCount) return this.finishExperiment()
+
+        await this.getReconstructedImage()
       }
       catch (err) {
         console.error('Failed to load new test', err)

+ 16 - 22
src/views/Experiments/IsImageCorrectOneExtract.vue

@@ -9,7 +9,7 @@
       <!-- ## Template to place in the header (example: Extract-configurator) ## -->
     </template>
 
-    <template v-if="reconstructedImage" v-slot:content>
+    <template v-if="extracts" v-slot:content>
       <!-- ## Actual experiment template ## -->
 
       <!-- Image -->
@@ -17,7 +17,7 @@
         <v-card dark color="primary">
           <v-card-text class="px-0">Reconstructed image</v-card-text>
           <v-container class="pa-1">
-            <ExtractsToImage :extracts="reconstructedImage" :extract-config="extractConfig" />
+            <ExtractsToImage :extracts="extracts" :extract-config="extractConfig" />
           </v-container>
         </v-card>
       </v-flex>
@@ -52,7 +52,6 @@ import { EXPERIMENT as experimentMsgId } from '@/../config.messagesId'
 import { rand } from '@/functions'
 
 export default {
-  name: 'IsImageCorrectOneExtract', // experiment filename
   components: {
     ExperimentBlock,
     ExtractsToImage
@@ -62,11 +61,6 @@ export default {
   ],
   data() {
     return {
-      experimentName: 'IsImageCorrectOneExtract', // experiment filename
-      refImage: null,
-      randImage: null,
-      reconstructedImage: null,
-      selectedQuality: 50,
       testCount: 1,
       maxTestCount: 10
     }
@@ -85,7 +79,7 @@ export default {
       // Load scene qualities list
       await this.getQualitiesList()
 
-      if (!this.reconstructedImage) await this.getReconstructedImage()
+      if (!this.extracts) await this.getReconstructedImage()
     }
     catch (err) {
       console.error(err)
@@ -106,27 +100,27 @@ export default {
     // load reconstructed image
     async getReconstructedImage() {
       const randomQuality = this.qualities[rand(0, this.qualities.length - 1)]
-      const maxQuality = this.qualities[this.qualities.length - 1]
 
       // Get the reference image, extracts of reference image and random quality extracts
-      const [maxExtracts, randomExtracts, maxImage, randImage] = await Promise.all([
+      const [maxExtracts, randomExtracts] = await Promise.all([
         this.getExtracts('max'),
-        this.getExtracts(randomQuality),
-        this.getImage(maxQuality),
-        this.getImage(randomQuality)
+        this.getExtracts(randomQuality)
       ])
 
-      this.refImage = maxImage
-      this.randImage = randImage
+      this.extractsInfos = {
+        refImage: maxExtracts.info,
+        randImage: randomExtracts.info
+      }
 
       // get part to keep into refImage
-      let position = rand(0, randomExtracts.extracts.length)
+      let position = rand(0, randomExtracts.extracts.length - 1)
       this.randZonePosition = position
 
       // construct new image with two different parts
       maxExtracts.extracts[position] = randomExtracts.extracts[position]
-      this.reconstructedImage = maxExtracts.extracts
+      this.extracts = maxExtracts.extracts
     },
+
     // get next reconstructed image
     async nextReconstructedImage(correct) {
       this.loadingMessage = 'Loading new test...'
@@ -135,8 +129,8 @@ export default {
         this.testCount++
 
         const experimentalData = {
-          refImage: this.refImage,
-          randImage: this.randImage,
+          refImage: this.extractsInfos.refImage,
+          randImage: this.extractsInfos.randImage,
           randZoneId: this.randZonePosition,
           imageCorrect: correct,
           stepCounter: this.testCount,
@@ -146,10 +140,10 @@ export default {
         }
         this.sendMessage({ msgId: experimentMsgId.DATA, msg: experimentalData })
 
-        await this.getReconstructedImage()
-
         // Experiment end
         if (this.testCount > this.maxTestCount) return this.finishExperiment()
+
+        await this.getReconstructedImage()
       }
       catch (err) {
         console.error('Failed to load new test', err)

+ 0 - 2
src/views/Experiments/MatchExtractsWithReference.vue

@@ -86,7 +86,6 @@ import ExperimentBaseExtracts from '@/mixins/ExperimentBaseExtracts'
 import ExtractConfiguration from '@/components/ExperimentsComponents/ExtractConfiguration.vue'
 
 export default {
-  name: 'MatchExtractsWithReference',
   components: {
     ExperimentBlock,
     ExtractConfiguration
@@ -95,7 +94,6 @@ export default {
 
   data() {
     return {
-      experimentName: 'MatchExtractsWithReference',
       referenceImage: null
     }
   },

+ 2 - 4
src/views/Experiments/PercentQualityRandom.vue

@@ -64,14 +64,12 @@ import { EXPERIMENT as experimentMsgId } from '@/../config.messagesId'
 import { rand } from '@/functions'
 
 export default {
-  name: 'PercentQualityRandom', // experiment filename
   components: {
     ExperimentBlock
   },
   mixins: [ExperimentBase],
   data() {
     return {
-      experimentName: 'PercentQualityRandom', // experiment filename
       image1: null,
       selectedQuality: 50,
       testCount: 1,
@@ -131,10 +129,10 @@ export default {
         }
         this.sendMessage({ msgId: experimentMsgId.DATA, msg: experimentalData })
 
-        await this.getTest()
-
         // Experiment end
         if (this.testCount > this.maxTestCount) return this.finishExperiment()
+
+        await this.getTest()
       }
       catch (err) {
         console.error('Failed to load new test', err)

+ 3 - 6
src/views/Experiments/_template.vue

@@ -17,18 +17,15 @@
 
 <script>
 import ExperimentBlock from '@/components/ExperimentBlock.vue'
-import ExperimentBaseAreTheSame from '@/mixins/ExperimentBaseAreSameImages'
+import ExperimentBase from '@/mixins/ExperimentBase'
 
 export default {
-  name: 'YourExperimentName',
   components: {
     ExperimentBlock
   },
-  mixins: [ExperimentBaseAreTheSame],
+  mixins: [ExperimentBase],
   data() {
-    return {
-      experimentName: 'YourExperimentName'
-    }
+    return {}
   },
 
   // When experiment is loaded, this function is ran

+ 4 - 4
src/views/HostConfig.vue

@@ -99,9 +99,9 @@ export default {
   data() {
     return {
       config: {
-        ssl: false,
+        ssl: true,
         host: 'diran.univ-littoral.fr',
-        port: '80'
+        port: '443'
       },
 
       id: {
@@ -135,8 +135,8 @@ export default {
     ...mapActions(['setHostConfig', 'setUserExperimentId']),
     reset() {
       this.config.ssl = true
-      this.config.host = ''
-      this.config.port = null
+      this.config.host = 'diran.univ-littoral.fr'
+      this.config.port = 443
       this.id.user = null
       this.id.hasUserId = false
       this.id.experiment = null

+ 54 - 20
src/views/LinkGenerator.vue

@@ -38,7 +38,7 @@
 
                 <h2>User ID and experiment ID</h2>
                 <v-layout row wrap>
-                  <v-flex xs5>
+                  <v-flex xs4>
                     <v-checkbox
                       v-model="form.userId.activated"
                       color="primary"
@@ -46,7 +46,7 @@
                     />
                   </v-flex>
                   <v-spacer />
-                  <v-flex xs6>
+                  <v-flex xs8>
                     <v-text-field
                       v-model="form.userId.value"
                       label="User ID"
@@ -57,7 +57,7 @@
                 </v-layout>
 
                 <v-layout row wrap>
-                  <v-flex xs5>
+                  <v-flex xs4>
                     <v-checkbox
                       v-model="form.experimentId.activated"
                       color="primary"
@@ -65,7 +65,7 @@
                     />
                   </v-flex>
                   <v-spacer />
-                  <v-flex xs6>
+                  <v-flex xs8>
                     <v-text-field
                       v-model="form.experimentId.value"
                       label="Experiment ID"
@@ -78,38 +78,43 @@
 
                 <h2>Experiment name and scene name</h2>
                 <v-layout row wrap>
-                  <v-flex xs5>
+                  <v-flex xs4>
                     <v-checkbox
                       v-model="form.experimentName.activated"
                       color="primary"
                       label="Experiment name"
+                      @click="form.sceneName.activated = false"
                     />
                   </v-flex>
-                  <v-spacer />
-                  <v-flex xs6>
-                    <v-text-field
+                  <v-flex xs8>
+                    <v-select
                       v-model="form.experimentName.value"
+                      :items="experimentsSelectItems"
+                      item-text="text"
+                      item-value="value"
                       label="Experiment name"
-                      type="text"
                       :disabled="!form.experimentName.activated"
                     />
                   </v-flex>
                 </v-layout>
 
                 <v-layout row wrap>
-                  <v-flex xs5>
+                  <v-flex xs4>
                     <v-checkbox
                       v-model="form.sceneName.activated"
                       color="primary"
                       label="Scene name"
+                      :disabled="!form.experimentName.activated || form.experimentName.value === ''"
                     />
                   </v-flex>
                   <v-spacer />
-                  <v-flex xs6>
-                    <v-text-field
+                  <v-flex xs8>
+                    <v-select
                       v-model="form.sceneName.value"
+                      :items="scenesSelectItems"
+                      item-text="text"
+                      item-value="value"
                       label="Scene name"
-                      type="text"
                       :disabled="!form.sceneName.activated"
                     />
                   </v-flex>
@@ -123,7 +128,7 @@
                     :value="linkOutput"
                     label="Your generated link"
                     type="text"
-                    disabled
+                    readonly
                   />
 
                   Data in the generated link:
@@ -143,6 +148,9 @@
 </template>
 
 <script>
+import Experiments from '@/router/experiments'
+import { getExperimentSceneList } from '@/config.utils'
+
 export default {
   name: 'LinkGenerator',
   components: {
@@ -150,9 +158,9 @@ export default {
   data() {
     return {
       form: {
-        webAppUrl: 'http://diran.univ-littoral.fr',
+        webAppUrl: 'https://diran.univ-littoral.fr',
         server: {
-          ssl: false,
+          ssl: true,
           host: 'diran.univ-littoral.fr',
           port: '80'
         },
@@ -178,12 +186,38 @@ export default {
         }
       },
 
+      experimentsSelectItems: null,
+      scenesSelectItems: null,
+
       linkOutput: null,
       dataOutput: null,
       alertMessage: null
     }
   },
 
+  watch: {
+    'form.experimentName.activated'(newValue) {
+      // Reset available scenes if experiment changed
+      if (!newValue) this.scenesSelectItems = null
+    },
+    'form.experimentName.value'(newValue) {
+      // Reset available scenes if experiment changed
+      if (newValue !== '') this.scenesSelectItems = getExperimentSceneList(this.form.experimentName.value)
+    },
+    'form.sceneName.activated'(newValue) {
+      // Load available scenes when sceneName is activated
+      if (newValue) this.scenesSelectItems = getExperimentSceneList(this.form.experimentName.value)
+      else this.scenesSelectItems = null
+    }
+  },
+
+  mounted() {
+    this.experimentsSelectItems = Experiments.map(expe => ({
+      text: `${expe.name} - ${expe.meta.fullName}`,
+      value: expe.name
+    }))
+  },
+
   methods: {
     generateLink() {
       this.alertMessage = null
@@ -204,10 +238,10 @@ export default {
           port: this.form.server.port
         }
       }
-      if (this.form.userId.activated && this.form.userId.value !== '')obj.userId = this.form.userId.value
-      if (this.form.experimentId.activated && this.form.experimentId.value !== '')obj.experimentId = this.form.experimentId.value
-      if (this.form.experimentName.activated && this.form.experimentName.value !== '')obj.experimentName = this.form.experimentName.value
-      if (this.form.sceneName.activated && this.form.sceneName.value !== '')obj.sceneName = this.form.sceneName.value
+      if (this.form.userId.activated && this.form.userId.value !== '') obj.userId = this.form.userId.value
+      if (this.form.experimentId.activated && this.form.experimentId.value !== '') obj.experimentId = this.form.experimentId.value
+      if (this.form.experimentName.activated && this.form.experimentName.value !== '') obj.experimentName = this.form.experimentName.value
+      if (this.form.sceneName.activated && this.form.sceneName.value !== '') obj.sceneName = this.form.sceneName.value
 
       // eslint-disable-next-line no-div-regex
       const q = btoa(JSON.stringify(obj)).replace(/=/g, '')

+ 6 - 2
test/server/api/dataCollect.js

@@ -17,24 +17,28 @@ test('POST /dataCollect - No body', async t => {
   t.is(res.status, 400, json(res))
   t.true(res.body.message.includes('Missing parameter'), json(res.body))
   t.true(res.body.message.includes('uuid'), json(res.body))
+  t.true(res.body.message.includes('userId'), json(res.body))
+  t.true(res.body.message.includes('experimentId'), json(res.body))
   t.true(res.body.message.includes('screen'), json(res.body))
 })
 
 test('POST /dataCollect - Invalid body parameters', async t => {
   const res = await request(t.context.server)
     .post(`${apiPrefix}/dataCollect`)
-    .send({ uuid: 42, screen: 'not an object' })
+    .send({ uuid: 42, userId: 42, experimentId: 42, screen: 'not an object' })
 
   t.is(res.status, 400, json(res))
   t.true(res.body.message.includes('Invalid body parameter'), json(res.body))
   t.truthy(res.body.data.find(x => x.includes('"uuid" must be a string.')), json(res.body))
+  t.truthy(res.body.data.find(x => x.includes('"userId" must be a string.')), json(res.body))
+  t.truthy(res.body.data.find(x => x.includes('"experimentId" must be a string.')), json(res.body))
   t.truthy(res.body.data.find(x => x.includes('"screen" must be a valid object.')), json(res.body))
 })
 
 test('POST /dataCollect - Valid body parameters', async t => {
   const res = await request(t.context.server)
     .post(`${apiPrefix}/dataCollect`)
-    .send({ uuid: 'test', screen: { width: 1920, height: 1080 } })
+    .send({ uuid: 'test', userId: 'user-test', experimentId: 'expe-test', screen: { width: 1920, height: 1080 } })
 
   t.is(res.status, 200, json(res))
   t.is(res.body.message, 'OK', json(res.body))