argus-alm 0.12.7__tar.gz → 0.12.9__tar.gz

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 (97) hide show
  1. {argus_alm-0.12.7 → argus_alm-0.12.9}/PKG-INFO +1 -1
  2. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/backend/cli.py +3 -1
  3. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/backend/controller/api.py +7 -4
  4. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/backend/controller/client_api.py +1 -5
  5. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/backend/controller/testrun_api.py +2 -1
  6. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/backend/db.py +1 -1
  7. argus_alm-0.12.9/argus/backend/models/result.py +138 -0
  8. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/backend/models/web.py +2 -1
  9. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/backend/plugins/loader.py +1 -1
  10. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/backend/plugins/sct/testrun.py +10 -3
  11. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/backend/service/client_service.py +31 -10
  12. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/backend/service/notification_manager.py +4 -2
  13. argus_alm-0.12.9/argus/backend/service/results_service.py +317 -0
  14. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/backend/service/testrun.py +16 -22
  15. argus_alm-0.12.9/argus/backend/tests/argus_web.test.yaml +39 -0
  16. argus_alm-0.12.9/argus/backend/tests/conftest.py +44 -0
  17. argus_alm-0.12.9/argus/backend/tests/results_service/__init__.py +0 -0
  18. argus_alm-0.12.9/argus/backend/tests/results_service/test_best_results.py +70 -0
  19. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/client/generic_result.py +36 -7
  20. argus_alm-0.12.9/argus/client/generic_result_old.py +143 -0
  21. argus_alm-0.12.9/argus/db/.gitkeep +0 -0
  22. {argus_alm-0.12.7 → argus_alm-0.12.9}/pyproject.toml +2 -2
  23. argus_alm-0.12.7/argus/backend/models/result.py +0 -63
  24. argus_alm-0.12.7/argus/backend/service/results_service.py +0 -140
  25. {argus_alm-0.12.7 → argus_alm-0.12.9}/LICENSE +0 -0
  26. {argus_alm-0.12.7 → argus_alm-0.12.9}/README.md +0 -0
  27. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/__init__.py +0 -0
  28. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/backend/.gitkeep +0 -0
  29. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/backend/__init__.py +0 -0
  30. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/backend/controller/__init__.py +0 -0
  31. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/backend/controller/admin.py +0 -0
  32. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/backend/controller/admin_api.py +0 -0
  33. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/backend/controller/auth.py +0 -0
  34. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/backend/controller/main.py +0 -0
  35. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/backend/controller/notification_api.py +0 -0
  36. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/backend/controller/notifications.py +0 -0
  37. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/backend/controller/team.py +0 -0
  38. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/backend/controller/team_ui.py +0 -0
  39. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/backend/controller/view_api.py +0 -0
  40. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/backend/error_handlers.py +0 -0
  41. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/backend/events/event_processors.py +0 -0
  42. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/backend/models/__init__.py +0 -0
  43. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/backend/plugins/__init__.py +0 -0
  44. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/backend/plugins/core.py +0 -0
  45. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/backend/plugins/driver_matrix_tests/controller.py +0 -0
  46. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/backend/plugins/driver_matrix_tests/model.py +0 -0
  47. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/backend/plugins/driver_matrix_tests/plugin.py +0 -0
  48. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/backend/plugins/driver_matrix_tests/raw_types.py +0 -0
  49. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/backend/plugins/driver_matrix_tests/service.py +0 -0
  50. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/backend/plugins/driver_matrix_tests/udt.py +0 -0
  51. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/backend/plugins/generic/model.py +0 -0
  52. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/backend/plugins/generic/plugin.py +0 -0
  53. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/backend/plugins/generic/types.py +0 -0
  54. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/backend/plugins/sct/controller.py +0 -0
  55. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/backend/plugins/sct/plugin.py +0 -0
  56. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/backend/plugins/sct/resource_setup.py +0 -0
  57. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/backend/plugins/sct/service.py +0 -0
  58. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/backend/plugins/sct/types.py +0 -0
  59. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/backend/plugins/sct/udt.py +0 -0
  60. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/backend/plugins/sirenada/model.py +0 -0
  61. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/backend/plugins/sirenada/plugin.py +0 -0
  62. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/backend/plugins/sirenada/types.py +0 -0
  63. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/backend/service/admin.py +0 -0
  64. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/backend/service/argus_service.py +0 -0
  65. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/backend/service/build_system_monitor.py +0 -0
  66. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/backend/service/event_service.py +0 -0
  67. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/backend/service/jenkins_service.py +0 -0
  68. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/backend/service/release_manager.py +0 -0
  69. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/backend/service/stats.py +0 -0
  70. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/backend/service/team_manager_service.py +0 -0
  71. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/backend/service/user.py +0 -0
  72. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/backend/service/views.py +0 -0
  73. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/backend/template_filters.py +0 -0
  74. /argus_alm-0.12.7/argus/db/.gitkeep → /argus_alm-0.12.9/argus/backend/tests/__init__.py +0 -0
  75. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/backend/util/common.py +0 -0
  76. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/backend/util/config.py +0 -0
  77. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/backend/util/encoders.py +0 -0
  78. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/backend/util/enums.py +0 -0
  79. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/backend/util/logsetup.py +0 -0
  80. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/backend/util/module_loaders.py +0 -0
  81. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/backend/util/send_email.py +0 -0
  82. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/client/__init__.py +0 -0
  83. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/client/base.py +0 -0
  84. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/client/driver_matrix_tests/cli.py +0 -0
  85. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/client/driver_matrix_tests/client.py +0 -0
  86. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/client/generic/cli.py +0 -0
  87. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/client/generic/client.py +0 -0
  88. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/client/sct/client.py +0 -0
  89. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/client/sct/types.py +0 -0
  90. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/client/sirenada/client.py +0 -0
  91. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/db/argus_json.py +0 -0
  92. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/db/cloud_types.py +0 -0
  93. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/db/config.py +0 -0
  94. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/db/db_types.py +0 -0
  95. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/db/interface.py +0 -0
  96. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/db/testrun.py +0 -0
  97. {argus_alm-0.12.7 → argus_alm-0.12.9}/argus/db/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: argus-alm
3
- Version: 0.12.7
3
+ Version: 0.12.9
4
4
  Summary: Argus
5
5
  Home-page: https://github.com/scylladb/argus
6
6
  License: Apache-2.0
@@ -15,6 +15,9 @@ LOGGER = logging.getLogger(__name__)
15
15
  @click.command('sync-models')
16
16
  @with_appcontext
17
17
  def sync_models_command():
18
+ sync_models()
19
+
20
+ def sync_models():
18
21
  cluster = ScyllaCluster.get()
19
22
  cluster.sync_core_tables()
20
23
  LOGGER.info("Synchronizing plugin types...")
@@ -29,7 +32,6 @@ def sync_models_command():
29
32
  LOGGER.info("Plugins ready.")
30
33
  click.echo("All models synchronized.")
31
34
 
32
-
33
35
  @cli_bp.cli.add_command
34
36
  @click.command('scan-jenkins')
35
37
  @with_appcontext
@@ -4,7 +4,7 @@ import requests
4
4
  from flask import (
5
5
  Blueprint,
6
6
  g,
7
- request
7
+ request, Response
8
8
  )
9
9
  from flask.json import jsonify
10
10
  from argus.backend.error_handlers import handle_api_exception
@@ -382,18 +382,21 @@ def test_info():
382
382
  "response": info
383
383
  }
384
384
 
385
- @bp.route("/test-results", methods=["GET"])
385
+ @bp.route("/test-results", methods=["GET", "HEAD"])
386
386
  @api_login_required
387
387
  def test_results():
388
388
  test_id = request.args.get("testId")
389
389
  if not test_id:
390
390
  raise Exception("No testId provided")
391
391
  service = ResultsService()
392
- info = service.get_results(test_id=UUID(test_id))
392
+ if request.method == 'HEAD':
393
+ exists = service.is_results_exist(test_id=UUID(test_id))
394
+ return Response(status=200 if exists else 404)
395
+ graphs, ticks = service.get_test_graphs(test_id=UUID(test_id))
393
396
 
394
397
  return {
395
398
  "status": "ok",
396
- "response": info
399
+ "response": {"graphs": graphs, "ticks": ticks}
397
400
  }
398
401
 
399
402
  @bp.route("/test_run/comment/get", methods=["GET"]) # TODO: remove
@@ -105,8 +105,4 @@ def run_finalize(run_type: str, run_id: str):
105
105
  @api_login_required
106
106
  def submit_results(run_type: str, run_id: str):
107
107
  payload = get_payload(request)
108
- result = ClientService().submit_results(run_type=run_type, run_id=run_id, results=payload)
109
- return {
110
- "status": "ok",
111
- "response": result
112
- }
108
+ return ClientService().submit_results(run_type=run_type, run_id=run_id, results=payload)
@@ -8,6 +8,7 @@ from flask import (
8
8
  from argus.backend.error_handlers import handle_api_exception
9
9
  from argus.backend.models.web import ArgusTest
10
10
  from argus.backend.service.jenkins_service import JenkinsService
11
+ from argus.backend.service.results_service import ResultsService
11
12
  from argus.backend.service.testrun import TestRunService
12
13
  from argus.backend.service.user import api_login_required
13
14
  from argus.backend.util.common import get_payload
@@ -67,7 +68,7 @@ def test_run_activity(run_id: str):
67
68
  @bp.route("/run/<string:test_id>/<string:run_id>/fetch_results", methods=["GET"])
68
69
  @api_login_required
69
70
  def fetch_results(test_id: str, run_id: str):
70
- tables = TestRunService().fetch_results(test_id=UUID(test_id), run_id=UUID(run_id))
71
+ tables = ResultsService().get_run_results(test_id=UUID(test_id), run_id=UUID(run_id))
71
72
  return {
72
73
  "status": "ok",
73
74
  "tables": tables
@@ -54,7 +54,7 @@ class ScyllaCluster:
54
54
  return self.cluster.connect(keyspace=self.config["SCYLLA_KEYSPACE_NAME"])
55
55
 
56
56
  @classmethod
57
- def get(cls, config: Config = None) -> 'ScyllaCluster':
57
+ def get(cls, config: dict = None) -> 'ScyllaCluster':
58
58
  if cls.APP_INSTANCE:
59
59
  return cls.APP_INSTANCE
60
60
 
@@ -0,0 +1,138 @@
1
+ import math
2
+ from datetime import datetime, timezone
3
+
4
+ from cassandra.cqlengine import columns
5
+ from cassandra.cqlengine.models import Model
6
+ from cassandra.cqlengine.usertype import UserType
7
+
8
+ class ValidationRules(UserType):
9
+ valid_from = columns.DateTime()
10
+ best_pct = columns.Double() # max value limit relative to best result in percent unit
11
+ best_abs = columns.Double() # max value limit relative to best result in absolute unit
12
+ fixed_limit = columns.Double() # fixed limit
13
+
14
+ class ColumnMetadata(UserType):
15
+ name = columns.Ascii()
16
+ unit = columns.Text()
17
+ type = columns.Ascii()
18
+ higher_is_better = columns.Boolean() # used for tracking best results, if None - no tracking
19
+
20
+
21
+ class ArgusGenericResultMetadata(Model):
22
+ __table_name__ = "generic_result_metadata_v1"
23
+ test_id = columns.UUID(partition_key=True)
24
+ name = columns.Text(required=True, primary_key=True)
25
+ description = columns.Text()
26
+ columns_meta = columns.List(value_type=columns.UserDefinedType(ColumnMetadata))
27
+ validation_rules = columns.Map(key_type=columns.Ascii(), value_type=columns.List(columns.UserDefinedType(ValidationRules)))
28
+ rows_meta = columns.List(value_type=columns.Ascii())
29
+
30
+ def __init__(self, **kwargs):
31
+ kwargs["columns_meta"] = [ColumnMetadata(**col) for col in kwargs.pop('columns_meta', [])]
32
+ validation_rules = kwargs.pop('validation_rules', {})
33
+
34
+ if validation_rules:
35
+ for column, rule in validation_rules.items():
36
+ if not isinstance(rule, list):
37
+ rule['valid_from'] = datetime.now(timezone.utc)
38
+ validation_rules[column] = [rule]
39
+ kwargs["validation_rules"] = {k: [ValidationRules(**rules) for rules in v] for k, v in validation_rules.items()}
40
+ super().__init__(**kwargs)
41
+
42
+ def update_validation_rules(self, key: str, new_rule_dict: dict) -> bool:
43
+ """
44
+ Checks if the most recent ValidationRule for the given key matches the new_rule_dict.
45
+ If not, adds the new rule to the list with the current timestamp.
46
+
47
+ :param key: The key (column name) in the validation_rules map to update.
48
+ :param new_rule_dict: A dictionary containing the new validation rule values.
49
+ :return: True if a new rule was added, False if the existing rule matches.
50
+ """
51
+ rules_list = self.validation_rules.get(key, [])
52
+ most_recent_rule = None
53
+
54
+ if rules_list:
55
+ most_recent_rule = rules_list[-1]
56
+
57
+ fields_to_compare = [field for field in ValidationRules._fields if field != 'valid_from']
58
+ rules_match = True
59
+ if most_recent_rule:
60
+ for field in fields_to_compare:
61
+ db_value = getattr(most_recent_rule, field)
62
+ new_value = new_rule_dict.get(field)
63
+ if db_value is None and new_value is None:
64
+ continue
65
+ if db_value is None or new_value is None:
66
+ rules_match = False
67
+ break
68
+ if not math.isclose(db_value, new_value, rel_tol=1e-9, abs_tol=0.0):
69
+ rules_match = False
70
+ break
71
+ else:
72
+ rules_match = False
73
+
74
+ if not rules_match:
75
+ new_rule = ValidationRules(
76
+ valid_from=datetime.now(timezone.utc),
77
+ best_pct=new_rule_dict.get('best_pct'),
78
+ best_abs=new_rule_dict.get('best_abs'),
79
+ fixed_limit=new_rule_dict.get('fixed_limit')
80
+ )
81
+ rules_list.append(new_rule)
82
+ self.validation_rules = self.validation_rules or {}
83
+ self.validation_rules.update({key: rules_list})
84
+ return True
85
+
86
+ return False # Existing rule matches
87
+
88
+ def update_if_changed(self, new_data: dict) -> "ArgusGenericResultMetadata":
89
+ """
90
+ Updates table metadata if changed column/description or new rows were added.
91
+ See that rows can only be added, not removed once was sent.
92
+ Columns may be removed, but data in results table persists.
93
+ """
94
+ updated = False
95
+ for field, value in new_data.items():
96
+ if field == "columns_meta":
97
+ value = [ColumnMetadata(**col) for col in value]
98
+ if self.columns_meta != value:
99
+ self.columns_meta = value
100
+ updated = True
101
+ elif field == "rows_meta":
102
+ added_rows = []
103
+ for row in value:
104
+ if row not in self.rows_meta:
105
+ added_rows.append(row)
106
+ updated = True
107
+ self.rows_meta += added_rows
108
+ elif field == "validation_rules":
109
+ if any([self.update_validation_rules(key, rules) for key, rules in value.items()]):
110
+ updated = True
111
+ elif getattr(self, field) != value:
112
+ setattr(self, field, value)
113
+ updated = True
114
+
115
+ if updated:
116
+ self.save()
117
+ return self
118
+
119
+ class ArgusGenericResultData(Model):
120
+ __table_name__ = "generic_result_data_v1"
121
+ test_id = columns.UUID(partition_key=True)
122
+ name = columns.Text(partition_key=True)
123
+ run_id = columns.UUID(primary_key=True)
124
+ column = columns.Ascii(primary_key=True, index=True)
125
+ row = columns.Ascii(primary_key=True, index=True)
126
+ sut_timestamp = columns.DateTime() # for sorting
127
+ value = columns.Double()
128
+ value_text = columns.Text()
129
+ status = columns.Ascii()
130
+
131
+ class ArgusBestResultData(Model):
132
+ __table_name__ = "generic_result_best_v1"
133
+ test_id = columns.UUID(partition_key=True)
134
+ name = columns.Text(partition_key=True)
135
+ key = columns.Ascii(primary_key=True) # represents pair column:row
136
+ result_date = columns.DateTime(primary_key=True, clustering_order="DESC")
137
+ value = columns.Double()
138
+ run_id = columns.UUID()
@@ -6,7 +6,7 @@ from cassandra.cqlengine.usertype import UserType
6
6
  from cassandra.cqlengine import columns
7
7
  from cassandra.util import uuid_from_time, unix_time_from_uuid1 # pylint: disable=no-name-in-module
8
8
 
9
- from argus.backend.models.result import ArgusGenericResultMetadata, ArgusGenericResultData
9
+ from argus.backend.models.result import ArgusGenericResultMetadata, ArgusGenericResultData, ArgusBestResultData
10
10
 
11
11
 
12
12
  def uuid_now():
@@ -381,6 +381,7 @@ USED_MODELS: list[Model] = [
381
381
  ArgusScheduleTest,
382
382
  ArgusGenericResultMetadata,
383
383
  ArgusGenericResultData,
384
+ ArgusBestResultData,
384
385
  ]
385
386
 
386
387
  USED_TYPES: list[UserType] = [
@@ -30,7 +30,7 @@ def plugin_loader() -> dict[str, PluginInfoBase]:
30
30
 
31
31
 
32
32
  AVAILABLE_PLUGINS = plugin_loader()
33
-
33
+ print(AVAILABLE_PLUGINS)
34
34
 
35
35
  def all_plugin_models(include_all=False) -> list[PluginModelBase]:
36
36
  return [model for plugin in AVAILABLE_PLUGINS.values() for model in plugin.all_models if issubclass(model, PluginModelBase) or include_all]
@@ -1,6 +1,6 @@
1
1
  from enum import Enum
2
2
  import logging
3
- from datetime import datetime
3
+ from datetime import datetime, timezone
4
4
  from dataclasses import dataclass, field
5
5
  from typing import Optional
6
6
  from uuid import UUID
@@ -254,8 +254,15 @@ class SCTTestRun(PluginModelBase):
254
254
  def sut_timestamp(self) -> float:
255
255
  """converts scylla-server date to timestamp and adds revision in subseconds precision to diffirentiate
256
256
  scylla versions from the same day. It's not perfect, but we don't know exact version time."""
257
- scylla_package = [package for package in self.packages if package.name == "scylla-server"][0]
258
- return (datetime.strptime(scylla_package.date, '%Y%m%d').timestamp()
257
+ try:
258
+ scylla_package_upgraded = [package for package in self.packages if package.name == "scylla-server-upgraded"][0]
259
+ except IndexError:
260
+ scylla_package_upgraded = None
261
+ try:
262
+ scylla_package = [package for package in self.packages if package.name == "scylla-server"][0]
263
+ except IndexError:
264
+ raise ValueError("Scylla package not found in packages - cannot determine SUT timestamp")
265
+ return (datetime.strptime(scylla_package.date, '%Y%m%d').replace(tzinfo=timezone.utc).timestamp()
259
266
  + int(scylla_package.revision_id, 16) % 1000000 / 1000000)
260
267
 
261
268
 
@@ -1,8 +1,14 @@
1
+ import operator
2
+ from dataclasses import asdict, is_dataclass
3
+ from datetime import datetime, timezone
4
+ from functools import partial
1
5
  from uuid import UUID
6
+
2
7
  from argus.backend.db import ScyllaCluster
3
8
  from argus.backend.models.result import ArgusGenericResultMetadata, ArgusGenericResultData
4
9
  from argus.backend.plugins.core import PluginModelBase
5
10
  from argus.backend.plugins.loader import AVAILABLE_PLUGINS
11
+ from argus.backend.service.results_service import ResultsService, Cell
6
12
  from argus.backend.util.enums import TestStatus
7
13
 
8
14
 
@@ -25,8 +31,9 @@ class ClientService:
25
31
  def submit_run(self, run_type: str, request_data: dict) -> str:
26
32
  model = self.get_model(run_type)
27
33
  model.submit_run(request_data=request_data)
34
+
28
35
  return "Created"
29
-
36
+
30
37
  def get_run(self, run_type: str, run_id: str):
31
38
  model = self.get_model(run_type)
32
39
  try:
@@ -79,23 +86,37 @@ class ClientService:
79
86
 
80
87
  return "Finalized"
81
88
 
82
- def submit_results(self, run_type: str, run_id: str, results: dict) -> str:
89
+ def submit_results(self, run_type: str, run_id: str, results: dict) -> dict[str, str]:
83
90
  model = self.get_model(run_type)
84
- run = model.load_test_run(UUID(run_id))
85
- existing_table = ArgusGenericResultMetadata.objects(test_id=run.test_id, name=results["meta"]["name"]).first()
86
- if existing_table:
87
- existing_table.update_if_changed(results["meta"])
91
+ try:
92
+ run = model.load_test_run(UUID(run_id))
93
+ except model.DoesNotExist:
94
+ return {"status": "error", "response": {
95
+ "exception": "DoesNotExist",
96
+ "arguments": [run_id]
97
+ }}
98
+ table_name = results["meta"]["name"]
99
+ results_service = ResultsService()
100
+ cells = [Cell(**cell) for cell in results["results"]]
101
+ table_metadata = results_service.get_table_metadata(test_id=run.test_id, table_name=table_name)
102
+ if table_metadata:
103
+ table_metadata = table_metadata.update_if_changed(results["meta"])
88
104
  else:
89
- ArgusGenericResultMetadata(test_id=run.test_id, **results["meta"]).save()
105
+ table_metadata = ArgusGenericResultMetadata(test_id=run.test_id, **results["meta"])
106
+ table_metadata.save()
90
107
  if results.get("sut_timestamp", 0) == 0:
91
108
  results["sut_timestamp"] = run.sut_timestamp() # automatic sut_timestamp
109
+ results["sut_timestamp"] = datetime.fromtimestamp(results["sut_timestamp"])
110
+ best_results = results_service.update_best_results(test_id=run.test_id, table_name=table_name, table_metadata=table_metadata,
111
+ cells=cells, run_id=run_id)
92
112
  table_name = results["meta"]["name"]
93
113
  sut_timestamp = results["sut_timestamp"]
94
- for cell in results["results"]:
114
+ for cell in cells:
115
+ cell.update_cell_status_based_on_rules(table_metadata, best_results)
95
116
  ArgusGenericResultData(test_id=run.test_id,
96
117
  run_id=run.id,
97
118
  name=table_name,
98
119
  sut_timestamp=sut_timestamp,
99
- **cell
120
+ **asdict(cell)
100
121
  ).save()
101
- return "Submitted"
122
+ return {"status": "ok", "message": "Results submitted"}
@@ -93,7 +93,8 @@ class NotificationSenderBase:
93
93
 
94
94
  class ArgusDBNotificationSaver(NotificationSenderBase):
95
95
  CONTENT_TEMPLATES = {
96
- ArgusNotificationTypes.Mention: lambda p: render_template("notifications/mention.html.j2", **p if p else {})
96
+ ArgusNotificationTypes.Mention: lambda p: render_template("notifications/mention.html.j2", **p if p else {}),
97
+ ArgusNotificationTypes.AssigneeChange: lambda p: render_template("notifications/assigned.html.j2", **p if p else {}),
97
98
  }
98
99
 
99
100
  def send_notification(self, receiver: UUID, sender: UUID, notification_type: ArgusNotificationTypes, source_type: ArgusNotificationSourceTypes,
@@ -117,7 +118,8 @@ class ArgusDBNotificationSaver(NotificationSenderBase):
117
118
  class EmailNotificationServiceSender(NotificationSenderBase):
118
119
  CONTENT_TEMPLATES = {
119
120
  ArgusNotificationTypes.Mention: lambda p: render_template(
120
- "notifications/email_mention.html.j2", **p if p else {})
121
+ "notifications/email_mention.html.j2", **p if p else {}),
122
+ ArgusNotificationTypes.AssigneeChange: lambda p: render_template("notifications/assigned_email.html.j2", **p if p else {}),
121
123
  }
122
124
 
123
125
  def __init__(self):