argus-alm 0.12.3__py3-none-any.whl → 0.12.4b2__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 (31) hide show
  1. argus/backend/controller/admin_api.py +26 -0
  2. argus/backend/controller/api.py +26 -1
  3. argus/backend/controller/main.py +21 -0
  4. argus/backend/controller/testrun_api.py +16 -0
  5. argus/backend/controller/view_api.py +162 -0
  6. argus/backend/models/web.py +16 -0
  7. argus/backend/plugins/core.py +25 -10
  8. argus/backend/plugins/driver_matrix_tests/controller.py +39 -0
  9. argus/backend/plugins/driver_matrix_tests/model.py +251 -3
  10. argus/backend/plugins/driver_matrix_tests/raw_types.py +27 -0
  11. argus/backend/plugins/driver_matrix_tests/service.py +18 -0
  12. argus/backend/plugins/driver_matrix_tests/udt.py +14 -13
  13. argus/backend/plugins/generic/model.py +5 -2
  14. argus/backend/plugins/sct/service.py +13 -1
  15. argus/backend/service/argus_service.py +116 -20
  16. argus/backend/service/build_system_monitor.py +37 -7
  17. argus/backend/service/jenkins_service.py +2 -1
  18. argus/backend/service/release_manager.py +14 -0
  19. argus/backend/service/stats.py +147 -11
  20. argus/backend/service/testrun.py +44 -5
  21. argus/backend/service/views.py +258 -0
  22. argus/backend/template_filters.py +7 -0
  23. argus/backend/util/common.py +14 -2
  24. argus/client/driver_matrix_tests/cli.py +110 -0
  25. argus/client/driver_matrix_tests/client.py +56 -193
  26. argus_alm-0.12.4b2.dist-info/METADATA +129 -0
  27. {argus_alm-0.12.3.dist-info → argus_alm-0.12.4b2.dist-info}/RECORD +30 -27
  28. {argus_alm-0.12.3.dist-info → argus_alm-0.12.4b2.dist-info}/entry_points.txt +1 -0
  29. argus_alm-0.12.3.dist-info/METADATA +0 -207
  30. {argus_alm-0.12.3.dist-info → argus_alm-0.12.4b2.dist-info}/LICENSE +0 -0
  31. {argus_alm-0.12.3.dist-info → argus_alm-0.12.4b2.dist-info}/WHEEL +0 -0
@@ -261,3 +261,29 @@ def get_tests_for_group():
261
261
  "status": "ok",
262
262
  "response": tests
263
263
  }
264
+
265
+
266
+ @bp.route("/release/test/state/toggle", methods=["POST"])
267
+ @check_roles(UserRoles.Admin)
268
+ @api_login_required
269
+ def quick_toggle_test_enabled():
270
+
271
+ payload = get_payload(request)
272
+ res = ReleaseManagerService().toggle_test_enabled(test_id=payload["entityId"], new_state=payload["state"])
273
+ return {
274
+ "status": "ok",
275
+ "response": res
276
+ }
277
+
278
+
279
+ @bp.route("/release/group/state/toggle", methods=["POST"])
280
+ @check_roles(UserRoles.Admin)
281
+ @api_login_required
282
+ def quick_toggle_group_enabled():
283
+
284
+ payload = get_payload(request)
285
+ res = ReleaseManagerService().toggle_group_enabled(group_id=payload["entityId"], new_state=payload["state"])
286
+ return {
287
+ "status": "ok",
288
+ "response": res
289
+ }
@@ -12,7 +12,8 @@ from argus.backend.controller.notification_api import bp as notifications_bp
12
12
  from argus.backend.controller.client_api import bp as client_bp
13
13
  from argus.backend.controller.testrun_api import bp as testrun_bp
14
14
  from argus.backend.controller.team import bp as team_bp
15
- from argus.backend.service.argus_service import ArgusService
15
+ from argus.backend.controller.view_api import bp as view_bp
16
+ from argus.backend.service.argus_service import ArgusService, ScheduleUpdateRequest
16
17
  from argus.backend.service.user import UserService, api_login_required
17
18
  from argus.backend.service.stats import ReleaseStatsCollector
18
19
  from argus.backend.models.web import ArgusRelease, ArgusGroup, ArgusTest, User, UserOauthToken
@@ -23,6 +24,7 @@ bp.register_blueprint(notifications_bp)
23
24
  bp.register_blueprint(client_bp)
24
25
  bp.register_blueprint(testrun_bp)
25
26
  bp.register_blueprint(team_bp)
27
+ bp.register_blueprint(view_bp)
26
28
  bp.register_error_handler(Exception, handle_api_exception)
27
29
  LOGGER = logging.getLogger(__name__)
28
30
 
@@ -227,6 +229,8 @@ def release_schedules_submit():
227
229
  groups=payload["groups"],
228
230
  assignees=payload["assignees"],
229
231
  tag=payload["tag"],
232
+ comments=payload.get("comments"),
233
+ group_ids=payload.get("groupIds"),
230
234
  )
231
235
 
232
236
  return jsonify({
@@ -251,6 +255,27 @@ def release_schedules_delete():
251
255
  })
252
256
 
253
257
 
258
+ @bp.route("/release/schedules/update", methods=["POST"])
259
+ @api_login_required
260
+ def release_schedule_update():
261
+ payload = get_payload(request)
262
+ req = ScheduleUpdateRequest(**payload)
263
+ service = ArgusService()
264
+ update_result = service.update_schedule(
265
+ release_id=req.release_id,
266
+ schedule_id=req.schedule_id,
267
+ old_tests=req.old_tests,
268
+ new_tests=req.new_tests,
269
+ comments=req.comments,
270
+ assignee=req.assignee
271
+ )
272
+
273
+ return jsonify({
274
+ "status": "ok",
275
+ "response": update_result
276
+ })
277
+
278
+
254
279
  @bp.route("/groups", methods=["GET"])
255
280
  @api_login_required
256
281
  def argus_groups():
@@ -1,3 +1,5 @@
1
+ import datetime
2
+ import json
1
3
  import logging
2
4
  from uuid import UUID
3
5
  from flask import (
@@ -8,6 +10,7 @@ from argus.backend.controller.team_ui import bp as teams_bp
8
10
  from argus.backend.service.argus_service import ArgusService
9
11
  from argus.backend.models.web import WebFileStorage
10
12
  from argus.backend.service.user import UserService, login_required
13
+ from argus.backend.service.views import UserViewService
11
14
 
12
15
  LOGGER = logging.getLogger(__name__)
13
16
 
@@ -55,6 +58,24 @@ def releases():
55
58
  return render_template("releases.html.j2", releases=all_releases)
56
59
 
57
60
 
61
+ @bp.route("/views")
62
+ @login_required
63
+ def views():
64
+ service = UserViewService()
65
+ all_views = service.get_all_views()
66
+ return render_template("views.html.j2", views=sorted(all_views, key=lambda view: view.created or datetime.datetime.fromtimestamp(0), reverse=True))
67
+
68
+
69
+ @bp.route("/view/<string:view_name>")
70
+ @login_required
71
+ def view_dashboard(view_name: str):
72
+ service = UserViewService()
73
+ view = service.get_view_by_name(view_name=view_name)
74
+ data_json = view
75
+ view["widget_settings"] = json.loads(view["widget_settings"])
76
+ return render_template("view_dashboard.html.j2", data=data_json)
77
+
78
+
58
79
  @bp.route("/alert_debug")
59
80
  @login_required
60
81
  def alert_debug():
@@ -289,6 +289,22 @@ def ignore_jobs():
289
289
  }
290
290
 
291
291
 
292
+ @bp.route("/get_runs_by_test_id_run_id", methods=["POST"])
293
+ @api_login_required
294
+ def get_runs_by_test_id_run_id():
295
+ payload: list[tuple[UUID, UUID]] = get_payload(request)
296
+ service = TestRunService()
297
+
298
+ result = service.resolve_run_build_id_and_number_multiple(payload)
299
+
300
+ return {
301
+ "status": "ok",
302
+ "response": {
303
+ "runs": result
304
+ }
305
+ }
306
+
307
+
292
308
  @bp.route("/jenkins/params", methods=["POST"])
293
309
  @api_login_required
294
310
  def get_jenkins_job_params():
@@ -0,0 +1,162 @@
1
+ import logging
2
+ from uuid import UUID
3
+ from flask import (
4
+ Blueprint,
5
+ jsonify,
6
+ request,
7
+ )
8
+ from argus.backend.error_handlers import handle_api_exception
9
+ from argus.backend.models.web import User
10
+ from argus.backend.service.stats import ViewStatsCollector
11
+ from argus.backend.service.user import api_login_required
12
+ from argus.backend.service.views import UserViewService
13
+ from argus.backend.util.common import get_payload
14
+
15
+ bp = Blueprint('view_api', __name__, url_prefix='/views')
16
+ LOGGER = logging.getLogger(__name__)
17
+ bp.register_error_handler(Exception, handle_api_exception)
18
+
19
+
20
+ class ViewApiException(Exception):
21
+ pass
22
+
23
+
24
+ @bp.route("/", methods=["GET"])
25
+ @api_login_required
26
+ def index():
27
+ return {
28
+ "status": "ok",
29
+ "response": {
30
+ "version": "v1",
31
+ }
32
+ }
33
+
34
+
35
+ @bp.route("/create", methods=["POST"])
36
+ @api_login_required
37
+ def create_view():
38
+ payload = get_payload(request)
39
+ service = UserViewService()
40
+ view = service.create_view(
41
+ name=payload["name"],
42
+ items=payload["items"],
43
+ widget_settings=payload["settings"],
44
+ description=payload.get("description"),
45
+ display_name=payload.get("displayName")
46
+ )
47
+ return {
48
+ "status": "ok",
49
+ "response": view
50
+ }
51
+
52
+
53
+ @bp.route("/get", methods=["GET"])
54
+ @api_login_required
55
+ def get_view():
56
+ view_id = request.args.get("viewId")
57
+ if not view_id:
58
+ raise ViewApiException("No viewId provided.")
59
+ service = UserViewService()
60
+ view = service.get_view(UUID(view_id))
61
+ return {
62
+ "status": "ok",
63
+ "response": view
64
+ }
65
+
66
+
67
+ @bp.route("/all", methods=["GET"])
68
+ @api_login_required
69
+ def get_all_views():
70
+ user_id = request.args.get("userId")
71
+ if user_id:
72
+ user = User.get(id=user_id)
73
+ else:
74
+ user = None
75
+ service = UserViewService()
76
+ views = service.get_all_views(user)
77
+ return {
78
+ "status": "ok",
79
+ "response": views
80
+ }
81
+
82
+
83
+ @bp.route("/update", methods=["POST"])
84
+ @api_login_required
85
+ def update_view():
86
+ payload = get_payload(request)
87
+ service = UserViewService()
88
+ res = service.update_view(view_id=payload["viewId"], update_data=payload["updateData"])
89
+ return {
90
+ "status": "ok",
91
+ "response": res
92
+ }
93
+
94
+
95
+ @bp.route("/delete", methods=["POST"])
96
+ @api_login_required
97
+ def delete_view():
98
+ payload = get_payload(request)
99
+ service = UserViewService()
100
+ res = service.delete_view(payload["viewId"])
101
+ return {
102
+ "status": "ok",
103
+ "response": res
104
+ }
105
+
106
+
107
+ @bp.route("/search", methods=["GET"])
108
+ @api_login_required
109
+ def search_tests():
110
+ query = request.args.get("query")
111
+ service = UserViewService()
112
+ if query:
113
+ res = service.test_lookup(query)
114
+ else:
115
+ res = []
116
+ return {
117
+ "status": "ok",
118
+ "response": {
119
+ "hits": res,
120
+ "total": len(res)
121
+ }
122
+ }
123
+
124
+ @bp.route("/stats", methods=["GET"])
125
+ @api_login_required
126
+ def view_stats():
127
+ view_id = request.args.get("viewId")
128
+ if not view_id:
129
+ raise ViewApiException("No view id provided.")
130
+ limited = bool(int(request.args.get("limited", 0)))
131
+ version = request.args.get("productVersion", None)
132
+ include_no_version = bool(int(request.args.get("includeNoVersion", True)))
133
+ force = bool(int(request.args.get("force", 0)))
134
+ collector = ViewStatsCollector(view_id=view_id, filter=version)
135
+ stats = collector.collect(limited=limited, force=force, include_no_version=include_no_version)
136
+
137
+ res = jsonify({
138
+ "status": "ok",
139
+ "response": stats
140
+ })
141
+ res.cache_control.max_age = 300
142
+ return res
143
+
144
+ @bp.route("/<string:view_id>/versions", methods=["GET"])
145
+ @api_login_required
146
+ def view_versions(view_id: str):
147
+ service = UserViewService()
148
+ res = service.get_versions_for_view(view_id)
149
+ return {
150
+ "status": "ok",
151
+ "response": res
152
+ }
153
+
154
+ @bp.route("/<string:view_id>/resolve", methods=["GET"])
155
+ @api_login_required
156
+ def view_resolve(view_id: str):
157
+ service = UserViewService()
158
+ res = service.resolve_view_for_edit(view_id)
159
+ return {
160
+ "status": "ok",
161
+ "response": res
162
+ }
@@ -146,6 +146,20 @@ class ArgusGroup(Model):
146
146
  return super().__eq__(other)
147
147
 
148
148
 
149
+ class ArgusUserView(Model):
150
+ id = columns.UUID(primary_key=True, partition_key=True, default=uuid4)
151
+ name = columns.Text(required=True, index=True)
152
+ display_name = columns.Text()
153
+ description = columns.Text()
154
+ user_id = columns.UUID(required=True, index=True)
155
+ tests = columns.List(value_type=columns.UUID, default=lambda: [])
156
+ release_ids = columns.List(value_type=columns.UUID, default=lambda: [])
157
+ group_ids = columns.List(value_type=columns.UUID, default=lambda: [])
158
+ created = columns.DateTime(default=datetime.utcnow)
159
+ last_updated = columns.DateTime(default=datetime.utcnow)
160
+ widget_settings = columns.Text(required=True)
161
+
162
+
149
163
  class ArgusTest(Model):
150
164
  __table_name__ = "argus_test_v2"
151
165
  id = columns.UUID(primary_key=True, default=uuid4)
@@ -180,6 +194,7 @@ class ArgusTestRunComment(Model):
180
194
  test_run_id = columns.UUID(required=True, index=True)
181
195
  user_id = columns.UUID(required=True, index=True)
182
196
  release_id = columns.UUID(required=True, index=True)
197
+ test_id = columns.UUID(required=True, index=True)
183
198
  posted_at = columns.Integer(
184
199
  required=True, clustering_order="desc", primary_key=True)
185
200
  message = columns.Text(min_length=1, max_length=65535)
@@ -350,6 +365,7 @@ USED_MODELS: list[Model] = [
350
365
  Team,
351
366
  WebFileStorage,
352
367
  ArgusRelease,
368
+ ArgusUserView,
353
369
  ArgusGroup,
354
370
  ArgusTest,
355
371
  ArgusTestRunComment,
@@ -1,5 +1,6 @@
1
1
  import logging
2
2
  from datetime import datetime
3
+ from math import ceil
3
4
  from uuid import UUID
4
5
  from time import time
5
6
 
@@ -16,8 +17,10 @@ from argus.backend.models.web import (
16
17
  ArgusSchedule,
17
18
  ArgusScheduleTest,
18
19
  ArgusScheduleAssignee,
20
+ ArgusUserView,
19
21
  User
20
22
  )
23
+ from argus.backend.util.common import chunk
21
24
  from argus.backend.util.enums import TestInvestigationStatus, TestStatus
22
25
 
23
26
  LOGGER = logging.getLogger(__name__)
@@ -27,7 +30,7 @@ class PluginModelBase(Model):
27
30
  _plugin_name = "unknown"
28
31
  # Metadata
29
32
  build_id = columns.Text(required=True, partition_key=True)
30
- start_time = columns.DateTime(required=True, primary_key=True, clustering_order="DESC", default=datetime.now, custom_index=True)
33
+ start_time = columns.DateTime(required=True, primary_key=True, clustering_order="DESC", default=datetime.utcnow, custom_index=True)
31
34
  id = columns.UUID(index=True, required=True)
32
35
  release_id = columns.UUID(index=True)
33
36
  group_id = columns.UUID(index=True)
@@ -136,17 +139,14 @@ class PluginModelBase(Model):
136
139
  cluster = ScyllaCluster.get()
137
140
  query = cluster.prepare(cls._stats_query())
138
141
  futures = []
139
- step = 0
140
142
  step_size = 90
141
- total_tests = len(build_ids)
142
- while total_tests > 0:
143
- id_slice = build_ids[step:step+step_size]
144
- futures.append(cluster.session.execute_async(query=query, parameters=(id_slice,)))
145
- step += step_size
146
- total_tests = max(0, total_tests - step_size)
147
143
 
148
- return futures
144
+ for step in range(0, ceil(len(build_ids) / step_size)):
145
+ start_pos = step*step_size
146
+ next_slice = build_ids[start_pos:start_pos+step_size]
147
+ futures.append(cluster.session.execute_async(query=query, parameters=(next_slice,)))
149
148
 
149
+ return futures
150
150
  @classmethod
151
151
  def get_run_meta_by_build_id(cls, build_id: str, limit: int = 10):
152
152
  cluster = ScyllaCluster.get()
@@ -177,6 +177,21 @@ class PluginModelBase(Model):
177
177
  def get_distinct_product_versions(cls, release: ArgusRelease) -> list[str]:
178
178
  raise NotImplementedError()
179
179
 
180
+ @classmethod
181
+ def get_distinct_versions_for_view(cls, tests: list[ArgusTest]) -> list[str]:
182
+ cluster = ScyllaCluster.get()
183
+ statement = cluster.prepare(f"SELECT scylla_version FROM {cls.table_name()} WHERE build_id IN ?")
184
+ futures = []
185
+ for batch in chunk(tests):
186
+ futures.append(cluster.session.execute_async(query=statement, parameters=([t.build_system_id for t in batch],)))
187
+
188
+ rows = []
189
+ for future in futures:
190
+ rows.extend(future.result())
191
+ unique_versions = {r["scylla_version"] for r in rows if r["scylla_version"]}
192
+
193
+ return sorted(list(unique_versions), reverse=True)
194
+
180
195
  def update_heartbeat(self):
181
196
  self.heartbeat = int(time())
182
197
 
@@ -189,7 +204,7 @@ class PluginModelBase(Model):
189
204
  def submit_product_version(self, version: str):
190
205
  raise NotImplementedError()
191
206
 
192
- def set_product_version(self, version: str):
207
+ def set_full_version(self, version: str):
193
208
  self.product_version = version
194
209
 
195
210
  def submit_logs(self, logs: list[dict]):
@@ -1,8 +1,10 @@
1
1
  from flask import Blueprint, request
2
2
 
3
3
  from argus.backend.error_handlers import handle_api_exception
4
+ from argus.backend.plugins.driver_matrix_tests.raw_types import DriverMatrixSubmitEnvRequest, DriverMatrixSubmitFailureRequest, DriverMatrixSubmitResultRequest
4
5
  from argus.backend.service.user import api_login_required
5
6
  from argus.backend.plugins.driver_matrix_tests.service import DriverMatrixService
7
+ from argus.backend.util.common import get_payload
6
8
 
7
9
  bp = Blueprint("driver_matrix_api", __name__, url_prefix="/driver_matrix")
8
10
  bp.register_error_handler(Exception, handle_api_exception)
@@ -22,3 +24,40 @@ def driver_matrix_test_report():
22
24
  "status": "ok",
23
25
  "response": result
24
26
  }
27
+
28
+ @bp.route("/result/submit", methods=["POST"])
29
+ @api_login_required
30
+ def submit_result():
31
+ payload = get_payload(request)
32
+ request_data = DriverMatrixSubmitResultRequest(**payload)
33
+
34
+ result = DriverMatrixService().submit_driver_result(driver_name=request_data.driver_name, driver_type=request_data.driver_type, run_id=request_data.run_id, raw_xml=request_data.raw_xml)
35
+ return {
36
+ "status": "ok",
37
+ "response": result
38
+ }
39
+
40
+
41
+ @bp.route("/result/fail", methods=["POST"])
42
+ @api_login_required
43
+ def submit_failure():
44
+ payload = get_payload(request)
45
+ request_data = DriverMatrixSubmitFailureRequest(**payload)
46
+
47
+ result = DriverMatrixService().submit_driver_failure(driver_name=request_data.driver_name, driver_type=request_data.driver_type, run_id=request_data.run_id, failure_reason=request_data.failure_reason)
48
+ return {
49
+ "status": "ok",
50
+ "response": result
51
+ }
52
+
53
+ @bp.route("/env/submit", methods=["POST"])
54
+ @api_login_required
55
+ def submit_env():
56
+ payload = get_payload(request)
57
+ request_data = DriverMatrixSubmitEnvRequest(**payload)
58
+
59
+ result = DriverMatrixService().submit_env_info(run_id=request_data.run_id, raw_env=request_data.raw_env)
60
+ return {
61
+ "status": "ok",
62
+ "response": result
63
+ }