From aaa9668ac9d3e2b386313a14b2bb413a1d2a62df Mon Sep 17 00:00:00 2001
From: Sandro Weber <webers@in.tum.de>
Date: Mon, 8 Feb 2021 23:22:47 +0100
Subject: [PATCH] WIP

---
 .../experiment-list-element.js                |   2 +-
 .../experiment-overview.js                    |  30 +++--
 .../experiment-storage-service.test.js        |   0
 .../experiment-storage-service.js             |   9 +-
 .../files/shared-experiments-service.js       | 127 ++++++++++++++++++
 src/services/proxy/data/endpoints.json        |   3 +
 6 files changed, 150 insertions(+), 21 deletions(-)
 rename src/services/experiments/{storage => files}/__tests__/experiment-storage-service.test.js (100%)
 rename src/services/experiments/{storage => files}/experiment-storage-service.js (95%)
 create mode 100644 src/services/experiments/files/shared-experiments-service.js

diff --git a/src/components/experiment-list/experiment-list-element.js b/src/components/experiment-list/experiment-list-element.js
index 0d6f4ce..f6b3af6 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 6b67ad2..bc68bd8 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 dcbd4d1..3097dcb 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 0000000..d668ef1
--- /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 20b3b46..bf01f00 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": {
-- 
GitLab