-
-
+
+
+
+ Experiment Statistics from {{ fromTimeDisplay }} to
+ {{ toTimeDisplay }}
+
+
-
-
-
+
+
+
+ All
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- View Details
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ View Details
+
+
+
+
+
+
+
+
-
-
-
-
- {{ experimentTab.tabTitle }}
-
+
+
+ {{ experimentTab.tabTitle }}
+
-
- Close experiment tab
-
-
-
-
-
+ " class="text-secondary">
+
+ Close experiment tab
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/components/statistics/UserStatisticsContainer.vue b/django_airavata/apps/admin/static/django_airavata_admin/src/components/statistics/UserStatisticsContainer.vue
new file mode 100644
index 000000000..bbc34cce0
--- /dev/null
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/components/statistics/UserStatisticsContainer.vue
@@ -0,0 +1,291 @@
+
+
+
+
+
+
+
+ Number of users who submitted at least a single job within the selected period: {{ countOfUsersWithAtLeastSingleJob }}
+
+
+
+ Download emails.csv
+
+
+
+
+
+
+
+
+
+ Total number of unique users: {{ countOfAllUsers }}
+
+
+ Download emails.csv
+
+
+
+
+
+
+
+
+
+
+
+ Only years with atleast 1 registration are shown
+
+
+
+
+
+
+
+ Only organizations with atleast 1 registration are shown
+
+
+
+
+
+
+ Only countries with atleast 1 registration are shown
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/django_airavata/apps/admin/yarn.lock b/django_airavata/apps/admin/yarn.lock
index 7e2dbc880..4def71183 100644
--- a/django_airavata/apps/admin/yarn.lock
+++ b/django_airavata/apps/admin/yarn.lock
@@ -3723,6 +3723,11 @@ char-regex@^2.0.0:
resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-2.0.1.tgz#6dafdb25f9d3349914079f010ba8d0e6ff9cd01e"
integrity sha512-oSvEeo6ZUD7NepqAat3RqoucZ5SeqLJgOvVIwkafu6IP3V0pO38s/ypdVUmDDK6qIIHNlYHJAKX9E7R7HoKElw==
+chart.js@^3.7.0:
+ version "3.9.1"
+ resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-3.9.1.tgz#3abf2c775169c4c71217a107163ac708515924b8"
+ integrity sha512-Ro2JbLmvg83gXF5F4sniaQ+lTbSv18E+TIf2cOeiH1Iqd2PGFOtem+DUufMZsCJwFE7ywPOpfXFBwRTGq7dh6w==
+
chokidar@^2.0.2:
version "2.1.8"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917"
@@ -11154,6 +11159,11 @@ vue-async-computed@^3.9.0:
resolved "https://registry.yarnpkg.com/vue-async-computed/-/vue-async-computed-3.9.0.tgz#af3181c25168bfe9d86d8ffbc7033bf9e484fe84"
integrity sha512-ac6m/9zxHHNGGKNOU1en8qNk+fAmEbJLuWL7qyQTFuH3vjv3V4urv//QHcVzCobROM6btnaDG2b+XYMncF/ETA==
+vue-chartjs@^4.1.2:
+ version "4.1.2"
+ resolved "https://registry.yarnpkg.com/vue-chartjs/-/vue-chartjs-4.1.2.tgz#f899ba14f3b80660f8d2c610a015341806dfc437"
+ integrity sha512-QSggYjeFv/L4jFSBQpX8NzrAvX0B+Ha6nDgxkTG8tEXxYOOTwKI4phRLe+B4f+REnkmg7hgPY24R0cixZJyXBg==
+
vue-datetime@^1.0.0-beta.10:
version "1.0.0-beta.11"
resolved "https://registry.yarnpkg.com/vue-datetime/-/vue-datetime-1.0.0-beta.11.tgz#283b5182dabe0ab372e375e9c47acb3b41524ffa"
diff --git a/django_airavata/apps/api/serializers.py b/django_airavata/apps/api/serializers.py
index 1f5f97ae3..4617a93ad 100644
--- a/django_airavata/apps/api/serializers.py
+++ b/django_airavata/apps/api/serializers.py
@@ -44,6 +44,7 @@
DataReplicaLocationModel
)
from airavata.model.experiment.ttypes import (
+ CpuUsage,
ExperimentModel,
ExperimentStatistics,
ExperimentSummaryModel
@@ -1202,6 +1203,9 @@ class ExperimentStatisticsSerializer(
createdExperiments = BaseExperimentSummarySerializer(many=True)
runningExperiments = BaseExperimentSummarySerializer(many=True)
+class CpuUsageSerializer(thrift_utils.create_serializer_class(CpuUsage)):
+ pass
+
class UnverifiedEmailUserProfile(serializers.Serializer):
userId = serializers.CharField()
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 9cb8a6550..4da4019a5 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
@@ -118,6 +118,7 @@ const services = {
ApplicationModuleService: ServiceFactory.service("ApplicationModules"),
CloudJobSubmissionService,
ComputeResourceService: ServiceFactory.service("ComputeResources"),
+ CpuUsageService: ServiceFactory.service("CpuUsages"),
CredentialSummaryService: ServiceFactory.service("CredentialSummaries"),
DataProductService: ServiceFactory.service("DataProducts"),
ExperimentArchiveService: ServiceFactory.service("ExperimentArchive"),
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/models/CpuUsage.js b/django_airavata/apps/api/static/django_airavata_api/js/models/CpuUsage.js
new file mode 100644
index 000000000..83663c943
--- /dev/null
+++ b/django_airavata/apps/api/static/django_airavata_api/js/models/CpuUsage.js
@@ -0,0 +1,14 @@
+import BaseModel from "./BaseModel";
+
+const FIELDS = [
+ "experimentId",
+ "executionId",
+ "userName",
+ "cpuHours",
+];
+
+export default class CpuUsage extends BaseModel {
+ constructor(data = {}) {
+ super(FIELDS, data);
+ }
+ }
\ No newline at end of file
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 73d134f5f..3809ea5e4 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
@@ -3,6 +3,7 @@ import ApplicationInterfaceDefinition from "./models/ApplicationInterfaceDefinit
import ApplicationModule from "./models/ApplicationModule";
import BatchQueue from "./models/BatchQueue";
import ComputeResourceDescription from "./models/ComputeResourceDescription";
+import CpuUsage from "./models/CpuUsage";
import CredentialSummary from "./models/CredentialSummary";
import DataProduct from "./models/DataProduct";
import Experiment from "./models/Experiment";
@@ -128,6 +129,23 @@ export default {
},
modelClass: ComputeResourceDescription,
},
+ CpuUsages: {
+ url: "/api/cpu-usages",
+ methods: {
+ get: {
+ url: "/api/cpu-usages",
+ requestType: "get",
+ queryParams: [
+ "fromTime",
+ "toTime",
+ "limit",
+ "offset",
+ ],
+ pagination: true,
+ modelClass: CpuUsage,
+ },
+ },
+ },
CredentialSummaries: {
url: "/api/credential-summaries/",
viewSet: ["list", "retrieve", "delete"],
@@ -317,6 +335,20 @@ export default {
pagination: true,
queryParams: ["limit", "offset"],
modelClass: Group,
+ methods: {
+ getGroupsFilteredByCreationDate: {
+ url: "/api/groups/groups_filtered_by_creation_date",
+ requestType: "get",
+ queryParams: [
+ "fromTime",
+ "toTime",
+ "limit",
+ "offset",
+ ],
+ pagination: true,
+ modelClass: Group
+ },
+ },
},
IAMUserProfiles: {
url: "/api/iam-user-profiles",
diff --git a/django_airavata/apps/api/urls.py b/django_airavata/apps/api/urls.py
index 474ebca1c..0b0ecc0b8 100644
--- a/django_airavata/apps/api/urls.py
+++ b/django_airavata/apps/api/urls.py
@@ -90,6 +90,9 @@
re_path(r'^experiment-statistics',
views.ExperimentStatisticsView.as_view(),
name="experiment-statistics"),
+ re_path(r'^cpu-usages',
+ views.CpuUsageView.as_view(),
+ name="cpu-usage"),
re_path(r'ack-notifications/
/',
views.AckNotificationViewSet.as_view(), name="ack-notifications"),
re_path(r'ack-notifications/', views.AckNotificationViewSet.as_view(),
diff --git a/django_airavata/apps/api/views.py b/django_airavata/apps/api/views.py
index b5cc24e5d..aecb93e8c 100644
--- a/django_airavata/apps/api/views.py
+++ b/django_airavata/apps/api/views.py
@@ -139,6 +139,31 @@ def _send_users_added_to_group(self, internal_user_ids, group):
user=user_profile,
groups=[group],
request=self.request)
+
+ @action(detail=False)
+ def groups_filtered_by_creation_date(self, request):
+ view = self
+
+ 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
+ from_time = int(from_time)
+ 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
+ to_time = int(to_time)
+ limit = int(request.GET.get('limit', '-1'))
+ offset = int(request.GET.get('offset', '0'))
+ group_manager = view.request.profile_service['group_manager']
+ groups = group_manager.getGroupsFilteredByCreationDate(view.authz_token, from_time, to_time)
+ end = offset + limit if limit > 0 else len(groups)
+ groups = groups[offset:end] if groups else []
+ return Response(groups)
class ProjectViewSet(APIBackedViewSet):
@@ -1731,6 +1756,32 @@ def get(self, request, format=None):
response.data['offset'] = offset
return response
+class CpuUsageView(APIView):
+ serializer_class = serializers.CpuUsageSerializer
+ permission_classes = (IsAuthenticated, IsInAdminsGroupPermission,)
+
+ def get(self, request, format=None):
+ 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
+ from_time = int(from_time)
+ 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
+ to_time = int(to_time)
+ limit = int(request.GET.get('limit', '-1'))
+ offset = int(request.GET.get('offset', '0'))
+ cpu_usages = request.airavata_client.getCpuUsages(
+ request.authz_token, settings.GATEWAY_ID, from_time, to_time)
+ end = offset + limit if limit > 0 else len(cpu_usages)
+ cpu_usages = cpu_usages[offset:end] if cpu_usages else []
+ return Response(cpu_usages)
+
class UnverifiedEmailUserViewSet(mixins.ListModelMixin,
mixins.RetrieveModelMixin,
diff --git a/django_airavata/static/common/js/components/BarChart.vue b/django_airavata/static/common/js/components/BarChart.vue
new file mode 100644
index 000000000..ccd2f310e
--- /dev/null
+++ b/django_airavata/static/common/js/components/BarChart.vue
@@ -0,0 +1,33 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/django_airavata/static/common/js/components/LineChart.vue b/django_airavata/static/common/js/components/LineChart.vue
new file mode 100644
index 000000000..d7880fc17
--- /dev/null
+++ b/django_airavata/static/common/js/components/LineChart.vue
@@ -0,0 +1,38 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/django_airavata/static/common/js/components/PieChart.vue b/django_airavata/static/common/js/components/PieChart.vue
new file mode 100644
index 000000000..c5b0b7e77
--- /dev/null
+++ b/django_airavata/static/common/js/components/PieChart.vue
@@ -0,0 +1,33 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/django_airavata/static/common/js/index.js b/django_airavata/static/common/js/index.js
index 68c9719dd..02d16a1c7 100644
--- a/django_airavata/static/common/js/index.js
+++ b/django_airavata/static/common/js/index.js
@@ -1,6 +1,7 @@
import ApplicationCard from "./components/ApplicationCard.vue";
import ApplicationName from "./components/ApplicationName";
import AutocompleteTextInput from "./components/AutocompleteTextInput.vue";
+import BarChart from "./components/BarChart.vue";
import ClipboardCopyButton from "./components/ClipboardCopyButton.vue";
import ClipboardCopyLink from "./components/ClipboardCopyLink.vue";
import ComputeResourceName from "./components/ComputeResourceName";
@@ -14,8 +15,10 @@ import FavoriteToggle from "./components/FavoriteToggle";
import GatewayGroupsBadge from "./components/GatewayGroupsBadge";
import HumanDate from "./components/HumanDate.vue";
import Linkify from "./components/Linkify.vue";
+import LineChart from "./components/LineChart.vue";
import MainLayout from "./components/MainLayout.vue";
import Pager from "./components/Pager.vue";
+import PieChart from "./components/PieChart.vue";
import ShareButton from "./components/ShareButton.vue";
import Sidebar from "./components/Sidebar.vue";
import SidebarFeed from "./components/SidebarFeed.vue";
@@ -46,6 +49,7 @@ const components = {
ApplicationCard,
ApplicationName,
AutocompleteTextInput,
+ BarChart,
ClipboardCopyButton,
ClipboardCopyLink,
ComputeResourceName,
@@ -59,7 +63,9 @@ const components = {
GatewayGroupsBadge,
HumanDate,
Linkify,
+ LineChart,
MainLayout,
+ PieChart,
ShareButton,
Sidebar,
SidebarFeed,
diff --git a/django_airavata/static/common/package.json b/django_airavata/static/common/package.json
index f6a8925da..952c05e69 100644
--- a/django_airavata/static/common/package.json
+++ b/django_airavata/static/common/package.json
@@ -25,6 +25,7 @@
"bootstrap": "^4.3.1",
"bootstrap-vue": "^2.21.2",
"browserslist": "^4.16.7",
+ "chart.js": "^3.7.0",
"clipboard": "^2.0.4",
"core-js": "^3.8.3",
"django-airavata-api": "link:../../apps/api",
@@ -34,7 +35,8 @@
"popper.js": "^1.14.6",
"terser": "^4.1.2",
"vue": "^2.5.22",
- "vue-async-computed": "^3.9.0"
+ "vue-async-computed": "^3.9.0",
+ "vue-chartjs": "^4.1.2"
},
"devDependencies": {
"@babel/core": "^7.12.16",
diff --git a/django_airavata/static/common/yarn.lock b/django_airavata/static/common/yarn.lock
index ecc4c4f12..ed41eaa28 100644
--- a/django_airavata/static/common/yarn.lock
+++ b/django_airavata/static/common/yarn.lock
@@ -2553,6 +2553,11 @@ chalk@^4.1.0:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
+chart.js@^3.7.0:
+ version "3.9.1"
+ resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-3.9.1.tgz#3abf2c775169c4c71217a107163ac708515924b8"
+ integrity sha512-Ro2JbLmvg83gXF5F4sniaQ+lTbSv18E+TIf2cOeiH1Iqd2PGFOtem+DUufMZsCJwFE7ywPOpfXFBwRTGq7dh6w==
+
"chokidar@>=3.0.0 <4.0.0", chokidar@^3.5.3:
version "3.5.3"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"
@@ -7983,6 +7988,11 @@ vue-async-computed@^3.9.0:
resolved "https://registry.yarnpkg.com/vue-async-computed/-/vue-async-computed-3.9.0.tgz#af3181c25168bfe9d86d8ffbc7033bf9e484fe84"
integrity sha512-ac6m/9zxHHNGGKNOU1en8qNk+fAmEbJLuWL7qyQTFuH3vjv3V4urv//QHcVzCobROM6btnaDG2b+XYMncF/ETA==
+vue-chartjs@^4.1.2:
+ version "4.1.2"
+ resolved "https://registry.yarnpkg.com/vue-chartjs/-/vue-chartjs-4.1.2.tgz#f899ba14f3b80660f8d2c610a015341806dfc437"
+ integrity sha512-QSggYjeFv/L4jFSBQpX8NzrAvX0B+Ha6nDgxkTG8tEXxYOOTwKI4phRLe+B4f+REnkmg7hgPY24R0cixZJyXBg==
+
vue-eslint-parser@^8.0.1:
version "8.3.0"
resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-8.3.0.tgz#5d31129a1b3dd89c0069ca0a1c88f970c360bd0d"