blob: f190021aed6771ff80cb9a306f8597b0065b53d0 [file] [log] [blame]
# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Django chart model implementation.
Produce the data behind a google visualisation data table that can
be rendered into a chart.
This file is broken in 3 sections:
1. Data queries wrapped in stateless function wrappers.
2. Common helper functions to massage query results in to data tables.
3. Data retrieval entry points that are called from views.
Data entry points at this time include:
-GetRangedKeyByBuildLinechartData(): produce a value by builds data table.
-GetMultiTestKeyReleaseTableData(): produce a values by builds data table.
-GetReleaseReportData(): produce perf stats comparison data table.
-GetRangedTestReportData(): produce a tests by #executed data table.
-GetRangedLabTestReportData(): produce labtest execution data table.
"""
import json
import logging
import os
import re
from autotest_lib.frontend.afe import readonly_connection
import autotest_lib.frontend.croschart.chartutils as chartutils
from autotest_lib.frontend.croschart.charterrors import ChartDBError
from autotest_lib.frontend.croschart.charterrors import ChartInputError
import gviz_api
FIELD_SEPARATOR = ','
BUILD_PART_SEPARATOR = ' '
BUILD_PATTERN = re.compile(
'([\w\-]+-r[c0-9]+)-([\d]+\.[\d]+\.[\d]+\.[\d]+)-([ar][\w]*)-(b[\d]+)')
COMMON_REGEXP = "'(%s).*'"
NO_DIFF = 'n/a'
###############################################################################
# Queries: These are designed as stateless functions with static relationships.
# e.g. GetBuildRangedChartQuery() depends on
# GetBasePerfQuery() for efficiency.
COMMON_PERF_QUERY_TEMPLATE = """
SELECT %(select_keys)s
FROM tko_perf_view_2
WHERE job_name REGEXP %(job_name)s
AND platform = '%(platform)s'
AND job_owner = 'chromeos-test'
AND NOT ISNULL(iteration_value)
AND iteration_value >= 0.0
AND NOT ISNULL(test_started_time)
AND NOT ISNULL(test_finished_time)
AND NOT ISNULL(job_finished_time)"""
CHART_SELECT_KEYS = 'job_name, job_tag, iteration_key, iteration_value'
RELEASE_SELECT_KEYS = """
job_name, job_tag, test_name, iteration_key, iteration_value"""
CHART_QUERY_KEYS = """
AND test_name = '%(test_name)s'
AND iteration_key in ('%(test_keys)s')"""
RELEASEREPORT_QUERY_KEYS = """
AND test_name in ('%(test_names)s')
AND iteration_key in ('%(test_keys)s')"""
# Use subqueries to find bracketing dates mapping version to job_names.
RANGE_QUERY_TEMPLATE = """
AND test_started_time >= (%(min_query)s)
AND test_started_time <= (%(max_query)s)"""
# Can only get date order from the db.
DEFAULT_ORDER = 'ORDER BY test_started_time'
# Release data sorted here.
RELEASE_ORDER = ''
TEST_QUERY_TEMPLATE = """
SELECT * FROM
(SELECT name as test_name, test_class, test_type, path, author, test_category,
'' as platform, 0 as run_count, 0.0 as avg_test_time
FROM afe_autotests
UNION
SELECT test_name, '' as test_class, '' as test_type, '' as path, '' as author,
'' as test_category, platform, COUNT(*) as run_count,
ROUND(AVG(TIME_TO_SEC(TIMEDIFF(test_finished_time, test_started_time))))
as avg_test_time
FROM tko_test_view_2
WHERE NOT test_name REGEXP '(CLIENT|SERVER)_JOB.*'
AND NOT test_name REGEXP 'boot\.[0-9]'
AND NOT ISNULL(test_started_time)
AND NOT ISNULL(test_finished_time)
%s
GROUP BY test_name, subdir, platform) AS q"""
LABTEST_QUERY_TEMPLATE = """
SELECT job_name, job_owner,
STR_TO_DATE(CONCAT(YEARWEEK(test_started_time), ' Sunday'), '%%X%%V %%W'),
COUNT(*) AS test_count
FROM tko_test_view_2
WHERE job_owner != 'chromeos-test'
AND NOT test_name REGEXP '(CLIENT|SERVER)_JOB.*'
AND NOT test_name REGEXP 'boot\.[0-9]'
AND NOT ISNULL(test_started_time)
AND NOT ISNULL(test_finished_time)
AND job_owner = LEFT(job_name, LENGTH(job_owner))
%s
GROUP BY job_name, job_owner, YEARWEEK(test_started_time)"""
def GetBasePerfQueryParts(request):
"""Fully populates and returns a base query string."""
query = COMMON_PERF_QUERY_TEMPLATE + CHART_QUERY_KEYS
boards = '|'.join(request.GET.getlist('board'))
platform = 'netbook_%s' % request.GET.get('system').upper()
test_name, test_keys = chartutils.GetTestNameKeys(request.GET.get('testkey'))
query_parameters = {}
query_parameters['select_keys'] = CHART_SELECT_KEYS
query_parameters['job_name'] = (COMMON_REGEXP % boards)
query_parameters['platform'] = platform
query_parameters['test_name'] = test_name
query_parameters['test_keys'] = "','".join(test_keys)
return query, query_parameters
def GetBasePerfQuery(request):
"""Produce the assembled query."""
query, parameters = GetBasePerfQueryParts(request)
return query % parameters
def GetBuildRangedChartQuery(request):
"""Apply a build range against the BaseQuery."""
query = RANGE_QUERY_TEMPLATE
boards = request.GET.getlist('board')
from_build = request.GET.get('from_build')
to_build = request.GET.get('to_build')
base_query, base_query_parameters = GetBasePerfQueryParts(request)
min_parameters = base_query_parameters.copy()
min_parameters['select_keys'] = (
'IFNULL(MIN(test_started_time), DATE_SUB(NOW(), INTERVAL 1 DAY))')
min_parameters['job_name'] = (COMMON_REGEXP % '|'.join(
'%s-%s' % (b, from_build.replace('.', '\.')) for b in boards))
max_parameters = base_query_parameters
max_parameters['select_keys'] = (
'IFNULL(MAX(test_started_time), NOW())')
max_parameters['job_name'] = (COMMON_REGEXP % '|'.join(
'%s-%s' % (b, to_build.replace('.', '\.')) for b in boards))
query_parameters = {}
query_parameters['min_query'] = (base_query % min_parameters)
query_parameters['max_query'] = (base_query % max_parameters)
"""Fully populates and returns a filter query string."""
return query % query_parameters
def GetDateRangedChartQuery(request):
"""Apply a date range against the BaseQuery."""
query = RANGE_QUERY_TEMPLATE
from_date = request.GET.get('from_date')
to_date = request.GET.get('to_date')
query_parameters = {}
query_parameters['min_query'] = "SELECT '%s'" % from_date
query_parameters['max_query'] = "SELECT '%s'" % to_date
"""Fully populates and returns a filter query string."""
return query % query_parameters
def GetIntervalRangedChartQuery(request):
"""Apply an interval range against the BaseQuery."""
query = RANGE_QUERY_TEMPLATE
interval = request.GET.get('interval')
interval = interval.replace(FIELD_SEPARATOR, ' ')
query_parameters = {}
query_parameters['min_query'] = (
'SELECT DATE_SUB(NOW(), INTERVAL %s)' % interval)
query_parameters['max_query'] = 'SELECT NOW()'
"""Fully populates and returns a filter query string."""
return query % query_parameters
def GetReleaseQueryParts(request):
"""Fully populates and returns a base query string."""
query = COMMON_PERF_QUERY_TEMPLATE + RELEASEREPORT_QUERY_KEYS
boards = request.GET.getlist('board')
platform = 'netbook_%s' % request.GET.get('system').upper()
test_names = set()
test_keys = set()
test_key_tuples = {}
for t in request.GET.getlist('testkey'):
test_key_tuples[t] = ''
if not test_key_tuples:
test_key_tuples = json.load(open(os.path.join(
os.path.abspath(os.path.dirname(__file__)),
'crosrelease_defaults.json')))
for t in test_key_tuples:
test_name, test_key = chartutils.GetTestNameKeys(t)
if not test_key or len(test_key) > 1:
raise ChartInputError('testkey must be a test,key pair.')
test_names.add(test_name)
test_keys.add(test_key[0])
from_build = request.GET.get('from_build')
to_build = request.GET.get('to_build')
query_parameters = {}
query_parameters['select_keys'] = RELEASE_SELECT_KEYS
query_parameters['job_name'] = "'(%s)-(%s|%s)-.*'" % (
'|'.join(boards), from_build, to_build)
query_parameters['platform'] = platform
query_parameters['test_names'] = "','".join(test_names)
query_parameters['test_keys'] = "','".join(test_keys)
# Use the query_parameters to communicate parsed data.
query_parameters['lowhigh'] = test_key_tuples
return query, query_parameters
def GetReleaseQuery(request):
"""Produce the assembled query."""
query, parameters = GetReleaseQueryParts(request)
return query % parameters
def GetBaseTestQuery(request):
"""Test query is simple with no parameters."""
query = TEST_QUERY_TEMPLATE
return query
def GetBaseLabTestQuery(request):
"""Test query is simple with no parameters."""
query = LABTEST_QUERY_TEMPLATE
return query
###############################################################################
# Helpers
def AbbreviateBuild(build, chrome_versions, with_board=False):
"""Condense full build string for x-axis representation."""
m = re.match(BUILD_PATTERN, build)
if not m or m.lastindex < 4:
logging.warning('Skipping poorly formatted build: %s.', build)
return None
chrome_version = ''
if chrome_versions and m.group(2) in chrome_versions:
chrome_version = '%s(%s)' % (BUILD_PART_SEPARATOR,
chrome_versions[m.group(2)])
if with_board:
new_build = '%s%s%s-%s%s' % (m.group(1), BUILD_PART_SEPARATOR,
m.group(2), m.group(4), chrome_version)
else:
new_build = '%s-%s%s' % (m.group(2), m.group(4), chrome_version)
return new_build
def BuildNumberCmp(build_number1, build_number2):
"""Compare build numbers and return in ascending order."""
# 6 different build patterns:
#1. xxx-yyy-r13 0.12.133.0-b1 [(chrome version)]
#2. ttt_sss-rc 0.12.133.0-b1 [(chrome version)]
#3. 0.12.133.0-b1 [(chrome version)]
def GetPureBuild(build):
"""This code coordinated with AbbreviateBuilds()."""
divided = build.split('(')[0].strip().split(BUILD_PART_SEPARATOR)
dlen = len(divided)
if dlen > 2:
raise ChartDBError('Unexpected build format: %s' % build)
# Get only the w.x.y.z part.
return divided[dlen-1].split('-')
build1, b1 = GetPureBuild(build_number1)
build2, b2 = GetPureBuild(build_number2)
if build1 != build2:
# Compare each part of the build.
major1 = build1.split('.')
major2 = build2.split('.')
major_len = min([len(major1), len(major2)])
for i in xrange(major_len):
if major1[i] != major2[i]:
return cmp(int(major1[i]), int(major2[i]))
return cmp(build1, build2)
else:
# Compare the buildbot sequence numbers only.
return cmp(int(b1[1:]), int(b2[1:]))
def GetChromeVersions(request):
"""Get Chrome-ChromeOS version map if requested."""
chrome_versions = None
chrome_version_flag = request.GET.get('chromeversion', 'true')
if chrome_version_flag and chrome_version_flag.lower() == 'true':
map_file = os.path.join(os.path.abspath(os.path.dirname(__file__)),
'chromeos-chrome-version.json')
if os.path.exists(map_file):
chrome_versions = json.load(open(map_file))
return chrome_versions
def GetKernelTeam():
"""Get Kernel team if requested."""
kernel_team = None
team_file = os.path.join(os.path.abspath(os.path.dirname(__file__)),
'kernel-team.json')
if os.path.exists(team_file):
kernel_team = json.load(open(team_file))
return kernel_team
###############################################################################
# Models
def GetKeysByBuildLinechartData(test_name, test_keys, chrome_versions, query,
query_order=DEFAULT_ORDER):
"""Prepare and run the db query and massage the results."""
def AggregateBuilds(test_keys, chrome_versions, data_list):
"""Groups and averages data by build and extracts job_tags."""
# raw_dict
# build
# test_key: values
raw_dict = {} # unsummarized data
builds_inorder = [] # summarized data
job_tags = []
# Aggregate all the data values by test_name, test_key, build.
for build, tag, test_key, test_value in data_list:
build = AbbreviateBuild(build, chrome_versions)
if not build:
continue
if not build in raw_dict:
builds_inorder.append({'build': build})
job_tags.append(tag)
key_dict = raw_dict.setdefault(build, {})
value_list = key_dict.setdefault(test_key, [])
value_list.append(test_value)
if not raw_dict:
raise ChartDBError('No data returned')
# Now calculate averages.
for data_dict in builds_inorder:
build_dict = raw_dict[data_dict['build']]
for test_key, value_list in build_dict.iteritems():
avg = round(sum(value_list, 0.0) / len(value_list), 2)
data_dict[test_key] = avg
return job_tags, builds_inorder
def ToGVizJsonTable(test_keys, new_test_keys, table_data):
"""Massage data into gviz data table in proper order."""
# Now format for gviz table.
description = {'build': ('string', 'Build')}
keys_in_order = ['build']
for i in xrange(len(test_keys)):
description[test_keys[i]] = ('number', new_test_keys[i])
keys_in_order.append(test_keys[i])
gviz_data_table = gviz_api.DataTable(description)
gviz_data_table.LoadData(table_data)
gviz_data_table = gviz_data_table.ToJSon(keys_in_order)
return gviz_data_table
cursor = readonly_connection.connection().cursor()
cursor.execute('%s %s' % (query, query_order))
job_tags, build_data = AggregateBuilds(test_keys, chrome_versions,
cursor.fetchall())
new_test_name, new_test_keys = chartutils.AbridgeCommonKeyPrefix(test_name,
test_keys)
gviz_data_table = ToGVizJsonTable(test_keys, new_test_keys, build_data)
return {'test_name': test_name, 'test_keys': new_test_keys,
'chart_title': new_test_name, 'gviz_data_table': gviz_data_table,
'job_tags': job_tags}
def GetRangedKeyByBuildLinechartData(request):
"""Assemble the proper query and order."""
ranged_queries = {'from_build': GetBuildRangedChartQuery,
'from_date': GetDateRangedChartQuery,
'interval': GetIntervalRangedChartQuery}
query_list = [GetBasePerfQuery(request)]
for range_key in ['from_build', 'from_date', 'interval', None]:
if request.GET.get(range_key, None):
break
if not range_key:
raise ChartInputError('One interval-type parameter must be supplied.')
query_list.append(ranged_queries[range_key](request))
chrome_versions = GetChromeVersions(request)
test_name, test_keys = chartutils.GetTestNameKeys(request.GET.get('testkey'))
data_dict = GetKeysByBuildLinechartData(test_name, test_keys, chrome_versions,
' '.join(query_list))
return data_dict
def GetMultiTestKeyReleaseTableData(chrome_versions, query,
query_order=RELEASE_ORDER, extra=None):
"""Prepare and run the db query and massage the results."""
def GetHighlights(test_name, test_key, lowhigh, diff):
"""Select the background color based on a setting and the diff value."""
black_fg = '#000000'
green_fg = '#009900'
red_fg = '#cc0000'
highlights = {'test': test_name, 'metric': test_key, 'diff': diff}
if not lowhigh:
# Cannot decide which indicators to show.
return highlights
# Lookup if this key is driven up or down.
image_template = '<img src="/images/%s" />'
lowhigh_indicator = {'lowisgood': image_template % 'downisgoodmetric.png',
'highisgood': image_template % 'upisgoodmetric.png'}
lookup = lowhigh.get('%s,%s' % (test_name, test_key), None)
if not lookup or not lookup in lowhigh_indicator:
# Cannot get a key indicator or diff indicator.
return highlights
highlights['metric'] = '%s%s' % (test_key, lowhigh_indicator[lookup])
if diff == NO_DIFF:
# Cannot pick a diff indicator.
return highlights
image_vector = [(red_fg, image_template % 'unhappymetric.png'),
(black_fg, ''),
(green_fg, image_template % 'happymetric.png')]
media_lookup = {'lowisgood': image_vector,
'highisgood': image_vector[::-1]}
cmp_diff = float(diff.split(' ')[0])
fg_color, diff_indicator = media_lookup[lookup][cmp(cmp_diff, 0.0)+1]
diff_template = '<span style="color:%s">%s%s</span>'
highlights['diff'] = diff_template % (fg_color, diff, diff_indicator)
return highlights
def CalculateDiff(diff_list):
"""Produce a diff string."""
if len(diff_list) < 2:
return NO_DIFF
return '%s (%s%%)' % (
diff_list[0] - diff_list[1],
round((diff_list[0] - diff_list[1]) / diff_list[0] * 100))
def AggregateBuilds(lowhigh, chrome_versions, data_list):
"""Groups and averages data by build and extracts job_tags."""
# Aggregate all the data values.
# raw_dict
# test_name
# test_key
# build
# 'tag'
# 'values'
raw_dict = {} # unsummarized data
builds = set()
for build, tag, test_name, test_key, test_value in data_list:
key_dict = raw_dict.setdefault(test_name, {})
build_dict = key_dict.setdefault(test_key, {})
build = AbbreviateBuild(build, chrome_versions, with_board=True)
if not build:
continue
job_dict = build_dict.setdefault(build, {})
job_dict.setdefault('tag', tag)
value_list = job_dict.setdefault('values', [])
value_list.append(test_value)
builds.add(build)
if not raw_dict:
raise ChartDBError('No data returned')
if len(builds) < 2:
raise ChartDBError(
'Release report expected 2 builds and found %s builds.' % len(builds))
# Now calculate averages, diff and acquire indicators.
builds = sorted(builds, cmp=BuildNumberCmp)
build_data = []
for test_name, key_dict in raw_dict.iteritems():
for test_key, build_dict in key_dict.iteritems():
data_dict = {}
diff_stats = []
for build in builds:
job_dict = build_dict.get(build, None)
# Need to make sure there is a value for every build.
if job_dict:
value_list = job_dict['values']
avg = round(sum(value_list, 0.0) / len(value_list), 2)
diff_stats.append(avg)
data_dict[build] = (
'<a href="http://cautotest/results/%s/%s/results/keyval" '
'target="_blank">%s</a>' % (job_dict['tag'], test_name, avg))
else:
data_dict[build] = 0.0
diff = CalculateDiff(diff_stats)
data_dict.update(GetHighlights(test_name, test_key, lowhigh, diff))
build_data.append(data_dict)
return builds, build_data
def ToGVizJsonTable(builds, table_data):
"""Massage data into gviz data table in proper order."""
# Now format for gviz table.
description = {'test': ('string', 'Test'),
'metric': ('string', 'Metric'),
'diff': ('string', 'Diff')}
keys_in_order = ['test', 'metric']
for build in builds:
description[build] = ('string', build)
keys_in_order.append(build)
keys_in_order.append('diff')
gviz_data_table = gviz_api.DataTable(description)
gviz_data_table.LoadData(table_data)
gviz_data_table = gviz_data_table.ToJSon(keys_in_order)
return gviz_data_table
# Now massage the returned data into a gviz data table.
cursor = readonly_connection.connection().cursor()
cursor.execute('%s %s' % (query, query_order))
builds, build_data = AggregateBuilds(extra.get('lowhigh', None),
chrome_versions,
data_list=cursor.fetchall())
gviz_data_table = ToGVizJsonTable(builds, build_data)
return {'gviz_data_table': gviz_data_table}
def GetReleaseReportData(request):
"""Prepare and run the db query and massage the results."""
query, parameters = GetReleaseQueryParts(request)
chrome_versions = GetChromeVersions(request)
data_dict = GetMultiTestKeyReleaseTableData(chrome_versions,
query=query % parameters, extra=parameters)
return data_dict
def GetTestReportData(query):
"""Prepare and run the db query and massage the results."""
def AggregateTests(data_list):
"""Groups multiple row data by test name and platform."""
raw_dict = {}
test_attributes = set()
platform_attributes = set()
for (test_name, test_class, test_type, path, author, test_category,
platform, run_count, avg_test_time) in data_list:
if test_name.find(':') > -1:
continue
test_dict = raw_dict.setdefault(test_name, {'test_name': test_name,
'run_count': 0})
if test_class:
test_dict.setdefault('test_class', test_class)
test_attributes.add('test_class')
if test_type:
test_dict.setdefault('test_type', test_type)
test_attributes.add('test_type')
if path:
test_dict.setdefault('path', path)
test_attributes.add('path')
if author:
test_dict.setdefault('author', author)
test_attributes.add('author')
if test_category:
test_dict.setdefault('test_category', test_category)
test_attributes.add('test_category')
if platform and run_count:
test_dict['run_count'] += int(run_count)
test_dict.setdefault('%s-run_count' % platform[8:], run_count)
platform_attributes.add('%s-run_count' % platform[8:])
if platform and avg_test_time:
test_dict.setdefault('%s-avg_test_time' % platform[8:], avg_test_time)
platform_attributes.add('%s-avg_test_time' % platform[8:])
if not raw_dict:
raise ChartDBError('No data returned')
return (raw_dict.values(),
sorted(list(test_attributes)) + sorted(list(platform_attributes)))
def ToGVizJsonTable(table_data, test_attributes):
"""Massage data into gviz data table in proper order."""
# Now format for gviz table.
description = {'test_name': ('string', 'Name'),
'run_count': ('number', '#Run')}
keys_in_order = ['test_name', 'run_count']
for a in test_attributes:
description[a] = ('string', a)
keys_in_order.append(a)
gviz_data_table = gviz_api.DataTable(description)
gviz_data_table.LoadData(table_data)
gviz_data_table = gviz_data_table.ToJSon(keys_in_order)
return gviz_data_table
cursor = readonly_connection.connection().cursor()
cursor.execute(query)
test_data, test_attributes = AggregateTests(cursor.fetchall())
gviz_data_table = ToGVizJsonTable(test_data, test_attributes)
return {'gviz_data_table': gviz_data_table}
def GetRangedTestReportData(request):
"""Prepare and run the db query and massage the results."""
ranged_queries = {'from_date': GetDateRangedChartQuery,
'interval': GetIntervalRangedChartQuery}
for range_key in ['from_date', 'interval', None]:
if request.GET.get(range_key, None):
break
if not range_key:
raise ChartInputError('One interval-type parameter must be supplied.')
query = GetBaseTestQuery(request) % (ranged_queries[range_key](request))
data_dict = GetTestReportData(query)
return data_dict
def GetLabTestReportData(query):
"""Prepare and run the db query and massage the results."""
def AggregateTests(data_list):
"""Groups multiple row data by test name and platform."""
raw_data = []
user_data = {}
for job_name, job_owner, week_date, test_count in data_list:
raw_data.append({'job_name': job_name,
'job_owner': job_owner,
'week_date': week_date,
'test_count': test_count})
if not job_owner in user_data:
user_data[job_owner] = {'job_owner': job_owner,
'test_count': test_count}
else:
user_data[job_owner]['test_count'] += test_count
if not raw_data:
raise ChartDBError('No data returned')
# Add zero-values for members not found.
kernel_team = GetKernelTeam()
if kernel_team:
for k in kernel_team:
if not k in user_data:
user_data[k] = {'job_owner': k, 'test_count': 0}
return raw_data, sorted(user_data.values())
def ToGVizJsonTable(table_data, user_table_data):
"""Massage data into gviz data table in proper order."""
# Now format for gviz tables: jobs and users.
description = {'job_name': ('string', 'Job'),
'job_owner': ('string', 'Owner'),
'week_date': ('string', 'Week'),
'test_count': ('number', '#Tests')}
keys_in_order = ['job_name', 'job_owner', 'week_date', 'test_count']
gviz_data_table_jobs = gviz_api.DataTable(description)
gviz_data_table_jobs.LoadData(table_data)
gviz_data_table_jobs = gviz_data_table_jobs.ToJSon(keys_in_order)
description = {'job_owner': ('string', 'Owner'),
'test_count': ('number', '#Tests')}
keys_in_order = ['job_owner', 'test_count']
gviz_data_table_users = gviz_api.DataTable(description)
gviz_data_table_users.LoadData(user_table_data)
gviz_data_table_users = gviz_data_table_users.ToJSon(keys_in_order)
return {'jobs': gviz_data_table_jobs, 'users': gviz_data_table_users}
cursor = readonly_connection.connection().cursor()
cursor.execute(query)
test_data, user_data = AggregateTests(cursor.fetchall())
gviz_data_table = ToGVizJsonTable(test_data, user_data)
return {'gviz_data_table': gviz_data_table}
def GetRangedLabTestReportData(request):
"""Prepare and run the db query and massage the results."""
ranged_queries = {'from_date': GetDateRangedChartQuery,
'interval': GetIntervalRangedChartQuery}
for range_key in ['from_date', 'interval', None]:
if request.GET.get(range_key, None):
break
if not range_key:
raise ChartInputError('One interval-type parameter must be supplied.')
query = GetBaseLabTestQuery(request) % (ranged_queries[range_key](request))
data_dict = GetLabTestReportData(query)
return data_dict