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 2019/05/24 13:08:07 UTC

[airavata-django-portal] 01/03: AIRAVATA-3034 WIP: initial user storage browser

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

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

commit bb35ab03ec7348e16051bdc145c57cdda7ef830a
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Thu May 23 12:18:47 2019 -0400

    AIRAVATA-3034 WIP: initial user storage browser
---
 django_airavata/apps/api/serializers.py            |  1 +
 .../api/static/django_airavata_api/js/index.js     |  1 +
 .../js/models/UserStorageDirectory.js              |  9 +++
 .../js/models/UserStorageFile.js                   |  9 +++
 .../js/models/UserStoragePath.js                   | 22 +++++++
 .../django_airavata_api/js/service_config.js       | 12 ++++
 .../js/services/ServiceFactory.js                  | 52 +++++++++++-----
 django_airavata/apps/workspace/package.json        |  3 +-
 .../components/storage/UserStoragePathViewer.vue   | 72 ++++++++++++++++++++++
 .../js/containers/UserStorageContainer.vue         | 23 +++++++
 .../js/entry-user-storage.js                       | 26 ++++++++
 .../templates/django_airavata_workspace/base.html  |  3 +
 django_airavata/apps/workspace/urls.py             |  1 +
 django_airavata/apps/workspace/views.py            |  8 +++
 django_airavata/apps/workspace/vue.config.js       |  1 +
 15 files changed, 227 insertions(+), 16 deletions(-)

diff --git a/django_airavata/apps/api/serializers.py b/django_airavata/apps/api/serializers.py
index e1aff77..2f7c726 100644
--- a/django_airavata/apps/api/serializers.py
+++ b/django_airavata/apps/api/serializers.py
@@ -764,6 +764,7 @@ class UserStorageFileSerializer(serializers.Serializer):
 
 class UserStorageDirectorySerializer(serializers.Serializer):
     name = serializers.CharField()
+    path = serializers.CharField()
     url = FullyEncodedHyperlinkedIdentityField(
         view_name='django_airavata_api:user-storage-items',
         lookup_field='path',
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/index.js b/django_airavata/apps/api/static/django_airavata_api/js/index.js
index c4d24c8..f410066 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/index.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/index.js
@@ -117,6 +117,7 @@ const services = {
   UnicoreDataMovementService,
   UnicoreJobSubmissionService,
   UserProfileService,
+  UserStoragePathService: ServiceFactory.service("UserStoragePaths"),
   WorkspacePreferencesService: ServiceFactory.service("WorkspacePreferences")
 };
 
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/models/UserStorageDirectory.js b/django_airavata/apps/api/static/django_airavata_api/js/models/UserStorageDirectory.js
new file mode 100644
index 0000000..8c7f3a0
--- /dev/null
+++ b/django_airavata/apps/api/static/django_airavata_api/js/models/UserStorageDirectory.js
@@ -0,0 +1,9 @@
+import BaseModel from "./BaseModel";
+
+const FIELDS = ["name", "path"];
+
+export default class UserStorageDirectory extends BaseModel {
+  constructor(data = {}) {
+    super(FIELDS, data);
+  }
+}
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/models/UserStorageFile.js b/django_airavata/apps/api/static/django_airavata_api/js/models/UserStorageFile.js
new file mode 100644
index 0000000..dfcf55c
--- /dev/null
+++ b/django_airavata/apps/api/static/django_airavata_api/js/models/UserStorageFile.js
@@ -0,0 +1,9 @@
+import BaseModel from "./BaseModel";
+
+const FIELDS = ["name", "downloadURL", "dataProductURI"];
+
+export default class UserStorageFile extends BaseModel {
+  constructor(data = {}) {
+    super(FIELDS, data);
+  }
+}
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/models/UserStoragePath.js b/django_airavata/apps/api/static/django_airavata_api/js/models/UserStoragePath.js
new file mode 100644
index 0000000..108a7fd
--- /dev/null
+++ b/django_airavata/apps/api/static/django_airavata_api/js/models/UserStoragePath.js
@@ -0,0 +1,22 @@
+import BaseModel from "./BaseModel";
+import UserStorageDirectory from "./UserStorageDirectory";
+import UserStorageFile from "./UserStorageFile";
+
+const FIELDS = [
+  {
+    name: "files",
+    type: UserStorageFile,
+    list: true
+  },
+  {
+    name: "directories",
+    type: UserStorageDirectory,
+    list: true
+  }
+];
+
+export default class UserStoragePath extends BaseModel {
+  constructor(data = {}) {
+    super(FIELDS, data);
+  }
+}
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/service_config.js b/django_airavata/apps/api/static/django_airavata_api/js/service_config.js
index 9cdc380..36103c4 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/service_config.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/service_config.js
@@ -18,6 +18,7 @@ import SharedEntity from "./models/SharedEntity";
 import StoragePreference from "./models/StoragePreference";
 import StorageResourceDescription from "./models/StorageResourceDescription";
 import UserProfile from "./models/UserProfile";
+import UserStoragePath from "./models/UserStoragePath";
 import WorkspacePreferences from "./models/WorkspacePreferences";
 
 /*
@@ -273,6 +274,17 @@ export default {
     viewSet: ["list"],
     modelClass: UserProfile
   },
+  UserStoragePaths: {
+    url: "/api/user-storage",
+    methods: {
+      get: {
+        url: "/api/user-storage/<path>",
+        requestType: "get",
+        modelClass: UserStoragePath,
+        encodePathParams: false
+      }
+    },
+  },
   WorkspacePreferences: {
     url: "/api/workspace-preferences",
     methods: {
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/services/ServiceFactory.js b/django_airavata/apps/api/static/django_airavata_api/js/services/ServiceFactory.js
index c409d33..9270e04 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/services/ServiceFactory.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/services/ServiceFactory.js
@@ -40,6 +40,10 @@ const parseServiceMapping = function(serviceConfiguration) {
   let modelClass = serviceConfiguration.modelClass;
   let queryParams = serviceConfiguration.queryParams;
   let defaultPagination = serviceConfiguration.pagination ? true : false;
+  let encodePathParams =
+    "encodePathParams" in serviceConfiguration
+      ? serviceConfiguration.encodePathParams
+      : true;
   for (let viewSetFunction of viewSetFunctions) {
     let viewSetFunctionName = viewSetFunction;
     let pagination = defaultPagination;
@@ -56,7 +60,8 @@ const parseServiceMapping = function(serviceConfiguration) {
           requestType: getKey,
           modelClass: modelClass,
           queryParams: queryParams,
-          initialDataParam: viewSetFunction.initialDataParam
+          initialDataParam: viewSetFunction.initialDataParam,
+          encodePathParams: encodePathParams
         };
         break;
       case "create":
@@ -67,7 +72,8 @@ const parseServiceMapping = function(serviceConfiguration) {
             name: "data"
           },
           modelClass: modelClass,
-          queryParams: queryParams
+          queryParams: queryParams,
+          encodePathParams: encodePathParams
         };
         break;
       case "update":
@@ -78,7 +84,8 @@ const parseServiceMapping = function(serviceConfiguration) {
             name: "data"
           },
           modelClass: modelClass,
-          queryParams: queryParams
+          queryParams: queryParams,
+          encodePathParams: encodePathParams
         };
         break;
       case "retrieve":
@@ -87,7 +94,8 @@ const parseServiceMapping = function(serviceConfiguration) {
           requestType: getKey,
           modelClass: modelClass,
           queryParams: queryParams,
-          initialDataParam: viewSetFunction.initialDataParam
+          initialDataParam: viewSetFunction.initialDataParam,
+          encodePathParams: encodePathParams
         };
         break;
       case "delete":
@@ -95,7 +103,8 @@ const parseServiceMapping = function(serviceConfiguration) {
           url: url + "<lookup>/",
           requestType: delKey,
           modelClass: modelClass,
-          queryParams: queryParams
+          queryParams: queryParams,
+          encodePathParams: encodePathParams
         };
         break;
       default:
@@ -115,7 +124,11 @@ const parseServiceMapping = function(serviceConfiguration) {
         pagination:
           "pagination" in methodConfig
             ? methodConfig.pagination
-            : defaultPagination
+            : defaultPagination,
+        encodePathParams:
+          "encodePathParams" in serviceConfiguration
+            ? serviceConfiguration.encodePathParams
+            : true
       };
       if ("modelClass" in methodConfig) {
         mappedFunctions[methodName]["modelClass"] = methodConfig.modelClass;
@@ -198,7 +211,10 @@ class ServiceFactory {
       let queryParamsMapping = parseQueryMapping(config.queryParams);
       serviceObj[functionName] = function(
         params = {},
-        { ignoreErrors, showSpinner } = { ignoreErrors: false, showSpinner: true }
+        { ignoreErrors, showSpinner } = {
+          ignoreErrors: false,
+          showSpinner: true
+        }
       ) {
         let url = config.url;
         let paramKeys = Object.keys(params);
@@ -210,12 +226,16 @@ class ServiceFactory {
             if (pathParamsMapping[paramKey] !== null) {
               url = url.replace(
                 "<" + pathParamsMapping[paramKey] + ":" + paramKey + ">",
-                encodeURIComponent(params[paramKey])
+                config.encodePathParams
+                  ? encodeURIComponent(params[paramKey])
+                  : params[paramKey]
               );
             } else {
               url = url.replace(
                 "<" + paramKey + ">",
-                encodeURIComponent(params[paramKey])
+                config.encodePathParams
+                  ? encodeURIComponent(params[paramKey])
+                  : params[paramKey]
               );
             }
           } else if (paramKey in queryParamsMapping) {
@@ -265,14 +285,16 @@ class ServiceFactory {
             if (initialData) {
               return Promise.resolve(paginationHandler(initialData));
             } else {
-              return FetchUtils.get(url, queryParams, { ignoreErrors, showSpinner }).then(
-                paginationHandler
-              );
+              return FetchUtils.get(url, queryParams, {
+                ignoreErrors,
+                showSpinner
+              }).then(paginationHandler);
             }
           case putKey:
-            return FetchUtils.put(url, bodyParams, { ignoreErrors, showSpinner }).then(
-              resultHandler
-            );
+            return FetchUtils.put(url, bodyParams, {
+              ignoreErrors,
+              showSpinner
+            }).then(resultHandler);
           case delKey:
             return FetchUtils.delete(url, { ignoreErrors, showSpinner });
         }
diff --git a/django_airavata/apps/workspace/package.json b/django_airavata/apps/workspace/package.json
index a1d8f30..833f1dc 100644
--- a/django_airavata/apps/workspace/package.json
+++ b/django_airavata/apps/workspace/package.json
@@ -20,7 +20,8 @@
     "django-airavata-common-ui": "file:../../static/common",
     "django-airavata-workspace-plugin-api": "file:django-airavata-workspace-plugin-api",
     "moment": "^2.21.0",
-    "vue": "^2.5.22"
+    "vue": "^2.5.22",
+    "vue-router": "^3.0.6"
   },
   "devDependencies": {
     "@vue/cli-plugin-babel": "^3.1.1",
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/storage/UserStoragePathViewer.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/storage/UserStoragePathViewer.vue
new file mode 100644
index 0000000..7b3e168
--- /dev/null
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/storage/UserStoragePathViewer.vue
@@ -0,0 +1,72 @@
+<template>
+  <b-table :fields="fields" :items="items">
+    <template slot="name" slot-scope="data">
+      <router-link v-if="data.item.type === 'dir'" :to="'/~/' + data.item.path">{{ data.item.name }}</router-link>
+      <b-link v-else :href="data.item.downloadURL">{{ data.item.name }}</b-link>
+    </template>
+  </b-table>
+</template>
+<script>
+import { services } from "django-airavata-api";
+
+export default {
+  name: "user-storage-path-viewer",
+  props: {
+    path: {
+      type: String,
+      required: true
+    }
+  },
+  data() {
+    return {
+      userStoragePath: null,
+      fields: [
+        {
+          label: "Name",
+          key: "name"
+        }
+      ]
+    };
+  },
+  computed: {
+    items() {
+      if (this.userStoragePath) {
+
+        const dirs = this.userStoragePath.directories.map(d => {
+          return {
+            name: d.name,
+            path: d.path,
+            type: "dir"
+          }
+        });
+        const files = this.userStoragePath.files.map(f => {
+          return {
+            name: f.name,
+            type: "file",
+            downloadURL: f.downloadURL
+          }
+        })
+        return dirs.concat(files);
+      } else {
+        return [];
+      }
+    }
+  },
+  methods: {
+    loadUserStoragePath(path) {
+      return services.UserStoragePathService.get({ path }).then(result => {
+        this.userStoragePath = result;
+      });
+    }
+  },
+  created() {
+    this.loadUserStoragePath(this.path);
+  },
+  watch: {
+    path(newValue) {
+      this.loadUserStoragePath(newValue);
+    }
+  }
+};
+</script>
+
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/containers/UserStorageContainer.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/containers/UserStorageContainer.vue
new file mode 100644
index 0000000..13a553a
--- /dev/null
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/containers/UserStorageContainer.vue
@@ -0,0 +1,23 @@
+<template>
+  <router-view :path="storagePath"></router-view>
+</template>
+
+<script>
+export default {
+  name: "user-storage-container",
+  computed: {
+    storagePath() {
+      if (this.$route.path.startsWith("/")) {
+        return this.$route.path.substring(1);
+      } else {
+        return this.$route.path;
+      }
+    }
+  },
+  watch: {
+    // '$route' (to, from) {
+
+    // }
+  }
+};
+</script>
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/entry-user-storage.js b/django_airavata/apps/workspace/static/django_airavata_workspace/js/entry-user-storage.js
new file mode 100644
index 0000000..96230cd
--- /dev/null
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/entry-user-storage.js
@@ -0,0 +1,26 @@
+import { components, entry } from "django-airavata-common-ui";
+import UserStorageContainer from "./containers/UserStorageContainer.vue";
+import UserStoragePathViewer from "./components/storage/UserStoragePathViewer.vue";
+
+import VueRouter from "vue-router";
+
+const routes = [
+  {
+    path: "*",
+    component: UserStoragePathViewer
+  }
+];
+const router = new VueRouter({
+  mode: "history",
+  base: "/workspace/storage",
+  routes: routes
+});
+entry(Vue => {
+  Vue.use(VueRouter);
+  new Vue({
+    render(h) {
+      return h(components.MainLayout, [h(UserStorageContainer)]);
+    },
+    router
+  }).$mount("#user-storage");
+});
diff --git a/django_airavata/apps/workspace/templates/django_airavata_workspace/base.html b/django_airavata/apps/workspace/templates/django_airavata_workspace/base.html
index 44441cf..05fa03b 100644
--- a/django_airavata/apps/workspace/templates/django_airavata_workspace/base.html
+++ b/django_airavata/apps/workspace/templates/django_airavata_workspace/base.html
@@ -20,6 +20,9 @@
         <a href="{% url 'django_airavata_workspace:projects' %}" class="c-nav__item {% if request.active_nav_item == 'projects' %}is-active{% endif %}" data-toggle=tooltip data-placement=right title=Projects>
             <i class="fa fa-folder-open"></i> <span class=sr-only>Projects</span>
         </a>
+        <a href="{% url 'django_airavata_workspace:storage' %}" class="c-nav__item {% if request.active_nav_item == 'storage' %}is-active{% endif %}" data-toggle=tooltip data-placement=right title=Storage>
+            <i class="fa fa-folder-open"></i> <span class=sr-only>Storage</span>
+        </a>
 {% endblock %}
 
 {% block content %}
diff --git a/django_airavata/apps/workspace/urls.py b/django_airavata/apps/workspace/urls.py
index af3d2ae..f06f529 100644
--- a/django_airavata/apps/workspace/urls.py
+++ b/django_airavata/apps/workspace/urls.py
@@ -16,4 +16,5 @@ urlpatterns = [
     url(r'^applications/(?P<app_module_id>[^/]+)/create_experiment$',
         views.create_experiment, name='create_experiment'),
     url(r'^dashboard$', views.dashboard, name='dashboard'),
+    url(r'^storage', views.user_storage, name='storage'),
 ]
diff --git a/django_airavata/apps/workspace/views.py b/django_airavata/apps/workspace/views.py
index 45675cf..2c018e9 100644
--- a/django_airavata/apps/workspace/views.py
+++ b/django_airavata/apps/workspace/views.py
@@ -133,6 +133,14 @@ def view_experiment(request, experiment_id):
     })
 
 
+@login_required
+def user_storage(request):
+    request.active_nav_item = 'storage'
+    return render(request, 'django_airavata_workspace/base.html', {
+        'bundle_name': 'user-storage'
+    })
+
+
 experiment_data_storage = FileSystemStorage(
     location=settings.GATEWAY_DATA_STORE_DIR)
 
diff --git a/django_airavata/apps/workspace/vue.config.js b/django_airavata/apps/workspace/vue.config.js
index a8509f3..92f8662 100644
--- a/django_airavata/apps/workspace/vue.config.js
+++ b/django_airavata/apps/workspace/vue.config.js
@@ -16,6 +16,7 @@ module.exports = {
       'experiment-list': './static/django_airavata_workspace/js/entry-experiment-list',
       'edit-experiment': './static/django_airavata_workspace/js/entry-edit-experiment',
       'edit-project': './static/django_airavata_workspace/js/entry-edit-project',
+      'user-storage': './static/django_airavata_workspace/js/entry-user-storage',
   },
   css: {
     loaderOptions: {