lodash#minBy TypeScript Examples

The following examples show how to use lodash#minBy. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: bitrise_client.ts    From CIAnalyzer with MIT License 6 votes vote down vote up
// Filter to: lastRunId < Id < firstInprogressId
  filterBuilds (builds: BuildResponse[], lastRunId?: number): BuildResponse[] {
    builds = (lastRunId)
      ? builds.filter((build) => build.build_number > lastRunId)
      : builds
    const firstInprogress = minBy(
      builds.filter((build) => build.status === NOT_FINISHED_STATUS ),
      (build) => build.build_number
    )
    builds = (firstInprogress)
      ? builds.filter((build) => build.build_number < firstInprogress.build_number)
      : builds
    return builds
  }
Example #2
Source File: spatial-viewport.ts    From roam-toolkit with MIT License 6 votes vote down vote up
private nodeInMiddleOfViewport(): NodeSingular {
        const viewport = this.cy.extent()
        const viewportMiddle = {
            x: viewport.x1 + viewport.w / 2,
            y: viewport.y1 + viewport.h / 2,
        }
        return assumeExists(
            minBy(
                this.cy.nodes().map(node => node),
                node => {
                    return getDistance(viewportMiddle, node.position())
                }
            )
        )
    }
Example #3
Source File: spatial-viewport.ts    From roam-toolkit with MIT License 6 votes vote down vote up
private selectClosestInCone(coneCenter: number, coneWidth: number, doublingAngle = 70) {
        const selection = this.selectedNodes().first()
        const lowerAngle = coneCenter - coneWidth / 2
        const higherAngle = coneCenter + coneWidth / 2
        const polarDistances = this.cy
            .nodes()
            .filter(node => node.id() !== selection.id())
            .map((node: NodeSingular) => ({
                node,
                angle: getAngle(selection.position(), node.position(), lowerAngle),
                distance: getDistance(selection.position(), node.position()),
            }))

        // Treat nodes offset by the doublingAngle as twice as far away
        const adjustedDistance = (distance: number, offsetFromConeCenter: number) =>
            (distance * (doublingAngle + Math.abs(offsetFromConeCenter))) / doublingAngle
        const closestInCone = minBy(
            polarDistances.filter(({angle}) => lowerAngle < angle && angle < higherAngle),
            ({distance, angle}) => adjustedDistance(distance, angle - coneCenter)
        )?.node
        if (closestInCone) {
            this.selectNode(closestInCone)
            this.panToSelectionIfNeeded()
        }
    }
Example #4
Source File: traceSummary.ts    From erda-ui with GNU Affero General Public License v3.0 6 votes vote down vote up
export function totalServiceTime(stamps: any, acc = 0): any {
  // This is a recursive function that performs arithmetic on duration
  // If duration is undefined, it will infinitely recurse. Filter out that case
  const filtered = stamps.filter((s: any) => s.duration);
  if (filtered.length === 0) {
    return acc;
  }
  const ts = minBy(filtered, (s: any) => s.timestamp);
  const [current, next] = partition(
    filtered,
    (t) => t.timestamp >= ts.timestamp && t.timestamp + t.duration <= ts.timestamp + ts.duration,
  );
  const endTs = Math.max(...current.map((t) => t.timestamp + t.duration));
  return totalServiceTime(next, acc + (endTs - ts.timestamp));
}
Example #5
Source File: map.ts    From aqualink-app with MIT License 6 votes vote down vote up
findClosestSurveyPoint = (
  sitePolygon?: Polygon | Point,
  points?: SurveyPoints[]
) => {
  if (!sitePolygon || !points) {
    return undefined;
  }

  const [siteLng, siteLat] =
    sitePolygon.type === "Polygon"
      ? getMiddlePoint(sitePolygon)
      : sitePolygon.coordinates;

  const closestPoint = minBy(
    points.filter((item) => item.polygon),
    (point) => {
      const polygon = point.polygon as Polygon | Point;
      return radDistanceCalculator(
        [siteLng, siteLat],
        polygon.type === "Point" ? polygon.coordinates : getMiddlePoint(polygon)
      );
    }
  );

  // if there is no closestPoint - return the first one by id.
  const resultingPoint = closestPoint || minBy(points, "id");

  return {
    ...resultingPoint,
    id: resultingPoint?.id.toString(),
    name: resultingPoint?.name || undefined,
  };
}
Example #6
Source File: utils.ts    From aqualink-app with MIT License 6 votes vote down vote up
calculateAxisLimits = (
  datasets: ChartProps["datasets"],
  startDate: ChartProps["startDate"],
  endDate: ChartProps["endDate"],
  temperatureThreshold: ChartProps["temperatureThreshold"]
) => {
  const timestampsToConsiderForXAxis = getDatasetsTimestamps(
    datasets?.filter((dataset) => dataset.considerForXAxisLimits)
  );
  const accumulatedYAxisData = flatten(
    map(datasets, ({ data }) =>
      map(
        filter(data, ({ value }) => !isNil(value)),
        ({ value }) => value
      )
    )
  );

  // x axis limits calculation
  const datasetsXMin = minBy(
    timestampsToConsiderForXAxis,
    (timestamp) => new Date(timestamp)
  );
  const datasetsXMax = maxBy(
    timestampsToConsiderForXAxis,
    (timestamp) => new Date(timestamp)
  );
  const xAxisMin = startDate || datasetsXMin;
  const xAxisMax = endDate || datasetsXMax;

  // y axis limits calculation
  const datasetsYMin = Math.min(...accumulatedYAxisData);
  const datasetsYMax = Math.max(...accumulatedYAxisData);
  const ySpacing = Math.ceil(
    Y_SPACING_PERCENTAGE * (datasetsYMax - datasetsYMin)
  ); // Set ySpacing as a percentage of the data range
  const yAxisMinTemp = datasetsYMin - ySpacing;
  const yAxisMaxTemp = datasetsYMax + ySpacing;
  const yAxisMin = Math.round(
    temperatureThreshold
      ? Math.min(yAxisMinTemp, temperatureThreshold - ySpacing)
      : yAxisMinTemp
  );
  const yAxisMax = Math.round(
    temperatureThreshold
      ? Math.max(yAxisMaxTemp, temperatureThreshold + ySpacing)
      : yAxisMaxTemp
  );

  return { xAxisMin, xAxisMax, yAxisMin, yAxisMax };
}
Example #7
Source File: helpers.ts    From aqualink-app with MIT License 6 votes vote down vote up
calculateCardMetrics = (
  from: string,
  to: string,
  data?: ValueWithTimestamp[],
  keyPrefix?: string
): CardColumn["rows"] => {
  const filteredData = filterSofarData(from, to)(data);

  return [
    {
      key: `${keyPrefix}-max`,
      value: filteredData?.[0]
        ? maxBy(filteredData, "value")?.value
        : undefined,
    },
    {
      key: `${keyPrefix}-mean`,
      value: filteredData?.[0] ? meanBy(filteredData, "value") : undefined,
    },
    {
      key: `${keyPrefix}-min`,
      value: filteredData?.[0]
        ? minBy(filteredData, "value")?.value
        : undefined,
    },
  ];
}
Example #8
Source File: jenkins_client.ts    From CIAnalyzer with MIT License 6 votes vote down vote up
// Filter to: lastRunId < Id < firstInprogressId
  filterJobRuns (runs: WfapiRunResponse[], lastRunId?: number): WfapiRunResponse[] {
    runs = (lastRunId)
      ? runs.filter((run) => Number(run.id) > lastRunId)
      : runs
    const firstInprogress = minBy(
      runs.filter((run) => run.status === 'IN_PROGRESS' ),
      (run) => Number(run.id)
    )
    runs = (firstInprogress)
      ? runs.filter((run) => Number(run.id) < Number(firstInprogress.id))
      : runs
    return runs
  }
Example #9
Source File: github_client.ts    From CIAnalyzer with MIT License 6 votes vote down vote up
// Filter to: lastRunId < Id < firstInprogressId
  filterWorkflowRuns (runs: WorkflowRunsItem[], lastRunId?: number): WorkflowRunsItem[] {
    runs = (lastRunId)
      ? runs.filter((run) => run.run_number > lastRunId)
      : runs
    const firstInprogress = minBy(
      runs.filter((run) => run.status as RunStatus === 'in_progress'),
      (run) => run.run_number
    )
    runs = (firstInprogress)
      ? runs.filter((run) => run.run_number < firstInprogress.run_number)
      : runs
    return runs
  }
Example #10
Source File: circleci_client_v2.ts    From CIAnalyzer with MIT License 6 votes vote down vote up
// Filter pipelines with last build number < first running build number
  // And also ignore still running pipelines.
  private filterPipelines (pipelines: Pipeline[]): Pipeline[] {
    // Ignore pipeline that has not any workflows.
    // Ignore pipeline that has 'not_run' status workflows that are [ci-skip] commit OR skipped redundant build.
    pipelines = pipelines.filter((pipeline) => {
      return pipeline.workflows.length > 0
        && !pipeline.workflows.some((workflow) => workflow.status === 'not_run')
    })

    const inprogressPipeline = pipelines.filter((pipeline) => {
      return pipeline.workflows.every((workflow) => workflow.status === 'running')
    })
    const firstInprogress = minBy(
      inprogressPipeline,
      (pipeline) => pipeline.number,
    )
    pipelines = (firstInprogress)
      ? pipelines.filter((pipeline) => pipeline.number < firstInprogress.number)
      : pipelines
    return pipelines
  }
Example #11
Source File: circleci_client.ts    From CIAnalyzer with MIT License 6 votes vote down vote up
// Filter to: Each workflow's last build number < first running build number
  filterWorkflowRuns (runs: WorkflowRun[]): WorkflowRun[] {
    // Ignore not_run workflows that are [ci-skip] commit OR skipped redundant build
    runs = runs.filter((run) => { return !run.lifecycles.some((lifecycle) => lifecycle === 'not_run') })

    const inprogressRuns = runs.filter((run) => {
      return !run.lifecycles.every((lifecycle) => lifecycle === 'finished')
    })
    const firstInprogress = minBy(
      inprogressRuns,
      (run) => run.last_build_num,
    )
    runs = (firstInprogress)
      ? runs.filter((run) => run.last_build_num < firstInprogress.last_build_num)
      : runs
    return runs
  }
Example #12
Source File: upload-hobo-data.ts    From aqualink-app with MIT License 5 votes vote down vote up
parseHoboData = async (
  poiEntities: SiteSurveyPoint[],
  dbIdToCSVId: Record<number, number>,
  rootPath: string,
  poiToSourceMap: Dictionary<Sources>,
  timeSeriesRepository: Repository<TimeSeries>,
) => {
  // Parse hobo data
  const parsedData = poiEntities.map((poi) => {
    const colonyId = poi.name.split(' ')[1].padStart(3, '0');
    const dataFile = COLONY_DATA_FILE.replace('{}', colonyId);
    const colonyFolder = COLONY_FOLDER_PREFIX + colonyId;
    const siteFolder = FOLDER_PREFIX + dbIdToCSVId[poi.site.id];
    const filePath = path.join(rootPath, siteFolder, colonyFolder, dataFile);
    const headers = [undefined, 'id', 'dateTime', 'bottomTemperature'];
    const castFunction = castCsvValues(
      ['id'],
      ['bottomTemperature'],
      ['dateTime'],
    );
    return parseCSV<Data>(filePath, headers, castFunction).map((data) => ({
      timestamp: data.dateTime,
      value: data.bottomTemperature,
      source: poiToSourceMap[poi.id],
      metric: Metric.BOTTOM_TEMPERATURE,
    }));
  });

  // Find the earliest date of data
  const startDates = parsedData.reduce((acc, data) => {
    const minimum = minBy(data, (o) => o.timestamp);

    if (!minimum) {
      return acc;
    }

    return acc.concat(minimum);
  }, []);

  const groupedStartedDates = keyBy(startDates, (o) => o.source.site.id);

  // Start a backfill for each site
  const siteDiffDays: [number, number][] = Object.keys(groupedStartedDates).map(
    (siteId) => {
      const startDate = groupedStartedDates[siteId];
      if (!startDate) {
        return [parseInt(siteId, 10), 0];
      }

      const start = moment(startDate.timestamp);
      const end = moment();
      const diff = Math.min(end.diff(start, 'd'), 200);

      return [startDate.source.site.id, diff];
    },
  );

  const bottomTemperatureData = parsedData.flat();

  // Data are to much to added with one bulk insert
  // So we need to break them in batches
  const batchSize = 1000;
  logger.log(`Saving time series data in batches of ${batchSize}`);
  const inserts = chunk(bottomTemperatureData, batchSize).map((batch) => {
    return timeSeriesRepository
      .createQueryBuilder('time_series')
      .insert()
      .values(batch)
      .onConflict('ON CONSTRAINT "no_duplicate_data" DO NOTHING')
      .execute();
  });

  // Return insert promises and print progress updates
  const actionsLength = inserts.length;
  await Bluebird.Promise.each(inserts, (props, idx) => {
    logger.log(`Saved ${idx + 1} out of ${actionsLength} batches`);
  });

  return siteDiffDays;
}
Example #13
Source File: hooks.ts    From glide-frontend with GNU General Public License v3.0 5 votes vote down vote up
useGetEarliestEpoch = () => {
  return useSelector((state: State) => {
    const earliestRound = minBy(Object.values(state.predictions.rounds), 'epoch')
    return earliestRound?.epoch
  })
}
Example #14
Source File: hooks.ts    From glide-frontend with GNU General Public License v3.0 5 votes vote down vote up
useGetEarliestEpoch = () => {
  return useSelector((state: State) => {
    const earliestRound = minBy(Object.values(state.predictions.rounds), 'epoch')
    return earliestRound?.epoch
  })
}
Example #15
Source File: gamelogic.ts    From sc2-planner with MIT License 5 votes vote down vote up
/**
     * Checks if what an item requires is present or not
     */
    addRequirements(itemName: string, requires: string[][]): boolean {
        this.requirements = this.requirements || []
        const itemPresence: { [unitName: string]: boolean } = {}
        for (const item of this.units) {
            let itemName = item.name
            if (item.hasTechlab) {
                itemName += "TechLab"
            }
            if (item.hasReactor) {
                itemName += "Reactor"
            }
            itemPresence[itemName] = true
        }
        for (const upgradeName of this.upgrades) {
            itemPresence[upgradeName] = true
        }

        const errorList: IError[] = []
        for (const requirementList of requires) {
            const error: IError = {
                message: "",
                requirements: [],
                neededEffort: 0,
            }
            errorList.push(error)
            for (const requiredItem of requirementList) {
                if (!itemPresence[requiredItem]) {
                    error.message = `Required ${requiredItem} for ${itemName} could not be found.`
                    error.requirements.push(BO_ITEMS[requiredItem])
                    error.neededEffort += 1 / requirementList.length
                }
            }
        }

        const leastEffortError = minBy(errorList, "neededEffort")
        if (leastEffortError && leastEffortError.neededEffort) {
            this.errorMessage = leastEffortError.message
            this.requirements.push(...leastEffortError.requirements)
            return false
        }
        return true
    }
Example #16
Source File: hooks.ts    From vvs-ui with GNU General Public License v3.0 5 votes vote down vote up
useGetEarliestEpoch = () => {
  return useSelector((state: State) => {
    const earliestRound = minBy(Object.values(state.predictions.rounds), 'epoch')
    return earliestRound?.epoch
  })
}
Example #17
Source File: upload-sheet-data.ts    From aqualink-app with MIT License 4 votes vote down vote up
uploadTimeSeriesData = async (
  filePath: string,
  fileName: string,
  siteId: string,
  surveyPointId: string | undefined,
  sourceType: SourceType,
  repositories: Repositories,
  failOnWarning?: boolean,
  mimetype?: Mimetype,
) => {
  // // TODO
  // // - Add foreign key constraint to sources on site_id
  console.time(`Upload datafile ${fileName}`);
  const { site, surveyPoint } = surveyPointId
    ? await getSiteAndSurveyPoint(
        parseInt(siteId, 10),
        parseInt(surveyPointId, 10),
        repositories.siteRepository,
        repositories.surveyPointRepository,
      )
    : {
        site: await getSite(parseInt(siteId, 10), repositories.siteRepository),
        surveyPoint: undefined,
      };

  const existingSourceEntity = await repositories.sourcesRepository.findOne({
    relations: ['surveyPoint', 'site'],
    where: {
      site: { id: siteId },
      surveyPoint: surveyPointId || null,
      type: sourceType,
    },
  });

  const sourceEntity =
    existingSourceEntity ||
    (await repositories.sourcesRepository.save({
      type: sourceType,
      site,
      surveyPoint,
    }));

  if (
    sourceType === SourceType.SONDE ||
    sourceType === SourceType.METLOG ||
    sourceType === SourceType.HOBO
  ) {
    const workSheetsFromFile = xlsx.parse(filePath, { raw: true });
    const workSheetData = workSheetsFromFile[0]?.data;
    const { ignoredHeaders, importedHeaders } = validateHeaders(
      fileName,
      workSheetData,
      sourceType,
    );

    if (failOnWarning && ignoredHeaders.length > 0) {
      throw new BadRequestException(
        `${fileName}: The columns ${ignoredHeaders
          .map((header) => `"${header}"`)
          .join(
            ', ',
          )} are not configured for import yet and cannot be uploaded.`,
      );
    }

    const signature = await md5Fle(filePath);

    const uploadExists = await repositories.dataUploadsRepository.findOne({
      where: {
        signature,
        site,
        surveyPoint,
        sensorType: sourceType,
      },
    });

    if (uploadExists) {
      throw new ConflictException(
        `${fileName}: A file upload named '${uploadExists.file}' with the same data already exists`,
      );
    }
    console.time(`Get data from sheet ${fileName}`);
    const results = findSheetDataWithHeader(
      fileName,
      workSheetData,
      sourceType,
      mimetype,
    );
    console.timeEnd(`Get data from sheet ${fileName}`);

    console.time(`Remove duplicates and empty values ${fileName}`);
    const data = uniqBy(
      results
        .reduce((timeSeriesObjects: any[], object) => {
          const { timestamp } = object;
          return [
            ...timeSeriesObjects,
            ...Object.keys(object)
              .filter((k) => k !== 'timestamp')
              .map((key) => {
                return {
                  timestamp,
                  value: parseFloat(object[key]),
                  metric: key,
                  source: sourceEntity,
                };
              }),
          ];
        }, [])
        .filter((valueObject) => {
          if (!isNaN(parseFloat(valueObject.value))) {
            return true;
          }
          logger.log('Excluding incompatible value:');
          logger.log(valueObject);
          return false;
        }),
      ({ timestamp, metric, source }) =>
        `${timestamp}, ${metric}, ${source.id}`,
    );
    console.timeEnd(`Remove duplicates and empty values ${fileName}`);

    const minDate = get(
      minBy(data, (item) => new Date(get(item, 'timestamp')).getTime()),
      'timestamp',
    );
    const maxDate = get(
      maxBy(data, (item) => new Date(get(item, 'timestamp')).getTime()),
      'timestamp',
    );

    // Initialize google cloud service, to be used for media upload
    const googleCloudService = new GoogleCloudService();

    // Note this may fail. It would still return a location, but the file may not have been uploaded
    const fileLocation = googleCloudService.uploadFileAsync(
      filePath,
      sourceType,
      'data_uploads',
      'data_upload',
    );

    const dataUploadsFile = await repositories.dataUploadsRepository.save({
      file: fileName,
      signature,
      sensorType: sourceType,
      site,
      surveyPoint,
      minDate,
      maxDate,
      metrics: importedHeaders,
      fileLocation,
    });

    const dataAsTimeSeries = data.map((x: any) => {
      return {
        timestamp: x.timestamp,
        value: x.value,
        metric: x.metric,
        source: x.source,
        dataUpload: dataUploadsFile,
      };
    });

    // Data is too big to added with one bulk insert so we batch the upload.
    console.time(`Loading into DB ${fileName}`);
    const batchSize = 100;
    logger.log(`Saving time series data in batches of ${batchSize}`);
    const inserts = chunk(dataAsTimeSeries, batchSize).map(
      async (batch: any[]) => {
        try {
          await repositories.timeSeriesRepository
            .createQueryBuilder('time_series')
            .insert()
            .values(batch)
            // If there's a conflict, replace data with the new value.
            // onConflict is deprecated, but updating it is tricky.
            // See https://github.com/typeorm/typeorm/issues/8731?fbclid=IwAR2Obg9eObtGNRXaFrtKvkvvVSWfvjtHpFu-VEM47yg89SZcPpxEcZOmcLw
            .onConflict(
              'ON CONSTRAINT "no_duplicate_data" DO UPDATE SET "value" = excluded.value',
            )
            .execute();
        } catch {
          console.warn('The following batch failed to upload:');
          console.warn(batch);
        }
        return true;
      },
    );

    // Return insert promises and print progress updates
    const actionsLength = inserts.length;
    await Bluebird.Promise.each(inserts, (props, idx) => {
      logger.log(`Saved ${idx + 1} out of ${actionsLength} batches`);
    });
    console.timeEnd(`Loading into DB ${fileName}`);
    logger.log('loading complete');

    refreshMaterializedView(repositories);

    console.timeEnd(`Upload datafile ${fileName}`);
    return ignoredHeaders;
  }

  return [];
}
Example #18
Source File: ChartWithTooltip.tsx    From aqualink-app with MIT License 4 votes vote down vote up
function ChartWithTooltip({
  chartSettings,
  children,
  className,
  style,
  ...rest
}: PropsWithChildren<ChartWithTooltipProps>) {
  const { siteId, surveys, timeZone, startDate, endDate, datasets } = rest;
  const chartDataRef = useRef<Line>(null);

  const [tooltipPosition, setTooltipPosition] = useState({ top: 0, left: 0 });
  const [tooltipData, setTooltipData] = useState<TooltipData>({
    siteId,
    date: "",
    datasets: [],
    surveyId: null,
  });
  const [showTooltip, setShowTooltip] = useState<boolean>(false);

  const customTooltip =
    (ref: React.RefObject<Line>) => (tooltipModel: ChartTooltipModel) => {
      const chart = ref.current;
      if (!chart?.chartInstance.canvas) {
        return;
      }

      const date = tooltipModel.dataPoints?.[0]?.xLabel;
      if (typeof date !== "string") return;

      const dateObject = new Date(date);

      const surveyId = findSurveyFromDate(date, surveys);

      const datasetsDates = getDatasetsTimestamps(datasets);
      const minDataDate = minBy(datasetsDates, (item) => new Date(item));
      const maxDataDate = maxBy(datasetsDates, (item) => new Date(item));

      const closestDatasetData = getTooltipClosestData(dateObject, datasets);

      const nValues = closestDatasetData
        .map(({ data }) => head(data)?.value)
        .filter(isNumber).length;

      // Chart.js displays tooltips in a parallel to the X axis preference, meaning
      // that it will appear right or left from the chart point. We want to change that,
      // and display the tooltip in a Y axis preference, and more specifically, above the chart point.
      const position = chart.chartInstance.canvas.getBoundingClientRect();

      // We center the tooltip in the X axis by subtracting half its width.
      const left = position.left + tooltipModel.caretX - TOOLTIP_WIDTH / 2;

      // We increase the tooltip's top, so that it lands above the chart point. The amount by
      // which we increase varies based on how many values we display and if there is a survey at that point,
      // as we display a `VIEW SURVEY` button.
      const top =
        position.top +
        tooltipModel.caretY -
        ((surveyId ? 30 : 0) + nValues * 20 + 50);

      // We display the tooltip only if there are data to display at this point and it lands
      // between the chart's X axis limits.
      if (
        nValues > 0 &&
        moment(date).isBetween(
          moment(startDate || minDataDate),
          moment(endDate || maxDataDate),
          undefined,
          "[]"
        )
      ) {
        setTooltipPosition({ top, left });
        setTooltipData({
          ...tooltipData,
          date,
          surveyId,
          datasets: closestDatasetData,
        });
        setShowTooltip(true);
      }
    };

  const hideTooltip = () => {
    setShowTooltip(false);
  };

  // Hide tooltip on scroll to avoid dragging it on the page.
  if (showTooltip) {
    window.addEventListener("scroll", hideTooltip);
  }

  return (
    <div className={className} style={style} onMouseLeave={hideTooltip}>
      {children}
      <Chart
        {...rest}
        chartRef={chartDataRef}
        chartSettings={{
          tooltips: {
            enabled: false,
            intersect: false,
            custom: customTooltip(chartDataRef),
          },
          legend: {
            display: false,
          },
          // we could use mergeWith here too, but currently nothing would use it.
          ...chartSettings,
        }}
      />
      {showTooltip ? (
        <div
          className="chart-tooltip"
          id="chart-tooltip"
          style={{
            position: "fixed",
            top: tooltipPosition.top,
            left: tooltipPosition.left,
          }}
        >
          <Tooltip
            {...tooltipData}
            siteTimeZone={timeZone}
            userTimeZone={Intl.DateTimeFormat().resolvedOptions().timeZone}
          />
        </div>
      ) : null}
    </div>
  );
}