github.com/web-platform-tests/wpt.fyi@v0.0.0-20240530210107-70cf978996f1/webapp/components/product-info.js (about)

     1  /**
     2   * Copyright 2018 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  const DisplayNames = (() => {
     7    let m = new Map();
     8    ['chrome', 'chrome-experimental'].forEach(n => m.set(n, 'Chrome'));
     9    ['edge', 'edge-experimental'].forEach(n => m.set(n, 'Edge'));
    10    ['firefox', 'firefox-experimental'].forEach(n => m.set(n, 'Firefox'));
    11    ['safari', 'safari-experimental'].forEach(n => m.set(n, 'Safari'));
    12    m.set('android_webview', 'WebView');
    13    m.set('chrome_android', 'ChromeAndroid');
    14    m.set('chrome_ios', 'ChromeIOS');
    15    m.set('chromium', 'Chromium');
    16    m.set('deno', 'Deno');
    17    m.set('firefox_android', 'Firefox Android');
    18    m.set('flow', 'Flow');
    19    m.set('node.js', 'Node.js');
    20    m.set('servo', 'Servo');
    21    m.set('uc', 'UC Browser');
    22    m.set('wktr', 'macOS WebKit');
    23    m.set('webkitgtk', 'WebKitGTK');
    24    // Platforms
    25    m.set('android', 'Android');
    26    m.set('linux', 'Linux');
    27    m.set('mac', 'macOS');
    28    m.set('win', 'Windows');
    29    // Channels
    30    m.set('stable', 'Stable');
    31    m.set('beta', 'Beta');
    32    m.set('experimental', 'Experimental');
    33    m.set('dev', 'Dev'); // Chrome
    34    m.set('preview', 'Technology Preview'); // Safari
    35    m.set('nightly', 'Nightly'); // Firefox
    36    // Sources
    37    m.set('azure', 'Azure Pipelines');
    38    m.set('buildbot', 'Buildbot');
    39    m.set('msedge', 'MS Edge');
    40    m.set('taskcluster', 'Taskcluster');
    41    return m;
    42  })();
    43  const versionPatterns = Object.freeze({
    44    Major: /(\d+)/,
    45    MajorAndMinor: /(\d+\.\d+)/,
    46    Node: /(\d+\.\d+\.\d+(?:-[a-zA-Z]+)?)/,
    47  });
    48  
    49  // The set of all browsers known to the wpt.fyi UI.
    50  const AllBrowserNames = Object.freeze(['android_webview', 'chrome_android', 'chrome_ios', 'chrome',
    51    'chromium', 'deno', 'edge', 'firefox_android', 'firefox', 'flow', 'node.js', 'safari', 'servo', 'webkitgtk', 'wktr']);
    52  
    53  // The list of default browsers used in cases where the user has not otherwise
    54  // chosen a set of browsers (e.g. which browsers to show runs for). Stored as
    55  // an ordered list so that the first entry can be used as a consistent default.
    56  const DefaultBrowserNames = Object.freeze(['chrome', 'edge', 'firefox', 'safari']);
    57  const DefaultProductSpecs = DefaultBrowserNames;
    58  
    59  // The above sets, encoded as product objects. This avoids repeatedly calling
    60  // parseProductSpec when product objects are needed.
    61  const AllProducts = AllBrowserNames.map(p => Object.freeze(parseProductSpec(p)));
    62  const DefaultProducts = DefaultProductSpecs.map(p => Object.freeze(parseProductSpec(p)));
    63  
    64  const CommitTypes = new Set(['pr_head', 'master']);
    65  const Channels = new Set(['stable', 'beta', 'experimental']);
    66  const Sources = new Set(['buildbot', 'taskcluster', 'msedge', 'azure']);
    67  const Platforms = new Set(['linux', 'win', 'mac', 'ios', 'android']);
    68  const SemanticLabels = [
    69    { property: '_channel', values: Channels },
    70    { property: '_source', values: Sources },
    71  ];
    72  
    73  function parseProductSpec(spec) {
    74    // @sha (optional)
    75    let revision = '';
    76    const atIndex = spec.indexOf('@');
    77    if (atIndex > 0) {
    78      revision = spec.substr(atIndex + 1);
    79      spec = spec.substr(0, atIndex);
    80    }
    81    // [foo,bar] labels syntax (optional)
    82    let labels = [];
    83    const arrayIndex = spec.indexOf('[');
    84    if (arrayIndex > 0) {
    85      let labelsStr = spec.substr(arrayIndex + 1);
    86      if (labelsStr[labelsStr.length - 1] !== ']') {
    87        throw 'Expected closing bracket';
    88      }
    89      const seenLabels = new Set();
    90      labelsStr = labelsStr.substr(0, labelsStr.length - 1);
    91      for (const label of labelsStr.split(',')) {
    92        if (!seenLabels.has(label)) {
    93          seenLabels.add(label);
    94          labels.push(label);
    95        }
    96      }
    97      spec = spec.substr(0, arrayIndex);
    98    }
    99    // product
   100    const product = parseProduct(spec);
   101    product.revision = revision;
   102    product.labels = labels;
   103    return product;
   104  }
   105  
   106  function parseProduct(name) {
   107    // -version (optional)
   108    let version;
   109    const dashIndex = name.indexOf('-');
   110    if (dashIndex > 0) {
   111      version = name.substr(dashIndex + 1);
   112      name = name.substr(0, dashIndex);
   113    }
   114    return {
   115      browser_name: name,
   116      browser_version: version,
   117    };
   118  }
   119  
   120  function productFromRun(run) {
   121    const product = {
   122      browser_name: run.browser_name,
   123      browser_version: run.browser_version,
   124      labels: run.labels,
   125      revision: run.revision,
   126    };
   127    return product;
   128  }
   129  
   130  // eslint-disable-next-line no-unused-vars
   131  const ProductInfo = (superClass) => class extends superClass {
   132    static get properties() {
   133      return {
   134        // Polymer templates can only access variables in the scope of the owning
   135        // class. Forward some declarations so that subclasses can use them in
   136        // template parameters.
   137        allProducts: {
   138          type: Array,
   139          value: AllProducts,
   140          readOnly: true,
   141        }
   142      };
   143    }
   144  
   145    displayName(name) {
   146      return DisplayNames.get(name) || name;
   147    }
   148  
   149    displayLabels(labels) {
   150      if (labels && labels instanceof Array) {
   151        return labels.join(', ');
   152      }
   153      return '';
   154    }
   155  
   156    displayMetadataLogo(productName) {
   157      // Special case for metadata; an empty product name maps to the WPT logo.
   158      if (productName === '') {
   159        productName = 'wpt';
   160      }
   161      return this.displayLogo(productName);
   162    }
   163  
   164    displayLogo(name, labels) {
   165      if (!name) {
   166        return;
   167      }
   168      labels = new Set(labels);
   169      // Special case for Chrome nightly, which is in fact Chromium ToT:
   170      if (name === 'chrome' && labels.has('nightly') && !labels.has('canary')) {
   171        name = 'chromium';
   172  
   173      } else if (name === 'android_webview') {
   174        return `/static/${name}.png`;
   175  
   176      } else if (name === 'chrome_android' || name === 'chrome_ios') {
   177        // TODO(kyle): A temporary workaround; remove this check when
   178        // chrome_android and chrome_ios is mapped to chrome on wptrunner.
   179        return '/static/chrome_64x64.png';
   180      } else if (name === 'firefox_android') {
   181        // For now use the geckoview logo for Firefox for Android,
   182        // although it would be better to have some variant of the Firefox logo.
   183        return '/static/geckoview_64x64.png';
   184  
   185      } else if (name !== 'chromium' && name !== 'deno' && name !== 'flow' && name !== 'node.js' && name !== 'servo' && name !== 'wktr') {  // Products without per-channel logos.
   186        let channel;
   187        const candidates = ['beta', 'dev', 'canary', 'nightly', 'preview'];
   188        for (const label of candidates) {
   189          if (labels.has(label)) {
   190            channel = label;
   191            break;
   192          }
   193        }
   194        if (channel) {
   195          name = `${name}-${channel}`;
   196        }
   197      }
   198      return `/static/${name}_64x64.png`;
   199    }
   200  
   201    sourceName(product) {
   202      if (product.labels) {
   203        return this.displayName(product.labels.find(s => Sources.has(s)));
   204      }
   205      return '';
   206    }
   207  
   208    minorIsSignificant(browserName) {
   209      return browserName === 'deno' || browserName === 'flow' || browserName === 'safari' || browserName === 'webkitgtk';
   210    }
   211  
   212    /**
   213     * Truncate a software version identifier to include only the most
   214     * salient information for the specified browser.
   215     */
   216    shortVersion(browserName, browserVersion) {
   217      let pattern;
   218      if (browserName === 'node.js') {
   219        pattern = versionPatterns.Node;
   220      } else {
   221        pattern = this.minorIsSignificant(browserName)
   222          ? versionPatterns.MajorAndMinor
   223          : versionPatterns.Major;
   224      }
   225      const match = pattern.exec(browserVersion);
   226  
   227      if (!match) {
   228        return browserVersion;
   229      }
   230  
   231      return match[1];
   232    }
   233  
   234    parseProductSpec(spec) {
   235      return parseProductSpec(spec);
   236    }
   237  
   238    parseProduct(name) {
   239      return parseProduct(name);
   240    }
   241  
   242    getSpec(product, withRevision=true) {
   243      let spec = product.browser_name;
   244      if (product.browser_version) {
   245        spec += `-${product.browser_version}`;
   246      }
   247      if (product.labels && product.labels.length) {
   248        spec += `[${product.labels.join(',')}]`;
   249      }
   250      if (withRevision && product.revision && !this.computeIsLatest(product.revision)) {
   251        spec += `@${product.revision}`;
   252      }
   253      return spec;
   254    }
   255  
   256    computeIsLatest(sha) {
   257      if (Array.isArray(sha)) {
   258        return !sha.length || sha.length === 1 && this.computeIsLatest(sha[0]);
   259      }
   260      return !sha || sha === 'latest';
   261    }
   262  };
   263  
   264  export {
   265    AllBrowserNames,
   266    AllProducts,
   267    DisplayNames,
   268    DefaultBrowserNames,
   269    DefaultProductSpecs,
   270    DefaultProducts,
   271    CommitTypes,
   272    Channels,
   273    Platforms,
   274    Sources,
   275    SemanticLabels,
   276    ProductInfo,
   277    parseProductSpec,
   278    parseProduct,
   279    productFromRun,
   280  };