argus-alm 0.15.1__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.
- argus/_version.py +2 -2
- argus/client/generic_result.py +6 -1
- {argus_alm-0.15.1.dist-info → argus_alm-0.15.3.dist-info}/METADATA +1 -1
- argus_alm-0.15.3.dist-info/RECORD +22 -0
- argus/backend/.gitkeep +0 -0
- argus/backend/__init__.py +0 -0
- argus/backend/cli.py +0 -57
- argus/backend/controller/__init__.py +0 -0
- argus/backend/controller/admin.py +0 -20
- argus/backend/controller/admin_api.py +0 -355
- argus/backend/controller/api.py +0 -589
- argus/backend/controller/auth.py +0 -67
- argus/backend/controller/client_api.py +0 -109
- argus/backend/controller/main.py +0 -316
- argus/backend/controller/notification_api.py +0 -72
- argus/backend/controller/notifications.py +0 -13
- argus/backend/controller/planner_api.py +0 -194
- argus/backend/controller/team.py +0 -129
- argus/backend/controller/team_ui.py +0 -19
- argus/backend/controller/testrun_api.py +0 -513
- argus/backend/controller/view_api.py +0 -188
- argus/backend/controller/views_widgets/__init__.py +0 -0
- argus/backend/controller/views_widgets/graphed_stats.py +0 -54
- argus/backend/controller/views_widgets/graphs.py +0 -68
- argus/backend/controller/views_widgets/highlights.py +0 -135
- argus/backend/controller/views_widgets/nemesis_stats.py +0 -26
- argus/backend/controller/views_widgets/summary.py +0 -43
- argus/backend/db.py +0 -98
- argus/backend/error_handlers.py +0 -41
- argus/backend/events/event_processors.py +0 -34
- argus/backend/models/__init__.py +0 -0
- argus/backend/models/argus_ai.py +0 -24
- argus/backend/models/github_issue.py +0 -60
- argus/backend/models/plan.py +0 -24
- argus/backend/models/result.py +0 -187
- argus/backend/models/runtime_store.py +0 -58
- argus/backend/models/view_widgets.py +0 -25
- argus/backend/models/web.py +0 -403
- argus/backend/plugins/__init__.py +0 -0
- argus/backend/plugins/core.py +0 -248
- argus/backend/plugins/driver_matrix_tests/controller.py +0 -66
- argus/backend/plugins/driver_matrix_tests/model.py +0 -429
- argus/backend/plugins/driver_matrix_tests/plugin.py +0 -21
- argus/backend/plugins/driver_matrix_tests/raw_types.py +0 -62
- argus/backend/plugins/driver_matrix_tests/service.py +0 -61
- argus/backend/plugins/driver_matrix_tests/udt.py +0 -42
- argus/backend/plugins/generic/model.py +0 -86
- argus/backend/plugins/generic/plugin.py +0 -15
- argus/backend/plugins/generic/types.py +0 -14
- argus/backend/plugins/loader.py +0 -39
- argus/backend/plugins/sct/controller.py +0 -224
- argus/backend/plugins/sct/plugin.py +0 -37
- argus/backend/plugins/sct/resource_setup.py +0 -177
- argus/backend/plugins/sct/service.py +0 -682
- argus/backend/plugins/sct/testrun.py +0 -288
- argus/backend/plugins/sct/udt.py +0 -100
- argus/backend/plugins/sirenada/model.py +0 -118
- argus/backend/plugins/sirenada/plugin.py +0 -16
- argus/backend/service/admin.py +0 -26
- argus/backend/service/argus_service.py +0 -696
- argus/backend/service/build_system_monitor.py +0 -185
- argus/backend/service/client_service.py +0 -127
- argus/backend/service/event_service.py +0 -18
- argus/backend/service/github_service.py +0 -233
- argus/backend/service/jenkins_service.py +0 -269
- argus/backend/service/notification_manager.py +0 -159
- argus/backend/service/planner_service.py +0 -608
- argus/backend/service/release_manager.py +0 -229
- argus/backend/service/results_service.py +0 -690
- argus/backend/service/stats.py +0 -610
- argus/backend/service/team_manager_service.py +0 -82
- argus/backend/service/test_lookup.py +0 -172
- argus/backend/service/testrun.py +0 -489
- argus/backend/service/user.py +0 -308
- argus/backend/service/views.py +0 -219
- argus/backend/service/views_widgets/__init__.py +0 -0
- argus/backend/service/views_widgets/graphed_stats.py +0 -180
- argus/backend/service/views_widgets/highlights.py +0 -374
- argus/backend/service/views_widgets/nemesis_stats.py +0 -34
- argus/backend/template_filters.py +0 -27
- argus/backend/tests/__init__.py +0 -0
- argus/backend/tests/client_service/__init__.py +0 -0
- argus/backend/tests/client_service/test_submit_results.py +0 -79
- argus/backend/tests/conftest.py +0 -180
- argus/backend/tests/results_service/__init__.py +0 -0
- argus/backend/tests/results_service/test_best_results.py +0 -178
- argus/backend/tests/results_service/test_cell.py +0 -65
- argus/backend/tests/results_service/test_chartjs_additional_functions.py +0 -259
- argus/backend/tests/results_service/test_create_chartjs.py +0 -220
- argus/backend/tests/results_service/test_result_metadata.py +0 -100
- argus/backend/tests/results_service/test_results_service.py +0 -203
- argus/backend/tests/results_service/test_validation_rules.py +0 -213
- argus/backend/tests/view_widgets/__init__.py +0 -0
- argus/backend/tests/view_widgets/test_highlights_api.py +0 -532
- argus/backend/util/common.py +0 -65
- argus/backend/util/config.py +0 -38
- argus/backend/util/encoders.py +0 -56
- argus/backend/util/logsetup.py +0 -80
- argus/backend/util/module_loaders.py +0 -30
- argus/backend/util/send_email.py +0 -91
- argus/client/tests/__init__.py +0 -0
- argus/client/tests/conftest.py +0 -19
- argus/client/tests/test_package.py +0 -45
- argus/client/tests/test_results.py +0 -224
- argus_alm-0.15.1.dist-info/RECORD +0 -122
- {argus_alm-0.15.1.dist-info → argus_alm-0.15.3.dist-info}/WHEEL +0 -0
- {argus_alm-0.15.1.dist-info → argus_alm-0.15.3.dist-info}/entry_points.txt +0 -0
- {argus_alm-0.15.1.dist-info → argus_alm-0.15.3.dist-info}/licenses/LICENSE +0 -0
- {argus_alm-0.15.1.dist-info → argus_alm-0.15.3.dist-info}/top_level.txt +0 -0
argus/backend/service/stats.py
DELETED
|
@@ -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]]
|