function displayEnvironmentChart()

in hack/jenkins/test-flake-chart/flake_chart.js [416:665]


function displayEnvironmentChart(testData, environmentName) {
  // Number of days to use to look for "flaky-est" tests.
  const dateRange = 15;
  // Number of tests to display in chart.
  const topFlakes = 10;

  testData = testData
    // Filter to only contain unskipped runs of the requested environment.
    .filter(test => test.environment === environmentName && test.status !== testStatus.SKIPPED);

  const testRuns = testData
    .groupBy(test => test.name);

  const aggregatedRuns = new Map(testRuns.map(test => [
    test[0].name,
    new Map(aggregateRuns(test)
      .map(runDate => [ runDate.date.getTime(), runDate ]))]));
  const uniqueDates = new Set();
  for (const [_, runDateMap] of aggregatedRuns) {
    for (const [dateTime, _] of runDateMap) {
      uniqueDates.add(dateTime);
    }
  }
  const orderedDates = Array.from(uniqueDates).sort();
  const recentDates = orderedDates.slice(-dateRange);
  const previousDates = orderedDates.slice(-2 * dateRange, -dateRange);

  const computeFlakePercentage = (runs, dates) => Array.from(runs).map(([testName, data]) => {
    const {flakeCount, totalCount} = dates.map(date => {
      const dateInfo = data.get(date);
      return dateInfo === undefined ? null : {
        flakeRate: dateInfo.flakeRate,
        runs: dateInfo.jobs.length
      };
    }).filter(dateInfo => dateInfo != null)
      .reduce(({flakeCount, totalCount}, {flakeRate, runs}) => ({
        flakeCount: flakeRate * runs + flakeCount,
        totalCount: runs + totalCount
      }), {flakeCount: 0, totalCount: 0});
    return {
      testName,
      flakeRate: totalCount === 0 ? 0 : flakeCount / totalCount,
    };
  });
  
  const recentFlakePercentage = computeFlakePercentage(aggregatedRuns, recentDates)
    .sort((a, b) => b.flakeRate - a.flakeRate);
  const previousFlakePercentageMap = new Map(
    computeFlakePercentage(aggregatedRuns, previousDates)
      .map(({testName, flakeRate}) => [testName, flakeRate]));

  const recentTopFlakes = recentFlakePercentage
    .slice(0, topFlakes)
    .map(({testName}) => testName);

  const chartsContainer = document.getElementById('chart_div');
  {
    const data = new google.visualization.DataTable();
    data.addColumn('date', 'Date');
    for (const name of recentTopFlakes) {
      data.addColumn('number', `Flake Percentage - ${name}`);
      data.addColumn({ type: 'string', role: 'tooltip', 'p': { 'html': true } });
    }
    data.addRows(
      orderedDates.map(dateTime => [new Date(dateTime)].concat(recentTopFlakes.map(name => {
        const data = aggregatedRuns.get(name).get(dateTime);
        return data !== undefined ? [
          data.flakeRate,
          `<div style="padding: 1rem; font-family: 'Arial'; font-size: 14">
            <b style="display: block">${name}</b><br>
            <b>Date:</b> ${data.date.toLocaleString([], {dateStyle: 'medium'})}<br>
            <b>Flake Percentage:</b> ${data.flakeRate.toFixed(2)}%<br>
            <b>Jobs:</b><br>
            ${data.jobs.map(({ id, status }) => `  - <a href="${testGopoghLink(id, environmentName, name)}">${id}</a> (${status})`).join("<br>")}
          </div>`
        ] : [null, null];
      })).flat())
    );
    const options = {
      title: `Flake rate by day of top ${topFlakes} of recent test flakiness (past ${dateRange} days) on ${environmentName}`,
      width: window.innerWidth,
      height: window.innerHeight,
      pointSize: 10,
      pointShape: "circle",
      vAxes: {
        0: { title: "Flake rate", minValue: 0, maxValue: 100 },
      },
      tooltip: { trigger: "selection", isHtml: true }
    };
    const flakeRateContainer = document.createElement("div");
    flakeRateContainer.style.width = "100vw";
    flakeRateContainer.style.height = "100vh";
    chartsContainer.appendChild(flakeRateContainer);
    const chart = new google.visualization.LineChart(flakeRateContainer);
    chart.draw(data, options);
  }
  {
    const dates = testData.map(run => run.date.getTime());
    const startDate = new Date(dates.min());
    const endDate = new Date(dates.max());
  
    const weekDates = [];
    let currentDate = startDate;
    while (currentDate < endDate) {
      weekDates.push(currentDate);
      currentDate = new Date(currentDate);
      currentDate.setDate(currentDate.getDate() + 7);
    }
    weekDates.push(currentDate);
    weekDates.findRounded = function (value) {
      let index = this.findIndex(v => value < v);
      if (index == 0) {
        return this[index];
      }
      if (index < 0) {
        index = this.length;
      }
      return this[index - 1];
    }
    const aggregatedWeeklyRuns = new Map(testRuns.map(test => [
      test[0].name,
      new Map(aggregateWeeklyRuns(test, weekDates)
        .map(runDate => [ weekDates.findRounded(runDate.date).getTime(), runDate ]))]));
    const uniqueWeekDates = new Set();
    for (const [_, runDateMap] of aggregatedWeeklyRuns) {
      for (const [dateTime, _] of runDateMap) {
        uniqueWeekDates.add(dateTime);
      }
    }
    const orderedWeekDates = Array.from(uniqueWeekDates).sort();
    const recentWeeklyTopFlakes = computeFlakePercentage(aggregatedWeeklyRuns, [orderedWeekDates[orderedWeekDates.length - 1]])
      .sort((a, b) => b.flakeRate - a.flakeRate)
      .slice(0, topFlakes)
      .map(({testName}) => testName);
    const data = new google.visualization.DataTable();
    data.addColumn('date', 'Date');
    for (const name of recentWeeklyTopFlakes) {
      data.addColumn('number', `Flake Percentage - ${name}`);
      data.addColumn({ type: 'string', role: 'tooltip', 'p': { 'html': true } });
    }
    data.addRows(
      orderedWeekDates.map(dateTime => [new Date(dateTime)].concat(recentTopFlakes.map(name => {
        const data = aggregatedWeeklyRuns.get(name).get(dateTime);
        return data !== undefined ? [
          data.flakeRate,
          `<div style="padding: 1rem; font-family: 'Arial'; font-size: 14">
            <b style="display: block">${name}</b><br>
            <b>Date:</b> ${data.date.toLocaleString([], {dateStyle: 'medium'})}<br>
            <b>Flake Percentage:</b> ${data.flakeRate.toFixed(2)}%<br>
            <b>Jobs:</b><br>
            ${data.jobs.map(({ id, status }) => `  - <a href="${testGopoghLink(id, environmentName, name)}">${id}</a> (${status})`).join("<br>")}
          </div>`
        ] : [null, null];
      })).flat())
    );
    const options = {
      title: `Flake rate by week of top ${topFlakes} of recent test flakiness (past week) on ${environmentName}`,
      width: window.innerWidth,
      height: window.innerHeight,
      pointSize: 10,
      pointShape: "circle",
      vAxes: {
        0: { title: "Flake rate", minValue: 0, maxValue: 100 },
      },
      tooltip: { trigger: "selection", isHtml: true }
    };
    const flakeRateContainer = document.createElement("div");
    flakeRateContainer.style.width = "100vw";
    flakeRateContainer.style.height = "100vh";
    chartsContainer.appendChild(flakeRateContainer);
    const chart = new google.visualization.LineChart(flakeRateContainer);
    chart.draw(data, options);
  }
  {
    const jobData = 
      testData
        .groupBy(run => run.date.getTime())
        .map(runDate => ({
          date: runDate[0].date,
          runInfo: runDate
            .oneOfEach(run => run.rootJob)
            .map(run => ({
              commit: run.commit,
              rootJob: run.rootJob,
              testCount: run.testCount,
              totalDuration: run.totalDuration
            }))
        }))
        .sort((a, b) => a.date - b.date)
        .map(({date, runInfo}) => ({
          date,
          runInfo,
          testCount: runInfo.map(job => job.testCount).average(),
          totalDuration: runInfo.map(job => job.totalDuration).average(),
        }));

    const data = new google.visualization.DataTable();
    data.addColumn('date', 'Date');
    data.addColumn('number', 'Test Count');
    data.addColumn({ type: 'string', role: 'tooltip', 'p': { 'html': true } });
    data.addColumn('number', 'Duration');
    data.addColumn({ type: 'string', role: 'tooltip', 'p': { 'html': true } });
    data.addRows(
      jobData.map(dateInfo => [
        dateInfo.date,
        dateInfo.testCount,
        `<div style="padding: 1rem; font-family: 'Arial'; font-size: 14">
          <b>Date:</b> ${dateInfo.date.toLocaleString([], {dateStyle: 'medium'})}<br>
          <b>Test Count (averaged): </b> ${+dateInfo.testCount.toFixed(2)}<br>
          <b>Jobs:</b><br>
          ${dateInfo.runInfo.map(job => `  - <a href="${testGopoghLink(job.rootJob, environmentName)}">${job.rootJob}</a> Test count: ${job.testCount}`).join("<br>")}
        </div>`,
        dateInfo.totalDuration,
        `<div style="padding: 1rem; font-family: 'Arial'; font-size: 14">
          <b>Date:</b> ${dateInfo.date.toLocaleString([], {dateStyle: 'medium'})}<br>
          <b>Total Duration (averaged): </b> ${+dateInfo.totalDuration.toFixed(2)}<br>
          <b>Jobs:</b><br>
          ${dateInfo.runInfo.map(job => `  - <a href="${testGopoghLink(job.rootJob, environmentName)}">${job.rootJob}</a> Total Duration: ${+job.totalDuration.toFixed(2)}s`).join("<br>")}
        </div>`,
      ]));
    const options = {
      title: `Test count and total duration by day on ${environmentName}`,
      width: window.innerWidth,
      height: window.innerHeight,
      pointSize: 10,
      pointShape: "circle",
      series: {
        0: { targetAxisIndex: 0 },
        1: { targetAxisIndex: 1 },
      },
      vAxes: {
        0: { title: "Test Count", minValue: 0 },
        1: { title: "Duration (seconds)", minValue: 0 },
      },
      tooltip: { trigger: "selection", isHtml: true }
    };
    const testCountContainer = document.createElement("div");
    testCountContainer.style.width = "100vw";
    testCountContainer.style.height = "100vh";
    chartsContainer.appendChild(testCountContainer);
    const chart = new google.visualization.LineChart(testCountContainer);
    chart.draw(data, options);
  }

  chartsContainer.appendChild(
    createRecentFlakePercentageTable(
      recentFlakePercentage,
      previousFlakePercentageMap,
      environmentName));
}