2021-05-24 18:15:56 +00:00
|
|
|
|
2021-05-24 20:20:20 +00:00
|
|
|
// Displays an error message to the UI. Any previous message will be erased.
|
2021-05-24 18:15:56 +00:00
|
|
|
function displayError(message) {
|
2021-06-25 18:13:46 +00:00
|
|
|
// Clear the body of all children.
|
|
|
|
while (document.body.firstChild) {
|
|
|
|
document.body.removeChild(document.body.firstChild);
|
|
|
|
}
|
|
|
|
const element = document.createElement("p");
|
|
|
|
element.innerText = "Error: " + message;
|
|
|
|
element.style.color = "red";
|
|
|
|
element.style.fontFamily = "Arial";
|
|
|
|
element.style.fontWeight = "bold";
|
|
|
|
element.style.margin = "5rem";
|
|
|
|
document.body.appendChild(element);
|
2021-05-24 18:15:56 +00:00
|
|
|
}
|
|
|
|
|
2021-05-24 20:20:20 +00:00
|
|
|
// Creates a generator that reads the response body one line at a time.
|
2021-06-25 17:47:06 +00:00
|
|
|
async function* bodyByLinesIterator(response, updateProgress) {
|
|
|
|
const utf8Decoder = new TextDecoder('utf-8');
|
|
|
|
const reader = response.body.getReader();
|
|
|
|
|
|
|
|
const re = /\n|\r|\r\n/gm;
|
|
|
|
let pendingText = "";
|
|
|
|
|
|
|
|
let readerDone = false;
|
|
|
|
while (!readerDone) {
|
|
|
|
// Read a chunk.
|
|
|
|
const { value: chunk, done } = await reader.read();
|
|
|
|
readerDone = done;
|
|
|
|
if (!chunk) {
|
|
|
|
continue;
|
2021-05-24 20:20:20 +00:00
|
|
|
}
|
2021-06-25 17:47:06 +00:00
|
|
|
// Notify the listener of progress.
|
|
|
|
updateProgress(chunk.length);
|
|
|
|
const decodedChunk = utf8Decoder.decode(chunk);
|
|
|
|
|
|
|
|
let startIndex = 0;
|
|
|
|
let result;
|
|
|
|
// Keep processing until there are no more new lines.
|
|
|
|
while ((result = re.exec(decodedChunk)) !== null) {
|
|
|
|
const text = decodedChunk.substring(startIndex, result.index);
|
|
|
|
startIndex = re.lastIndex;
|
|
|
|
|
|
|
|
const line = pendingText + text;
|
|
|
|
pendingText = "";
|
|
|
|
if (line !== "") {
|
|
|
|
yield line;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Any text after the last new line is appended to any pending text.
|
|
|
|
pendingText += decodedChunk.substring(startIndex);
|
|
|
|
}
|
|
|
|
|
|
|
|
// If there is any text remaining, return it.
|
|
|
|
if (pendingText !== "") {
|
|
|
|
yield pendingText;
|
2021-05-24 20:20:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Determines whether `str` matches at least one value in `enumObject`.
|
|
|
|
function isValidEnumValue(enumObject, str) {
|
|
|
|
for (const enumKey in enumObject) {
|
|
|
|
if (enumObject[enumKey] === str) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Enum for test status.
|
|
|
|
const testStatus = {
|
|
|
|
PASSED: "Passed",
|
|
|
|
FAILED: "Failed",
|
|
|
|
SKIPPED: "Skipped"
|
|
|
|
}
|
|
|
|
|
2021-05-25 20:19:03 +00:00
|
|
|
async function loadTestData() {
|
2021-05-24 20:20:20 +00:00
|
|
|
const response = await fetch("data.csv");
|
2021-05-24 18:15:56 +00:00
|
|
|
if (!response.ok) {
|
|
|
|
const responseText = await response.text();
|
2021-05-25 20:19:03 +00:00
|
|
|
throw `Failed to fetch data from GCS bucket. Error: ${responseText}`;
|
2021-05-24 18:15:56 +00:00
|
|
|
}
|
|
|
|
|
2021-06-25 18:05:49 +00:00
|
|
|
const box = document.createElement("div");
|
|
|
|
box.style.width = "100%";
|
|
|
|
const innerBox = document.createElement("div");
|
|
|
|
innerBox.style.margin = "5rem";
|
|
|
|
box.appendChild(innerBox);
|
|
|
|
const progressBarPrompt = document.createElement("h1");
|
|
|
|
progressBarPrompt.style.fontFamily = "Arial";
|
|
|
|
progressBarPrompt.style.textAlign = "center";
|
|
|
|
progressBarPrompt.innerText = "Downloading data...";
|
|
|
|
innerBox.appendChild(progressBarPrompt);
|
|
|
|
const progressBar = document.createElement("progress");
|
|
|
|
progressBar.setAttribute("max", Number(response.headers.get('Content-Length')));
|
|
|
|
progressBar.style.width = "100%";
|
|
|
|
innerBox.appendChild(progressBar);
|
|
|
|
document.body.appendChild(box);
|
|
|
|
|
|
|
|
let readBytes = 0;
|
|
|
|
const lines = bodyByLinesIterator(response, value => {
|
|
|
|
readBytes += value;
|
|
|
|
progressBar.setAttribute("value", readBytes);
|
|
|
|
});
|
2021-05-24 20:20:20 +00:00
|
|
|
// Consume the header to ensure the data has the right number of fields.
|
|
|
|
const header = (await lines.next()).value;
|
2021-06-01 17:17:29 +00:00
|
|
|
if (header.split(",").length != 6) {
|
2021-06-25 18:05:49 +00:00
|
|
|
document.body.removeChild(box);
|
2021-06-01 17:17:29 +00:00
|
|
|
throw `Fetched CSV data contains wrong number of fields. Expected: 6. Actual Header: "${header}"`;
|
2021-05-24 20:20:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const testData = [];
|
2021-06-01 19:15:16 +00:00
|
|
|
let lineData = ["", "", "", "", "", ""];
|
2021-05-24 20:20:20 +00:00
|
|
|
for await (const line of lines) {
|
2021-06-01 19:15:16 +00:00
|
|
|
let splitLine = line.split(",");
|
2021-06-01 17:17:29 +00:00
|
|
|
if (splitLine.length != 6) {
|
|
|
|
console.warn(`Found line with wrong number of fields. Actual: ${splitLine.length} Expected: 6. Line: "${line}"`);
|
2021-05-24 20:20:20 +00:00
|
|
|
continue;
|
|
|
|
}
|
2021-06-01 19:15:16 +00:00
|
|
|
splitLine = splitLine.map((value, index) => value === "" ? lineData[index] : value);
|
|
|
|
lineData = splitLine;
|
2021-05-24 20:20:20 +00:00
|
|
|
if (!isValidEnumValue(testStatus, splitLine[4])) {
|
|
|
|
console.warn(`Invalid test status provided. Actual: ${splitLine[4]} Expected: One of ${Object.values(testStatus).join(", ")}`);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
testData.push({
|
|
|
|
commit: splitLine[0],
|
|
|
|
date: new Date(splitLine[1]),
|
|
|
|
environment: splitLine[2],
|
|
|
|
name: splitLine[3],
|
2021-06-01 17:17:29 +00:00
|
|
|
status: splitLine[4],
|
|
|
|
duration: Number(splitLine[5]),
|
2021-05-24 20:20:20 +00:00
|
|
|
});
|
|
|
|
}
|
2021-06-25 18:05:49 +00:00
|
|
|
document.body.removeChild(box);
|
2021-05-24 20:20:20 +00:00
|
|
|
if (testData.length == 0) {
|
2021-05-25 20:19:03 +00:00
|
|
|
throw "Fetched CSV data is empty or poorly formatted.";
|
2021-05-24 20:20:20 +00:00
|
|
|
}
|
2021-05-25 20:19:03 +00:00
|
|
|
return testData;
|
|
|
|
}
|
2021-05-24 20:20:20 +00:00
|
|
|
|
2021-06-24 18:42:50 +00:00
|
|
|
Array.prototype.sum = function() {
|
|
|
|
return this.reduce((sum, value) => sum + value, 0);
|
|
|
|
};
|
|
|
|
|
2021-05-26 16:36:44 +00:00
|
|
|
// Computes the average of an array of numbers.
|
|
|
|
Array.prototype.average = function () {
|
2021-06-24 18:42:50 +00:00
|
|
|
return this.length === 0 ? 0 : (this.sum() / this.length);
|
2021-05-26 16:36:44 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
// Groups array elements by keys obtained through `keyGetter`.
|
|
|
|
Array.prototype.groupBy = function (keyGetter) {
|
|
|
|
return Array.from(this.reduce((mapCollection, element) => {
|
|
|
|
const key = keyGetter(element);
|
|
|
|
if (mapCollection.has(key)) {
|
|
|
|
mapCollection.get(key).push(element);
|
|
|
|
} else {
|
|
|
|
mapCollection.set(key, [element]);
|
|
|
|
}
|
|
|
|
return mapCollection;
|
|
|
|
}, new Map()).values());
|
|
|
|
};
|
|
|
|
|
2021-06-01 17:18:26 +00:00
|
|
|
// Parse URL search `query` into [{key, value}].
|
|
|
|
function parseUrlQuery(query) {
|
|
|
|
if (query[0] === '?') {
|
|
|
|
query = query.substring(1);
|
|
|
|
}
|
|
|
|
return Object.fromEntries((query === "" ? [] : query.split("&")).map(element => {
|
|
|
|
const keyValue = element.split("=");
|
|
|
|
return [unescape(keyValue[0]), unescape(keyValue[1])];
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
2021-06-24 20:26:52 +00:00
|
|
|
// Takes a set of test runs (all of the same test), and aggregates them into one element per date.
|
|
|
|
function aggregateRuns(testRuns) {
|
|
|
|
return testRuns
|
|
|
|
// Group runs by the date it ran.
|
|
|
|
.groupBy(run => run.date.getTime())
|
|
|
|
// Sort by run date, past to future.
|
|
|
|
.sort((a, b) => a[0].date - b[0].date)
|
|
|
|
// Map each group to all variables need to format the rows.
|
|
|
|
.map(tests => ({
|
|
|
|
date: tests[0].date, // Get one of the dates from the tests (which will all be the same).
|
|
|
|
flakeRate: tests.map(test => test.status === testStatus.FAILED ? 100 : 0).average(), // Compute average of runs where FAILED counts as 100%.
|
|
|
|
duration: tests.map(test => test.duration).average(), // Compute average duration of runs.
|
|
|
|
commitHashes: tests.map(test => ({ // Take all hashes, statuses, and durations of tests in this group.
|
|
|
|
hash: test.commit,
|
|
|
|
status: test.status,
|
|
|
|
duration: test.duration
|
|
|
|
})).groupBy(run => run.hash).map(runsWithSameHash => ({
|
|
|
|
hash: runsWithSameHash[0].hash,
|
|
|
|
failures: runsWithSameHash.map(run => run.status === testStatus.FAILED ? 1 : 0).sum(),
|
|
|
|
runs: runsWithSameHash.length,
|
|
|
|
duration: runsWithSameHash.map(run => run.duration).average(),
|
|
|
|
}))
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
2021-06-24 21:55:05 +00:00
|
|
|
const hashToLink = (hash, environment) => `https://storage.googleapis.com/minikube-builds/logs/master/${hash.substring(0,7)}/${environment}.html`;
|
2021-05-25 23:20:56 +00:00
|
|
|
|
2021-06-24 20:34:23 +00:00
|
|
|
function displayTestAndEnvironmentChart(testData, testName, environmentName) {
|
2021-05-25 23:20:56 +00:00
|
|
|
const data = new google.visualization.DataTable();
|
|
|
|
data.addColumn('date', 'Date');
|
|
|
|
data.addColumn('number', 'Flake Percentage');
|
2021-06-01 18:33:22 +00:00
|
|
|
data.addColumn({ type: 'string', role: 'tooltip', 'p': { 'html': true } });
|
|
|
|
data.addColumn('number', 'Duration');
|
|
|
|
data.addColumn({ type: 'string', role: 'tooltip', 'p': { 'html': true } });
|
2021-05-25 23:20:56 +00:00
|
|
|
|
2021-06-24 20:26:52 +00:00
|
|
|
const testRuns = testData
|
2021-05-26 16:36:44 +00:00
|
|
|
// Filter to only contain unskipped runs of the requested test and requested environment.
|
2021-06-24 20:34:23 +00:00
|
|
|
.filter(test => test.name === testName && test.environment === environmentName && test.status !== testStatus.SKIPPED);
|
2021-05-25 23:20:56 +00:00
|
|
|
|
|
|
|
data.addRows(
|
2021-06-24 20:26:52 +00:00
|
|
|
aggregateRuns(testRuns)
|
2021-05-25 23:20:56 +00:00
|
|
|
.map(groupData => [
|
|
|
|
groupData.date,
|
|
|
|
groupData.flakeRate,
|
2021-06-24 18:57:07 +00:00
|
|
|
`<div style="padding: 1rem; font-family: 'Arial'; font-size: 14">
|
2021-05-25 23:20:56 +00:00
|
|
|
<b>${groupData.date.toString()}</b><br>
|
|
|
|
<b>Flake Percentage:</b> ${groupData.flakeRate.toFixed(2)}%<br>
|
|
|
|
<b>Hashes:</b><br>
|
2021-06-24 20:34:23 +00:00
|
|
|
${groupData.commitHashes.map(({ hash, failures, runs }) => ` - <a href="${hashToLink(hash, environmentName)}">${hash}</a> (Failures: ${failures}/${runs})`).join("<br>")}
|
2021-06-01 18:33:22 +00:00
|
|
|
</div>`,
|
|
|
|
groupData.duration,
|
2021-06-24 18:57:07 +00:00
|
|
|
`<div style="padding: 1rem; font-family: 'Arial'; font-size: 14">
|
2021-06-01 18:33:22 +00:00
|
|
|
<b>${groupData.date.toString()}</b><br>
|
|
|
|
<b>Average Duration:</b> ${groupData.duration.toFixed(2)}s<br>
|
|
|
|
<b>Hashes:</b><br>
|
2021-06-24 20:34:23 +00:00
|
|
|
${groupData.commitHashes.map(({ hash, runs, duration }) => ` - <a href="${hashToLink(hash, environmentName)}">${hash}</a> (Average of ${runs}: ${duration.toFixed(2)}s)`).join("<br>")}
|
2021-06-01 18:33:22 +00:00
|
|
|
</div>`,
|
2021-05-25 23:20:56 +00:00
|
|
|
])
|
|
|
|
);
|
|
|
|
|
|
|
|
const options = {
|
2021-06-24 20:34:23 +00:00
|
|
|
title: `Flake rate and duration by day of ${testName} on ${environmentName}`,
|
2021-06-01 18:33:22 +00:00
|
|
|
width: window.innerWidth,
|
|
|
|
height: window.innerHeight,
|
2021-05-25 23:20:56 +00:00
|
|
|
pointSize: 10,
|
|
|
|
pointShape: "circle",
|
2021-06-01 18:33:22 +00:00
|
|
|
series: {
|
|
|
|
0: { targetAxisIndex: 0 },
|
|
|
|
1: { targetAxisIndex: 1 },
|
|
|
|
},
|
|
|
|
vAxes: {
|
|
|
|
0: { title: "Flake rate", minValue: 0, maxValue: 100 },
|
|
|
|
1: { title: "Duration (seconds)" },
|
|
|
|
},
|
2021-06-17 16:41:40 +00:00
|
|
|
colors: ['#dc3912', '#3366cc'],
|
2021-05-25 23:20:56 +00:00
|
|
|
tooltip: { trigger: "selection", isHtml: true }
|
|
|
|
};
|
|
|
|
const chart = new google.visualization.LineChart(document.getElementById('chart_div'));
|
|
|
|
chart.draw(data, options);
|
2021-05-24 18:15:56 +00:00
|
|
|
}
|
|
|
|
|
2021-07-01 19:01:09 +00:00
|
|
|
function createRecentFlakePercentageTable(recentFlakePercentage, previousFlakePercentageMap, environmentName) {
|
2021-06-24 23:24:22 +00:00
|
|
|
const createCell = (elementType, text) => {
|
|
|
|
const element = document.createElement(elementType);
|
|
|
|
element.innerHTML = text;
|
|
|
|
return element;
|
|
|
|
}
|
|
|
|
|
|
|
|
const table = document.createElement("table");
|
|
|
|
const tableHeaderRow = document.createElement("tr");
|
2021-07-01 18:47:40 +00:00
|
|
|
tableHeaderRow.appendChild(createCell("th", "Rank"));
|
2021-06-24 23:24:22 +00:00
|
|
|
tableHeaderRow.appendChild(createCell("th", "Test Name")).style.textAlign = "left";
|
|
|
|
tableHeaderRow.appendChild(createCell("th", "Recent Flake Percentage"));
|
2021-07-01 19:01:09 +00:00
|
|
|
tableHeaderRow.appendChild(createCell("th", "Growth (since last 15 days)"));
|
2021-06-24 23:24:22 +00:00
|
|
|
table.appendChild(tableHeaderRow);
|
2021-07-01 18:47:40 +00:00
|
|
|
for (let i = 0; i < recentFlakePercentage.length; i++) {
|
|
|
|
const {testName, flakeRate} = recentFlakePercentage[i];
|
2021-06-24 23:24:22 +00:00
|
|
|
const row = document.createElement("tr");
|
2021-07-01 18:47:40 +00:00
|
|
|
row.appendChild(createCell("td", "" + (i + 1))).style.textAlign = "center";
|
2021-06-24 23:29:36 +00:00
|
|
|
row.appendChild(createCell("td", `<a href="${window.location.pathname}?env=${environmentName}&test=${testName}">${testName}</a>`));
|
2021-06-24 23:24:22 +00:00
|
|
|
row.appendChild(createCell("td", `${flakeRate.toFixed(2)}%`)).style.textAlign = "right";
|
2021-07-01 19:01:09 +00:00
|
|
|
const growth = previousFlakePercentageMap.has(testName) ?
|
|
|
|
flakeRate - previousFlakePercentageMap.get(testName) : 0;
|
|
|
|
row.appendChild(createCell("td", `<span style="color: ${growth === 0 ? "black" : (growth > 0 ? "red" : "green")}">${growth > 0 ? '+' + growth.toFixed(2) : growth.toFixed(2)}%</span>`));
|
2021-06-24 23:24:22 +00:00
|
|
|
table.appendChild(row);
|
|
|
|
}
|
|
|
|
return table;
|
|
|
|
}
|
|
|
|
|
2021-06-24 21:55:05 +00:00
|
|
|
function displayEnvironmentChart(testData, environmentName) {
|
2021-06-24 22:49:00 +00:00
|
|
|
// Number of days to use to look for "flaky-est" tests.
|
|
|
|
const dateRange = 15;
|
|
|
|
// Number of tests to display in chart.
|
|
|
|
const topFlakes = 10;
|
|
|
|
|
2021-06-24 21:55:05 +00:00
|
|
|
const testRuns = testData
|
|
|
|
// Filter to only contain unskipped runs of the requested test and requested environment.
|
|
|
|
.filter(test => test.environment === environmentName && test.status !== testStatus.SKIPPED)
|
|
|
|
.groupBy(test => test.name);
|
|
|
|
|
|
|
|
const aggregatedRuns = new Map(testRuns.map(test => [
|
|
|
|
test[0].name,
|
|
|
|
new Map(aggregateRuns(test)
|
|
|
|
.map(runDate => [ runDate.date.getTime(), runDate ]))]));
|
|
|
|
const uniqueDates = new Set();
|
|
|
|
for (const [_, runDateMap] of aggregatedRuns) {
|
|
|
|
for (const [dateTime, _] of runDateMap) {
|
|
|
|
uniqueDates.add(dateTime);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const orderedDates = Array.from(uniqueDates).sort();
|
2021-06-24 22:49:00 +00:00
|
|
|
const recentDates = orderedDates.slice(-dateRange);
|
2021-07-01 19:01:09 +00:00
|
|
|
const previousDates = orderedDates.slice(-2 * dateRange, -dateRange);
|
|
|
|
|
|
|
|
const computeFlakePercentage = (runs, dates) => Array.from(runs).map(([testName, data]) => {
|
|
|
|
const {flakeCount, totalCount} = dates.map(date => {
|
2021-06-24 22:49:00 +00:00
|
|
|
const dateInfo = data.get(date);
|
|
|
|
return dateInfo === undefined ? null : {
|
|
|
|
flakeRate: dateInfo.flakeRate,
|
|
|
|
runs: dateInfo.commitHashes.length
|
|
|
|
};
|
|
|
|
}).filter(dateInfo => dateInfo != null)
|
|
|
|
.reduce(({flakeCount, totalCount}, {flakeRate, runs}) => ({
|
|
|
|
flakeCount: flakeRate * runs + flakeCount,
|
|
|
|
totalCount: runs + totalCount
|
|
|
|
}), {flakeCount: 0, totalCount: 0});
|
|
|
|
return {
|
|
|
|
testName,
|
|
|
|
flakeRate: totalCount === 0 ? 0 : flakeCount / totalCount,
|
|
|
|
};
|
2021-07-01 19:01:09 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
const recentFlakePercentage = computeFlakePercentage(aggregatedRuns, recentDates)
|
|
|
|
.sort((a, b) => b.flakeRate - a.flakeRate);
|
|
|
|
const previousFlakePercentageMap = new Map(
|
|
|
|
computeFlakePercentage(aggregatedRuns, previousDates)
|
|
|
|
.map(({testName, flakeRate}) => [testName, flakeRate]));
|
2021-06-24 22:49:00 +00:00
|
|
|
|
|
|
|
const recentTopFlakes = recentFlakePercentage
|
|
|
|
.slice(0, topFlakes)
|
|
|
|
.map(({testName}) => testName);
|
|
|
|
|
|
|
|
const data = new google.visualization.DataTable();
|
|
|
|
data.addColumn('date', 'Date');
|
|
|
|
for (const name of recentTopFlakes) {
|
|
|
|
data.addColumn('number', `Flake Percentage - ${name}`);
|
|
|
|
data.addColumn({ type: 'string', role: 'tooltip', 'p': { 'html': true } });
|
|
|
|
}
|
2021-06-24 21:55:05 +00:00
|
|
|
data.addRows(
|
2021-06-24 22:49:00 +00:00
|
|
|
orderedDates.map(dateTime => [new Date(dateTime)].concat(recentTopFlakes.map(name => {
|
2021-06-24 21:55:05 +00:00
|
|
|
const data = aggregatedRuns.get(name).get(dateTime);
|
|
|
|
return data !== undefined ? [
|
|
|
|
data.flakeRate,
|
|
|
|
`<div style="padding: 1rem; font-family: 'Arial'; font-size: 14">
|
2021-06-25 16:43:47 +00:00
|
|
|
<b style="display: block">${name}</b><br>
|
2021-06-24 21:55:05 +00:00
|
|
|
<b>${data.date.toString()}</b><br>
|
|
|
|
<b>Flake Percentage:</b> ${data.flakeRate.toFixed(2)}%<br>
|
|
|
|
<b>Hashes:</b><br>
|
|
|
|
${data.commitHashes.map(({ hash, failures, runs }) => ` - <a href="${hashToLink(hash, environmentName)}">${hash}</a> (Failures: ${failures}/${runs})`).join("<br>")}
|
|
|
|
</div>`
|
|
|
|
] : [null, null];
|
|
|
|
})).flat())
|
|
|
|
);
|
|
|
|
const options = {
|
2021-06-24 22:49:00 +00:00
|
|
|
title: `Flake rate by day of top ${topFlakes} of recent test flakiness (past ${dateRange} days) on ${environmentName}`,
|
2021-06-24 21:55:05 +00:00
|
|
|
width: window.innerWidth,
|
|
|
|
height: window.innerHeight,
|
|
|
|
pointSize: 10,
|
|
|
|
pointShape: "circle",
|
|
|
|
vAxes: {
|
|
|
|
0: { title: "Flake rate", minValue: 0, maxValue: 100 },
|
|
|
|
},
|
|
|
|
tooltip: { trigger: "selection", isHtml: true }
|
|
|
|
};
|
|
|
|
const chart = new google.visualization.LineChart(document.getElementById('chart_div'));
|
|
|
|
chart.draw(data, options);
|
2021-06-24 23:24:22 +00:00
|
|
|
|
2021-07-01 19:01:09 +00:00
|
|
|
document.body.appendChild(createRecentFlakePercentageTable(recentFlakePercentage, previousFlakePercentageMap, environmentName));
|
2021-06-24 21:55:05 +00:00
|
|
|
}
|
|
|
|
|
2021-06-24 20:34:23 +00:00
|
|
|
async function init() {
|
|
|
|
google.charts.load('current', { 'packages': ['corechart'] });
|
|
|
|
let testData;
|
|
|
|
try {
|
|
|
|
// Wait for Google Charts to load, and for test data to load.
|
|
|
|
// Only store the test data (at index 1) into `testData`.
|
|
|
|
testData = (await Promise.all([
|
|
|
|
new Promise(resolve => google.charts.setOnLoadCallback(resolve)),
|
|
|
|
loadTestData()
|
|
|
|
]))[1];
|
|
|
|
} catch (err) {
|
|
|
|
displayError(err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const query = parseUrlQuery(window.location.search);
|
2021-06-24 21:55:05 +00:00
|
|
|
const desiredTest = query.test, desiredEnvironment = query.env || "";
|
2021-06-24 20:34:23 +00:00
|
|
|
|
2021-06-24 21:55:05 +00:00
|
|
|
if (desiredTest === undefined) {
|
|
|
|
displayEnvironmentChart(testData, desiredEnvironment);
|
|
|
|
} else {
|
|
|
|
displayTestAndEnvironmentChart(testData, desiredTest, desiredEnvironment);
|
|
|
|
}
|
2021-06-24 20:34:23 +00:00
|
|
|
}
|
|
|
|
|
2021-05-24 18:15:56 +00:00
|
|
|
init();
|