diff --git a/static/js/dashboard.js b/static/js/dashboard.js deleted file mode 100644 index 3cb1d80fe..000000000 --- a/static/js/dashboard.js +++ /dev/null @@ -1,572 +0,0 @@ -class Badge { - - constructor(base, key, date) { // Class constructor - this.base = base; // Class body/properties - this.key = key; // Class body/properties - const fields = key.split("-"); - this.pattern = fields[0]; - this.platform = fields[1]; - if ( fields[2] != "ci.json" ) { - this.version = fields[2]; - } else { - this.version = ""; - } - this.date = date.substr(5, 5); - } - - string() { - return this.key; - } - - getLabel(field) { - if(field == "pattern") { - return stringForKey(this.platform)+" "+this.version; - } - if(field == "platform") { - return stringForKey(this.pattern)+" - "+this.version; - } - if(field == "version") { - return stringForKey(this.pattern)+" : "+stringForKey(this.platform); - } - if(field == "date") { - return stringForKey(this.pattern)+" : "+stringForKey(this.platform)+" "+this.version; - } - return stringForKey(this.pattern)+" : "+ stringForKey(this.platform)+" "+this.version+" @ "+ this.date; - } - - getURI() { - return this.base+"/"+this.key; - } - - getJenkinsURI() { - return jenkins_base_url(this.pattern)+"/job/"+jenkins_job(this.pattern, this.platform, this.version)+"/lastBuild/"; - } - - getJiraSearch() { - return "https://issues.redhat.com/issues/?jql=project%3D%22Validated%20Patterns%22%20and%20labels%20in%20(ci-fail)%20and%20component%3D"+jira_component(this.pattern)+"%20and%20status%20not%20in%20(Closed)"; - } -} - -function sleep(ms) { - return new Promise(resolve => setTimeout(resolve, ms)); -} - -function filterBadges(badges, field, value) { - if ( field === "date" ) { - return badges.filter(badge => badge.date === value); - } - if ( field === "pattern" ) { - return badges.filter(badge => badge.pattern === value); - } - if ( field === "platform" ) { - return badges.filter(badge => badge.platform === value); - } - if ( field === "version" ) { - return badges.filter(badge => badge.version === value); - } - return badges; -} - -function rowTitle(field, value) { - if ( field === "date" ) { - return stringForKey(value); - } - if ( field === "pattern" ) { - return stringForKey(value); - } - if ( field === "platform" ) { - return stringForKey(value); - } - return value; -} - -function get_shield_url(badge, label, links) { - base = 'https://img.shields.io/endpoint?style=flat&logo=git&logoColor=white'; - // TODO: Replace the second link with the CI Job URL - if ( links === "internal") { - base = base +'&link='+ encodeURI(badge.getJenkinsURI()) + '&link=' + encodeURI(badge.getJiraSearch()); - } else { - base = base +'&link='+ encodeURI(badge.getURI()) + '&link=' + encodeURI(repo_url(badge.pattern)); - } - if ( label != "" ) { - base = base +'&label='+ encodeURI(label); - } - return base + '&url=' + encodeURI(badge.getURI()); -} - -function get_key_url(color, label, links) { - uri = 'https://validatedpatterns.io/'+color+'.json'; - base = 'https://img.shields.io/endpoint?style=flat&logo=git&logoColor=white'; - - base = base +'&link='+ encodeURI("/") + '&link=' + encodeURI(uri); - if ( label != "" ) { - base = base +'&label='+ encodeURI(label); - } else if ( links === "internal" ) { - base = base +'&label='+ encodeURI("Job name with link"); - } else { - base = base +'&label='+ encodeURI("Job name"); - } - return base + '&url=' + encodeURI(uri); -} - -function jira_component(pattern) { - const dictionary = { - aegitops: "ansible-edge", - devsecops: "devsecops", - manuela: "industrial-edge", - mcgitops: "multicloud-gitops", - medicaldiag: "medical-diagnosis" - }; - - if ( pattern in dictionary ) { - return dictionary[pattern]; - } - return pattern; -} - -function jenkins_job(pattern, platform, version) { - ciplatform = platform - if ( platform == "azr" ) { - ciplatform = "azure"; - } - - return pattern+"-"+ciplatform+"-ocp"+version+"-interop"; -} - -function jenkins_base_url(key) { - const prefix = 'https://mps-jenkins-csb-mpqe.apps.ocp-c1.prod.psi.redhat.com/job/ValidatedPatterns'; - const dictionary = { - aegitops: "AnsibleEdgeGitops", - devsecops: "MulticlusterDevSecOps", - manuela: "Manuela", - mcgitops: "MultiCloudGitops", - medicaldiag: "MedicalDiagnosis" - }; - - if ( key in dictionary ) { - return prefix + '/job/' + dictionary[key]; - } - return prefix; -} - -function pattern_url(key) { - const prefix = 'https://validatedpatterns.io/patterns/' - const dictionary = { - aegitops: "ansible-edge-gitops", - devsecops: "devsecops", - manuela: "industrial-edge", - mcgitops: "multicloud-gitops", - medicaldiag: "medical-diagnosis" - }; - - if ( key in dictionary ) { - return prefix + dictionary[key] + '/'; - } - return prefix + key + '/'; -} - -function repo_url(key) { - const prefix = 'https://github.com/hybrid-cloud-patterns/' - const dictionary = { - aegitops: "ansible-edge-gitops", - devsecops: "multicluster-devsecops", - manuela: "industrial-edge", - mcgitops: "multicloud-gitops", - medicaldiag: "medical-diagnosis" - }; - - if ( key in dictionary ) { - return prefix + dictionary[key] + '/'; - } - return prefix + key + '/'; -} - -function stringForKey(key) { - const dateRegex = /^(\d{2})-(\d{2})$/; - const months = [ - "Jan", - "Feb", - "March", - "April", - "May", - "June", - "July", - "Aug", - "Sept", - "Oct", - "Nov", - "Dec" - ]; - - const dictionary = { - aegitops: "Ansible Edge", - devsecops: "DevSecOps", - manuela: "Industrial", - mcgitops: "Core GitOps", - medicaldiag: "Image Classification", - azr: "Azure", - gcp: "GCP", - aws: "AWS" - }; - - if ( key in dictionary ) { - return dictionary[key]; - } - - const matches = dateRegex.exec(key); - if ( matches ) { - monthIndex = parseInt(matches[1], 10) - 1; - return months[monthIndex] + " "+ matches[2]; - } - return key; -} - -function getBadgeDate(xml) { - parent = xml.parentNode; - for (j = 0; j < parent.childNodes.length; j++) { - if ( parent.childNodes[j].nodeName == "LastModified" ) { - return parent.childNodes[j].childNodes[0].nodeValue; - } - } - return "2033-03-22T16:45:47.966Z"; -} - -function getUniqueValues(badges, field){ - results = []; - badges.forEach(b => { - if (field == 'date' && ! results.includes(b.date) ) { - results.push(b.date); - } else if (field == 'platform' && ! results.includes(b.platform) ) { - results.push(b.platform); - } else if (field == 'pattern' && ! results.includes(b.pattern) ) { - results.push(b.pattern); - } else if (field == 'version' && b.version != "" && ! results.includes(b.version) ) { - results.push(b.version); - } - }); - - if ( field === "pattern" ) { - return results.sort(function(a, b){return -1 * a.localeCompare(b)}); - } else if ( field === "version" ) { - return results.sort(function(a, b){return -1 * a.localeCompare(b)}); - } else if ( field === "date" ) { - return results.sort(function(a, b){return -1 * a.localeCompare(b)}); - } - - return results.sort(); -} - -function patternSort(a, b){ - if ( a.pattern != b.pattern ) { - return a.pattern.localeCompare(b.pattern); - } - if ( a.platform != b.platform ) { - return a.platform.localeCompare(b.platform); - } - if ( a.version != b.version ) { - return -1 * a.version.localeCompare(b.version); - } - return -1 * a.date.localeCompare(b.date) -} - -function patternVertSort(a, b){ - if ( a.version != b.version ) { - return -1 * a.version.localeCompare(b.version); - } - if ( a.pattern != b.pattern ) { - return a.pattern.localeCompare(b.pattern); - } - if ( a.platform != b.platform ) { - return a.platform.localeCompare(b.platform); - } - return -1 * a.date.localeCompare(b.date) -} - -function toCamelCase(str) { - return str.replace(/(?:^\w|[A-Z]|\b\w|\s+)/g, function(match, index) { - if (+match === 0) return ""; // or if (/\s+/.test(match)) for white spaces - return index === 0 ? match.toLowerCase() : match.toUpperCase(); - }); -} - -function toTitleCase(str) { - return str.replace( - /\w\S*/g, - function(txt) { - return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); - } - ); -} - -function createKeyTable(rows, links) { - //document.getElementById('data').innerHTML = 'Hello World!'; - - tableText = "
"; - tableText = tableText + "

Key

"; - tableText = tableText + ""; - - tableText = tableText + ""; - rows.forEach(r => { - tableText = tableText + ""; - }); - tableText = tableText + ""; - - tableText = tableText + "
  
"; - return tableText; -} - -function getContentPrefix() { - prefix = "" - if ( false ) { - prefix = prefix + ""; - } else { - prefix = prefix + "
"; - prefix = prefix + "
"; - prefix = prefix + "
"; - prefix = prefix + "
"; - } else { - prefix = prefix + " "; - prefix = prefix + " "; - prefix = prefix + " "; - prefix = prefix + " "; - } - prefix = prefix + ""; - return prefix; -} - - -function createFilteredHorizontalTable(badges, field, value, titles, links = "public", max = 20) { - //document.getElementById('data').innerHTML = 'Hello World!'; - - tableText = "
"; - if ( titles ) { - tableText = tableText + "

"+toTitleCase("By "+field)+"

"; - } - tableText = tableText + ""; - - rows = getUniqueValues(badges, field); - - rows.forEach(r => { - pBadges = filterBadges(badges, field, r); - - tableText = tableText + ""; - if ( value == null && field == "pattern" ) { - tableText = tableText + ""; - } else if ( value == null ) { - tableText = tableText + ""; - } - - let index = 0; - max = 100; - tableText = tableText + getContentPrefix(); - - pBadges.forEach(b => { - if ( pBadges.length > max && index >= max ) { - tableText = tableText + ""; - index = 0; - } - - tableText = tableText + " "; - tableText = tableText + " " - index = index + 1; - }); - tableText = tableText + getContentSuffix(); - tableText = tableText + ""; - }); - - return tableText + "
" + rowTitle(field, r) + "" + rowTitle(field, r) + "
" - tableText = tableText + "'"; - //tableText = tableText + "
"; -} - -function createFilteredVerticalTable(badges, field, value, titles, links = "public") { - //document.getElementById('data').innerHTML = 'Hello World!'; - - tableText = "
"; - if ( titles ) { - tableText = tableText + "

"+toTitleCase("By "+field)+"

"; - } - tableText = tableText + ""; - - //style='vertical-align:top' - rows = getUniqueValues(badges, field); - - fieldColumns = []; - tableText = tableText + ""; - rows.forEach(r => { - fieldColumns.push(filterBadges(badges, field, r)); - - // https://stackoverflow.com/questions/43775947/dynamically-generate-table-from-json-array-in-javascript - if ( value == null && field != "date" ) { - tableText = tableText + ""; - } - }); - tableText = tableText + ""; - - any = true; - row = 0; - numColumns = fieldColumns.length; - while (any) { - any = false; - tableText = tableText + ""; - for ( i = 0; i < numColumns; i++) { - blist = fieldColumns[i]; - tableText = tableText + ""; - } - tableText = tableText + ""; - row = row + 1; - } - - return tableText + "
" + rowTitle(field, r) + "
"; - if ( blist.length > row ) { - b = blist[row]; - // console.log(b); - tableText = tableText + "'"; - any = true; - } - tableText = tableText + "
"; - -} - -function getBadges(xmlText, bucket_url, badge_set) { - parser = new DOMParser(); - var xmlDoc = parser.parseFromString(xmlText, "application/xml"); - const errorNode = xmlDoc.querySelector("parsererror"); - if (errorNode) { - // parsing failed - console.log("failed: "+errorNode); - return; - } - - var badges = []; - var entries = xmlDoc.getElementsByTagName("Key"); - // var entries = xmlDoc.childNodes.getElementsByTagName("Contents"); - // var entries = xmlDoc.childNodes; - // document.getElementById("data").innerHTML = errorNode.childNodes[1].nodeValue; - - l = entries.length; - console.log("processing +entries[0].nodeName+ "+l); - - for (i = 0; i < l; i++) { - // entries[i].childNodes[0].nodeValue - key = entries[i].childNodes[0].nodeValue; - - if (badge_set == "GA" && key.endsWith("stable-badge.json") ) { - badges.push(new Badge(bucket_url, key, getBadgeDate(entries[i]))); - } else if (badge_set == "early" && key.endsWith("prerelease-badge.json") ) { - badges.push(new Badge(bucket_url, key, getBadgeDate(entries[i]))); - } else if (badge_set == "all" && key.endsWith("-badge.json") ) { - badges.push(new Badge(bucket_url, key, getBadgeDate(entries[i]))); - } else { - console.log("Skipping: " + key + " - "+badge_set); - } - } - - return badges; -} - -function processBucketXML(text, options) { - const filter_field = options.get("filter_field"); - const filter_value = options.get("filter_value"); - const links = options.get("links"); - badges = getBadges(text, options.get('bucket'), options.get('sets')); - - htmlText = ""; - - if (filter_field != null ) { - if ( filter_value != null) { - badges = filterBadges(badges, filter_field, filter_value); - } - badges.sort(patternSort); - numElements = Math.min(Math.floor(window.innerWidth/140), 6); - // if ( window.innerWidth < 1200 ) { - // No left or right sidebar - // numElements = Math.floor(window.innerWidth/140); - // } else if ( window.innerWidth < 1450 ) { - // No right sidebar - // numElements = Math.floor((window.innerWidth-290)/140); - // } else { - // Left and right sidebar, but fixed inner width - // numElements = Math.floor(832/140); - // } - htmlText = createFilteredHorizontalTable(badges, filter_field, filter_value, false, links, numElements); - - } else { - htmlText = createKeyTable(["green", "yellow", "red"], links); - - badges.sort(function(a, b){return -1 * a.date.localeCompare(b.date)}); - htmlText = htmlText + createFilteredHorizontalTable(badges, "date", null, true, links, Math.floor((window.innerWidth-200)/200)); - - badges.sort(patternVertSort); - htmlText = htmlText + createFilteredHorizontalTable(badges, "pattern", null, true, links, Math.floor((window.innerWidth-200)/140)); - htmlText = htmlText + createFilteredVerticalTable(badges, "platform", null, true, links); - htmlText = htmlText + createFilteredVerticalTable(badges, "version", null, true, links); - } - document.getElementById(options.get('target')).innerHTML = htmlText; -} - -function getBucketOptions(input) { - const options = new Map(); - const queryString = window.location.search; - const urlParams = new URLSearchParams(queryString); - - options.set('sets', 'GA'); - options.set('links', 'public'); - options.set('target', 'dataset'); - options.set('bucket', 'https://storage.googleapis.com/hcp-results'); - - // input.bucket , or input["bucket"] - - const fields = [ "sets", "bucket", "target", "filter_field", "filter_value", "links" ]; - for ( i=0; i < fields.length; i++) { - const key = fields[i]; - var value = input[key]; - if ( value == null ) { - value = urlParams.get(fields[i]); - } - if ( value != null ) { - console.log(key, value); - options.set(fields[i], value); - } - } - - const sections = [ "date", "version", "platform", "pattern" ]; - - filter_field = options.get("filter_field"); - if ( filter_field == null ) { - for ( i=0; i < sections.length; i++) { - if ( urlParams.get(sections[i]) != null ) { - options.set("filter_field", sections[i]); - if (options.get("filter_value") == null && urlParams.get(sections[i]) != null) { - options.set("filter_value", urlParams.get(sections[i])); - } - } - } - } - - console.log(options); - return options; -} - -function obtainBadges(inputs) { - let req = new XMLHttpRequest(); - const options = getBucketOptions(inputs); - req.open('GET', options.get('bucket')); - req.onload = function() { - if (req.status == 200) { - processBucketXML(req.responseText, options); - } else { - console.log("Error: " + req.status); - } - } - req.send(); -} diff --git a/static/js/dashboard.v2.js b/static/js/dashboard.v2.js index 8ac24c7f7..0b33a9cb4 100644 --- a/static/js/dashboard.v2.js +++ b/static/js/dashboard.v2.js @@ -4,30 +4,76 @@ class Badge { - constructor (base, key, date) { + constructor(base, key, date) { this.base = base this.key = key + this._json = null + this.operator = 'N/A' + this.date = date.substr(0, 10) + this.pattern = '' + this.platform = '' + this.version = '' + + if (isNewCiBadgeBase(base)) { + return + } + const fields = key.split('-') this.pattern = fields[0] this.platform = fields[1] - if (fields[fields.length-2] == 'operator') { - this.operator = fields[fields.length-5] + if (fields[fields.length - 2] == 'operator') { + this.operator = fields[fields.length - 5] } else { - this.operator = 'N/A' + this.operator = 'N/A' } if (fields[2] != 'ci.json') { - this.version = fields[2] + this.version = fields[2] } else { - this.version = '' + this.version = '' } - this.date = date.substr(0, 10) } - string () { + applyJson(json) { + this._json = json + + if (!isNewCiBadgeBase(this.base)) { + return this + } + + var patternId = ciPatternIdFromJson(json, this.key) + + if (patternId != null) { + this.pattern = patternId + } + + var platform = normalizeInfraProvider(json.infraProvider) + if (platform != null && json.infraProvider) { + this.platform = platform + } + + var version = ocpMajorMinor(json.openshiftVersion) + if (version != null) { + this.version = version + } else if (json.openshiftVersion != null && json.openshiftVersion !== '') { + this.version = String(json.openshiftVersion) + } + + if (json.date != null && json.date !== '') { + this.date = String(json.date).substr(0, 10) + } + + if (json.operator != null && json.operator !== '') { + this.operator = json.operator + } + + return this + } + + string() { return this.key } - getURI () { + getURI() { return this.base + '/' + this.key } } @@ -36,18 +82,10 @@ class Badge { // Helper Functions // ============================================ -function getJiraSearch (pattern) { - return 'https://issues.redhat.com/issues/?jql=project%3D%22Validated%20Patterns%22%20and%20labels%20in%20(ci-fail)%20and%20component%3D' + jira_component(pattern) + '%20and%20status%20not%20in%20(Closed)' -} - -function sleep (ms) { - return new Promise(resolve => setTimeout(resolve, ms)) -} - // Still present in badge buckets but no longer in the CI matrix (omit from dashboard) var CI_DASHBOARD_EXCLUDED_OCP_VERSIONS = ['4.19'] -function excludeRetiredOcpVersionsFromDashboard (badges) { +function excludeRetiredOcpVersionsFromDashboard(badges) { if (!badges || badges.length === 0) { return badges || [] } @@ -56,7 +94,7 @@ function excludeRetiredOcpVersionsFromDashboard (badges) { }) } -function filterBadges (badges, field, value) { +function filterBadges(badges, field, value) { if (field === 'pattern') { return badges.filter(badge => badge.pattern === value) } @@ -75,19 +113,6 @@ function filterBadges (badges, field, value) { return badges } -function jira_component (pattern) { - if (pattern == 'aegitops') { - return 'ansible-edge' - } else if (pattern == 'manuela') { - return 'industrial-edge' - } else if (pattern == 'mcgitops') { - return 'multicloud-gitops' - } else if (pattern == 'medicaldiag') { - return 'medical-diagnosis' - } - return pattern -} - // CI pattern keys for patterns in the Archived tier. Keep in sync with // content/patterns/**/_index.* where tier: archived. var ARCHIVED_CI_PATTERNS = { @@ -132,7 +157,83 @@ var CI_PATTERN_DOC_SLUG = { vsk: 'virtualization-starter-kit' } -function pattern_url (key) { +// New CI badge bucket (S3 keys under ci-badges/) — distinct from legacy vp-results filenames. + +var NEW_CI_BADGE_KEY_PREFIX = "https://vp-qe-ci-badges" + +function isNewCiBadgeBase(base) { + return base != null && base.startsWith(NEW_CI_BADGE_KEY_PREFIX) +} + +function normalizeInfraProvider(provider) { + if (provider == null || provider === '') { + return null + } + var p = String(provider).toLowerCase() + if (p === 'azure') { + return 'azr' + } + if (p === 'google' || p === 'gcp') { + return 'gcp' + } + if (p === 'amazon' || p === 'aws') { + return 'aws' + } + return p +} + +function ocpMajorMinor(version) { + if (version == null || version === '') { + return null + } + var match = String(version).match(/^(\d+\.\d+)/) + return match ? match[1] : null +} + +function badgeOcpVersion(badge) { + if (badge == null) { + return null + } + if (badge._json && badge._json.openshiftVersion) { + var fromJson = ocpMajorMinor(badge._json.openshiftVersion) + if (fromJson != null) { + return fromJson + } + } + if (badge.version != null && badge.version !== '' && badge.version !== 'none') { + return badge.version + } + return null +} + +function ciPatternIdFromJson(json, key) { + if (json == null) { + return null + } + if (json.patternName == null || json.patternName === '') { + return null + } + var name = String(json.patternName).toLowerCase() + if (name === 'mcg') { + if (json.variant === 'hub') { + return 'mcgitops' + } + if (String(key).includes("-hosted-")) { + + return 'mcgitopshcp' + } + if (String(key).includes("-single-")) { + return 'mcgitopsstandalone' + } + return 'mcgitops' + } + if (name === 'ansibleedgegitops' || name === 'ansible-edge' || name === 'aegitops') { + return 'aegitops' + } + return name +} + +function pattern_url(key) { if (key == null || key === '') { return '/patterns/' } @@ -150,28 +251,58 @@ function pattern_url (key) { return '/patterns/' + key + '/' } -function stringForKey (key) { +function stringForKey(key) { if (key == 'azr') { return 'Azure' } + if (key == 'agof') { + return 'AnsibleGitopsFramework' + } + if (key == 'coco') { + return 'CoCo' + } if (key == 'gcp') { return 'Google' } if (key == 'aws') { - return 'Amazon' + return 'AWS' } if (key == 'aegitops') { - return 'Ansible Edge' + return 'AnsibleEdgeGitops' } if (key == 'devsecops') { return 'DevSecOps' } - if (key == 'manuela') { - return 'Industrial' + if (key == 'manuela' || key == 'industrialedge') { + return 'IndustrialEdge' } if (key == 'mcgitops') { return 'Core GitOps' } + if (key == 'mcgitopshcp') { + return 'MultiCloudGitopsHypershift' + } + if (key == 'mcgitopsstandalone') { + return 'MultiCloudGitopsStandalone' + } + if (key == 'ragllm') { + return 'RAG-LLM' + } + if (key == 'travelops') { + return 'TravelOps' + } + if (key == 'layeredzerotrust') { + return 'LayeredZeroTrust' + } + if (key == 'hypershift') { + return 'Hypershift' + } + if (key == 'openshiftai') { + return 'OpenShiftAI' + } + if (key == 'patternsoperator') { + return 'PatternsOperator' + } if (key == 'medicaldiag') { return 'Medical Diagnosis' } @@ -190,30 +321,30 @@ function stringForKey (key) { return key } -function getBadgeDate (xml) { - parent = xml.parentNode - for (j = 0; j < parent.childNodes.length; j++) { +function getBadgeDate(xml) { + let parent = xml.parentNode + for (let j = 0; j < parent.childNodes.length; j++) { if (parent.childNodes[j].nodeName == 'LastModified') { - return parent.childNodes[j].childNodes[0].nodeValue + return parent.childNodes[j].childNodes[0].nodeValue } } return '2033-03-22T16:45:47.966Z' } -function getUniqueValues (badges, field) { - results = [] +function getUniqueValues(badges, field) { + let results = [] badges.forEach(b => { if (field == 'date' && !results.includes(b.date)) { - results.push(b.date) + results.push(b.date) } else if (field == 'platform' && !results.includes(b.platform)) { - results.push(b.platform) + results.push(b.platform) } else if (field == 'pattern' && !results.includes(b.pattern)) { - results.push(b.pattern) + results.push(b.pattern) } else if (field == 'version' && b.version != '' && !results.includes(b.version)) { - results.push(b.version) + results.push(b.version) } else if (field == 'operator' && b.operator != '' && !results.includes(b.operator)) { - results.push(b.operator) + results.push(b.operator) } }) @@ -228,7 +359,7 @@ function getUniqueValues (badges, field) { return results.sort() } -function patternSort (a, b) { +function patternSort(a, b) { if (a.pattern != b.pattern) { return a.pattern.localeCompare(b.pattern) } @@ -241,7 +372,7 @@ function patternSort (a, b) { return -1 * a.date.localeCompare(b.date) } -function patternVertSort (a, b) { +function patternVertSort(a, b) { if (a.version != b.version) { return -1 * a.version.localeCompare(b.version) } @@ -254,78 +385,68 @@ function patternVertSort (a, b) { return -1 * a.date.localeCompare(b.date) } -function toCamelCase (str) { - return str.replace(/(?:^\w|[A-Z]|\b\w|\s+)/g, function (match, index) { - if (+match === 0) return '' - return index === 0 ? match.toLowerCase() : match.toUpperCase() - }) -} - -function toTitleCase (str) { +function toTitleCase(str) { return str.replace( - /\w\S*/g, - function (txt) { - return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase() - } - ) + /\w\S*/g, + function (txt) { + return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase() + } + ) } // ============================================ // Network / JSON Fetching // ============================================ -function jsonSuccess() { - if (this.status < 200 || this.status >= 300) { - console.warn('getJSON HTTP', this.status, this.responseURL) - } - this.callback.apply(this, this.arguments) -} - -function jsonError() { - console.error(this.statusText); +function badgeStatusColor(badge) { + if (badge == null) return 'unavailable' + if (badge._json != null && badge._json.color) return badge._json.color + if (badge._color) return badge._color + return 'unavailable' } -function parseBadgeJsonResponseText (text) { - if (text == null) return null - var t = String(text).replace(/^\uFEFF/, '').trim() - if (t.length === 0) return null - var first = t.charCodeAt(0) - if (first === 0x3C) return null // '<' — XML/HTML/S3 error bodies - try { - return JSON.parse(t) - } catch (e) { - return null +function enrichBadgesFromJson(badges) { + if (!badges || badges.length === 0) { + return Promise.resolve(badges || []) } -} - -function getJSON(url, callback, ...args) { - const jsonRequest = new XMLHttpRequest(); - jsonRequest.callback = callback; - jsonRequest.arguments = args; - jsonRequest.onload = jsonSuccess; - jsonRequest.onerror = jsonError; - jsonRequest.open("GET", url, true); - jsonRequest.send(null); + return Promise.all(badges.map(function (badge) { + return fetch(badge.getURI()) + .then(function (response) { + if (!response.ok) { + throw new Error('HTTP error: ' + response.status) + } + return response.json() + }) + .then(function (json) { + return badge.applyJson(json) + }) + .catch(function (err) { + console.error('Fetch problem: ' + err.message + ' when fetching ' + badge.getURI()) + return badge + }) + })).then(function () { + return badges + }) } // ============================================ // Legacy Badge Rendering (for pattern page embeds) // ============================================ -function getLabel (field, json_obj) { +function getLabel(field, badge) { if (field == 'pattern') { - return stringForKey(json_obj.infraProvider) + ' ' + json_obj.openshiftVersion + return platformDisplayName(badge._json.infraProvider) + ' ' + badge._json.openshiftVersion } if (field == 'platform') { - return stringForKey(json_obj.patternName) + ' - ' + json_obj.openshiftVersion + return stringForKey(badge.pattern) + ' - ' + badge._json.openshiftVersion } if (field == 'version') { - return stringForKey(json_obj.patternName) + ' : ' + stringForKey(json_obj.infraProvider) + return stringForKey(badge.pattern) + ' : ' + platformDisplayName(badge._json.infraProvider) } - return stringForKey(json_obj.patternName) + ' : ' + stringForKey(json_obj.infraProvider) + ' ' + json_obj.openshiftVersion + return stringForKey(badge.pattern) + ' : ' + platformDisplayName(badge._json.infraProvider) + ' ' + badge._json.openshiftVersion } -function rowTitle (field, value) { +function rowTitle(field, value) { if (field === 'pattern') { return stringForKey(value) } @@ -335,112 +456,81 @@ function rowTitle (field, value) { return value } -function renderSetButtons(sets){ - var currentURL = new URL(window.location.href) - if (currentURL.searchParams.get('view') === 'classic') { - ;['pattern', 'platform', 'version', 'date', 'sort'].forEach(function (k) { - currentURL.searchParams.delete(k) - }) - currentURL.searchParams.set('view', 'classic') - } - const queryString = window.location.search - const urlParams = new URLSearchParams(queryString) - var setList = {'GA': 'GA', 'early': 'Pre-release', 'all': 'All'} - var buttonText = "" - buttonText += '' - return buttonText -} - -function renderSingleBadge (key, field, linkType, badge_url) { - var json_obj = parseBadgeJsonResponseText(this.responseText) - if (json_obj == null) { - var failedEl = document.getElementById(key + '-' + field) - if (failedEl) { - var fallbackLink = document.createElement('a') - fallbackLink.href = badge_url - fallbackLink.className = 'ci-label ci-label-load-error' - fallbackLink.target = '_blank' - fallbackLink.rel = 'noopener noreferrer' - fallbackLink.textContent = 'Unavailable' - fallbackLink.setAttribute('aria-label', 'Open badge URL in a new tab') - failedEl.replaceWith(fallbackLink) - } - console.warn('Badge JSON parse failed:', badge_url) - return - } - var branchLabel = json_obj.patternBranch - var color = json_obj.color - - var envLabel = getLabel(field, json_obj) - if (badge_url.endsWith("stable-badge.json") ) { - var badgeClass = "ci-label-environment-stable"; - } else if (badge_url.endsWith("prerelease-badge.json") ) { - var badgeClass = "ci-label-environment-prerelease"; - } else if (badge_url.endsWith("operator-badge.json") ) { - var badgeClass = "ci-label-environment-prerelease"; - branchLabel = json_obj.triggerSource +" "+json_obj.triggerVersion; - } else if (badge_url.endsWith("nightly-badge.json") ) { - var badgeClass = "ci-label-environment-prerelease"; - branchLabel = "nightly ("+ json_obj.patternBranch+")"; - } - - if ( linkType == "internal") { - envLink = json_obj.jenkinsURL - branchLink = encodeURI(getJiraSearch(json_obj.patternName)); - } else { - envLink = encodeURI(badge_url) - branchLink = encodeURI(json_obj.patternRepo) - } - - badgeText = '' - if (envLink != null) { - badgeText += '' + envLabel + '' - } else { - badgeText += '' + envLabel + '' - } - if (branchLink != null) { - badgeText += '' + branchLabel + '' +function renderSetButtons(sets) { + var currentURL = new URL(window.location.href) + if (currentURL.searchParams.get('view') === 'classic') { + ;['pattern', 'platform', 'version', 'date', 'sort'].forEach(function (k) { + currentURL.searchParams.delete(k) + }) + currentURL.searchParams.set('view', 'classic') + } + var setList = { 'GA': 'GA', 'early': 'Pre-release', 'all': 'All' } + var buttonText = "" + buttonText += '' + return buttonText } -function renderBadgePlaceholder (key, field) { - return '' +function renderBadgeHtml(badge, field) { + let badge_url = badge.getURI() + let json_obj = badge._json + if (json_obj == null) { + return 'Unavailable' + } + + let branchLabel = json_obj.patternBranch + let color = json_obj.color || 'green' + let envLabel = getLabel(field, badge) + let badgeClass = 'ci-label-environment-prerelease' + + if (badge_url.endsWith('stable-badge.json')) { + badgeClass = 'ci-label-environment-stable' + } else if (badge_url.endsWith('operator-badge.json')) { + branchLabel = json_obj.triggerSource + ' ' + json_obj.triggerVersion + } else if (badge_url.endsWith('nightly-badge.json')) { + branchLabel = 'nightly (' + json_obj.patternBranch + ')' + } + + let envLink = encodeURI(badge_url) + let branchLink = json_obj.patternRepo != null ? encodeURI(json_obj.patternRepo) : null + let badgeText = '' + badgeText += '' + envLabel + '' + if (branchLink != null) { + badgeText += '' + branchLabel + '' + } else { + badgeText += '' + branchLabel + '' + } + badgeText += '' + return badgeText } -function renderBadges (badges, field, value, links) { +function renderBadges(badges, field, value) { + let pBadges = badges if (field != null && value != null) { - pBadges = filterBadges(badges, field, value) - } else { - pBadges = badges + pBadges = filterBadges(badges, field, value) } - badgeText = '
' - pBadges.forEach(b => { - badgeText += renderBadgePlaceholder(b.string(), field) - getJSON(b.getURI(), renderSingleBadge, b.string(), field, links, b.getURI()) + let badgeText = '
' + pBadges.forEach(function (b) { + badgeText += renderBadgeHtml(b, field) }) badgeText += '
' return badgeText } -function legacyFilteredHref (field, rowValue) { +function legacyFilteredHref(field, rowValue) { var cur = new URLSearchParams(window.location.search) if (cur.get('view') !== 'classic') { return '?' + field + '=' + encodeURIComponent(rowValue) @@ -453,38 +543,37 @@ function legacyFilteredHref (field, rowValue) { return '?' + next.toString() } -function createFilteredHorizontalTable (badges, field, value, titles, links) { - tableText = "
" +function createFilteredHorizontalTable(badges, field, value, titles) { + let tableText = "
" if (titles) { tableText += "
" + toTitleCase('By ' + field) + '
' } tableText += "
" - rows = getUniqueValues(badges, field) + let rows = getUniqueValues(badges, field) tableText += "
" rows.forEach(r => { tableText += "
" tableText += "
" if (value == null && field == 'pattern') { - tableText += "" + rowTitle(field, r) + '' + tableText += "" + rowTitle(field, r) + '' } else if (value == null) { - tableText += "" + rowTitle(field, r) + '' + tableText += "" + rowTitle(field, r) + '' } tableText += '
' tableText += '
' tableText += '
' - tableText += renderBadges(badges, field, r, links) + tableText += renderBadges(badges, field, r) tableText += '
' }) tableText += '
' return tableText + '
' } -function processBadgesLegacy (badges, options) { +function processBadgesLegacy(badges, options) { const filter_field = options.get('filter_field') const filter_value = options.get('filter_value') - const links = options.get("links") var htmlText = "" if (options.get('show_dashboard_tabs') === true) { @@ -495,9 +584,9 @@ function processBadgesLegacy (badges, options) { if (filter_field === 'date') { badges.sort(function (a, b) { return -1 * a.date.localeCompare(b.date) }) if (filter_value != null && filter_value != "all") { - htmlText += renderBadges(badges, filter_field, filter_value, links) + htmlText += renderBadges(badges, filter_field, filter_value) } else if (filter_value == "all") { - htmlText += createFilteredHorizontalTable(badges, filter_field, null, true, links) + htmlText += createFilteredHorizontalTable(badges, filter_field, null, true) } } else if (filter_field != null) { if (filter_value != null && filter_value != "all") { @@ -505,21 +594,21 @@ function processBadgesLegacy (badges, options) { } badges.sort(patternSort) if (filter_value != null && filter_value != "all") { - htmlText += renderBadges(badges, filter_field, filter_value, links) + htmlText += renderBadges(badges, filter_field, filter_value) } else if (filter_value == "all") { - htmlText += createFilteredHorizontalTable(badges, filter_field, null, true, links) + htmlText += createFilteredHorizontalTable(badges, filter_field, null, true) } } else { // Classic home: same long-scroll layout as pre-redesign CI page — all groupings at once badges.sort(function (a, b) { return -1 * a.date.localeCompare(b.date) }) badges.sort(patternVertSort) - htmlText += createFilteredHorizontalTable(badges, 'date', null, true, links) - htmlText += createFilteredHorizontalTable(badges, 'pattern', null, true, links) - htmlText += createFilteredHorizontalTable(badges, 'platform', null, true, links) - htmlText += createFilteredHorizontalTable(badges, 'version', null, true, links) + htmlText += createFilteredHorizontalTable(badges, 'date', null, true) + htmlText += createFilteredHorizontalTable(badges, 'pattern', null, true) + htmlText += createFilteredHorizontalTable(badges, 'platform', null, true) + htmlText += createFilteredHorizontalTable(badges, 'version', null, true) var setsVal = options.get('sets') || 'GA' if (String(setsVal).includes('all') || String(setsVal).includes('early')) { - htmlText += createFilteredHorizontalTable(badges, 'operator', null, true, links) + htmlText += createFilteredHorizontalTable(badges, 'operator', null, true) } } @@ -530,16 +619,16 @@ function processBadgesLegacy (badges, options) { // New Dashboard Rendering (for CI status page) // ============================================ -function statusLabel (color) { +function statusLabel(color) { if (color === 'green') return 'Passed' if (color === 'yellow') return 'CI infrastructure failure' if (color === 'red') return 'CI test failure' return 'Unknown' } -function platformDisplayName (platform) { +function platformDisplayName(platform) { var names = { - 'aws': 'AWS (us-east-1)', + 'aws': 'AWS', 'azr': 'Azure', 'gcp': 'Google Cloud', 'nutanix': 'Nutanix', @@ -548,7 +637,7 @@ function platformDisplayName (platform) { return names[platform] || stringForKey(platform) } -function platformIconSvg (platform) { +function platformIconSvg(platform) { if (platform === 'aws' || platform === 'gcp') { return '' } @@ -561,7 +650,7 @@ function platformIconSvg (platform) { return '' } -function timeAgo (dateStr) { +function timeAgo(dateStr) { if (!dateStr) return '' var date = new Date(dateStr + 'T00:00:00') var now = new Date() @@ -584,24 +673,11 @@ function timeAgo (dateStr) { return years + (years === 1 ? ' year ago' : ' years ago') } -function sanitizeId (str) { +function sanitizeId(str) { return str.replace(/\./g, '_').replace(/[^a-zA-Z0-9_-]/g, '-') } -function getLatestPerPatternPlatform (badges) { - var seen = {} - var result = [] - badges.forEach(function (b) { - var compositeKey = b.pattern + '-' + b.platform - if (!seen[compositeKey]) { - seen[compositeKey] = true - result.push(b) - } - }) - return result -} - -function getCurrentTab () { +function getCurrentTab() { var params = new URLSearchParams(window.location.search) if (params.get('view') === 'classic') return 'classic' if (params.get('date') != null) return 'history' @@ -615,11 +691,11 @@ function getCurrentTab () { return 'overview' } -function buildTabUrl (paramKey) { +function buildTabUrl(paramKey) { var u = new URL(window.location.href) - ;['pattern', 'platform', 'version', 'date', 'view'].forEach(function (k) { - u.searchParams.delete(k) - }) + ;['pattern', 'platform', 'version', 'date', 'view'].forEach(function (k) { + u.searchParams.delete(k) + }) if (paramKey != null) { u.searchParams.set(paramKey, 'all') } @@ -627,7 +703,7 @@ function buildTabUrl (paramKey) { return u.pathname + (qs ? '?' + qs : '') } -function buildPatternDetailHref (pattern) { +function buildPatternDetailHref(pattern) { var u = new URL(window.location.href) u.searchParams.delete('view') u.searchParams.set('pattern', pattern) @@ -635,17 +711,17 @@ function buildPatternDetailHref (pattern) { return u.pathname + (qs ? '?' + qs : '') } -function buildClassicTabUrl () { +function buildClassicTabUrl() { var u = new URL(window.location.href) - ;['pattern', 'platform', 'version', 'date', 'sort'].forEach(function (k) { - u.searchParams.delete(k) - }) + ;['pattern', 'platform', 'version', 'date', 'sort'].forEach(function (k) { + u.searchParams.delete(k) + }) u.searchParams.set('view', 'classic') var qs = u.searchParams.toString() return u.pathname + (qs ? '?' + qs : '') } -function renderTabs (activeTab) { +function renderTabs(activeTab) { var tabs = [ { id: 'overview', label: 'Overview', href: buildTabUrl(null) }, { id: 'infrastructure', label: 'By Platform', href: buildTabUrl('platform') }, @@ -664,7 +740,7 @@ function renderTabs (activeTab) { return html } -function renderSortControl (currentSort) { +function renderSortControl(currentSort) { var html = '
' html += '' html += '' @@ -689,7 +765,7 @@ function renderTimeRangeControl (rangeKey) { return html } -function renderDashboardFiltersRow (rangeKey, currentSort, showSort) { +function renderDashboardFiltersRow(rangeKey, currentSort, showSort) { var html = '
' html += '
' if (showSort) { @@ -700,7 +776,7 @@ function renderDashboardFiltersRow (rangeKey, currentSort, showSort) { return html } -function renderOverviewLegendRow (rangeKey) { +function renderOverviewLegendRow(rangeKey) { var legendWindow = timeRangeLegendPhrase(rangeKey) var legendBody = 'Cards show the latest test per platform for the selected time range. Status bars show ' + @@ -716,7 +792,7 @@ function renderOverviewLegendRow (rangeKey) { return html } -function renderDashboardTableHeader () { +function renderDashboardTableHeader() { return '' + '' + '' + @@ -726,22 +802,34 @@ function renderDashboardTableHeader () { '' } -function renderDashboardTableRow (badge) { - var rowId = sanitizeId(badge.key) +function renderDashboardTableRow(badge) { var patternName = stringForKey(badge.pattern) var platformName = platformDisplayName(badge.platform) - var version = (badge.version && badge.version !== 'none' && badge.version !== '') ? 'OCP ' + badge.version : '' + let version = badgeOcpVersion(badge) != null ? ('OCP ' + badgeOcpVersion(badge)) : '' var time = timeAgo(badge.date) + var color = badgeStatusColor(badge) + var branch = badge._json && badge._json.patternBranch ? badge._json.patternBranch : '' + if (!branch && badge._color) { + branch = 'main' + } var html = '' - html += '' html += '' + if (branch) { + html += '#' + branch + '' + } + html += '' html += '' @@ -752,58 +840,14 @@ function renderDashboardTableRow (badge) { return html } -function updateRowStatus (badgeKey) { - var json_obj = parseBadgeJsonResponseText(this.responseText) - if (json_obj == null) { - var rowIdFail = sanitizeId(badgeKey) - var statusElFail = document.getElementById('ci-status-' + rowIdFail) - if (statusElFail) { - statusElFail.textContent = '' - var dotEl = document.createElement('span') - dotEl.className = 'ci-status-dot unavailable' - statusElFail.appendChild(dotEl) - var failUrl = typeof this.responseURL === 'string' && this.responseURL.length > 0 ? this.responseURL : '' - if (failUrl) { - var linkEl = document.createElement('a') - linkEl.href = failUrl - linkEl.className = 'ci-status-text unavailable ci-status-unavailable-link' - linkEl.target = '_blank' - linkEl.rel = 'noopener noreferrer' - linkEl.textContent = 'Unavailable' - linkEl.setAttribute('aria-label', 'Open badge URL in a new tab') - statusElFail.appendChild(linkEl) - } else { - var spanEl = document.createElement('span') - spanEl.className = 'ci-status-text unavailable' - spanEl.textContent = 'Unavailable' - statusElFail.appendChild(spanEl) - } - } - console.warn('Row badge JSON parse failed:', badgeKey) - return - } - var color = json_obj.color || 'green' - var branch = json_obj.patternBranch || '' - var rowId = sanitizeId(badgeKey) - - var statusEl = document.getElementById('ci-status-' + rowId) - if (statusEl) { - statusEl.innerHTML = - '' + - '' + statusLabel(color) + '' - } - - var branchEl = document.getElementById('ci-branch-' + rowId) - if (branchEl && branch) { - branchEl.textContent = '#' + branch - } -} - -function isProductionSite () { - return window.location.hostname === 'validatedpatterns.io' +function isProductionSite() { + var host = window.location.hostname + return host === 'validatedpatterns.io' || + host === 'localhost' || + host === '127.0.0.1' } -function sampleColorForKey (badgeKey) { +function sampleColorForKey(badgeKey) { var colors = ['green', 'green', 'green', 'green', 'green', 'green', 'yellow', 'red'] var hash = 0 for (var i = 0; i < badgeKey.length; i++) { @@ -813,46 +857,16 @@ function sampleColorForKey (badgeKey) { return colors[Math.abs(hash) % colors.length] } -function simulateSampleStatus (badgeKey) { - var rowId = sanitizeId(badgeKey) - var color = sampleColorForKey(badgeKey) - var branch = 'main' - var hash = 0 - for (var i = 0; i < badgeKey.length; i++) { - hash = ((hash << 5) - hash) + badgeKey.charCodeAt(i) - hash = hash & hash - } - - setTimeout(function () { - var statusEl = document.getElementById('ci-status-' + rowId) - if (statusEl) { - statusEl.innerHTML = - '' + - '' + statusLabel(color) + '' - } - var branchEl = document.getElementById('ci-branch-' + rowId) - if (branchEl) { - branchEl.textContent = '#' + branch - } - }, 100 + Math.abs(hash) % 300) -} - -function renderDashboardTableWithBadges (badges) { +function renderDashboardTableWithBadges(badges) { var html = renderDashboardTableHeader() - var useSample = !isProductionSite() badges.forEach(function (b) { html += renderDashboardTableRow(b) - if (useSample) { - simulateSampleStatus(b.key) - } else { - getJSON(b.getURI(), updateRowStatus, b.key) - } }) html += '
StatusPattern
' - html += '' - html += '' + html += '
' + if (color === 'unavailable') { + html += '' + html += 'Unavailable' + } else { + html += '' + html += '' + statusLabel(color) + '' + } html += '
' html += '' + patternName + '' - html += '
' + platformIconSvg(badge.platform) + ' ' + platformName + '
' return html } -function renderGroupedTables (badges, groupField) { +function renderGroupedTables(badges, groupField) { var groups = getUniqueValues(badges, groupField) var html = '' @@ -883,7 +897,7 @@ function renderGroupedTables (badges, groupField) { var _cardTracker = {} -function groupBadgesByPattern (badges) { +function groupBadgesByPattern(badges) { var groups = {} badges.forEach(function (b) { if (!groups[b.pattern]) { @@ -894,7 +908,7 @@ function groupBadgesByPattern (badges) { return groups } -function getLatestBadgePerPlatform (badges) { +function getLatestBadgePerPlatform(badges) { var seen = {} var result = [] badges.forEach(function (b) { @@ -906,7 +920,7 @@ function getLatestBadgePerPlatform (badges) { return result } -function filterRecentBadges (badges, months) { +function filterRecentBadges(badges, months) { var cutoff = new Date() cutoff.setMonth(cutoff.getMonth() - months) var cutoffStr = cutoff.toISOString().substr(0, 10) @@ -920,31 +934,31 @@ var CI_TIME_RANGE_OPTIONS = [ { id: 'all', label: 'All' } ] -function getTimeRangeFromParams (params) { +function getTimeRangeFromParams(params) { var r = params.get('range') if (r === '6m' || r === '1y' || r === 'all') return r return '3m' } -function monthsForTimeRange (rangeKey) { +function monthsForTimeRange(rangeKey) { if (rangeKey === '6m') return 6 if (rangeKey === '1y') return 12 return 3 } -function applyTimeRangeToBadges (badges, rangeKey) { +function applyTimeRangeToBadges(badges, rangeKey) { if (rangeKey === 'all') return badges.slice() return filterRecentBadges(badges, monthsForTimeRange(rangeKey)) } -function timeRangeLegendPhrase (rangeKey) { +function timeRangeLegendPhrase(rangeKey) { if (rangeKey === 'all') return 'all recorded tests in this view' if (rangeKey === '6m') return 'tests from the last 6 months' if (rangeKey === '1y') return 'tests from the last year' return 'tests from the last 3 months' } -function getLatestBadgePerCombo (badges) { +function getLatestBadgePerCombo(badges) { var seen = {} var result = [] badges.forEach(function (b) { @@ -957,8 +971,7 @@ function getLatestBadgePerCombo (badges) { return result } -function computeCardHealth (tracker) { - if (tracker.loaded < tracker.total) return 'loading' +function computeCardHealth(tracker) { var colors = Object.values(tracker.platforms) var hasRed = colors.indexOf('red') !== -1 var hasYellow = colors.indexOf('yellow') !== -1 @@ -970,7 +983,7 @@ function computeCardHealth (tracker) { return 'green' } -function cardHealthLabel (tracker) { +function cardHealthLabel(tracker) { var colors = Object.values(tracker.platforms) var total = colors.length var greenCount = colors.filter(function (c) { return c === 'green' }).length @@ -978,6 +991,7 @@ function cardHealthLabel (tracker) { var redCount = colors.filter(function (c) { return c === 'red' }).length var unavailableCount = colors.filter(function (c) { return c === 'unavailable' }).length + if (total === 0) return 'No platforms' if (unavailableCount === total) return 'Status unavailable' if (unavailableCount > 0) return 'Some platform status unavailable' @@ -989,10 +1003,10 @@ function cardHealthLabel (tracker) { if (redCount > 0) { return 'Passing on ' + passing + '/' + total + ' platforms' } - return 'Loading...' + return 'Unknown' } -function cardHealthIcon (health) { +function cardHealthIcon(health) { if (health === 'green' || health === 'green-with-infra') { return '' } @@ -1005,114 +1019,50 @@ function cardHealthIcon (health) { return '' } -function updateCardUI (pattern) { - var tracker = _cardTracker[pattern] - if (!tracker) return - var health = computeCardHealth(tracker) - - Object.keys(tracker.platforms).forEach(function (platform) { - var dotEl = document.getElementById('ci-card-dot-' + sanitizeId(pattern) + '-' + sanitizeId(platform)) - if (dotEl) { - dotEl.className = 'ci-card-platform-dot ' + tracker.platforms[platform] - } - }) - - Object.keys(tracker.tests).forEach(function (comboKey) { - var blockEl = document.getElementById('ci-card-block-' + sanitizeId(pattern) + '-' + comboKey) - if (blockEl) { - blockEl.className = 'ci-card-block ' + tracker.tests[comboKey] - } - }) - - var summaryEl = document.getElementById('ci-card-summary-' + sanitizeId(pattern)) - if (summaryEl) { - summaryEl.textContent = cardHealthLabel(tracker) - summaryEl.className = 'ci-card-summary ' + health - } - - var iconEl = document.getElementById('ci-card-icon-' + sanitizeId(pattern)) - if (iconEl) { - iconEl.innerHTML = cardHealthIcon(health) - } - - var cardEl = document.getElementById('ci-card-' + sanitizeId(pattern)) - if (cardEl) { - cardEl.setAttribute('data-health', health) - } - - updateOverallStats() -} - -function updateOverallStats () { - var statsEl = document.getElementById('ci-overall-stats') - if (!statsEl) return - +function renderOverallStatsBar() { var patterns = Object.keys(_cardTracker) var totalPatterns = patterns.length var passingCount = 0 - var infraCount = 0 - var failureCount = 0 - var loading = 0 patterns.forEach(function (p) { var health = computeCardHealth(_cardTracker[p]) - if (health === 'green') passingCount++ - else if (health === 'green-with-infra') { passingCount++; infraCount++ } - else if (health === 'has-failures') failureCount++ - else loading++ + if (health === 'green' || health === 'green-with-infra') passingCount++ }) - if (loading > 0) { - statsEl.innerHTML = '
Loading test results...
' - return + if (totalPatterns === 0) { + return '
No CI results found.
' } var pct = Math.round((passingCount / totalPatterns) * 100) - var html = '
' + var html = '
' html += '
' html += '' + passingCount + ' of ' + totalPatterns + ' patterns passing' html += '
' html += '
' - html += '
' - statsEl.innerHTML = html -} - -function cardStatusCallback (badgeKey, pattern, platform, comboKey) { - var json_obj = parseBadgeJsonResponseText(this.responseText) - var color = json_obj != null ? (json_obj.color || 'green') : 'unavailable' - - if (_cardTracker[pattern]) { - _cardTracker[pattern].tests[comboKey] = color - _cardTracker[pattern].loadedTests++ - - // Platform dot uses latest badge only (first one seen, since badges sorted by date desc) - if (!_cardTracker[pattern].platforms[platform]) { - _cardTracker[pattern].platforms[platform] = color - _cardTracker[pattern].loaded++ - } - - updateCardUI(pattern) - } + html += '
' + return html } -function renderPatternCard (pattern, platformBadges, comboBadges) { +function renderPatternCard(pattern, platformBadges, comboBadges, tracker) { var patternId = sanitizeId(pattern) var patternName = stringForKey(pattern) var latestDate = platformBadges.length > 0 ? platformBadges[0].date : '' + var health = computeCardHealth(tracker) - var html = '' + var html = '' html += '
' - html += '' + cardHealthIcon('loading') + '' + html += '' + cardHealthIcon(health) + '' html += '' + patternName + '' html += '
' - html += '
Loading...
' + html += '
' + cardHealthLabel(tracker) + '
' html += '
' platformBadges.forEach(function (b) { + var platformColor = tracker.platforms[b.platform] || 'unavailable' html += '' - html += '' + html += '' html += '' + stringForKey(b.platform) + '' html += '' }) @@ -1121,7 +1071,8 @@ function renderPatternCard (pattern, platformBadges, comboBadges) { html += '
' comboBadges.slice().reverse().forEach(function (b) { var comboId = sanitizeId(b.platform) + '-' + sanitizeId(b.version) - html += '' + var blockColor = tracker.tests[comboId] || 'unavailable' + html += '' }) html += '
' @@ -1131,7 +1082,7 @@ function renderPatternCard (pattern, platformBadges, comboBadges) { return html } -function renderPatternCards (badges) { +function renderPatternCards(badges) { badges.sort(function (a, b) { return -1 * a.date.localeCompare(b.date) }) var groups = groupBadgesByPattern(badges) @@ -1141,67 +1092,39 @@ function renderPatternCards (badges) { _cardTracker = {} - var html = '
Loading test results...
' - - html += '
' - - var useSample = !isProductionSite() + var html = '
' patternNames.forEach(function (pattern) { var patternBadges = groups[pattern] var latestPerPlatform = getLatestBadgePerPlatform(patternBadges) var latestPerCombo = getLatestBadgePerCombo(patternBadges) - _cardTracker[pattern] = { - total: latestPerPlatform.length, - loaded: 0, + var tracker = { platforms: {}, - tests: {}, - totalTests: latestPerCombo.length, - loadedTests: 0, - recentComboKeys: {} + tests: {} } - latestPerCombo.forEach(function (b) { - _cardTracker[pattern].recentComboKeys[sanitizeId(b.platform) + '-' + sanitizeId(b.version)] = true + latestPerPlatform.forEach(function (b) { + tracker.platforms[b.platform] = badgeStatusColor(b) }) - html += renderPatternCard(pattern, latestPerPlatform, latestPerCombo) - latestPerCombo.forEach(function (b) { var comboKey = sanitizeId(b.platform) + '-' + sanitizeId(b.version) - if (useSample) { - var color = sampleColorForKey(b.key) - _cardTracker[pattern].tests[comboKey] = color - - // Platform dot uses latest badge only (first one seen) - if (!_cardTracker[pattern].platforms[b.platform]) { - _cardTracker[pattern].platforms[b.platform] = color - } - _cardTracker[pattern].loadedTests++ - _cardTracker[pattern].loaded = latestPerPlatform.length - } else { - getJSON(b.getURI(), cardStatusCallback, b.key, pattern, b.platform, comboKey) - } + tracker.tests[comboKey] = badgeStatusColor(b) }) + + _cardTracker[pattern] = tracker + html += renderPatternCard(pattern, latestPerPlatform, latestPerCombo, tracker) }) html += '
' - - if (useSample) { - setTimeout(function () { - patternNames.forEach(function (pattern) { - updateCardUI(pattern) - }) - }, 150) - } - + html = renderOverallStatsBar() + html html += '
View complete history →' return html } -function renderStatusKey () { +function renderStatusKey() { return '
' + ' Passed: Pattern deployed and tests succeeded' + ' CI infrastructure failure: Cloud or pipeline problem, not a pattern issue' + @@ -1209,13 +1132,13 @@ function renderStatusKey () { '
' } -function handleSort (value) { +function handleSort(value) { var url = new URL(window.location.href) url.searchParams.set('sort', value) window.location.href = url.toString() } -function handleTimeRange (value) { +function handleTimeRange(value) { var url = new URL(window.location.href) if (value === '3m') { url.searchParams.delete('range') @@ -1225,28 +1148,28 @@ function handleTimeRange (value) { window.location.href = url.toString() } -function syncCiSidebarNavWithUrl () { +function syncCiSidebarNavWithUrl() { var list = document.querySelector('.ci-sidebar-nav') if (!list) return var cur = new URL(window.location.href) list.querySelectorAll('a[href]').forEach(function (a) { var linkUrl = new URL(a.getAttribute('href'), window.location.origin) - ;['range', 'sort'].forEach(function (k) { - var v = cur.searchParams.get(k) - if (k === 'range') { - if (v == null || v === '' || v === '3m') linkUrl.searchParams.delete('range') - else linkUrl.searchParams.set('range', v) - } else if (k === 'sort') { - if (v == null || v === '' || v === 'latest') linkUrl.searchParams.delete('sort') - else linkUrl.searchParams.set('sort', v) - } - }) + ;['range', 'sort'].forEach(function (k) { + var v = cur.searchParams.get(k) + if (k === 'range') { + if (v == null || v === '' || v === '3m') linkUrl.searchParams.delete('range') + else linkUrl.searchParams.set('range', v) + } else if (k === 'sort') { + if (v == null || v === '' || v === 'latest') linkUrl.searchParams.delete('sort') + else linkUrl.searchParams.set('sort', v) + } + }) var qs = linkUrl.searchParams.toString() a.setAttribute('href', linkUrl.pathname + (qs ? '?' + qs : '')) }) } -function renderDashboard (badges, options) { +function renderDashboard(badges, options) { var filter_field = options.get('filter_field') var filter_value = options.get('filter_value') var target = options.get('target') @@ -1344,8 +1267,8 @@ function renderDashboard (badges, options) { // Badge Data Processing // ============================================ -function getBadges (xmlText, bucket_url, badge_set) { - parser = new DOMParser() +function getBadges(xmlText, bucket_url, badge_set) { + let parser = new DOMParser() var xmlDoc = parser.parseFromString(xmlText, 'application/xml') const errorNode = xmlDoc.querySelector('parsererror') if (errorNode) { @@ -1356,31 +1279,31 @@ function getBadges (xmlText, bucket_url, badge_set) { var badges = [] var entries = xmlDoc.getElementsByTagName('Key') - l = entries.length + let l = entries.length - for (i = 0; i < l; i++) { - key = entries[i].childNodes[0].nodeValue - if (badge_set == "GA" && key.endsWith("stable-badge.json") ) { - badges.push(new Badge(bucket_url, key, getBadgeDate(entries[i]))); - } else if (badge_set == "early" && (key.endsWith("prerelease-badge.json") || key.endsWith("nightly-badge.json") || key.endsWith("operator-badge.json")) ) { - badges.push(new Badge(bucket_url, key, getBadgeDate(entries[i]))); - } else if (badge_set == "all" && key.endsWith("-badge.json") ) { - badges.push(new Badge(bucket_url, key, getBadgeDate(entries[i]))); - } else { - console.log("Skipping: " + key); - } + for (let i = 0; i < l; i++) { + let key = entries[i].childNodes[0].nodeValue + if (badge_set == "GA" && key.endsWith("stable-badge.json")) { + badges.push(new Badge(bucket_url, key, getBadgeDate(entries[i]))); + } else if (badge_set == "early" && (key.endsWith("prerelease-badge.json") || key.endsWith("nightly-badge.json") || key.endsWith("operator-badge.json"))) { + badges.push(new Badge(bucket_url, key, getBadgeDate(entries[i]))); + } else if (badge_set == "all" && key.endsWith("-badge.json")) { + badges.push(new Badge(bucket_url, key, getBadgeDate(entries[i]))); + } else { + console.log("Skipping: " + key); + } } return badges } -function excludeArchivedPatternsFromDashboard (badges) { +function excludeArchivedPatternsFromDashboard(badges) { return badges.filter(function (badge) { return !ARCHIVED_CI_PATTERNS[badge.pattern] }) } -function processBadges (badges, options) { +function processBadges(badges, options) { badges = excludeRetiredOcpVersionsFromDashboard(badges) badges = excludeArchivedPatternsFromDashboard(badges) if (options.get('disable_buttons') === true) { @@ -1395,7 +1318,7 @@ function processBadges (badges, options) { // Entry Points // ============================================ -function getBucketOptions (input) { +function getBucketOptions(input) { const options = new Map() const queryString = window.location.search const urlParams = new URLSearchParams(queryString) @@ -1408,38 +1331,37 @@ function getBucketOptions (input) { options.set('links', 'public') options.set('target', 'dataset') - buckets = [] + let buckets = [] const bucket = input['bucket'] if (bucket != null) { buckets.push(bucket) } else { buckets.push('https://storage.googleapis.com/vp-results') buckets.push('https://vp-ntnx-results.s3.amazonaws.com') + buckets.push('https://vp-qe-ci-badges.s3.us-east-2.amazonaws.com') } options.set('buckets', buckets) - const fields = [ 'sets', 'target', 'filter_field', 'filter_value', 'links', 'disable_buttons' ] - for (i = 0; i < fields.length; i++) { + const fields = ['sets', 'target', 'filter_field', 'filter_value', 'links', 'disable_buttons'] + for (let i = 0; i < fields.length; i++) { const key = fields[i] const value = input[key] if (value != null) { - options.set(fields[i], value) + options.set(fields[i], value) } } - const sections = [ 'date', 'version', 'platform', 'pattern' ] + const sections = ['date', 'version', 'platform', 'pattern'] - filter_field = options.get('filter_field') + let filter_field = options.get('filter_field') - if (options.get('filter_field') == null) { - for (i = 0; i < sections.length; i++) { - if (urlParams.get(sections[i]) != null) { + if (filter_field == null) { + for (let i = 0; i < sections.length; i++) { + if (urlParams.get(sections[i]) != null) { options.set('filter_field', sections[i]) if (options.get('filter_value') == null && urlParams.get(sections[i]) != null) { - options.set('filter_value', urlParams.get(sections[i])) - } else if (options.get('filter_value') == null && urlParams.get(sections[i]) != null) { - options.set('filter_value', 'all') + options.set('filter_value', urlParams.get(sections[i])) } - } + } } } @@ -1479,7 +1401,7 @@ function fetchBucketBadges(bucket, inputs) { }) } -function obtainBadgesFromSample (inputs) { +function obtainBadgesFromSample(inputs) { const options = getBucketOptions(inputs); fetch('/data/sample-badges.json') @@ -1503,7 +1425,7 @@ function obtainBadgesFromSample (inputs) { }) } -function obtainBadges (inputs) { +function obtainBadges(inputs) { if (!isProductionSite()) { console.log('Non-production site detected, using sample badge data') obtainBadgesFromSample(inputs) @@ -1530,7 +1452,10 @@ function obtainBadges (inputs) { }) console.log('All badges:', allBadges) - + return enrichBadgesFromJson(allBadges) + }) + .then(function (allBadges) { + console.log('Enriched badges:', allBadges.length); processBadges(allBadges, options) }) }