This commit is contained in:
Marco Castelluccio 2020-11-21 00:24:02 +01:00 коммит произвёл GitHub
Родитель a670b89e47
Коммит be258faf40
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
19 изменённых файлов: 14669 добавлений и 38 удалений

3
.gitignore поставляемый
Просмотреть файл

@ -44,8 +44,9 @@ share/python-wheels/
*.egg
MANIFEST
cache/
node_modules/
# Logs
*.log
# Desktop Service Store
*.DS_Store
*.DS_Store

Просмотреть файл

@ -208,12 +208,41 @@ tasks:
owner: mcastelluccio@mozilla.com
source: ${repository}/raw/${head_rev}/.taskcluster.yml
- taskId: {$eval: as_slugid("frontend_build")}
created: {$fromNow: ''}
deadline: {$fromNow: '1 hour'}
provisionerId: proj-bugbug
workerType: batch
payload:
maxRunTime: 3600
image: node
command:
- "/bin/sh"
- "-lcxe"
- "git clone --quiet ${repository} /bugbug &&
cd /bugbug &&
git -c advice.detachedHead=false checkout ${head_rev} &&
cd ui/changes &&
npm install --no-progress &&
npm run release"
artifacts:
public/frontend:
expires: {$fromNow: '2 weeks'}
path: /bugbug/ui/changes/dist
type: directory
metadata:
name: bugbug ui build
description: bugbug ui build
owner: mcastelluccio@mozilla.com
source: ${repository}/raw/${head_rev}/.taskcluster.yml
- $if: 'tasks_for == "github-push" && head_branch[:10] == "refs/tags/"'
then:
dependencies:
- {$eval: as_slugid("lint_task")}
- {$eval: as_slugid("tests_task")}
- {$eval: as_slugid("http_tests_task")}
- {$eval: as_slugid("frontend_build")}
- {$eval: as_slugid("packaging_test_task")}
- {$eval: as_slugid("version_check_task")}
- {$eval: as_slugid("integration_test")}
@ -250,6 +279,7 @@ tasks:
- {$eval: as_slugid("version_check_task")}
- {$eval: as_slugid("tests_task")}
- {$eval: as_slugid("http_tests_task")}
- {$eval: as_slugid("frontend_build")}
- {$eval: as_slugid("packaging_test_task")}
scopes:
- secrets:get:project/bugbug/deploy

Просмотреть файл

@ -1,40 +1,71 @@
version: 1
tasks:
- ID: landings-risk-report-generator
created: {$fromNow: ''}
deadline: {$fromNow: '2 hours'}
expires: {$fromNow: '2 weeks'}
provisionerId: proj-bugbug
workerType: compute-small
payload:
features:
taskclusterProxy:
true
maxRunTime: 7200
image: mozilla/bugbug-commit-retrieval
env:
TC_SECRET_ID: project/bugbug/production
command:
- bugbug-generate-landings-risk-report
- /cache/mozilla-central
- --days=365
- --meta-bugs
$let:
repository: https://github.com/mozilla/bugbug
in:
- ID: landings-risk-report-generator
created: {$fromNow: ''}
deadline: {$fromNow: '2 hours'}
expires: {$fromNow: '2 weeks'}
provisionerId: proj-bugbug
workerType: compute-small
payload:
features:
taskclusterProxy:
true
maxRunTime: 7200
image: mozilla/bugbug-commit-retrieval
env:
TC_SECRET_ID: project/bugbug/production
command:
- bugbug-generate-landings-risk-report
- /cache/mozilla-central
- --days=365
- --meta-bugs
artifacts:
public/landings_by_date.json:
path: /landings_by_date.json
type: file
public/component_connections.json:
path: /component_connections.json
type: file
cache:
bugbug-mercurial-repository: /cache
routes:
- notify.email.release-mgmt-analysis@mozilla.com.on-failed
- notify.irc-channel.#bugbug.on-failed
- index.project.bugbug.landings_risk_report.latest
metadata:
name: BugBug landings risk report
description: BugBug landings risk report
owner: release-mgmt-analysis@mozilla.com
source: https://github.com/mozilla/bugbug/raw/master/infra/landings-pipeline.yml
artifacts:
public/landings_by_date.json:
path: /landings_by_date.json
type: file
public/component_connections.json:
path: /component_connections.json
type: file
cache:
bugbug-mercurial-repository: /cache
routes:
- notify.email.release-mgmt-analysis@mozilla.com.on-failed
- notify.irc-channel.#bugbug.on-failed
- index.project.bugbug.landings_risk_report.latest
metadata:
name: BugBug landings risk report
description: BugBug landings risk report
owner: release-mgmt-analysis@mozilla.com
source: https://github.com/mozilla/bugbug/raw/${version}/infra/landings-pipeline.yml
- ID: frontend-build
created: {$fromNow: ''}
deadline: {$fromNow: '1 hour'}
expires: {$fromNow: '2 weeks'}
provisionerId: proj-bugbug
workerType: batch
payload:
maxRunTime: 3600
image: node
command:
- "/bin/sh"
- "-lcxe"
- "git clone --quiet ${repository} /bugbug &&
cd /bugbug &&
git -c advice.detachedHead=false checkout ${version} &&
cd ui/changes &&
npm install --no-progress &&
npm run release"
artifacts:
public/frontend:
path: /bugbug/ui/changes/dist
type: directory
metadata:
name: bugbug ui build
description: bugbug ui build
owner: mcastelluccio@mozilla.com
source: https://github.com/mozilla/bugbug/raw/${version}/infra/landings-pipeline.yml

16
ui/changes/.eslintrc.yml Normal file
Просмотреть файл

@ -0,0 +1,16 @@
env:
browser: true
es6: true
plugins:
- prettier
- mozilla
extends:
- standard
- prettier
- plugin:mozilla/recommended
parserOptions:
ecmaVersion: 2018
sourceType: module
rules:
max-len: off
prettier/prettier: "error"

12436
ui/changes/package-lock.json сгенерированный Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

32
ui/changes/package.json Normal file
Просмотреть файл

@ -0,0 +1,32 @@
{
"name": "changes",
"version": "1.0.0",
"description": "To update Temporal polyfill:",
"private": true,
"scripts": {
"build": "webpack --config webpack.dev.js",
"release": "webpack --config webpack.prod.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"clean-webpack-plugin": "^3.0.0",
"copy-webpack-plugin": "^6.3.2",
"css-minimizer-webpack-plugin": "^1.1.5",
"eslint-config-standard": "^16.0.2",
"eslint-plugin-mozilla": "^2.9.1",
"eslint-webpack-plugin": "^2.4.0",
"html-webpack-plugin": "^4.5.0",
"prettier": "^1.19.1",
"webpack": "^5.6.0",
"webpack-cli": "^4.2.0",
"webpack-merge": "^5.4.0"
},
"dependencies": {
"apexcharts": "^3.6.12",
"localforage": "^1.9.0",
"proposal-temporal": "^0.6.0"
}
}

203
ui/changes/src/common.js Normal file
Просмотреть файл

@ -0,0 +1,203 @@
import { Temporal } from "proposal-temporal/lib/index.mjs";
import localForage from "localforage";
// let METABUGS_URL =
// "https://bugzilla.mozilla.org/rest/bug?include_fields=id,summary,status&keywords=feature-testing-meta%2C%20&keywords_type=allwords";
let LANDINGS_URL =
"https://community-tc.services.mozilla.com/api/index/v1/task/project.bugbug.landings_risk_report.latest/artifacts/public/landings_by_date.json";
function getCSSVariableValue(name) {
return getComputedStyle(document.documentElement)
.getPropertyValue(name)
.trim();
}
const EXPIRE_CACHE = (() => {
localForage.config({
driver: localForage.INDEXEDDB,
});
return {
get: async (key) => {
let data;
try {
data = await localForage.getItem(key);
} catch (e) {}
if (!data) return data;
const { expire, value } = data;
if (expire < Date.now()) {
localForage.removeItem(key);
return null;
}
return value;
},
set: (key, value, expire = false, callback = false) => {
if (expire && typeof expire === "number")
expire = Math.round(expire * 1000 + Date.now()); // * 1000 to use seconds
return localForage.setItem(key, { value, expire }, expire && callback);
},
};
})();
export const TESTING_TAGS = {
"testing-approved": {
color: getCSSVariableValue("--green-60"),
label: "approved",
},
"testing-exception-unchanged": {
color: getCSSVariableValue("--teal-60"),
label: "unchanged",
},
"testing-exception-elsewhere": {
color: getCSSVariableValue("--blue-50"),
label: "elsewhere",
},
"testing-exception-ui": {
color: getCSSVariableValue("--purple-50"),
label: "ui",
},
"testing-exception-other": {
color: getCSSVariableValue("--yellow-50"),
label: "other",
},
none: {
color: getCSSVariableValue("--red-60"),
label: "missing",
},
unknown: {
color: getCSSVariableValue("--grey-30"),
label: "unknown",
}
};
let taskclusterLandingsArtifact = (async function () {
let json = await EXPIRE_CACHE.get("taskclusterLandingsArtifact");
if (!json) {
let response = await fetch(LANDINGS_URL);
json = await response.json();
// 30 minutes
EXPIRE_CACHE.set("taskclusterLandingsArtifact", json, 60 * 30);
} else {
console.log("cache hit", json);
}
return json;
})();
export let featureMetabugs = (async function () {
let json = await taskclusterLandingsArtifact;
return json.featureMetaBugs;
})();
export let landingsData = (async function () {
let json = await taskclusterLandingsArtifact;
json = json.landings;
// Sort the dates so object iteration will be sequential:
let orderedDates = [];
for (let date in json) {
orderedDates.push(date);
}
orderedDates.sort((a, b) => {
return Temporal.PlainDate.compare(
Temporal.PlainDate.from(a),
Temporal.PlainDate.from(b)
);
});
let returnedObject = {};
for (let date of orderedDates) {
returnedObject[date] = json[date];
}
document.body.classList.remove("loading-data");
return returnedObject;
})();
export function getNewTestingTagCountObject() {
let obj = {};
for (let tag in TESTING_TAGS) {
obj[tag] = 0;
}
return obj;
}
export async function getTestingPolicySummaryData(grouping = "daily", filter) {
let data = await landingsData;
// console.log(data);
// let startDate = grouping == "daily" ? "2020-09-15" : "2020-08-16";
let startDate = "2020-09-01";
let dailyData = {};
for (let date in data) {
// Ignore data before the testing policy took place.
if (
Temporal.PlainDate.compare(
Temporal.PlainDate.from(date),
Temporal.PlainDate.from(startDate)
) < 1
) {
continue;
}
let returnedDataForDate = getNewTestingTagCountObject();
let originalData = data[date];
for (let bug of originalData) {
if (filter && !filter(bug)) {
continue;
}
for (let commit of bug.commits) {
if (!commit.testing ) {
returnedDataForDate.unknown++;
} else {
returnedDataForDate[commit.testing] =
returnedDataForDate[commit.testing] + 1;
}
}
}
dailyData[date] = returnedDataForDate;
}
console.log(dailyData);
if (grouping == "weekly") {
let weeklyData = {};
for (let daily in dailyData) {
let date = Temporal.PlainDate.from(daily);
let weekStart = date.minus({ days: date.dayOfWeek }).toString();
if (!weeklyData[weekStart]) {
weeklyData[weekStart] = getNewTestingTagCountObject();
}
for (let tag in dailyData[daily]) {
weeklyData[weekStart][tag] += dailyData[daily][tag];
}
}
return weeklyData;
} else if (grouping == "monthly") {
let monthlyData = {};
for (let daily in dailyData) {
let date = Temporal.PlainDate.from(daily);
let yearMonth = date.toYearMonth();
if (!monthlyData[yearMonth]) {
monthlyData[yearMonth] = getNewTestingTagCountObject();
}
for (let tag in dailyData[daily]) {
monthlyData[yearMonth][tag] += dailyData[daily][tag];
}
}
return monthlyData;
}
return dailyData;
}

Просмотреть файл

@ -0,0 +1,342 @@
/* Photon Colors CSS Variables v3.3.2:
https://github.com/FirefoxUX/photon-colors/blob/master/photon-colors.css */
html {
--magenta-50: #ff1ad9;
--magenta-60: #ed00b5;
--magenta-70: #b5007f;
--magenta-80: #7d004f;
--magenta-90: #440027;
--purple-30: #c069ff;
--purple-40: #ad3bff;
--purple-50: #9400ff;
--purple-60: #8000d7;
--purple-70: #6200a4;
--purple-80: #440071;
--purple-90: #25003e;
--blue-40: #45a1ff;
--blue-50: #0a84ff;
--blue-50-a30: rgba(10, 132, 255, 0.3);
--blue-60: #0060df;
--blue-70: #003eaa;
--blue-80: #002275;
--blue-90: #000f40;
--teal-50: #00feff;
--teal-60: #00c8d7;
--teal-70: #008ea4;
--teal-80: #005a71;
--teal-90: #002d3e;
--green-50: #30e60b;
--green-60: #12bc00;
--green-70: #058b00;
--green-80: #006504;
--green-90: #003706;
--yellow-50: #ffe900;
--yellow-60: #d7b600;
--yellow-60-a30: rgba(215, 182, 0, 0.3);
--yellow-70: #a47f00;
--yellow-80: #715100;
--yellow-90: #3e2800;
--red-50: #ff0039;
--red-60: #d70022;
--red-60-a30: rgba(215, 0, 34, 0.3);
--red-70: #a4000f;
--red-80: #5a0002;
--red-90: #3e0200;
--orange-50: #ff9400;
--orange-60: #d76e00;
--orange-70: #a44900;
--orange-80: #712b00;
--orange-90: #3e1300;
--grey-10: #f9f9fa;
--grey-10-a10: rgba(249, 249, 250, 0.1);
--grey-10-a20: rgba(249, 249, 250, 0.2);
--grey-10-a40: rgba(249, 249, 250, 0.4);
--grey-10-a60: rgba(249, 249, 250, 0.6);
--grey-10-a80: rgba(249, 249, 250, 0.8);
--grey-20: #ededf0;
--grey-30: #d7d7db;
--grey-40: #b1b1b3;
--grey-50: #737373;
--grey-60: #4a4a4f;
--grey-70: #38383d;
--grey-80: #2a2a2e;
--grey-90: #0c0c0d;
--grey-90-a05: rgba(12, 12, 13, 0.05);
--grey-90-a10: rgba(12, 12, 13, 0.1);
--grey-90-a20: rgba(12, 12, 13, 0.2);
--grey-90-a30: rgba(12, 12, 13, 0.3);
--grey-90-a40: rgba(12, 12, 13, 0.4);
--grey-90-a50: rgba(12, 12, 13, 0.5);
--grey-90-a60: rgba(12, 12, 13, 0.6);
--grey-90-a70: rgba(12, 12, 13, 0.7);
--grey-90-a80: rgba(12, 12, 13, 0.8);
--grey-90-a90: rgba(12, 12, 13, 0.9);
--ink-40: #7175A8;
--ink-50: #595E91;
--ink-60: #464B76;
--ink-70: #363959;
--ink-80: #202340;
--ink-90: #0f1126;
}
html {
--main-background: white;
--main-text-color: var(--ink-90);
--main-font-family: 'Fira Sans', 'Open Sans', sans-serif, Arial;
--main-font: 400 14px/1.5 var(--main-font-family);
--mono-font-family: 'Fira Mono', monospace;
--mono-font: 400 14px/1.5 var(--mono-font-family);
--heading-color: var(--ink-70);
--heading-background: var(--grey-20);
}
html {
font-family: 'Fira Sans', 'Open Sans', sans-serif, Arial;
font: 400 14px/1.5 "Fira Sans";
}
body,
html {
margin: 0;
padding: 0;
height: 100%;
}
body {
background: var(--main-background);
color: var(--main-text-color);
font: var(--main-font);
}
hr {
color: rgba(0, 0, 0, .2);
}
h1 {
font-weight: 300;
color: var(--heading-color);
background: var(--heading-background);
padding: 5px;
text-align: center;
margin: 0;
margin-bottom: 5px;
}
.iframe-container {
width: 50%;
max-width: 800px;
margin: 0 auto;
}
.iframe-container > div {
height: 0;
padding-bottom: 50%;
position: relative;
}
.iframe-container > div > iframe {
border: none;
position:absolute;
top:0;
left:0;
width:100%;
height:100%;
display: block;
}
.summary-box {
width: 95%;
margin: 0 auto;
padding-left: 33px;
padding-right: 17px;
padding-bottom: 30px;
}
.summary-box h2 {
text-align: center;
border-bottom: solid 1px rgba(0, 0, 0, 0.1);
}
.desc-box {
width: 230px;
padding-left: 33px;
padding-right: 17px;
padding-bottom: 30px;
box-shadow: 1px 2px 2px 2px rgba(0, 0, 0, 0.1);
border-radius: 5px;
}
.milestones {
display: grid;
grid-template-columns: repeat(3, 1fr);
width: 90%;
margin: 0 auto;
margin-bottom: 20px;
}
.milestones > .desc-box {
width: auto;
margin: 10px;
padding-bottom: 18px;
}
.desc-box h2,
.summary-box h2 {
font-size: 22px;
font-weight: 400;
line-height: 33px;
}
.desc-box p,
.summary-box p {
font-size: 15px;
font-weight: 300;
}
.arewe-timeline {
position: relative;
padding: 1em 10px;
margin-left: -10px;
margin-right: -10px;
margin-top: 10px;
border: solid 1px #d7e4ed;
color: #7f8c97;
background-color: #e9f0f5;
}
.arewe-timeline h2 {
font-weight: normal;
margin: 0;
padding: 0;
}
.arewe-date {
font-weight: bold;
}
.arewe-timeline::before {
/* this is the vertical line */
content: "";
position: absolute;
top: 0;
left: 28px;
height: 100%;
width: 4px;
background: #d7e4ed;
}
.arewe-timeline-block {
position: relative;
margin: 2em 0;
}
.arewe-timeline-block:first-child {
margin-top: 0;
}
.arewe-timeline-block:last-child {
margin-bottom: 0;
}
.arewe-timeline-img {
position: absolute;
top: 0;
left: 0;
width: 40px;
height: 40px;
border-radius: 50%;
box-shadow: 0 0 0 4px white, inset 0 2px 0 rgba(0, 0, 0, 0.08), 0 3px 0 4px rgba(0, 0, 0, 0.05);
}
.arewe-timeline-img.arewe-subtraction {
background: var(--green-50);
}
.arewe-timeline-img.arewe-addition {
background: var(--red-50);
}
.arewe-timeline-content {
position: relative;
margin-left: 60px;
background: white;
border-radius: 0.25em;
padding: .5em;
box-shadow: 0 3px 0 #d7e4ed;
}
.arewe-timeline-content h2 {
color: #303e49;
}
.arewe-timeline-content p {
margin: 1em 0;
line-height: 1.6;
}
.arewe-timeline-content .arewe-read-more {
float: right;
padding: .8em 1em;
background: #acb7c0;
color: white;
border-radius: 0.25em;
}
.arewe-timeline-content::before {
content: "";
position: absolute;
top: 16px;
right: 100%;
height: 0;
width: 0;
border: 7px solid transparent;
border-right: 7px solid white;
}
.arewe-timeline-content small {
border: solid 1px;
border-radius: 5px;
background: rgba(0, 0, 255, .05);
font-size: 90%;
vertical-align: middle;
margin-right: 2px;
padding: 3px;
}
.arewe-timeline-path {
font: var(--mono-font);
}
.arewe-timeline-details {
margin: 0 auto;
width: 95%;
}
.arewe-timeline-details summary {
cursor: pointer;
}
.arewe-timeline-details h2 {
display: inline-block;
}
.arewe-update {
position: relative;
margin-top: 16px;
margin-bottom: 16px;
padding: 16px;
color: #032f62;
background-color: #dbedff;
border: 1px solid rgba(0, 0, 0, 0.08);
border-radius: 2px;
}
.arewe-update-warn {
color: #735c0f;
background-color: #fff3dd;
}

185
ui/changes/src/css/page.css Normal file
Просмотреть файл

@ -0,0 +1,185 @@
@import url("./common.css");
* {
box-sizing: border-box;
}
th,
td {
vertical-align: top;
}
table {
table-layout: fixed;
width: 100%;
white-space: nowrap;
}
table td,
table th {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
text-align: center;
}
table tr td:nth-child(1),
table tr th:nth-child(1) {
width: 3em;
}
table tr td:nth-child(2),
table tr th:nth-child(2) {
text-align: left;
}
table tr td:nth-child(3),
table tr th:nth-child(3),
table tr td:nth-child(4),
table tr th:nth-child(4),
table tr td:nth-child(5),
table tr th:nth-child(5) {
width: 100px;
}
table td ul {
margin: 0;
padding: 0;
list-style: none;
}
tr {
padding: 4px 0;
border-bottom: solid 1px rgba(0, 0, 0, 0.2);
}
td .desc-box {
width: auto;
padding: 3px;
}
td .desc-box ul {
margin: 0;
padding-inline-start: 20px;
}
#links {
position: absolute;
right: 5px;
top: 5px;
}
h3 {
margin: 0;
border-bottom: solid 1px rgba(0, 0, 0, 0.2);
}
details h3 {
display: inline-block;
border-bottom: none;
}
details summary {
border-bottom: solid 1px rgba(0, 0, 0, 0.2);
}
#grid {
display: grid;
grid-template-columns: auto 1fr;
padding: 0 5px;
max-width: 100vw;
overflow: hidden;
}
#grid aside {
padding-right: 5px;
}
#grid main {
overflow: auto;
}
#filter-container input:not([type="checkbox"]),
#filter-container select {
width: 180px;
display: block;
margin: 2px 0;
padding: 0;
}
#filter-container label {
font-style: italic;
}
/* For testing/ graph page */
aside ul {
margin: 0;
padding: 0;
}
aside ul li {
list-style: none;
}
/*
.chart-container {
display: flex;
justify-content: center;
}
*/
.loading-data #grid,
.loader {
display: none;
}
.loading-data .loader {
display: block;
}
/* Spinner */
.loader,
.loader:before,
.loader:after {
background: var(--heading-color);
-webkit-animation: load1 1s infinite ease-in-out;
animation: load1 1s infinite ease-in-out;
width: 1em;
height: 4em;
}
.loader {
color: var(--heading-color);
text-indent: -9999em;
margin: 88px auto;
position: relative;
font-size: 11px;
-webkit-transform: translateZ(0);
-ms-transform: translateZ(0);
transform: translateZ(0);
-webkit-animation-delay: -0.16s;
animation-delay: -0.16s;
}
.loader:before,
.loader:after {
position: absolute;
top: 0;
content: "";
}
.loader:before {
left: -1.5em;
-webkit-animation-delay: -0.32s;
animation-delay: -0.32s;
}
.loader:after {
left: 1.5em;
}
@-webkit-keyframes load1 {
0%,
80%,
100% {
box-shadow: 0 0;
height: 4em;
}
40% {
box-shadow: 0 -2em;
height: 5em;
}
}
@keyframes load1 {
0%,
80%,
100% {
box-shadow: 0 0;
height: 4em;
}
40% {
box-shadow: 0 -2em;
height: 5em;
}
}

38
ui/changes/src/graph.html Normal file
Просмотреть файл

@ -0,0 +1,38 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>testing policy - mc-changes</title>
<link href='css/page.css' rel='stylesheet' type='text/css'>
<link rel="icon"
href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22></text></svg>">
</head>
<body class="loading-data">
<h1>mc changes</h1>
<div id="links">
<a href="index.html">Changes</a> |
<a href="testing.html">Testing Policy</a>
</div>
<div class="loader"></div>
<div id="grid">
<aside>
<h3>Controls</h3>
<ul>
<li><label><input name="grouping" type="radio" value="daily" />Daily</label></li>
<li><label><input name="grouping" type="radio" value="weekly" checked />Weekly</label></li>
<li><label><input name="grouping" type="radio" value="monthly" />Monthly</label></li>
</ul>
</aside>
<main>
<div class="chart-container">
<div id="chart"></div>
</div>
</main>
</div>
<script src="graph.js"></script>
</body>
</html>

135
ui/changes/src/graph.js Normal file
Просмотреть файл

@ -0,0 +1,135 @@
import ApexCharts from 'apexcharts'
// JSON from https://bugzilla.mozilla.org/show_bug.cgi?id=1669363
import teamComponentMapping from './teams.json'
function generateData(count, yrange) {
var i = 0;
var series = [];
while (i < count) {
var x = (i + 1).toString();
var y = Math.floor(Math.random() * (yrange.max - yrange.min + 1)) + yrange.min;
series.push({
x: x,
y: y
});
i++;
}
return series;
}
function rerender() {
var options = {
series: [
{
name: "Metric1",
data: generateData(20, {
min: 0,
max: 90,
}),
},
{
name: "Metric2",
data: generateData(20, {
min: 0,
max: 90,
}),
},
{
name: "Metric3",
data: generateData(20, {
min: 0,
max: 90,
}),
},
{
name: "Metric4",
data: generateData(20, {
min: 0,
max: 90,
}),
},
{
name: "Metric5",
data: generateData(20, {
min: 0,
max: 90,
}),
},
{
name: "Metric6",
data: generateData(20, {
min: 0,
max: 90,
}),
},
{
name: "Metric7",
data: generateData(20, {
min: 0,
max: 90,
}),
},
{
name: "Metric8",
data: generateData(20, {
min: 0,
max: 90,
}),
},
{
name: "Metric8",
data: generateData(20, {
min: 0,
max: 90,
}),
},
],
chart: {
height: 300,
width: 800,
type: "heatmap",
},
stroke: {
width: 0,
},
plotOptions: {
heatmap: {
radius: 30,
enableShades: false,
colorScale: {
ranges: [
{
from: 0,
to: 50,
color: "#008FFB",
},
{
from: 51,
to: 100,
color: "#00E396",
},
],
},
},
},
dataLabels: {
enabled: true,
style: {
colors: ["#fff"],
},
},
xaxis: {
type: "category",
},
};
var chart = new ApexCharts(document.querySelector("#chart"), options);
chart.render();
}
(async function () {
console.log(teamComponentMapping);
rerender();
})();

87
ui/changes/src/index.html Normal file
Просмотреть файл

@ -0,0 +1,87 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>mc-changes</title>
<link href='css/page.css' rel='stylesheet' type='text/css'>
<link rel="icon"
href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22></text></svg>">
</head>
<body class="loading-data">
<h1>mc changes</h1>
<div id="links">
<a href="index.html">Changes</a> |
<a href="testing.html">Testing Policy</a>
</div>
<div class="loader"></div>
<div id="grid">
<aside>
<div id="filter-container">
<h3>Filters</h3>
<label>Metabug
<select name="featureMetabugs" id="featureMetabugs">
<option value="" selected>Choose a feature metabug</option>
</select>
<input type="text" name="metaBugID" id="metaBugID" placeholder="…or enter bug ID">
</label>
<hr />
<label>Start date<br />
<input id="startDate" type="date" value="2020-10-01">
</label>
<label>End date<br />
<input id="endDate" type="date">
</label>
<hr />
<label>Testing Tags<br />
<select name="testingTags" id="testingTags" multiple>
<option value="testing-approved" selected>testing-approved</option>
<option value="testing-exception-elsewhere" selected>exception-elsewhere</option>
<option value="testing-exception-other" selected>exception-other</option>
<option value="testing-exception-ui" selected>exception-ui</option>
<option value="testing-exception-unchanged" selected>exception-unchanged</option>
<option value="missing" selected>missing</option>
<option value="unknown" selected>unknown</option>
</select>
</label>
<hr />
<label>
<input type="checkbox" id="riskinessEnabled" checked>Riskiness
</label>
</div>
</aside>
<main>
<details open>
<summary>
<h3>Summary</h3>
</summary>
<div id="result-summary"></div>
</details>
<details open>
<summary>
<h3>Bugs</h3>
</summary>
<table id="datatable"></table>
<table id="table" class="table table-bordered table-hover">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Bug</th>
<th scope="col">Date</th>
<th scope="col">Testing Tags</th>
<th scope="col" id="riskinessColumn">Riskiness</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</details>
</main>
</div>
<script src="index.js"></script>
</body>
</html>

395
ui/changes/src/index.js Normal file
Просмотреть файл

@ -0,0 +1,395 @@
// TODO: On click, show previous components affected by similar patches.
// TODO: On click, show previous bugs caused by similar patches.
import { Temporal } from 'proposal-temporal/lib/index.mjs';
import ApexCharts from 'apexcharts'
import {
TESTING_TAGS,
featureMetabugs,
landingsData,
getNewTestingTagCountObject,
} from "./common.js";
const HIGH_RISK_COLOR = "rgb(255, 13, 87)";
const MEDIUM_RISK_COLOR = "darkkhaki";
const LOW_RISK_COLOR = "green";
let options = {
metaBugID: {
value: null,
type: "text",
},
testingTags: {
value: null,
type: "select",
},
startDate: {
value: null,
type: "text",
},
endDate: {
value: null,
type: "text",
},
riskinessEnabled: {
value: null,
type: "checkbox",
},
};
if (new URLSearchParams(window.location.search).has("riskiness")) {
document.querySelector("#riskinessEnabled").checked = true;
}
let resultSummary = document.getElementById("result-summary");
let metabugsDropdown = document.querySelector("#featureMetabugs");
// TODO: port this to an option maybe
async function buildMetabugsDropdown() {
metabugsDropdown.addEventListener("change", () => {
setOption("metaBugID", metabugsDropdown.value);
rebuildTable();
});
let bugs = await featureMetabugs;
metabugsDropdown.innerHTML = `<option value="" selected>Choose a feature metabug</option>`;
for (let bug of bugs) {
let option = document.createElement("option");
option.setAttribute("value", bug.id);
option.textContent = bug.summary;
metabugsDropdown.append(option);
}
}
function getOption(name) {
return options[name].value;
}
function getOptionType(name) {
return options[name].type;
}
function setOption(name, value) {
return (options[name].value = value);
}
function addRow(bugSummary) {
let table = document.getElementById("table");
let row = table.insertRow(table.rows.length);
let num_column = document.createElement("td");
num_column.append(document.createTextNode(table.rows.length - 1));
row.append(num_column);
let bug_column = row.insertCell(1);
let bug_link = document.createElement("a");
bug_link.textContent = `Bug ${bugSummary["id"]}`;
bug_link.href = `https://bugzilla.mozilla.org/show_bug.cgi?id=${bugSummary["id"]}`;
bug_link.target = "_blank";
bug_column.append(bug_link);
bug_column.append(document.createTextNode(` - ${bugSummary["summary"]}`));
let components_percentages = Object.entries(
bugSummary["most_common_regression_components"]
);
if (components_percentages.length > 0) {
let component_container = document.createElement("div");
component_container.classList.add("desc-box");
bug_column.append(component_container);
components_percentages.sort(
([component1, percentage1], [component2, percentage2]) =>
percentage2 - percentage1
);
component_container.append(
document.createTextNode("Most common regression components:")
);
let component_list = document.createElement("ul");
for (let [component, percentage] of components_percentages.slice(0, 3)) {
let component_list_item = document.createElement("li");
component_list_item.append(
document.createTextNode(
`${component} - ${Math.round(100 * percentage)}%`
)
);
component_list.append(component_list_item);
}
component_container.append(component_list);
}
/*<hr>
The patches have a high chance of causing regressions of type <b>crash</b> and <b>high severity</b>.
<br><br>
The patches could affect the <b>Search</b> and <b>Bookmarks</b> features.
<br><br>
Examples of previous bugs caused by similar patches:
<ul>
<li>Bug 1 - Can"t bookmark pages</li>
<li>Bug 7 - Search doesn"t work anymore <span style="background-color:gold;color:yellow;">STR</span></li>
</ul>*/
let date_column = row.insertCell(2);
date_column.textContent = bugSummary.date;
let testing_tags_column = row.insertCell(3);
testing_tags_column.classList.add("testing-tags");
let testing_tags_list = document.createElement("ul");
for (let commit of bugSummary.commits) {
let testing_tags_list_item = document.createElement("li");
if (!commit.testing) {
testing_tags_list_item.append(document.createTextNode("unknown"));
} else {
testing_tags_list_item.append(
document.createTextNode(TESTING_TAGS[commit.testing].label)
);
}
testing_tags_list.append(testing_tags_list_item);
}
testing_tags_column.append(testing_tags_list);
if (getOption("riskinessEnabled")) {
let risk_list = document.createElement("ul");
let risk_column = row.insertCell(4);
for (let commit of bugSummary.commits) {
let risk = commit.risk;
let risk_list_item = document.createElement("li");
let riskText = document.createElement("a");
riskText.setAttribute(
"href",
`https://hg.mozilla.org/mozilla-central/rev/${commit.id}`
);
riskText.setAttribute("target", "_blank");
riskText.textContent = Math.round(100 * risk);
if (risk > 0.8) {
riskText.style.color = HIGH_RISK_COLOR;
} else if (risk > 0.5) {
riskText.style.color = MEDIUM_RISK_COLOR;
} else {
riskText.style.color = LOW_RISK_COLOR;
}
risk_list_item.append(riskText);
risk_list.append(risk_list_item);
}
risk_column.append(risk_list);
}
}
function renderTestingSummary(bugSummaries) {
let metaBugID = getOption("metaBugID");
let changesets = [];
if (bugSummaries.length) {
changesets = bugSummaries
.map((summary) => summary.commits.length)
.reduce((a, b) => a + b);
}
let testingCounts = getNewTestingTagCountObject();
bugSummaries.forEach((summary) => {
summary.commits.forEach((commit) => {
if (!commit.testing) {
testingCounts.unknown++;
} else {
testingCounts[commit.testing] = testingCounts[commit.testing] + 1;
}
});
});
let bugText = metaBugID ? `For bug ${metaBugID}: ` : ``;
let summaryText = `${bugText}There are ${bugSummaries.length} bugs with ${changesets} changesets.`;
resultSummary.textContent = summaryText;
// let pre = document.createElement("pre");
// pre.textContent = `${JSON.stringify(
// testingCounts
// )}`;
// resultSummary.append(pre);
let categories = [];
let colors = [];
let data = [];
for (let tag in testingCounts) {
categories.push(TESTING_TAGS[tag].label);
data.push(testingCounts[tag]);
colors.push(TESTING_TAGS[tag].color);
}
var options = {
series: [
{
name: "Tags",
data,
},
],
chart: {
height: 150,
type: "bar",
},
plotOptions: {
bar: {
dataLabels: {
position: "top", // top, center, bottom
},
},
},
xaxis: {
categories,
// position: "bottom",
axisBorder: {
show: false,
},
axisTicks: {
show: false,
},
},
yaxis: {
axisBorder: {
show: false,
},
axisTicks: {
show: false,
},
labels: {
show: false,
},
},
};
let chartEl = document.createElement("div");
resultSummary.append(chartEl);
var chart = new ApexCharts(chartEl, options);
chart.render();
}
async function buildTable() {
let data = await landingsData;
let metaBugID = getOption("metaBugID");
let testingTags = getOption("testingTags");
let includeUnknown = testingTags.includes("unknown");
if (testingTags.includes("missing")) {
testingTags[testingTags.indexOf("missing")] = "none";
}
let bugSummaries = [].concat.apply([], Object.values(data));
if (metaBugID) {
bugSummaries = bugSummaries.filter((bugSummary) =>
bugSummary["meta_ids"].includes(Number(metaBugID))
);
}
let startDate = getOption("startDate");
if (startDate) {
startDate = Temporal.PlainDate.from(startDate);
bugSummaries = bugSummaries.filter((bugSummary) => {
return (
Temporal.PlainDate.compare(
Temporal.PlainDate.from(bugSummary.date),
startDate
) >= 0
);
});
}
let endDate = getOption("endDate");
if (endDate) {
endDate = Temporal.PlainDate.from(endDate);
bugSummaries = bugSummaries.filter((bugSummary) => {
return (
Temporal.PlainDate.compare(
Temporal.PlainDate.from(bugSummary.date),
endDate
) <= 0
);
});
}
if (testingTags) {
bugSummaries = bugSummaries.filter((bugSummary) =>
bugSummary.commits.some((commit) => {
if (includeUnknown && !commit.testing) {
return true;
}
return commit.testing && testingTags.includes(commit.testing);
})
);
}
bugSummaries.reverse();
if (getOption("riskinessEnabled")) {
// bugSummaries.sort(
// (bugSummary1, bugSummary2) => bugSummary2["risk"] - bugSummary1["risk"]
// );
document.getElementById("riskinessColumn").style.removeProperty("display");
} else {
document.getElementById("riskinessColumn").style.display = "none";
}
renderTestingSummary(bugSummaries);
for (let bugSummary of bugSummaries) {
addRow(bugSummary);
}
}
function rebuildTable() {
let table = document.getElementById("table");
let summary = resultSummary;
summary.textContent = "";
while (table.rows.length > 1) {
table.deleteRow(table.rows.length - 1);
}
buildTable();
}
(function init() {
buildMetabugsDropdown();
Object.keys(options).forEach(function (optionName) {
let optionType = getOptionType(optionName);
let elem = document.getElementById(optionName);
if (optionType === "text") {
setOption(optionName, elem.value);
elem.addEventListener("change", function () {
setOption(optionName, elem.value);
rebuildTable();
});
} else if (optionType === "checkbox") {
setOption(optionName, elem.checked);
elem.onchange = function () {
setOption(optionName, elem.checked);
rebuildTable();
};
} else if (optionType === "select") {
let value = [];
for (let option of elem.options) {
if (option.selected) {
value.push(option.value);
}
}
setOption(optionName, value);
elem.onchange = function () {
let value = [];
for (let option of elem.options) {
if (option.selected) {
value.push(option.value);
}
}
setOption(optionName, value);
rebuildTable();
};
} else {
throw new Error("Unexpected option type.");
}
});
buildTable();
})();

421
ui/changes/src/teams.json Normal file
Просмотреть файл

@ -0,0 +1,421 @@
{
"Crypto": {
"Core": {
"all_components": false,
"named_components": [
"Security: PSM"
],
"prefixed_components": [
]
},
"JSS": {
"all_components": true
},
"NSS": {
"all_components": true
}
},
"DOM": {
"Core": {
"all_components": false,
"named_components": [
"Image Blocking",
"Security: CAPS",
"XBL",
"XML",
"XPConnect",
"XSLT",
"XUL"
],
"prefixed_components": [
"DOM",
"Storage"
]
}
},
"DevTools": {
"Core": {
"all_components": false,
"named_components": [
"Gecko Profiler",
"Web Replay"
],
"prefixed_components": [
]
},
"DevTools": {
"all_components": true
}
},
"Frontend": {
"Core": {
"all_components": false,
"named_components": [
"AutoConfig (Mission Control Desktop)",
"Find Backend",
"Window Management"
],
"prefixed_components": [
]
},
"Firefox": {
"all_components": false,
"named_components": [
"about:logins",
"Address Bar",
"Bookmarks & History",
"Disability Access",
"Distributions",
"Downloads Panel",
"Enterprise Policies",
"File Handling",
"General",
"Installer",
"Keyboard Navigation",
"Menus",
"Migration",
"Page Info Window",
"Preferences",
"Search",
"Session Restore",
"Tabbed Browser",
"Theme",
"Toolbars and Customization",
"Tours",
"Translation",
"WebPayments UI"
],
"prefixed_components": [
]
},
"Toolkit": {
"all_components": false,
"named_components": [
"Application Update",
"Autocomplete",
"Downloads API",
"Find Toolbar",
"Form Autofill",
"Form Manager",
"General",
"Notifications and Alerts",
"Password Manager",
"Password Manager: Site Compatibility",
"Places",
"Preferences",
"Printing",
"Reader Mode",
"Startup and Profile System",
"Storage",
"Themes",
"Toolbars and Toolbar Customization",
"Video/Audio Controls",
"View Source",
"WebPayments UI",
"XUL Widgets"
],
"prefixed_components": [
]
}
},
"GFX": {
"Core": {
"all_components": false,
"named_components": [
"Canvas: 2D",
"ImageLib",
"Panning and Zooming",
"Web Painting"
],
"prefixed_components": [
"GFX",
"Graphics"
]
}
},
"Javascript": {
"Core": {
"all_components": false,
"named_components": [
"js-ctypes"
],
"prefixed_components": [
"Javascript"
]
}
},
"Layout": {
"Core": {
"all_components": false,
"named_components": [
"Internationalization",
"MathML",
"SVG"
],
"prefixed_components": [
"CSS",
"Layout",
"Print"
]
}
},
"Media": {
"Core": {
"all_components": false,
"named_components": [
"Web Audio"
],
"prefixed_components": [
"Audio/Video",
"WebRTC"
]
}
},
"Mobile": {
"Emerging Markets": {
"all_components": true
},
"Fenix": {
"all_components": true
},
"Firefox for Android": {
"all_components": true
},
"Firefox for Echo Show": {
"all_components": true
},
"Firefox for FireTV": {
"all_components": true
},
"Firefox for iOS": {
"all_components": true
},
"Focus": {
"all_components": true
},
"Focus-iOS": {
"all_components": true
},
"GeckoView": {
"all_components": true
}
},
"Networking": {
"Core": {
"all_components": false,
"named_components": [
],
"prefixed_components": [
"Networking"
]
}
},
"Other": {
"Core": {
"all_components": false,
"named_components": [
"Disability Access APIs",
"General",
"Performance",
"Platform Fuzzing Team",
"Spelling checker",
"Web Speech",
"WebVR",
"Untriaged"
],
"prefixed_components": [
]
},
"External Software Affecting Firefox": {
"all_components": true
},
"Firefox": {
"all_components": false,
"named_components": [
"Headless",
"Normandy Client",
"Normandy Server",
"PDF Viewer",
"Screenshots",
"Shell Integration",
"System Add-ons: Off-train Deployment",
"Untriaged"
],
"prefixed_components": [
]
},
"Firefox Build System": {
"all_components": true
},
"NSPR": {
"all_components": true
},
"Toolkit": {
"all_components": false,
"named_components": [
"about:memory",
"Crash Reporting",
"FeatureGate",
"Performance Monitoring",
"Telemetry"
],
"prefixed_components": [
]
}
},
"Platform": {
"Core": {
"all_components": false,
"named_components": [
"Canvas: WebGL",
"DMD",
"Hardware Abstraction Layer (HAL)",
"IPC",
"IPC: MSCOM",
"MFBT",
"Memory Allocator",
"Plug-ins",
"Preferences: Backend",
"Security: Process Sandboxing",
"String",
"XPCOM",
"mfbt",
"mozglue"
],
"prefixed_components": [
"Embedding",
"Widget"
]
},
"Firefox": {
"all_components": false,
"named_components": [
"Launcher Process"
],
"prefixed_components": [
]
},
"Toolkit": {
"all_components": false,
"named_components": [
"Async Tooling",
"NSIS Installer",
"OS.File"
],
"prefixed_components": [
]
}
},
"Pocket and User Journey": {
"Firefox": {
"all_components": false,
"named_components": [
"Messaging System",
"New Tab Page",
"Pocket"
],
"prefixed_components": [
"Activity Stream"
]
},
"Pocket": {
"all_components": true
}
},
"Privacy and Security": {
"Core": {
"all_components": false,
"named_components": [
"Image Blocking",
"Permission Manager",
"Privacy: Anti-Tracking",
"Security",
"Security Blacklists, Whitelists, and other State"
],
"prefixed_components": [
]
},
"Firefox": {
"all_components": false,
"named_components": [
"Firefox Monitor",
"Private Browsing",
"Protections UI",
"Site Identity",
"Site Permissions"
],
"prefixed_components": [
"Security"
]
},
"Firefox Private Network": {
"all_components": true
},
"Lockwise": {
"all_components": true
},
"Toolkit": {
"all_components": false,
"named_components": [
"Data Sanitization",
"Safe Browsing"
],
"prefixed_components": [
]
}
},
"Services": {
"Cloud Services": {
"all_components": true
},
"Firefox": {
"all_components": false,
"named_components": [
"Firefox Accounts",
"Remote Settings Client",
"Sync"
],
"prefixed_components": [
]
}
},
"Web Extensions": {
"WebExtensions": {
"all_components": true
},
"Firefox": {
"all_components": false,
"named_components": [
"Extension Compatibility"
],
"prefixed_components": [
]
},
"Toolkit": {
"all_components": false,
"named_components": [
"Add-ons Manager",
"Blocklist Implementation",
"Blocklist Policy Requests"
],
"prefixed_components": [
]
}
}
}

Просмотреть файл

@ -0,0 +1,84 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>testing policy - mc-changes</title>
<link href='css/page.css' rel='stylesheet' type='text/css'>
<link rel="icon"
href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22></text></svg>">
</head>
<body class="loading-data">
<h1>mc changes</h1>
<div id="links">
<a href="index.html">Changes</a> |
<a href="testing.html">Testing Policy</a>
</div>
<div class="loader"></div>
<div id="grid">
<aside>
<h3>Controls</h3>
<ul>
<li><label><input name="grouping" type="radio" value="daily" />Daily</label></li>
<li><label><input name="grouping" type="radio" value="weekly" checked />Weekly</label></li>
<li><label><input name="grouping" type="radio" value="monthly" />Monthly</label></li>
</ul>
</aside>
<main>
<div class="arewe-update">
See the <a href="https://firefox-source-docs.mozilla.org/testing/testing-policy/">Testing Policy for code landing in mozilla-central</a> for a description of each tag.
In addition to these:
<ul>
<li>`unknown` is for commits that don't have an accessible phabricator revision associated with them (i.e. automated wpt vendoring or restricted bugs).</li>
<li>`missing` is for commits that haven't had any tags applied.</li>
</ul>
</div>
<details open>
<summary>
<h3>All Commits</h3>
</summary>
<div id="all-chart" class="chart-container">
<div class="chart"></div>
</div>
<textarea id="all-data">
</textarea>
</details>
<details open>
<summary>
<h3>Backed Out Commits</h3>
</summary>
<div class="arewe-update arewe-update-warn">
Note that this includes _all_ commits in a bug that gets backed out,
so if there are a stack of commits this can overestimate tags.
</div>
<div id="backedout-chart" class="chart-container">
<div class="chart"></div>
</div>
<textarea id="backedout-data">
</textarea>
</details>
<details open>
<summary>
<h3>Commits that caused a regression</h3>
</summary>
<div class="arewe-update arewe-update-warn">
Note that this includes _all_ commits in a bug that gets backed out,
so if there are a stack of commits this can overestimate tags.
</div>
<div id="regression-chart" class="chart-container">
<div class="chart"></div>
</div>
<textarea id="regression-data">
</textarea>
</details>
</main>
</div>
<script src="testing.js"></script>
<script>
</script>
</body>
</html>

144
ui/changes/src/testing.js Normal file
Просмотреть файл

@ -0,0 +1,144 @@
import ApexCharts from 'apexcharts'
import { TESTING_TAGS, getTestingPolicySummaryData } from "./common.js";
(function () {
let charts = {
all: null,
backedout: null,
regression: null,
};
let radios = [...document.querySelectorAll("input[name=grouping]")];
var previouslySelected = document.querySelector(
"input[name=grouping]:checked"
);
for (let radio of radios) {
radio.addEventListener("change", function () {
if (this !== previouslySelected) {
previouslySelected = this;
rerender(this.value);
}
});
}
function rerenderChart(name, data, grouping) {
let series = [];
let colors = [];
for (let tag in TESTING_TAGS) {
series.push({ name: TESTING_TAGS[tag].label, data: [] });
colors.push(TESTING_TAGS[tag].color);
}
let dataContainer = document.querySelector(`#${name}-data`);
dataContainer.textContent = "\t";
dataContainer.textContent += "total commits\t";
for (let tag in TESTING_TAGS) {
dataContainer.textContent += `${tag}\t`;
}
for (let date in data) {
dataContainer.textContent += `\n`;
dataContainer.textContent += `${date}\t`;
let total = 0;
for (let tag in data[date]) {
total += data[date][tag];
}
dataContainer.textContent += `${total}\t`;
for (let tag in data[date]) {
// dataContainer.textContent += `${Math.round(parseFloat((data[date][tag] / total) * 100))}% (${data[date][tag]})\t`;
// dataContainer.textContent += `${Math.round(parseFloat((data[date][tag] / total) * 100))}%\t`;
dataContainer.textContent += `${data[date][tag]}\t`;
}
}
let xaxisCategories = [];
for (let date in data) {
xaxisCategories.push(date);
let i = 0;
for (let tag in data[date]) {
series[i].data.push(data[date][tag]);
i++;
}
}
let annotations =
grouping === "monthly"
? undefined
: {
xaxis: [
{
x: "2020-09-20",
borderColor: "#775DD0aa",
offsetX: 0,
label: {
style: {
color: "orange",
},
text: "rollout",
},
},
],
};
var options = {
series,
colors,
chart: {
type: "bar",
height: 300,
stacked: true,
toolbar: {
show: true,
},
zoom: {
enabled: true,
},
},
plotOptions: {
bar: {
horizontal: false,
},
},
xaxis: {
categories: xaxisCategories,
},
annotations,
};
// TODO: Print summary percentages etc on top of graph
let chartContainer = document.querySelector(`#${name}-chart`);
if (charts[name]) {
charts[name].destroy();
}
charts[name] = new ApexCharts(
chartContainer.querySelector(".chart"),
options
);
charts[name].render();
}
async function rerender(grouping) {
let allData = await getTestingPolicySummaryData(grouping);
rerenderChart("all", allData, grouping);
let backedoutData = await getTestingPolicySummaryData(grouping, (bug) => {
return bug.commits.some((c) => {
return c.backedout;
});
});
rerenderChart("backedout", backedoutData, grouping);
let regressionData = await getTestingPolicySummaryData(grouping, (bug) => {
return bug.commits.some((c) => {
return c.regressor;
});
});
rerenderChart("regression", regressionData, grouping);
}
rerender(previouslySelected ? previouslySelected.value : undefined);
})();

Просмотреть файл

@ -0,0 +1,28 @@
const path = require('path');
const CopyPlugin = require("copy-webpack-plugin");
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const ESLintPlugin = require('eslint-webpack-plugin');
module.exports = {
mode: 'development',
devtool: 'inline-source-map',
entry: {
index: './src/index.js',
testing: './src/testing.js',
graph: './src/graph.js',
},
output: {
path: path.resolve(__dirname, 'dist'),
},
plugins: [
// TODO: Fix eslint errors.
// new ESLintPlugin(),
new CleanWebpackPlugin(),
new CopyPlugin({
patterns: [
{ context: "src", from: "*.html" },
{ from: "src/css", to: "css" },
],
}),
],
};

10
ui/changes/webpack.dev.js Normal file
Просмотреть файл

@ -0,0 +1,10 @@
const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');
module.exports = merge(common, {
mode: 'development',
devtool: 'inline-source-map',
devServer: {
contentBase: './dist'
}
});

Просмотреть файл

@ -0,0 +1,13 @@
const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
module.exports = merge(common, {
mode: 'production',
optimization: {
minimizer: [
`...`,
new CssMinimizerPlugin(),
],
},
});