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/06/17 14:23:17 UTC

[airavata-django-portal] 04/11: AIRAVATA-2990 Add UI component for picking date range for stats

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 1ce5892dcdc47db7967d2ee4ea480a0096adc67d
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Fri Jun 7 16:51:30 2019 -0400

    AIRAVATA-2990 Add UI component for picking date range for stats
---
 django_airavata/apps/admin/package.json            |  1 +
 .../statistics/ExperimentStatisticsCard.vue        |  2 +-
 .../statistics/ExperimentStatisticsContainer.vue   | 96 ++++++++++++++--------
 .../admin/static/django_airavata_admin/src/main.js |  4 +
 .../django_airavata_api/js/service_config.js       |  2 +-
 django_airavata/apps/api/view_utils.py             | 16 ++++
 django_airavata/apps/api/views.py                  | 27 ++++--
 7 files changed, 105 insertions(+), 43 deletions(-)

diff --git a/django_airavata/apps/admin/package.json b/django_airavata/apps/admin/package.json
index f1f904c..d6b9048 100644
--- a/django_airavata/apps/admin/package.json
+++ b/django_airavata/apps/admin/package.json
@@ -17,6 +17,7 @@
     "django-airavata-common-ui": "file:../../static/common",
     "moment": "^2.22.2",
     "vue": "^2.5.22",
+    "vue-flatpickr-component": "^8.1.2",
     "vue-resource": "^1.3.4",
     "vue-router": "^2.7.0",
     "vuedraggable": "^2.16.0",
diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/components/statistics/ExperimentStatisticsCard.vue b/django_airavata/apps/admin/static/django_airavata_admin/src/components/statistics/ExperimentStatisticsCard.vue
index 63dc8ed..db8a464 100644
--- a/django_airavata/apps/admin/static/django_airavata_admin/src/components/statistics/ExperimentStatisticsCard.vue
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/components/statistics/ExperimentStatisticsCard.vue
@@ -2,7 +2,7 @@
 
   <b-card
     :bg-variant="bgVariant"
-    body-bg-variant="light"
+    body-bg-variant="white"
     :header-text-variant="headerTextVariant"
     class="statistics-card"
   >
diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/components/statistics/ExperimentStatisticsContainer.vue b/django_airavata/apps/admin/static/django_airavata_admin/src/components/statistics/ExperimentStatisticsContainer.vue
index 0a20b76..dcbe586 100644
--- a/django_airavata/apps/admin/static/django_airavata_admin/src/components/statistics/ExperimentStatisticsContainer.vue
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/components/statistics/ExperimentStatisticsContainer.vue
@@ -6,6 +6,17 @@
       </div>
     </div>
     <div class="row">
+      <div class="col">
+        <b-card>
+          <flat-pickr
+            v-model="dateRange"
+            :config="dateConfig"
+            @on-change="dateRangeChanged"
+          />
+        </b-card>
+      </div>
+    </div>
+    <div class="row">
       <div class="col-lg-2 col-md-4">
         <experiment-statistics-card
           bg-variant="primary"
@@ -82,35 +93,37 @@
       v-if="items.length"
     >
       <div class="col">
-        <b-table
-          :fields="fields"
-          :items="items"
-        >
-          <template
-            slot="executionId"
-            slot-scope="data"
-          >
-            <application-name :application-interface-id="data.value" />
-          </template>
-          <template
-            slot="resourceHostId"
-            slot-scope="data"
+        <b-card>
+          <b-table
+            :fields="fields"
+            :items="items"
           >
-            <compute-resource-name :compute-resource-id="data.value" />
-          </template>
-          <template
-            slot="creationTime"
-            slot-scope="data"
-          >
-            <human-date :date="data.value" />
-          </template>
-          <template
-            slot="experimentStatus"
-            slot-scope="data"
-          >
-            <experiment-status-badge :status-name="data.value.name" />
-          </template>
-        </b-table>
+            <template
+              slot="executionId"
+              slot-scope="data"
+            >
+              <application-name :application-interface-id="data.value" />
+            </template>
+            <template
+              slot="resourceHostId"
+              slot-scope="data"
+            >
+              <compute-resource-name :compute-resource-id="data.value" />
+            </template>
+            <template
+              slot="creationTime"
+              slot-scope="data"
+            >
+              <human-date :date="data.value" />
+            </template>
+            <template
+              slot="experimentStatus"
+              slot-scope="data"
+            >
+              <experiment-status-badge :status-name="data.value.name" />
+            </template>
+          </b-table>
+        </b-card>
       </div>
     </div>
   </div>
@@ -123,15 +136,22 @@ import ExperimentStatisticsCard from "./ExperimentStatisticsCard";
 export default {
   name: "experiment-statistics-container",
   data() {
+    const fromTime = new Date(Date.now() - 24 * 60 * 60 * 1000); // 24 hours ago
+    const toTime = new Date();
     return {
       experimentStatistics: {},
-      selectedExperimentSummaries: null
+      selectedExperimentSummaries: null,
+      fromTime: fromTime,
+      toTime: toTime,
+      dateRange: [fromTime, toTime],
+      dateConfig: {
+        mode: "range",
+        maxDate: new Date()
+      }
     };
   },
   created() {
-    services.ExperimentStatisticsService.get().then(
-      stats => (this.experimentStatistics = stats)
-    );
+    this.loadStatistics();
   },
   components: {
     ExperimentStatisticsCard,
@@ -207,6 +227,18 @@ export default {
   methods: {
     selectExperiments(experiments) {
       this.selectedExperimentSummaries = experiments;
+    },
+    dateRangeChanged(selectedDates) {
+      [this.fromTime, this.toTime] = selectedDates;
+      if (this.fromTime && this.toTime) {
+        this.loadStatistics();
+      }
+    },
+    loadStatistics() {
+      services.ExperimentStatisticsService.get({
+        fromTime: this.fromTime.toJSON(),
+        toTime: this.toTime.toJSON()
+      }).then(stats => (this.experimentStatistics = stats));
     }
   }
 };
diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/main.js b/django_airavata/apps/admin/static/django_airavata_admin/src/main.js
index 60cd37a..31a3644 100644
--- a/django_airavata/apps/admin/static/django_airavata_admin/src/main.js
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/main.js
@@ -1,14 +1,18 @@
 import { components, entry } from "django-airavata-common-ui";
 import VueResource from "vue-resource";
 import VueRouter from "vue-router";
+import VueFlatPickr from 'vue-flatpickr-component';
 import App from "./App.vue";
 import router from "./router";
 
+import 'flatpickr/dist/flatpickr.css';
+
 entry(Vue => {
   Vue.config.productionTip = false;
 
   Vue.use(VueResource);
   Vue.use(VueRouter);
+  Vue.use(VueFlatPickr);
 
   new Vue({
     render: h => h(components.MainLayout, [h(App)]),
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 43551c7..0770e81 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
@@ -194,7 +194,7 @@ export default {
       get: {
         url: "/api/experiment-statistics",
         requestType: "get",
-        queryParams: ["fromDate", "toDate"],
+        queryParams: ["fromTime", "toTime"],
         modelClass: ExperimentStatistics
       }
     }
diff --git a/django_airavata/apps/api/view_utils.py b/django_airavata/apps/api/view_utils.py
index 1046684..8e932da 100644
--- a/django_airavata/apps/api/view_utils.py
+++ b/django_airavata/apps/api/view_utils.py
@@ -1,5 +1,8 @@
+import logging
 from collections.__init__ import OrderedDict
+from datetime import datetime
 
+import pytz
 from django.conf import settings
 from django.http import Http404
 from rest_framework import mixins, pagination
@@ -8,6 +11,8 @@ from rest_framework.reverse import reverse
 from rest_framework.utils.urls import remove_query_param, replace_query_param
 from rest_framework.viewsets import GenericViewSet
 
+logger = logging.getLogger(__name__)
+
 
 class GenericAPIBackedViewSet(GenericViewSet):
     # Make lookup_value_regex to any set of non-forward-slash characters. Many
@@ -178,3 +183,14 @@ class APIResultPagination(pagination.LimitOffsetPagination):
             return self.request.build_absolute_uri(reverse(self.viewname))
         else:
             return self.request.build_absolute_uri()
+
+
+def convert_utc_iso8601_to_date(iso8601_utc_string):
+    # This is meant to convert a JavaScript `new Date().toJSON()` into a
+    # datetime instance
+    timestamp = datetime.strptime(
+        iso8601_utc_string, "%Y-%m-%dT%H:%M:%S.%fZ")
+    timestamp = timestamp.replace(tzinfo=pytz.UTC)
+    logger.debug("convert_utc_iso8601_to_date({})={}".format(
+        iso8601_utc_string, timestamp))
+    return timestamp
diff --git a/django_airavata/apps/api/views.py b/django_airavata/apps/api/views.py
index 6bd8a93..e58326e 100644
--- a/django_airavata/apps/api/views.py
+++ b/django_airavata/apps/api/views.py
@@ -2,6 +2,7 @@ import logging
 import os
 from datetime import datetime, timedelta
 
+import pytz
 from django.conf import settings
 from django.contrib.auth.decorators import login_required
 from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
@@ -47,7 +48,8 @@ from . import (
     models,
     output_views,
     serializers,
-    thrift_utils
+    thrift_utils,
+    view_utils
 )
 
 READ_PERMISSION_TYPE = '{}:READ'
@@ -1448,16 +1450,23 @@ class ExperimentStatisticsView(APIView):
     serializer_class = serializers.ExperimentStatisticsSerializer
 
     def get(self, request, format=None):
-        # TODO: convert from ISO-8601 to posix timestamp
-        from_time = request.GET.get(
-            'fromTime',
-            (datetime.utcnow() - timedelta(days=7)).timestamp() * 1000)
-        to_time = request.GET.get(
-            'toTime',
-            datetime.utcnow().timestamp() * 1000)
+        if 'fromTime' in request.GET:
+            from_time = view_utils.convert_utc_iso8601_to_date(
+                request.GET['fromTime']).timestamp() * 1000
+        else:
+            from_time = (datetime.utcnow() -
+                         timedelta(days=7)).timestamp() * 1000
+        if 'toTime' in request.GET:
+            to_time = view_utils.convert_utc_iso8601_to_date(
+                request.GET['toTime']).timestamp() * 1000
+        else:
+            to_time = datetime.utcnow().timestamp() * 1000
+        username = request.GET.get('userName', None)
+        application_name = request.GET.get('applicationName', None)
+        resource_hostname = request.GET.get('requestHostName', None)
         statistics = request.airavata_client.getExperimentStatistics(
             request.authz_token, settings.GATEWAY_ID, from_time, to_time,
-            None, None, None)
+            username, application_name, resource_hostname)
         serializer = self.serializer_class(
             statistics, context={'request': request})
         return Response(serializer.data)