You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airavata.apache.org by ma...@apache.org on 2022/02/17 00:43:22 UTC

[airavata-django-portal] 01/16: AIRAVATA-3557 Refactored experiment summary to use vuex store

This is an automated email from the ASF dual-hosted git repository.

machristie pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git

commit 07655c44fafa8f7d9a859cb57770f80266940dde
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Thu Jan 13 15:38:08 2022 -0500

    AIRAVATA-3557 Refactored experiment summary to use vuex store
---
 django_airavata/apps/api/serializers.py            |   1 +
 .../js/components/experiment/ExperimentSummary.vue | 108 +++++-------------
 .../js/containers/ViewExperimentContainer.vue      |  52 ---------
 .../js/entry-view-experiment.js                    |  71 +++++++-----
 .../django_airavata_workspace/js/store/index.js    |  14 +++
 .../js/store/modules/view-experiment.js            | 124 +++++++++++++++++++++
 6 files changed, 211 insertions(+), 159 deletions(-)

diff --git a/django_airavata/apps/api/serializers.py b/django_airavata/apps/api/serializers.py
index 6eb8034..4c6297e 100644
--- a/django_airavata/apps/api/serializers.py
+++ b/django_airavata/apps/api/serializers.py
@@ -517,6 +517,7 @@ class FullExperimentSerializer(serializers.Serializer):
         lookup_field='experimentId',
         lookup_url_kwarg='experiment_id')
     experiment = ExperimentSerializer()
+    experimentId = serializers.CharField(read_only=True)
     outputDataProducts = DataProductSerializer(many=True, read_only=True)
     inputDataProducts = DataProductSerializer(many=True, read_only=True)
     applicationModule = ApplicationModuleSerializer(read_only=True)
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/ExperimentSummary.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/ExperimentSummary.vue
index 2813e55..c622480 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/ExperimentSummary.vue
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/ExperimentSummary.vue
@@ -1,5 +1,5 @@
 <template>
-  <div>
+  <div v-if="localFullExperiment">
     <div class="row">
       <div class="col-auto mr-auto">
         <h1 class="h4 mb-4">
@@ -12,15 +12,15 @@
           Edit
           <i class="fa fa-edit" aria-hidden="true"></i>
         </b-link>
-        <b-link v-if="isLaunchable" class="btn btn-primary" @click="launch">
+        <b-link v-if="isLaunchable" class="btn btn-primary" @click="onLaunch">
           Launch
           <i class="fa fa-running" aria-hidden="true"></i>
         </b-link>
-        <b-btn v-if="isClonable" variant="primary" @click="clone">
+        <b-btn v-if="isClonable" variant="primary" @click="onClone">
           Clone
           <i class="fa fa-copy" aria-hidden="true"></i>
         </b-btn>
-        <b-btn v-if="isCancelable" variant="primary" @click="cancel">
+        <b-btn v-if="isCancelable" variant="primary" @click="onCancel">
           Cancel
           <i class="fa fa-window-close" aria-hidden="true"></i>
         </b-btn>
@@ -293,7 +293,7 @@
 </template>
 
 <script>
-import { models, services } from "django-airavata-api";
+import { models } from "django-airavata-api";
 import { components, notifications } from "django-airavata-common-ui";
 import OutputDisplayContainer from "./output-displays/OutputDisplayContainer";
 import urls from "../../utils/urls";
@@ -301,24 +301,10 @@ import urls from "../../utils/urls";
 import moment from "moment";
 import ExperimentStorageViewContainer from "../storage/ExperimentStorageViewContainer.vue";
 import DataProductViewer from "django-airavata-common-ui/js/components/DataProductViewer.vue";
+import { mapActions, mapState } from "vuex";
 
 export default {
   name: "experiment-summary",
-  props: {
-    fullExperiment: {
-      type: models.FullExperiment,
-      required: true,
-    },
-    launching: {
-      type: Boolean,
-      default: false,
-    },
-  },
-  data() {
-    return {
-      localFullExperiment: this.fullExperiment.clone(),
-    };
-  },
   components: {
     "clipboard-copy-link": components.ClipboardCopyLink,
     "share-button": components.ShareButton,
@@ -327,6 +313,14 @@ export default {
     DataProductViewer,
   },
   computed: {
+    ...mapState("viewExperiment", [
+      "fullExperiment",
+      "launching",
+      "clonedExperiment",
+    ]),
+    localFullExperiment() {
+      return this.fullExperiment;
+    },
     inputDataProducts() {
       const result = {};
       if (
@@ -410,57 +404,23 @@ export default {
     },
   },
   methods: {
-    loadExperiment: function () {
-      return services.FullExperimentService.retrieve(
-        { lookup: this.localFullExperiment.experiment.experimentId },
-        { ignoreErrors: true, showSpinner: false }
-      ).then((exp) => (this.localFullExperiment = exp));
-    },
-    initPollingExperiment: function () {
-      var pollExperiment = function () {
-        if (
-          (this.launching &&
-            !this.localFullExperiment.experiment.hasLaunched) ||
-          this.localFullExperiment.experiment.isProgressing
-        ) {
-          this.loadExperiment()
-            .then(() => {
-              setTimeout(pollExperiment.bind(this), 3000);
-            })
-            .catch(() => {
-              // Wait 30 seconds after an error and then try again
-              setTimeout(pollExperiment.bind(this), 30000);
-            });
-        }
-      }.bind(this);
-      setTimeout(pollExperiment, 3000);
-    },
-    clone() {
-      services.ExperimentService.clone({
-        lookup: this.experiment.experimentId,
-      }).then((clonedExperiment) => {
-        urls.navigateToEditExperiment(clonedExperiment);
-      });
+    ...mapActions("viewExperiment", ["clone", "launch", "cancel"]),
+    async onClone() {
+      await this.clone();
+      urls.navigateToEditExperiment(this.clonedExperiment);
     },
-    launch() {
-      services.ExperimentService.launch({
-        lookup: this.experiment.experimentId,
-      }).then(() => {
-        this.$emit("Launched");
-      });
+    onLaunch() {
+      this.launch();
     },
-    cancel() {
-      services.ExperimentService.cancel({
-        lookup: this.experiment.experimentId,
-      }).then(() => {
-        notifications.NotificationList.add(
-          new notifications.Notification({
-            type: "SUCCESS",
-            message: "Cancel-experiment requested",
-            duration: 5,
-          })
-        );
-      });
+    async onCancel() {
+      await this.cancel();
+      notifications.NotificationList.add(
+        new notifications.Notification({
+          type: "SUCCESS",
+          message: "Cancel-experiment requested",
+          duration: 5,
+        })
+      );
     },
     getDataProducts(io, collection) {
       if (!io.value || !collection) {
@@ -483,15 +443,5 @@ export default {
         : [];
     },
   },
-  watch: {
-    launching: function (val) {
-      if (val == true) {
-        this.initPollingExperiment();
-      }
-    },
-  },
-  mounted: function () {
-    this.initPollingExperiment();
-  },
 };
 </script>
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/containers/ViewExperimentContainer.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/containers/ViewExperimentContainer.vue
deleted file mode 100644
index 2570701..0000000
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/containers/ViewExperimentContainer.vue
+++ /dev/null
@@ -1,52 +0,0 @@
-<template>
-  <experiment-summary
-    v-if="fullExperiment"
-    :fullExperiment="fullExperiment"
-    :launching="launch"
-    @Launched="handleLaunchedExperiment"
-    :key="componentKey"
-  >
-  </experiment-summary>
-</template>
-
-<script>
-import { services } from "django-airavata-api";
-import ExperimentSummary from "../components/experiment/ExperimentSummary.vue";
-
-export default {
-  name: "view-experiment-container",
-  props: {
-    initialFullExperimentData: {
-      required: true,
-    },
-    launching: {
-      type: Boolean,
-      default: false,
-    },
-  },
-  data() {
-    return {
-      fullExperiment: null,
-      componentKey: 0,
-      launch: this.launching,
-    };
-  },
-  components: {
-    ExperimentSummary,
-  },
-  methods: {
-    handleLaunchedExperiment: function () {
-      this.launch = true;
-      this.componentKey = 1;
-    },
-  },
-  computed: {},
-  beforeMount: function () {
-    services.FullExperimentService.retrieve({
-      lookup: this.initialFullExperimentData.experimentId,
-      initialFullExperimentData: this.initialFullExperimentData,
-    }).then((exp) => (this.fullExperiment = exp));
-  },
-};
-</script>
-<style></style>
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/entry-view-experiment.js b/django_airavata/apps/workspace/static/django_airavata_workspace/js/entry-view-experiment.js
index c6289e9..4a0ab0d 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/entry-view-experiment.js
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/entry-view-experiment.js
@@ -1,29 +1,44 @@
-import { components, entry } from "django-airavata-common-ui";
-import ViewExperimentContainer from "./containers/ViewExperimentContainer.vue";
+import { components, errors } from "django-airavata-common-ui";
+import { mapActions } from "vuex";
+import ExperimentSummary from "./components/experiment/ExperimentSummary.vue";
+import store from "./store";
 
-entry((Vue) => {
-  new Vue({
-    render(h) {
-      return h(components.MainLayout, [
-        h(ViewExperimentContainer, {
-          props: {
-            initialFullExperimentData: this.fullExperimentData,
-            launching: this.launching,
-          },
-        }),
-      ]);
-    },
-    data() {
-      return {
-        fullExperimentData: null,
-        launching: false,
-      };
-    },
-    beforeMount() {
-      this.fullExperimentData = JSON.parse(this.$el.dataset.fullExperimentData);
-      if ("launching" in this.$el.dataset) {
-        this.launching = JSON.parse(this.$el.dataset.launching);
-      }
-    },
-  }).$mount("#view-experiment");
-});
+import Vue from "vue";
+import BootstrapVue from "bootstrap-vue";
+import AsyncComputed from "vue-async-computed";
+
+errors.GlobalErrorHandler.init();
+
+// This is imported globally on the website (see main.js) so no need to include
+// it again in this view
+// import 'bootstrap/dist/css/bootstrap.css'
+import "bootstrap-vue/dist/bootstrap-vue.css";
+
+// Common Vue configuration
+Vue.use(BootstrapVue);
+Vue.use(AsyncComputed);
+
+// TODO: fix entry so that we can still get common configuration when using Vuex
+// entry((Vue) => {
+new Vue({
+  store,
+  render(h) {
+    return h(components.MainLayout, [h(ExperimentSummary)]);
+  },
+  async beforeMount() {
+    const fullExperimentData = JSON.parse(this.$el.dataset.fullExperimentData);
+    this.setInitialFullExperimentData({ fullExperimentData });
+    if ("launching" in this.$el.dataset) {
+      const launching = JSON.parse(this.$el.dataset.launching);
+      this.setLaunching({ launching });
+    }
+  },
+  methods: {
+    ...mapActions("viewExperiment", [
+      "setInitialFullExperimentData",
+      "setLaunching",
+    ]),
+  },
+}).$mount("#view-experiment");
+
+// });
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/store/index.js b/django_airavata/apps/workspace/static/django_airavata_workspace/js/store/index.js
new file mode 100644
index 0000000..b997094
--- /dev/null
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/store/index.js
@@ -0,0 +1,14 @@
+import Vue from "vue";
+import Vuex from "vuex";
+import viewExperiment from "./modules/view-experiment";
+
+Vue.use(Vuex);
+
+const debug = process.env.NODE_ENV !== "production";
+
+export default new Vuex.Store({
+  modules: {
+    viewExperiment,
+  },
+  strict: debug,
+});
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/store/modules/view-experiment.js b/django_airavata/apps/workspace/static/django_airavata_workspace/js/store/modules/view-experiment.js
new file mode 100644
index 0000000..72e09c8
--- /dev/null
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/store/modules/view-experiment.js
@@ -0,0 +1,124 @@
+import { services } from "django-airavata-api";
+
+// const PROMISES = {
+//   workspacePreferences: null,
+// };
+export const mutations = {
+  setFullExperiment(state, { fullExperiment }) {
+    state.fullExperiment = fullExperiment;
+  },
+  setLaunching(state, { launching }) {
+    state.launching = launching;
+  },
+  startPolling(state) {
+    state.polling = true;
+  },
+  stopPolling(state) {
+    state.polling = false;
+  },
+  setClonedExperiment(state, { clonedExperiment }) {
+    state.clonedExperiment = clonedExperiment;
+  },
+};
+export const actions = {
+  async setInitialFullExperimentData(
+    { commit, dispatch },
+    { fullExperimentData }
+  ) {
+    const fullExperiment = await services.FullExperimentService.retrieve({
+      lookup: fullExperimentData.experimentId,
+      initialFullExperimentData: fullExperimentData,
+    });
+    commit("setFullExperiment", { fullExperiment });
+    dispatch("initPollingExperiment");
+  },
+  setFullExperiment({ dispatch, commit }, { fullExperiment }) {
+    commit("setFullExperiment", { fullExperiment });
+    dispatch("initPollingExperiment");
+  },
+  setLaunching({ dispatch, commit }, { launching }) {
+    commit("setLaunching", { launching });
+    if (launching) {
+      dispatch("initPollingExperiment");
+    }
+  },
+  async loadExperiment({ commit }, { experimentId }) {
+    const fullExperiment = await services.FullExperimentService.retrieve(
+      { lookup: experimentId },
+      { ignoreErrors: true, showSpinner: false }
+    );
+    commit("setFullExperiment", { fullExperiment });
+  },
+  async pollExperiment({ commit, dispatch, state }) {
+    if (!state.fullExperiment) {
+      return;
+    }
+    if (
+      (state.launching && !state.fullExperiment.experiment.hasLaunched) ||
+      state.fullExperiment.experiment.isProgressing
+    ) {
+      commit("startPolling");
+      try {
+        await dispatch("loadExperiment", {
+          experimentId: state.fullExperiment.experimentId,
+        });
+        setTimeout(() => {
+          dispatch("pollExperiment");
+        }, 3000);
+      } catch (error) {
+        // Wait 30 seconds after an error and then try again
+        setTimeout(() => {
+          dispatch("pollExperiment");
+        }, 30000);
+      }
+    } else {
+      commit("stopPolling");
+    }
+  },
+  initPollingExperiment({ dispatch, getters }) {
+    // Only start polling if we aren't already polling
+    if (!getters.isPolling) {
+      dispatch("pollExperiment");
+    }
+  },
+  async clone({ commit, getters }) {
+    const clonedExperiment = await services.ExperimentService.clone({
+      lookup: getters.experimentId,
+    });
+    commit("setClonedExperiment", { clonedExperiment });
+  },
+  async launch({ dispatch, getters }) {
+    try {
+      await services.ExperimentService.launch({
+        lookup: getters.experimentId,
+      });
+      dispatch("setLaunching", { launching: true });
+    } catch (error) {
+      // TODO: handle launch error
+    }
+  },
+  async cancel({ dispatch, getters }) {
+    await services.ExperimentService.cancel({
+      lookup: getters.experimentId,
+    });
+    dispatch("loadExperiment", { experimentId: getters.experimentId });
+  },
+};
+
+export const getters = {
+  isPolling: (state) => state.polling,
+  experimentId: (state) => state.fullExperiment.experimentId,
+};
+
+export default {
+  namespaced: true,
+  state: {
+    fullExperiment: null,
+    launching: false,
+    polling: false,
+    clonedExperiment: null,
+  },
+  mutations,
+  actions,
+  getters,
+};