argus-alm 0.12.2__py3-none-any.whl → 0.12.4b1__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 (40) hide show
  1. argus/backend/cli.py +1 -1
  2. argus/backend/controller/admin_api.py +26 -0
  3. argus/backend/controller/api.py +26 -1
  4. argus/backend/controller/main.py +21 -0
  5. argus/backend/controller/testrun_api.py +132 -1
  6. argus/backend/controller/view_api.py +162 -0
  7. argus/backend/models/web.py +16 -0
  8. argus/backend/plugins/core.py +28 -5
  9. argus/backend/plugins/driver_matrix_tests/controller.py +39 -0
  10. argus/backend/plugins/driver_matrix_tests/model.py +252 -4
  11. argus/backend/plugins/driver_matrix_tests/raw_types.py +27 -0
  12. argus/backend/plugins/driver_matrix_tests/service.py +18 -0
  13. argus/backend/plugins/driver_matrix_tests/udt.py +14 -13
  14. argus/backend/plugins/generic/model.py +6 -3
  15. argus/backend/plugins/loader.py +2 -2
  16. argus/backend/plugins/sct/controller.py +31 -0
  17. argus/backend/plugins/sct/plugin.py +2 -1
  18. argus/backend/plugins/sct/service.py +101 -3
  19. argus/backend/plugins/sct/testrun.py +8 -2
  20. argus/backend/plugins/sct/types.py +18 -0
  21. argus/backend/plugins/sct/udt.py +6 -0
  22. argus/backend/plugins/sirenada/model.py +1 -1
  23. argus/backend/service/argus_service.py +116 -11
  24. argus/backend/service/build_system_monitor.py +37 -7
  25. argus/backend/service/jenkins_service.py +176 -1
  26. argus/backend/service/release_manager.py +14 -0
  27. argus/backend/service/stats.py +179 -21
  28. argus/backend/service/testrun.py +44 -5
  29. argus/backend/service/views.py +258 -0
  30. argus/backend/template_filters.py +7 -0
  31. argus/backend/util/common.py +14 -2
  32. argus/client/driver_matrix_tests/cli.py +110 -0
  33. argus/client/driver_matrix_tests/client.py +56 -193
  34. argus/client/sct/client.py +34 -0
  35. argus_alm-0.12.4b1.dist-info/METADATA +129 -0
  36. {argus_alm-0.12.2.dist-info → argus_alm-0.12.4b1.dist-info}/RECORD +39 -36
  37. {argus_alm-0.12.2.dist-info → argus_alm-0.12.4b1.dist-info}/WHEEL +1 -1
  38. {argus_alm-0.12.2.dist-info → argus_alm-0.12.4b1.dist-info}/entry_points.txt +1 -0
  39. argus_alm-0.12.2.dist-info/METADATA +0 -206
  40. {argus_alm-0.12.2.dist-info → argus_alm-0.12.4b1.dist-info}/LICENSE +0 -0
argus/backend/cli.py CHANGED
@@ -22,7 +22,7 @@ def sync_models_command():
22
22
  LOGGER.info("Synchronizing plugin type %s...", user_type.__name__)
23
23
  sync_type(ks_name=cluster.config["SCYLLA_KEYSPACE_NAME"], type_model=user_type)
24
24
  LOGGER.info("Synchronizing plugin models...")
25
- for model in all_plugin_models():
25
+ for model in all_plugin_models(True):
26
26
  LOGGER.info("Synchronizing plugin model %s...", model.__name__)
27
27
  sync_table(model=model, keyspaces=[cluster.config["SCYLLA_KEYSPACE_NAME"]])
28
28
 
@@ -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():
@@ -6,6 +6,7 @@ from flask import (
6
6
  )
7
7
 
8
8
  from argus.backend.error_handlers import handle_api_exception
9
+ from argus.backend.models.web import ArgusTest
9
10
  from argus.backend.service.jenkins_service import JenkinsService
10
11
  from argus.backend.service.testrun import TestRunService
11
12
  from argus.backend.service.user import api_login_required
@@ -288,6 +289,22 @@ def ignore_jobs():
288
289
  }
289
290
 
290
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
+
291
308
  @bp.route("/jenkins/params", methods=["POST"])
292
309
  @api_login_required
293
310
  def get_jenkins_job_params():
@@ -336,4 +353,118 @@ def get_queue_info():
336
353
  "response": {
337
354
  "queueItem": result
338
355
  }
339
- }
356
+ }
357
+
358
+
359
+ @bp.route("/jenkins/clone/targets")
360
+ @api_login_required
361
+ def get_clone_targets():
362
+ test_id = request.args.get("testId")
363
+ if not test_id:
364
+ raise Exception("No testId provided")
365
+ service = JenkinsService()
366
+ result = service.get_releases_for_clone(test_id)
367
+
368
+ return {
369
+ "status": "ok",
370
+ "response": {
371
+ "targets": result
372
+ }
373
+ }
374
+
375
+
376
+ @bp.route("/jenkins/clone/groups")
377
+ @api_login_required
378
+ def get_groups_for_target():
379
+ target_id = request.args.get("targetId")
380
+ if not target_id:
381
+ raise Exception("No targetId provided")
382
+ service = JenkinsService()
383
+ result = service.get_groups_for_release(target_id)
384
+
385
+ return {
386
+ "status": "ok",
387
+ "response": {
388
+ "groups": result
389
+ }
390
+ }
391
+
392
+
393
+ @bp.route("/jenkins/clone/create", methods=["POST"])
394
+ @api_login_required
395
+ def clone_jenkins_job():
396
+
397
+ payload = get_payload(request)
398
+ service = JenkinsService()
399
+
400
+ result = service.clone_job(
401
+ current_test_id=payload["currentTestId"],
402
+ new_name=payload["newName"],
403
+ target=payload["target"],
404
+ group=payload["group"],
405
+ advanced_settings=payload["advancedSettings"],
406
+ )
407
+
408
+ return {
409
+ "status": "ok",
410
+ "response": result
411
+ }
412
+
413
+
414
+ @bp.route("/jenkins/clone/build", methods=["POST"])
415
+ @api_login_required
416
+ def clone_build_jenkins_job():
417
+
418
+ payload = get_payload(request)
419
+ service = JenkinsService()
420
+
421
+ result = service.clone_build_job(build_id=payload["buildId"], params=payload["parameters"])
422
+
423
+ return {
424
+ "status": "ok",
425
+ "response": result
426
+ }
427
+
428
+
429
+ @bp.route("/jenkins/clone/settings")
430
+ @api_login_required
431
+ def get_clone_job_advanced_settings():
432
+ build_id = request.args.get("buildId")
433
+ if not build_id:
434
+ raise Exception("No testId provided")
435
+ service = JenkinsService()
436
+ result = service.get_advanced_settings(build_id)
437
+
438
+ return {
439
+ "status": "ok",
440
+ "response": result
441
+ }
442
+
443
+
444
+ @bp.route("/jenkins/clone/settings/change", methods=["POST"])
445
+ @api_login_required
446
+ def set_job_settings():
447
+ payload = get_payload(request)
448
+ service = JenkinsService()
449
+ test = ArgusTest.get(build_system_id=payload["buildId"])
450
+ result = service.adjust_job_settings(build_id=test.build_system_id, plugin_name=test.plugin_name, settings=payload["settings"])
451
+
452
+ return {
453
+ "status": "ok",
454
+ "response": result
455
+ }
456
+
457
+
458
+ @bp.route("/jenkins/clone/settings/validate", methods=["POST"])
459
+ @api_login_required
460
+ def clone_validate_new_settings():
461
+
462
+ payload = get_payload(request)
463
+ service = JenkinsService()
464
+
465
+ result = service.verify_job_settings(build_id=payload["buildId"], new_settings=payload["newSettings"])
466
+
467
+ return {
468
+ "status": "ok",
469
+ "response": result
470
+ }
@@ -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)
@@ -132,13 +135,18 @@ class PluginModelBase(Model):
132
135
  return bound_query
133
136
 
134
137
  @classmethod
135
- def get_stats_for_release(cls, release: ArgusRelease):
138
+ def get_stats_for_release(cls, release: ArgusRelease, build_ids=list[str]):
136
139
  cluster = ScyllaCluster.get()
137
140
  query = cluster.prepare(cls._stats_query())
138
- rows = cluster.session.execute(query=query, parameters=(release.id,))
141
+ futures = []
142
+ step_size = 90
139
143
 
140
- return list(rows)
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,)))
141
148
 
149
+ return futures
142
150
  @classmethod
143
151
  def get_run_meta_by_build_id(cls, build_id: str, limit: int = 10):
144
152
  cluster = ScyllaCluster.get()
@@ -169,6 +177,21 @@ class PluginModelBase(Model):
169
177
  def get_distinct_product_versions(cls, release: ArgusRelease) -> list[str]:
170
178
  raise NotImplementedError()
171
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
+
172
195
  def update_heartbeat(self):
173
196
  self.heartbeat = int(time())
174
197
 
@@ -181,7 +204,7 @@ class PluginModelBase(Model):
181
204
  def submit_product_version(self, version: str):
182
205
  raise NotImplementedError()
183
206
 
184
- def set_product_version(self, version: str):
207
+ def set_full_version(self, version: str):
185
208
  self.product_version = version
186
209
 
187
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
+ }