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;
|
||||
}
|
||||