in prow/cmd/deck/static/prow/prow.ts [430:737]
function redraw(fz: FuzzySearch, pushState: boolean = true): void {
const rerunStatus = getParameterByName("rerun");
const modal = document.getElementById('rerun')!;
const rerunCommand = document.getElementById('rerun-content')!;
const builds = document.getElementById("builds")!.getElementsByTagName(
"tbody")[0];
while (builds.firstChild) {
builds.removeChild(builds.firstChild);
}
const args: string[] = [];
function getSelection(name: string): string {
const sel = selectionText(document.getElementById(name) as HTMLSelectElement);
if (sel && name !== 'repo' && !opts[name + 's' as keyof RepoOptions][sel]) {
return "";
}
if (sel !== "") {
args.push(`${name}=${encodeURIComponent(sel)}`);
}
return sel;
}
function getSelectionFuzzySearch(id: string, inputId: string): RegExp {
const input = document.getElementById(inputId) as HTMLInputElement;
const inputText = input.value;
if (inputText === "") {
return new RegExp('');
}
if (inputText !== "") {
args.push(`${id}=${encodeURIComponent(inputText)}`);
}
if (inputText !== "" && opts[id + 's' as keyof RepoOptions][inputText]) {
return new RegExp(`^${escapeRegexLiteral(inputText)}$`);
}
const expr = inputText.split('*').map(escapeRegexLiteral);
return new RegExp(`^${expr.join('.*')}$`);
}
const repoSel = getSelection("repo");
const opts = optionsForRepo(repoSel);
const typeSel = getSelection("type") as ProwJobType;
const pullSel = getSelection("pull");
const authorSel = getSelection("author");
const jobSel = getSelectionFuzzySearch("job", "job-input");
const stateSel = getSelection("state");
const clusterSel = getSelection("cluster");
if (pushState && window.history && window.history.pushState !== undefined) {
if (args.length > 0) {
history.pushState(null, "", "/?" + args.join('&'));
} else {
history.pushState(null, "", "/");
}
}
fz.setDict(Object.keys(opts.jobs));
redrawOptions(fz, opts);
let lastKey = '';
const jobCountMap = new Map() as Map<ProwJobState, number>;
const jobInterval: Array<[number, number]> = [[3600 * 3, 0], [3600 * 12, 0], [3600 * 48, 0]];
let currentInterval = 0;
const jobHistogram = new JobHistogram();
const now = Date.now() / 1000;
let totalJob = 0;
let displayedJob = 0;
for (let i = 0; i < allBuilds.items.length; i++) {
const build = allBuilds.items[i];
const {
metadata: {
name: prowJobName = "",
},
spec: {
cluster = "",
type = "",
job = "",
agent = "",
refs: {repo_link = "", base_sha = "", base_link = "", pulls = [], base_ref = ""} = {},
pod_spec,
},
status: {startTime, completionTime = "", state = "", pod_name, build_id = "", url = ""},
} = build;
let buildUrl = url;
if (url.includes('/view/')) {
buildUrl = `${window.location.origin}/${url.slice(url.indexOf('/view/') + 1)}`;
}
let org = "";
let repo = "";
if (build.spec.refs !== undefined) {
org = build.spec.refs.org;
repo = build.spec.refs.repo;
} else if (build.spec.extra_refs !== undefined && build.spec.extra_refs.length > 0 ) {
org = build.spec.extra_refs[0].org;
repo = build.spec.extra_refs[0].repo;
}
if (!equalSelected(typeSel, type)) {
continue;
}
if (!equalSelected(repoSel, `${org}/${repo}`)) {
continue;
}
if (!equalSelected(stateSel, state)) {
continue;
}
if (!equalSelected(clusterSel, cluster)) {
continue;
}
if (!jobSel.test(job)) {
continue;
}
if (pullSel) {
if (!pulls.length) {
continue;
}
if (!pulls.some((pull: Pull): boolean => {
const {number: prNumber} = pull;
return equalSelected(pullSel, prNumber.toString());
})) {
continue;
}
}
if (authorSel) {
if (!pulls.length) {
continue;
}
if (!pulls.some((pull: Pull): boolean => {
const {author} = pull;
return equalSelected(authorSel, author);
})) {
continue;
}
}
totalJob++;
jobCountMap.set(state, (jobCountMap.get(state) || 0) + 1);
// accumulate a count of the percentage of successful jobs over each interval
const started = Date.parse(startTime) / 1000;
const finished = Date.parse(completionTime) / 1000;
// const finished = completionTime ? Date.parse(completionTime): now;
const durationSec = completionTime ? finished - started : 0;
const durationStr = completionTime ? formatDuration(durationSec) : "";
if (currentInterval >= 0 && (now - started) > jobInterval[currentInterval][0]) {
const successCount = jobCountMap.get("success") || 0;
const failureCount = jobCountMap.get("failure") || 0;
const total = successCount + failureCount;
if (total > 0) {
jobInterval[currentInterval][1] = successCount / total;
} else {
jobInterval[currentInterval][1] = 0;
}
currentInterval++;
if (currentInterval >= jobInterval.length) {
currentInterval = -1;
}
}
if (displayedJob >= 500) {
jobHistogram.add(new JobSample(started, durationSec, state, -1));
continue;
} else {
jobHistogram.add(new JobSample(started, durationSec, state, builds.childElementCount));
}
displayedJob++;
const r = document.createElement("tr");
r.appendChild(cell.state(state));
if ((agent === "kubernetes" && pod_name) || agent !== "kubernetes") {
const logIcon = icon.create("description", "Build log");
if (pod_spec == null || pod_spec.containers.length <= 1) {
logIcon.href = `log?job=${job}&id=${build_id}`;
} else {
// this logic exists for legacy jobs that are configured for gubernator compatibility
const buildIndex = buildUrl.indexOf('/build/');
if (buildIndex !== -1) {
const gcsUrl = `${window.location.origin}/view/gcs/${buildUrl.substring(buildIndex + '/build/'.length)}`;
logIcon.href = gcsUrl;
} else if (buildUrl.includes('/view/')) {
logIcon.href = buildUrl;
} else {
logIcon.href = `log?job=${job}&id=${build_id}`;
}
}
const c = document.createElement("td");
c.classList.add("icon-cell");
c.appendChild(logIcon);
r.appendChild(c);
} else {
r.appendChild(cell.text(""));
}
r.appendChild(createRerunCell(modal, rerunCommand, prowJobName));
r.appendChild(createViewJobCell(prowJobName));
const key = groupKey(build);
if (key !== lastKey) {
// This is a different PR or commit than the previous row.
lastKey = key;
r.className = "changed";
if (type === "periodic") {
r.appendChild(cell.text(""));
} else {
let repoLink = repo_link;
if (!repoLink) {
repoLink = `/github-link?dest=${org}/${repo}`;
}
r.appendChild(cell.link(`${org}/${repo}`, repoLink));
}
if (type === "presubmit") {
if (pulls.length) {
r.appendChild(cell.prRevision(`${org}/${repo}`, pulls[0]));
} else {
r.appendChild(cell.text(""));
}
} else if (type === "batch") {
r.appendChild(batchRevisionCell(build));
} else if (type === "postsubmit") {
r.appendChild(cell.commitRevision(`${org}/${repo}`, base_ref, base_sha, base_link));
} else if (type === "periodic") {
r.appendChild(cell.text(""));
}
} else {
// Don't render identical cells for the same PR/commit.
r.appendChild(cell.text(""));
r.appendChild(cell.text(""));
}
if (spyglass) {
// this logic exists for legacy jobs that are configured for gubernator compatibility
const buildIndex = buildUrl.indexOf('/build/');
if (buildIndex !== -1) {
const gcsUrl = `${window.location.origin}/view/gcs/${buildUrl.substring(buildIndex + '/build/'.length)}`;
r.appendChild(createSpyglassCell(gcsUrl));
} else if (buildUrl.includes('/view/')) {
r.appendChild(createSpyglassCell(buildUrl));
} else {
r.appendChild(cell.text(''));
}
} else {
r.appendChild(cell.text(''));
}
if (buildUrl === "") {
r.appendChild(cell.text(job));
} else {
r.appendChild(cell.link(job, buildUrl));
}
r.appendChild(cell.time(i.toString(), moment.unix(started)));
r.appendChild(cell.text(durationStr));
builds.appendChild(r);
}
// fill out the remaining intervals if necessary
if (currentInterval !== -1) {
let successCount = jobCountMap.get("success");
if (!successCount) {
successCount = 0;
}
let failureCount = jobCountMap.get("failure");
if (!failureCount) {
failureCount = 0;
}
const total = successCount + failureCount;
for (let i = currentInterval; i < jobInterval.length; i++) {
if (total > 0) {
jobInterval[i][1] = successCount / total;
} else {
jobInterval[i][1] = 0;
}
}
}
const jobSummary = document.getElementById("job-histogram-summary")!;
const success = jobInterval.map((interval) => {
if (interval[1] < 0.5) {
return `${formatDuration(interval[0])}: <span class="state failure">${Math.ceil(interval[1] * 100)}%</span>`;
}
return `${formatDuration(interval[0])}: <span class="state success">${Math.ceil(interval[1] * 100)}%</span>`;
}).join(", ");
jobSummary.innerHTML = `Success rate over time: ${success}`;
const jobCount = document.getElementById("job-count")!;
jobCount.textContent = `Showing ${displayedJob}/${totalJob} jobs`;
drawJobBar(totalJob, jobCountMap);
// if we aren't filtering the output, cap the histogram y axis to 2 hours because it
// contains the bulk of our jobs
let max = Number.MAX_SAFE_INTEGER;
if (totalJob === allBuilds.items.length) {
max = 2 * 3600;
}
drawJobHistogram(totalJob, jobHistogram, now - (12 * 3600), now, max);
if (rerunStatus === "gh_redirect") {
modal.style.display = "block";
rerunCommand.innerHTML = "Rerunning that job requires GitHub login. Now that you're logged in, try again";
}
// we need to upgrade DOM for new created dynamic elements
// see https://getmdl.io/started/index.html#dynamic
componentHandler.upgradeDom();
}