argus-alm 0.15.2__py3-none-any.whl → 0.15.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. argus/_version.py +2 -2
  2. argus/client/generic_result.py +6 -1
  3. {argus_alm-0.15.2.dist-info → argus_alm-0.15.3.dist-info}/METADATA +1 -1
  4. argus_alm-0.15.3.dist-info/RECORD +22 -0
  5. argus/backend/.gitkeep +0 -0
  6. argus/backend/__init__.py +0 -0
  7. argus/backend/cli.py +0 -57
  8. argus/backend/controller/__init__.py +0 -0
  9. argus/backend/controller/admin.py +0 -20
  10. argus/backend/controller/admin_api.py +0 -355
  11. argus/backend/controller/api.py +0 -589
  12. argus/backend/controller/auth.py +0 -67
  13. argus/backend/controller/client_api.py +0 -109
  14. argus/backend/controller/main.py +0 -316
  15. argus/backend/controller/notification_api.py +0 -72
  16. argus/backend/controller/notifications.py +0 -13
  17. argus/backend/controller/planner_api.py +0 -194
  18. argus/backend/controller/team.py +0 -129
  19. argus/backend/controller/team_ui.py +0 -19
  20. argus/backend/controller/testrun_api.py +0 -513
  21. argus/backend/controller/view_api.py +0 -188
  22. argus/backend/controller/views_widgets/__init__.py +0 -0
  23. argus/backend/controller/views_widgets/graphed_stats.py +0 -54
  24. argus/backend/controller/views_widgets/graphs.py +0 -68
  25. argus/backend/controller/views_widgets/highlights.py +0 -135
  26. argus/backend/controller/views_widgets/nemesis_stats.py +0 -26
  27. argus/backend/controller/views_widgets/summary.py +0 -43
  28. argus/backend/db.py +0 -98
  29. argus/backend/error_handlers.py +0 -41
  30. argus/backend/events/event_processors.py +0 -34
  31. argus/backend/models/__init__.py +0 -0
  32. argus/backend/models/argus_ai.py +0 -24
  33. argus/backend/models/github_issue.py +0 -60
  34. argus/backend/models/plan.py +0 -24
  35. argus/backend/models/result.py +0 -187
  36. argus/backend/models/runtime_store.py +0 -58
  37. argus/backend/models/view_widgets.py +0 -25
  38. argus/backend/models/web.py +0 -403
  39. argus/backend/plugins/__init__.py +0 -0
  40. argus/backend/plugins/core.py +0 -248
  41. argus/backend/plugins/driver_matrix_tests/controller.py +0 -66
  42. argus/backend/plugins/driver_matrix_tests/model.py +0 -429
  43. argus/backend/plugins/driver_matrix_tests/plugin.py +0 -21
  44. argus/backend/plugins/driver_matrix_tests/raw_types.py +0 -62
  45. argus/backend/plugins/driver_matrix_tests/service.py +0 -61
  46. argus/backend/plugins/driver_matrix_tests/udt.py +0 -42
  47. argus/backend/plugins/generic/model.py +0 -86
  48. argus/backend/plugins/generic/plugin.py +0 -15
  49. argus/backend/plugins/generic/types.py +0 -14
  50. argus/backend/plugins/loader.py +0 -39
  51. argus/backend/plugins/sct/controller.py +0 -224
  52. argus/backend/plugins/sct/plugin.py +0 -37
  53. argus/backend/plugins/sct/resource_setup.py +0 -177
  54. argus/backend/plugins/sct/service.py +0 -682
  55. argus/backend/plugins/sct/testrun.py +0 -288
  56. argus/backend/plugins/sct/udt.py +0 -100
  57. argus/backend/plugins/sirenada/model.py +0 -118
  58. argus/backend/plugins/sirenada/plugin.py +0 -16
  59. argus/backend/service/admin.py +0 -26
  60. argus/backend/service/argus_service.py +0 -696
  61. argus/backend/service/build_system_monitor.py +0 -185
  62. argus/backend/service/client_service.py +0 -127
  63. argus/backend/service/event_service.py +0 -18
  64. argus/backend/service/github_service.py +0 -233
  65. argus/backend/service/jenkins_service.py +0 -269
  66. argus/backend/service/notification_manager.py +0 -159
  67. argus/backend/service/planner_service.py +0 -608
  68. argus/backend/service/release_manager.py +0 -229
  69. argus/backend/service/results_service.py +0 -690
  70. argus/backend/service/stats.py +0 -610
  71. argus/backend/service/team_manager_service.py +0 -82
  72. argus/backend/service/test_lookup.py +0 -172
  73. argus/backend/service/testrun.py +0 -489
  74. argus/backend/service/user.py +0 -308
  75. argus/backend/service/views.py +0 -219
  76. argus/backend/service/views_widgets/__init__.py +0 -0
  77. argus/backend/service/views_widgets/graphed_stats.py +0 -180
  78. argus/backend/service/views_widgets/highlights.py +0 -374
  79. argus/backend/service/views_widgets/nemesis_stats.py +0 -34
  80. argus/backend/template_filters.py +0 -27
  81. argus/backend/tests/__init__.py +0 -0
  82. argus/backend/tests/client_service/__init__.py +0 -0
  83. argus/backend/tests/client_service/test_submit_results.py +0 -79
  84. argus/backend/tests/conftest.py +0 -180
  85. argus/backend/tests/results_service/__init__.py +0 -0
  86. argus/backend/tests/results_service/test_best_results.py +0 -178
  87. argus/backend/tests/results_service/test_cell.py +0 -65
  88. argus/backend/tests/results_service/test_chartjs_additional_functions.py +0 -259
  89. argus/backend/tests/results_service/test_create_chartjs.py +0 -220
  90. argus/backend/tests/results_service/test_result_metadata.py +0 -100
  91. argus/backend/tests/results_service/test_results_service.py +0 -203
  92. argus/backend/tests/results_service/test_validation_rules.py +0 -213
  93. argus/backend/tests/view_widgets/__init__.py +0 -0
  94. argus/backend/tests/view_widgets/test_highlights_api.py +0 -532
  95. argus/backend/util/common.py +0 -65
  96. argus/backend/util/config.py +0 -38
  97. argus/backend/util/encoders.py +0 -56
  98. argus/backend/util/logsetup.py +0 -80
  99. argus/backend/util/module_loaders.py +0 -30
  100. argus/backend/util/send_email.py +0 -91
  101. argus/client/tests/__init__.py +0 -0
  102. argus/client/tests/conftest.py +0 -19
  103. argus/client/tests/test_package.py +0 -45
  104. argus/client/tests/test_results.py +0 -224
  105. argus_alm-0.15.2.dist-info/RECORD +0 -122
  106. {argus_alm-0.15.2.dist-info → argus_alm-0.15.3.dist-info}/WHEEL +0 -0
  107. {argus_alm-0.15.2.dist-info → argus_alm-0.15.3.dist-info}/entry_points.txt +0 -0
  108. {argus_alm-0.15.2.dist-info → argus_alm-0.15.3.dist-info}/licenses/LICENSE +0 -0
  109. {argus_alm-0.15.2.dist-info → argus_alm-0.15.3.dist-info}/top_level.txt +0 -0
@@ -1,610 +0,0 @@
1
- from collections import defaultdict
2
- from functools import reduce
3
- import json
4
- import logging
5
-
6
- from datetime import datetime
7
- from typing import Any, TypedDict
8
- from uuid import UUID
9
-
10
- from cassandra.cqlengine.models import Model
11
- from argus.backend.models.github_issue import GithubIssue, IssueLink
12
- from argus.backend.models.plan import ArgusReleasePlan
13
- from argus.backend.plugins.loader import all_plugin_models
14
- from argus.backend.util.common import chunk, get_build_number
15
- from argus.common.enums import TestStatus, TestInvestigationStatus
16
- from argus.backend.models.web import ArgusRelease, ArgusGroup, ArgusScheduleTest, ArgusTest, \
17
- ArgusTestRunComment, ArgusUserView
18
- from argus.backend.db import ScyllaCluster
19
-
20
- LOGGER = logging.getLogger(__name__)
21
-
22
-
23
- class TestRunStatRow(TypedDict):
24
- build_id: str
25
- status: TestStatus
26
- investigation_status: TestInvestigationStatus
27
- assignee: UUID
28
- scylla_version: str # TODO: rework SCT specific field
29
- start_time: datetime
30
- end_time: datetime
31
- heartbeat: int
32
- id: UUID
33
- test_id: UUID
34
- group_id: UUID
35
- release_id: UUID
36
- build_job_url: str
37
-
38
-
39
- class ComparableTestStatus:
40
- PRIORITY_MAP = {
41
- TestStatus.FAILED: 10,
42
- TestStatus.TEST_ERROR: 10,
43
- TestStatus.ABORTED: 9,
44
- TestStatus.RUNNING: 8,
45
- TestStatus.CREATED: 7,
46
- TestStatus.PASSED: 6,
47
- }
48
-
49
- def __init__(self, status: TestStatus):
50
- self._status = status
51
-
52
- def _get_prio(self):
53
- return self.PRIORITY_MAP.get(self._status, 0)
54
-
55
- def __eq__(self, __o: object) -> bool:
56
- if not isinstance(__o, ComparableTestStatus):
57
- return False
58
- return self._get_prio() == __o._get_prio()
59
-
60
- def __ne__(self, __o: object) -> bool:
61
- if not isinstance(__o, ComparableTestStatus):
62
- return False
63
- return not self.__eq__(__o)
64
-
65
- def __lt__(self, __o: object) -> bool:
66
- if not isinstance(__o, ComparableTestStatus):
67
- return False
68
- return self._get_prio() < __o._get_prio()
69
-
70
- def __gt__(self, __o: object) -> bool:
71
- if not isinstance(__o, ComparableTestStatus):
72
- return False
73
- return self._get_prio() > __o._get_prio()
74
-
75
- def __ge__(self, __o: object) -> bool:
76
- if not isinstance(__o, ComparableTestStatus):
77
- return False
78
- return self._get_prio() >= __o._get_prio()
79
-
80
- def __le__(self, __o: object) -> bool:
81
- if not isinstance(__o, ComparableTestStatus):
82
- return False
83
- return self._get_prio() <= __o._get_prio()
84
-
85
-
86
- class ComparableTestInvestigationStatus:
87
- PRIORITY_MAP = {
88
- TestInvestigationStatus.NOT_INVESTIGATED: 10,
89
- TestInvestigationStatus.IN_PROGRESS: 9,
90
- TestInvestigationStatus.INVESTIGATED: 8,
91
- TestInvestigationStatus.IGNORED: 7,
92
- }
93
-
94
- def __init__(self, status: TestInvestigationStatus):
95
- self._status = status
96
-
97
- def _get_prio(self):
98
- return self.PRIORITY_MAP.get(self._status, 0)
99
-
100
- def __eq__(self, __o: object) -> bool:
101
- if not isinstance(__o, ComparableTestInvestigationStatus):
102
- return False
103
- return self._get_prio() == __o._get_prio()
104
-
105
- def __ne__(self, __o: object) -> bool:
106
- if not isinstance(__o, ComparableTestInvestigationStatus):
107
- return False
108
- return not self.__eq__(__o)
109
-
110
- def __lt__(self, __o: object) -> bool:
111
- if not isinstance(__o, ComparableTestInvestigationStatus):
112
- return False
113
- return self._get_prio() < __o._get_prio()
114
-
115
- def __gt__(self, __o: object) -> bool:
116
- if not isinstance(__o, ComparableTestInvestigationStatus):
117
- return False
118
- return self._get_prio() > __o._get_prio()
119
-
120
- def __ge__(self, __o: object) -> bool:
121
- if not isinstance(__o, ComparableTestInvestigationStatus):
122
- return False
123
- return self._get_prio() >= __o._get_prio()
124
-
125
- def __le__(self, __o: object) -> bool:
126
- if not isinstance(__o, ComparableTestInvestigationStatus):
127
- return False
128
- return self._get_prio() <= __o._get_prio()
129
-
130
-
131
- def generate_field_status_map(
132
- last_runs: list[TestRunStatRow],
133
- field_name="status",
134
- container_class=TestStatus,
135
- cmp_class=ComparableTestStatus
136
- ) -> dict[int, tuple[str, TestRunStatRow]]:
137
-
138
- status_map = {}
139
- for run in last_runs:
140
- run_number = get_build_number(run["build_job_url"])
141
- match status := status_map.get(run_number):
142
- case (str(), dict()):
143
- last_status, _ = status
144
- if cmp_class(container_class(last_status)) < cmp_class(container_class(run[field_name])):
145
- status_map[run_number] = (run[field_name], run)
146
- case _:
147
- status_map[run_number] = (run[field_name], run)
148
- return status_map
149
-
150
-
151
- def _fetch_multiple_release_queries(entity: Model, releases: list[str]):
152
- result_set = []
153
- for release_id in releases:
154
- result_set.extend(entity.filter(release_id=release_id).all())
155
- return result_set
156
-
157
-
158
- def fetch_issues(release: list[UUID] | UUID):
159
- if isinstance(release, UUID):
160
- links = IssueLink.filter(release_id=release)
161
- else:
162
- links = _fetch_multiple_release_queries(IssueLink, release)
163
-
164
- unique_issues = {link.issue_id for link in links}
165
- resolved_issues = {}
166
- for batch in chunk(unique_issues):
167
- for issue in GithubIssue.filter(id__in=batch).all():
168
- resolved_issues[issue.id] = issue
169
-
170
- linked_issues = []
171
- for link in links:
172
- linked = dict(link.items())
173
- issue = resolved_issues.get(link.issue_id)
174
- linked = {
175
- **linked,
176
- **dict(issue.items())
177
- }
178
- linked_issues.append(linked)
179
-
180
- return linked_issues
181
-
182
-
183
- class ViewStats:
184
- def __init__(self, release: ArgusUserView) -> None:
185
- self.release = release
186
- self.groups: list[GroupStats] = []
187
- self.status_map = {status: 0 for status in TestStatus}
188
- self.total_tests = 0
189
- self.last_status = TestStatus.NOT_PLANNED
190
- self.last_investigation_status = TestInvestigationStatus.NOT_INVESTIGATED
191
- self.has_bug_report = False
192
- self.issues: list[GithubIssue] = []
193
- self.comments: list[ArgusTestRunComment] = []
194
- self.plans: list[ArgusReleasePlan] = []
195
- self.test_schedules = {}
196
- self.forced_collection = False
197
- self.rows = []
198
- self.releases = {}
199
- self.all_tests = []
200
-
201
- def to_dict(self) -> dict:
202
- converted_groups = {str(group.group.id): group.to_dict() for group in self.groups}
203
- aggregated_investigation_status = {}
204
- for group in converted_groups.values():
205
- for investigation_status in TestInvestigationStatus:
206
- current_status = aggregated_investigation_status.get(investigation_status.value, {})
207
- result = {
208
- status.value: current_status.get(status.value, 0) +
209
- group.get(investigation_status.value, {}).get(status, 0)
210
- for status in TestStatus
211
- }
212
- aggregated_investigation_status[investigation_status.value] = result
213
-
214
- return {
215
- "release": dict(self.release.items()),
216
- "releases": self.releases,
217
- "groups": converted_groups,
218
- "total": self.total_tests,
219
- **self.status_map,
220
- "disabled": False,
221
- "perpetual": False,
222
- "lastStatus": self.last_investigation_status,
223
- "lastInvestigationStatus": self.last_investigation_status,
224
- "hasBugReport": self.has_bug_report,
225
- **aggregated_investigation_status
226
- }
227
-
228
- def collect(self, rows: list[TestRunStatRow], limited=False, force=False, dict: dict[str, TestRunStatRow] | None = None, tests: list[ArgusTest] = None, version_filter: str = None) -> None:
229
- self.forced_collection = force
230
- all_release_ids = list({t.release_id for t in tests})
231
- if not limited:
232
- if self.release.plan_id:
233
- plan = ArgusReleasePlan.get(id=self.release.plan_id)
234
- self.plans = [plan]
235
- else:
236
- plans_by_release = [ArgusReleasePlan.filter(release_id=release_id).all()
237
- for release_id in all_release_ids]
238
- plans: list[ArgusReleasePlan] = [plan for plans in plans_by_release for plan in plans]
239
- self.plans = plans if not version_filter else [
240
- plan for plan in plans if version_filter == plan.target_version]
241
- # TODO: Legacy and unconditional, will show extra data.
242
- self.test_schedules = reduce(
243
- lambda acc, row: acc[row["test_id"]].append(row) or acc,
244
- _fetch_multiple_release_queries(ArgusScheduleTest, all_release_ids),
245
- defaultdict(list)
246
- )
247
-
248
- self.rows = rows
249
- self.dict = dict
250
- if not limited or force:
251
- self.issues = reduce(
252
- lambda acc, row: acc[row["run_id"]].append(row) or acc,
253
- fetch_issues(all_release_ids),
254
- defaultdict(list)
255
- )
256
- self.comments = reduce(
257
- lambda acc, row: acc[row["test_run_id"]].append(row) or acc,
258
- _fetch_multiple_release_queries(ArgusTestRunComment, all_release_ids),
259
- defaultdict(list)
260
- )
261
- self.all_tests = tests
262
- groups = []
263
- for slice in chunk(list({t.release_id for t in tests})):
264
- self.releases.update({str(release.id): release for release in ArgusRelease.filter(id__in=slice).all()})
265
-
266
- for slice in chunk(list({t.group_id for t in tests})):
267
- groups.extend(ArgusGroup.filter(id__in=slice).all())
268
- for group in groups:
269
- if group.enabled:
270
- stats = GroupStats(group=group, parent_release=self)
271
- stats.collect(limited=limited)
272
- self.groups.append(stats)
273
-
274
- def increment_status(self, status=TestStatus.NOT_PLANNED):
275
- self.total_tests += 1
276
- self.status_map[TestStatus(status)] += 1
277
- self.last_status = TestStatus(status)
278
-
279
-
280
- class ReleaseStats:
281
- def __init__(self, release: ArgusRelease) -> None:
282
- self.release = release
283
- self.groups: list[GroupStats] = []
284
- self.status_map = {status: 0 for status in TestStatus}
285
- self.total_tests = 0
286
- self.last_status = TestStatus.NOT_PLANNED
287
- self.last_investigation_status = TestInvestigationStatus.NOT_INVESTIGATED
288
- self.has_bug_report = False
289
- self.issues: list[GithubIssue] = []
290
- self.comments: list[ArgusTestRunComment] = []
291
- self.plans: list[ArgusReleasePlan] = []
292
- self.test_schedules = {}
293
- self.forced_collection = False
294
- self.rows = []
295
- self.all_tests = []
296
-
297
- def to_dict(self) -> dict:
298
- converted_groups = {str(group.group.id): group.to_dict() for group in self.groups}
299
- aggregated_investigation_status = {}
300
- for group in converted_groups.values():
301
- for investigation_status in TestInvestigationStatus:
302
- current_status = aggregated_investigation_status.get(investigation_status.value, {})
303
- result = {
304
- status.value: current_status.get(status.value, 0) +
305
- group.get(investigation_status.value, {}).get(status, 0)
306
- for status in TestStatus
307
- }
308
- aggregated_investigation_status[investigation_status.value] = result
309
-
310
- return {
311
- "release": dict(self.release.items()),
312
- "groups": converted_groups,
313
- "total": self.total_tests,
314
- **self.status_map,
315
- "disabled": not self.release.enabled,
316
- "perpetual": self.release.perpetual,
317
- "lastStatus": self.last_investigation_status,
318
- "lastInvestigationStatus": self.last_investigation_status,
319
- "hasBugReport": self.has_bug_report,
320
- **aggregated_investigation_status
321
- }
322
-
323
- def collect(self, rows: list[TestRunStatRow], limited=False, force=False, dict: dict | None = None, tests=None, version_filter: str = None) -> None:
324
- self.forced_collection = force
325
- if not self.release.enabled and not force:
326
- return
327
-
328
- if not limited:
329
- plans: list[ArgusReleasePlan] = list(ArgusReleasePlan.filter(release_id=self.release.id).all())
330
- self.plans = plans if not filter else [plan for plan in plans if version_filter == plan.target_version]
331
- self.test_schedules = reduce(
332
- lambda acc, row: acc[row["test_id"]].append(row) or acc,
333
- ArgusScheduleTest.filter(release_id=self.release.id).all(),
334
- defaultdict(list)
335
- )
336
-
337
- self.rows = rows
338
- self.dict = dict
339
- if not limited or force:
340
- self.issues = reduce(
341
- lambda acc, row: acc[row["run_id"]].append(row) or acc,
342
- fetch_issues(self.release.id),
343
- defaultdict(list)
344
- )
345
- self.comments = reduce(
346
- lambda acc, row: acc[row["test_run_id"]].append(row) or acc,
347
- ArgusTestRunComment.filter(release_id=self.release.id).all(),
348
- defaultdict(list)
349
- )
350
- self.all_tests = ArgusTest.filter(release_id=self.release.id).all() if not tests else tests
351
- groups: list[ArgusGroup] = ArgusGroup.filter(release_id=self.release.id).all()
352
- for group in groups:
353
- if group.enabled:
354
- stats = GroupStats(group=group, parent_release=self)
355
- stats.collect(limited=limited)
356
- self.groups.append(stats)
357
-
358
- def increment_status(self, status=TestStatus.NOT_PLANNED):
359
- self.total_tests += 1
360
- self.status_map[TestStatus(status)] += 1
361
- self.last_status = TestStatus(status)
362
-
363
-
364
- class GroupStats:
365
- def __init__(self, group: ArgusGroup, parent_release: ReleaseStats) -> None:
366
- self.group = group
367
- self.parent_release = parent_release
368
- self.status_map = {status: 0 for status in TestStatus}
369
- self.total_tests = 0
370
- self.last_status = TestStatus.NOT_PLANNED
371
- self.last_investigation_status = TestInvestigationStatus.NOT_INVESTIGATED
372
- self.disabled = False
373
- self.tests: list[TestStats] = []
374
-
375
- def to_dict(self) -> dict:
376
- converted_tests = {str(test.test.id): test.to_dict() for test in self.tests}
377
- investigation_progress = {}
378
- for test in converted_tests.values():
379
- progress_for_status = investigation_progress.get(test["investigation_status"], {})
380
- status_count = progress_for_status.get(test["status"], 0)
381
- status_count += 1
382
- progress_for_status[test["status"]] = status_count
383
- investigation_progress[test["investigation_status"]] = progress_for_status
384
-
385
- return {
386
- "group": dict(self.group.items()),
387
- "total": self.total_tests,
388
- **self.status_map,
389
- "lastStatus": self.last_status,
390
- "lastInvestigationStatus": self.last_investigation_status,
391
- "disabled": self.disabled,
392
- "tests": converted_tests,
393
- **investigation_progress
394
- }
395
-
396
- def collect(self, limited=False):
397
- tests = [test for test in self.parent_release.all_tests if test.group_id == self.group.id]
398
-
399
- for test in tests:
400
- if test.enabled:
401
- is_scheduled = False
402
- for plan in self.parent_release.plans:
403
- if test.id in plan.tests:
404
- is_scheduled = True
405
- break
406
- if self.group.id in plan.groups:
407
- is_scheduled = True
408
- break
409
- stats = TestStats(
410
- test=test,
411
- parent_group=self,
412
- scheduled=is_scheduled,
413
- schedules=self.parent_release.test_schedules.get(test.id, []),
414
- )
415
- stats.collect(limited=limited)
416
- self.tests.append(stats)
417
-
418
- def increment_status(self, status=TestStatus.NOT_PLANNED):
419
- self.status_map[TestStatus(status)] += 1
420
- self.total_tests += 1
421
- self.last_status = TestStatus(status)
422
- self.parent_release.increment_status(status)
423
-
424
-
425
- class TestStats:
426
- def __init__(
427
- self,
428
- test: ArgusTest,
429
- parent_group: GroupStats,
430
- scheduled: bool = False,
431
- schedules: list[ArgusScheduleTest] | None = None,
432
- ) -> None:
433
- self.test = test
434
- self.parent_group = parent_group
435
- self.start_time = datetime.fromtimestamp(0)
436
- self.status = TestStatus.NOT_PLANNED
437
- self.investigation_status = TestInvestigationStatus.NOT_INVESTIGATED
438
- self.last_runs: list[dict] = []
439
- self.has_bug_report = False
440
- self.has_comments = False
441
- self.is_scheduled = scheduled
442
- self.schedules = schedules if schedules else tuple()
443
- self.is_scheduled_legacy = len(self.schedules) > 0 # TODO: Remove once old scheduling system is removed
444
- self.tracked_run_number = None
445
-
446
- def to_dict(self) -> dict:
447
- return {
448
- "test": dict(self.test.items()),
449
- "status": self.status,
450
- "investigation_status": self.investigation_status,
451
- "last_runs": self.last_runs,
452
- "start_time": self.start_time,
453
- "hasBugReport": self.has_bug_report,
454
- "hasComments": self.has_comments,
455
- "buildNumber": self.tracked_run_number,
456
- "buildId": self.test.build_system_id,
457
- }
458
-
459
- def collect(self, limited=False):
460
-
461
- # TODO: Parametrize run limit
462
- # FIXME: This is only a mitigation, build_number overflows on the build system side.
463
- if not self.parent_group.parent_release.dict:
464
- last_runs = [r for r in self.parent_group.parent_release.rows if r["build_id"] == self.test.build_system_id]
465
- else:
466
- last_runs = self.parent_group.parent_release.dict.get(self.test.build_system_id, [])
467
- last_runs: list[TestRunStatRow] = sorted(
468
- last_runs, reverse=True, key=lambda r: get_build_number(r["build_job_url"]))
469
- try:
470
- last_run = last_runs[0]
471
- except IndexError:
472
- self.status = TestStatus.NOT_RUN if self.is_scheduled or self.is_scheduled_legacy else TestStatus.NOT_PLANNED
473
- self.parent_group.increment_status(status=self.status)
474
- return
475
- status_map = generate_field_status_map(last_runs)
476
-
477
- worst_case = status_map.get(get_build_number(last_run["build_job_url"]))
478
- self.status = worst_case[0]
479
- self.investigation_status = worst_case[1]["investigation_status"]
480
- self.start_time = last_run["start_time"]
481
-
482
- self.parent_group.increment_status(status=self.status)
483
- if limited and not self.parent_group.parent_release.forced_collection:
484
- return
485
-
486
- self.last_runs = [
487
- {
488
- "id": run["id"],
489
- "status": run["status"],
490
- "build_number": get_build_number(run["build_job_url"]),
491
- "build_job_name": run["build_id"],
492
- "start_time": run["start_time"],
493
- "assignee": run["assignee"],
494
- "issues": [dict(issue.items()) for issue in self.parent_group.parent_release.issues[run["id"]]],
495
- "comments": [dict(comment.items()) for comment in self.parent_group.parent_release.comments[run["id"]]],
496
- }
497
- for run in last_runs
498
- ]
499
- try:
500
- target_run = next(run for run in self.last_runs if run["id"] == worst_case[1]["id"])
501
- except StopIteration:
502
- target_run = worst_case[1]
503
- target_run["issues"] = [dict(issue.items())
504
- for issue in self.parent_group.parent_release.issues[target_run["id"]]]
505
- target_run["comments"] = [dict(comment.items())
506
- for comment in self.parent_group.parent_release.comments[target_run["id"]]]
507
- self.has_bug_report = len(target_run["issues"]) > 0
508
- self.parent_group.parent_release.has_bug_report = self.has_bug_report or self.parent_group.parent_release.has_bug_report
509
- self.has_comments = len(target_run["comments"]) > 0
510
- self.last_runs = self.last_runs[:5]
511
- self.tracked_run_number = target_run.get("build_number", get_build_number(target_run.get("build_job_url")))
512
-
513
-
514
- class ReleaseStatsCollector:
515
- def __init__(self, release_name: str, release_version: str | None = None) -> None:
516
- self.database = ScyllaCluster.get()
517
- self.session = self.database.get_session()
518
- self.release = None
519
- self.release_stats = None
520
- self.release_rows = []
521
- self.release_name = release_name
522
- self.release_version = release_version
523
-
524
- def collect(self, limited=False, force=False, include_no_version=False) -> dict:
525
- self.release: ArgusRelease = ArgusRelease.get(name=self.release_name)
526
- all_tests: list[ArgusTest] = list(ArgusTest.filter(release_id=self.release.id).all())
527
- build_ids = reduce(lambda acc, test: acc[test.plugin_name or "unknown"].append(
528
- test.build_system_id) or acc, all_tests, defaultdict(list))
529
- self.release_rows = [futures for plugin in all_plugin_models()
530
- for futures in plugin.get_stats_for_release(release=self.release, build_ids=build_ids.get(plugin._plugin_name, []))]
531
- self.release_rows = [row for future in self.release_rows for row in future.result()]
532
- if self.release.dormant and not force:
533
- return {
534
- "dormant": True
535
- }
536
- if self.release_version:
537
- if include_no_version:
538
- expr = lambda row: row["scylla_version"] == self.release_version or not row["scylla_version"]
539
- elif self.release_version == "!noVersion":
540
- expr = lambda row: not row["scylla_version"]
541
- else:
542
- expr = lambda row: row["scylla_version"] == self.release_version
543
- else:
544
- if include_no_version:
545
- expr = lambda row: row
546
- else:
547
- expr = lambda row: row["scylla_version"]
548
- self.release_rows = list(filter(expr, self.release_rows))
549
- self.release_dict = {}
550
- for row in self.release_rows:
551
- runs = self.release_dict.get(row["build_id"], [])
552
- runs.append(row)
553
- self.release_dict[row["build_id"]] = runs
554
-
555
- self.release_stats = ReleaseStats(release=self.release)
556
- self.release_stats.collect(rows=self.release_rows, limited=limited, force=force,
557
- dict=self.release_dict, tests=all_tests, version_filter=self.release_version)
558
- return self.release_stats.to_dict()
559
-
560
-
561
- class ViewStatsCollector:
562
- def __init__(self, view_id: UUID, filter: str | None = None) -> None:
563
- self.database = ScyllaCluster.get()
564
- self.session = self.database.get_session()
565
- self.view = None
566
- self.view_stats = None
567
- self.view_rows = []
568
- self.runs_by_build_id = {}
569
- self.view_id = view_id
570
- self.filter = filter
571
-
572
- def collect(self, limited=False, force=False, include_no_version = False, widget_id: int = None) -> dict:
573
- self.view: ArgusUserView = ArgusUserView.get(id=self.view_id)
574
- widget: dict[str, Any] | None = None
575
- if isinstance(widget_id, int):
576
- settings = json.loads(self.view.widget_settings)
577
- widget = next((widget for widget in settings if widget["position"] == widget_id), None)
578
- all_tests: list[ArgusTest] = []
579
- for slice in chunk(self.view.tests):
580
- all_tests.extend(ArgusTest.filter(id__in=slice).all())
581
-
582
- if widget and widget.get("filter"):
583
- all_tests = [test for test in all_tests if any(str(test[key]) in widget["filter"] for key in ["id", "group_id", "release_id"])]
584
- build_ids = reduce(lambda acc, test: acc[test.plugin_name or "unknown"].append(test.build_system_id) or acc, all_tests, defaultdict(list))
585
- self.view_rows = [futures for plugin in all_plugin_models()
586
- for futures in plugin.get_stats_for_release(release=self.view, build_ids=build_ids.get(plugin._plugin_name, []))]
587
- self.view_rows = [row for future in self.view_rows for row in future.result()]
588
-
589
- if self.filter:
590
- if include_no_version:
591
- expr = lambda row: row["scylla_version"] == self.filter or not row["scylla_version"]
592
- elif self.filter == "!noVersion":
593
- expr = lambda row: not row["scylla_version"]
594
- else:
595
- expr = lambda row: row["scylla_version"] == self.filter
596
- else:
597
- if include_no_version:
598
- expr = lambda row: row
599
- else:
600
- expr = lambda row: row["scylla_version"]
601
- self.view_rows = list(filter(expr, self.view_rows))
602
- for row in self.view_rows:
603
- runs = self.runs_by_build_id.get(row["build_id"], [])
604
- runs.append(row)
605
- self.runs_by_build_id[row["build_id"]] = runs
606
-
607
- self.view_stats = ViewStats(release=self.view)
608
- self.view_stats.collect(rows=self.view_rows, limited=limited, force=force,
609
- dict=self.runs_by_build_id, tests=all_tests, version_filter=self.filter)
610
- return self.view_stats.to_dict()
@@ -1,82 +0,0 @@
1
- import logging
2
- from uuid import UUID
3
-
4
- from flask.globals import g
5
- from argus.backend.db import ScyllaCluster
6
- from argus.backend.models.web import Team, User
7
-
8
- LOGGER = logging.getLogger(__name__)
9
-
10
-
11
- class TeamManagerException(Exception):
12
- pass
13
-
14
-
15
- class TeamManagerService:
16
- def __init__(self, database: ScyllaCluster | None = None) -> None:
17
- if not database:
18
- database = ScyllaCluster.get()
19
-
20
- self.session = database.get_session()
21
- self.database = database
22
-
23
- def create_team(self, name: str, leader: UUID, members: list[UUID]):
24
- team = Team()
25
- team.name = name
26
- team.leader = leader
27
- team.members = [leader, *members]
28
-
29
- team.save()
30
- return team
31
-
32
- def get_teams_for_user(self, user_id: UUID) -> list[Team]:
33
- return list(Team.filter(leader=user_id).all())
34
-
35
- def get_team_by_id(self, team_id: UUID) -> Team:
36
- try:
37
- return Team.get(id=team_id)
38
- except Team.DoesNotExist as exc:
39
- raise TeamManagerException(f"Team {team_id} does not exist", team_id) from exc
40
-
41
- def edit_team(self, team_id: UUID, name: str, members: list[UUID]):
42
- user: User = g.user
43
- try:
44
- team: Team = Team.get(id=team_id)
45
- if team.leader != user.id:
46
- raise TeamManagerException(f"Cannot edit team \"{team.name}\" as it doesn't belong to the user.")
47
- team.name = name
48
- team.members = [team.leader, *members] if team.leader not in members else members
49
-
50
- team.save()
51
-
52
- return team
53
- except Team.DoesNotExist as exc:
54
- raise TeamManagerException(f"Team {team_id} doesn't exist!", team_id) from exc
55
-
56
- def edit_team_motd(self, team_id: UUID, message: str):
57
- user: User = g.user
58
- try:
59
- team: Team = Team.get(id=team_id)
60
- if team.leader != user.id:
61
- raise TeamManagerException(f"Cannot edit team \"{team.name}\"'s MOTD as it doesn't belong to the user.")
62
- team.motd = message
63
-
64
- team.save()
65
- except Team.DoesNotExist as exc:
66
- raise TeamManagerException(f"Team {team_id} doesn't exist!", team_id) from exc
67
-
68
- def delete_team(self, team_id: UUID):
69
- user: User = g.user
70
- try:
71
- team: Team = Team.get(id=team_id)
72
- if team.leader != user.id:
73
- raise TeamManagerException(f"Cannot delete team \"{team.name}\" as it doesn't belong to the user.")
74
- team.delete()
75
- except Team.DoesNotExist as exc:
76
- raise TeamManagerException(f"Team {team_id} doesn't exist!", team_id) from exc
77
-
78
- def get_users_teams(self, user_id: UUID) -> list[Team]:
79
- teams_containing_user = list(Team.all())
80
- users_teams = self.get_teams_for_user(user_id)
81
- created_teams_ids = [team.id for team in users_teams]
82
- return [*users_teams, *[team for team in teams_containing_user if team.id not in created_teams_ids]]