Skip to content

Commit 7f94bd3

Browse files
authored
Merge pull request #412 from kikkomep/feat/stats
feat: workflows statistics
2 parents 53418ce + 718ffb8 commit 7f94bd3

File tree

4 files changed

+248
-30
lines changed

4 files changed

+248
-30
lines changed

lifemonitor/api/controllers.py

Lines changed: 67 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,42 @@ def _row_to_dict(row):
5555
return d
5656

5757

58+
@cached(timeout=Timeout.REQUEST)
59+
def stats_workflows_get():
60+
"""
61+
Docstring for stats_get
62+
"""
63+
workflows = None
64+
if current_user and not current_user.is_anonymous:
65+
workflows = lm.get_user_workflows(current_user, include_public=False,
66+
include_subscriptions=True, only_subscriptions=True)
67+
elif current_registry:
68+
workflows = lm.get_registry_workflows(current_registry)
69+
else:
70+
workflows = lm.get_public_workflows()
71+
stats = lm.get_workflows_stats(workflows)
72+
logger.debug("stats_get. Got stats: %s", stats)
73+
return stats
74+
75+
76+
@cached(timeout=Timeout.REQUEST)
77+
def stats_workflows_status_get():
78+
"""
79+
Docstring for stats_get
80+
"""
81+
workflows = None
82+
if current_user and not current_user.is_anonymous:
83+
workflows = lm.get_user_workflows(current_user, include_public=False,
84+
include_subscriptions=True, only_subscriptions=True)
85+
elif current_registry:
86+
workflows = lm.get_registry_workflows(current_registry)
87+
else:
88+
workflows = lm.get_public_workflows()
89+
stats = lm.get_workflows_status_stats(workflows)
90+
logger.debug("stats_get. Got stats: %s", stats)
91+
return stats
92+
93+
5894
@cached(timeout=Timeout.REQUEST)
5995
def workflow_registries_get():
6096
registries = lm.get_workflow_registries()
@@ -108,54 +144,54 @@ def workflow_registries_get_current():
108144
return lm_exceptions.report_problem(401, "Unauthorized")
109145

110146

111-
@cached(timeout=Timeout.REQUEST)
112-
def workflows_get(status=False, versions=False, subscriptions=False,
113-
page: int = 1, per_page: Optional[int] = None,
114-
max_items: Optional[int] = None,
115-
only_subscriptions=False,):
116-
# Prepare pagination info
147+
def _get_workflows_list_(page: int = 1, per_page: Optional[int] = None, max_items: Optional[int] = None,
148+
subscriptions: bool = False, only_subscriptions: bool = False):
117149
page_info = PaginationInfo(page=page, per_page=per_page, max_items=max_items)
118-
# Collect workflows depending on the current user/registry in session
119150
workflows = []
120-
# If a user is logged in
121151
if current_user and not current_user.is_anonymous:
122152
logger.info("Getting workflows for user %s", current_user)
123-
workflows.extend(lm.get_user_workflows(current_user,
124-
include_subscriptions=subscriptions,
125-
include_public=True,
126-
only_subscriptions=only_subscriptions,
127-
page=page_info))
128-
logger.info("Total workflows for user %s: %d", current_user, len(workflows))
129-
# If a registry is in the current session,
130-
# get both public and registry workflows
153+
workflows.extend(lm.get_user_workflows(
154+
current_user,
155+
include_subscriptions=subscriptions,
156+
include_public=True,
157+
only_subscriptions=only_subscriptions,
158+
page=page_info
159+
))
131160
elif current_registry:
132161
logger.info("Getting workflows for registry %s", current_registry)
133162
workflows.extend(lm.get_registry_workflows(
134163
current_registry, include_public=True,
135-
page=page_info))
136-
# Engage public workflows only
164+
page=page_info
165+
))
137166
else:
138167
logger.info("Getting public workflows")
139168
workflows.extend(lm.get_public_workflows(page=page_info))
169+
return list(dict.fromkeys(workflows)), page_info
170+
171+
172+
@cached(timeout=Timeout.REQUEST)
173+
def workflows_get(status=False, versions=False, subscriptions=False,
174+
page: int = 1, per_page: Optional[int] = None,
175+
max_items: Optional[int] = None,
176+
only_subscriptions=False,
177+
stats: bool = False):
178+
workflows, page_info = _get_workflows_list_(page, per_page, max_items,
179+
subscriptions, only_subscriptions)
140180

141181
logger.debug("workflows_get. Got %s workflows (user: %s)", len(workflows), current_user)
142182

143-
# Determine whose subscriptions to include
144183
subscriptions_of = []
145184
if subscriptions and current_user and not current_user.is_anonymous:
146185
subscriptions_of = [current_user]
147186

148-
# Create the serializer
149187
serializer = serializers.ListOfWorkflows(
150188
workflow_status=status,
151189
workflow_versions=versions,
152190
subscriptionsOf=subscriptions_of,
191+
statistics=lm.get_workflows_stats(workflows) if stats else None,
153192
page=page_info
154193
)
155-
156-
# Remove duplicates and serialize the workflows
157-
unique_workflows = list(dict.fromkeys(workflows))
158-
return serializer.dump(unique_workflows)
194+
return serializer.dump(workflows)
159195

160196

161197
def __get_workflow_version__(wf_uuid, wf_version=None) -> models.WorkflowVersion:
@@ -260,13 +296,14 @@ def workflows_rocrate_download(wf_uuid, wf_version):
260296

261297
@authorized
262298
@cached(timeout=Timeout.REQUEST)
263-
def registry_workflows_get(status=False, versions=False,
299+
def registry_workflows_get(status=False, versions=False, stats=False,
264300
page=None, per_page=None, max_items=None):
265301
page_info = PaginationInfo(page=page, per_page=per_page, max_items=max_items)
266302
workflows = lm.get_registry_workflows(current_registry, page=page_info)
267303
logger.debug("workflows_get. Got %s workflows (registry: %s)", len(workflows), current_registry)
268304
return serializers.ListOfWorkflows(
269305
workflow_status=status, workflow_versions=versions,
306+
statistics=lm.get_workflows_stats(workflows) if stats else None,
270307
page=page_info
271308
).dump(workflows)
272309

@@ -282,6 +319,7 @@ def registry_workflows_post(body):
282319
@authorized
283320
@cached(timeout=Timeout.REQUEST)
284321
def registry_user_workflows_get(user_id, status=False, versions=False,
322+
stats=False,
285323
page=None, per_page=None, max_items=None):
286324
if not current_registry:
287325
return lm_exceptions.report_problem(401, "Unauthorized", detail=messages.no_registry_found)
@@ -291,7 +329,8 @@ def registry_user_workflows_get(user_id, status=False, versions=False,
291329
workflows = lm.get_user_registry_workflows(identity.user, current_registry, page=page_info)
292330
logger.debug("registry_user_workflows_get. Got %s workflows (user: %s)", len(workflows), current_user)
293331
return serializers.ListOfWorkflows(
294-
workflow_status=status, workflow_versions=versions, page=page_info
332+
workflow_status=status, workflow_versions=versions,
333+
statistics=lm.get_workflows_stats(workflows) if stats else None, page=page_info
295334
).dump(workflows)
296335
except OAuthIdentityNotFoundException as e:
297336
if logger.isEnabledFor(logging.DEBUG):
@@ -312,6 +351,7 @@ def registry_user_workflows_post(user_id, body):
312351
@authorized
313352
@cached(timeout=Timeout.REQUEST)
314353
def user_workflows_get(status=False, versions=False, subscriptions=False, only_subscriptions: bool = False,
354+
stats=False,
315355
page=None, per_page=None, max_items=None):
316356
if not current_user or current_user.is_anonymous:
317357
return lm_exceptions.report_problem(401, "Unauthorized", detail=messages.no_user_in_session)
@@ -325,6 +365,7 @@ def user_workflows_get(status=False, versions=False, subscriptions=False, only_s
325365
return serializers.ListOfWorkflows(workflow_status=status,
326366
workflow_versions=versions,
327367
subscriptionsOf=[current_user] if subscriptions else None,
368+
statistics=lm.get_workflows_stats(workflows) if stats else None,
328369
page=page_info).dump(workflows)
329370

330371

lifemonitor/api/serializers.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,11 @@
2121
from __future__ import annotations
2222

2323
import logging
24-
from typing import List
24+
from typing import List, Optional
2525
from urllib.parse import urljoin
2626

27+
from marshmallow import fields, post_dump
28+
2729
from lifemonitor import exceptions as lm_exceptions
2830
from lifemonitor import utils as lm_utils
2931
from lifemonitor.api.models.issues import WorkflowRepositoryIssue
@@ -32,7 +34,6 @@
3234
from lifemonitor.serializers import (BaseSchema, ListOfItems,
3335
ResourceMetadataSchema, ResourceSchema,
3436
ma)
35-
from marshmallow import fields, post_dump
3637

3738
from . import models
3839

@@ -525,13 +526,21 @@ class ListOfWorkflows(ListOfItems):
525526

526527
subscriptionsOf: List[auth_models.User] = None
527528

529+
statistics = fields.Method("get_statistics")
530+
528531
def __init__(self, *args,
529532
workflow_status: bool = False, workflow_versions: bool = False,
530-
subscriptionsOf: List[auth_models.User] = None, **kwargs):
533+
subscriptionsOf: List[auth_models.User] = None,
534+
statistics: Optional[object] = None,
535+
**kwargs):
531536
super().__init__(*args, **kwargs)
532537
self.workflow_status = workflow_status
533538
self.workflow_versions = workflow_versions
534539
self.subscriptionsOf = subscriptionsOf
540+
self._statistics = statistics
541+
542+
def get_statistics(self, workflow: models.Workflow):
543+
return self._statistics if self._statistics else None
535544

536545
def get_items(self, obj):
537546
exclude = ['meta', 'links']

lifemonitor/api/services.py

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,11 @@
2222

2323
import logging
2424
from datetime import datetime, timezone
25-
from typing import Dict, List, Optional, Union
25+
from typing import Any, Dict, List, Optional, Union
2626

2727
import lifemonitor.exceptions as lm_exceptions
2828
from lifemonitor.api import models
29+
from lifemonitor.api.models.status import AggregateTestStatus
2930
from lifemonitor.auth.models import (EventType,
3031
ExternalServiceAuthorizationHeader,
3132
HostingService, Notification, Permission,
@@ -822,3 +823,49 @@ def list_workflow_updates(since: datetime = datetime.now) -> Dict[str, datetime]
822823
# "lastUpdate": int(row[2].timestamp() * 1000)
823824
})
824825
return result
826+
827+
@classmethod
828+
def get_workflows_stats(cls, workflows: Optional[List[models.Workflow]] = None,
829+
status: bool = True) -> Dict[str, Any]:
830+
# Reference to the list of workflows
831+
workflows = workflows if workflows is not None else models.Workflow.all()
832+
workflow_versions = [v for w in workflows for v in w.versions.values()]
833+
test_suites = [s for wv in workflow_versions for s in wv.test_suites]
834+
test_instances = [ti for ts in test_suites for ti in ts.test_instances]
835+
836+
# Basic stats
837+
stats = {
838+
"workflows": len(workflows),
839+
"workflow_versions": len(workflow_versions),
840+
"test_suites": len(test_suites),
841+
"test_instances": len(test_instances),
842+
"public_workflows": len([w for w in workflows if w.public]),
843+
"private_workflows": len([w for w in workflows if not w.public]),
844+
}
845+
846+
# Workflows status stats
847+
if status:
848+
stats["workflows_by_status"] = cls.get_workflows_status_stats(workflows)
849+
return stats
850+
851+
@classmethod
852+
def get_workflows_status_stats(cls, workflows: Optional[List[models.Workflow]] = None):
853+
# Reference to the list of workflows
854+
# Use provided workflows or all workflows if None
855+
workflows = workflows if workflows is not None else models.Workflow.all()
856+
857+
# Group workflows by aggregated status
858+
status_counts = {
859+
AggregateTestStatus.ALL_PASSING: 0,
860+
AggregateTestStatus.SOME_PASSING: 0,
861+
AggregateTestStatus.ALL_FAILING: 0,
862+
AggregateTestStatus.NOT_AVAILABLE: 0,
863+
}
864+
865+
# Count workflows per status
866+
for wf in workflows:
867+
status = wf.latest_version.status.aggregated_status
868+
if status not in status_counts:
869+
status_counts[status] = 0
870+
status_counts[status] += 1
871+
return status_counts

0 commit comments

Comments
 (0)