diff --git a/releasenotes/notes/new_db_api_function_get_run_times_times_series-36bca8c069633859.yaml b/releasenotes/notes/new_db_api_function_get_run_times_times_series-36bca8c069633859.yaml new file mode 100644 index 0000000..93f0215 --- /dev/null +++ b/releasenotes/notes/new_db_api_function_get_run_times_times_series-36bca8c069633859.yaml @@ -0,0 +1,7 @@ +--- +features: + - A new DB API function, + get_run_times_time_series_grouped_by_run_metadata_key(), similar to the + existing get_run_times_grouped_by_run_metadata_key() function except that + the output is a time series dict. + diff --git a/subunit2sql/db/api.py b/subunit2sql/db/api.py index be1958c..626a3f1 100644 --- a/subunit2sql/db/api.py +++ b/subunit2sql/db/api.py @@ -1258,6 +1258,65 @@ def get_run_times_grouped_by_run_metadata_key(key, start_date=None, return result +def get_run_times_time_series_grouped_by_run_metadata_key(key, start_date=None, + stop_date=None, + session=None, + match_key=None, + match_value=None): + """Get a times series dict of aggregate run times grouped by a metadata key + + The results of the output can be limited to runs with a different matching + key value run_metadata pair using the match_key and match_value parameters. + + :param key: The run_metadata key to use for grouping runs + :param session: Optional session object if one isn't provided a new session + will be acquired for the duration of this operation + :param str match_key: An optional key as part of a key value run_metadata + pair to filter the runs used to. This can not be the + same as the key parameter. If match_value is not also + specified this does nothing + :param str match_value: An optional value as part of a key value + run_metadata pair to filter the runs used to. If + match_key is not also specified this does nothing. + + :return: A dictionary where keys are the time stamp and the value is a dict + with the key being the value of the provided metadata key + and the values are run times for the successful runs + :rtype: dict + """ + if key == match_key: + raise ValueError('match_key cannot have the same value as key') + session = session or get_session() + run_times_query = db_utils.model_query(models.Run, session).filter( + models.Run.fails == 0, models.Run.passes > 0).join( + models.RunMetadata, + models.Run.id == models.RunMetadata.run_id).filter( + models.RunMetadata.key == key) + if match_key and match_value: + subquery = session.query(models.RunMetadata.run_id).filter( + models.RunMetadata.key == match_key, + models.RunMetadata.value == match_value).subquery() + run_times_query = run_times_query.filter(models.Run.id.in_(subquery)) + + run_times_query = _filter_runs_by_date(run_times_query, start_date, + stop_date) + run_times = run_times_query.values(models.Run.run_at, models.Run.run_time, + models.RunMetadata.value) + result = {} + for run in run_times: + if run.run_at in result: + if run.value not in result[run.run_at]: + result[run.run_at][run.value] = run.run_time + # NOTE(mtreinish) if there is more than one run with the metadata + # value and the same start time, just average the results. + else: + old = result[run.run_at][run.value] + result[run.run_at][run.value] = float(old + run.run_time) / 2.0 + else: + result[run.run_at] = {run.value: run.run_time} + return result + + def get_test_counts_in_date_range(test_id, start_date=None, stop_date=None, session=None): """Return the number of successes, failures, and skips for a single test. diff --git a/subunit2sql/tests/db/test_api.py b/subunit2sql/tests/db/test_api.py index 097ca74..d670980 100644 --- a/subunit2sql/tests/db/test_api.py +++ b/subunit2sql/tests/db/test_api.py @@ -461,6 +461,35 @@ class TestDatabaseAPI(base.TestCase): expected_res = {'value_a': [2.2], 'value_b': [3.5]} self.assertEqual(expected_res, res) + def test_get_run_time_series_grouped_by_run_metadata_key(self): + run_at_a = datetime.datetime(1914, 11, 11, 11, 11, 11) + run_a = api.create_run(run_time=2.2, passes=2, run_at=run_at_a) + run_at_b = datetime.datetime.utcnow().replace(microsecond=0) + run_b = api.create_run(run_time=3.5, passes=3, run_at=run_at_b) + api.add_run_metadata({'key': 'value_a'}, run_a.id) + api.add_run_metadata({'key': 'value_b'}, run_b.id) + res = api.get_run_times_time_series_grouped_by_run_metadata_key('key') + expected_res = {run_at_a: {'value_a': 2.2}, + run_at_b: {'value_b': 3.5}} + self.assertEqual(expected_res, res) + + def test_get_run_ts_grouped_by_run_meta_key_with_match_filter(self): + run_at_a = datetime.datetime(1914, 11, 11, 11, 11, 11) + run_a = api.create_run(run_time=2.2, passes=2, run_at=run_at_a) + run_at_b = datetime.datetime.utcnow().replace(microsecond=0) + run_b = api.create_run(run_time=3.5, passes=3, run_at=run_at_b) + run_c = api.create_run(run_time=75.432, passes=11) + api.add_run_metadata({'key': 'value_a'}, run_a.id) + api.add_run_metadata({'match_key': 'value_c'}, run_a.id) + api.add_run_metadata({'key': 'value_b'}, run_b.id) + api.add_run_metadata({'match_key': 'value_c'}, run_b.id) + api.add_run_metadata({'key': 'value_b'}, run_c.id) + res = api.get_run_times_time_series_grouped_by_run_metadata_key( + 'key', match_key='match_key', match_value='value_c') + expected_res = {run_at_a: {'value_a': 2.2}, + run_at_b: {'value_b': 3.5}} + self.assertEqual(expected_res, res) + def test_get_test_run_dict_by_run_meta_key_value(self): timestamp_a = datetime.datetime.utcnow().replace(microsecond=0) timestamp_b = timestamp_a + datetime.timedelta(minutes=2)