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