Add rest api method for first page view

This commit adds a new rest endpoint /runs/group_by/metadata_key to
return a json dictionary of the runs stats for all the runs in a given
time resolution grouped by the metadata_key provided in the url.

This will be used for the first page view to return a json dict that
is grouped by project by making a call like: GET /runs/group_by/project
which will return all the runs grouped by project.

Change-Id: I73a9a040a8950b72b399d1866246422b08f2e60d
This commit is contained in:
Matthew Treinish 2015-09-16 14:15:17 -04:00
parent 005f16b1da
commit b6cfad0d7c
No known key found for this signature in database
GPG Key ID: FD12A0F214C9E177
3 changed files with 187 additions and 0 deletions

View File

@ -14,6 +14,7 @@
import ConfigParser
from dateutil import parser as date_parser
import sys
import flask
@ -36,6 +37,63 @@ def get_runs_from_build_name(build_name):
return jsonify({'runs': runs})
def _filter_by_date_res(date_res, sec_runs):
runs = {}
for run in sec_runs:
# Filter resolution
if date_res == 'min':
corr_res = run.replace(second=0, microsecond=0)
elif date_res == 'hour':
corr_res = run.replace(minute=0, second=0, microsecond=0)
elif date_res == 'day':
corr_res = run.date()
# Vuild runs dict with correct resolution
if corr_res in runs:
for local_run in sec_runs[run]:
if runs[corr_res].get(local_run, None):
runs[corr_res][local_run].extend(
sec_runs[run][local_run])
else:
runs[corr_res][
local_run] = sec_runs[run][local_run]
else:
runs[corr_res] = sec_runs[run]
return runs
def _parse_datetimes(datetime_str):
if datetime_str:
return date_parser.parse(datetime_str)
else:
return datetime_str
@app.route('/runs/group_by/<string:key>', methods=['GET'])
def get_runs_grouped_by_metadata_per_datetime(key):
global Session
session = Session()
start_date = _parse_datetimes(flask.request.args.get('start_date', None))
stop_date = _parse_datetimes(flask.request.args.get('stop_date', None))
date_range = flask.request.args.get('datetime_resolution', None)
sec_runs = api.get_all_runs_time_series_by_key(key, start_date,
stop_date, session)
if not date_range:
runs = sec_runs
else:
runs = {}
if date_range not in ['sec', 'min', 'hour', 'day']:
return ('Datetime resolution: %s, is not a valid'
' choice' % date_range), 400
elif date_range != 'sec':
runs = _filter_by_date_res(date_range, sec_runs)
else:
runs = sec_runs
out_runs = {}
for run in runs:
out_runs[run.isoformat()] = runs[run]
return jsonify({'runs': out_runs})
@app.route('/runs', methods=['GET'])
def get_runs():
global Session

View File

@ -157,3 +157,131 @@ class TestRestAPI(base.TestCase):
}
}}
self.assertEqual(json.loads(res.data), expected_response)
@mock.patch('subunit2sql.db.api.get_all_runs_time_series_by_key',
return_value={
timestamp_a: [{'pass': 2, 'fail': 3, 'skip': 1}]
})
def test_get_runs_by_date(self, api_mock):
api.Session = mock.MagicMock()
res = self.app.get('/runs/group_by/project')
self.assertEqual(200, res.status_code)
expected_response = {u'runs': {
unicode(timestamp_a.isoformat()): [{
u'pass': 2, u'fail': 3, u'skip': 1
}]
}}
self.assertEqual(expected_response, json.loads(res.data))
# Ensure db api called correctly
api_mock.assert_called_once_with('project', None, None, api.Session())
@mock.patch('subunit2sql.db.api.get_all_runs_time_series_by_key',
return_value={
timestamp_a: [{'pass': 2, 'fail': 3, 'skip': 1}]
})
def test_get_runs_by_date_min_res(self, api_mock):
api.Session = mock.MagicMock()
res = self.app.get('/runs/group_by/project?datetime_resolution=min')
self.assertEqual(200, res.status_code)
expected_response = {u'runs': {
unicode(timestamp_a.replace(second=0,
microsecond=0).isoformat()): [{
u'pass': 2,
u'fail': 3,
u'skip': 1}]
}}
self.assertEqual(expected_response, json.loads(res.data))
@mock.patch('subunit2sql.db.api.get_all_runs_time_series_by_key',
return_value={
timestamp_a: [{'pass': 2, 'fail': 3, 'skip': 1}]
})
def test_get_runs_by_date_hour_res(self, api_mock):
api.Session = mock.MagicMock()
res = self.app.get('/runs/group_by/projects?datetime_resolution=hour')
self.assertEqual(200, res.status_code)
expected_response = {u'runs': {
unicode(timestamp_a.replace(minute=0, second=0,
microsecond=0).isoformat()): [{
u'pass': 2,
u'fail': 3,
u'skip': 1}]
}}
self.assertEqual(expected_response, json.loads(res.data))
@mock.patch('subunit2sql.db.api.get_all_runs_time_series_by_key',
return_value={
timestamp_a: {'tempest': [{'pass': 2,
'fail': 3,
'skip': 1}],
'neutron': [{'pass': 2,
'fail': 0,
'skip': 1}]},
timestamp_b: {'neutron': [{'pass': 0,
'fail': 1,
'skip': 0}]}
})
def test_get_runs_by_date_day_res(self, api_mock):
api.Session = mock.MagicMock()
res = self.app.get('runs/group_by/projects?datetime_resolution=day')
self.assertEqual(200, res.status_code)
date = unicode(timestamp_a.date().isoformat())
expected_response = {u'runs': {
date: {
u'neutron': sorted([{u'pass': 2,
u'fail': 0,
u'skip': 1},
{u'fail': 1,
u'pass': 0,
u'skip': 0}]),
u'tempest': [{u'fail': 3,
u'pass': 2,
u'skip': 1}]}
}}
response = json.loads(res.data)
self.assertEqual(sorted(expected_response['runs'].keys()),
sorted(response['runs'].keys()))
for project in expected_response['runs'][date]:
self.assertIn(project, response['runs'][date])
self.assertEqual(sorted(expected_response['runs'][date][project]),
sorted(response['runs'][date][project]))
def test_parse_datetimes_isoformat(self):
parsed_out = api._parse_datetimes(timestamp_a.isoformat())
self.assertEqual(timestamp_a, parsed_out)
def test_parse_datetimes_almost_isoformat(self):
format_str = timestamp_a.strftime('%Y-%m-%d %H:%M:%S')
parsed_out = api._parse_datetimes(format_str)
self.assertEqual(timestamp_a, parsed_out)
def test_parse_datetimes_no_input(self):
parsed_out = api._parse_datetimes('')
self.assertEqual('', parsed_out)
parsed_out = api._parse_datetimes(None)
self.assertIsNone(parsed_out)
@mock.patch('subunit2sql.db.api.get_all_runs_time_series_by_key',
return_value={
timestamp_a: [{'pass': 2, 'fail': 3, 'skip': 1}]
})
def test_get_runs_by_date_explicit_sec_res(self, api_mock):
api.Session = mock.MagicMock()
res = self.app.get('/runs/group_by/project?datetime_resolution=sec')
self.assertEqual(200, res.status_code)
expected_response = {u'runs': {
unicode(timestamp_a.isoformat()): [{
u'pass': 2, u'fail': 3, u'skip': 1
}]
}}
self.assertEqual(expected_response, json.loads(res.data))
# Ensure db api called correctly
api_mock.assert_called_once_with('project', None, None, api.Session())
def test_get_runs_by_date_invalid_resolution(self):
api.Session = mock.MagicMock()
res = self.app.get(
'/runs/group_by/projects?datetime_resolution=century')
self.assertEqual(res.status_code, 400)
self.assertEqual('Datetime resolution: century, is not a valid choice',
res.data)

View File

@ -7,3 +7,4 @@ subunit2sql>=0.8.0
sqlalchemy
flask-jsonpify
PyMySQL>=0.6.2
python-dateutil>=2.4.2