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/07/18 20:15:29 UTC

[airavata-django-portal] branch master updated (89794ac -> f8ff476)

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

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


    from 89794ac  Remove unused import
     new 6abf9ff  AIRAVATA-3115 ApplicationPreferences model for favoriting apps
     new f8ff476  AIRAVATA-3115 UI for toggling favorites

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../apps/api/migrations/0003_auto_20190716_1549.py |  30 ++++++
 django_airavata/apps/api/models.py                 |  11 +++
 django_airavata/apps/api/serializers.py            |   9 ++
 .../js/models/WorkspacePreferences.js              |   2 +-
 .../django_airavata_api/js/service_config.js       |   8 ++
 django_airavata/apps/api/views.py                  |  36 ++++++++
 .../js/containers/DashboardContainer.vue           | 102 ++++++++++++++++++++-
 .../common/js/components/ApplicationCard.vue       |  76 +++++++++------
 .../static/common/js/components/FavoriteToggle.vue |  61 ++++++++++++
 django_airavata/static/common/js/index.js          |   2 +
 10 files changed, 303 insertions(+), 34 deletions(-)
 create mode 100644 django_airavata/apps/api/migrations/0003_auto_20190716_1549.py
 create mode 100644 django_airavata/static/common/js/components/FavoriteToggle.vue


[airavata-django-portal] 01/02: AIRAVATA-3115 ApplicationPreferences model for favoriting apps

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 6abf9ff5cdccbde7c1a345a2484ad4d2e2508340
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Tue Jul 16 12:13:15 2019 -0400

    AIRAVATA-3115 ApplicationPreferences model for favoriting apps
---
 .../apps/api/migrations/0003_auto_20190716_1549.py | 30 ++++++++++++++++++
 django_airavata/apps/api/models.py                 | 11 +++++++
 django_airavata/apps/api/serializers.py            |  9 ++++++
 django_airavata/apps/api/views.py                  | 36 ++++++++++++++++++++++
 4 files changed, 86 insertions(+)

diff --git a/django_airavata/apps/api/migrations/0003_auto_20190716_1549.py b/django_airavata/apps/api/migrations/0003_auto_20190716_1549.py
new file mode 100644
index 0000000..3840aab
--- /dev/null
+++ b/django_airavata/apps/api/migrations/0003_auto_20190716_1549.py
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.21 on 2019-07-16 15:49
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('django_airavata_api', '0002_auto_20190711_1438'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='ApplicationPreferences',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('username', models.CharField(max_length=64)),
+                ('application_id', models.CharField(max_length=64)),
+                ('favorite', models.BooleanField(default=False)),
+                ('workspace_preferences', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='django_airavata_api.WorkspacePreferences')),
+            ],
+        ),
+        migrations.AlterUniqueTogether(
+            name='applicationpreferences',
+            unique_together=set([('username', 'application_id')]),
+        ),
+    ]
diff --git a/django_airavata/apps/api/models.py b/django_airavata/apps/api/models.py
index fd44cca..33f6b01 100644
--- a/django_airavata/apps/api/models.py
+++ b/django_airavata/apps/api/models.py
@@ -11,6 +11,17 @@ class WorkspacePreferences(models.Model):
         return WorkspacePreferences(username=username)
 
 
+class ApplicationPreferences(models.Model):
+    username = models.CharField(max_length=64)
+    application_id = models.CharField(max_length=64)
+    favorite = models.BooleanField(default=False)
+    workspace_preferences = models.ForeignKey(WorkspacePreferences,
+                                              on_delete=models.CASCADE)
+
+    class Meta:
+        unique_together = (('username', 'application_id'),)
+
+
 class User_Notifications(models.Model):
     class Meta:
         unique_together = (('username', 'notification_id'),)
diff --git a/django_airavata/apps/api/serializers.py b/django_airavata/apps/api/serializers.py
index e9e4ade..08fb723 100644
--- a/django_airavata/apps/api/serializers.py
+++ b/django_airavata/apps/api/serializers.py
@@ -848,7 +848,16 @@ class UserStoragePathSerializer(serializers.Serializer):
 
 
 # ModelSerializers
+class ApplicationPreferencesSerializer(serializers.ModelSerializer):
+    class Meta:
+        model = models.ApplicationPreferences
+        exclude = ('id', 'username', 'workspace_preferences')
+
+
 class WorkspacePreferencesSerializer(serializers.ModelSerializer):
+    application_preferences = ApplicationPreferencesSerializer(
+        source="applicationpreferences_set", many=True)
+
     class Meta:
         model = models.WorkspacePreferences
         exclude = ('username',)
diff --git a/django_airavata/apps/api/views.py b/django_airavata/apps/api/views.py
index d24a562..096a294 100644
--- a/django_airavata/apps/api/views.py
+++ b/django_airavata/apps/api/views.py
@@ -563,6 +563,42 @@ class ApplicationModuleViewSet(APIBackedViewSet):
             app_deployments, many=True, context={'request': request})
         return Response(serializer.data)
 
+    @detail_route(methods=['post'])
+    def favorite(self, request, app_module_id):
+        helper = helpers.WorkspacePreferencesHelper()
+        workspace_preferences = helper.get(request)
+        try:
+            application_preferences = (
+                workspace_preferences.applicationpreferences_set.get(
+                    application_id=app_module_id))
+            application_preferences.favorite = True
+            application_preferences.save()
+        except ObjectDoesNotExist:
+            workspace_preferences.applicationpreferences_set.create(
+                username=request.user.username,
+                application_id=app_module_id,
+                favorite=True)
+
+        return HttpResponse(status=204)
+
+    @detail_route(methods=['post'])
+    def unfavorite(self, request, app_module_id):
+        helper = helpers.WorkspacePreferencesHelper()
+        workspace_preferences = helper.get(request)
+        try:
+            application_preferences = (
+                workspace_preferences.applicationpreferences_set.get(
+                    application_id=app_module_id))
+            application_preferences.favorite = False
+            application_preferences.save()
+        except ObjectDoesNotExist:
+            workspace_preferences.applicationpreferences_set.create(
+                username=request.user.username,
+                application_id=app_module_id,
+                favorite=False)
+
+        return HttpResponse(status=204)
+
     @list_route()
     def list_all(self, request, format=None):
         all_modules = self.request.airavata_client.getAllAppModules(


[airavata-django-portal] 02/02: AIRAVATA-3115 UI for toggling favorites

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit f8ff4763453b12069ae62d50785e146292a3fa6e
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Thu Jul 18 16:14:21 2019 -0400

    AIRAVATA-3115 UI for toggling favorites
---
 .../js/models/WorkspacePreferences.js              |   2 +-
 .../django_airavata_api/js/service_config.js       |   8 ++
 .../js/containers/DashboardContainer.vue           | 102 ++++++++++++++++++++-
 .../common/js/components/ApplicationCard.vue       |  76 +++++++++------
 .../static/common/js/components/FavoriteToggle.vue |  61 ++++++++++++
 django_airavata/static/common/js/index.js          |   2 +
 6 files changed, 217 insertions(+), 34 deletions(-)

diff --git a/django_airavata/apps/api/static/django_airavata_api/js/models/WorkspacePreferences.js b/django_airavata/apps/api/static/django_airavata_api/js/models/WorkspacePreferences.js
index f0cf1ff..34eb4b3 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/models/WorkspacePreferences.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/models/WorkspacePreferences.js
@@ -1,6 +1,6 @@
 import BaseModel from "./BaseModel";
 
-const FIELDS = ["most_recent_project_id"];
+const FIELDS = ["most_recent_project_id", "application_preferences"];
 
 export default class WorkspacePreferences extends BaseModel {
   constructor(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 82261a6..002cd74 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
@@ -96,6 +96,14 @@ export default {
         url: "/api/applications/list_all/",
         requestType: "get",
         modelClass: ApplicationModule
+      },
+      favorite: {
+        url: "/api/applications/<lookup>/favorite/",
+        requestType: "post"
+      },
+      unfavorite: {
+        url: "/api/applications/<lookup>/unfavorite/",
+        requestType: "post"
       }
     },
     modelClass: ApplicationModule
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/containers/DashboardContainer.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/containers/DashboardContainer.vue
index b833d23..ef5c95f 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/containers/DashboardContainer.vue
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/containers/DashboardContainer.vue
@@ -20,17 +20,53 @@
           contact to grant you the appropriate privileges.</b-alert>
       </div>
     </div>
+    <template v-if="favoriteApplicationsData.length > 0">
+      <div class="row">
+        <div class="col">
+          <h1 class="h5 mb-2">Favorites</h1>
+        </div>
+      </div>
+      <div class="row">
+        <application-card
+          v-for="item in favoriteApplicationsData"
+          v-bind:appModule="item.appModule"
+          v-bind:key="item.appModule.appModuleId"
+          @app-selected="handleAppSelected"
+          :disabled="item.disabled"
+          @favorite="markFavorite(item.appModule)"
+          @unfavorite="markNotFavorite(item.appModule)"
+          ref="favoriteApplicationCards"
+        >
+          <favorite-toggle
+            slot="card-actions"
+            :favorite="true"
+            class="card-link"
+            @favorite="markFavorite(item.appModule)"
+            @unfavorite="markNotFavorite(item.appModule)"
+          />
+        </application-card>
+      </div>
+      <hr />
+    </template>
     <div class="row">
       <application-card
-        v-for="item in allApplicationData"
+        v-for="item in nonFavoriteApplicationsData"
         v-bind:appModule="item.appModule"
         v-bind:key="item.appModule.appModuleId"
         @app-selected="handleAppSelected"
         :disabled="item.disabled"
+        @favorite="markFavorite(item.appModule)"
+        @unfavorite="markNotFavorite(item.appModule)"
       >
+        <favorite-toggle
+          slot="card-actions"
+          :favorite="false"
+          class="card-link"
+          @favorite="markFavorite(item.appModule)"
+          @unfavorite="markNotFavorite(item.appModule)"
+        />
       </application-card>
     </div>
-    </div>
 
   </div>
 </template>
@@ -46,15 +82,46 @@ export default {
     return {
       accessibleAppModules: null,
       userProfile: null,
-      allApplicationModules: null
+      allApplicationModules: null,
+      workspacePreferences: null
     };
   },
   components: {
-    "application-card": comps.ApplicationCard
+    "application-card": comps.ApplicationCard,
+    "favorite-toggle": comps.FavoriteToggle
   },
   methods: {
     handleAppSelected: function(appModule) {
       urls.navigateToCreateExperiment(appModule);
+    },
+    markFavorite(appModule) {
+      services.ApplicationModuleService.favorite({
+        lookup: appModule.appModuleId
+      })
+        .then(() => {
+          return services.WorkspacePreferencesService.get().then(
+            prefs => (this.workspacePreferences = prefs)
+          );
+        })
+        .then(() => {
+          const index = this.favoriteApplicationsData.findIndex(
+            data => data.appModule.appModuleId === appModule.appModuleId
+          );
+          this.$nextTick(() => {
+            this.$refs.favoriteApplicationCards[index].$el.scrollIntoView({
+              behavior: "smooth", block: "center"
+            });
+          });
+        });
+    },
+    markNotFavorite(appModule) {
+      services.ApplicationModuleService.unfavorite({
+        lookup: appModule.appModuleId
+      }).then(() => {
+        return services.WorkspacePreferencesService.get().then(
+          prefs => (this.workspacePreferences = prefs)
+        );
+      });
     }
   },
   computed: {
@@ -86,6 +153,30 @@ export default {
             };
           })
         : [];
+    },
+    favoriteApplicationsData() {
+      return this.allApplicationData.filter(
+        app =>
+          this.favoriteApplicationIds.indexOf(app.appModule.appModuleId) >= 0
+      );
+    },
+    nonFavoriteApplicationsData() {
+      return this.allApplicationData.filter(
+        app =>
+          this.favoriteApplicationIds.indexOf(app.appModule.appModuleId) < 0
+      );
+    },
+    favoriteApplicationIds() {
+      if (
+        this.workspacePreferences &&
+        this.workspacePreferences.application_preferences
+      ) {
+        return this.workspacePreferences.application_preferences
+          .filter(p => p.favorite)
+          .map(p => p.application_id);
+      } else {
+        return [];
+      }
     }
   },
   beforeMount: function() {
@@ -99,6 +190,9 @@ export default {
     services.ApplicationModuleService.listAll().then(
       result => (this.allApplicationModules = result)
     );
+    services.WorkspacePreferencesService.get().then(
+      prefs => (this.workspacePreferences = prefs)
+    );
   }
 };
 </script>
diff --git a/django_airavata/static/common/js/components/ApplicationCard.vue b/django_airavata/static/common/js/components/ApplicationCard.vue
index 5e2e4d8..756f7e2 100644
--- a/django_airavata/static/common/js/components/ApplicationCard.vue
+++ b/django_airavata/static/common/js/components/ApplicationCard.vue
@@ -1,44 +1,62 @@
 <template>
-    <div class="col-md-6 col-xl-4">
-        <div class="card application-card" :class="cardClasses">
-            <b-link :disabled="disabled" class="card-link text-dark" @click.prevent="handleAppClick">
-                <div class="card-body"  >
-                    <h2 class="card-title h5">{{appModule.appModuleName}}</h2>
-                    <span class="badge badge-primary mr-1" v-for="tag in appModule.tags" :key="tag">{{tag}}</span>
-                    <span class="badge badge-primary mr-1" v-if="appModule.appModuleVersion" >{{appModule.appModuleVersion}}</span>
-                    <p class="card-text card-text--small mt-3 text-secondary">{{appModule.appModuleDescription}}</p>
-                </div>
-            </b-link>
+  <div class="col-md-6 col-xl-4">
+    <div
+      class="card application-card"
+      :class="cardClasses"
+    >
+      <b-link
+        :disabled="disabled"
+        class="card-link text-dark"
+        @click.prevent="handleAppClick"
+      >
+        <div class="card-body">
+          <h2 class="card-title h5">{{appModule.appModuleName}}</h2>
+          <span
+            class="badge badge-primary mr-1"
+            v-for="tag in appModule.tags"
+            :key="tag"
+          >{{tag}}</span>
+          <span
+            class="badge badge-primary mr-1"
+            v-if="appModule.appModuleVersion"
+          >{{appModule.appModuleVersion}}</span>
+          <p class="card-text card-text--small mt-3 text-secondary">{{appModule.appModuleDescription}}</p>
+          <p class="card-text">
+            <slot name="card-actions">
+            </slot>
+          </p>
         </div>
+      </b-link>
     </div>
+  </div>
 </template>
 
 <script>
 export default {
-    name: 'application-card',
-    props: ['appModule', 'disabled'],
-    data:function () {
-        return {
-        };
-    },
-    methods: {
-        handleAppClick: function() {
-            this.$emit('app-selected', this.appModule);
-        }
-    },
-    computed: {
-      cardClasses() {
-        return this.disabled ? ['is-disabled'] : [];
-      },
+  name: "application-card",
+  props: ["appModule", "disabled"],
+  data: function() {
+    return {};
+  },
+  methods: {
+    handleAppClick: function() {
+      this.$emit("app-selected", this.appModule);
     }
-}
+  },
+  computed: {
+    cardClasses() {
+      return this.disabled ? ["is-disabled"] : [];
+    }
+  }
+};
 </script>
 
 <style>
 .application-card {
-    height: calc(100% - 30px); /* 30px margin at the botton */
+  height: calc(100% - 30px); /* 30px margin at the botton */
 }
-.application-card .card-link, .application-card .card-body {
-    height: 100%;
+.application-card .card-link,
+.application-card .card-body {
+  height: 100%;
 }
 </style>
diff --git a/django_airavata/static/common/js/components/FavoriteToggle.vue b/django_airavata/static/common/js/components/FavoriteToggle.vue
new file mode 100644
index 0000000..671554e
--- /dev/null
+++ b/django_airavata/static/common/js/components/FavoriteToggle.vue
@@ -0,0 +1,61 @@
+<template>
+  <b-link
+    class="text-primary"
+    @click.stop="toggleFavorite"
+    v-b-tooltip
+    :title="titleText"
+  >
+    <i
+      class="fa fa-star favorite-toggle"
+      :class="classes"
+    ><span class="sr-only">Toggle favorite</span></i>
+  </b-link>
+</template>
+
+<script>
+export default {
+  name: "favorite-toggle",
+  props: {
+    favorite: {
+      type: Boolean,
+      default: false
+    }
+  },
+  methods: {
+    toggleFavorite() {
+      if (this.favorite) {
+        this.$emit("unfavorite");
+      } else {
+        this.$emit("favorite");
+      }
+    }
+  },
+  computed: {
+    classes() {
+      if (!this.favorite) {
+        return ["not-favorite"];
+      } else {
+        return [];
+      }
+    },
+    titleText() {
+      if (this.favorite) {
+        return "Unmark as favorite";
+      } else {
+        return "Mark as favorite";
+      }
+    }
+  }
+};
+</script>
+
+<style scoped>
+.not-favorite {
+  color: #999999;
+}
+.not-favorite:hover {
+  color: inherit;
+}
+</style>
+
+
diff --git a/django_airavata/static/common/js/index.js b/django_airavata/static/common/js/index.js
index b48c033..9bec96e 100644
--- a/django_airavata/static/common/js/index.js
+++ b/django_airavata/static/common/js/index.js
@@ -9,6 +9,7 @@ import DataProductViewer from "./components/DataProductViewer";
 import DeleteButton from "./components/DeleteButton.vue";
 import DeleteLink from "./components/DeleteLink.vue";
 import ExperimentStatusBadge from "./components/ExperimentStatusBadge";
+import FavoriteToggle from "./components/FavoriteToggle";
 import GatewayGroupsBadge from "./components/GatewayGroupsBadge";
 import HumanDate from "./components/HumanDate.vue";
 import MainLayout from "./components/MainLayout.vue";
@@ -46,6 +47,7 @@ const components = {
   DeleteButton,
   DeleteLink,
   ExperimentStatusBadge,
+  FavoriteToggle,
   GatewayGroupsBadge,
   HumanDate,
   MainLayout,