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:14 UTC
[airavata-django-portal] 01/11: AIRAVATA-2990 Display of experiment
statistics counts
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 5e4527c8a5410ac72175a9434a71418e90a8c574
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Wed Jun 5 13:01:35 2019 -0400
AIRAVATA-2990 Display of experiment statistics counts
---
django_airavata/apps/admin/package.json | 4 +-
.../statistics/ExperimentStatisticsCard.vue | 69 ++++++++++++
.../statistics/ExperimentStatisticsContainer.vue | 121 +++++++++++++++++++++
.../static/django_airavata_admin/src/router.js | 6 +
.../apps/admin/templates/admin/admin_base.html | 5 +
django_airavata/apps/admin/urls.py | 2 +
django_airavata/apps/admin/views.py | 6 +
django_airavata/apps/api/serializers.py | 11 ++
.../api/static/django_airavata_api/js/index.js | 1 +
.../js/models/ExperimentStatistics.js | 43 ++++++++
.../django_airavata_api/js/service_config.js | 14 ++-
django_airavata/apps/api/urls.py | 6 +-
django_airavata/apps/api/views.py | 21 ++++
13 files changed, 304 insertions(+), 5 deletions(-)
diff --git a/django_airavata/apps/admin/package.json b/django_airavata/apps/admin/package.json
index 21d49f4..f1f904c 100644
--- a/django_airavata/apps/admin/package.json
+++ b/django_airavata/apps/admin/package.json
@@ -11,8 +11,8 @@
"lint:visualstudio": "vue-cli-service lint --format visualstudio ./static/django_airavata_admin/src/"
},
"dependencies": {
- "bootstrap": "^4.0.0",
- "bootstrap-vue": "^2.0.0-rc.11",
+ "bootstrap": "^4.3.1",
+ "bootstrap-vue": "^2.0.0-rc.22",
"django-airavata-api": "file:../api",
"django-airavata-common-ui": "file:../../static/common",
"moment": "^2.22.2",
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
new file mode 100644
index 0000000..63dc8ed
--- /dev/null
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/components/statistics/ExperimentStatisticsCard.vue
@@ -0,0 +1,69 @@
+<template>
+
+ <b-card
+ :bg-variant="bgVariant"
+ body-bg-variant="light"
+ :header-text-variant="headerTextVariant"
+ class="statistics-card"
+ >
+ <div
+ slot="header"
+ class="text-right"
+ >
+ <div class="statistic-count">{{ count }}</div>
+ <div>{{ title }}</div>
+ </div>
+ <b-link
+ :class="'text-decoration-none text-' + linkVariant"
+ @click="$emit('click')"
+ >
+ <slot name="link-text">
+ <div
+ v-for="state in states"
+ :key="state.value"
+ >{{ state.name }}</div>
+ </slot>
+ </b-link>
+ </b-card>
+</template>
+
+<script>
+export default {
+ name: "experiment-statistics-card",
+ props: {
+ bgVariant: {
+ type: String,
+ default: "light"
+ },
+ headerTextVariant: {
+ type: String,
+ default: "dark"
+ },
+ linkVariant: {
+ type: String,
+ default: "primary"
+ },
+ count: {
+ type: Number,
+ required: true
+ },
+ title: {
+ type: String,
+ required: true
+ },
+ states: {
+ type: Array,
+ default: () => []
+ }
+ }
+};
+</script>
+
+<style scoped>
+.statistic-count {
+ font-size: 3rem;
+}
+.statistics-card {
+ height: calc(100% - 30px);
+}
+</style>
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
new file mode 100644
index 0000000..803460a
--- /dev/null
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/components/statistics/ExperimentStatisticsContainer.vue
@@ -0,0 +1,121 @@
+<template>
+ <div>
+ <div class="row">
+ <div class="col">
+ <h1 class="h4 mb-4">Experiment Statistics</h1>
+ </div>
+ </div>
+ <div class="row">
+ <div class="col-lg-2 col-md-4">
+ <experiment-statistics-card
+ bg-variant="primary"
+ header-text-variant="white"
+ :count="experimentStatistics.allExperimentCount || 0"
+ title="Total Experiments"
+ >
+ <span slot="link-text">All</span>
+ </experiment-statistics-card>
+ </div>
+ <div class="col-lg-2 col-md-4">
+ <experiment-statistics-card
+ bg-variant="light"
+ :count="experimentStatistics.createdExperimentCount || 0"
+ :states="createdStates"
+ title="Created Experiments"
+ >
+ </experiment-statistics-card>
+ </div>
+ <div class="col-lg-2 col-md-4">
+ <experiment-statistics-card
+ bg-variant="light"
+ header-text-variant="success"
+ :count="experimentStatistics.runningExperimentCount || 0"
+ :states="runningStates"
+ title="Running Experiments"
+ >
+ </experiment-statistics-card>
+
+ </div>
+ <div class="col-lg-2 col-md-4">
+ <experiment-statistics-card
+ bg-variant="success"
+ header-text-variant="white"
+ link-variant="success"
+ :count="experimentStatistics.completedExperimentCount || 0"
+ :states="completedStates"
+ title="Completed Experiments"
+ >
+ </experiment-statistics-card>
+ </div>
+ <div class="col-lg-2 col-md-4">
+ <experiment-statistics-card
+ bg-variant="warning"
+ header-text-variant="white"
+ link-variant="warning"
+ :count="experimentStatistics.cancelledExperimentCount || 0"
+ :states="canceledStates"
+ title="Cancelled Experiments"
+ >
+ </experiment-statistics-card>
+ </div>
+ <div class="col-lg-2 col-md-4">
+ <experiment-statistics-card
+ bg-variant="danger"
+ header-text-variant="white"
+ link-variant="danger"
+ :count="experimentStatistics.failedExperimentCount || 0"
+ :states="failedStates"
+ title="Failed Experiments"
+ >
+ </experiment-statistics-card>
+
+ </div>
+ </div>
+ </div>
+</template>
+<script>
+import { models, services } from "django-airavata-api";
+import ExperimentStatisticsCard from "./ExperimentStatisticsCard";
+
+export default {
+ name: "experiment-statistics-container",
+ data() {
+ return {
+ experimentStatistics: {}
+ };
+ },
+ created() {
+ services.ExperimentStatisticsService.get().then(
+ stats => (this.experimentStatistics = stats)
+ );
+ },
+ components: {
+ ExperimentStatisticsCard
+ },
+ computed: {
+ createdStates() {
+ // TODO: moved to ExperimentStatistics model
+ return [models.ExperimentState.CREATED, models.ExperimentState.VALIDATED];
+ },
+ runningStates() {
+ return [
+ models.ExperimentState.SCHEDULED,
+ models.ExperimentState.LAUNCHED,
+ models.ExperimentState.EXECUTING
+ ];
+ },
+ completedStates() {
+ return [models.ExperimentState.COMPLETED];
+ },
+ canceledStates() {
+ return [
+ models.ExperimentState.CANCELING,
+ models.ExperimentState.CANCELED
+ ];
+ },
+ failedStates() {
+ return [models.ExperimentState.FAILED];
+ }
+ }
+};
+</script>
diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/router.js b/django_airavata/apps/admin/static/django_airavata_admin/src/router.js
index be3b2c0..b4070d4 100644
--- a/django_airavata/apps/admin/static/django_airavata_admin/src/router.js
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/router.js
@@ -6,6 +6,7 @@ import ApplicationModuleEditor from "./components/applications/ApplicationModule
import ApplicationsDashboard from "./components/dashboards/ApplicationsDashboard.vue";
import ComputePreference from "./components/admin/group_resource_preferences/ComputePreference";
import ComputeResourcePreferenceDashboard from "./components/dashboards/ComputeResourcePreferenceDashboard";
+import ExperimentStatisticsContainer from "./components/statistics/ExperimentStatisticsContainer";
import CredentialStoreDashboard from "./components/dashboards/CredentialStoreDashboard";
import GatewayResourceProfileEditorContainer from "./components/gatewayprofile/GatewayResourceProfileEditorContainer.vue";
import GroupComputeResourcePreference from "./components/admin/group_resource_preferences/GroupComputeResourcePreference";
@@ -122,6 +123,11 @@ const routes = [
path: "/users",
component: UserManagementContainer,
name: "users"
+ },
+ {
+ path: "/experiment-statistics",
+ component: ExperimentStatisticsContainer,
+ name: "experiment-statistics"
}
];
export default new VueRouter({
diff --git a/django_airavata/apps/admin/templates/admin/admin_base.html b/django_airavata/apps/admin/templates/admin/admin_base.html
index e883735..9b48f21 100644
--- a/django_airavata/apps/admin/templates/admin/admin_base.html
+++ b/django_airavata/apps/admin/templates/admin/admin_base.html
@@ -19,6 +19,11 @@
<i class="fa fa-users"></i> <span class=sr-only>Manage Users</span>
</a>
{% endif %}
+ {% if request.is_gateway_admin or request.is_read_only_gateway_admin %}
+ <a href="{% url 'django_airavata_admin:experiment-statistics' %}" class="c-nav__item {% if request.active_nav_item == 'experiment-statistics' %}is-active{% endif %}" data-toggle=tooltip data-placement=right title="Experiment Statistics">
+ <i class="fa fa-chart-bar"></i> <span class=sr-only>Experiment Statistics</span>
+ </a>
+ {% endif %}
<a href="{% url 'django_airavata_admin:credential_store' %}" class="c-nav__item {% if request.active_nav_item == 'credential_store' %}is-active{% endif %}" data-toggle=tooltip data-placement=right title="Credential Store">
<i class="fa fa-lock"></i> <span class=sr-only>Credential Store</span>
</a>
diff --git a/django_airavata/apps/admin/urls.py b/django_airavata/apps/admin/urls.py
index d706078..c56da01 100644
--- a/django_airavata/apps/admin/urls.py
+++ b/django_airavata/apps/admin/urls.py
@@ -7,6 +7,8 @@ urlpatterns = [
url(r'^$', views.home, name='home'),
url(r'^applications/', views.app_catalog, name='app_catalog'),
url(r'^credentials/', views.credential_store, name='credential_store'),
+ url(r'^experiment-statistics/', views.experiment_statistics,
+ name="experiment-statistics"),
url(r'^group-resource-profiles/', views.group_resource_profile,
name='group_resource_profile'),
url(r'^gateway-resource-profile/', views.gateway_resource_profile,
diff --git a/django_airavata/apps/admin/views.py b/django_airavata/apps/admin/views.py
index 627febf..18908fc 100644
--- a/django_airavata/apps/admin/views.py
+++ b/django_airavata/apps/admin/views.py
@@ -45,3 +45,9 @@ def gateway_resource_profile(request):
def users(request):
request.active_nav_item = 'users'
return render(request, 'admin/admin_base.html')
+
+
+@login_required
+def experiment_statistics(request):
+ request.active_nav_item = 'experiment-statistics'
+ return render(request, 'admin/admin_base.html')
diff --git a/django_airavata/apps/api/serializers.py b/django_airavata/apps/api/serializers.py
index 90fd2ed..a33287d 100644
--- a/django_airavata/apps/api/serializers.py
+++ b/django_airavata/apps/api/serializers.py
@@ -47,6 +47,7 @@ from airavata.model.data.replica.ttypes import (
)
from airavata.model.experiment.ttypes import (
ExperimentModel,
+ ExperimentStatistics,
ExperimentSummaryModel
)
from airavata.model.group.ttypes import GroupModel, ResourcePermissionType
@@ -852,3 +853,13 @@ class ManagedUserProfile(serializers.Serializer):
instance['_removed_group_ids'] = list(
set(existing_group_ids) - set(new_group_ids))
return instance
+
+
+class ExperimentStatisticsSerializer(
+ thrift_utils.create_serializer_class(ExperimentStatistics)):
+ allExperiments = ExperimentSummarySerializer(many=True)
+ completedExperiments = ExperimentSummarySerializer(many=True)
+ failedExperiments = ExperimentSummarySerializer(many=True)
+ cancelledExperiments = ExperimentSummarySerializer(many=True)
+ createdExperiments = ExperimentSummarySerializer(many=True)
+ runningExperiments = ExperimentSummarySerializer(many=True)
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 67a26d5..1729443 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
@@ -101,6 +101,7 @@ const services = {
DataProductService: ServiceFactory.service("DataProducts"),
ExperimentSearchService: ServiceFactory.service("ExperimentSearch"),
ExperimentService: ServiceFactory.service("Experiments"),
+ ExperimentStatisticsService: ServiceFactory.service("ExperimentStatistics"),
FullExperimentService: ServiceFactory.service("FullExperiments"),
GatewayResourceProfileService: ServiceFactory.service(
"GatewayResourceProfiles"
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/models/ExperimentStatistics.js b/django_airavata/apps/api/static/django_airavata_api/js/models/ExperimentStatistics.js
new file mode 100644
index 0000000..ffad82c
--- /dev/null
+++ b/django_airavata/apps/api/static/django_airavata_api/js/models/ExperimentStatistics.js
@@ -0,0 +1,43 @@
+import BaseModel from "./BaseModel";
+import ExperimentSummary from "./ExperimentSummary";
+
+const FIELDS = [
+ "allExperimentCount",
+ "completedExperimentCount",
+ "cancelledExperimentCount",
+ "failedExperimentCount",
+ "createdExperimentCount",
+ "runningExperimentCount",
+ {
+ name: "allExperiments",
+ type: ExperimentSummary,
+ list: true
+ },
+ {
+ name: "completedExperiments",
+ type: ExperimentSummary,
+ list: true
+ },
+ {
+ name: "failedExperiments",
+ type: ExperimentSummary,
+ list: true
+ },
+ {
+ name: "cancelledExperiments",
+ type: ExperimentSummary,
+ list: true
+ },
+ {
+ name: "createdExperiments",
+ type: ExperimentSummary,
+ list: true
+ },
+ { name: "runningExperiments", type: ExperimentSummary, list: true }
+];
+
+export default class ExperimentStatistics 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 ad2e322..43551c7 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
@@ -7,6 +7,7 @@ import CredentialSummary from "./models/CredentialSummary";
import DataProduct from "./models/DataProduct";
import Experiment from "./models/Experiment";
import ExperimentSearchFields from "./models/ExperimentSearchFields";
+import ExperimentStatistics from "./models/ExperimentStatistics";
import ExperimentSummary from "./models/ExperimentSummary";
import FullExperiment from "./models/FullExperiment";
import GatewayResourceProfile from "./models/GatewayResourceProfile";
@@ -187,6 +188,17 @@ export default {
ExperimentSearchFields.values.map(f => f.name)
)
},
+ ExperimentStatistics: {
+ url: "/api/experiment-statistics",
+ methods: {
+ get: {
+ url: "/api/experiment-statistics",
+ requestType: "get",
+ queryParams: ["fromDate", "toDate"],
+ modelClass: ExperimentStatistics
+ }
+ }
+ },
FullExperiments: {
url: "/api/full-experiments",
viewSet: [
@@ -293,7 +305,7 @@ export default {
modelClass: UserStoragePath,
encodePathParams: false
}
- },
+ }
},
WorkspacePreferences: {
url: "/api/workspace-preferences",
diff --git a/django_airavata/apps/api/urls.py b/django_airavata/apps/api/urls.py
index 1810513..267da92 100644
--- a/django_airavata/apps/api/urls.py
+++ b/django_airavata/apps/api/urls.py
@@ -76,10 +76,12 @@ urlpatterns = [
url(r'^workspace-preferences',
views.WorkspacePreferencesView.as_view(),
name="workspace-preferences"),
- # url(r'^user-storage/~/(?P<path>.*)/$',
url(r'^user-storage/~/(?P<path>.*)$',
views.UserStoragePathView.as_view(),
- name="user-storage-items")
+ name="user-storage-items"),
+ url(r'^experiment-statistics',
+ views.ExperimentStatisticsView.as_view(),
+ name="experiment-statistics")
]
if logger.isEnabledFor(logging.DEBUG):
diff --git a/django_airavata/apps/api/views.py b/django_airavata/apps/api/views.py
index 0fe5192..3e20e17 100644
--- a/django_airavata/apps/api/views.py
+++ b/django_airavata/apps/api/views.py
@@ -1,5 +1,6 @@
import logging
import os
+from datetime import datetime, timedelta
from django.conf import settings
from django.contrib.auth.decorators import login_required
@@ -1440,3 +1441,23 @@ class ManagedUserViewSet(mixins.CreateModelMixin,
'creationTime': user_profile.creationTime,
'groups': groups
}
+
+
+class ExperimentStatisticsView(APIView):
+ # TODO: restrict to only Admins or Read Only Admins group members
+ 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=1)).timestamp() * 1000)
+ to_time = request.GET.get(
+ 'toTime',
+ datetime.utcnow().timestamp() * 1000)
+ statistics = request.airavata_client.getExperimentStatistics(
+ request.authz_token, settings.GATEWAY_ID, from_time, to_time,
+ None, None, None)
+ serializer = self.serializer_class(
+ statistics, context={'request': request})
+ return Response(serializer.data)