mirror of
https://github.com/psychopy/psychojs.git
synced 2025-05-10 02:30:53 +00:00
progress with partial saving of results
This commit is contained in:
parent
5c6408e1da
commit
c3a2b4b9f6
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "psychojs",
|
||||
"version": "2023.2.3",
|
||||
"version": "2024.4.1",
|
||||
"private": true,
|
||||
"description": "Helps run in-browser neuroscience, psychology, and psychophysics experiments",
|
||||
"license": "MIT",
|
||||
|
@ -81,13 +81,16 @@ export class GUI
|
||||
* @param {String} options.title - name of the project
|
||||
* @param {boolean} [options.requireParticipantClick=true] - whether the participant must click on the OK
|
||||
* button, when it becomes enabled, to move on with the experiment
|
||||
* @param {boolean} [options.OKAlwaysEnabledForLocal=false] - whether the OK button is always enabled
|
||||
* when the experiment runs locally
|
||||
*/
|
||||
DlgFromDict({
|
||||
logoUrl,
|
||||
text,
|
||||
dictionary,
|
||||
title,
|
||||
requireParticipantClick = GUI.DEFAULT_SETTINGS.DlgFromDict.requireParticipantClick
|
||||
requireParticipantClick = GUI.DEFAULT_SETTINGS.DlgFromDict.requireParticipantClick,
|
||||
OKAlwaysEnabledForLocal = true
|
||||
})
|
||||
{
|
||||
this._progressBarMax = 0;
|
||||
@ -96,6 +99,7 @@ export class GUI
|
||||
this._setRequiredKeys = new Map();
|
||||
this._progressMessage = " ";
|
||||
this._requireParticipantClick = requireParticipantClick;
|
||||
this._OKAlwaysEnabledForLocal = OKAlwaysEnabledForLocal;
|
||||
this._dictionary = dictionary;
|
||||
|
||||
// prepare a PsychoJS component:
|
||||
@ -276,7 +280,7 @@ export class GUI
|
||||
self._updateProgressBar();
|
||||
|
||||
// setup change event handlers for all required keys:
|
||||
this._requiredKeys.forEach((keyId) =>
|
||||
self._requiredKeys.forEach((keyId) =>
|
||||
{
|
||||
const input = document.getElementById(keyId);
|
||||
if (input)
|
||||
@ -685,7 +689,10 @@ export class GUI
|
||||
if (typeof this._okButton !== "undefined")
|
||||
{
|
||||
// locally the OK button is always enabled, otherwise only if all requirements have been fulfilled:
|
||||
if (this._psychoJS.getEnvironment() === ExperimentHandler.Environment.LOCAL || allRequirementsFulfilled)
|
||||
if (
|
||||
(this._OKAlwaysEnabledForLocal && this._psychoJS.getEnvironment() === ExperimentHandler.Environment.LOCAL)
|
||||
|| allRequirementsFulfilled
|
||||
)
|
||||
{
|
||||
this._okButton.classList.add("dialog-button");
|
||||
this._okButton.classList.remove("disabled");
|
||||
|
@ -108,9 +108,9 @@ export class PsychoJS
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object} options
|
||||
* @param {boolean} [options.debug= true] whether to log debug information in the browser console
|
||||
* @param {boolean} [options.collectIP= false] whether to collect the IP information of the participant
|
||||
* @param {Object} options - options
|
||||
* @param {boolean} [options.debug= true] - whether to log debug information in the browser console
|
||||
* @param {boolean} [options.collectIP= false] - whether to collect the IP information of the participant
|
||||
*/
|
||||
constructor({
|
||||
debug = true,
|
||||
@ -186,7 +186,7 @@ export class PsychoJS
|
||||
this._saveResults = saveResults;
|
||||
|
||||
this.logger.info("[PsychoJS] Initialised.");
|
||||
this.logger.info("[PsychoJS] @version 2022.3.0");
|
||||
this.logger.info("[PsychoJS] @version 2024.1.0");
|
||||
|
||||
// hide the initialisation message:
|
||||
const root = document.getElementById("root");
|
||||
@ -399,9 +399,18 @@ export class PsychoJS
|
||||
{
|
||||
if (self._config.session.status === "OPEN")
|
||||
{
|
||||
// stop the regular uploading of results, if need be:
|
||||
if (self._config.experiment.resultsUpload.intervalId > 0)
|
||||
{
|
||||
clearInterval(self._config.experiment.resultsUpload.intervalId);
|
||||
self._config.experiment.resultsUpload.intervalId = -1;
|
||||
}
|
||||
|
||||
// save the incomplete results if need be:
|
||||
if (self._config.experiment.saveIncompleteResults && self._saveResults)
|
||||
{
|
||||
// note: we set lastUploadTimestamp to undefined to prevent uploadData from throttling this call
|
||||
delete self._config.experiment.resultsUpload.lastUploadTimestamp;
|
||||
self._experiment.save({ sync: true });
|
||||
}
|
||||
|
||||
@ -414,6 +423,20 @@ export class PsychoJS
|
||||
self._window.close();
|
||||
}
|
||||
});
|
||||
|
||||
// upload the data at regular interval, if need be:
|
||||
if (self._saveResults && self._config.experiment.resultsUpload.period > 0)
|
||||
{
|
||||
self._config.experiment.resultsUpload.intervalId = setInterval(() =>
|
||||
{
|
||||
self._experiment.save({
|
||||
tag: "",
|
||||
clear: true
|
||||
});
|
||||
},
|
||||
self._config.experiment.resultsUpload.period * 60 * 1000
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// start the asynchronous download of resources:
|
||||
@ -423,7 +446,7 @@ export class PsychoJS
|
||||
if (this._checkWebGLSupport && !Window.checkWebGLSupport())
|
||||
{
|
||||
// add an entry to experiment results to warn the designer about a potential WebGL issue:
|
||||
this._experiment.addData('hardware_acceleration', 'NOT SUPPORTED');
|
||||
this._experiment.addData("hardware_acceleration", "NOT SUPPORTED");
|
||||
this._experiment.nextEntry();
|
||||
|
||||
this._gui.dialog({
|
||||
@ -519,9 +542,10 @@ export class PsychoJS
|
||||
* <p>Note: if the resource manager is busy, we inform the participant
|
||||
* that he or she needs to wait for a bit.</p>
|
||||
*
|
||||
* @param {Object} options
|
||||
* @param {Object} options - options
|
||||
* @param {string} [options.message] - optional message to be displayed in a dialog box before quitting
|
||||
* @param {boolean} [options.isCompleted = false] - whether the participant has completed the experiment
|
||||
* @return {void}
|
||||
*/
|
||||
async quit({ message, isCompleted = false, closeWindow = true, showOK = true } = {})
|
||||
{
|
||||
@ -545,6 +569,14 @@ export class PsychoJS
|
||||
window.removeEventListener("beforeunload", this.beforeunloadCallback);
|
||||
}
|
||||
|
||||
// stop the regular uploading of results, if need be:
|
||||
if (this._config.experiment.resultsUpload.intervalId > 0)
|
||||
{
|
||||
clearInterval(this._config.experiment.resultsUpload.intervalId);
|
||||
this._config.experiment.resultsUpload.intervalId = -1;
|
||||
}
|
||||
delete this._config.experiment.resultsUpload.lastUploadTimestamp;
|
||||
|
||||
// save the results and the logs of the experiment:
|
||||
this.gui.finishDialog({
|
||||
text: "Terminating the experiment. Please wait a few moments...",
|
||||
@ -629,6 +661,7 @@ export class PsychoJS
|
||||
* @protected
|
||||
* @param {string} configURL - the URL of the configuration file
|
||||
* @param {string} name - the name of the experiment
|
||||
* @return {void}
|
||||
*/
|
||||
async _configure(configURL, name)
|
||||
{
|
||||
@ -701,10 +734,15 @@ export class PsychoJS
|
||||
name,
|
||||
saveFormat: ExperimentHandler.SaveFormat.CSV,
|
||||
saveIncompleteResults: true,
|
||||
keys: [],
|
||||
keys: []
|
||||
},
|
||||
};
|
||||
}
|
||||
// init the partial results upload options
|
||||
this._config.experiment.resultsUpload = {
|
||||
period: -1,
|
||||
intervalId: -1
|
||||
};
|
||||
|
||||
// get the server parameters (those starting with a double underscore):
|
||||
this._serverMsg = new Map();
|
||||
@ -737,6 +775,7 @@ export class PsychoJS
|
||||
*
|
||||
* <p>Note: we use [http://www.geoplugin.net/json.gp]{@link http://www.geoplugin.net/json.gp}.</p>
|
||||
* @protected
|
||||
* @return {void}
|
||||
*/
|
||||
async _getParticipantIPInfo()
|
||||
{
|
||||
|
@ -58,6 +58,13 @@ export class ServerManager extends PsychObject
|
||||
this._nbLoadedResources = 0;
|
||||
this._setupPreloadQueue();
|
||||
|
||||
// throttling period for calls to uploadData and uploadLog (in mn):
|
||||
// note: (a) the period is potentially updated when a session is opened to reflect that associated with
|
||||
// the experiment on the back-end database
|
||||
// (b) throttling is also enforced on the back-end: artificially altering the period
|
||||
// on the participant's browser will result in server errors
|
||||
this._uploadThrottlePeriod = 5;
|
||||
|
||||
this._addAttribute("autoLog", autoLog);
|
||||
this._addAttribute("status", ServerManager.Status.READY);
|
||||
}
|
||||
@ -194,6 +201,21 @@ export class ServerManager extends PsychObject
|
||||
self._psychoJS.config.experiment.keys = [];
|
||||
}
|
||||
|
||||
// partial results upload options:
|
||||
if ("partialResultsUploadPeriod" in experiment)
|
||||
{
|
||||
// note: resultsUpload is initialised in PsychoJS._configure but we reinitialise it here
|
||||
// all the same (belt and braces approach)
|
||||
self._psychoJS.config.experiment.resultsUpload = {
|
||||
period: experiment.partialResultsUploadPeriod,
|
||||
intervalId: -1
|
||||
};
|
||||
}
|
||||
if ("uploadThrottlePeriod" in experiment)
|
||||
{
|
||||
this._uploadThrottlePeriod = experiment.uploadThrottlePeriod;
|
||||
}
|
||||
|
||||
self.setStatus(ServerManager.Status.READY);
|
||||
resolve({...response, token: openSessionResponse.token, status: openSessionResponse.status });
|
||||
}
|
||||
@ -783,6 +805,16 @@ export class ServerManager extends PsychObject
|
||||
};
|
||||
this._psychoJS.logger.debug("uploading data for experiment: " + this._psychoJS.config.experiment.fullpath);
|
||||
|
||||
// data upload throttling:
|
||||
const now = MonotonicClock.getReferenceTime();
|
||||
if ( (typeof this._psychoJS.config.experiment.resultsUpload.lastUploadTimestamp !== "undefined") &&
|
||||
(now - this._psychoJS.config.experiment.resultsUpload.lastUploadTimestamp < this._uploadThrottlePeriod * 60)
|
||||
)
|
||||
{
|
||||
return Promise.reject({ ...response, error: "upload canceled by throttling"});
|
||||
}
|
||||
this._psychoJS.config.experiment.resultsUpload.lastUploadTimestamp = now;
|
||||
|
||||
this.setStatus(ServerManager.Status.BUSY);
|
||||
|
||||
const path = `experiments/${this._psychoJS.config.gitlab.projectId}/sessions/${this._psychoJS.config.session.token}/results`;
|
||||
|
@ -266,7 +266,7 @@ export class ExperimentHandler extends PsychObject
|
||||
}
|
||||
}
|
||||
}
|
||||
for (let a in this.extraInfo)
|
||||
for (const a in this.extraInfo)
|
||||
{
|
||||
if (this.extraInfo.hasOwnProperty(a))
|
||||
{
|
||||
@ -303,7 +303,7 @@ export class ExperimentHandler extends PsychObject
|
||||
&& !this._psychoJS._serverMsg.has("__pilotToken")
|
||||
)
|
||||
{
|
||||
return /*await*/ this._psychoJS.serverManager.uploadData(key, csv, sync);
|
||||
return this._psychoJS.serverManager.uploadData(key, csv, sync);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -320,7 +320,7 @@ export class ExperimentHandler extends PsychObject
|
||||
|
||||
for (let r = 0; r < data.length; r++)
|
||||
{
|
||||
let doc = {
|
||||
const doc = {
|
||||
__projectId,
|
||||
__experimentName: this._experimentName,
|
||||
__participant: this._participant,
|
||||
|
@ -21,7 +21,7 @@ export class PsychObject extends EventEmitter
|
||||
{
|
||||
/**
|
||||
* @param {module:core.PsychoJS} psychoJS - the PsychoJS instance
|
||||
* @param {string} name - the name of the object (mostly useful for debugging)
|
||||
* @param {string} [name] - the name of the object (mostly useful for debugging)
|
||||
*/
|
||||
constructor(psychoJS, name)
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user