github.com/web-platform-tests/wpt.fyi@v0.0.0-20240530210107-70cf978996f1/webapp/components/compat-2021.js (about) 1 /** 2 * Copyright 2021 The WPT Dashboard Project. All rights reserved. 3 * Use of this source code is governed by a BSD-style license that can be 4 * found in the LICENSE file. 5 */ 6 7 import {load} from '../node_modules/@google-web-components/google-chart/google-chart-loader.js'; 8 import '../node_modules/@polymer/paper-button/paper-button.js'; 9 import '../node_modules/@polymer/paper-dialog/paper-dialog.js'; 10 import '../node_modules/@polymer/paper-input/paper-input.js'; 11 import '../node_modules/@polymer/polymer/lib/elements/dom-if.js'; 12 import { html, PolymerElement } from '../node_modules/@polymer/polymer/polymer-element.js'; 13 14 const GITHUB_URL_PREFIX = 'https://raw.githubusercontent.com/web-platform-tests/results-analysis'; 15 const DATA_BRANCH = 'gh-pages'; 16 // Support a 'use_webkitgtk' query parameter to substitute WebKitGTK in for 17 // Safari, to deal with the ongoing lack of new STP versions on wpt.fyi. 18 const DATA_FILES_PATH = (new URL(document.location)).searchParams.has('use_webkitgtk') 19 ? 'data/compat2021/webkitgtk' 20 : 'data/compat2021'; 21 22 const SUMMARY_FEATURE_NAME = 'summary'; 23 const FEATURES = [ 24 'aspect-ratio', 25 'css-flexbox', 26 'css-grid', 27 'css-transforms', 28 'position-sticky', 29 ]; 30 31 // Compat2021DataManager encapsulates the loading of the CSV data that backs 32 // both the summary scores and graphs shown on the Compat 2021 dashboard. It 33 // fetches the CSV data, processes it into sets of datatables, and then caches 34 // those tables for later use by the dashboard. 35 class Compat2021DataManager { 36 constructor() { 37 this._dataLoaded = load().then(() => { 38 return Promise.all([this._loadCsv('stable'), this._loadCsv('experimental')]); 39 }); 40 } 41 42 // Fetches the datatable for the given feature and stable/experimental state. 43 // This will wait as needed for the underlying CSV data to be loaded and 44 // processed before returning the datatable. 45 async getDataTable(feature, stable) { 46 await this._dataLoaded; 47 return stable ? 48 this.stableDatatables.get(feature) : 49 this.experimentalDatatables.get(feature); 50 } 51 52 // Fetches a list of browser versions for stable or experimental. This is a 53 // helper method for building tooltip actions; the returned list has one 54 // entry per row in the corresponding datatables. 55 async getBrowserVersions(stable) { 56 await this._dataLoaded; 57 return stable ? 58 this.stableBrowserVersions : 59 this.experimentalBrowserVersions; 60 } 61 62 // Loads the unified CSV file for either stable or experimental, and 63 // processes it into the set of datatables provided by this class. Will 64 // ultimately set either this.stableDatatables or this.experimentalDatatables 65 // with a map of {feature name --> datatable}. 66 async _loadCsv(label) { 67 const url = `${GITHUB_URL_PREFIX}/${DATA_BRANCH}/${DATA_FILES_PATH}/unified-scores-${label}.csv`; 68 const csvLines = await fetchCsvContents(url); 69 70 const features = [SUMMARY_FEATURE_NAME, ...FEATURES]; 71 const dataTables = new Map(features.map(feature => { 72 const dataTable = new window.google.visualization.DataTable(); 73 dataTable.addColumn('date', 'Date'); 74 dataTable.addColumn('number', 'Chrome/Edge'); 75 dataTable.addColumn({type: 'string', role: 'tooltip'}); 76 dataTable.addColumn('number', 'Firefox'); 77 dataTable.addColumn({type: 'string', role: 'tooltip'}); 78 dataTable.addColumn('number', 'Safari'); 79 dataTable.addColumn({type: 'string', role: 'tooltip'}); 80 return [feature, dataTable]; 81 })); 82 83 // We list Chrome/Edge on the legend, but when creating the tooltip we 84 // include the version information and so should be clear about which browser 85 // exactly gave the results. 86 const tooltipBrowserNames = [ 87 'Chrome', 88 'Firefox', 89 'Safari', 90 ]; 91 92 // We store a lookup table of browser versions to help with the 'show 93 // revision changelog' tooltip action. 94 const browserVersions = [[], [], []]; 95 96 csvLines.forEach(line => { 97 // We control the CSV data source, so are quite lazy with parsing it. 98 // 99 // The format is: 100 // date, [browser-version, browser-feature-a, browser-feature-b, ...]+ 101 const csvValues = line.split(','); 102 103 // JavaScript Date objects use 0-indexed months whilst the CSV is 104 // 1-indexed, so adjust for that. 105 const dateParts = csvValues[0].split('-').map(x => parseInt(x)); 106 const date = new Date(dateParts[0], dateParts[1] - 1, dateParts[2]); 107 108 // Initialize a new row for each feature, with the date column set. 109 const newRows = new Map(features.map(feature => { 110 return [feature, [date]]; 111 })); 112 113 // Now handle each of the browsers. For each there is a version column, 114 // then the scores for each of the five features. 115 for (let i = 1; i < csvValues.length; i += 6) { 116 const browserIdx = Math.floor(i / 6); 117 const browserName = tooltipBrowserNames[browserIdx]; 118 const version = csvValues[i]; 119 browserVersions[browserIdx].push(version); 120 121 let summaryScore = 0; 122 FEATURES.forEach((feature, j) => { 123 const score = parseFloat(csvValues[i + 1 + j]); 124 const tooltip = this.createTooltip(browserName, version, score.toFixed(3)); 125 newRows.get(feature).push(score); 126 newRows.get(feature).push(tooltip); 127 128 // The summary scores are calculated as a x/100 score, where each 129 // feature is allowed to contribute up to 20 points. We use floor 130 // rather than round to avoid claiming the full 20 points until we 131 // are at 100% 132 summaryScore += Math.floor(score * 20); 133 }); 134 135 const summaryTooltip = this.createTooltip(browserName, version, summaryScore); 136 newRows.get(SUMMARY_FEATURE_NAME).push(summaryScore); 137 newRows.get(SUMMARY_FEATURE_NAME).push(summaryTooltip); 138 } 139 140 // Push the new rows onto the corresponding datatable. 141 newRows.forEach((row, feature) => { 142 dataTables.get(feature).addRow(row); 143 }); 144 }); 145 146 // The datatables are now complete, so assign them to the appropriate 147 // member variable. 148 if (label === 'stable') { 149 this.stableDatatables = dataTables; 150 this.stableBrowserVersions = browserVersions; 151 } else { 152 this.experimentalDatatables = dataTables; 153 this.experimentalBrowserVersions = browserVersions; 154 } 155 } 156 157 createTooltip(browser, version, score) { 158 return `${browser} ${version}: ${score}`; 159 } 160 } 161 162 // Compat2021 is a custom element that holds the overall compat-2021 dashboard. 163 // The dashboard breaks down into top-level summary scores, a small description, 164 // graphs per feature, and a table of currently tracked tests. 165 class Compat2021 extends PolymerElement { 166 static get template() { 167 return html` 168 <style> 169 :host { 170 display: block; 171 max-width: 700px; 172 /* Override wpt.fyi's automatically injected common.css */ 173 margin: 0 auto !important; 174 font-family: system-ui, sans-serif; 175 line-height: 1.5; 176 } 177 178 h1 { 179 text-align: center; 180 } 181 182 .channel-area { 183 display: inline-flex; 184 height: 35px; 185 margin-top: 0; 186 margin-bottom: 10px; 187 } 188 189 .channel-label { 190 font-size: 18px; 191 display: flex; 192 justify-content: center; 193 flex-direction: column; 194 } 195 196 .unselected { 197 background-color: white; 198 } 199 .selected { 200 background-color: var(--paper-blue-100); 201 } 202 203 .focus-area { 204 font-size: 18px; 205 } 206 207 #featureSelect { 208 padding: 0.5rem; 209 } 210 211 #testListText { 212 padding-top: 1em; 213 } 214 </style> 215 <h1>Compat 2021 Dashboard</h1> 216 <compat-2021-summary stable="[[stable]]"></compat-2021-summary> 217 <p> 218 These scores represent how well browser engines are doing on the 2021 219 Compat Focus Areas, as measured by wpt.fyi test results. Each feature 220 contributes up to 20 points to the score, based on passing-test 221 percentage, giving a maximum possible score of 100 for each browser. 222 </p> 223 <p> 224 The set of tests used is derived from the full wpt.fyi test suite for 225 each feature, filtered by believed importance to web developers. 226 The results shown here are from 227 <template is="dom-if" if="[[stable]]"> 228 released stable builds. 229 </template> 230 <template is="dom-if" if="[[!stable]]"> 231 developer preview builds with experimental features enabled. 232 </template> 233 </p> 234 235 <fieldset> 236 <legend>Configuration:</legend> 237 238 <div class="channel-area"> 239 <span class="channel-label">Browser Type:</span> 240 <paper-button class\$="[[experimentalButtonClass(stable)]]" raised on-click="clickExperimental">Experimental</paper-button> 241 <paper-button class\$="[[stableButtonClass(stable)]]" raised on-click="clickStable">Stable</paper-button> 242 </div> 243 244 <!-- TODO: replace with paper-dropdown-menu --> 245 <div class="focus-area"> 246 <label for="featureSelect">Focus area:</label> 247 <select id="featureSelect"> 248 <option value="summary">Summary</option> 249 <option value="aspect-ratio">aspect-ratio</option> 250 <option value="css-flexbox">css-flexbox</option> 251 <option value="css-grid">css-grid</option> 252 <option value="css-transforms">css-transforms</option> 253 <option value="position-sticky">position-sticky</option> 254 </select> 255 </div> 256 </fieldset> 257 258 <compat-2021-feature-chart data-manager="[[dataManager]]" 259 stable="[[stable]]" 260 feature="{{feature}}"> 261 </compat-2021-feature-chart> 262 263 <!-- We use a 'hidden' style rather than dom-if to avoid layout shift when 264 the feature is changed to/from summary. --> 265 <div id="testListText" style$="visibility: [[getTestListTextVisibility(feature)]]"> 266 The score for this component is determined by pass rate on 267 <a href="[[getTestListHref(feature)]]" target="_blank">this set of tests</a>. 268 The test suite is never complete, and improvements are always welcome. 269 Please contribute changes to 270 <a href="https://github.com/web-platform-tests/wpt" target="_blank">WPT</a> 271 and then 272 <a href="https://github.com/web-platform-tests/wpt.fyi/issues/new?title=[compat2021]%20Add%20new%20tests%20to%20dashboard&body=" target="_blank">file an issue</a> 273 to add them to the Compat 2021 effort! 274 </div> 275 276 <!-- TODO: Test results table --> 277 `; 278 } 279 280 static get is() { 281 return 'compat-2021'; 282 } 283 284 static get properties() { 285 return { 286 embedded: Boolean, 287 useWebkitGTK: Boolean, 288 stable: Boolean, 289 feature: String, 290 dataManager: Object, 291 }; 292 } 293 294 static get observers() { 295 return [ 296 'updateUrlParams(embedded, useWebKitGTK, stable, feature)', 297 ]; 298 } 299 300 ready() { 301 super.ready(); 302 303 this.dataManager = new Compat2021DataManager(); 304 305 const params = (new URL(document.location)).searchParams; 306 this.embedded = params.get('embedded') !== null; 307 this.useWebKitGTK = params.get('use_webkitgtk') !== null; 308 // The default view of the page is the summary scores graph for 309 // experimental releases of browsers. 310 this.stable = params.get('stable') !== null; 311 this.feature = params.get('feature') || SUMMARY_FEATURE_NAME; 312 313 this.$.featureSelect.value = this.feature; 314 this.$.featureSelect.addEventListener('change', () => { 315 this.feature = this.$.featureSelect.value; 316 }); 317 } 318 319 updateUrlParams(embedded, useWebKitGTK, stable, feature) { 320 // Our observer may be called before the feature is set, so debounce that. 321 if (feature === undefined) { 322 return; 323 } 324 325 const params = []; 326 if (feature) { 327 params.push(`feature=${feature}`); 328 } 329 if (stable) { 330 params.push('stable'); 331 } 332 if (embedded) { 333 params.push('embedded'); 334 } 335 if (useWebKitGTK) { 336 params.push('use_webkitgtk'); 337 } 338 339 let url = location.pathname; 340 if (params.length) { 341 url += `?${params.join('&')}`; 342 } 343 history.pushState('', '', url); 344 } 345 346 experimentalButtonClass(stable) { 347 return stable ? 'unselected' : 'selected'; 348 } 349 350 stableButtonClass(stable) { 351 return stable ? 'selected' : 'unselected'; 352 } 353 354 clickExperimental() { 355 if (!this.stable) { 356 return; 357 } 358 this.stable = false; 359 } 360 361 clickStable() { 362 if (this.stable) { 363 return; 364 } 365 this.stable = true; 366 } 367 368 getTestListTextVisibility(feature) { 369 return FEATURES.includes(feature) ? 'visible' : 'hidden'; 370 } 371 372 getTestListHref(feature) { 373 return `${GITHUB_URL_PREFIX}/main/compat-2021/${feature}-tests.txt`; 374 } 375 } 376 window.customElements.define(Compat2021.is, Compat2021); 377 378 const STABLE_TITLES = [ 379 'Chrome/Edge Stable', 380 'Firefox Stable', 381 'Safari Stable', 382 ]; 383 384 const EXPERIMENTAL_TITLES = [ 385 'Chrome/Edge Dev', 386 'Firefox Nightly', 387 'Safari Preview', 388 ]; 389 390 class Compat2021Summary extends PolymerElement { 391 static get template() { 392 return html` 393 <link rel="preconnect" href="https://fonts.gstatic.com"> 394 <link href="https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@400&display=swap" rel="stylesheet"> 395 396 <style> 397 #summaryContainer { 398 padding-top: 1em; 399 display: flex; 400 justify-content: center; 401 gap: 30px; 402 } 403 404 .summary-flex-item { 405 position: relative; 406 width: 125px; 407 cursor: help; 408 } 409 410 .summary-number { 411 font-size: 5em; 412 font-family: 'Roboto Mono', monospace; 413 text-align: center; 414 } 415 416 .summary-browser-name { 417 text-align: center; 418 } 419 420 .summary-flex-item:hover .summary-tooltip, 421 .summary-flex-item:focus .summary-tooltip { 422 display: block; 423 } 424 425 .summary-tooltip { 426 display: none; 427 position: absolute; 428 /* TODO: find a better solution for drawing on-top of other numbers */ 429 z-index: 1; 430 width: 150px; 431 border: 1px lightgrey solid; 432 background: white; 433 border-radius: 3px; 434 padding: 5px; 435 top: 105%; 436 left: -20%; 437 padding: 0.5rem 0.75rem; 438 line-height: 1.4; 439 box-shadow: 0 0 20px 0px #c3c3c3; 440 } 441 442 .summary-tooltip > div { 443 display: flex; 444 justify-content: space-between; 445 } 446 </style> 447 448 <div id="summaryContainer"> 449 <!-- Chrome/Edge --> 450 <div class="summary-flex-item" tabindex="0"> 451 <span class="summary-tooltip"></span> 452 <div class="summary-number">--</div> 453 <div class="summary-browser-name"></div> 454 </div> 455 <!-- Firefox --> 456 <div class="summary-flex-item" tabindex="0"> 457 <span class="summary-tooltip"></span> 458 <div class="summary-number">--</div> 459 <div class="summary-browser-name"></div> 460 </div> 461 <!-- Safari --> 462 <div class="summary-flex-item" tabindex="0"> 463 <span class="summary-tooltip"></span> 464 <div class="summary-number">--</div> 465 <div class="summary-browser-name"></div> 466 </div> 467 </div> 468 `; 469 } 470 471 static get is() { 472 return 'compat-2021-summary'; 473 } 474 475 static get properties() { 476 return { 477 stable: { 478 type: Boolean, 479 observer: '_stableChanged', 480 } 481 }; 482 } 483 484 _stableChanged() { 485 this.updateSummaryTitles(); 486 this.updateSummaryScores(); 487 } 488 489 updateSummaryTitles() { 490 let titleDivs = this.$.summaryContainer.querySelectorAll('.summary-browser-name'); 491 let titles = this.stable ? STABLE_TITLES : EXPERIMENTAL_TITLES; 492 for (let i = 0; i < titleDivs.length; i++) { 493 titleDivs[i].innerText = titles[i]; 494 } 495 } 496 497 async updateSummaryScores() { 498 let scores = await this.calculateSummaryScores(this.stable); 499 let numbers = this.$.summaryContainer.querySelectorAll('.summary-number'); 500 let tooltips = this.$.summaryContainer.querySelectorAll('.summary-tooltip'); 501 for (let i = 0; i < scores.length; i++) { 502 numbers[i].innerText = scores[i].total; 503 numbers[i].style.color = this.calculateColor(scores[i].total); 504 505 // TODO: Replace tooltips with paper-tooltip. 506 this.updateSummaryTooltip(tooltips[i], scores[i].breakdown); 507 } 508 } 509 510 updateSummaryTooltip(tooltipDiv, scoreBreakdown) { 511 tooltipDiv.innerHTML = ''; 512 513 scoreBreakdown.forEach((val, key) => { 514 const keySpan = document.createElement('span'); 515 keySpan.innerText = `${key}: `; 516 const valueSpan = document.createElement('span'); 517 valueSpan.innerText = val; 518 valueSpan.style.color = this.calculateColor(val * 5); // Scale to 0-100 519 520 const textDiv = document.createElement('div'); 521 textDiv.appendChild(keySpan); 522 textDiv.appendChild(valueSpan); 523 524 tooltipDiv.appendChild(textDiv); 525 }); 526 } 527 528 async calculateSummaryScores(stable) { 529 const label = stable ? 'stable' : 'experimental'; 530 const url = `${GITHUB_URL_PREFIX}/${DATA_BRANCH}/${DATA_FILES_PATH}/summary-${label}.csv`; 531 const csvLines = await fetchCsvContents(url); 532 533 if (csvLines.length !== 5) { 534 throw new Error(`${url} did not contain 5 results`); 535 } 536 537 let scores = [ 538 { total: 0, breakdown: new Map() }, 539 { total: 0, breakdown: new Map() }, 540 { total: 0, breakdown: new Map() }, 541 ]; 542 543 for (const line of csvLines) { 544 let parts = line.split(','); 545 if (parts.length !== 4) { 546 throw new Error(`${url} had an invalid line`); 547 } 548 549 const feature = parts.shift(); 550 for (let i = 0; i < parts.length; i++) { 551 // Use floor rather than round to avoid claiming the full 20 points until 552 // definitely there. 553 let contribution = Math.floor(parseFloat(parts[i]) * 20); 554 scores[i].total += contribution; 555 scores[i].breakdown.set(feature, contribution); 556 } 557 } 558 559 return scores; 560 } 561 562 // TODO: Reuse the code from wpt-colors.js 563 calculateColor(score) { 564 // RGB values from https://material.io/design/color/ 565 if (score >= 95) { 566 return '#388E3C'; // Green 700 567 } 568 if (score >= 75) { 569 return '#689F38'; // Light Green 700 570 } 571 if (score >= 50) { 572 return '#FBC02D'; // Yellow 700 573 } 574 if (score >= 25) { 575 return '#F57C00'; // Orange 700 576 } 577 return '#D32F2F'; // Red 700 578 } 579 } 580 window.customElements.define(Compat2021Summary.is, Compat2021Summary); 581 582 // Compat2021FeatureChart is a wrapper around a Google Charts chart. We cannot 583 // use the polymer google-chart element as it does not support setting tooltip 584 // actions, which we rely on to let users load a changelog between subsequent 585 // versions of the same browser. 586 class Compat2021FeatureChart extends PolymerElement { 587 static get template() { 588 return html` 589 <style> 590 .chart { 591 /* Reserve vertical space to avoid layout shift. Should be kept in sync 592 with the JavaScript defined height. */ 593 height: 350px; 594 margin: 0 auto; 595 display: flex; 596 justify-content: center; 597 } 598 599 paper-dialog { 600 max-width: 600px; 601 } 602 </style> 603 <div id="failuresChart" class="chart"></div> 604 605 <paper-dialog with-backdrop id="firefoxNightlyDialog"> 606 <h2>Firefox Nightly Changelogs</h2> 607 <div> 608 Nightly builds of Firefox are all given the same sub-version, 609 <code>0a1</code>, so we cannot automatically determine the changelog. 610 To find the changelog of a specific Nightly release, locate the 611 corresponding revision on the 612 <a href="https://hg.mozilla.org/mozilla-central/firefoxreleases" 613 target="_blank">release page</a>, enter them below, and click "Go". 614 <paper-input id="firefoxNightlyDialogFrom" label="From revision"></paper-input> 615 <paper-input id="firefoxNightlyDialogTo" label="To revision"></paper-input> 616 </div> 617 618 <div class="buttons"> 619 <paper-button dialog-dismiss>Cancel</paper-button> 620 <paper-button dialog-confirm on-click="clickFirefoxNightlyDialogGoButton">Go</paper-button> 621 </div> 622 </paper-dialog> 623 624 <paper-dialog with-backdrop id="safariDialog"> 625 <h2>Safari Changelogs</h2> 626 <template is="dom-if" if="[[stable]]"> 627 <div> 628 Stable releases of Safari do not publish changelogs, but some insight 629 may be gained from the 630 <a href="https://developer.apple.com/documentation/safari-release-notes" 631 target="_blank">Release Notes</a>. 632 </div> 633 </template> 634 <template is="dom-if" if="[[!stable]]"> 635 <div> 636 For Safari Technology Preview releases, release notes can be found on 637 the <a href="https://webkit.org/blog/" target="_blank">WebKit Blog</a>. 638 Each post usually contains a revision changelog link - look for the 639 text "This release covers WebKit revisions ...". 640 </div> 641 </template> 642 643 <div class="buttons"> 644 <paper-button dialog-dismiss>Dismiss</paper-button> 645 </div> 646 </paper-dialog> 647 `; 648 } 649 650 static get properties() { 651 return { 652 dataManager: Object, 653 stable: Boolean, 654 feature: String, 655 }; 656 } 657 658 static get observers() { 659 return [ 660 'updateChart(feature, stable)', 661 ]; 662 } 663 664 static get is() { 665 return 'compat-2021-feature-chart'; 666 } 667 668 ready() { 669 super.ready(); 670 671 // Google Charts is not responsive, even if one sets a percentage-width, so 672 // we add a resize observer to redraw the chart if the size changes. 673 window.addEventListener('resize', () => { 674 this.updateChart(this.feature, this.stable); 675 }); 676 } 677 678 async updateChart(feature, stable) { 679 // Our observer may be called before the feature is set, so debounce that. 680 if (!feature) { 681 return; 682 } 683 684 // Fetching the datatable first ensures that Google Charts has been loaded. 685 const dataTable = await this.dataManager.getDataTable(feature, stable); 686 687 const div = this.$.failuresChart; 688 const chart = new window.google.visualization.LineChart(div); 689 690 // We define a tooltip action that can quickly show users the changelog 691 // between two subsequent versions of a browser. The goal is to help users 692 // understand why an improvement or regression may have happened - though 693 // this only exposes browser changes and not test suite changes. 694 const browserVersions = await this.dataManager.getBrowserVersions(stable); 695 chart.setAction({ 696 id: 'revisionChangelog', 697 text: 'Show browser changelog', 698 action: () => { 699 let selection = chart.getSelection(); 700 let row = selection[0].row; 701 let column = selection[0].column; 702 703 // Map from the selected column to the browser index. In the datatable 704 // Chrome is 1, Firefox is 3, Safari is 5 => these must map to [0, 1, 2]. 705 let browserIdx = Math.floor(column / 2); 706 707 let version = browserVersions[browserIdx][row]; 708 let lastVersion = version; 709 while (row > 0 && lastVersion === version) { 710 row -= 1; 711 lastVersion = browserVersions[browserIdx][row]; 712 } 713 // TODO: If row == -1 here then we've failed. 714 715 if (browserIdx === 0) { 716 window.open(this.getChromeChangelogUrl(lastVersion, version)); 717 return; 718 } 719 720 if (browserIdx === 1) { 721 if (stable) { 722 window.open(this.getFirefoxStableChangelogUrl(lastVersion, version)); 723 } else { 724 this.$.firefoxNightlyDialog.open(); 725 } 726 return; 727 } 728 729 this.$.safariDialog.open(); 730 }, 731 }); 732 733 chart.draw(dataTable, this.getChartOptions(div, feature)); 734 } 735 736 getChromeChangelogUrl(fromVersion, toVersion) { 737 // Strip off the 'dev' suffix if there. 738 fromVersion = fromVersion.split(' ')[0]; 739 toVersion = toVersion.split(' ')[0]; 740 return `https://chromium.googlesource.com/chromium/src/+log/${fromVersion}..${toVersion}?pretty=fuller&n=10000`; 741 } 742 743 getFirefoxStableChangelogUrl(fromVersion, toVersion) { 744 // The version numbers are reported as XX.Y.Z, but pushlog wants 745 // 'FIREFOX_XX_Y_Z_RELEASE'. 746 const fromParts = fromVersion.split('.'); 747 const fromRelease = `FIREFOX_${fromParts.join('_')}_RELEASE`; 748 const toParts = toVersion.split('.'); 749 const toRelease = `FIREFOX_${toParts.join('_')}_RELEASE`; 750 return `https://hg.mozilla.org/mozilla-unified/pushloghtml?fromchange=${fromRelease}&tochange=${toRelease}`; 751 } 752 753 clickFirefoxNightlyDialogGoButton() { 754 const fromSha = this.$.firefoxNightlyDialogFrom.value; 755 const toSha = this.$.firefoxNightlyDialogTo.value; 756 const url = `https://hg.mozilla.org/mozilla-unified/pushloghtml?fromchange=${fromSha}&tochange=${toSha}`; 757 window.open(url); 758 } 759 760 getChartOptions(containerDiv, feature) { 761 const options = { 762 height: 350, 763 fontSize: 14, 764 tooltip: { 765 trigger: 'both', 766 }, 767 hAxis: { 768 title: 'Date', 769 format: 'MMM-YYYY', 770 }, 771 explorer: { 772 actions: ['dragToZoom', 'rightClickToReset'], 773 axis: 'horizontal', 774 keepInBounds: true, 775 maxZoomIn: 4.0, 776 }, 777 colors: ['#4285f4', '#ea4335', '#fbbc04'], 778 }; 779 780 if (feature === SUMMARY_FEATURE_NAME) { 781 options.vAxis = { 782 title: 'Compat 2021 Score', 783 viewWindow: { 784 min: 50, 785 max: 100, 786 } 787 }; 788 } else { 789 options.vAxis = { 790 title: 'Percentage of tests passing', 791 format: 'percent', 792 viewWindow: { 793 // We set a global minimum value for the y-axis to keep the graphs 794 // consistent when you switch features. Currently the lowest value 795 // is aspect-ratio, with a ~25% pass-rate on Safari STP, Safari 796 // Stable, and Firefox Stable. 797 min: 0.2, 798 max: 1, 799 } 800 }; 801 } 802 803 // We draw the chart in two ways, depending on the viewport width. In 804 // 'full' mode the legend is on the right and we limit the chart size to 805 // 700px wide. In 'mobile' mode the legend is on the top and we use all the 806 // space we can get for the chart. 807 if (containerDiv.clientWidth >= 700) { 808 options.width = 700; 809 options.chartArea = { 810 height: '80%' 811 }; 812 } else { 813 options.width = '100%'; 814 options.legend = { 815 position: 'top', 816 alignment: 'center', 817 }; 818 options.chartArea = { 819 left: 75, 820 width: '80%', 821 }; 822 } 823 824 return options; 825 } 826 } 827 window.customElements.define(Compat2021FeatureChart.is, Compat2021FeatureChart); 828 829 async function fetchCsvContents(url) { 830 const csvResp = await fetch(url); 831 if (!csvResp.ok) { 832 throw new Error(`Fetching chart csv data failed: ${csvResp.status}`); 833 } 834 const csvText = await csvResp.text(); 835 const csvLines = csvText.split('\n').filter(l => l); 836 csvLines.shift(); // We don't need the CSV header. 837 return csvLines; 838 }