diff --git a/src/components/experiment-list/experiment-list-element.js b/src/components/experiment-list/experiment-list-element.js index 0d6f4ceb26d783c2e2f4eff5ea5e50396551aac1..f6b3af6d81d724b6b1e1fe47d92d8f4b7f5bb522 100644 --- a/src/components/experiment-list/experiment-list-element.js +++ b/src/components/experiment-list/experiment-list-element.js @@ -4,7 +4,7 @@ import { VscTriangleUp, VscTriangleDown } from 'react-icons/vsc'; import { GoFileSubmodule } from 'react-icons/go'; import timeDDHHMMSS from '../../utility/time-filter.js'; -import ExperimentStorageService from '../../services/experiments/storage/experiment-storage-service.js'; +import ExperimentStorageService from '../../services/experiments/files/experiment-storage-service.js'; import ExperimentExecutionService from '../../services/experiments/execution/experiment-execution-service.js'; import SimulationDetails from './simulation-details'; diff --git a/src/components/experiment-overview/experiment-overview.js b/src/components/experiment-overview/experiment-overview.js index 6b67ad2b22f13efd8520d3744fc8fb24c571d2ea..bc68bd82e7fe5d1b0a992621c01263c051360843 100644 --- a/src/components/experiment-overview/experiment-overview.js +++ b/src/components/experiment-overview/experiment-overview.js @@ -2,7 +2,8 @@ import React from 'react'; import { Tab, Tabs, TabList, TabPanel } from 'react-tabs'; import 'react-tabs/style/react-tabs.css'; -import ExperimentStorageService from '../../services/experiments/storage/experiment-storage-service.js'; +import ExperimentStorageService from '../../services/experiments/files/experiment-storage-service.js'; +import SharedExperimentsService from '../../services/experiments/files/shared-experiments-service.js'; import ExperimentServerService from '../../services/experiments/execution/server-resources-service.js'; import ExperimentExecutionService from '../../services/experiments/execution/experiment-execution-service.js'; @@ -15,7 +16,8 @@ export default class ExperimentOverview extends React.Component { constructor(props) { super(props); this.state = { - experiments: [], + storageExperiments: [], + templateExperiments: [], joinableExperiments: [], availableServers: [], startingExperiment: undefined @@ -24,9 +26,11 @@ export default class ExperimentOverview extends React.Component { async componentDidMount() { try { - const experiments = await ExperimentStorageService.instance.getExperiments(); + const storageExperiments = await ExperimentStorageService.instance.getExperiments(); + const templateExperiments = await SharedExperimentsService.instance.getExperiments(); this.setState({ - experiments: experiments + storageExperiments: storageExperiments, + templateExperiments: templateExperiments }); } catch (error) { @@ -45,10 +49,10 @@ export default class ExperimentOverview extends React.Component { this.onStartExperiment ); - this.onUpdateExperiments = this.onUpdateExperiments.bind(this); + this.onUpdateExperiments = this.onUpdateStorageExperiments.bind(this); ExperimentStorageService.instance.addListener( ExperimentStorageService.EVENTS.UPDATE_EXPERIMENTS, - this.onUpdateExperiments + this.onUpdateStorageExperiments ); } @@ -65,7 +69,7 @@ export default class ExperimentOverview extends React.Component { ExperimentStorageService.instance.removeListener( ExperimentStorageService.EVENTS.UPDATE_EXPERIMENTS, - this.onUpdateExperiments + this.onUpdateStorageExperiments ); } @@ -77,11 +81,11 @@ export default class ExperimentOverview extends React.Component { this.setState({ startingExperiment: experiment }); }; - onUpdateExperiments(experiments) { - let joinableExperiments = experiments.filter( + onUpdateStorageExperiments(storageExperiments) { + let joinableExperiments = storageExperiments.filter( experiment => experiment.joinableServers && experiment.joinableServers.length > 0); this.setState({ - experiments: experiments, + storageExperiments: storageExperiments, joinableExperiments: joinableExperiments }); } @@ -104,7 +108,7 @@ export default class ExperimentOverview extends React.Component { </TabList> <TabPanel> - <ExperimentList experiments={this.state.experiments} + <ExperimentList experiments={this.state.storageExperiments} availableServers={this.state.availableServers} startingExperiment={this.state.startingExperiment} /> </TabPanel> @@ -118,7 +122,9 @@ export default class ExperimentOverview extends React.Component { <h2>"Experiment Files" tab coming soon ...</h2> </TabPanel> <TabPanel> - <h2>"Templates" tab coming soon ...</h2> + <ExperimentList experiments={this.state.templateExperiments} + availableServers={this.state.availableServers} + startingExperiment={this.state.startingExperiment} /> </TabPanel> <TabPanel> <ExperimentList diff --git a/src/services/experiments/storage/__tests__/experiment-storage-service.test.js b/src/services/experiments/files/__tests__/experiment-storage-service.test.js similarity index 100% rename from src/services/experiments/storage/__tests__/experiment-storage-service.test.js rename to src/services/experiments/files/__tests__/experiment-storage-service.test.js diff --git a/src/services/experiments/storage/experiment-storage-service.js b/src/services/experiments/files/experiment-storage-service.js similarity index 95% rename from src/services/experiments/storage/experiment-storage-service.js rename to src/services/experiments/files/experiment-storage-service.js index dcbd4d12ca75f76b0ff0bd8a5168ddc5c52ef447..3097dcbf12e5d15c0b253ea30cdb3bab5fd4fe75 100644 --- a/src/services/experiments/storage/experiment-storage-service.js +++ b/src/services/experiments/files/experiment-storage-service.js @@ -3,13 +3,12 @@ import { HttpService } from '../../http-service.js'; import endpoints from '../../proxy/data/endpoints.json'; import config from '../../../config.json'; const storageExperimentsURL = `${config.api.proxy.url}${endpoints.proxy.storage.experiments.url}`; -const cloneURL = `${config.api.proxy.url}${endpoints.proxy.storage.clone.url}`; let _instance = null; const SINGLETON_ENFORCER = Symbol(); /** - * Service that fetches the template experiments list from the proxy given + * Service that handles storage experiment files and configurations given * that the user has authenticated successfully. */ class ExperimentStorageService extends HttpService { @@ -231,12 +230,6 @@ class ExperimentStorageService extends HttpService { 'please make sure that the contentType and the body type match.'); } } - - async cloneExperiment(experiment) { - let expPath = experiment.configuration.experimentConfiguration; - let response = await this.httpRequestPOST(cloneURL, { expPath }); - console.info(response); - } } ExperimentStorageService.EVENTS = Object.freeze({ diff --git a/src/services/experiments/files/shared-experiments-service.js b/src/services/experiments/files/shared-experiments-service.js new file mode 100644 index 0000000000000000000000000000000000000000..d668ef1b975ddd77ecc467711ee0b49bcf2948ec --- /dev/null +++ b/src/services/experiments/files/shared-experiments-service.js @@ -0,0 +1,127 @@ +import { HttpService } from '../../http-service.js'; + +import endpoints from '../../proxy/data/endpoints.json'; +import config from '../../../config.json'; +const experimentsURL = `${config.api.proxy.url}${endpoints.proxy.experiments.url}`; +const cloneURL = `${config.api.proxy.url}${endpoints.proxy.storage.clone.url}`; + +let _instance = null; +const SINGLETON_ENFORCER = Symbol(); + +/** + * Service that handles storage experiment files and configurations given + * that the user has authenticated successfully. + */ +class SharedExperimentsService extends HttpService { + constructor(enforcer) { + super(); + if (enforcer !== SINGLETON_ENFORCER) { + throw new Error('Use ' + this.constructor.name + '.instance'); + } + + this.startUpdates(); + window.addEventListener('beforeunload', (event) => { + this.stopUpdates(); + event.returnValue = ''; + }); + } + + static get instance() { + if (_instance == null) { + _instance = new SharedExperimentsService(SINGLETON_ENFORCER); + } + + return _instance; + } + + /** + * Start polling updates. + */ + startUpdates() { + this.getExperiments(true); + this.intervalPollExperiments = setInterval( + () => { + this.getExperiments(true); + }, + SharedExperimentsService.CONSTANTS.INTERVAL_POLL_EXPERIMENTS + ); + } + + /** + * Stop polling updates. + */ + stopUpdates() { + this.intervalPollExperiments && clearInterval(this.intervalPollExperiments); + } + + /** + * Retrieves the list of template experiments from the proxy and stores + * them in the experiments class property. If the experiments are already + * there it just returns them, else does an HTTP request. + * + * @param {boolean} forceUpdate forces an update of the list + * @return experiments - the list of template experiments + */ + async getExperiments(forceUpdate = false) { + if (!this.experiments || forceUpdate) { + let response = await (await this.httpRequestGET(experimentsURL)).json(); + this.experiments = response.values(); + console.info(this.experiments); + this.sortExperiments(); + //await this.fillExperimentDetails(); + this.emit(SharedExperimentsService.EVENTS.UPDATE_EXPERIMENTS, this.experiments); + } + + return this.experiments; + }; + + /** + * Sort the local list of experiments alphabetically. + */ + sortExperiments() { + this.experiments = this.experiments.sort( + (a, b) => { + let nameA = a.configuration.name.toLowerCase(); + let nameB = b.configuration.name.toLowerCase(); + if (nameA < nameB) { + return -1; + } + if (nameA > nameB) { + return 1; + } + return 0; + } + ); + } + + /** + * Fill in some details for the local experiment list that might be missing. + */ + async fillExperimentDetails() { + this.experiments.forEach(exp => { + if (!exp.configuration.brainProcesses && exp.configuration.bibiConfSrc) { + exp.configuration.brainProcesses = 1; + } + }); + } + + /** + * Clone an experiment setup to storage + * @param {Object} experiment The Experiment configuration + */ + async cloneExperiment(experiment) { + let expPath = experiment.configuration.experimentConfiguration; + let response = await this.httpRequestPOST(cloneURL, { expPath }); + console.info(response); + } +} + +SharedExperimentsService.EVENTS = Object.freeze({ + UPDATE_EXPERIMENTS: 'UPDATE_EXPERIMENTS' +}); + +SharedExperimentsService.CONSTANTS = Object.freeze({ + INTERVAL_POLL_EXPERIMENTS: 5000 +}); + +export default SharedExperimentsService; diff --git a/src/services/proxy/data/endpoints.json b/src/services/proxy/data/endpoints.json index 20b3b4658fa7db78ade27342f052c9c1d3cfca06..bf01f00031af2a2d34b346a4ae20b41414683183 100644 --- a/src/services/proxy/data/endpoints.json +++ b/src/services/proxy/data/endpoints.json @@ -6,6 +6,9 @@ "experimentImage": { "url": "/experimentImage" }, + "experiments": { + "url": "/experiments" + }, "identity": { "url": "/identity", "me": {