From 9086e9bb6b62c3b5c43370037482523f1ef95b78 Mon Sep 17 00:00:00 2001
From: Sandro Weber <webers@in.tum.de>
Date: Wed, 10 Feb 2021 16:55:51 +0100
Subject: [PATCH] experiment rights managed by services

---
 .../experiment-list-element.js                | 58 ++++++---------
 .../experiment-list/experiment-list.js        |  2 +-
 .../experiment-overview.js                    | 39 ++++++----
 .../execution/experiment-execution-service.js |  2 +-
 ...rvice.js => running-simulation-service.js} |  0
 .../experiments/experiment-constants.js       | 17 ++++-
 .../files/experiment-storage-service.js       | 41 ++++++++---
 ...rvice.js => public-experiments-service.js} | 73 ++++++++++++++-----
 8 files changed, 146 insertions(+), 86 deletions(-)
 rename src/services/experiments/execution/{simulation-service.js => running-simulation-service.js} (100%)
 rename src/services/experiments/files/{shared-experiments-service.js => public-experiments-service.js} (50%)

diff --git a/src/components/experiment-list/experiment-list-element.js b/src/components/experiment-list/experiment-list-element.js
index f6b3af6..d03645a 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/files/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';
@@ -24,23 +24,12 @@ export default class ExperimentListElement extends React.Component {
       showSimDetails: true
     };
 
-    //TODO: put in service?
-    this.canLaunchExperiment = (this.props.experiment.private && this.props.experiment.owned) ||
-      !this.props.experiment.private;
     this.launchButtonTitle = '';
 
     this.wrapperRef = React.createRef();
   }
 
   async componentDidMount() {
-    // retrieve the experiment thumbnail
-    let thumbnail = await ExperimentStorageService.instance.getThumbnail(
-      this.props.experiment.name,
-      this.props.experiment.configuration.thumbnail);
-    this.setState({
-      thumbnail: URL.createObjectURL(thumbnail)
-    });
-
     this.handleClickOutside = this.handleClickOutside.bind(this);
     document.addEventListener('mousedown', this.handleClickOutside);
   }
@@ -88,11 +77,11 @@ export default class ExperimentListElement extends React.Component {
   }
 
   isLaunchDisabled() {
-    let isDisabled = !this.canLaunchExperiment ||
+    let isDisabled = !this.props.experiment.rights.launch ||
       this.props.availableServers.length === 0 ||
       this.props.startingExperiment === this.props.experiment;
 
-    if (!this.canLaunchExperiment) {
+    if (!this.props.experiment.rights.launch) {
       this.launchButtonTitle = 'Sorry, no permission to start experiment.';
     }
     else if (this.props.availableServers.length === 0) {
@@ -121,13 +110,13 @@ export default class ExperimentListElement extends React.Component {
         ref={this.wrapperRef}>
 
         <div className='list-entry-left' style={{ position: 'relative' }}>
-          <img className='entity-thumbnail' src={this.state.thumbnail} alt='' />
+          <img className='entity-thumbnail' src={exp.thumbnailURL} alt='' />
         </div>
 
         <div className='list-entry-middle flex-container up-down'>
           <div className='flex-container left-right title-line'>
             <div className='h4'>
-              {exp.configuration.name}
+              {config.name}
             </div>
             {exp.joinableServers.length > 0 ?
               <div className='exp-title-sim-info'>
@@ -136,9 +125,9 @@ export default class ExperimentListElement extends React.Component {
               : null}
           </div>
           <div>
-            {!this.state.selected && exp.configuration.description.length > SHORT_DESCRIPTION_LENGTH ?
-              exp.configuration.description.substr(0, SHORT_DESCRIPTION_LENGTH) + ' ...' :
-              exp.configuration.description}
+            {!this.state.selected && config.description.length > SHORT_DESCRIPTION_LENGTH ?
+              config.description.substr(0, SHORT_DESCRIPTION_LENGTH) + ' ...' :
+              config.description}
             <br />
           </div>
 
@@ -146,12 +135,12 @@ export default class ExperimentListElement extends React.Component {
             <div className='experiment-details' >
               <i>
                 Timeout:
-                {timeDDHHMMSS(exp.configuration.timeout)}
-                ({(exp.configuration.timeoutType === 'simulation' ? 'simulation' : 'real')} time)
+                {timeDDHHMMSS(config.timeout)}
+                ({(config.timeoutType === 'simulation' ? 'simulation' : 'real')} time)
               </i>
               <br />
               <i>
-                Brain processes: {exp.configuration.brainProcesses}
+                Brain processes: {config.brainProcesses}
               </i>
               <br />
               <div style={{ display: 'flex' }}>
@@ -167,8 +156,7 @@ export default class ExperimentListElement extends React.Component {
               /*return exp.id === pageState.selected;*/
             }}>
               <div className='btn-group' role='group' >
-                {this.canLaunchExperiment &&
-                  exp.configuration.experimentFile && exp.configuration.bibiConfSrc ?
+                {exp.rights.launch ?
                   <button
                     onClick={() => {
                       ExperimentExecutionService.instance.startNewExperiment(exp, false);
@@ -180,30 +168,27 @@ export default class ExperimentListElement extends React.Component {
                   </button>
                   : null}
 
-                {this.canLaunchExperiment && config.brainProcesses > 1 &&
-                  this.props.availableServers.length > 0 &&
-                  exp.configuration.experimentFile && exp.configuration.bibiConfSrc ?
+                {exp.rights.launch /*&& config.brainProcesses > 1*/ ?
                   <button className='btn btn-default'>
                     <FaPlay className='icon' />Launch in Single Process Mode
                   </button>
                   : null}
 
-                {this.canLaunchExperiment && this.props.availableServers.length > 1 &&
-                  exp.configuration.experimentFile && exp.configuration.bibiConfSrc ?
+                {exp.rights.launch /*&& this.props.availableServers.length > 1*/ ?
                   <button className='btn btn-default' >
                     <FaPlay className='icon' />Launch Multiple
                   </button>
                   : null}
 
                 {/* isPrivateExperiment */}
-                {this.canLaunchExperiment ?
+                {exp.rights.delete ?
                   <button className='btn btn-default'>
                     <FaTrash className='icon' />Delete
                   </button>
                   : null}
 
                 {/* Records button */}
-                {this.canLaunchExperiment ?
+                {exp.rights.launch ?
                   <button className='btn btn-default'>
                     {this.state.showRecordings ?
                       <VscTriangleUp className='icon' /> : <VscTriangleDown className='icon' />
@@ -213,14 +198,14 @@ export default class ExperimentListElement extends React.Component {
                   : null}
 
                 {/* Export button */}
-                {this.canLaunchExperiment ?
+                {exp.rights.launch ?
                   <button className='btn btn-default'>
                     <FaFileExport className='icon' />Export
                   </button>
                   : null}
 
                 {/* Simulations button */}
-                {this.canLaunchExperiment && exp.joinableServers.length > 0 ?
+                {exp.rights.launch && exp.joinableServers.length > 0 ?
                   <button className='btn btn-default'
                     onClick={() => {
                       this.setState({ showSimDetails: !this.state.showSimDetails });
@@ -233,22 +218,21 @@ export default class ExperimentListElement extends React.Component {
                   : null}
 
                 {/* Clone button */}
-                {config.canCloneExperiments && (!exp.configuration.privateStorage ||
-                  (exp.configuration.experimentFile && exp.configuration.bibiConfSrc)) ?
+                {exp.rights.clone ?
                   <button className='btn btn-default'>
                     <FaClone className='icon' />Clone
                   </button>
                   : null}
 
                 {/* Files button */}
-                {this.canLaunchExperiment ?
+                {exp.rights.launch ?
                   <button className='btn btn-default' >
                     <GoFileSubmodule className='icon' />Files
                   </button>
                   : null}
 
                 {/* Shared button */}
-                {this.canLaunchExperiment ?
+                {exp.rights.launch ?
                   <button className='btn btn-default'>
                     <FaShareAlt className='icon' />Share
                   </button>
diff --git a/src/components/experiment-list/experiment-list.js b/src/components/experiment-list/experiment-list.js
index 8ff9165..8a146aa 100644
--- a/src/components/experiment-list/experiment-list.js
+++ b/src/components/experiment-list/experiment-list.js
@@ -14,7 +14,7 @@ export default class ExperimentList extends React.Component {
             <ol>
               {this.props.experiments.map(experiment => {
                 return (
-                  <li key={experiment.id} className='nostyle'>
+                  <li key={experiment.id || experiment.configuration.id} className='nostyle'>
                     <ExperimentListElement experiment={experiment}
                       availableServers={this.props.availableServers}
                       startingExperiment={this.props.startingExperiment} />
diff --git a/src/components/experiment-overview/experiment-overview.js b/src/components/experiment-overview/experiment-overview.js
index bc68bd8..34a4555 100644
--- a/src/components/experiment-overview/experiment-overview.js
+++ b/src/components/experiment-overview/experiment-overview.js
@@ -3,7 +3,7 @@ import { Tab, Tabs, TabList, TabPanel } from 'react-tabs';
 import 'react-tabs/style/react-tabs.css';
 
 import ExperimentStorageService from '../../services/experiments/files/experiment-storage-service.js';
-import SharedExperimentsService from '../../services/experiments/files/shared-experiments-service.js';
+import PublicExperimentsService from '../../services/experiments/files/public-experiments-service.js';
 import ExperimentServerService from '../../services/experiments/execution/server-resources-service.js';
 import ExperimentExecutionService from '../../services/experiments/execution/experiment-execution-service.js';
 
@@ -17,7 +17,7 @@ export default class ExperimentOverview extends React.Component {
     super(props);
     this.state = {
       storageExperiments: [],
-      templateExperiments: [],
+      publicExperiments: [],
       joinableExperiments: [],
       availableServers: [],
       startingExperiment: undefined
@@ -25,18 +25,6 @@ export default class ExperimentOverview extends React.Component {
   }
 
   async componentDidMount() {
-    try {
-      const storageExperiments = await ExperimentStorageService.instance.getExperiments();
-      const templateExperiments = await SharedExperimentsService.instance.getExperiments();
-      this.setState({
-        storageExperiments: storageExperiments,
-        templateExperiments: templateExperiments
-      });
-    }
-    catch (error) {
-      console.error(`Failed to fetch the list of experiments. Error: ${error}`);
-    }
-
     this.onUpdateServerAvailability = this.onUpdateServerAvailability.bind(this);
     ExperimentServerService.instance.addListener(
       ExperimentServerService.EVENTS.UPDATE_SERVER_AVAILABILITY,
@@ -49,11 +37,17 @@ export default class ExperimentOverview extends React.Component {
       this.onStartExperiment
     );
 
-    this.onUpdateExperiments = this.onUpdateStorageExperiments.bind(this);
+    this.onUpdateStorageExperiments = this.onUpdateStorageExperiments.bind(this);
     ExperimentStorageService.instance.addListener(
       ExperimentStorageService.EVENTS.UPDATE_EXPERIMENTS,
       this.onUpdateStorageExperiments
     );
+
+    this.onUpdatePublicExperiments = this.onUpdatePublicExperiments.bind(this);
+    PublicExperimentsService.instance.addListener(
+      PublicExperimentsService.EVENTS.UPDATE_EXPERIMENTS,
+      this.onUpdatePublicExperiments
+    );
   }
 
   componentWillUnmount() {
@@ -71,6 +65,11 @@ export default class ExperimentOverview extends React.Component {
       ExperimentStorageService.EVENTS.UPDATE_EXPERIMENTS,
       this.onUpdateStorageExperiments
     );
+
+    PublicExperimentsService.instance.removeListener(
+      PublicExperimentsService.EVENTS.UPDATE_EXPERIMENTS,
+      this.onUpdatePublicExperiments
+    );
   }
 
   onUpdateServerAvailability(availableServers) {
@@ -82,6 +81,7 @@ export default class ExperimentOverview extends React.Component {
   };
 
   onUpdateStorageExperiments(storageExperiments) {
+    //console.info(storageExperiments);
     let joinableExperiments = storageExperiments.filter(
       experiment => experiment.joinableServers && experiment.joinableServers.length > 0);
     this.setState({
@@ -90,6 +90,13 @@ export default class ExperimentOverview extends React.Component {
     });
   }
 
+  onUpdatePublicExperiments(publicExperiments) {
+    //console.info(publicExperiments);
+    this.setState({
+      publicExperiments: publicExperiments.filter(exp => exp.configuration.maturity === 'production')
+    });
+  }
+
   render() {
     return (
       <div className='experiment-overview-wrapper'>
@@ -122,7 +129,7 @@ export default class ExperimentOverview extends React.Component {
             <h2>"Experiment Files" tab coming soon ...</h2>
           </TabPanel>
           <TabPanel>
-            <ExperimentList experiments={this.state.templateExperiments}
+            <ExperimentList experiments={this.state.publicExperiments}
               availableServers={this.state.availableServers}
               startingExperiment={this.state.startingExperiment} />
           </TabPanel>
diff --git a/src/services/experiments/execution/experiment-execution-service.js b/src/services/experiments/execution/experiment-execution-service.js
index 37ff851..924c608 100644
--- a/src/services/experiments/execution/experiment-execution-service.js
+++ b/src/services/experiments/execution/experiment-execution-service.js
@@ -2,7 +2,7 @@ import _ from 'lodash';
 
 //import NrpAnalyticsService from '../../nrp-analytics-service.js';
 import ServerResourcesService from './server-resources-service.js';
-import SimulationService from './simulation-service.js';
+import SimulationService from './running-simulation-service.js';
 import { HttpService } from '../../http-service.js';
 import { EXPERIMENT_STATE } from '../experiment-constants.js';
 
diff --git a/src/services/experiments/execution/simulation-service.js b/src/services/experiments/execution/running-simulation-service.js
similarity index 100%
rename from src/services/experiments/execution/simulation-service.js
rename to src/services/experiments/execution/running-simulation-service.js
diff --git a/src/services/experiments/experiment-constants.js b/src/services/experiments/experiment-constants.js
index b09c414..078d95c 100644
--- a/src/services/experiments/experiment-constants.js
+++ b/src/services/experiments/experiment-constants.js
@@ -8,4 +8,19 @@ const EXPERIMENT_STATE = {
   STOPPED: 'stopped'
 };
 
-module.exports = {EXPERIMENT_STATE};
\ No newline at end of file
+const EXPERIMENT_RIGHTS = {
+  PUBLICLY_SHARED: {
+    launch: false,
+    delete: false,
+    clone: true,
+    share: false
+  },
+  OWNED: {
+    launch: true,
+    delete: true,
+    clone: true,
+    share: true
+  }
+};
+
+module.exports = { EXPERIMENT_STATE, EXPERIMENT_RIGHTS };
\ No newline at end of file
diff --git a/src/services/experiments/files/experiment-storage-service.js b/src/services/experiments/files/experiment-storage-service.js
index 3097dcb..d140384 100644
--- a/src/services/experiments/files/experiment-storage-service.js
+++ b/src/services/experiments/files/experiment-storage-service.js
@@ -1,4 +1,5 @@
 import { HttpService } from '../../http-service.js';
+import { EXPERIMENT_RIGHTS } from '../experiment-constants';
 
 import endpoints from '../../proxy/data/endpoints.json';
 import config from '../../../config.json';
@@ -61,12 +62,14 @@ class ExperimentStorageService extends HttpService {
    * @param {boolean} forceUpdate forces an update of the list
    * @return experiments - the list of template experiments
    */
+  //TODO: between storage experiments and shared experiments, can this be unified?
+  // move to experiment-configuration-service?
   async getExperiments(forceUpdate = false) {
     if (!this.experiments || forceUpdate) {
-      let response = await this.httpRequestGET(storageExperimentsURL);
-      this.experiments = await response.json();
-      this.sortExperiments();
-      await this.fillExperimentDetails();
+      let experimentList = await (await this.httpRequestGET(storageExperimentsURL)).json();
+      this.sortExperiments(experimentList);
+      await this.fillExperimentDetails(experimentList);
+      this.experiments = experimentList;
       this.emit(ExperimentStorageService.EVENTS.UPDATE_EXPERIMENTS, this.experiments);
     }
 
@@ -80,6 +83,8 @@ class ExperimentStorageService extends HttpService {
    *
    * @returns {Blob} image object
    */
+  //TODO: between storage experiments and shared experiments, can this be unified?
+  // move to experiment-configuration-service?
   async getThumbnail(experimentName, thumbnailFilename) {
     return await this.getBlob(experimentName, thumbnailFilename, true);
   }
@@ -87,8 +92,10 @@ class ExperimentStorageService extends HttpService {
   /**
    * Sort the local list of experiments alphabetically.
    */
-  sortExperiments() {
-    this.experiments = this.experiments.sort(
+  //TODO: between storage experiments and shared experiments, can this be unified?
+  // move to experiment-configuration-service?
+  sortExperiments(experimentList) {
+    experimentList = experimentList.sort(
       (a, b) => {
         let nameA = a.configuration.name.toLowerCase();
         let nameB = b.configuration.name.toLowerCase();
@@ -106,12 +113,26 @@ class ExperimentStorageService extends HttpService {
   /**
    * 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;
+  //TODO: between storage experiments and shared experiments, can this be unified?
+  // move to experiment-configuration-service?
+  async fillExperimentDetails(experimentList) {
+    let experimentUpdates = [];
+    experimentList.forEach(experiment => {
+      if (!experiment.configuration.brainProcesses && experiment.configuration.bibiConfSrc) {
+        experiment.configuration.brainProcesses = 1;
       }
+
+      // retrieve the experiment thumbnail
+      experimentUpdates.push(this.getThumbnail(experiment.name, experiment.configuration.thumbnail)
+        .then(thumbnail => {
+          experiment.thumbnailURL = URL.createObjectURL(thumbnail);
+        }));
+
+      experiment.rights = EXPERIMENT_RIGHTS.OWNED;
+      experiment.rights.launch = (experiment.private && experiment.owned) || !experiment.private;
     });
+
+    return Promise.all(experimentUpdates);
   }
 
   /**
diff --git a/src/services/experiments/files/shared-experiments-service.js b/src/services/experiments/files/public-experiments-service.js
similarity index 50%
rename from src/services/experiments/files/shared-experiments-service.js
rename to src/services/experiments/files/public-experiments-service.js
index d668ef1..306c22f 100644
--- a/src/services/experiments/files/shared-experiments-service.js
+++ b/src/services/experiments/files/public-experiments-service.js
@@ -1,8 +1,10 @@
 import { HttpService } from '../../http-service.js';
+import { EXPERIMENT_RIGHTS } from '../experiment-constants';
 
 import endpoints from '../../proxy/data/endpoints.json';
 import config from '../../../config.json';
 const experimentsURL = `${config.api.proxy.url}${endpoints.proxy.experiments.url}`;
+const experimentImageURL = `${config.api.proxy.url}${endpoints.proxy.experimentImage.url}`;
 const cloneURL = `${config.api.proxy.url}${endpoints.proxy.storage.clone.url}`;
 
 let _instance = null;
@@ -12,7 +14,7 @@ const SINGLETON_ENFORCER = Symbol();
  * Service that handles storage experiment files and configurations given
  * that the user has authenticated successfully.
  */
-class SharedExperimentsService extends HttpService {
+class PublicExperimentsService extends HttpService {
   constructor(enforcer) {
     super();
     if (enforcer !== SINGLETON_ENFORCER) {
@@ -28,7 +30,7 @@ class SharedExperimentsService extends HttpService {
 
   static get instance() {
     if (_instance == null) {
-      _instance = new SharedExperimentsService(SINGLETON_ENFORCER);
+      _instance = new PublicExperimentsService(SINGLETON_ENFORCER);
     }
 
     return _instance;
@@ -43,7 +45,7 @@ class SharedExperimentsService extends HttpService {
       () => {
         this.getExperiments(true);
       },
-      SharedExperimentsService.CONSTANTS.INTERVAL_POLL_EXPERIMENTS
+      PublicExperimentsService.CONSTANTS.INTERVAL_POLL_EXPERIMENTS
     );
   }
 
@@ -62,14 +64,15 @@ class SharedExperimentsService extends HttpService {
    * @param {boolean} forceUpdate forces an update of the list
    * @return experiments - the list of template experiments
    */
+  //TODO: between storage experiments and shared experiments, can this be unified?
+  // move to experiment-configuration-service?
   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);
+      let experimentList = Object.values(await (await this.httpRequestGET(experimentsURL)).json());
+      this.sortExperiments(experimentList);
+      await this.fillExperimentDetails(experimentList);
+      this.experiments = experimentList;
+      this.emit(PublicExperimentsService.EVENTS.UPDATE_EXPERIMENTS, this.experiments);
     }
 
     return this.experiments;
@@ -78,8 +81,10 @@ class SharedExperimentsService extends HttpService {
   /**
    * Sort the local list of experiments alphabetically.
    */
-  sortExperiments() {
-    this.experiments = this.experiments.sort(
+  //TODO: between storage experiments and shared experiments, can this be unified?
+  // move to experiment-configuration-service?
+  sortExperiments(experimentList) {
+    experimentList = experimentList.sort(
       (a, b) => {
         let nameA = a.configuration.name.toLowerCase();
         let nameB = b.configuration.name.toLowerCase();
@@ -97,12 +102,40 @@ class SharedExperimentsService extends HttpService {
   /**
    * 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;
+  //TODO: between storage experiments and shared experiments, can this be unified?
+  // move to experiment-configuration-service?
+  async fillExperimentDetails(experimentList) {
+    let experimentUpdates = [];
+    experimentList.forEach(experiment => {
+      if (!experiment.configuration.brainProcesses && experiment.configuration.bibiConfSrc) {
+        experiment.configuration.brainProcesses = 1;
       }
+
+      // retrieve the experiment thumbnail
+      experimentUpdates.push(this.getThumbnailURL(experiment.configuration.id).then(thumbnailURL => {
+        if (thumbnailURL) {
+          experiment.thumbnailURL = thumbnailURL; //URL.createObjectURL(thumbnail);
+        }
+      }));
+
+      experiment.rights = EXPERIMENT_RIGHTS.PUBLICLY_SHARED;
     });
+
+    return Promise.all(experimentUpdates);
+  }
+
+  /**
+   * Retrieves the thumbnail image for a given experiment.
+   * @param {string} experimentName - name of the experiment
+   * @param {string} thumbnailFilename - name of the thumbnail file
+   *
+   * @returns {Blob} image object
+   */
+  //TODO: between storage experiments and shared experiments, can this be unified?
+  // move to experiment-configuration-service?
+  async getThumbnailURL(experimentName) {
+    let url = experimentImageURL + '/' + experimentName;
+    return url;
   }
 
   /**
@@ -110,18 +143,18 @@ class SharedExperimentsService extends HttpService {
    * @param {Object} experiment The Experiment configuration
    */
   async cloneExperiment(experiment) {
-    let expPath = experiment.configuration.experimentConfiguration;
-    let response = await this.httpRequestPOST(cloneURL, { expPath });
+    let experimentConfigFilepath = experiment.configuration.experimentConfiguration;
+    let response = await this.httpRequestPOST(cloneURL, { expPath: experimentConfigFilepath });
     console.info(response);
   }
 }
 
-SharedExperimentsService.EVENTS = Object.freeze({
+PublicExperimentsService.EVENTS = Object.freeze({
   UPDATE_EXPERIMENTS: 'UPDATE_EXPERIMENTS'
 });
 
-SharedExperimentsService.CONSTANTS = Object.freeze({
+PublicExperimentsService.CONSTANTS = Object.freeze({
   INTERVAL_POLL_EXPERIMENTS: 5000
 });
 
-export default SharedExperimentsService;
+export default PublicExperimentsService;
-- 
GitLab