Skip to content

frontend: Support suite & platform filters #143

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Sep 6, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 23 additions & 26 deletions frontend/src/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<h2>
<nav>
{{#navbar}}
<a href="#{{ path }}">{{ name }}</a>
<a href="{{ route }}">{{ name }}</a>
{{/navbar}}
</nav>
<span> : {{ total }} files</span>
Expand Down Expand Up @@ -47,7 +47,7 @@ <h2>
<h2>
<nav>
{{#navbar}}
<a href="#{{ revision }}:{{ path }}">{{ name }}</a>
<a href="{{ route }}">{{ name }}</a>
{{/navbar}}
</nav>
</h2>
Expand All @@ -73,7 +73,7 @@ <h2>
<h2>
<nav>
{{#navbar}}
<a href="#{{ revision }}:{{ path }}">{{ name }}</a>
<a href="{{ route }}">{{ name }}</a>
{{/navbar}}
</nav>
<span> : {{ files.length }} files</span>
Expand All @@ -88,7 +88,7 @@ <h2>

{{#files}}
<div class="row">
<span class="filename"><a href="#{{ revision }}:{{ path }}">{{ file_name }}</a></span>
<span class="filename"><a href="{{ route }}">{{ file_name }}</a></span>
<span>{{ children }}</span>
<span>{{ coveragePercent }} %</span>
</div>
Expand All @@ -102,29 +102,26 @@ <h2>
<span>Revision <samp>{{ revision }}</samp> from {{ date }}</span>
</script>

<header>
<div id="menu_browser">
<a href="#zero:">View the zero coverage report</a>
&bull;
<input type="text" id="revision" placeholder="Mercurial revision" </input>
</div>
<script id="menu_browser" type="x-tmpl-mustache">
<a href="#view=zero&third_party=on&cpp=on&js=on=java=on&rust=on">View the zero coverage report</a>
&bull;
<input type="text" name="revision" placeholder="Mercurial revision" value="{{revision}}"></input>
</script>

<div id="menu_zero">
<input type="checkbox" name="third_party" id="third_party" checked="checked"><label for="third_party">Show third-party files</label>
<input type="checkbox" name="headers" id="headers"><label for="headers">Show headers</label>
<input type="checkbox" name="completely_uncovered" id="completely_uncovered"><label for="completely_uncovered">Show completely uncovered files only</label>
<input type="checkbox" name="cpp" id="cpp" checked="checked"><label for="cpp">C/C++</label>
<input type="checkbox" name="js" id="js" checked="checked"><label for="js">JavaScript</label>
<input type="checkbox" name="java" id="java" checked="checked"><label for="java">Java</label>
<input type="checkbox" name="rust" id="rust" checked="checked"><label for="rust">Rust</label>
<select id="last_push">
<option value="all">All</option>
<option value="one_year">0 &lt; 1 year</option>
<option value="two_years">1 &lt; 2 years</option>
<option value="older_than_two_years">Older than 2 years</option>
</select>
</div>
</header>
<script id="menu_zero" type="x-tmpl-mustache">
{{#filters}}
<input type="checkbox" name="{{ key }}" id="{{ key }}" {{#checked}}checked="checked"{{/checked}}>
<label for="{{ key }}">{{ message }}</label>
{{/filters}}

<select name="last_push" id="last_push">
{{#last_pushes}}
<option {{#selected}}selected="selected"{{/selected}} value="{{value}}">{{message}}</option>
{{/last_pushes}}
</select>
</script>

<header id="menu"></header>

<main id="main">
<div id="message" class="message loading">Loading...</div>
Expand Down
55 changes: 35 additions & 20 deletions frontend/src/common.js
Original file line number Diff line number Diff line change
@@ -1,39 +1,34 @@
import Mustache from 'mustache';
import { buildRoute, readRoute, updateRoute } from './route.js';

export const REV_LATEST = 'latest';

function assert(condition, message) {
if (!condition) {
throw new Error(message || "Assertion failed");
}
}

function domContentLoaded() {
return new Promise(resolve => document.addEventListener('DOMContentLoaded', resolve));
}
export const DOM_READY = domContentLoaded();

export async function main(load, display, opts) {
// Immediately listen to DOM event

export async function main(load, display) {
// Load initial data before DOM is available
let data = await load();

// Wait for DOM to be ready before displaying
await DOM_READY;
await display(data);
monitor_options();

// Full workflow, loading then displaying data
// used for following updates
let full = async function() {
let data = await load();
await display(data);
monitor_options();
};
monitor_options(opts, full);

// React to url changes
window.onhashchange = full;
}


// Coverage retrieval.

const COVERAGE_BACKEND_HOST = process.env.BACKEND_URL;
Expand Down Expand Up @@ -134,18 +129,38 @@ export async function get_zero_coverage_data() {
// Option handling.

function is_enabled(opt) {
let elem = document.getElementById(opt);
return elem.checked;
let route = readRoute();
return route[opt] === 'on';
}

function monitor_options(opts, callback) {
for (let opt of opts) {
let elem = document.getElementById(opt);
elem.onchange = callback;
function monitor_options() {
// Monitor input & select changes
let fields = document.querySelectorAll('input, select');
for(let field of fields) {
if (field.type == 'text') {
// React on enter
field.onkeydown = async (evt) => {
if(evt.keyCode === 13) {
let params = {};
params[evt.target.name] = evt.target.value;
updateRoute(params);
}
}
} else {
// React on change
field.onchange = async (evt) => {
let value = evt.target.value;
if (evt.target.type == 'checkbox') {
value = evt.target.checked ? 'on' : 'off';
}
let params = {};
params[evt.target.name] = value;
updateRoute(params);
}
}
}
}


// hgmo.

export async function get_source(file) {
Expand Down Expand Up @@ -267,14 +282,14 @@ export function build_navbar(path, revision) {
let links = [
{
'name': 'mozilla-central',
'path': '',
'route': buildRoute({path: '', revision})
}
];
return links.concat(path.split('/').map(file => {
base += (base ? '/' : '') + file;
return {
'name': file,
'path': base,
'route': buildRoute({path: base, revision})
};
}));
}
Expand Down
108 changes: 43 additions & 65 deletions frontend/src/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {REV_LATEST, DOM_READY, main, show, hide, message, get_path_coverage, get_history, get_zero_coverage_data, build_navbar, render, get_source} from './common.js';
import {zero_coverage_display} from './zero_coverage_report.js';
import {buildRoute, readRoute, updateRoute} from './route.js';
import {zero_coverage_display, zero_coverage_menu} from './zero_coverage_report.js';
import './style.css';
import Prism from 'prismjs';
import Chartist from 'chartist';
Expand All @@ -8,6 +9,14 @@ import 'chartist/dist/chartist.css';
const VIEW_ZERO_COVERAGE = 'zero';
const VIEW_BROWSER = 'browser';


function browser_menu(revision) {
let context = {
revision,
};
render('menu_browser', context, 'menu');
}

async function graphHistory(history, path) {
if (history === null) {
message('warning', `No history data for ${path}`);
Expand Down Expand Up @@ -53,7 +62,7 @@ async function graphHistory(history, path) {
// Load revision from graph when a point is clicked
let revision = history[evt.index].changeset;
evt.element._node.onclick = function(){
updateHash(revision, path);
updateRoute({revision});
};

// Display revision from graph when a point is overed
Expand All @@ -71,7 +80,12 @@ async function graphHistory(history, path) {
async function showDirectory(dir, revision, files) {
let context = {
navbar: build_navbar(dir, revision),
files: files,
files: files.map(file => {
file.route = buildRoute({
path: file.path
});
return file;
}),
revision: revision || REV_LATEST,
file_name: function(){
// Build filename relative to current dir
Expand Down Expand Up @@ -125,49 +139,29 @@ async function showFile(file, revision) {
Prism.highlightAll(output);
}

function readHash() {
// Reads changeset & path from current URL hash
let hash = window.location.hash.substring(1);
let pos = hash.indexOf(':');
if (pos === -1) {
return ['', ''];
}
return [
hash.substring(0, pos),
hash.substring(pos+1),
]
}

function updateHash(newChangeset, newPath) {
// Set the URL hash with both changeset & path
let [changeset, path] = readHash();
changeset = newChangeset || changeset || REV_LATEST;
path = newPath || path || '';
window.location.hash = '#' + changeset + ':' + path;
}

async function load() {
let [revision, path] = readHash();
let route = readRoute();

// Reset display, dom-safe
hide('history');
hide('output');
message('loading', 'Loading coverage data for ' + (path || 'mozilla-central') + ' @ ' + (revision || REV_LATEST));
message('loading', 'Loading coverage data for ' + (route.path || 'mozilla-central') + ' @ ' + (route.revision || REV_LATEST));

// Load only zero coverage for that specific view
if (revision === VIEW_ZERO_COVERAGE) {
if (route.view === VIEW_ZERO_COVERAGE) {
let zero_coverage = await get_zero_coverage_data();
return {
view: VIEW_ZERO_COVERAGE,
path,
path: route.path,
zero_coverage,
route,
}
}

try {
var [coverage, history] = await Promise.all([
get_path_coverage(path, revision),
get_history(path),
get_path_coverage(route.path, route.revision),
get_history(route.path),
]);
} catch (err) {
console.warn('Failed to load coverage', err);
Expand All @@ -178,54 +172,38 @@ async function load() {

return {
view: VIEW_BROWSER,
path,
revision,
path: route.path,
revision: route.revision,
route,
coverage,
history,
};
}

async function display(data) {

// Toggle menu per views
if (data.view === VIEW_BROWSER) {
show('menu_browser');
hide('menu_zero');
} else if (data.view === VIEW_ZERO_COVERAGE) {
show('menu_zero');
hide('menu_browser');
} else {
message('error', 'Invalid view : ' + data.view);
}

// Revision input management
const revision = document.getElementById('revision');
revision.onkeydown = async function(evt){
if(evt.keyCode === 13) {
updateHash(data.revision.value);
}
};

// Also update the revision element
if (data.revision && data.revision != REV_LATEST) {
let input = document.getElementById('revision');
input.value = data.revision;
}

if (data.view === VIEW_ZERO_COVERAGE ) {
await zero_coverage_menu(data.route);
await zero_coverage_display(data.zero_coverage, data.path);

} else if (data.view === VIEW_BROWSER && data.coverage.type === 'directory') {
hide('message');
await graphHistory(data.history, data.path);
await showDirectory(data.path, data.revision, data.coverage.children);
} else if (data.view === VIEW_BROWSER) {
browser_menu(data.revision);

if (data.coverage.type === 'directory') {
hide('message');
await graphHistory(data.history, data.path);
await showDirectory(data.path, data.revision, data.coverage.children);

} else if (data.view === VIEW_BROWSER && data.coverage.type === 'file') {
await showFile(data.coverage, data.revision);
} else if (data.coverage.type === 'file') {
await showFile(data.coverage, data.revision);

} else {
message('error', 'Invalid file type: ' + data.coverate.type);
}

} else {
message('error', 'Invalid file type: ' + data.coverage.type);
message('error', 'Invalid view : ' + data.view);
}
}

main(load, display, ['third_party', 'headers', 'completely_uncovered', 'cpp', 'js', 'java', 'rust', 'last_push'])
main(load, display);
Loading