# Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
#
# Use of this source code is governed by a BSD-style license
# that can be found in the LICENSE file in the root of the source
# tree. An additional intellectual property rights grant can be found
# in the file PATENTS. All contributing project authors may
# be found in the AUTHORS file in the root of the source tree.
import functools
import hashlib
import logging
import os
import re
import sys
try:
import csscompressor
except ImportError:
logging.critical(
'Cannot import the third-party Python package csscompressor')
sys.exit(1)
try:
import jsmin
except ImportError:
logging.critical('Cannot import the third-party Python package jsmin')
sys.exit(1)
class HtmlExport(object):
"""HTML exporter class for APM quality scores."""
_NEW_LINE = '\n'
# CSS and JS file paths.
_PATH = os.path.dirname(os.path.realpath(__file__))
_CSS_FILEPATH = os.path.join(_PATH, 'results.css')
_CSS_MINIFIED = True
_JS_FILEPATH = os.path.join(_PATH, 'results.js')
_JS_MINIFIED = True
def __init__(self, output_filepath):
self._scores_data_frame = None
self._output_filepath = output_filepath
def Export(self, scores_data_frame):
"""Exports scores into an HTML file.
Args:
scores_data_frame: DataFrame instance.
"""
self._scores_data_frame = scores_data_frame
html = [
'',
self._BuildHeader(),
(''), '
',
self._BuildBody(), '', ''
]
self._Save(self._output_filepath, self._NEW_LINE.join(html))
def _BuildHeader(self):
"""Builds the section of the HTML file.
The header contains the page title and either embedded or linked CSS and JS
files.
Returns:
A string with ... HTML.
"""
html = ['', 'Results']
# Add Material Design hosted libs.
html.append('')
html.append(
'')
html.append(
'')
html.append('')
# Embed custom JavaScript and CSS files.
html.append('')
html.append('')
html.append('')
return self._NEW_LINE.join(html)
def _BuildBody(self):
"""Builds the content of the section."""
score_names = self._scores_data_frame[
'eval_score_name'].drop_duplicates().values.tolist()
html = [
('
')
# Placeholder for the audio inspector.
html.append('')
return self._NEW_LINE.join(html)
def _BuildScoreStatsInspectorTableCell(self, score_tuple, anchor_data):
"""Builds the content of a cell of a score stats inspector."""
anchor = '&'.join(anchor_data)
html = [('
{}
'
'').format(score_tuple.score, anchor)]
# Add all the available file paths as hidden data.
for field_name in score_tuple.keys():
if field_name.endswith('_filepath'):
html.append(
''.format(
field_name, score_tuple[field_name]))
return self._NEW_LINE.join(html)
def _SliceDataForScoreTableCell(self, score_name, apm_config,
test_data_gen, test_data_gen_params):
"""Slices `self._scores_data_frame` to extract the data for a tab."""
masks = []
masks.append(self._scores_data_frame.eval_score_name == score_name)
masks.append(self._scores_data_frame.apm_config == apm_config)
masks.append(self._scores_data_frame.test_data_gen == test_data_gen)
masks.append(self._scores_data_frame.test_data_gen_params ==
test_data_gen_params)
mask = functools.reduce((lambda i1, i2: i1 & i2), masks)
del masks
return self._scores_data_frame[mask]
@classmethod
def _SliceDataForScoreStatsTableCell(cls, scores, capture, render,
echo_simulator):
"""Slices `scores` to extract the data for a tab."""
masks = []
masks.append(scores.capture == capture)
masks.append(scores.render == render)
masks.append(scores.echo_simulator == echo_simulator)
mask = functools.reduce((lambda i1, i2: i1 & i2), masks)
del masks
sliced_data = scores[mask]
assert len(sliced_data) == 1, 'single score is expected'
return sliced_data.iloc[0]
@classmethod
def _FindUniqueTuples(cls, data_frame, fields):
"""Slices `data_frame` to a list of fields and finds unique tuples."""
return data_frame[fields].drop_duplicates().values.tolist()
@classmethod
def _ComputeScoreStats(cls, data_frame):
"""Computes score stats."""
scores = data_frame['score']
return {
'count': scores.count(),
'min': scores.min(),
'max': scores.max(),
'mean': scores.mean(),
'std dev': scores.std(),
}
@classmethod
def _ScoreStatsInspectorDialogId(cls, score_name, apm_config,
test_data_gen, test_data_gen_params):
"""Assigns a unique name to a dialog."""
return 'score-stats-dialog-' + hashlib.md5(
'score-stats-inspector-{}-{}-{}-{}'.format(
score_name, apm_config, test_data_gen,
test_data_gen_params).encode('utf-8')).hexdigest()
@classmethod
def _Save(cls, output_filepath, html):
"""Writes the HTML file.
Args:
output_filepath: output file path.
html: string with the HTML content.
"""
with open(output_filepath, 'w') as f:
f.write(html)
@classmethod
def _FormatName(cls, name):
"""Formats a name.
Args:
name: a string.
Returns:
A copy of name in which underscores and dashes are replaced with a space.
"""
return re.sub(r'[_\-]', ' ', name)