Remove all Django-related functionality.
Exporter functionality in `export.py` no longer uses Django to render pages, and instead generates its own `.json` files. Additionally, a configuration file is generated that can be consumed by the in-progress Angular frontend. Change-Id: I37b5be581dbd784fd009c1040422f1cadb009a3a
This commit is contained in:
parent
9aaee2b681
commit
8be1f587d7
1
.gitignore
vendored
1
.gitignore
vendored
@ -4,7 +4,6 @@ node_modules
|
||||
build
|
||||
app/js/templates.js
|
||||
app/data
|
||||
*.py[cod]
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
10
manage.py
10
manage.py
@ -1,10 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
import os
|
||||
import sys
|
||||
|
||||
if __name__ == "__main__":
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "stackviz.settings")
|
||||
|
||||
from django.core.management import execute_from_command_line
|
||||
|
||||
execute_from_command_line(sys.argv)
|
@ -1,5 +1,3 @@
|
||||
Django<1.8,>=1.4.2
|
||||
DjangoRestless>=0.0.10
|
||||
python-subunit>=0.0.18
|
||||
testtools>=0.9.30
|
||||
testrepository>=0.0.18
|
||||
|
@ -14,163 +14,171 @@
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import django
|
||||
import datetime
|
||||
import gzip
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
|
||||
from argparse import ArgumentParser
|
||||
from django.http import Http404
|
||||
|
||||
from django.core.urlresolvers import resolve
|
||||
from django.test import RequestFactory
|
||||
from functools import partial
|
||||
|
||||
from stackviz.parser import tempest_subunit
|
||||
from stackviz import settings
|
||||
|
||||
|
||||
EXPORT_PATHS = [
|
||||
'/index.html',
|
||||
'/tempest_aggregate.html'
|
||||
]
|
||||
|
||||
|
||||
_base = os.path.dirname(os.path.abspath(__file__))
|
||||
_tempest_count = 0
|
||||
|
||||
|
||||
def fake_render_view(path):
|
||||
factory = RequestFactory()
|
||||
request = factory.get(path)
|
||||
|
||||
match = resolve(path)
|
||||
response = match.func(request, *match.args, **match.kwargs)
|
||||
|
||||
if hasattr(response, "render"):
|
||||
response.render()
|
||||
|
||||
return response
|
||||
|
||||
|
||||
def export_single_page(path, dest_dir, use_gzip=False):
|
||||
dest_file = path
|
||||
if dest_file.startswith('/'):
|
||||
dest_file = dest_file[1:]
|
||||
|
||||
open_func = open
|
||||
if use_gzip:
|
||||
def open_compressed(output_dir, file_name, compress):
|
||||
if compress:
|
||||
file_name += ".gz"
|
||||
open_func = gzip.open
|
||||
dest_file += ".gz"
|
||||
else:
|
||||
open_func = open
|
||||
|
||||
try:
|
||||
content = fake_render_view(path).content
|
||||
|
||||
with open_func(os.path.join(dest_dir, dest_file), 'wb') as f:
|
||||
f.write(content)
|
||||
except Http404 as ex:
|
||||
print("Warning: skipping %s due to error: %s" % (path, ex.message))
|
||||
return open_func(os.path.join(output_dir, file_name), 'wb'), file_name
|
||||
|
||||
|
||||
def init_django(args):
|
||||
# remove leading / from static URL to give them correct filesystem paths
|
||||
settings.STATIC_URL = settings.STATIC_URL[1:]
|
||||
settings.USE_GZIP = args.gzip
|
||||
settings.OFFLINE = True
|
||||
def json_date_handler(object):
|
||||
if isinstance(object, (datetime.datetime, datetime.date)):
|
||||
return object.isoformat()
|
||||
|
||||
if args.repository or args.stream_file or args.stdin:
|
||||
settings.TEST_REPOSITORIES = []
|
||||
settings.TEST_STREAMS = []
|
||||
settings.TEST_STREAM_STDIN = False
|
||||
return None
|
||||
|
||||
if args.repository:
|
||||
settings.TEST_REPOSITORIES = args.repository
|
||||
|
||||
if args.stream_file:
|
||||
settings.TEST_STREAMS = args.stream_file
|
||||
def export_tempest_tree(stream, output_stream):
|
||||
converted = tempest_subunit.convert_stream(stream, strip_details=True)
|
||||
tree = tempest_subunit.reorganize(converted)
|
||||
json.dump(tree, output_stream, default=json_date_handler)
|
||||
output_stream.close()
|
||||
|
||||
if args.stdin:
|
||||
settings.TEST_STREAM_STDIN = True
|
||||
|
||||
if args.dstat:
|
||||
settings.DSTAT_CSV = args.dstat
|
||||
def export_tempest_raw(stream, output_stream):
|
||||
converted = tempest_subunit.convert_stream(stream, strip_details=True)
|
||||
json.dump(converted, output_stream, default=json_date_handler)
|
||||
output_stream.close()
|
||||
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "stackviz.settings")
|
||||
django.setup()
|
||||
|
||||
def export_tempest_details(stream, output_stream):
|
||||
converted = tempest_subunit.convert_stream(stream, strip_details=True)
|
||||
|
||||
output = {}
|
||||
for entry in converted:
|
||||
output[entry['name']] = entry['details']
|
||||
|
||||
json.dump(output, output_stream, default=json_date_handler)
|
||||
output_stream.close()
|
||||
|
||||
|
||||
def export_tempest(provider, output_dir, dstat, compress):
|
||||
global _tempest_count
|
||||
|
||||
ret = []
|
||||
|
||||
for i in range(provider.count):
|
||||
path_base = 'tempest_%s_%d' % (provider.name, i)
|
||||
if provider.count > 1:
|
||||
name = '%s (%d)' % (provider.description, i)
|
||||
else:
|
||||
name = provider.description
|
||||
|
||||
open_ = partial(open_compressed,
|
||||
output_dir=output_dir,
|
||||
compress=compress)
|
||||
|
||||
stream_raw, path_raw = open_(file_name=path_base + '_raw.json')
|
||||
export_tempest_raw(provider.get_stream(i), stream_raw)
|
||||
|
||||
stream_tree, path_tree = open_(file_name=path_base + '_tree.json')
|
||||
export_tempest_tree(provider.get_stream(i), stream_tree)
|
||||
|
||||
stream_details, path_details = open_(
|
||||
file_name=path_base + '_details.json')
|
||||
export_tempest_details(provider.get_stream(i), stream_details)
|
||||
|
||||
entry = {
|
||||
'id': _tempest_count,
|
||||
'name': name,
|
||||
'raw': path_raw,
|
||||
'tree': path_tree,
|
||||
'details': path_details
|
||||
}
|
||||
entry.update({'dstat': dstat} if dstat else {})
|
||||
|
||||
ret.append(entry)
|
||||
_tempest_count += 1
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def export_dstat(path, output_dir, compress):
|
||||
f = open(path, 'rb')
|
||||
out_stream, out_file = open_compressed(
|
||||
output_dir,
|
||||
'dstat_log.csv',
|
||||
compress)
|
||||
|
||||
shutil.copyfileobj(f, out_stream)
|
||||
|
||||
f.close()
|
||||
out_stream.close()
|
||||
|
||||
return out_file
|
||||
|
||||
|
||||
def main():
|
||||
parser = ArgumentParser(description="Generates a self-contained, static "
|
||||
"StackViz site at the given path.")
|
||||
parser = ArgumentParser(description="Generates JSON data files for a "
|
||||
"StackViz site.")
|
||||
parser.add_argument("path",
|
||||
help="The output directory. Will be created if it "
|
||||
"doesn't already exist.")
|
||||
parser.add_argument("--ignore-bower",
|
||||
help="Ignore missing Bower components.",
|
||||
action="store_true")
|
||||
parser.add_argument("-z", "--gzip",
|
||||
help="Enable gzip compression for data files.",
|
||||
action="store_true")
|
||||
parser.add_argument("-f", "--stream-file",
|
||||
action="append",
|
||||
help="Include the given direct subunit stream.")
|
||||
help="Include the given direct subunit stream; can be "
|
||||
"used multiple times.")
|
||||
parser.add_argument("-r", "--repository",
|
||||
action="append",
|
||||
help="A directory containing a `.testrepository` to "
|
||||
"include. If not provided, the `settings.py` "
|
||||
"configured values will be used.")
|
||||
"include; can be used multiple times.")
|
||||
parser.add_argument("-i", "--stdin",
|
||||
help="Read a direct subunit stream from standard "
|
||||
"input.",
|
||||
action="store_true")
|
||||
parser.add_argument("--dstat",
|
||||
help="The path to the DStat log file (CSV-formatted) "
|
||||
"to include. If not provided, the `settings.py` "
|
||||
"configured value will be used.")
|
||||
"to include.")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if not args.ignore_bower:
|
||||
if not os.listdir(os.path.join(_base, 'static', 'components')):
|
||||
print("Bower components have not been installed, please run "
|
||||
"`bower install`")
|
||||
return 1
|
||||
|
||||
if os.path.exists(args.path):
|
||||
if os.listdir(args.path):
|
||||
print("Destination exists and is not empty, cannot continue")
|
||||
return 1
|
||||
else:
|
||||
if not os.path.exists(args.path):
|
||||
os.mkdir(args.path)
|
||||
|
||||
init_django(args)
|
||||
dstat = None
|
||||
if args.dstat:
|
||||
print("Exporting DStat log")
|
||||
dstat = export_dstat(args.dstat, args.path, args.gzip)
|
||||
|
||||
print("Copying static files ...")
|
||||
shutil.copytree(os.path.join(_base, 'static'),
|
||||
os.path.join(args.path, 'static'))
|
||||
providers = tempest_subunit.get_providers(
|
||||
args.repository,
|
||||
args.stream_file,
|
||||
args.stdin)
|
||||
|
||||
for path in EXPORT_PATHS:
|
||||
print("Rendering:", path)
|
||||
export_single_page(path, args.path)
|
||||
tempest_config_entries = []
|
||||
|
||||
for provider in tempest_subunit.get_providers().values():
|
||||
for i in range(provider.count):
|
||||
param = (provider.name, i)
|
||||
for provider in providers.values():
|
||||
print("Exporting Tempest provider: %s (%d)" % (provider.description,
|
||||
provider.count))
|
||||
tempest_config_entries.extend(
|
||||
export_tempest(provider, args.path, dstat, args.gzip)
|
||||
)
|
||||
|
||||
print("Rendering views for tempest run %s #%d" % param)
|
||||
export_single_page('/tempest_timeline_%s_%d.html' % param,
|
||||
args.path)
|
||||
export_single_page('/tempest_results_%s_%d.html' % param,
|
||||
args.path)
|
||||
|
||||
print("Exporting data for tempest run %s #%d" % param)
|
||||
export_single_page('/tempest_api_tree_%s_%d.json' % param,
|
||||
args.path, args.gzip)
|
||||
export_single_page('/tempest_api_raw_%s_%d.json' % param,
|
||||
args.path, args.gzip)
|
||||
export_single_page('/tempest_api_details_%s_%d.json' % param,
|
||||
args.path, args.gzip)
|
||||
|
||||
print("Exporting DStat log: dstat_log.csv")
|
||||
export_single_page('/dstat_log.csv', args.path, args.gzip)
|
||||
with open(os.path.join(args.path, 'config.json'), 'w') as f:
|
||||
json.dump({
|
||||
'tempest': tempest_config_entries
|
||||
}, f)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
@ -1,35 +0,0 @@
|
||||
# Copyright 2015 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from stackviz.parser.tempest_subunit import get_providers
|
||||
from stackviz.settings import OFFLINE
|
||||
from stackviz.settings import USE_GZIP
|
||||
|
||||
|
||||
def inject_extra_context(request):
|
||||
ret = {
|
||||
'use_gzip': USE_GZIP,
|
||||
'offline': OFFLINE
|
||||
}
|
||||
|
||||
providers = get_providers()
|
||||
if providers:
|
||||
default = providers.values()[0]
|
||||
|
||||
ret.update({
|
||||
'tempest_providers': providers.values(),
|
||||
'tempest_default_provider': default,
|
||||
})
|
||||
|
||||
return ret
|
@ -29,16 +29,11 @@ from testtools import StreamToDict
|
||||
from testrepository.repository.file import RepositoryFactory
|
||||
from testrepository.repository.file import RepositoryNotFound
|
||||
|
||||
from stackviz import settings
|
||||
|
||||
|
||||
NAME_SCENARIO_PATTERN = re.compile(r'^(.+) \((.+)\)$')
|
||||
NAME_TAGS_PATTERN = re.compile(r'^(.+)\[(.+)\]$')
|
||||
|
||||
|
||||
_provider_cache = None
|
||||
|
||||
|
||||
class InvalidSubunitProvider(Exception):
|
||||
pass
|
||||
|
||||
@ -168,39 +163,44 @@ class StandardInputProvider(SubunitProvider):
|
||||
return self.buffer
|
||||
|
||||
|
||||
def get_providers():
|
||||
def get_providers(repository_paths=None, stream_paths=None, stdin=False):
|
||||
"""Loads all test providers from locations configured in settings.
|
||||
|
||||
:param repository_paths: a list of directory paths containing
|
||||
'.testrepository' folders to read
|
||||
:param stream_paths: a list of paths to direct subunit streams
|
||||
:param stdin: if true, read a subunit stream from standard input
|
||||
:return: a dict of loaded provider names and their associated
|
||||
:class:`SubunitProvider` instances
|
||||
:rtype: dict[str, SubunitProvider]
|
||||
"""
|
||||
global _provider_cache
|
||||
if repository_paths is None:
|
||||
repository_paths = []
|
||||
|
||||
if _provider_cache is not None:
|
||||
return _provider_cache
|
||||
if stream_paths is None:
|
||||
stream_paths = []
|
||||
|
||||
_provider_cache = {}
|
||||
ret = {}
|
||||
|
||||
for path in settings.TEST_REPOSITORIES:
|
||||
for path in repository_paths:
|
||||
try:
|
||||
p = RepositoryProvider(path)
|
||||
_provider_cache[p.name] = p
|
||||
ret[p.name] = p
|
||||
except (ValueError, RepositoryNotFound):
|
||||
continue
|
||||
|
||||
for path in settings.TEST_STREAMS:
|
||||
for path in stream_paths:
|
||||
try:
|
||||
p = FileProvider(path)
|
||||
_provider_cache[p.name] = p
|
||||
ret[p.name] = p
|
||||
except InvalidSubunitProvider:
|
||||
continue
|
||||
|
||||
if settings.TEST_STREAM_STDIN:
|
||||
if stdin:
|
||||
p = StandardInputProvider()
|
||||
_provider_cache[p.name] = p
|
||||
ret[p.name] = p
|
||||
|
||||
return _provider_cache
|
||||
return ret
|
||||
|
||||
|
||||
def _clean_name(name):
|
||||
|
@ -1,354 +0,0 @@
|
||||
/*!
|
||||
* Start Bootstrap - SB Admin 2 Bootstrap Admin Theme (http://startbootstrap.com)
|
||||
* Code licensed under the Apache License v2.0.
|
||||
* For details, see http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*/
|
||||
|
||||
body {
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
#wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#page-wrapper {
|
||||
padding: 0 15px;
|
||||
min-height: 568px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
@media(min-width:768px) {
|
||||
#page-wrapper {
|
||||
position: inherit;
|
||||
margin: 0 0 0 250px;
|
||||
padding: 0 30px;
|
||||
border-left: 1px solid #e7e7e7;
|
||||
}
|
||||
}
|
||||
|
||||
.navbar-top-links {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.navbar-top-links li {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.navbar-top-links li:last-child {
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.navbar-top-links li a {
|
||||
padding: 15px;
|
||||
min-height: 50px;
|
||||
}
|
||||
|
||||
.navbar-top-links .dropdown-menu li {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.navbar-top-links .dropdown-menu li:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.navbar-top-links .dropdown-menu li a {
|
||||
padding: 3px 20px;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.navbar-top-links .dropdown-menu li a div {
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.navbar-top-links .dropdown-messages,
|
||||
.navbar-top-links .dropdown-tasks,
|
||||
.navbar-top-links .dropdown-alerts {
|
||||
width: 310px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.navbar-top-links .dropdown-messages {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.navbar-top-links .dropdown-tasks {
|
||||
margin-left: -59px;
|
||||
}
|
||||
|
||||
.navbar-top-links .dropdown-alerts {
|
||||
margin-left: -123px;
|
||||
}
|
||||
|
||||
.navbar-top-links .dropdown-user {
|
||||
right: 0;
|
||||
left: auto;
|
||||
}
|
||||
|
||||
.sidebar .sidebar-nav.navbar-collapse {
|
||||
padding-right: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.sidebar .sidebar-search {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.sidebar ul li {
|
||||
border-bottom: 1px solid #e7e7e7;
|
||||
}
|
||||
|
||||
.sidebar ul li a.active {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
.sidebar .arrow {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.sidebar .fa.arrow:before {
|
||||
content: "\f104";
|
||||
}
|
||||
|
||||
.sidebar .active>a>.fa.arrow:before {
|
||||
content: "\f107";
|
||||
}
|
||||
|
||||
.sidebar .nav-second-level li,
|
||||
.sidebar .nav-third-level li {
|
||||
border-bottom: 0!important;
|
||||
}
|
||||
|
||||
.sidebar .nav-second-level li a {
|
||||
padding-left: 37px;
|
||||
}
|
||||
|
||||
.sidebar .nav-third-level li a {
|
||||
padding-left: 52px;
|
||||
}
|
||||
|
||||
@media(min-width:768px) {
|
||||
.sidebar {
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
width: 250px;
|
||||
margin-top: 51px;
|
||||
}
|
||||
|
||||
.navbar-top-links .dropdown-messages,
|
||||
.navbar-top-links .dropdown-tasks,
|
||||
.navbar-top-links .dropdown-alerts {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-outline {
|
||||
color: inherit;
|
||||
background-color: transparent;
|
||||
transition: all .5s;
|
||||
}
|
||||
|
||||
.btn-primary.btn-outline {
|
||||
color: #428bca;
|
||||
}
|
||||
|
||||
.btn-success.btn-outline {
|
||||
color: #5cb85c;
|
||||
}
|
||||
|
||||
.btn-info.btn-outline {
|
||||
color: #5bc0de;
|
||||
}
|
||||
|
||||
.btn-warning.btn-outline {
|
||||
color: #f0ad4e;
|
||||
}
|
||||
|
||||
.btn-danger.btn-outline {
|
||||
color: #d9534f;
|
||||
}
|
||||
|
||||
.btn-primary.btn-outline:hover,
|
||||
.btn-success.btn-outline:hover,
|
||||
.btn-info.btn-outline:hover,
|
||||
.btn-warning.btn-outline:hover,
|
||||
.btn-danger.btn-outline:hover {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.chat {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.chat li {
|
||||
margin-bottom: 10px;
|
||||
padding-bottom: 5px;
|
||||
border-bottom: 1px dotted #999;
|
||||
}
|
||||
|
||||
.chat li.left .chat-body {
|
||||
margin-left: 60px;
|
||||
}
|
||||
|
||||
.chat li.right .chat-body {
|
||||
margin-right: 60px;
|
||||
}
|
||||
|
||||
.chat li .chat-body p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.panel .slidedown .glyphicon,
|
||||
.chat .glyphicon {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.chat-panel .panel-body {
|
||||
height: 350px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.login-panel {
|
||||
margin-top: 25%;
|
||||
}
|
||||
|
||||
.flot-chart {
|
||||
display: block;
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
.flot-chart-content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.dataTables_wrapper {
|
||||
position: relative;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
table.dataTable thead .sorting,
|
||||
table.dataTable thead .sorting_asc,
|
||||
table.dataTable thead .sorting_desc,
|
||||
table.dataTable thead .sorting_asc_disabled,
|
||||
table.dataTable thead .sorting_desc_disabled {
|
||||
background: 0 0;
|
||||
}
|
||||
|
||||
table.dataTable thead .sorting_asc:after {
|
||||
content: "\f0de";
|
||||
float: right;
|
||||
font-family: fontawesome;
|
||||
}
|
||||
|
||||
table.dataTable thead .sorting_desc:after {
|
||||
content: "\f0dd";
|
||||
float: right;
|
||||
font-family: fontawesome;
|
||||
}
|
||||
|
||||
table.dataTable thead .sorting:after {
|
||||
content: "\f0dc";
|
||||
float: right;
|
||||
font-family: fontawesome;
|
||||
color: rgba(50,50,50,.5);
|
||||
}
|
||||
|
||||
.btn-circle {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
padding: 6px 0;
|
||||
border-radius: 15px;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
line-height: 1.428571429;
|
||||
}
|
||||
|
||||
.btn-circle.btn-lg {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
padding: 10px 16px;
|
||||
border-radius: 25px;
|
||||
font-size: 18px;
|
||||
line-height: 1.33;
|
||||
}
|
||||
|
||||
.btn-circle.btn-xl {
|
||||
width: 70px;
|
||||
height: 70px;
|
||||
padding: 10px 16px;
|
||||
border-radius: 35px;
|
||||
font-size: 24px;
|
||||
line-height: 1.33;
|
||||
}
|
||||
|
||||
.show-grid [class^=col-] {
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
border: 1px solid #ddd;
|
||||
background-color: #eee!important;
|
||||
}
|
||||
|
||||
.show-grid {
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
.huge {
|
||||
font-size: 40px;
|
||||
}
|
||||
|
||||
.panel-green {
|
||||
border-color: #5cb85c;
|
||||
}
|
||||
|
||||
.panel-green .panel-heading {
|
||||
border-color: #5cb85c;
|
||||
color: #fff;
|
||||
background-color: #5cb85c;
|
||||
}
|
||||
|
||||
.panel-green a {
|
||||
color: #5cb85c;
|
||||
}
|
||||
|
||||
.panel-green a:hover {
|
||||
color: #3d8b3d;
|
||||
}
|
||||
|
||||
.panel-red {
|
||||
border-color: #d9534f;
|
||||
}
|
||||
|
||||
.panel-red .panel-heading {
|
||||
border-color: #d9534f;
|
||||
color: #fff;
|
||||
background-color: #d9534f;
|
||||
}
|
||||
|
||||
.panel-red a {
|
||||
color: #d9534f;
|
||||
}
|
||||
|
||||
.panel-red a:hover {
|
||||
color: #b52b27;
|
||||
}
|
||||
|
||||
.panel-yellow {
|
||||
border-color: #f0ad4e;
|
||||
}
|
||||
|
||||
.panel-yellow .panel-heading {
|
||||
border-color: #f0ad4e;
|
||||
color: #fff;
|
||||
background-color: #f0ad4e;
|
||||
}
|
||||
|
||||
.panel-yellow a {
|
||||
color: #f0ad4e;
|
||||
}
|
||||
|
||||
.panel-yellow a:hover {
|
||||
color: #df8a13;
|
||||
}
|
@ -1,180 +0,0 @@
|
||||
.timeline {
|
||||
position: relative;
|
||||
padding: 20px 0 20px;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.timeline:before {
|
||||
content: " ";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
width: 3px;
|
||||
margin-left: -1.5px;
|
||||
background-color: #eeeeee;
|
||||
}
|
||||
|
||||
.timeline > li {
|
||||
position: relative;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.timeline > li:before,
|
||||
.timeline > li:after {
|
||||
content: " ";
|
||||
display: table;
|
||||
}
|
||||
|
||||
.timeline > li:after {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.timeline > li:before,
|
||||
.timeline > li:after {
|
||||
content: " ";
|
||||
display: table;
|
||||
}
|
||||
|
||||
.timeline > li:after {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.timeline > li > .timeline-panel {
|
||||
float: left;
|
||||
position: relative;
|
||||
width: 46%;
|
||||
padding: 20px;
|
||||
border: 1px solid #d4d4d4;
|
||||
border-radius: 2px;
|
||||
-webkit-box-shadow: 0 1px 6px rgba(0,0,0,0.175);
|
||||
box-shadow: 0 1px 6px rgba(0,0,0,0.175);
|
||||
}
|
||||
|
||||
.timeline > li > .timeline-panel:before {
|
||||
content: " ";
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
top: 26px;
|
||||
right: -15px;
|
||||
border-top: 15px solid transparent;
|
||||
border-right: 0 solid #ccc;
|
||||
border-bottom: 15px solid transparent;
|
||||
border-left: 15px solid #ccc;
|
||||
}
|
||||
|
||||
.timeline > li > .timeline-panel:after {
|
||||
content: " ";
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
top: 27px;
|
||||
right: -14px;
|
||||
border-top: 14px solid transparent;
|
||||
border-right: 0 solid #fff;
|
||||
border-bottom: 14px solid transparent;
|
||||
border-left: 14px solid #fff;
|
||||
}
|
||||
|
||||
.timeline > li > .timeline-badge {
|
||||
z-index: 100;
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
left: 50%;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
margin-left: -25px;
|
||||
border-radius: 50% 50% 50% 50%;
|
||||
text-align: center;
|
||||
font-size: 1.4em;
|
||||
line-height: 50px;
|
||||
color: #fff;
|
||||
background-color: #999999;
|
||||
}
|
||||
|
||||
.timeline > li.timeline-inverted > .timeline-panel {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.timeline > li.timeline-inverted > .timeline-panel:before {
|
||||
right: auto;
|
||||
left: -15px;
|
||||
border-right-width: 15px;
|
||||
border-left-width: 0;
|
||||
}
|
||||
|
||||
.timeline > li.timeline-inverted > .timeline-panel:after {
|
||||
right: auto;
|
||||
left: -14px;
|
||||
border-right-width: 14px;
|
||||
border-left-width: 0;
|
||||
}
|
||||
|
||||
.timeline-badge.primary {
|
||||
background-color: #2e6da4 !important;
|
||||
}
|
||||
|
||||
.timeline-badge.success {
|
||||
background-color: #3f903f !important;
|
||||
}
|
||||
|
||||
.timeline-badge.warning {
|
||||
background-color: #f0ad4e !important;
|
||||
}
|
||||
|
||||
.timeline-badge.danger {
|
||||
background-color: #d9534f !important;
|
||||
}
|
||||
|
||||
.timeline-badge.info {
|
||||
background-color: #5bc0de !important;
|
||||
}
|
||||
|
||||
.timeline-title {
|
||||
margin-top: 0;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.timeline-body > p,
|
||||
.timeline-body > ul {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.timeline-body > p + p {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
@media(max-width:767px) {
|
||||
ul.timeline:before {
|
||||
left: 40px;
|
||||
}
|
||||
|
||||
ul.timeline > li > .timeline-panel {
|
||||
width: calc(100% - 90px);
|
||||
width: -moz-calc(100% - 90px);
|
||||
width: -webkit-calc(100% - 90px);
|
||||
}
|
||||
|
||||
ul.timeline > li > .timeline-badge {
|
||||
top: 16px;
|
||||
left: 15px;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
ul.timeline > li > .timeline-panel {
|
||||
float: right;
|
||||
}
|
||||
|
||||
ul.timeline > li > .timeline-panel:before {
|
||||
right: auto;
|
||||
left: -15px;
|
||||
border-right-width: 15px;
|
||||
border-left-width: 0;
|
||||
}
|
||||
|
||||
ul.timeline > li > .timeline-panel:after {
|
||||
right: auto;
|
||||
left: -14px;
|
||||
border-right-width: 14px;
|
||||
border-left-width: 0;
|
||||
}
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
.highlight {
|
||||
font-weight: bold;
|
||||
}
|
@ -1,87 +0,0 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
var originalDetailsContent = null;
|
||||
|
||||
var detailsCache = null;
|
||||
var detailsInProgress = false;
|
||||
var detailsWaiting = [];
|
||||
var runId = null;
|
||||
|
||||
var loadDetails = function(callback) {
|
||||
if (detailsCache === null) {
|
||||
detailsWaiting.push(callback);
|
||||
|
||||
if (!detailsInProgress) {
|
||||
var url = "tempest_api_details_" + runId + ".json";
|
||||
if ("{{use_gzip}}" === "True") {
|
||||
url += ".gz";
|
||||
}
|
||||
|
||||
detailsInProgress = true;
|
||||
|
||||
d3.json(url, function(error, data) {
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
detailsCache = data;
|
||||
detailsWaiting.forEach(function(cb) {
|
||||
cb(detailsCache);
|
||||
});
|
||||
});
|
||||
}
|
||||
} else {
|
||||
callback(detailsCache);
|
||||
}
|
||||
};
|
||||
|
||||
var showDetails = function(item) {
|
||||
var parent = $("#details-dialog");
|
||||
|
||||
loadDetails(function(details) {
|
||||
if (!details.hasOwnProperty(item.name_full)) {
|
||||
console.log("Details not found for item:", item.name_full);
|
||||
return;
|
||||
}
|
||||
|
||||
if (originalDetailsContent === null) {
|
||||
originalDetailsContent = parent.html();
|
||||
}
|
||||
|
||||
parent.empty();
|
||||
for (var prop in details[item.name_full]) {
|
||||
$("<h3>").text(prop).appendTo(parent);
|
||||
$("<pre>").text(details[item.name_full][prop]).appendTo(parent);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
function addDialogButton(parentID, run_id) {
|
||||
//parentID: A string contiaining the parent div id to which the button will be appended
|
||||
runId=run_id;
|
||||
var button = $('<button/>',
|
||||
{
|
||||
text: 'View Log',
|
||||
click: function() {$("#details-dialog").dialog("open");}
|
||||
});
|
||||
|
||||
$(parentID).append(button);
|
||||
|
||||
$("#details-dialog").dialog({
|
||||
dialogClass: 'ui-dialog',
|
||||
autoOpen: false,
|
||||
width: 800,
|
||||
height: 500,
|
||||
buttons: [
|
||||
{
|
||||
text: "OK",
|
||||
click: function() {
|
||||
$( this ).dialog( "close" );
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
});
|
||||
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
$(function() {
|
||||
|
||||
$('#side-menu').metisMenu();
|
||||
|
||||
});
|
||||
|
||||
//Loads the correct sidebar on window load,
|
||||
//collapses the sidebar on window resize.
|
||||
// Sets the min-height of #page-wrapper to window size
|
||||
$(function() {
|
||||
$(window).bind("load resize", function() {
|
||||
topOffset = 50;
|
||||
width = (this.window.innerWidth > 0) ? this.window.innerWidth : this.screen.width;
|
||||
if (width < 768) {
|
||||
$('div.navbar-collapse').addClass('collapse');
|
||||
topOffset = 100; // 2-row-menu
|
||||
} else {
|
||||
$('div.navbar-collapse').removeClass('collapse');
|
||||
}
|
||||
|
||||
height = ((this.window.innerHeight > 0) ? this.window.innerHeight : this.screen.height) - 1;
|
||||
height = height - topOffset;
|
||||
if (height < 1) height = 1;
|
||||
if (height > topOffset) {
|
||||
$("#page-wrapper").css("min-height", (height) + "px");
|
||||
}
|
||||
});
|
||||
|
||||
var url = window.location;
|
||||
var element = $('ul.nav a').filter(function() {
|
||||
return this.href == url || url.href.indexOf(this.href) == 0;
|
||||
}).addClass('active').parent().parent().addClass('in').parent();
|
||||
if (element.is('li')) {
|
||||
element.addClass('active');
|
||||
}
|
||||
});
|
@ -1,118 +0,0 @@
|
||||
/*
|
||||
* (c) Copyright 2015 Hewlett-Packard Development Company, L.P.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
/*
|
||||
<div class="col-lg-12">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">Test Runs</div>
|
||||
<div class="panel-body" id="run-summary-div">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
*/
|
||||
|
||||
//@param data: JSON data of the test run
|
||||
function getData(data) {
|
||||
|
||||
var num_successes = 0;
|
||||
var num_failures = 0;
|
||||
var num_skipped = 0
|
||||
var total_time = 0;
|
||||
var longest_test={duration: 0};
|
||||
|
||||
function calculateChildrenTime(i) {
|
||||
var dur = 0;
|
||||
if (typeof i.duration !== "undefined") {
|
||||
if (i.status=="success") num_successes++;
|
||||
else if (i.status=="fail") num_failures++;
|
||||
else if (i.status=="skip") num_skipped++;
|
||||
|
||||
if (longest_test.duration < i.duration)
|
||||
longest_test = i;
|
||||
|
||||
dur = i.duration;
|
||||
}
|
||||
else {
|
||||
for (var k in i.children) {
|
||||
dur += calculateChildrenTime(i.children[k]);
|
||||
}
|
||||
}
|
||||
return dur;
|
||||
}
|
||||
|
||||
total_time=calculateChildrenTime(data);
|
||||
|
||||
var data_dict= { "Successes": num_successes,
|
||||
"Failures": num_failures,
|
||||
"Skipped": num_skipped,
|
||||
"Total Time": total_time.toFixed(2),
|
||||
"Longest Test": longest_test.name + " ("+longest_test.duration+")"};
|
||||
|
||||
return data_dict;
|
||||
}
|
||||
|
||||
|
||||
function createTable(data, entry) {
|
||||
var container = $("<div>")
|
||||
.addClass('col-lg-6' )
|
||||
.appendTo($("#run-summary-div"));
|
||||
|
||||
var panel = $("<div>")
|
||||
.addClass('panel panel-default')
|
||||
.appendTo(container);
|
||||
|
||||
var head = $("<div>")
|
||||
.addClass("panel-heading")
|
||||
.appendTo(panel);
|
||||
head.append($("<a>", {
|
||||
href: 'tempest_timeline_' + entry.provider + '_' + entry.run + '.html',
|
||||
text: entry.providerDescription + ", run #" + entry.run
|
||||
}));
|
||||
|
||||
var body = $("<div>")
|
||||
.addClass("panel-body")
|
||||
.appendTo(panel);
|
||||
|
||||
var table = $("<table>")
|
||||
.addClass("table table-bordered table-hover table-striped")
|
||||
.appendTo(body);
|
||||
|
||||
var data_dict = getData(data);
|
||||
for (var key in data_dict) {
|
||||
$("<tr>")
|
||||
.append($("<td>").text(key))
|
||||
.append($("<td>").text(data_dict[key]))
|
||||
.appendTo(table);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//@param run_id: The method is passed the latest run_id so it can populate the tables moving backwards
|
||||
function createTables(entries) {
|
||||
entries.forEach(function(entry) {
|
||||
//TODO: Sort tables when inserting so they appear in correct order
|
||||
d3.json(entry.url, function(error, data) {
|
||||
if (error) throw error;
|
||||
//create a table for the info
|
||||
// TODO: entry now has provider description, etc which should be
|
||||
// shown (categorized?)
|
||||
createTable(data, entry);
|
||||
});
|
||||
})
|
||||
}
|
@ -1,177 +0,0 @@
|
||||
/*
|
||||
* (c) Copyright 2015 Hewlett-Packard Development Company, L.P.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var runId = null;
|
||||
var providerName = null;
|
||||
|
||||
function populateTable(d, textColor) {
|
||||
var oldtbl = document.getElementById("result-table-div");
|
||||
oldtbl.innerHTML = "";
|
||||
var tbl = document.createElement('table');
|
||||
tbl.setAttribute("id","test-table");
|
||||
tbl.setAttribute("class","table table-bordered table-hover table-striped");
|
||||
if (typeof d.children == "undefined") {
|
||||
for (var key in d) {
|
||||
if (key=="status" || key=="name_full" || key=="name" || key=="duration" || key=="tags" || key=="timestamps") {
|
||||
var row = tbl.insertRow();
|
||||
var td1 = row.insertCell();
|
||||
var td2 = row.insertCell();
|
||||
td1.innerHTML = key;
|
||||
td2.innerHTML = d[key];
|
||||
}
|
||||
}
|
||||
document.getElementById("result-table-div").appendChild(tbl);
|
||||
document.getElementById("table-heading").innerHTML=d.name;
|
||||
addDialogButton("#result-table-div",providerName + "_" + runId);
|
||||
showDetails(d);
|
||||
}
|
||||
else {
|
||||
for (var j in d.children) {
|
||||
var row = tbl.insertRow();
|
||||
var td1 = row.insertCell();
|
||||
var td2 = row.insertCell();
|
||||
td1.innerHTML = d.children[j].name;
|
||||
td2.innerHTML = calculateChildrenTime(d.children[j]).toFixed(2);
|
||||
td1.style.color = textColor(d.children[j]);
|
||||
document.getElementById("result-table-div").appendChild(tbl);
|
||||
document.getElementById("table-heading").innerHTML=d.name +
|
||||
": " + calculateChildrenTime(d).toFixed(2) + " seconds"
|
||||
$( "table-test" ).DataTable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function calculateChildrenTime(i) {
|
||||
var dur = 0;
|
||||
if (typeof i.duration !== "undefined") {
|
||||
dur = i.duration;
|
||||
}
|
||||
else {
|
||||
for (var k in i.children) {
|
||||
dur += calculateChildrenTime(i.children[k]);
|
||||
}
|
||||
}
|
||||
return dur;
|
||||
}
|
||||
|
||||
function displayFailingTests(d) {
|
||||
|
||||
document.getElementById("failure-table-div").innerHTML="";
|
||||
var tbl = document.createElement('table');
|
||||
tbl.setAttribute("id","failure-table");
|
||||
tbl.setAttribute("class","table table-bordered table-hover table-striped");
|
||||
|
||||
function findFailingTests(i,result) {
|
||||
if (i.status == "fail") {
|
||||
result.push(i);
|
||||
}
|
||||
else {
|
||||
for (var k in i.children) {
|
||||
findFailingTests(i.children[k],result);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var failureList=[];
|
||||
|
||||
findFailingTests(d,failureList);
|
||||
for (var row in failureList) {
|
||||
var newRow = tbl.insertRow();
|
||||
newRow.setAttribute("class","failure-row");
|
||||
var td1 = newRow.insertCell();
|
||||
var td2 = newRow.insertCell();
|
||||
td1.innerHTML = failureList[row].name_full;
|
||||
td2.innerHTML = parseFloat(failureList[row].duration).toFixed(2);
|
||||
}
|
||||
|
||||
document.getElementById("failure-table-div").appendChild(tbl);
|
||||
$( "#failure-table-div" ).hide();
|
||||
}
|
||||
|
||||
|
||||
function createSunburst(url, provider_name, run_id) {
|
||||
runId = run_id;
|
||||
providerName = provider_name;
|
||||
|
||||
var width = 700,
|
||||
height = 500,
|
||||
radius = Math.min(width, height) / 2;
|
||||
|
||||
var x = d3.scale.linear()
|
||||
.range([0, 2 * Math.PI]);
|
||||
|
||||
var y = d3.scale.sqrt()
|
||||
.range([0, radius]);
|
||||
|
||||
var color = d3.scale.category20c();
|
||||
|
||||
var svg = d3.select("#sunburst").append("svg")
|
||||
.attr("width", width)
|
||||
.attr("height", height)
|
||||
.append("g")
|
||||
.attr("transform", "translate(" + width / 2 + "," + (height / 2 + 10) + ")");
|
||||
|
||||
var partition = d3.layout.partition()
|
||||
.value(function(d) { return d.duration; });
|
||||
|
||||
var arc = d3.svg.arc()
|
||||
.startAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x))); })
|
||||
.endAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx))); })
|
||||
.innerRadius(function(d) { return Math.max(0, y(d.y)); })
|
||||
.outerRadius(function(d) { return Math.max(0, y(d.y + d.dy)); });
|
||||
|
||||
d3.json(url, function(error, root) {
|
||||
if (error) throw error;
|
||||
|
||||
displayFailingTests(root);
|
||||
|
||||
var path = svg.selectAll("path")
|
||||
.data(partition.nodes(root))
|
||||
.enter().append("path")
|
||||
.attr("d", arc)
|
||||
.style("fill", function(d) { return color(d.name); })
|
||||
.on("click", click);
|
||||
|
||||
function click(d) {
|
||||
path.transition()
|
||||
.duration(750)
|
||||
.attrTween("d", arcTween(d));
|
||||
populateTable(d,mouse);
|
||||
}
|
||||
|
||||
function mouse(d) { return color(d.name); }
|
||||
|
||||
});
|
||||
|
||||
d3.select(self.frameElement).style("height", height + "px");
|
||||
|
||||
// Interpolate the scales!
|
||||
function arcTween(d) {
|
||||
var xd = d3.interpolate(x.domain(), [d.x, d.x + d.dx]),
|
||||
yd = d3.interpolate(y.domain(), [d.y, 1]),
|
||||
yr = d3.interpolate(y.range(), [d.y ? 20 : 0, radius]);
|
||||
return function(d, i) {
|
||||
return i
|
||||
? function(t) { return arc(d); }
|
||||
: function(t) { x.domain(xd(t)); y.domain(yd(t)).range(yr(t)); return arc(d); };
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -1,661 +0,0 @@
|
||||
/*
|
||||
* (c) Copyright 2015 Hewlett-Packard Development Company, L.P.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*global d3:false*/
|
||||
|
||||
var statusColorMap = {
|
||||
"success": "LightGreen",
|
||||
"fail": "Crimson",
|
||||
"skip": "DodgerBlue"
|
||||
};
|
||||
|
||||
var binaryMinIndex = function(min, array, func) {
|
||||
"use strict";
|
||||
|
||||
var left = 0;
|
||||
var right = array.length - 1;
|
||||
|
||||
while (left < right) {
|
||||
var mid = Math.floor((left + right) / 2);
|
||||
|
||||
if (min < func(array[mid])) {
|
||||
right = mid - 1;
|
||||
} else if (min > func(array[mid])) {
|
||||
left = mid + 1;
|
||||
} else {
|
||||
right = mid;
|
||||
}
|
||||
}
|
||||
|
||||
if (left >= array.length) {
|
||||
return array.length - 1;
|
||||
} else if (func(array[left]) <= min) {
|
||||
return left;
|
||||
} else {
|
||||
return left - 1;
|
||||
}
|
||||
};
|
||||
|
||||
var binaryMaxIndex = function(max, array, func) {
|
||||
"use strict";
|
||||
|
||||
var left = 0;
|
||||
var right = array.length - 1;
|
||||
|
||||
while (left < right) {
|
||||
var mid = Math.floor((left + right) / 2);
|
||||
|
||||
if (max < func(array[mid])) {
|
||||
right = mid - 1;
|
||||
} else if (max > func(array[mid])) {
|
||||
left = mid + 1;
|
||||
} else {
|
||||
right = mid;
|
||||
}
|
||||
}
|
||||
|
||||
if (right < 0) {
|
||||
return 0;
|
||||
} else if (func(array[right]) <= max) {
|
||||
return right + 1; // exclusive index
|
||||
} else {
|
||||
return right;
|
||||
}
|
||||
};
|
||||
|
||||
var parseWorker = function(tags) {
|
||||
"use strict";
|
||||
|
||||
for (var i = 0; i < tags.length; i++) {
|
||||
if (!tags[i].startsWith("worker")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return parseInt(tags[i].split("-")[1]);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
var getDstatLanes = function(data, mins, maxes) {
|
||||
if (!data) {
|
||||
return [];
|
||||
}
|
||||
|
||||
var row = data[0];
|
||||
var lanes = [];
|
||||
|
||||
if ('total_cpu_usage_usr' in row && 'total_cpu_usage_sys' in row) {
|
||||
lanes.push([{
|
||||
scale: d3.scale.linear().domain([0, 100]),
|
||||
value: function(d) {
|
||||
return d.total_cpu_usage_wai;
|
||||
},
|
||||
color: "rgba(224, 188, 188, 1)",
|
||||
text: "CPU wait"
|
||||
}, {
|
||||
scale: d3.scale.linear().domain([0, 100]),
|
||||
value: function(d) {
|
||||
return d.total_cpu_usage_usr + d.total_cpu_usage_sys;
|
||||
},
|
||||
color: "rgba(102, 140, 178, 0.75)",
|
||||
text: "CPU (user+sys)"
|
||||
}]);
|
||||
}
|
||||
|
||||
if ('memory_usage_used' in row) {
|
||||
lanes.push([{
|
||||
scale: d3.scale.linear().domain([0, maxes.memory_usage_used]),
|
||||
value: function(d) { return d.memory_usage_used; },
|
||||
color: "rgba(102, 140, 178, 0.75)",
|
||||
text: "Memory"
|
||||
}]);
|
||||
}
|
||||
|
||||
if ('net_total_recv' in row && 'net_total_send' in row) {
|
||||
lanes.push([{
|
||||
scale: d3.scale.linear().domain([0, maxes.net_total_recv]),
|
||||
value: function(d) { return d.net_total_recv; },
|
||||
color: "rgba(224, 188, 188, 1)",
|
||||
text: "Net Down"
|
||||
}, {
|
||||
scale: d3.scale.linear().domain([0, maxes.net_total_send]),
|
||||
value: function(d) { return d.net_total_send; },
|
||||
color: "rgba(102, 140, 178, 0.75)",
|
||||
text: "Net Up",
|
||||
type: "line"
|
||||
}]);
|
||||
}
|
||||
|
||||
if ('dsk_total_read' in row && 'dsk_total_writ' in row) {
|
||||
lanes.push([{
|
||||
scale: d3.scale.linear().domain([0, maxes.dsk_total_read]),
|
||||
value: function(d) { return d.dsk_total_read; },
|
||||
color: "rgba(224, 188, 188, 1)",
|
||||
text: "Disk Read",
|
||||
type: "line"
|
||||
}, {
|
||||
scale: d3.scale.linear().domain([0, maxes.dsk_total_writ]),
|
||||
value: function(d) { return d.dsk_total_writ; },
|
||||
color: "rgba(102, 140, 178, 0.75)",
|
||||
text: "Disk Write",
|
||||
type: "line"
|
||||
}]);
|
||||
}
|
||||
|
||||
return lanes;
|
||||
};
|
||||
|
||||
var initTimeline = function(options, data, timeExtents) {
|
||||
"use strict";
|
||||
|
||||
var container = $(options.container);
|
||||
|
||||
// http://bl.ocks.org/bunkat/2338034
|
||||
var margin = { top: 20, right: 10, bottom: 10, left: 80 };
|
||||
var width = container.width() - margin.left - margin.right;
|
||||
var height = 550 - margin.top - margin.bottom;
|
||||
|
||||
// filter dstat data immediately. if no timestamps overlap, we want to throw
|
||||
// it away quickly
|
||||
options.dstatData = options.dstatData.slice(
|
||||
binaryMinIndex(timeExtents[0], options.dstatData, function(d) { return d.system_time; }),
|
||||
binaryMaxIndex(timeExtents[1], options.dstatData, function(d) { return d.system_time; })
|
||||
);
|
||||
|
||||
var dstatLanes;
|
||||
if (options.dstatData.length > 2) {
|
||||
dstatLanes = getDstatLanes(
|
||||
options.dstatData,
|
||||
options.dstatMinimums,
|
||||
options.dstatMaximums);
|
||||
} else {
|
||||
dstatLanes = [];
|
||||
}
|
||||
|
||||
var miniHeight = data.length * 12 + 30;
|
||||
var dstatHeight = dstatLanes.length * 30 + 30;
|
||||
var mainHeight = height - miniHeight - dstatHeight - 10;
|
||||
|
||||
var x = d3.time.scale()
|
||||
.range([0, width])
|
||||
.domain(timeExtents);
|
||||
|
||||
var x1 = d3.scale.linear().range([0, width]);
|
||||
|
||||
var y1 = d3.scale.linear()
|
||||
.domain([0, data.length])
|
||||
.range([0, mainHeight]);
|
||||
var y2 = d3.scale.linear()
|
||||
.domain([0, data.length])
|
||||
.range([0, miniHeight]);
|
||||
var y3 = d3.scale.linear()
|
||||
.domain([0, dstatLanes.length])
|
||||
.range([0, dstatHeight]);
|
||||
|
||||
var chart = d3.select(options.container)
|
||||
.append("svg")
|
||||
.attr("width", width + margin.left + margin.right)
|
||||
.attr("height", height + margin.top + margin.bottom)
|
||||
.attr("class", "chart");
|
||||
|
||||
var defs = chart.append("defs")
|
||||
.append("clipPath")
|
||||
.attr("id", "clip")
|
||||
.append("rect")
|
||||
.attr("width", width)
|
||||
.attr("height", mainHeight);
|
||||
|
||||
var main = chart.append("g")
|
||||
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
|
||||
.attr("width", width)
|
||||
.attr("height", mainHeight)
|
||||
.attr("class", "main");
|
||||
|
||||
var laneLines = main.append("g");
|
||||
var laneLabels = main.append("g");
|
||||
|
||||
var itemGroups = main.append("g");
|
||||
|
||||
var dstatOffset = margin.top + mainHeight;
|
||||
var dstatGroup = chart.append("g")
|
||||
.attr("transform", "translate(" + margin.left + "," + dstatOffset + ")")
|
||||
.attr("width", width)
|
||||
.attr("height", dstatHeight);
|
||||
|
||||
dstatLanes.forEach(function(lane, i) {
|
||||
var laneGroup = dstatGroup.append("g");
|
||||
|
||||
var text = laneGroup.append("text")
|
||||
.attr("y", function(d) { return y3(i + 0.5); })
|
||||
.attr("dy", ".5ex")
|
||||
.attr("text-anchor", "end")
|
||||
.style("font", "10px sans-serif");
|
||||
|
||||
var dy = 0;
|
||||
|
||||
// precompute some known info for each lane's paths
|
||||
lane.forEach(function(pathDef) {
|
||||
var laneHeight = 0.8 * y3(1);
|
||||
|
||||
if ('text' in pathDef) {
|
||||
text.append("tspan")
|
||||
.attr("x", -margin.right)
|
||||
.attr("dy", dy)
|
||||
.text(pathDef.text)
|
||||
.attr("fill", function(d) { return pathDef.color; });
|
||||
|
||||
dy += 10;
|
||||
}
|
||||
|
||||
pathDef.scale.range([laneHeight, 0]);
|
||||
|
||||
pathDef.path = laneGroup.append("path");
|
||||
if (pathDef.type === "line") {
|
||||
pathDef.area = d3.svg.line()
|
||||
.x(function(d) { return x1(d.system_time); })
|
||||
.y(function(d) { return y3(i) + pathDef.scale(pathDef.value(d)); });
|
||||
|
||||
pathDef.path
|
||||
.style("stroke", pathDef.color)
|
||||
.style("stroke-width", "1.5px")
|
||||
.style("fill", "none");
|
||||
//.style("shape-rendering", 'crispEdges');
|
||||
} else {
|
||||
pathDef.area = d3.svg.area()
|
||||
.x(function(d) { return x1(d.system_time); })
|
||||
.y0(function(d) { return y3(i) + laneHeight; })
|
||||
.y1(function(d) {
|
||||
return y3(i) + pathDef.scale(pathDef.value(d));
|
||||
});
|
||||
|
||||
pathDef.path.style("fill", pathDef.color);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
var cursorGroup = main.append("g")
|
||||
.style("opacity", 0)
|
||||
.style("pointer-events", "none");
|
||||
|
||||
var cursor = cursorGroup.append("line")
|
||||
.attr("x1", 0)
|
||||
.attr("x2", 0)
|
||||
.attr("y1", y1(-0.1))
|
||||
.attr("stroke", "blue");
|
||||
|
||||
var cursorText = cursorGroup.append("text")
|
||||
.attr("x", 0)
|
||||
.attr("y", -10)
|
||||
.attr("dy", "-.5ex")
|
||||
.text("")
|
||||
.style("text-anchor", "middle")
|
||||
.style("font", "9px sans-serif");
|
||||
|
||||
var miniOffset = margin.top + mainHeight + dstatHeight;
|
||||
var mini = chart.append("g")
|
||||
.attr("transform", "translate(" + margin.left + "," + miniOffset + ")")
|
||||
.attr("width", width)
|
||||
.attr("height", mainHeight)
|
||||
.attr("class", "mini");
|
||||
|
||||
var miniGroups = mini.append("g");
|
||||
|
||||
// performance hack: performance in Firefox as of 39.0 is poor due to some
|
||||
// d3 bugs
|
||||
// Limit the initial selection to ~1/6th of the total to make things
|
||||
// bearable (user can still increase as desired)
|
||||
var start = timeExtents[0];
|
||||
var end = timeExtents[1];
|
||||
var reducedEnd = new Date(start.getTime() + ((end - start) / 8));
|
||||
|
||||
var brush = d3.svg.brush()
|
||||
.x(x)
|
||||
.extent([start, reducedEnd]);
|
||||
|
||||
chart.on("mouseout", function() {
|
||||
cursorGroup.style("opacity", 0);
|
||||
});
|
||||
|
||||
chart.on("mousemove", function() {
|
||||
var pos = d3.mouse(this);
|
||||
var px = pos[0];
|
||||
var py = pos[1];
|
||||
|
||||
if (px >= margin.left && px < (width + margin.left) &&
|
||||
py > margin.top && py < (mainHeight + margin.top)) {
|
||||
var relX = px - margin.left;
|
||||
|
||||
var currentTime = new Date(x1.invert(relX));
|
||||
|
||||
cursorGroup.style("opacity", "0.5");
|
||||
cursorGroup.attr("transform", "translate(" + relX + ", 0)");
|
||||
|
||||
cursorText.text(d3.time.format("%X")(currentTime));
|
||||
}
|
||||
});
|
||||
|
||||
function updateLanes() {
|
||||
var lines = laneLines.selectAll(".laneLine")
|
||||
.data(data, function(d) { return d.key; });
|
||||
|
||||
lines.enter().append("line")
|
||||
.attr("x1", 0)
|
||||
.attr("x2", width)
|
||||
.attr("stroke", "lightgray")
|
||||
.attr("class", "laneLine");
|
||||
|
||||
lines.attr("y1", function(d, i) { return y1(i - 0.1); })
|
||||
.attr("y2", function(d, i) { return y1(i - 0.1); });
|
||||
|
||||
lines.exit().remove();
|
||||
|
||||
var labels = laneLabels.selectAll(".laneLabel")
|
||||
.data(data, function(d) { return d.key; });
|
||||
|
||||
labels.enter().append("text")
|
||||
.text(function(d) { return "Worker #" + d.key; })
|
||||
.attr("x", -margin.right)
|
||||
.attr("dy", ".5ex")
|
||||
.attr("text-anchor", "end")
|
||||
.attr("class", "laneLabel");
|
||||
|
||||
labels.attr("y", function(d, i) { return y1(i + 0.5); });
|
||||
|
||||
labels.exit().remove();
|
||||
|
||||
cursor.attr("y2", y1(data.length - 0.1));
|
||||
}
|
||||
|
||||
function updateItems() {
|
||||
var minExtent = brush.extent()[0];
|
||||
var maxExtent = brush.extent()[1];
|
||||
|
||||
// filter visible items to include only those within the current extent
|
||||
// additionally prune extremely small values to improve performance
|
||||
var visibleItems = data.map(function(group) {
|
||||
return {
|
||||
key: group.key,
|
||||
values: group.values.filter(function(e) {
|
||||
if (x1(e.end_date) - x1(e.start_date) < 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (e.start_date > maxExtent || e.end_date < minExtent) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
})
|
||||
};
|
||||
});
|
||||
|
||||
var groups = itemGroups.selectAll("g")
|
||||
.data(visibleItems, function(d) { return d.key; });
|
||||
|
||||
groups.enter().append("g");
|
||||
|
||||
var rects = groups.selectAll("rect")
|
||||
.data(function(d) { return d.values; }, function(d) { return d.name; });
|
||||
|
||||
rects.enter().append("rect")
|
||||
.attr("y", function(d) { return y1(parseWorker(d.tags)); })
|
||||
.attr("height", 0.8 * y1(1))
|
||||
.attr("stroke", 'rgba(100, 100, 100, 0.25)')
|
||||
.attr("clip-path", "url(#clip)");
|
||||
|
||||
rects
|
||||
.attr("x", function(d) {
|
||||
return x1(d.start_date);
|
||||
})
|
||||
.attr("width", function(d) {
|
||||
return x1(d.end_date) - x1(d.start_date);
|
||||
})
|
||||
.attr("fill", function(d) { return statusColorMap[d.status]; })
|
||||
.on("mouseover", options.onMouseover)
|
||||
.on("mouseout", options.onMouseout)
|
||||
.on("click", options.onClick);
|
||||
|
||||
rects.exit().remove();
|
||||
groups.exit().remove();
|
||||
}
|
||||
|
||||
function updateDstat() {
|
||||
if (dstatLanes.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var minExtent = brush.extent()[0];
|
||||
var maxExtent = brush.extent()[1];
|
||||
|
||||
var dstat = options.dstatData;
|
||||
var timeFunc = function(d) { return d.system_time; };
|
||||
|
||||
var visibleEntries = dstat.slice(
|
||||
binaryMinIndex(minExtent, dstat, timeFunc),
|
||||
binaryMaxIndex(maxExtent, dstat, timeFunc)
|
||||
);
|
||||
|
||||
// apply the current dataset (visibleEntries) to each dstat path
|
||||
//
|
||||
dstatLanes.forEach(function(lane) {
|
||||
lane.forEach(function(pathDef) {
|
||||
pathDef.path
|
||||
.datum(visibleEntries)
|
||||
.attr("d", pathDef.area);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function updateMiniItems() {
|
||||
var groups = miniGroups.selectAll("g")
|
||||
.data(data, function(d) { return d.key; });
|
||||
|
||||
groups.enter().append("g");
|
||||
|
||||
var rects = groups.selectAll("rect").data(
|
||||
function(d) { return d.values; },
|
||||
function(d) { return d.name; });
|
||||
|
||||
rects.enter().append("rect")
|
||||
.attr("y", function(d) { return y2(parseWorker(d.tags) + 0.5) - 5; })
|
||||
.attr("height", 10);
|
||||
|
||||
rects.attr("x", function(d) { return x(d.start_date); })
|
||||
.attr("width", function(d) { return x(d.end_date) - x(d.start_date); })
|
||||
.attr("stroke", 'rgba(100, 100, 100, 0.25)')
|
||||
.attr("fill", function(d) { return statusColorMap[d.status]; });
|
||||
|
||||
rects.exit().remove();
|
||||
groups.exit().remove();
|
||||
}
|
||||
|
||||
function update() {
|
||||
x1.domain(brush.extent());
|
||||
|
||||
updateLanes();
|
||||
updateItems();
|
||||
updateDstat();
|
||||
}
|
||||
|
||||
brush.on("brush", update);
|
||||
|
||||
mini.append("g")
|
||||
.attr("class", "x brush")
|
||||
.call(brush)
|
||||
.selectAll("rect")
|
||||
.attr("y", 1)
|
||||
.attr("height", miniHeight - 1)
|
||||
.attr("fill", "dodgerblue")
|
||||
.attr("fill-opacity", 0.365);
|
||||
|
||||
updateMiniItems();
|
||||
update();
|
||||
|
||||
$(window).resize(function() {
|
||||
var brushExtent = brush.extent();
|
||||
|
||||
width = container.width() - margin.left - margin.right;
|
||||
x.range([0, width]);
|
||||
x1.range([0, width]);
|
||||
|
||||
chart.attr("width", container.width());
|
||||
defs.attr("width", width);
|
||||
main.attr("width", width);
|
||||
mini.attr("width", width);
|
||||
|
||||
laneLines.selectAll(".laneLine").attr("x2", width);
|
||||
|
||||
brush.extent(brushExtent);
|
||||
|
||||
updateMiniItems();
|
||||
update();
|
||||
});
|
||||
};
|
||||
|
||||
function fillArrayRight(array) {
|
||||
// "fill" the array to the right, overwriting empty values with the next
|
||||
// non-empty value to the left
|
||||
// only false values will be overwritten (e.g. "", null, etc)
|
||||
for (var i = 0; i < array.length - 1; i++) {
|
||||
if (!array[i + 1]) {
|
||||
array[i + 1] = array[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function mergeNames(primary, secondary) {
|
||||
// "zip" together strings in the same position in each array, and do some
|
||||
// basic cleanup of results
|
||||
var ret = [];
|
||||
for (var i = 0; i < primary.length; i++) {
|
||||
ret.push((primary[i] + '_' + secondary[i]).replace(/[ /]/g, '_'));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
function chainLoadDstat(path, yearOverride, callback) {
|
||||
"use strict";
|
||||
|
||||
d3.text(path, function(error, data) {
|
||||
if (error) {
|
||||
callback([]);
|
||||
return;
|
||||
}
|
||||
|
||||
var primaryNames = null;
|
||||
var secondaryNames = null;
|
||||
var names = null;
|
||||
|
||||
var minimums = {};
|
||||
var maximums = {};
|
||||
|
||||
// assume UTC - may not necessarily be the case?
|
||||
// dstat doesn't include the year in its logs, so we'll need to copy it
|
||||
// from the subunit logs
|
||||
var dateFormat = d3.time.format.utc("%d-%m %H:%M:%S");
|
||||
|
||||
var parsed = d3.csv.parseRows(data, function(row, i) {
|
||||
if (i <= 4) { // header rows - ignore
|
||||
return null;
|
||||
} else if (i == 5) { // primary
|
||||
primaryNames = row;
|
||||
fillArrayRight(primaryNames);
|
||||
return null;
|
||||
} else if (i == 6) { // secondary
|
||||
secondaryNames = row;
|
||||
|
||||
names = mergeNames(primaryNames, secondaryNames);
|
||||
return null;
|
||||
} else {
|
||||
var ret = {};
|
||||
|
||||
for (var col = 0; col < row.length; col++) {
|
||||
var name = names[col];
|
||||
var value = row[col];
|
||||
|
||||
if (name == "system_time") {
|
||||
value = dateFormat.parse(value);
|
||||
value.setFullYear(1900 + yearOverride);
|
||||
} else {
|
||||
value = parseFloat(value);
|
||||
}
|
||||
|
||||
if (!(name in minimums) || value < minimums[name]) {
|
||||
minimums[name] = value;
|
||||
}
|
||||
|
||||
if (!(name in maximums) || value > maximums[name]) {
|
||||
maximums[name] = value;
|
||||
}
|
||||
|
||||
ret[name] = value;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
});
|
||||
|
||||
callback(parsed, minimums, maximums);
|
||||
});
|
||||
}
|
||||
|
||||
function loadTimeline(path, options) { // eslint-disable-line no-unused-vars
|
||||
"use strict";
|
||||
|
||||
d3.json(path, function(error, data) {
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
var minStart = null;
|
||||
var maxEnd = null;
|
||||
data.forEach(function(d) {
|
||||
/*eslint-disable camelcase*/
|
||||
d.start_date = new Date(d.timestamps[0]);
|
||||
if (minStart === null || d.start_date < minStart) {
|
||||
minStart = d.start_date;
|
||||
}
|
||||
|
||||
d.end_date = new Date(d.timestamps[1]);
|
||||
if (maxEnd === null || d.end_date > maxEnd) {
|
||||
maxEnd = d.end_date;
|
||||
}
|
||||
/*eslint-enable camelcase*/
|
||||
});
|
||||
|
||||
data = data.filter(function (d) { return d.duration > 0; });
|
||||
|
||||
var nested = d3.nest()
|
||||
.key(function(d) { return parseWorker(d.tags); })
|
||||
.sortKeys(d3.ascending)
|
||||
.entries(data);
|
||||
|
||||
// include dstat if available
|
||||
if (options.dstatPath && !options.dstatData) {
|
||||
var year = data[0].start_date.getYear();
|
||||
chainLoadDstat(options.dstatPath, year, function(data, mins, maxes) {
|
||||
options.dstatData = data;
|
||||
options.dstatMinimums = mins;
|
||||
options.dstatMaximums = maxes;
|
||||
|
||||
initTimeline(options, nested, [ minStart, maxEnd ]);
|
||||
});
|
||||
} else {
|
||||
initTimeline(options, nested, [ minStart, maxEnd ]);
|
||||
}
|
||||
});
|
||||
}
|
@ -1,104 +0,0 @@
|
||||
/*
|
||||
* (c) Copyright 2015 Hewlett-Packard Development Company, L.P.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
window.addEventListener('load', function() {
|
||||
|
||||
|
||||
//default panel display
|
||||
$("#runs-panel").hide();
|
||||
$("#gerrit-panel").show();
|
||||
$("#run-metadata-panel").hide();
|
||||
|
||||
//dict containing all run_metadata objects, keyed by run_id
|
||||
var RUN_METADATA = {};
|
||||
|
||||
//sets the run metdata of the associated run in the proper div
|
||||
function show_run_metadata(id) {
|
||||
$("#run-metadata-table").html("<thead><th>Key</th><th>Value</th></thead>");
|
||||
var meta = RUN_METADATA[id];
|
||||
for (var i in meta) {
|
||||
var obj = meta[i];
|
||||
var row = $("<tr><td>" + obj['key'] + "</td>" +
|
||||
"<td>" + obj['value'] + "</td></tr>");
|
||||
$("#run-metadata-table").append(row);
|
||||
}
|
||||
}
|
||||
|
||||
//run_metadata will be queried from subunit2sql when given run_id
|
||||
function get_run_metadata(request, run_id) {
|
||||
$.getJSON((request),function(metadata) {
|
||||
RUN_METADATA[run_id]=metadata;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
//Takes a list of runs and creates a pretty div for each
|
||||
function display_runs(data) {
|
||||
$("#runs-panel").show();
|
||||
$("#runs-panel").append("<ul id=\"runs-list\"></ul>");
|
||||
|
||||
for (var i in data) {
|
||||
var run_obj = data[i];
|
||||
//get run_metadata
|
||||
var request = 'upstream_api_run_id_' + run_obj['id'] + '.json';
|
||||
get_run_metadata(request, run_obj['id']);
|
||||
|
||||
var li = $("<li class =\"run-li\" id=\"run-li-" + i + "\" value=\"" + run_obj['id'] + "\"></li>");
|
||||
//on mouseover, show the run_metadata for this run object (li)
|
||||
$(li).hover(
|
||||
function () {
|
||||
$(this).addClass("highlight");
|
||||
show_run_metadata($(this).attr("value"));
|
||||
},
|
||||
function () {
|
||||
$(this).removeClass("highlight");
|
||||
}
|
||||
);
|
||||
|
||||
$(li.append("<a href=" + run_obj['artifacts'] + " target=\"_blank\">" + run_obj['artifacts'] + "\n</a>"));
|
||||
$("#runs-list").append(li);
|
||||
$("#runs-panel-heading").html("Displaying " + i + " Runs");
|
||||
}
|
||||
$("#run-metadata-panel").show();
|
||||
}
|
||||
|
||||
|
||||
$('#gerrit-id').keypress(function (e) {
|
||||
if (e.which == 13) {
|
||||
$( "#gerrit-id-button" ).click();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
//click triggers the api call that returns the run data
|
||||
$('#gerrit-id-button').click(function() {
|
||||
var request = 'upstream_api_changeid_'+$("#gerrit-id").val()+'.json';
|
||||
$("#runs-panel").append("<a href=\"https://review.openstack.org/" + $("#gerrit-id").val() +
|
||||
"/\" target=\"_blank\"><h2> Change ID: " + $("#gerrit-id").val() + "</h2></a>");
|
||||
$("#gerrit-panel").html("Loading Test Runs...");
|
||||
|
||||
$.getJSON((request),function(data) {
|
||||
$("#gerrit-panel").hide();
|
||||
display_runs(data);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
});
|
@ -1,14 +0,0 @@
|
||||
{% extends 'template.html' %}
|
||||
|
||||
{% block title %}Devstack Latest Results{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<h1 class="page-header">Latest Results</h1>
|
||||
</div>
|
||||
<!-- /.col-lg-12 -->
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
@ -1,53 +0,0 @@
|
||||
{% extends 'template.html' %}
|
||||
|
||||
{% load staticfiles %}
|
||||
|
||||
{% block title %}Index{% endblock %}
|
||||
|
||||
{% block head-extra %}
|
||||
<!-- Script for summary page-->
|
||||
<script src="{% static 'js/summary.js' %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<h1 class="page-header">Local Run Summary</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">Tempest Runs</div>
|
||||
<div class="panel-body" id="run-summary-div">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var urlSuffix = "";
|
||||
if ("{{use_gzip}}" === "True") {
|
||||
urlSuffix = ".gz";
|
||||
}
|
||||
|
||||
var tempestRuns = [];
|
||||
|
||||
/* {% for provider in tempest_providers %} begin generated */
|
||||
/* {% for index in provider.indexes %} */
|
||||
tempestRuns.push({
|
||||
provider: "{{provider.name}}",
|
||||
providerDescription: "{{provider.description}}",
|
||||
run: parseInt("{{index}}"),
|
||||
url: "tempest_api_tree_{{provider.name}}_{{index}}.json" + urlSuffix
|
||||
});
|
||||
/* {% endfor %} */
|
||||
/* {% endfor %} end generated */
|
||||
|
||||
window.addEventListener('load', createTables( tempestRuns ));
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
@ -1,61 +0,0 @@
|
||||
<div class="sidebar-nav navbar-collapse">
|
||||
<ul class="nav" id="side-menu">
|
||||
<li>
|
||||
<a href="index.html"><i class="fa fa-pie-chart fa-fw"></i> Overview</a>
|
||||
</li>
|
||||
<!--<li>
|
||||
<a href="#"><i class="fa fa-bar-chart-o fa-fw"></i> DevStack<span class="fa arrow"></span></a>
|
||||
<ul class="nav nav-second-level">
|
||||
<li>
|
||||
<a href="/devstack/results"><i class="fa fa-clock-o fa-fw"></i> Results</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/devstack/"><i class="fa fa-calendar fa-fw"></i> History</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/devstack/"><i class="fa fa-database fa-fw"></i> Compare</a>
|
||||
</li>
|
||||
</ul>
|
||||
--><!-- /.nav-second-level --><!--
|
||||
</li>-->
|
||||
<li>
|
||||
<a href="#"><i class="fa fa-bar-chart-o fa-fw"></i> Tempest<span class="fa arrow"></span></a>
|
||||
<ul class="nav nav-second-level">
|
||||
<li>
|
||||
<a href="tempest_results_{{ tempest_default_provider.name }}_{{ tempest_default_provider.count | add:'-1' }}.html">
|
||||
<i class="fa fa-clock-o fa-fw"></i> Sunburst
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="tempest_timeline_{{ tempest_default_provider.name }}_{{ tempest_default_provider.count | add:'-1' }}.html">
|
||||
<i class="fa fa-calendar fa-fw"></i> Timeline
|
||||
</a>
|
||||
</li>
|
||||
<!--<li>
|
||||
<a href="/tempest/"><i class="fa fa-database fa-fw"></i> Compare</a>
|
||||
</li>-->
|
||||
{% if not offline %}
|
||||
<li class="online">
|
||||
<a href="tempest_aggregate.html"><i class="fa fa-bar-chart-o fa-fw"></i> Aggregate Results</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
<!-- /.nav-second-level -->
|
||||
</li>
|
||||
{% if not offline %}
|
||||
<li class="online">
|
||||
<a href="#"><i class="fa fa-bar-chart-o fa-fw"></i> Upstream<span class="fa arrow"></span></a>
|
||||
<ul class="nav nav-second-level">
|
||||
<li>
|
||||
<a href="upstream_test.html"><i class="fa fa-bar-chart-o fa-fw"></i> Test Stats</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="upstream_run.html"><i class="fa fa-clock-o fa-fw"></i> Run Data</a>
|
||||
</li>
|
||||
</ul>
|
||||
<!-- /.nav-second-level -->
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
<!-- /.sidebar-collapse -->
|
@ -1,14 +0,0 @@
|
||||
{% extends 'template.html' %}
|
||||
|
||||
{% block title %}Aggregate Results{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<h1 class="page-header">Aggregate Tempest Results</h1>
|
||||
</div>
|
||||
<!-- /.col-lg-12 -->
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
@ -1,99 +0,0 @@
|
||||
{% extends 'template.html' %}
|
||||
|
||||
{% load staticfiles %}
|
||||
|
||||
{% block title %}Tempest Results{% endblock %}
|
||||
|
||||
{% block head-extra %}
|
||||
<!-- Scripts for visualization-->
|
||||
<script src="{% static 'js/sunburst.js' %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
|
||||
<div id="details-dialog" title="Details Output">
|
||||
<p>Details not found for test.</p>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<h1 class="page-header">Results from Run #{{run_id}}</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-8">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
Runtime Diagram
|
||||
<div class="pull-right">
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-default btn-xs dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
|
||||
View Run...
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu pull-right" role="menu">
|
||||
{% for provider in tempest_providers %}
|
||||
<li class="dropdown-header">{{ provider.description }}</li>
|
||||
{% for index in provider.indexes %}
|
||||
<li><a href="tempest_results_{{ provider.name }}_{{ index }}.html">Run #{{ index }}</a></li>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div id="sunburst"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-4">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading" id="table-heading">Test Run Info</div>
|
||||
<div class="panel-body">
|
||||
<div class="table-responsive" id="result-table-div">
|
||||
<table class="table table-bordered table-hover table-striped" id="test-table">
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading"><button type="button" id="show-hide-failures">Show/Hide Failures</button></div>
|
||||
<div class="panel-body">
|
||||
<div class="table-responsive" id="failure-table-div">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<script>
|
||||
window.addEventListener('load', function() {
|
||||
$("#details-dialog").hide();
|
||||
var url = "tempest_api_tree_{{provider_name}}_{{run_id}}.json";
|
||||
if ("{{use_gzip}}" === "True") {
|
||||
url += ".gz";
|
||||
}
|
||||
|
||||
createSunburst( url, "{{provider_name}}", {{run_id}} );
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
$("#show-hide-failures").click(function() {
|
||||
$("#failure-table-div").toggle();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
{% endblock %}
|
@ -1,269 +0,0 @@
|
||||
{% extends 'template.html' %}
|
||||
{% load staticfiles %}
|
||||
|
||||
{% block title %}Tempest: Execution Timeline (run #{{run_id}}){% endblock %}
|
||||
|
||||
{% block head-extra %}
|
||||
<style>
|
||||
#timeline-info table {
|
||||
table-layout: fixed;
|
||||
width: 100%;
|
||||
word-wrap: break-word
|
||||
}
|
||||
|
||||
#timeline-info table td:nth-child(1) {
|
||||
width: 20%;
|
||||
}
|
||||
|
||||
#timeline-log pre {
|
||||
overflow-x: scroll;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div id="details-dialog" title="Details Output">
|
||||
<p>Details not found for test.</p>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<h1 class="page-header">Execution Timeline ({{provider_name}}, run #{{run_id}})</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-8">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<i class="fa fa-clock-o fa-fw"></i> Timeline
|
||||
<div class="pull-right">
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-default btn-xs dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
|
||||
View Run...
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu pull-right" role="menu">
|
||||
{% for provider in tempest_providers %}
|
||||
<li class="dropdown-header">{{ provider.description }}</li>
|
||||
{% for index in provider.indexes %}
|
||||
<li><a href="tempest_timeline_{{ provider.name }}_{{ index }}.html">Run #{{ index }}</a></li>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="timeline-container" class="panel-body">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<i class="fa fa-info fa-fw"></i> Info
|
||||
</div>
|
||||
|
||||
<div id="timeline-info" class="panel-body">
|
||||
<em>Mouse over an item to view info.</em>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="{% static 'js/timeline.js' %}"></script>
|
||||
<script>
|
||||
var originalInfoContent = null;
|
||||
var originalDetailsContent = null;
|
||||
|
||||
var detailsCache = null;
|
||||
var detailsInProgress = false;
|
||||
var detailsWaiting = [];
|
||||
|
||||
var showInfo = function(item) {
|
||||
var parent = $("#timeline-info");
|
||||
if (originalInfoContent === null) {
|
||||
originalInfoContent = parent.html();
|
||||
}
|
||||
|
||||
var e = $("<table>", {
|
||||
"class": 'table table-bordered table-hover table-striped'
|
||||
});
|
||||
|
||||
var nameParts = item.name.split(".");
|
||||
var pkg = nameParts.slice(0, nameParts.length - 2).join('.');
|
||||
|
||||
e.append($("<tr>")
|
||||
.append($("<td>", { text: 'Name' }))
|
||||
.append($("<td>", { text: nameParts[nameParts.length - 1] })));
|
||||
|
||||
e.append($("<tr>")
|
||||
.append($("<td>", { text: 'Class' }))
|
||||
.append($("<td>", { text: nameParts[nameParts.length - 2] })));
|
||||
|
||||
e.append($("<tr>")
|
||||
.append($("<td>", { text: 'Module' }))
|
||||
.append($("<td>", { text: pkg })));
|
||||
|
||||
e.append($("<tr>")
|
||||
.append($("<td>", { text: 'Status' }))
|
||||
.append($("<td>", { text: item.status })));
|
||||
|
||||
e.append($("<tr>")
|
||||
.append($("<td>", { text: 'Tags' }))
|
||||
.append($("<td>", { text: item.tags.join(", ") })));
|
||||
|
||||
e.append($("<tr>")
|
||||
.append($("<td>", { text: 'Duration' }))
|
||||
.append($("<td>", { text: item.duration + " seconds" })));
|
||||
|
||||
parent.empty();
|
||||
e.appendTo(parent);
|
||||
|
||||
addDialogButton(parent);
|
||||
};
|
||||
|
||||
var hideInfo = function() {
|
||||
$("#timeline-info").html(originalInfoContent);
|
||||
};
|
||||
|
||||
var loadDetails = function(callback) {
|
||||
if (detailsCache === null) {
|
||||
detailsWaiting.push(callback);
|
||||
|
||||
if (!detailsInProgress) {
|
||||
var url = "tempest_api_details_{{provider_name}}_{{run_id}}.json";
|
||||
if ("{{use_gzip}}" === "True") {
|
||||
url += ".gz";
|
||||
}
|
||||
|
||||
detailsInProgress = true;
|
||||
|
||||
d3.json(url, function(error, data) {
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
detailsCache = data;
|
||||
detailsWaiting.forEach(function(cb) {
|
||||
cb(detailsCache);
|
||||
});
|
||||
});
|
||||
}
|
||||
} else {
|
||||
callback(detailsCache);
|
||||
}
|
||||
};
|
||||
|
||||
var showDetails = function(item) {
|
||||
var parent = $("#details-dialog");
|
||||
|
||||
showInfo(item);
|
||||
|
||||
loadDetails(function(details) {
|
||||
if (!details.hasOwnProperty(item.name)) {
|
||||
console.log("Details not found for item:", item.name);
|
||||
return;
|
||||
}
|
||||
|
||||
if (originalDetailsContent === null) {
|
||||
originalDetailsContent = parent.html();
|
||||
}
|
||||
|
||||
parent.empty();
|
||||
for (var prop in details[item.name]) {
|
||||
$("<h3>").text(prop).appendTo(parent);
|
||||
$("<pre>").text(details[item.name][prop]).appendTo(parent);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var hideDetails = function() {
|
||||
$("#timeline-details").html(originalDetailsContent);
|
||||
};
|
||||
|
||||
window.addEventListener('load', function() {
|
||||
$("#details-dialog").hide();
|
||||
var selectedItem = null;
|
||||
var selectedValue = null;
|
||||
|
||||
var url = "tempest_api_raw_{{provider_name}}_{{run_id}}.json";
|
||||
if ("{{use_gzip}}" === "True") {
|
||||
url += ".gz";
|
||||
}
|
||||
|
||||
var dstatUrl = "dstat_log.csv";
|
||||
if ("{{use_gzip}}" === "True") {
|
||||
dstatUrl += ".gz";
|
||||
}
|
||||
|
||||
loadTimeline(url, {
|
||||
dstatPath: dstatUrl,
|
||||
container: $("#timeline-container")[0],
|
||||
onClick: function(d) {
|
||||
var self = d3.select(this);
|
||||
|
||||
// deselect old item, if any
|
||||
if (selectedItem !== null) {
|
||||
if (selectedItem.attr("data-old-fill")) {
|
||||
selectedItem.attr("fill", selectedItem.attr("data-old-fill"));
|
||||
selectedItem.attr("data-old-fill", null);
|
||||
}
|
||||
|
||||
if (selectedValue.name === d.name) {
|
||||
// remove selection on 2nd click - don't continue
|
||||
selectedItem = null;
|
||||
selectedValue = null;
|
||||
hideDetails();
|
||||
return;
|
||||
}
|
||||
|
||||
selectedItem = null;
|
||||
}
|
||||
|
||||
// select new item
|
||||
if (!self.attr("data-old-fill")) {
|
||||
self.attr("data-old-fill", self.attr("fill"));
|
||||
}
|
||||
|
||||
self.attr("fill", "goldenrod");
|
||||
selectedItem = self;
|
||||
selectedValue = d;
|
||||
|
||||
showDetails(d);
|
||||
},
|
||||
|
||||
onMouseover: function(d) {
|
||||
if (selectedItem !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var self = d3.select(this);
|
||||
if (!self.attr("data-old-fill")) {
|
||||
self.attr("data-old-fill", self.attr("fill"));
|
||||
}
|
||||
|
||||
d3.select(this).attr("fill", "darkturquoise");
|
||||
|
||||
showInfo(d);
|
||||
},
|
||||
|
||||
onMouseout: function(d) {
|
||||
if (selectedItem !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var self = d3.select(this);
|
||||
if (self.attr("data-old-fill")) {
|
||||
self.attr("fill", self.attr("data-old-fill"));
|
||||
self.attr("data-old-fill", null);
|
||||
}
|
||||
|
||||
hideInfo();
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
@ -1,114 +0,0 @@
|
||||
{% load staticfiles %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="description" content="">
|
||||
<meta name="author" content="">
|
||||
|
||||
<title>{% block title %}{% endblock %}</title>
|
||||
|
||||
|
||||
<!-- Bootstrap Core CSS -->
|
||||
<link href="{% static 'components/bootstrap/dist/css/bootstrap.min.css' %}" rel="stylesheet">
|
||||
|
||||
<!-- MetisMenu CSS -->
|
||||
<link href="{% static 'components/metisMenu/dist/metisMenu.min.css' %}" rel="stylesheet">
|
||||
|
||||
<!-- Timeline CSS -->
|
||||
<link href="{% static 'css/timeline.css' %}" rel="stylesheet">
|
||||
|
||||
<!-- Custom CSS -->
|
||||
<link href="{% static 'css/sb-admin-2.css' %}" rel="stylesheet">
|
||||
|
||||
<!-- Morris Charts CSS -->
|
||||
<link href="{% static 'components/morrisjs/morris.css' %}" rel="stylesheet">
|
||||
|
||||
<!-- Custom Fonts -->
|
||||
<link href="{% static 'components/font-awesome/css/font-awesome.min.css' %}" rel="stylesheet" type="text/css">
|
||||
|
||||
<!-- DataTables CSS -->
|
||||
<link href="{% static 'components/datatables/media/css/jquery.dataTables.min.css' %}" rel="stylesheet" type="text/css">
|
||||
|
||||
<!-- jQueryUI CSS -->
|
||||
<link href="{%static 'components/jquery-ui/themes/base/jquery-ui.css' %}" rel="stylesheet" type="text/css">
|
||||
|
||||
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
|
||||
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
|
||||
<!--[if lt IE 9]>
|
||||
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
|
||||
<script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
|
||||
<![endif]-->
|
||||
|
||||
<!-- jQuery -->
|
||||
<script src="{% static 'components/jquery/dist/jquery.min.js' %}"></script>
|
||||
|
||||
<!-- Bootstrap Core JavaScript -->
|
||||
<script src="{% static 'components/bootstrap/dist/js/bootstrap.min.js' %}"></script>
|
||||
|
||||
<!-- Metis Menu Plugin JavaScript -->
|
||||
<script src="{% static 'components/metisMenu/dist/metisMenu.min.js' %}"></script>
|
||||
|
||||
<!-- Custom Theme JavaScript -->
|
||||
<script src="{% static 'js/sb-admin-2.js' %}"></script>
|
||||
|
||||
<!-- d3.js -->
|
||||
<script src="{% static 'components/d3/d3.js' %}"></script>
|
||||
|
||||
<!-- DataTable for jQuery -->
|
||||
<script src="{% static 'components/datatables/media/js/jquery.dataTables.min.js' %}"></script>
|
||||
|
||||
<!-- jQueryUI -->
|
||||
<script src="{% static 'components/jquery-ui/jquery-ui.js' %}"></script>
|
||||
<script src="{% static 'js/log-dialog.js' %}"></script>
|
||||
|
||||
{% block head-extra %}{% endblock %}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="wrapper">
|
||||
|
||||
<!-- Navigation -->
|
||||
<nav class="navbar navbar-default navbar-static-top" role="navigation" style="margin-bottom: 0">
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
|
||||
<span class="sr-only">Toggle navigation</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a class="navbar-brand" href="index.html">StackViz</a>
|
||||
</div>
|
||||
<!-- /.navbar-header -->
|
||||
|
||||
<ul class="nav navbar-top-links navbar-right">
|
||||
|
||||
</ul>
|
||||
<!-- /.navbar-top-links -->
|
||||
|
||||
<div class="navbar-default sidebar" role="navigation">
|
||||
{% include 'menu.html' %}
|
||||
</div>
|
||||
<!-- /.navbar-static-side -->
|
||||
</nav>
|
||||
|
||||
<div id="page-wrapper">
|
||||
{% block body %}{% endblock %}
|
||||
</div>
|
||||
<!-- /#page-wrapper -->
|
||||
|
||||
</div>
|
||||
<!-- /#wrapper -->
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
@ -1,49 +0,0 @@
|
||||
{% extends 'template.html' %}
|
||||
{% load staticfiles %}
|
||||
|
||||
{% block title %}Upstream Run Metadata{% endblock %}
|
||||
|
||||
{% block head-extra %}
|
||||
<script src="{% static 'js/upstream_run.js' %}"></script>
|
||||
<link href="{% static 'css/upstream_run.css' %}" rel="stylesheet" type="text/css">
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<h1 class="page-header">Upstream Run Data</h1>
|
||||
</div>
|
||||
<!-- /.col-lg-12 -->
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel panel-heading" id="runs-panel-heading">Analyze Run</div>
|
||||
<div class="panel panel-body">
|
||||
<div id="gerrit-panel">
|
||||
Enter a Gerrit Change ID (six-digit): <input type="text" id="gerrit-id">
|
||||
<input id="gerrit-id-button" type="button" value="Submit">
|
||||
</div>
|
||||
<div id="runs-panel"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<div class="panel panel-default" id="run-metadata-panel">
|
||||
<div class="panel panel-heading">Run Metadata</div>
|
||||
<div class="panel panel-body">
|
||||
<div class="table-responsive" id="run-metadata-table-div">
|
||||
<table class="table table-bordered table-hover table-striped" id="run-metadata-table"></table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{% endblock %}
|
@ -1,30 +0,0 @@
|
||||
{% extends 'template.html' %}
|
||||
|
||||
{% block title %}Upstream Test Stats{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<h1 class="page-header">Upstream Test Stats</h1>
|
||||
</div>
|
||||
<!-- /.col-lg-12 -->
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-8">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel panel-heading" id="runs-panel-heading">Compare Tests</div>
|
||||
<div class="panel panel-body">
|
||||
<div class="col-lg-4">
|
||||
<div id="test-1" class="span6">Test</div>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<div id="test-avg" class="span6">Avg</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
@ -1,29 +0,0 @@
|
||||
# Copyright 2015 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from django.conf.urls import include
|
||||
from django.conf.urls import patterns
|
||||
from django.conf.urls import url
|
||||
|
||||
from stackviz.views.index import IndexView
|
||||
|
||||
urlpatterns = patterns(
|
||||
'',
|
||||
url(r'^$', IndexView.as_view()),
|
||||
url(r'^index.html$', IndexView.as_view(), name="index"),
|
||||
url(r'^tempest_', include('stackviz.views.tempest.urls')),
|
||||
url(r'^devstack_', include('stackviz.views.devstack.urls')),
|
||||
url(r'^upstream_', include('stackviz.views.upstream.urls')),
|
||||
url(r'^dstat_', include('stackviz.views.dstat.urls'))
|
||||
)
|
@ -1,19 +0,0 @@
|
||||
# Copyright 2015 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from django.views.generic import TemplateView
|
||||
|
||||
|
||||
class ResultsView(TemplateView):
|
||||
template_name = 'devstack/results.html'
|
@ -1,23 +0,0 @@
|
||||
# Copyright 2015 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from django.conf.urls import patterns
|
||||
from django.conf.urls import url
|
||||
|
||||
from stackviz.views.devstack.results import ResultsView
|
||||
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^results$', ResultsView.as_view()),
|
||||
)
|
@ -1 +0,0 @@
|
||||
__author__ = 'tim'
|
@ -1,47 +0,0 @@
|
||||
# Copyright 2015 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from django.http import Http404
|
||||
from django.http import HttpResponse
|
||||
|
||||
from django.views.generic import View
|
||||
|
||||
from stackviz import settings
|
||||
|
||||
_cached_csv = None
|
||||
|
||||
|
||||
def _load_csv():
|
||||
global _cached_csv
|
||||
|
||||
if _cached_csv:
|
||||
return _cached_csv
|
||||
|
||||
try:
|
||||
with open(settings.DSTAT_CSV, 'r') as f:
|
||||
_cached_csv = f.readlines()
|
||||
return _cached_csv
|
||||
except IOError:
|
||||
return None
|
||||
|
||||
|
||||
class DStatCSVEndpoint(View):
|
||||
def get(self, request):
|
||||
csv = _load_csv()
|
||||
|
||||
if not csv:
|
||||
raise Http404("DStat log could not be loaded at path %s"
|
||||
% settings.DSTAT_CSV)
|
||||
|
||||
return HttpResponse(csv, content_type="text/csv")
|
@ -1,21 +0,0 @@
|
||||
# Copyright 2015 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from django.conf.urls import patterns
|
||||
from django.conf.urls import url
|
||||
|
||||
from api import DStatCSVEndpoint
|
||||
|
||||
|
||||
urlpatterns = patterns('', url(r'^log.csv$', DStatCSVEndpoint.as_view()))
|
@ -1,19 +0,0 @@
|
||||
# Copyright 2015 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from django.views.generic import TemplateView
|
||||
|
||||
|
||||
class IndexView(TemplateView):
|
||||
template_name = 'index.html'
|
@ -1,19 +0,0 @@
|
||||
# Copyright 2015 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from django.views.generic import TemplateView
|
||||
|
||||
|
||||
class AggregateResultsView(TemplateView):
|
||||
template_name = 'tempest/aggregate.html'
|
@ -1,136 +0,0 @@
|
||||
# Copyright 2015 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from django.http import Http404
|
||||
from restless.views import Endpoint
|
||||
|
||||
from stackviz.parser.tempest_subunit import convert_stream
|
||||
from stackviz.parser.tempest_subunit import get_providers
|
||||
from stackviz.parser.tempest_subunit import reorganize
|
||||
|
||||
#: Cached results from loaded subunit logs indexed by their run number
|
||||
_cached_run = {}
|
||||
|
||||
#: Cached results converted into tree form
|
||||
_cached_tree = {}
|
||||
|
||||
#: Cached results for loaded subunit logs without details stripped out. Indexed
|
||||
#: initially by log number, but contains nested dicts indexed by the test name.
|
||||
_cached_details = {}
|
||||
|
||||
|
||||
class NoRunDataException(Http404):
|
||||
pass
|
||||
|
||||
|
||||
class ProviderNotFoundException(Http404):
|
||||
pass
|
||||
|
||||
|
||||
class RunNotFoundException(Http404):
|
||||
pass
|
||||
|
||||
|
||||
class TestNotFoundException(Http404):
|
||||
pass
|
||||
|
||||
|
||||
def _load_run(provider_name, run_id):
|
||||
if (provider_name, run_id) in _cached_run:
|
||||
return _cached_run[provider_name, run_id]
|
||||
|
||||
providers = get_providers()
|
||||
if not providers:
|
||||
raise NoRunDataException("No test providers could be loaded")
|
||||
|
||||
if provider_name not in providers:
|
||||
raise ProviderNotFoundException("Requested subunit provider could not "
|
||||
"be found")
|
||||
|
||||
p = providers[provider_name]
|
||||
|
||||
try:
|
||||
# assume first repo for now
|
||||
stream = p.get_stream(run_id)
|
||||
|
||||
# strip details for now
|
||||
# TODO(provide method for getting details on demand)
|
||||
# (preferably for individual tests to avoid bloat)
|
||||
converted_run = convert_stream(stream, strip_details=True)
|
||||
_cached_run[provider_name, run_id] = converted_run
|
||||
|
||||
return converted_run
|
||||
except KeyError:
|
||||
raise RunNotFoundException("Requested test run could not be found")
|
||||
|
||||
|
||||
def _load_tree(provider, run_id):
|
||||
if (provider, run_id) in _cached_tree:
|
||||
return _cached_tree[provider, run_id]
|
||||
|
||||
run = _load_run(provider, run_id)
|
||||
tree = reorganize(run)
|
||||
|
||||
_cached_tree[provider, run_id] = tree
|
||||
return tree
|
||||
|
||||
|
||||
def _load_details(provider_name, run_id, test_name):
|
||||
if (provider_name, run_id) not in _cached_details:
|
||||
providers = get_providers()
|
||||
if not providers:
|
||||
raise NoRunDataException("No test providers could be loaded")
|
||||
|
||||
if provider_name not in providers:
|
||||
raise ProviderNotFoundException("Requested subunit provider could "
|
||||
"not be found: " + provider_name)
|
||||
|
||||
provider = providers[provider_name]
|
||||
try:
|
||||
stream = provider.get_stream(run_id)
|
||||
converted_run = convert_stream(stream, strip_details=False)
|
||||
|
||||
# remap dict to allow direct access to details via test name
|
||||
dest = {}
|
||||
for entry in converted_run:
|
||||
dest[entry['name']] = entry['details']
|
||||
|
||||
_cached_details[provider_name, run_id] = dest
|
||||
except (KeyError, IndexError):
|
||||
raise RunNotFoundException("Requested test run could not be found")
|
||||
|
||||
details_map = _cached_details[provider_name, run_id]
|
||||
if test_name is None:
|
||||
return details_map
|
||||
else:
|
||||
if test_name in details_map:
|
||||
return details_map[test_name]
|
||||
else:
|
||||
raise TestNotFoundException(
|
||||
"Requested test could not be found in run")
|
||||
|
||||
|
||||
class TempestRunRawEndpoint(Endpoint):
|
||||
def get(self, request, provider_name, run_id):
|
||||
return _load_run(provider_name, int(run_id))
|
||||
|
||||
|
||||
class TempestRunTreeEndpoint(Endpoint):
|
||||
def get(self, request, provider_name, run_id):
|
||||
return _load_tree(provider_name, int(run_id))
|
||||
|
||||
|
||||
class TempestRunDetailsEndpoint(Endpoint):
|
||||
def get(self, request, run_id, provider_name, test_name=None):
|
||||
return _load_details(provider_name, int(run_id), test_name)
|
@ -1,26 +0,0 @@
|
||||
# Copyright 2015 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from django.views.generic import TemplateView
|
||||
|
||||
|
||||
class ResultsView(TemplateView):
|
||||
template_name = 'tempest/results.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(ResultsView, self).get_context_data(**kwargs)
|
||||
context['provider_name'] = self.kwargs['provider_name']
|
||||
context['run_id'] = self.kwargs['run_id']
|
||||
|
||||
return context
|
@ -1,26 +0,0 @@
|
||||
# Copyright 2015 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from django.views.generic import TemplateView
|
||||
|
||||
|
||||
class TimelineView(TemplateView):
|
||||
template_name = 'tempest/timeline.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(TimelineView, self).get_context_data(**kwargs)
|
||||
context['provider_name'] = self.kwargs['provider_name']
|
||||
context['run_id'] = self.kwargs['run_id']
|
||||
|
||||
return context
|
@ -1,52 +0,0 @@
|
||||
# Copyright 2015 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
|
||||
from django.conf.urls import patterns
|
||||
from django.conf.urls import url
|
||||
|
||||
from aggregate import AggregateResultsView
|
||||
from results import ResultsView
|
||||
from timeline import TimelineView
|
||||
|
||||
from api import TempestRunDetailsEndpoint
|
||||
from api import TempestRunRawEndpoint
|
||||
from api import TempestRunTreeEndpoint
|
||||
|
||||
|
||||
urlpatterns = patterns(
|
||||
'',
|
||||
url(r'^results_(?P<provider_name>[\w_\.]+)_(?P<run_id>\d+).html$',
|
||||
ResultsView.as_view(),
|
||||
name='tempest_results'),
|
||||
url(r'^timeline_(?P<provider_name>[\w_\.]+)_(?P<run_id>\d+).html$',
|
||||
TimelineView.as_view(),
|
||||
name='tempest_timeline'),
|
||||
|
||||
url(r'^api_tree_(?P<provider_name>[\w_\.]+)_(?P<run_id>\d+).json$',
|
||||
TempestRunTreeEndpoint.as_view(),
|
||||
name='tempest_api_tree'),
|
||||
url(r'^api_raw_(?P<provider_name>[\w_\.]+)_(?P<run_id>\d+).json$',
|
||||
TempestRunRawEndpoint.as_view(),
|
||||
name='tempest_api_raw'),
|
||||
url(r'^api_details_(?P<provider_name>[\w_\.]+)_(?P<run_id>\d+).json$',
|
||||
TempestRunDetailsEndpoint.as_view()),
|
||||
url(r'^api_details_(?P<provider_name>[\w_\.]+)_(?P<run_id>\d+)_'
|
||||
r'(?P<test_name>[^/]+).json$',
|
||||
TempestRunDetailsEndpoint.as_view()),
|
||||
|
||||
url(r'^aggregate.html$',
|
||||
AggregateResultsView.as_view(),
|
||||
name='tempest_aggregate_results'),
|
||||
)
|
@ -1,78 +0,0 @@
|
||||
# Copyright 2015 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from restless.views import Endpoint
|
||||
|
||||
from subunit2sql.db import api
|
||||
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
|
||||
def _get_runs(change_id):
|
||||
"""Returns the dict of run objects associated with a changeID
|
||||
|
||||
When given the change_id of a Gerrit change, a connection will be made to
|
||||
the upstream subunit2sql db and query all run meta having that change_id
|
||||
:param change_id: the Gerrit change_id to query
|
||||
:return: a json dict of run_meta objects
|
||||
"""
|
||||
|
||||
engine = create_engine('mysql://query:query@logstash.openstack.org' +
|
||||
':3306/subunit2sql')
|
||||
Session = sessionmaker(bind=engine)
|
||||
session = Session()
|
||||
|
||||
list_of_runs = api.get_runs_by_key_value(key="build_change",
|
||||
value=change_id,
|
||||
session=session)
|
||||
ret_list = []
|
||||
|
||||
for run in list_of_runs:
|
||||
ret_list.append(run.to_dict())
|
||||
|
||||
return ret_list
|
||||
|
||||
|
||||
def _get_metadata(run_id):
|
||||
"""Returns a dict of run_metadata objects associated with a run_id
|
||||
|
||||
:param run_id:
|
||||
:return:
|
||||
"""
|
||||
|
||||
engine = create_engine('mysql://query:query@logstash.openstack.org' +
|
||||
':3306/subunit2sql')
|
||||
Session = sessionmaker(bind=engine)
|
||||
session = Session()
|
||||
|
||||
metadata = api.get_run_metadata(run_id, session=session)
|
||||
ret_list = []
|
||||
|
||||
for meta in metadata:
|
||||
ret_list.append(meta.to_dict())
|
||||
|
||||
return ret_list
|
||||
|
||||
|
||||
class GerritURLEndpoint(Endpoint):
|
||||
|
||||
def get(self, request, change_id):
|
||||
return _get_runs(change_id)
|
||||
|
||||
|
||||
class RunMetadataEndpoint(Endpoint):
|
||||
|
||||
def get(self, request, run_id):
|
||||
return _get_metadata(run_id)
|
@ -1,19 +0,0 @@
|
||||
# Copyright 2015 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from django.views.generic import TemplateView
|
||||
|
||||
|
||||
class RunView(TemplateView):
|
||||
template_name = 'upstream/run.html'
|
@ -1,23 +0,0 @@
|
||||
# Copyright 2015 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from django.views.generic import TemplateView
|
||||
|
||||
# TODO(Planned functionality)
|
||||
# Compare one specific test against its moving average
|
||||
#
|
||||
|
||||
|
||||
class TestView(TemplateView):
|
||||
template_name = 'upstream/test.html'
|
@ -1,41 +0,0 @@
|
||||
# Copyright 2015 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from django.conf.urls import patterns
|
||||
from django.conf.urls import url
|
||||
|
||||
from run import RunView
|
||||
from test import TestView
|
||||
|
||||
from api import GerritURLEndpoint
|
||||
from api import RunMetadataEndpoint
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^run.html$',
|
||||
RunView.as_view(),
|
||||
name='run_metadata'),
|
||||
|
||||
url(r'^test.html$',
|
||||
TestView.as_view(),
|
||||
name='test_data'),
|
||||
|
||||
url(r'^api_changeid_(?P<change_id>\d+).json$',
|
||||
GerritURLEndpoint.as_view(),
|
||||
name='gerrit_url'),
|
||||
|
||||
url(r'^api_run_id_(?P<run_id>[a-zA-Z0-9!$* '
|
||||
'\t\r\n\-]+).json$',
|
||||
RunMetadataEndpoint.as_view(),
|
||||
name='run_metadata_url')
|
||||
)
|
@ -1,28 +0,0 @@
|
||||
# Copyright 2015 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
WSGI config for stackviz project.
|
||||
|
||||
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/1.7/howto/deployment/wsgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "stackviz.settings")
|
||||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
application = get_wsgi_application()
|
2
tox.ini
2
tox.ini
@ -32,4 +32,4 @@ commands = oslo_debug_helper {posargs}
|
||||
show-source = True
|
||||
ignore = E123,E125
|
||||
builtins = _
|
||||
exclude=.venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,build
|
||||
exclude=.venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,build,app,test,build,node_modules
|
||||
|
Loading…
Reference in New Issue
Block a user