mirror of
				https://github.com/open-metadata/OpenMetadata.git
				synced 2025-11-03 20:19:31 +00:00 
			
		
		
		
	* #13445 Profiler Graph Should not Show a Point if value is None * addressing comment * fixed sonarcloud
This commit is contained in:
		
							parent
							
								
									d3ebe6fb4b
								
							
						
					
					
						commit
						2337e239a4
					
				@ -16,6 +16,7 @@ import React from 'react';
 | 
			
		||||
import { getStatisticsDisplayValue } from '../../../utils/CommonUtils';
 | 
			
		||||
import { ProfilerLatestValueProps } from '../profilerDashboard.interface';
 | 
			
		||||
 | 
			
		||||
import { isUndefined } from 'lodash';
 | 
			
		||||
import '../profiler-dashboard.less';
 | 
			
		||||
 | 
			
		||||
const ProfilerLatestValue = ({
 | 
			
		||||
@ -23,6 +24,18 @@ const ProfilerLatestValue = ({
 | 
			
		||||
  tickFormatter,
 | 
			
		||||
  stringValue = false,
 | 
			
		||||
}: ProfilerLatestValueProps) => {
 | 
			
		||||
  const getLatestValue = (value?: number | string) => {
 | 
			
		||||
    if (isUndefined(value)) {
 | 
			
		||||
      return '--';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (tickFormatter || stringValue) {
 | 
			
		||||
      return `${value}${tickFormatter ?? ''}`;
 | 
			
		||||
    } else {
 | 
			
		||||
      return getStatisticsDisplayValue(value);
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Space data-testid="data-summary-container" direction="vertical" size={16}>
 | 
			
		||||
      {information.map((info) => (
 | 
			
		||||
@ -36,11 +49,7 @@ const ProfilerLatestValue = ({
 | 
			
		||||
              {info.title}
 | 
			
		||||
            </Typography.Text>
 | 
			
		||||
          }
 | 
			
		||||
          value={
 | 
			
		||||
            tickFormatter || stringValue
 | 
			
		||||
              ? `${info.latestValue}${tickFormatter ?? ''}`
 | 
			
		||||
              : getStatisticsDisplayValue(info.latestValue)
 | 
			
		||||
          }
 | 
			
		||||
          value={getLatestValue(info.latestValue)}
 | 
			
		||||
          valueStyle={{ color: info.color, fontSize: '18px', fontWeight: 700 }}
 | 
			
		||||
        />
 | 
			
		||||
      ))}
 | 
			
		||||
 | 
			
		||||
@ -12,31 +12,27 @@
 | 
			
		||||
 */
 | 
			
		||||
import { Card, Col, Row, Typography } from 'antd';
 | 
			
		||||
import { AxiosError } from 'axios';
 | 
			
		||||
import { first, isString, last, sortBy } from 'lodash';
 | 
			
		||||
import { first, isString, last } from 'lodash';
 | 
			
		||||
import React, { FC, useEffect, useMemo, useState } from 'react';
 | 
			
		||||
import { useTranslation } from 'react-i18next';
 | 
			
		||||
import DataDistributionHistogram from '../../../components/Chart/DataDistributionHistogram.component';
 | 
			
		||||
import ProfilerDetailsCard from '../../../components/ProfilerDashboard/component/ProfilerDetailsCard';
 | 
			
		||||
import { DateRangeObject } from '../../../components/ProfilerDashboard/component/TestSummary';
 | 
			
		||||
import { MetricChartType } from '../../../components/ProfilerDashboard/profilerDashboard.interface';
 | 
			
		||||
import {
 | 
			
		||||
  DEFAULT_RANGE_DATA,
 | 
			
		||||
  INITIAL_COUNT_METRIC_VALUE,
 | 
			
		||||
  INITIAL_MATH_METRIC_VALUE,
 | 
			
		||||
  INITIAL_PROPORTION_METRIC_VALUE,
 | 
			
		||||
  INITIAL_QUARTILE_METRIC_VALUE,
 | 
			
		||||
  INITIAL_SUM_METRIC_VALUE,
 | 
			
		||||
  INITIAL_COLUMN_METRICS_VALUE,
 | 
			
		||||
} from '../../../constants/profiler.constant';
 | 
			
		||||
import { ColumnProfile } from '../../../generated/entity/data/container';
 | 
			
		||||
import { Table } from '../../../generated/entity/data/table';
 | 
			
		||||
import { getColumnProfilerList } from '../../../rest/tableAPI';
 | 
			
		||||
import { getEncodedFqn } from '../../../utils/StringsUtils';
 | 
			
		||||
import {
 | 
			
		||||
  calculateColumnProfilerMetrics,
 | 
			
		||||
  calculateCustomMetrics,
 | 
			
		||||
  getColumnCustomMetric,
 | 
			
		||||
} from '../../../utils/TableProfilerUtils';
 | 
			
		||||
import { ColumnMetricsInterface } from '../../../utils/TableProfilerUtils.interface';
 | 
			
		||||
import { showErrorToast } from '../../../utils/ToastUtils';
 | 
			
		||||
import { customFormatDateTime } from '../../../utils/date-time/DateTimeUtils';
 | 
			
		||||
import CustomMetricGraphs from '../CustomMetricGraphs/CustomMetricGraphs.component';
 | 
			
		||||
import { useTableProfiler } from '../TableProfilerProvider';
 | 
			
		||||
 | 
			
		||||
@ -67,22 +63,10 @@ const SingleColumnProfile: FC<SingleColumnProfileProps> = ({
 | 
			
		||||
      ) ?? [],
 | 
			
		||||
    [tableCustomMetric, activeColumnFqn, tableDetails]
 | 
			
		||||
  );
 | 
			
		||||
  const [countMetrics, setCountMetrics] = useState<MetricChartType>(
 | 
			
		||||
    INITIAL_COUNT_METRIC_VALUE
 | 
			
		||||
  );
 | 
			
		||||
  const [proportionMetrics, setProportionMetrics] = useState<MetricChartType>(
 | 
			
		||||
    INITIAL_PROPORTION_METRIC_VALUE
 | 
			
		||||
  );
 | 
			
		||||
  const [mathMetrics, setMathMetrics] = useState<MetricChartType>(
 | 
			
		||||
    INITIAL_MATH_METRIC_VALUE
 | 
			
		||||
  );
 | 
			
		||||
  const [sumMetrics, setSumMetrics] = useState<MetricChartType>(
 | 
			
		||||
    INITIAL_SUM_METRIC_VALUE
 | 
			
		||||
  const [columnMetric, setColumnMetric] = useState<ColumnMetricsInterface>(
 | 
			
		||||
    INITIAL_COLUMN_METRICS_VALUE
 | 
			
		||||
  );
 | 
			
		||||
  const [isMinMaxStringData, setIsMinMaxStringData] = useState(false);
 | 
			
		||||
  const [quartileMetrics, setQuartileMetrics] = useState<MetricChartType>(
 | 
			
		||||
    INITIAL_QUARTILE_METRIC_VALUE
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const columnCustomMetrics = useMemo(
 | 
			
		||||
    () => calculateCustomMetrics(columnProfilerData, customMetrics),
 | 
			
		||||
@ -115,116 +99,17 @@ const SingleColumnProfile: FC<SingleColumnProfileProps> = ({
 | 
			
		||||
  }, [columnProfilerData]);
 | 
			
		||||
 | 
			
		||||
  const createMetricsChartData = () => {
 | 
			
		||||
    const updateProfilerData = sortBy(columnProfilerData, 'timestamp');
 | 
			
		||||
    const countMetricData: MetricChartType['data'] = [];
 | 
			
		||||
    const proportionMetricData: MetricChartType['data'] = [];
 | 
			
		||||
    const mathMetricData: MetricChartType['data'] = [];
 | 
			
		||||
    const sumMetricData: MetricChartType['data'] = [];
 | 
			
		||||
    const quartileMetricData: MetricChartType['data'] = [];
 | 
			
		||||
    updateProfilerData.forEach((col) => {
 | 
			
		||||
      const timestamp = customFormatDateTime(col.timestamp, 'MMM dd, hh:mm');
 | 
			
		||||
 | 
			
		||||
      countMetricData.push({
 | 
			
		||||
        name: timestamp,
 | 
			
		||||
        timestamp: col.timestamp,
 | 
			
		||||
        distinctCount: col.distinctCount || 0,
 | 
			
		||||
        nullCount: col.nullCount || 0,
 | 
			
		||||
        uniqueCount: col.uniqueCount || 0,
 | 
			
		||||
        valuesCount: col.valuesCount || 0,
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      sumMetricData.push({
 | 
			
		||||
        name: timestamp,
 | 
			
		||||
        timestamp: col.timestamp || 0,
 | 
			
		||||
        sum: col.sum || 0,
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      mathMetricData.push({
 | 
			
		||||
        name: timestamp,
 | 
			
		||||
        timestamp: col.timestamp || 0,
 | 
			
		||||
        max: col.max || 0,
 | 
			
		||||
        min: col.min || 0,
 | 
			
		||||
        mean: col.mean || 0,
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      proportionMetricData.push({
 | 
			
		||||
        name: timestamp,
 | 
			
		||||
        timestamp: col.timestamp || 0,
 | 
			
		||||
        distinctProportion: Math.round((col.distinctProportion || 0) * 100),
 | 
			
		||||
        nullProportion: Math.round((col.nullProportion || 0) * 100),
 | 
			
		||||
        uniqueProportion: Math.round((col.uniqueProportion || 0) * 100),
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      quartileMetricData.push({
 | 
			
		||||
        name: timestamp,
 | 
			
		||||
        timestamp: col.timestamp || 0,
 | 
			
		||||
        firstQuartile: col.firstQuartile || 0,
 | 
			
		||||
        thirdQuartile: col.thirdQuartile || 0,
 | 
			
		||||
        interQuartileRange: col.interQuartileRange || 0,
 | 
			
		||||
        median: col.median || 0,
 | 
			
		||||
      });
 | 
			
		||||
    const profileMetric = calculateColumnProfilerMetrics({
 | 
			
		||||
      columnProfilerData,
 | 
			
		||||
      ...columnMetric,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const countMetricInfo = countMetrics.information.map((item) => ({
 | 
			
		||||
      ...item,
 | 
			
		||||
      latestValue:
 | 
			
		||||
        countMetricData[countMetricData.length - 1]?.[item.dataKey] || 0,
 | 
			
		||||
    }));
 | 
			
		||||
    const proportionMetricInfo = proportionMetrics.information.map((item) => ({
 | 
			
		||||
      ...item,
 | 
			
		||||
      latestValue: parseFloat(
 | 
			
		||||
        `${
 | 
			
		||||
          proportionMetricData[proportionMetricData.length - 1]?.[
 | 
			
		||||
            item.dataKey
 | 
			
		||||
          ] || 0
 | 
			
		||||
        }`
 | 
			
		||||
      ).toFixed(2),
 | 
			
		||||
    }));
 | 
			
		||||
    const mathMetricInfo = mathMetrics.information.map((item) => ({
 | 
			
		||||
      ...item,
 | 
			
		||||
      latestValue:
 | 
			
		||||
        mathMetricData[mathMetricData.length - 1]?.[item.dataKey] || 0,
 | 
			
		||||
    }));
 | 
			
		||||
    const sumMetricInfo = sumMetrics.information.map((item) => ({
 | 
			
		||||
      ...item,
 | 
			
		||||
      latestValue: sumMetricData[sumMetricData.length - 1]?.[item.dataKey] || 0,
 | 
			
		||||
    }));
 | 
			
		||||
    const quartileMetricInfo = quartileMetrics.information.map((item) => ({
 | 
			
		||||
      ...item,
 | 
			
		||||
      latestValue:
 | 
			
		||||
        quartileMetricData[quartileMetricData.length - 1]?.[item.dataKey] || 0,
 | 
			
		||||
    }));
 | 
			
		||||
 | 
			
		||||
    setCountMetrics((pre) => ({
 | 
			
		||||
      ...pre,
 | 
			
		||||
      information: countMetricInfo,
 | 
			
		||||
      data: countMetricData,
 | 
			
		||||
    }));
 | 
			
		||||
    setProportionMetrics((pre) => ({
 | 
			
		||||
      ...pre,
 | 
			
		||||
      information: proportionMetricInfo,
 | 
			
		||||
      data: proportionMetricData,
 | 
			
		||||
    }));
 | 
			
		||||
    setMathMetrics((pre) => ({
 | 
			
		||||
      ...pre,
 | 
			
		||||
      information: mathMetricInfo,
 | 
			
		||||
      data: mathMetricData,
 | 
			
		||||
    }));
 | 
			
		||||
    setSumMetrics((pre) => ({
 | 
			
		||||
      ...pre,
 | 
			
		||||
      information: sumMetricInfo,
 | 
			
		||||
      data: sumMetricData,
 | 
			
		||||
    }));
 | 
			
		||||
    setQuartileMetrics((pre) => ({
 | 
			
		||||
      ...pre,
 | 
			
		||||
      information: quartileMetricInfo,
 | 
			
		||||
      data: quartileMetricData,
 | 
			
		||||
    }));
 | 
			
		||||
    setColumnMetric(profileMetric);
 | 
			
		||||
 | 
			
		||||
    // only min/max category can be string
 | 
			
		||||
    const isMinMaxString =
 | 
			
		||||
      isString(updateProfilerData[0]?.min) ||
 | 
			
		||||
      isString(updateProfilerData[0]?.max);
 | 
			
		||||
      isString(columnProfilerData[0]?.min) ||
 | 
			
		||||
      isString(columnProfilerData[0]?.max);
 | 
			
		||||
    setIsMinMaxStringData(isMinMaxString);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
@ -243,7 +128,7 @@ const SingleColumnProfile: FC<SingleColumnProfileProps> = ({
 | 
			
		||||
      gutter={[16, 16]}>
 | 
			
		||||
      <Col span={24}>
 | 
			
		||||
        <ProfilerDetailsCard
 | 
			
		||||
          chartCollection={countMetrics}
 | 
			
		||||
          chartCollection={columnMetric.countMetrics}
 | 
			
		||||
          isLoading={isLoading}
 | 
			
		||||
          name="count"
 | 
			
		||||
          title={t('label.data-count-plural')}
 | 
			
		||||
@ -251,7 +136,7 @@ const SingleColumnProfile: FC<SingleColumnProfileProps> = ({
 | 
			
		||||
      </Col>
 | 
			
		||||
      <Col span={24}>
 | 
			
		||||
        <ProfilerDetailsCard
 | 
			
		||||
          chartCollection={proportionMetrics}
 | 
			
		||||
          chartCollection={columnMetric.proportionMetrics}
 | 
			
		||||
          isLoading={isLoading}
 | 
			
		||||
          name="proportion"
 | 
			
		||||
          tickFormatter="%"
 | 
			
		||||
@ -260,7 +145,7 @@ const SingleColumnProfile: FC<SingleColumnProfileProps> = ({
 | 
			
		||||
      </Col>
 | 
			
		||||
      <Col span={24}>
 | 
			
		||||
        <ProfilerDetailsCard
 | 
			
		||||
          chartCollection={mathMetrics}
 | 
			
		||||
          chartCollection={columnMetric.mathMetrics}
 | 
			
		||||
          isLoading={isLoading}
 | 
			
		||||
          name="math"
 | 
			
		||||
          showYAxisCategory={isMinMaxStringData}
 | 
			
		||||
@ -270,7 +155,7 @@ const SingleColumnProfile: FC<SingleColumnProfileProps> = ({
 | 
			
		||||
      </Col>
 | 
			
		||||
      <Col span={24}>
 | 
			
		||||
        <ProfilerDetailsCard
 | 
			
		||||
          chartCollection={sumMetrics}
 | 
			
		||||
          chartCollection={columnMetric.sumMetrics}
 | 
			
		||||
          isLoading={isLoading}
 | 
			
		||||
          name="sum"
 | 
			
		||||
          title={t('label.data-aggregate')}
 | 
			
		||||
@ -278,7 +163,7 @@ const SingleColumnProfile: FC<SingleColumnProfileProps> = ({
 | 
			
		||||
      </Col>
 | 
			
		||||
      <Col span={24}>
 | 
			
		||||
        <ProfilerDetailsCard
 | 
			
		||||
          chartCollection={quartileMetrics}
 | 
			
		||||
          chartCollection={columnMetric.quartileMetrics}
 | 
			
		||||
          isLoading={isLoading}
 | 
			
		||||
          name="quartile"
 | 
			
		||||
          title={t('label.data-quartile-plural')}
 | 
			
		||||
 | 
			
		||||
@ -417,3 +417,11 @@ export const TEST_CASE_STATUS_OPTION = [
 | 
			
		||||
    value: value,
 | 
			
		||||
  })),
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
export const INITIAL_COLUMN_METRICS_VALUE = {
 | 
			
		||||
  countMetrics: INITIAL_COUNT_METRIC_VALUE,
 | 
			
		||||
  proportionMetrics: INITIAL_PROPORTION_METRIC_VALUE,
 | 
			
		||||
  mathMetrics: INITIAL_MATH_METRIC_VALUE,
 | 
			
		||||
  sumMetrics: INITIAL_SUM_METRIC_VALUE,
 | 
			
		||||
  quartileMetrics: INITIAL_QUARTILE_METRIC_VALUE,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,53 @@
 | 
			
		||||
/*
 | 
			
		||||
 *  Copyright 2023 Collate.
 | 
			
		||||
 *  Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 *  you may not use this file except in compliance with the License.
 | 
			
		||||
 *  You may obtain a copy of the License at
 | 
			
		||||
 *  http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *  Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 *  distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 *  See the License for the specific language governing permissions and
 | 
			
		||||
 *  limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
import { isHasKey } from './ObjectUtils';
 | 
			
		||||
 | 
			
		||||
const mockIsHasKeyData = {
 | 
			
		||||
  name: 'John',
 | 
			
		||||
  age: 30,
 | 
			
		||||
  address: '123 Main St',
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
describe('ObjectUtils', () => {
 | 
			
		||||
  it('isHasKey should return true if all keys are present in the object', () => {
 | 
			
		||||
    const keys = ['name', 'age', 'address'];
 | 
			
		||||
 | 
			
		||||
    const result = isHasKey(mockIsHasKeyData, keys, true);
 | 
			
		||||
 | 
			
		||||
    expect(result).toBe(true);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('isHasKey should return false if all keys are not present in the object', () => {
 | 
			
		||||
    const keys = ['name', 'age', 'address', 'gender'];
 | 
			
		||||
 | 
			
		||||
    const result = isHasKey(mockIsHasKeyData, keys, true);
 | 
			
		||||
 | 
			
		||||
    expect(result).toBe(false);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('isHasKey should return true if at least one key is present in the object', () => {
 | 
			
		||||
    const keys = ['name', 'gender'];
 | 
			
		||||
 | 
			
		||||
    const result = isHasKey(mockIsHasKeyData, keys);
 | 
			
		||||
 | 
			
		||||
    expect(result).toBe(true);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('isHasKey should return false if none of the keys are present in the object', () => {
 | 
			
		||||
    const keys = ['gender', 'occupation'];
 | 
			
		||||
 | 
			
		||||
    const result = isHasKey(mockIsHasKeyData, keys);
 | 
			
		||||
 | 
			
		||||
    expect(result).toBe(false);
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
@ -0,0 +1,32 @@
 | 
			
		||||
/*
 | 
			
		||||
 *  Copyright 2023 Collate.
 | 
			
		||||
 *  Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 *  you may not use this file except in compliance with the License.
 | 
			
		||||
 *  You may obtain a copy of the License at
 | 
			
		||||
 *  http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *  Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 *  distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 *  See the License for the specific language governing permissions and
 | 
			
		||||
 *  limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
import { has } from 'lodash';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Checks if the given object has the specified key(s).
 | 
			
		||||
 *
 | 
			
		||||
 * @template T - The type of the object.
 | 
			
		||||
 * @param {T} data - The object to check.
 | 
			
		||||
 * @param {string[]} keys - The key(s) to check for.
 | 
			
		||||
 * @param {boolean} checkAllKey - If true, checks if all keys are present; if false, checks if any key is present.
 | 
			
		||||
 * @returns {boolean} True if the object has the key(s), false otherwise.
 | 
			
		||||
 */
 | 
			
		||||
export const isHasKey = <T>(
 | 
			
		||||
  data: T,
 | 
			
		||||
  keys: string[],
 | 
			
		||||
  checkAllKey = false
 | 
			
		||||
): boolean => {
 | 
			
		||||
  const func = (item: string) => has(data, item);
 | 
			
		||||
 | 
			
		||||
  return checkAllKey ? keys.every(func) : keys.some(func);
 | 
			
		||||
};
 | 
			
		||||
@ -0,0 +1,26 @@
 | 
			
		||||
/*
 | 
			
		||||
 *  Copyright 2023 Collate.
 | 
			
		||||
 *  Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 *  you may not use this file except in compliance with the License.
 | 
			
		||||
 *  You may obtain a copy of the License at
 | 
			
		||||
 *  http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *  Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 *  distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 *  See the License for the specific language governing permissions and
 | 
			
		||||
 *  limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
import { MetricChartType } from '../components/ProfilerDashboard/profilerDashboard.interface';
 | 
			
		||||
import { ColumnProfile } from '../generated/entity/data/table';
 | 
			
		||||
 | 
			
		||||
export interface ColumnMetricsInterface {
 | 
			
		||||
  countMetrics: MetricChartType;
 | 
			
		||||
  proportionMetrics: MetricChartType;
 | 
			
		||||
  mathMetrics: MetricChartType;
 | 
			
		||||
  sumMetrics: MetricChartType;
 | 
			
		||||
  quartileMetrics: MetricChartType;
 | 
			
		||||
}
 | 
			
		||||
export interface CalculateColumnProfilerMetricsInterface
 | 
			
		||||
  extends ColumnMetricsInterface {
 | 
			
		||||
  columnProfilerData: ColumnProfile[];
 | 
			
		||||
}
 | 
			
		||||
@ -12,9 +12,11 @@
 | 
			
		||||
 */
 | 
			
		||||
import { Table, TableProfile } from '../generated/entity/data/table';
 | 
			
		||||
import {
 | 
			
		||||
  calculateColumnProfilerMetrics,
 | 
			
		||||
  calculateCustomMetrics,
 | 
			
		||||
  getColumnCustomMetric,
 | 
			
		||||
} from './TableProfilerUtils';
 | 
			
		||||
import { CalculateColumnProfilerMetricsInterface } from './TableProfilerUtils.interface';
 | 
			
		||||
 | 
			
		||||
jest.mock('./date-time/DateTimeUtils', () => {
 | 
			
		||||
  return {
 | 
			
		||||
@ -22,11 +24,71 @@ jest.mock('./date-time/DateTimeUtils', () => {
 | 
			
		||||
  };
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const columnFqn = 'fqn1';
 | 
			
		||||
const customMetrics = [
 | 
			
		||||
  {
 | 
			
		||||
    id: 'id1',
 | 
			
		||||
    name: 'name1',
 | 
			
		||||
    expression: 'expression1',
 | 
			
		||||
    updatedAt: 1701757494892,
 | 
			
		||||
    updatedBy: 'admin',
 | 
			
		||||
  },
 | 
			
		||||
];
 | 
			
		||||
const table = {
 | 
			
		||||
  fullyQualifiedName: 'fqn',
 | 
			
		||||
  name: 'name',
 | 
			
		||||
  columns: [
 | 
			
		||||
    {
 | 
			
		||||
      fullyQualifiedName: 'fqn1',
 | 
			
		||||
      name: 'name1',
 | 
			
		||||
      customMetrics: customMetrics,
 | 
			
		||||
    },
 | 
			
		||||
  ],
 | 
			
		||||
} as unknown as Table;
 | 
			
		||||
 | 
			
		||||
const countMetrics = {
 | 
			
		||||
  information: [
 | 
			
		||||
    { label: 'Distinct Count', dataKey: 'distinctCount' },
 | 
			
		||||
    { label: 'Null Count', dataKey: 'nullCount' },
 | 
			
		||||
    { label: 'Unique Count', dataKey: 'uniqueCount' },
 | 
			
		||||
    { label: 'Values Count', dataKey: 'valuesCount' },
 | 
			
		||||
  ],
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const proportionMetrics = {
 | 
			
		||||
  information: [
 | 
			
		||||
    { label: 'Distinct Proportion', dataKey: 'distinctProportion' },
 | 
			
		||||
    { label: 'Null Proportion', dataKey: 'nullProportion' },
 | 
			
		||||
    { label: 'Unique Proportion', dataKey: 'uniqueProportion' },
 | 
			
		||||
  ],
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const mathMetrics = {
 | 
			
		||||
  information: [
 | 
			
		||||
    { label: 'Max', dataKey: 'max' },
 | 
			
		||||
    { label: 'Min', dataKey: 'min' },
 | 
			
		||||
    { label: 'Mean', dataKey: 'mean' },
 | 
			
		||||
  ],
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const sumMetrics = {
 | 
			
		||||
  information: [{ label: 'Sum', dataKey: 'sum' }],
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const quartileMetrics = {
 | 
			
		||||
  information: [
 | 
			
		||||
    { label: 'First Quartile', dataKey: 'firstQuartile' },
 | 
			
		||||
    { label: 'Third Quartile', dataKey: 'thirdQuartile' },
 | 
			
		||||
    { label: 'Inter Quartile Range', dataKey: 'interQuartileRange' },
 | 
			
		||||
    { label: 'Median', dataKey: 'median' },
 | 
			
		||||
  ],
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
describe('TableProfilerUtils', () => {
 | 
			
		||||
  it('calculateCustomMetrics should return correct data', () => {
 | 
			
		||||
    const profiler = [
 | 
			
		||||
      {
 | 
			
		||||
        timestamp: 1701757499196,
 | 
			
		||||
        timestamp: 1701757494892,
 | 
			
		||||
        profileSampleType: 'PERCENTAGE',
 | 
			
		||||
        columnCount: 12,
 | 
			
		||||
        rowCount: 14567,
 | 
			
		||||
@ -69,14 +131,14 @@ describe('TableProfilerUtils', () => {
 | 
			
		||||
        {
 | 
			
		||||
          CountOfFRAddress: 1467,
 | 
			
		||||
          formattedTimestamp: 'Dec 05, 11:54',
 | 
			
		||||
          timestamp: 1701757499196,
 | 
			
		||||
          timestamp: 1701757494892,
 | 
			
		||||
        },
 | 
			
		||||
      ],
 | 
			
		||||
      CountOfUSAddress: [
 | 
			
		||||
        {
 | 
			
		||||
          CountOfUSAddress: 15467,
 | 
			
		||||
          formattedTimestamp: 'Dec 05, 11:54',
 | 
			
		||||
          timestamp: 1701757499196,
 | 
			
		||||
          timestamp: 1701757494892,
 | 
			
		||||
        },
 | 
			
		||||
      ],
 | 
			
		||||
    });
 | 
			
		||||
@ -89,54 +151,12 @@ describe('TableProfilerUtils', () => {
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('getColumnCustomMetric should return correct data', () => {
 | 
			
		||||
    const customMetrics = [
 | 
			
		||||
      {
 | 
			
		||||
        id: 'id1',
 | 
			
		||||
        name: 'name1',
 | 
			
		||||
        expression: 'expression1',
 | 
			
		||||
        updatedAt: 1701757494892,
 | 
			
		||||
        updatedBy: 'admin',
 | 
			
		||||
      },
 | 
			
		||||
    ];
 | 
			
		||||
    const table = {
 | 
			
		||||
      fullyQualifiedName: 'fqn',
 | 
			
		||||
      name: 'name',
 | 
			
		||||
      columns: [
 | 
			
		||||
        {
 | 
			
		||||
          fullyQualifiedName: 'fqn1',
 | 
			
		||||
          name: 'name1',
 | 
			
		||||
          customMetrics: customMetrics,
 | 
			
		||||
        },
 | 
			
		||||
      ],
 | 
			
		||||
    } as unknown as Table;
 | 
			
		||||
    const columnFqn = 'fqn1';
 | 
			
		||||
    const data = getColumnCustomMetric(table, columnFqn);
 | 
			
		||||
 | 
			
		||||
    expect(data).toEqual(customMetrics);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('getColumnCustomMetric should return undefined if table, fqn and both is not provided', () => {
 | 
			
		||||
    const columnFqn = 'fqn1';
 | 
			
		||||
    const customMetrics = [
 | 
			
		||||
      {
 | 
			
		||||
        id: 'id1',
 | 
			
		||||
        name: 'name1',
 | 
			
		||||
        expression: 'expression1',
 | 
			
		||||
        updatedAt: 1701757494892,
 | 
			
		||||
        updatedBy: 'admin',
 | 
			
		||||
      },
 | 
			
		||||
    ];
 | 
			
		||||
    const table = {
 | 
			
		||||
      fullyQualifiedName: 'fqn',
 | 
			
		||||
      name: 'name',
 | 
			
		||||
      columns: [
 | 
			
		||||
        {
 | 
			
		||||
          fullyQualifiedName: 'fqn1',
 | 
			
		||||
          name: 'name1',
 | 
			
		||||
          customMetrics: customMetrics,
 | 
			
		||||
        },
 | 
			
		||||
      ],
 | 
			
		||||
    } as unknown as Table;
 | 
			
		||||
    const withoutTable = getColumnCustomMetric(undefined, columnFqn);
 | 
			
		||||
    const withoutFqn = getColumnCustomMetric(table, undefined);
 | 
			
		||||
    const emptyData = getColumnCustomMetric();
 | 
			
		||||
@ -145,4 +165,142 @@ describe('TableProfilerUtils', () => {
 | 
			
		||||
    expect(withoutFqn).toBeUndefined();
 | 
			
		||||
    expect(emptyData).toBeUndefined();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('calculateColumnProfilerMetrics should calculate column profiler metrics correctly', () => {
 | 
			
		||||
    const columnProfilerData = [
 | 
			
		||||
      {
 | 
			
		||||
        timestamp: 1701757494892,
 | 
			
		||||
        distinctCount: 100,
 | 
			
		||||
        nullCount: 10,
 | 
			
		||||
        uniqueCount: 90,
 | 
			
		||||
        valuesCount: 200,
 | 
			
		||||
        sum: 500,
 | 
			
		||||
        max: 100,
 | 
			
		||||
        min: 0,
 | 
			
		||||
        mean: 50,
 | 
			
		||||
        distinctProportion: 0.5,
 | 
			
		||||
        nullProportion: 0.05,
 | 
			
		||||
        uniqueProportion: 0.45,
 | 
			
		||||
        firstQuartile: 25,
 | 
			
		||||
        thirdQuartile: 75,
 | 
			
		||||
        interQuartileRange: 50,
 | 
			
		||||
        median: 50,
 | 
			
		||||
      },
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    const result = calculateColumnProfilerMetrics({
 | 
			
		||||
      columnProfilerData,
 | 
			
		||||
      countMetrics,
 | 
			
		||||
      proportionMetrics,
 | 
			
		||||
      mathMetrics,
 | 
			
		||||
      sumMetrics,
 | 
			
		||||
      quartileMetrics,
 | 
			
		||||
    } as unknown as CalculateColumnProfilerMetricsInterface);
 | 
			
		||||
 | 
			
		||||
    expect(result.countMetrics.data).toEqual([
 | 
			
		||||
      {
 | 
			
		||||
        name: 'Dec 05, 11:54',
 | 
			
		||||
        timestamp: 1701757494892,
 | 
			
		||||
        distinctCount: 100,
 | 
			
		||||
        nullCount: 10,
 | 
			
		||||
        uniqueCount: 90,
 | 
			
		||||
        valuesCount: 200,
 | 
			
		||||
      },
 | 
			
		||||
    ]);
 | 
			
		||||
 | 
			
		||||
    expect(result.proportionMetrics.data).toEqual([
 | 
			
		||||
      {
 | 
			
		||||
        name: 'Dec 05, 11:54',
 | 
			
		||||
        timestamp: 1701757494892,
 | 
			
		||||
        distinctProportion: 50,
 | 
			
		||||
        nullProportion: 5,
 | 
			
		||||
        uniqueProportion: 45,
 | 
			
		||||
      },
 | 
			
		||||
    ]);
 | 
			
		||||
 | 
			
		||||
    expect(result.mathMetrics.data).toEqual([
 | 
			
		||||
      {
 | 
			
		||||
        name: 'Dec 05, 11:54',
 | 
			
		||||
        timestamp: 1701757494892,
 | 
			
		||||
        max: 100,
 | 
			
		||||
        min: 0,
 | 
			
		||||
        mean: 50,
 | 
			
		||||
      },
 | 
			
		||||
    ]);
 | 
			
		||||
 | 
			
		||||
    expect(result.sumMetrics.data).toEqual([
 | 
			
		||||
      {
 | 
			
		||||
        name: 'Dec 05, 11:54',
 | 
			
		||||
        timestamp: 1701757494892,
 | 
			
		||||
        sum: 500,
 | 
			
		||||
      },
 | 
			
		||||
    ]);
 | 
			
		||||
 | 
			
		||||
    expect(result.quartileMetrics.data).toEqual([
 | 
			
		||||
      {
 | 
			
		||||
        name: 'Dec 05, 11:54',
 | 
			
		||||
        timestamp: 1701757494892,
 | 
			
		||||
        firstQuartile: 25,
 | 
			
		||||
        thirdQuartile: 75,
 | 
			
		||||
        interQuartileRange: 50,
 | 
			
		||||
        median: 50,
 | 
			
		||||
      },
 | 
			
		||||
    ]);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('calculateColumnProfilerMetrics should only calculate metric based on available data', () => {
 | 
			
		||||
    const columnProfilerData = [
 | 
			
		||||
      {
 | 
			
		||||
        timestamp: 1701757494892,
 | 
			
		||||
        distinctCount: 100,
 | 
			
		||||
        nullCount: 10,
 | 
			
		||||
        uniqueCount: 90,
 | 
			
		||||
        valuesCount: 200,
 | 
			
		||||
        max: 100,
 | 
			
		||||
        min: 0,
 | 
			
		||||
        distinctProportion: 0.5,
 | 
			
		||||
        nullProportion: 0.05,
 | 
			
		||||
        uniqueProportion: 0.45,
 | 
			
		||||
      },
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    const result = calculateColumnProfilerMetrics({
 | 
			
		||||
      columnProfilerData,
 | 
			
		||||
      countMetrics,
 | 
			
		||||
      proportionMetrics,
 | 
			
		||||
      mathMetrics,
 | 
			
		||||
      sumMetrics,
 | 
			
		||||
      quartileMetrics,
 | 
			
		||||
    } as unknown as CalculateColumnProfilerMetricsInterface);
 | 
			
		||||
 | 
			
		||||
    expect(result.countMetrics.data).toEqual([
 | 
			
		||||
      {
 | 
			
		||||
        name: 'Dec 05, 11:54',
 | 
			
		||||
        timestamp: 1701757494892,
 | 
			
		||||
        distinctCount: 100,
 | 
			
		||||
        nullCount: 10,
 | 
			
		||||
        uniqueCount: 90,
 | 
			
		||||
        valuesCount: 200,
 | 
			
		||||
      },
 | 
			
		||||
    ]);
 | 
			
		||||
    expect(result.proportionMetrics.data).toEqual([
 | 
			
		||||
      {
 | 
			
		||||
        name: 'Dec 05, 11:54',
 | 
			
		||||
        timestamp: 1701757494892,
 | 
			
		||||
        distinctProportion: 50,
 | 
			
		||||
        nullProportion: 5,
 | 
			
		||||
        uniqueProportion: 45,
 | 
			
		||||
      },
 | 
			
		||||
    ]);
 | 
			
		||||
    expect(result.mathMetrics.data).toEqual([
 | 
			
		||||
      {
 | 
			
		||||
        name: 'Dec 05, 11:54',
 | 
			
		||||
        timestamp: 1701757494892,
 | 
			
		||||
        max: 100,
 | 
			
		||||
        min: 0,
 | 
			
		||||
      },
 | 
			
		||||
    ]);
 | 
			
		||||
    expect(result.sumMetrics.data).toEqual([]);
 | 
			
		||||
    expect(result.quartileMetrics.data).toEqual([]);
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
@ -11,12 +11,17 @@
 | 
			
		||||
 *  limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import { findLast, isUndefined, sortBy } from 'lodash';
 | 
			
		||||
import { findLast, isUndefined, last, sortBy } from 'lodash';
 | 
			
		||||
import { MetricChartType } from '../components/ProfilerDashboard/profilerDashboard.interface';
 | 
			
		||||
import { SystemProfile } from '../generated/api/data/createTableProfile';
 | 
			
		||||
import { Table, TableProfile } from '../generated/entity/data/table';
 | 
			
		||||
import { CustomMetric } from '../generated/tests/customMetric';
 | 
			
		||||
import { customFormatDateTime } from './date-time/DateTimeUtils';
 | 
			
		||||
import { isHasKey } from './ObjectUtils';
 | 
			
		||||
import {
 | 
			
		||||
  CalculateColumnProfilerMetricsInterface,
 | 
			
		||||
  ColumnMetricsInterface,
 | 
			
		||||
} from './TableProfilerUtils.interface';
 | 
			
		||||
 | 
			
		||||
export const calculateRowCountMetrics = (
 | 
			
		||||
  profiler: TableProfile[],
 | 
			
		||||
@ -31,13 +36,13 @@ export const calculateRowCountMetrics = (
 | 
			
		||||
    rowCountMetricData.push({
 | 
			
		||||
      name: timestamp,
 | 
			
		||||
      timestamp: data.timestamp,
 | 
			
		||||
      rowCount: Number(data.rowCount),
 | 
			
		||||
      rowCount: data.rowCount,
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
  const countMetricInfo = currentMetrics.information.map((item) => ({
 | 
			
		||||
    ...item,
 | 
			
		||||
    latestValue:
 | 
			
		||||
      rowCountMetricData[rowCountMetricData.length - 1]?.[item.dataKey] || 0,
 | 
			
		||||
      rowCountMetricData[rowCountMetricData.length - 1]?.[item.dataKey],
 | 
			
		||||
  }));
 | 
			
		||||
 | 
			
		||||
  return { data: rowCountMetricData, information: countMetricInfo };
 | 
			
		||||
@ -58,13 +63,13 @@ export const calculateSystemMetrics = (
 | 
			
		||||
    operationMetrics.push({
 | 
			
		||||
      name: timestamp,
 | 
			
		||||
      timestamp: Number(data.timestamp),
 | 
			
		||||
      [data.operation || 'value']: Number(data.rowsAffected),
 | 
			
		||||
      [data.operation ?? 'value']: data.rowsAffected,
 | 
			
		||||
    });
 | 
			
		||||
    operationDateMetrics.push({
 | 
			
		||||
      name: timestamp,
 | 
			
		||||
      timestamp: Number(data.timestamp),
 | 
			
		||||
      data: data.rowsAffected || 0,
 | 
			
		||||
      [data.operation || 'value']: 5,
 | 
			
		||||
      data: data.rowsAffected,
 | 
			
		||||
      [data.operation ?? 'value']: 5,
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
  const operationMetricsInfo = currentMetrics.information.map((item) => {
 | 
			
		||||
@ -76,7 +81,7 @@ export const calculateSystemMetrics = (
 | 
			
		||||
    return {
 | 
			
		||||
      ...item,
 | 
			
		||||
      stackId: stackId,
 | 
			
		||||
      latestValue: operation?.rowsAffected ?? 0,
 | 
			
		||||
      latestValue: operation?.rowsAffected,
 | 
			
		||||
    };
 | 
			
		||||
  });
 | 
			
		||||
  const operationDateMetricsInfo = currentMetrics.information.map((item) => {
 | 
			
		||||
@ -145,3 +150,145 @@ export const getColumnCustomMetric = (table?: Table, columnFqn?: string) => {
 | 
			
		||||
    (column) => column.fullyQualifiedName === columnFqn
 | 
			
		||||
  )?.customMetrics;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const calculateColumnProfilerMetrics = ({
 | 
			
		||||
  columnProfilerData,
 | 
			
		||||
  countMetrics,
 | 
			
		||||
  proportionMetrics,
 | 
			
		||||
  mathMetrics,
 | 
			
		||||
  sumMetrics,
 | 
			
		||||
  quartileMetrics,
 | 
			
		||||
}: CalculateColumnProfilerMetricsInterface): ColumnMetricsInterface => {
 | 
			
		||||
  const updateProfilerData = sortBy(columnProfilerData, 'timestamp');
 | 
			
		||||
  const countMetricData: MetricChartType['data'] = [];
 | 
			
		||||
  const proportionMetricData: MetricChartType['data'] = [];
 | 
			
		||||
  const mathMetricData: MetricChartType['data'] = [];
 | 
			
		||||
  const sumMetricData: MetricChartType['data'] = [];
 | 
			
		||||
  const quartileMetricData: MetricChartType['data'] = [];
 | 
			
		||||
  updateProfilerData.forEach((col) => {
 | 
			
		||||
    const { timestamp, sum } = col;
 | 
			
		||||
    const name = customFormatDateTime(timestamp, 'MMM dd, hh:mm');
 | 
			
		||||
    const defaultData = { name, timestamp };
 | 
			
		||||
 | 
			
		||||
    if (
 | 
			
		||||
      isHasKey(col, [
 | 
			
		||||
        'distinctCount',
 | 
			
		||||
        'nullCount',
 | 
			
		||||
        'uniqueCount',
 | 
			
		||||
        'valuesCount',
 | 
			
		||||
      ])
 | 
			
		||||
    ) {
 | 
			
		||||
      const { distinctCount, nullCount, uniqueCount, valuesCount } = col;
 | 
			
		||||
      countMetricData.push({
 | 
			
		||||
        ...defaultData,
 | 
			
		||||
        distinctCount,
 | 
			
		||||
        nullCount,
 | 
			
		||||
        uniqueCount,
 | 
			
		||||
        valuesCount,
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (isHasKey(col, ['sum'])) {
 | 
			
		||||
      sumMetricData.push({
 | 
			
		||||
        ...defaultData,
 | 
			
		||||
        sum,
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (isHasKey(col, ['max', 'min', 'mean'])) {
 | 
			
		||||
      const { max, min, mean } = col;
 | 
			
		||||
      mathMetricData.push({
 | 
			
		||||
        ...defaultData,
 | 
			
		||||
        max,
 | 
			
		||||
        min,
 | 
			
		||||
        mean,
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (
 | 
			
		||||
      isHasKey(col, [
 | 
			
		||||
        'distinctProportion',
 | 
			
		||||
        'nullProportion',
 | 
			
		||||
        'uniqueProportion',
 | 
			
		||||
      ])
 | 
			
		||||
    ) {
 | 
			
		||||
      const { distinctProportion, nullProportion, uniqueProportion } = col;
 | 
			
		||||
      proportionMetricData.push({
 | 
			
		||||
        ...defaultData,
 | 
			
		||||
        distinctProportion: isUndefined(distinctProportion)
 | 
			
		||||
          ? undefined
 | 
			
		||||
          : Math.round(distinctProportion * 100),
 | 
			
		||||
        nullProportion: isUndefined(nullProportion)
 | 
			
		||||
          ? undefined
 | 
			
		||||
          : Math.round(nullProportion * 100),
 | 
			
		||||
        uniqueProportion: isUndefined(uniqueProportion)
 | 
			
		||||
          ? undefined
 | 
			
		||||
          : Math.round(uniqueProportion * 100),
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (
 | 
			
		||||
      isHasKey(col, [
 | 
			
		||||
        'firstQuartile',
 | 
			
		||||
        'thirdQuartile',
 | 
			
		||||
        'interQuartileRange',
 | 
			
		||||
        'median',
 | 
			
		||||
      ])
 | 
			
		||||
    ) {
 | 
			
		||||
      const { firstQuartile, thirdQuartile, interQuartileRange, median } = col;
 | 
			
		||||
      quartileMetricData.push({
 | 
			
		||||
        ...defaultData,
 | 
			
		||||
        firstQuartile,
 | 
			
		||||
        thirdQuartile,
 | 
			
		||||
        interQuartileRange,
 | 
			
		||||
        median,
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const countMetricInfo = countMetrics.information.map((item) => ({
 | 
			
		||||
    ...item,
 | 
			
		||||
    latestValue: last(countMetricData)?.[item.dataKey],
 | 
			
		||||
  }));
 | 
			
		||||
  const proportionMetricInfo = proportionMetrics.information.map((item) => ({
 | 
			
		||||
    ...item,
 | 
			
		||||
    latestValue: isUndefined(last(proportionMetricData)?.[item.dataKey])
 | 
			
		||||
      ? undefined
 | 
			
		||||
      : parseFloat(`${last(proportionMetricData)?.[item.dataKey]}`).toFixed(2),
 | 
			
		||||
  }));
 | 
			
		||||
  const mathMetricInfo = mathMetrics.information.map((item) => ({
 | 
			
		||||
    ...item,
 | 
			
		||||
    latestValue: last(mathMetricData)?.[item.dataKey],
 | 
			
		||||
  }));
 | 
			
		||||
  const sumMetricInfo = sumMetrics.information.map((item) => ({
 | 
			
		||||
    ...item,
 | 
			
		||||
    latestValue: last(sumMetricData)?.[item.dataKey],
 | 
			
		||||
  }));
 | 
			
		||||
  const quartileMetricInfo = quartileMetrics.information.map((item) => ({
 | 
			
		||||
    ...item,
 | 
			
		||||
    latestValue: last(quartileMetricData)?.[item.dataKey],
 | 
			
		||||
  }));
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    countMetrics: {
 | 
			
		||||
      information: countMetricInfo,
 | 
			
		||||
      data: countMetricData,
 | 
			
		||||
    },
 | 
			
		||||
    proportionMetrics: {
 | 
			
		||||
      information: proportionMetricInfo,
 | 
			
		||||
      data: proportionMetricData,
 | 
			
		||||
    },
 | 
			
		||||
    mathMetrics: {
 | 
			
		||||
      information: mathMetricInfo,
 | 
			
		||||
      data: mathMetricData,
 | 
			
		||||
    },
 | 
			
		||||
    sumMetrics: {
 | 
			
		||||
      information: sumMetricInfo,
 | 
			
		||||
      data: sumMetricData,
 | 
			
		||||
    },
 | 
			
		||||
    quartileMetrics: {
 | 
			
		||||
      information: quartileMetricInfo,
 | 
			
		||||
      data: quartileMetricData,
 | 
			
		||||
    },
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user