This is an automated email from the ASF dual-hosted git repository.

aloyszhang pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/inlong.git


The following commit(s) were added to refs/heads/master by this push:
     new fc4695f669 [INLONG-11186][Dashboard] Export audit indicator data to 
csv (#11438)
fc4695f669 is described below

commit fc4695f669e326b4f87a6df0dc4e4cac447a0971
Author: kamianlaida <[email protected]>
AuthorDate: Fri Nov 1 10:51:09 2024 +0800

    [INLONG-11186][Dashboard] Export audit indicator data to csv (#11438)
---
 inlong-dashboard/package-lock.json                 |   5 +
 inlong-dashboard/package.json                      |   1 +
 inlong-dashboard/src/ui/locales/cn.json            |   2 +
 inlong-dashboard/src/ui/locales/en.json            |   2 +
 .../src/ui/pages/ClusterTags/TagDetailModal.tsx    |   1 -
 .../src/ui/pages/GroupDataTemplate/index.tsx       |   1 -
 .../src/ui/pages/GroupDetail/Audit/config.tsx      |  34 ++++-
 .../src/ui/pages/GroupDetail/Audit/index.tsx       |  56 +++++++-
 .../src/ui/pages/GroupDetail/index.tsx             |   1 -
 .../ui/pages/ModuleAudit/AuditModule/config.tsx    |  32 ++++-
 .../src/ui/pages/ModuleAudit/AuditModule/index.tsx |  58 ++++++++-
 .../src/ui/pages/ModuleAudit/IdModule/config.tsx   |  92 ++++++++++++-
 .../src/ui/pages/ModuleAudit/IdModule/index.tsx    | 131 +++++++++++++++----
 .../src/ui/pages/ModuleAudit/IpModule/config.tsx   |  96 ++++++++++++--
 .../src/ui/pages/ModuleAudit/IpModule/index.tsx    | 143 +++++++++++++++++----
 15 files changed, 561 insertions(+), 94 deletions(-)

diff --git a/inlong-dashboard/package-lock.json 
b/inlong-dashboard/package-lock.json
index efa9d0a413..599ba43811 100644
--- a/inlong-dashboard/package-lock.json
+++ b/inlong-dashboard/package-lock.json
@@ -13290,6 +13290,11 @@
         "whatwg-fetch": "^3.6.2"
       }
     },
+    "react-csv": {
+      "version": "2.2.2",
+      "resolved": "https://registry.npmjs.org/react-csv/-/react-csv-2.2.2.tgz";,
+      "integrity": 
"sha512-RG5hOcZKZFigIGE8LxIEV/OgS1vigFQT4EkaHeKgyuCbUAu9Nbd/1RYq++bJcJJ9VOqO/n9TZRADsXNDR4VEpw=="
+    },
     "react-dev-utils": {
       "version": "12.0.1",
       "resolved": 
"https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz";,
diff --git a/inlong-dashboard/package.json b/inlong-dashboard/package.json
index c287ad670b..7c61492967 100644
--- a/inlong-dashboard/package.json
+++ b/inlong-dashboard/package.json
@@ -14,6 +14,7 @@
     "nprogress": "^0.2.0",
     "path-to-regexp": "^6.2.0",
     "react": "17.0.1",
+    "react-csv": "^2.2.2",
     "react-dom": "17.0.1",
     "react-i18next": "^11.10.0",
     "react-redux": "^7.2.0",
diff --git a/inlong-dashboard/src/ui/locales/cn.json 
b/inlong-dashboard/src/ui/locales/cn.json
index 778c687270..7a8e2ea1dd 100644
--- a/inlong-dashboard/src/ui/locales/cn.json
+++ b/inlong-dashboard/src/ui/locales/cn.json
@@ -712,6 +712,7 @@
   "pages.GroupDetail.Audit.Sink": "数据目标",
   "pages.GroupDetail.Audit.Total": "总计",
   "pages.GroupDetail.Audit.DatepickerRule": "超出可选范围",
+  "pages.GroupDetail.Audit.ExportCSV": "导出 CSV 文件",
   "pages.GroupDetail.Delay.QueryDate": "查询日期",
   "pages.GroupDetail.Delay.AverageTitle": "平均传输时延 (ms)",
   "pages.GroupDetail.Delay.RealTimeTitle": "传输时延 (ms)",
@@ -977,6 +978,7 @@
   "pages.ModuleAudit.Id": "ID 查询",
   "pages.ModuleAudit.Metric": "指标查询",
   "pages.ModuleAudit.config.Ip": "机器 IP",
+  "pages.ModuleAudit.config.SubValue": "差异值",
   "pages.ModuleAudit.config.BenchmarkIndicator": "基准指标",
   "pages.ModuleAudit.config.ComparativeIndicators": "对比指标",
   "pages.ModuleAudit.config.InlongGroupId": "数据流组ID",
diff --git a/inlong-dashboard/src/ui/locales/en.json 
b/inlong-dashboard/src/ui/locales/en.json
index 8bb5c15ab5..ce06b61f3c 100644
--- a/inlong-dashboard/src/ui/locales/en.json
+++ b/inlong-dashboard/src/ui/locales/en.json
@@ -712,6 +712,7 @@
   "pages.GroupDetail.Metric.Item": "Metric item",
   "pages.GroupDetail.Audit.Total": "Total",
   "pages.GroupDetail.Audit.DatepickerRule": "Out of selectable time range",
+  "pages.GroupDetail.Audit.ExportCSV": "Export CSV File",
   "pages.GroupDetail.Delay.QueryDate": "Query date",
   "pages.GroupDetail.Delay.AverageTitle": "Average transmission delay (ms)",
   "pages.GroupDetail.Delay.RealTimeTitle": "Transmission delay (ms)",
@@ -977,6 +978,7 @@
   "pages.ModuleAudit.Id": "Query by id",
   "pages.ModuleAudit.Metric": "Query by Metric",
   "pages.ModuleAudit.config.Ip": "Machine ip",
+  "pages.ModuleAudit.config.SubValue": "Sub Value",
   "pages.ModuleAudit.config.BenchmarkIndicator": "Benchmark indicator",
   "pages.ModuleAudit.config.ComparativeIndicators": "Comparative indicator",
   "pages.ModuleAudit.config.InlongGroupId": "Inlong group id",
diff --git a/inlong-dashboard/src/ui/pages/ClusterTags/TagDetailModal.tsx 
b/inlong-dashboard/src/ui/pages/ClusterTags/TagDetailModal.tsx
index 743e3089a3..0375da7700 100644
--- a/inlong-dashboard/src/ui/pages/ClusterTags/TagDetailModal.tsx
+++ b/inlong-dashboard/src/ui/pages/ClusterTags/TagDetailModal.tsx
@@ -107,7 +107,6 @@ const TagDetailModal: React.FC<TagDetailModalProps> = ({ 
id, ...modalProps }) =>
           maxTagCount: 9,
           maxTagTextLength: 20,
           maxTagPlaceholder: omittedValues => {
-            console.log('omittedValues', omittedValues);
             return (
               <span>
                 {i18n.t('miscellaneous.total')}
diff --git a/inlong-dashboard/src/ui/pages/GroupDataTemplate/index.tsx 
b/inlong-dashboard/src/ui/pages/GroupDataTemplate/index.tsx
index 1a3326b179..52851f3b63 100644
--- a/inlong-dashboard/src/ui/pages/GroupDataTemplate/index.tsx
+++ b/inlong-dashboard/src/ui/pages/GroupDataTemplate/index.tsx
@@ -104,7 +104,6 @@ const Comp: React.FC = () => {
   );
   const onDelete = useCallback(
     record => {
-      console.log(record);
       Modal.confirm({
         title: i18n.t('basic.DeleteConfirm'),
         onOk: async () => {
diff --git a/inlong-dashboard/src/ui/pages/GroupDetail/Audit/config.tsx 
b/inlong-dashboard/src/ui/pages/GroupDetail/Audit/config.tsx
index 10cc589d50..fb571449d5 100644
--- a/inlong-dashboard/src/ui/pages/GroupDetail/Audit/config.tsx
+++ b/inlong-dashboard/src/ui/pages/GroupDetail/Audit/config.tsx
@@ -23,6 +23,8 @@ import dayjs from 'dayjs';
 import i18n from '@/i18n';
 import { sinks } from '@/plugins/sinks';
 import request from '@/core/utils/request';
+import { CSVLink } from 'react-csv';
+import audit from '@/ui/pages/GroupDetail/Audit/index';
 
 export const timeStaticsDimList = [
   {
@@ -131,11 +133,11 @@ export const getSourceDataWithPercent = (sourceKeys, 
sourceMap) => {
 
 export const getDiff = (first, current) => {
   if (first === 0) {
-    return '0%';
+    return first.toFixed(4) + '%';
   }
   let result;
-  const diff = Math.ceil((current / first - 1) * 100);
-  result = diff > 0 ? '+' + diff + '%' : diff + '%';
+  const diff = (current / first - 1) * 100;
+  result = diff > 0 ? '+' + diff.toFixed(4) + '%' : diff.toFixed(4) + '%';
   return result;
 };
 
@@ -154,10 +156,17 @@ export const getSourceDataWithCommas = sourceData => {
   });
   return sourceData;
 };
-
 let endTimeVisible = true;
-
-export const getFormContent = (inlongGroupId, initialValues, onSearch, 
onDataStreamSuccess) => [
+export const getFormContent = (
+  inlongGroupId,
+  initialValues,
+  onSearch,
+  onDataStreamSuccess,
+  sourceData,
+  csvData,
+  fileName,
+  setInlongStreamID,
+) => [
   {
     type: 'select',
     label: i18n.t('pages.ModuleAudit.config.InlongStreamId'),
@@ -165,6 +174,9 @@ export const getFormContent = (inlongGroupId, 
initialValues, onSearch, onDataStr
     props: {
       dropdownMatchSelectWidth: false,
       showSearch: true,
+      onChange: (value, option) => {
+        setInlongStreamID(value);
+      },
       options: {
         requestAuto: true,
         requestTrigger: ['onOpen', 'onSearch'],
@@ -244,7 +256,6 @@ export const getFormContent = (inlongGroupId, 
initialValues, onSearch, onDataStr
             return Promise.resolve();
           }
           const timeDiff = value - getFieldValue('startDate');
-          console.log('timeDiff', value, getFieldValue('startDate'), timeDiff);
           if (timeDiff >= 0) {
             const isHourDiff = dim === 'HOUR' && timeDiff < 1000 * 60 * 60 * 
24 * 3;
             const isDayDiff = dim === 'DAY' && timeDiff < 1000 * 60 * 60 * 24 
* 7;
@@ -332,6 +343,15 @@ export const getFormContent = (inlongGroupId, 
initialValues, onSearch, onDataStr
       </Button>
     ),
   },
+  {
+    type: (
+      <Button type="primary" disabled={!(sourceData.length > 0)}>
+        <CSVLink data={csvData} filename={fileName}>
+          {i18n.t('pages.GroupDetail.Audit.ExportCSV')}
+        </CSVLink>
+      </Button>
+    ),
+  },
 ];
 
 export const getTableColumns = (source, dim) => {
diff --git a/inlong-dashboard/src/ui/pages/GroupDetail/Audit/index.tsx 
b/inlong-dashboard/src/ui/pages/GroupDetail/Audit/index.tsx
index f4ba9fbbc4..c7eebf1895 100644
--- a/inlong-dashboard/src/ui/pages/GroupDetail/Audit/index.tsx
+++ b/inlong-dashboard/src/ui/pages/GroupDetail/Audit/index.tsx
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-import React, { useMemo, useState } from 'react';
+import React, { useCallback, useEffect, useMemo, useState } from 'react';
 import FormGenerator, { useForm } from '@/ui/components/FormGenerator';
 import HighTable from '@/ui/components/HighTable';
 import { useRequest } from '@/ui/hooks';
@@ -38,14 +38,13 @@ type Props = CommonInterface;
 
 const Comp: React.FC<Props> = ({ inlongGroupId }) => {
   const [form] = useForm();
-
   const [query, setQuery] = useState({
     inlongStreamId: '',
     startDate: +new Date(),
     endDate: +new Date(),
     timeStaticsDim: timeStaticsDimList[0].value,
   });
-
+  const [inlongStreamID, setInlongStreamID] = useState('');
   const { data: sourceData = [], run } = useRequest(
     {
       url: '/audit/list',
@@ -104,20 +103,67 @@ const Comp: React.FC<Props> = ({ inlongGroupId }) => {
   const onDataStreamSuccess = data => {
     const defaultDataStream = data[0]?.value;
     if (defaultDataStream) {
+      setInlongStreamID(defaultDataStream);
       form.setFieldsValue({ inlongStreamId: defaultDataStream });
       setQuery(prev => ({ ...prev, inlongStreamId: defaultDataStream }));
       run();
     }
   };
+  const numToName = useCallback(
+    num => {
+      let obj = {};
+      sourceData.forEach(item => {
+        obj = { ...obj, [item.auditId]: item.auditName };
+      });
+      obj = { ...obj, logTs: i18n.t('pages.GroupDetail.Audit.Time') };
+      return obj[num];
+    },
+    [sourceData],
+  );
+  const metricSum = useMemo(() => {
+    let obj = { logTs: i18n.t('pages.GroupDetail.Audit.Total') };
+    sourceData.map(item => {
+      const sum = item.auditSet?.reduce((total, cur) => {
+        return total + cur.count;
+      }, 0);
+      obj = { ...obj, [item.auditId]: sum };
+    });
+    return obj;
+  }, [sourceData]);
 
+  const csvData = useMemo(() => {
+    const result = [...toTableData(sourceData, sourceDataMap), 
metricSum].map(item => {
+      let obj = {};
+      Object.keys(item)
+        .reverse()
+        .forEach(key => {
+          obj = { ...obj, [numToName(key)]: item[key] };
+        });
+      return obj;
+    });
+    return result;
+  }, [sourceData, sourceDataMap, metricSum]);
+  const [fileName, setFileName] = useState('audit.csv');
+  useEffect(() => {
+    setFileName(`audit_${inlongGroupId}_${inlongStreamID}.csv`);
+  }, [inlongGroupId, inlongStreamID]);
   return (
     <>
       <div style={{ marginBottom: 40 }}>
         <FormGenerator
           form={form}
           layout="inline"
-          content={getFormContent(inlongGroupId, query, onSearch, 
onDataStreamSuccess)}
-          style={{ marginBottom: 30 }}
+          content={getFormContent(
+            inlongGroupId,
+            query,
+            onSearch,
+            onDataStreamSuccess,
+            sourceData,
+            csvData,
+            fileName,
+            setInlongStreamID,
+          )}
+          style={{ marginBottom: 30, gap: 10 }}
           onFilter={allValues =>
             setQuery({
               ...allValues,
diff --git a/inlong-dashboard/src/ui/pages/GroupDetail/index.tsx 
b/inlong-dashboard/src/ui/pages/GroupDetail/index.tsx
index 02961f38b4..93a119634a 100644
--- a/inlong-dashboard/src/ui/pages/GroupDetail/index.tsx
+++ b/inlong-dashboard/src/ui/pages/GroupDetail/index.tsx
@@ -64,7 +64,6 @@ const Comp: React.FC = () => {
     ready: !!id,
     refreshDeps: [id],
     onSuccess: result => {
-      console.log('res', result, getLocalStorage('tenant')?.['name']);
       if (getLocalStorage('tenant')?.['name'] !== result) {
         setLocalStorage({ name: result });
         message.success(t('components.Layout.Tenant.Success'));
diff --git a/inlong-dashboard/src/ui/pages/ModuleAudit/AuditModule/config.tsx 
b/inlong-dashboard/src/ui/pages/ModuleAudit/AuditModule/config.tsx
index e3821a15ec..423c096774 100644
--- a/inlong-dashboard/src/ui/pages/ModuleAudit/AuditModule/config.tsx
+++ b/inlong-dashboard/src/ui/pages/ModuleAudit/AuditModule/config.tsx
@@ -22,8 +22,7 @@ import { Button } from 'antd';
 import dayjs from 'dayjs';
 import i18n from '@/i18n';
 import { sinks } from '@/plugins/sinks';
-import request from '@/core/utils/request';
-
+import { CSVLink } from 'react-csv';
 export const timeStaticsDimList = [
   {
     label: i18n.t('pages.GroupDetail.Audit.Min'),
@@ -69,7 +68,6 @@ function getAuditLabel(auditId: number, nodeType?: string) {
 }
 
 export const toChartData = (source, sourceDataMap) => {
-  console.log(source, sourceDataMap);
   const xAxisData = Object.keys(sourceDataMap ? sourceDataMap : '12345');
   return {
     legend: {
@@ -158,7 +156,17 @@ export const getSourceDataWithCommas = sourceData => {
 
 let endTimeVisible = true;
 
-export const getFormContent = (initialValues, onSearch, onDataStreamSuccess, 
auditData) => [
+export const getFormContent = (
+  initialValues,
+  onSearch,
+  onDataStreamSuccess,
+  auditData,
+  sourceData,
+  csvData,
+  setInlongGroupId,
+  setInlongStreamID,
+  fileName,
+) => [
   {
     type: 'select',
     label: i18n.t('pages.ModuleAudit.config.InlongGroupId'),
@@ -167,6 +175,9 @@ export const getFormContent = (initialValues, onSearch, 
onDataStreamSuccess, aud
       dropdownMatchSelectWidth: false,
       showSearch: true,
       allowClear: true,
+      onChange: (value, option) => {
+        setInlongGroupId(value);
+      },
       options: {
         requestAuto: true,
         requestTrigger: ['onOpen', 'onSearch'],
@@ -200,6 +211,9 @@ export const getFormContent = (initialValues, onSearch, 
onDataStreamSuccess, aud
       showSearch: true,
       allowClear: true,
       disabled: !Boolean(values.inlongGroupId),
+      onChange: (value, option) => {
+        setInlongStreamID(value);
+      },
       options: {
         requestAuto: true,
         requestTrigger: ['onOpen', 'onSearch'],
@@ -249,7 +263,6 @@ export const getFormContent = (initialValues, onSearch, 
onDataStreamSuccess, aud
             return Promise.resolve();
           }
           const timeDiff = value - getFieldValue('startDate');
-          console.log('timeDiff', value, getFieldValue('startDate'), timeDiff);
           if (timeDiff >= 0) {
             const isHourDiff = dim === 'HOUR' && timeDiff < 1000 * 60 * 60 * 
24 * 3;
             const isDayDiff = dim === 'DAY' && timeDiff < 1000 * 60 * 60 * 24 
* 7;
@@ -325,6 +338,15 @@ export const getFormContent = (initialValues, onSearch, 
onDataStreamSuccess, aud
       </Button>
     ),
   },
+  {
+    type: (
+      <Button type="primary" disabled={!(sourceData.length > 0)}>
+        <CSVLink data={csvData} filename={fileName}>
+          {i18n.t('pages.GroupDetail.Audit.ExportCSV')}
+        </CSVLink>
+      </Button>
+    ),
+  },
 ];
 
 export const getTableColumns = (source, dim) => {
diff --git a/inlong-dashboard/src/ui/pages/ModuleAudit/AuditModule/index.tsx 
b/inlong-dashboard/src/ui/pages/ModuleAudit/AuditModule/index.tsx
index 8a4491b5e2..311d8be2d3 100644
--- a/inlong-dashboard/src/ui/pages/ModuleAudit/AuditModule/index.tsx
+++ b/inlong-dashboard/src/ui/pages/ModuleAudit/AuditModule/index.tsx
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-import React, { useMemo, useState } from 'react';
+import React, { useCallback, useEffect, useMemo, useState } from 'react';
 import FormGenerator, { useForm } from '@/ui/components/FormGenerator';
 import HighTable from '@/ui/components/HighTable';
 import { useRequest } from '@/ui/hooks';
@@ -45,7 +45,8 @@ const Comp: React.FC<AuditProps> = ({ auditData }) => {
     endDate: +new Date(),
     timeStaticsDim: timeStaticsDimList[0].value,
   });
-
+  const [inlongStreamID, setInlongStreamID] = useState('');
+  const [inlongGroupId, setInlongGroupId] = useState('');
   const { data: sourceData = [], run } = useRequest(
     {
       url: '/audit/list',
@@ -61,7 +62,17 @@ const Comp: React.FC<AuditProps> = ({ auditData }) => {
       formatResult: result => result.sort((a, b) => (a.auditId - b.auditId > 0 
? 1 : -1)),
     },
   );
-
+  const numToName = useCallback(
+    num => {
+      let obj = {};
+      sourceData.forEach(item => {
+        obj = { ...obj, [item.auditId]: item.auditName };
+      });
+      obj = { ...obj, logTs: i18n.t('pages.GroupDetail.Audit.Time') };
+      return obj[num];
+    },
+    [sourceData],
+  );
   const sourceDataMap = useMemo(() => {
     const flatArr = sourceData
       .reduce(
@@ -99,7 +110,29 @@ const Comp: React.FC<AuditProps> = ({ auditData }) => {
     }
     run();
   };
+  const metricSum = useMemo(() => {
+    let obj = { logTs: i18n.t('pages.GroupDetail.Audit.Total') };
+    sourceData.map(item => {
+      const sum = item.auditSet?.reduce((total, cur) => {
+        return total + cur.count;
+      }, 0);
+      obj = { ...obj, [item.auditId]: sum };
+    });
+    return obj;
+  }, [sourceData]);
 
+  const csvData = useMemo(() => {
+    const result = [...toTableData(sourceData, sourceDataMap), 
metricSum].map(item => {
+      let obj = {};
+      Object.keys(item)
+        .reverse()
+        .forEach(key => {
+          obj = { ...obj, [numToName(key)]: item[key] };
+        });
+      return obj;
+    });
+    return result;
+  }, [sourceData, sourceDataMap, metricSum]);
   const onDataStreamSuccess = data => {
     const defaultDataStream = data[0]?.value;
     if (defaultDataStream) {
@@ -108,15 +141,28 @@ const Comp: React.FC<AuditProps> = ({ auditData }) => {
       run();
     }
   };
-
+  const [fileName, setFileName] = useState('metrics.csv');
+  useEffect(() => {
+    setFileName(`metrics_${inlongGroupId}_${inlongStreamID}.csv`);
+  }, [inlongGroupId, inlongStreamID]);
   return (
     <>
       <div style={{ marginBottom: 40 }}>
         <FormGenerator
           form={form}
           layout="inline"
-          content={getFormContent(query, onSearch, onDataStreamSuccess, 
auditData)}
-          style={{ marginBottom: 30 }}
+          content={getFormContent(
+            query,
+            onSearch,
+            onDataStreamSuccess,
+            auditData,
+            sourceData,
+            csvData,
+            setInlongGroupId,
+            setInlongStreamID,
+            fileName,
+          )}
+          style={{ marginBottom: 30, gap: 10 }}
           onFilter={allValues =>
             setQuery({
               ...allValues,
diff --git a/inlong-dashboard/src/ui/pages/ModuleAudit/IdModule/config.tsx 
b/inlong-dashboard/src/ui/pages/ModuleAudit/IdModule/config.tsx
index cd868b6797..b3803906f5 100644
--- a/inlong-dashboard/src/ui/pages/ModuleAudit/IdModule/config.tsx
+++ b/inlong-dashboard/src/ui/pages/ModuleAudit/IdModule/config.tsx
@@ -22,7 +22,9 @@ import i18n from '@/i18n';
 import request from '@/core/utils/request';
 import { Button } from 'antd';
 import React from 'react';
-
+import { SortOrder } from 'antd/es/table/interface';
+import { range } from 'lodash';
+import { CSVLink } from 'react-csv';
 export const timeStaticsDimList = [
   {
     label: i18n.t('pages.GroupDetail.Audit.Min'),
@@ -38,7 +40,20 @@ export const timeStaticsDimList = [
   },
 ];
 
+export const sumSubValue = sourceDataMap => {
+  if (sourceDataMap === null || sourceDataMap === undefined) {
+    return 0;
+  }
+  return Object.keys(sourceDataMap).reduce((acc, cur) => {
+    const element = sourceDataMap[cur];
+    acc += element.subValue;
+    return acc;
+  }, 0);
+};
 export const toTableData = (source, sourceDataMap) => {
+  if (sourceDataMap === null || sourceDataMap === undefined) {
+    return [];
+  }
   return Object.keys(sourceDataMap)
     .reverse()
     .map(logTs => ({
@@ -47,7 +62,16 @@ export const toTableData = (source, sourceDataMap) => {
     }));
 };
 
-export const getFormContent = (initialValues, onSearch, auditData) => [
+export const getFormContent = (
+  initialValues,
+  onSearch,
+  auditData,
+  sourceData,
+  csvData,
+  setInlongGroupId,
+  setInlongStreamID,
+  fileName,
+) => [
   {
     type: 'select',
     label: i18n.t('pages.ModuleAudit.config.InlongGroupId'),
@@ -55,6 +79,9 @@ export const getFormContent = (initialValues, onSearch, 
auditData) => [
     props: {
       dropdownMatchSelectWidth: false,
       showSearch: true,
+      onChange: (value, option) => {
+        setInlongGroupId(value);
+      },
       options: {
         requestAuto: true,
         requestTrigger: ['onOpen', 'onSearch'],
@@ -85,6 +112,9 @@ export const getFormContent = (initialValues, onSearch, 
auditData) => [
     props: values => ({
       dropdownMatchSelectWidth: false,
       showSearch: true,
+      onChange: (value, option) => {
+        setInlongStreamID(value);
+      },
       options: {
         requestAuto: true,
         requestTrigger: ['onOpen', 'onSearch'],
@@ -116,7 +146,12 @@ export const getFormContent = (initialValues, onSearch, 
auditData) => [
     props: {
       allowClear: false,
       showTime: true,
-      format: 'YYYY-MM-DD HH:mm:ss',
+      format: 'YYYY-MM-DD HH:mm',
+      disabledTime: (date: dayjs.Dayjs, type, info: { from?: dayjs.Dayjs }) => 
{
+        return {
+          disabledSeconds: () => range(0, 60),
+        };
+      },
     },
   },
   {
@@ -127,7 +162,12 @@ export const getFormContent = (initialValues, onSearch, 
auditData) => [
     props: {
       allowClear: false,
       showTime: true,
-      format: 'YYYY-MM-DD HH:mm:ss',
+      format: 'YYYY-MM-DD HH:mm',
+      disabledTime: (date: dayjs.Dayjs, type, info: { from?: dayjs.Dayjs }) => 
{
+        return {
+          disabledSeconds: () => range(0, 60),
+        };
+      },
     },
   },
   {
@@ -181,18 +221,56 @@ export const getFormContent = (initialValues, onSearch, 
auditData) => [
       </Button>
     ),
   },
+  {
+    type: (
+      <Button type="primary" disabled={!(sourceData.length > 0)}>
+        <CSVLink data={csvData} filename={fileName}>
+          {i18n.t('pages.GroupDetail.Audit.ExportCSV')}
+        </CSVLink>
+      </Button>
+    ),
+  },
 ];
 
-export const getTableColumns = source => {
+const strSorter = (a, b) => {
+  return a?.ip.localeCompare(b?.ip);
+};
+const sortOrder: SortOrder = 'descend';
+
+const baseSorter = (a, b) => {
+  return a.base - b.base;
+};
+const comparedSorter = (a, b) => {
+  return a.compared - b.compared;
+};
+const subValueSorter = (a, b) => {
+  return a.subValue - b.subValue;
+};
+
+export const getTableColumns = (source: any) => {
   const data = source.map(item => ({
     title: item.auditName,
-    dataIndex: item.auditId,
+    dataIndex: source[0].auditId === item.auditId ? 'base' : 'compared',
+    key: source[0].auditId === item.auditId ? 'base' : 'compared',
+    sorter: {
+      compare: source[0].auditId === item.auditId ? baseSorter : 
comparedSorter,
+      multiple: source[0].auditId === item.auditId ? 3 : 4,
+    },
     render: text => text || 0,
   }));
   return [
     {
       title: i18n.t('pages.ModuleAudit.config.Ip'),
       dataIndex: 'ip',
+      defaultSortOrder: sortOrder,
+      sorter: strSorter,
     },
-  ].concat(data);
+  ]
+    .concat(data)
+    .concat({
+      title: i18n.t('pages.ModuleAudit.config.SubValue'),
+      dataIndex: 'subValue',
+      defaultSortOrder: null,
+      sorter: subValueSorter,
+    });
 };
diff --git a/inlong-dashboard/src/ui/pages/ModuleAudit/IdModule/index.tsx 
b/inlong-dashboard/src/ui/pages/ModuleAudit/IdModule/index.tsx
index 65fb7bd053..20f9d64fd6 100644
--- a/inlong-dashboard/src/ui/pages/ModuleAudit/IdModule/index.tsx
+++ b/inlong-dashboard/src/ui/pages/ModuleAudit/IdModule/index.tsx
@@ -17,25 +17,29 @@
  * under the License.
  */
 
-import React, { useMemo, useState } from 'react';
+import React, { useCallback, useEffect, useMemo, useState } from 'react';
 import HighTable, { useForm } from '@/ui/components/HighTable';
 import { useRequest } from '@/ui/hooks';
 import { timestampFormat } from '@/core/utils';
-import { getFormContent, toTableData, getTableColumns } from './config';
+import { getFormContent, toTableData, getTableColumns, sumSubValue } from 
'./config';
+import i18n from '@/i18n';
 import { AuditProps } from '@/ui/pages/ModuleAudit';
+import { Table } from 'antd';
+import dayjs from 'dayjs';
 
 export const idModule = 'id';
 const Comp: React.FC<AuditProps> = ({ auditData }) => {
   const [form] = useForm();
 
   const [query, setQuery] = useState({
-    startDate: +new Date(),
-    endDate: +new Date(),
+    startDate: dayjs().startOf('hour').valueOf(),
+    endDate: dayjs().startOf('hour').valueOf(),
     auditIds: ['3', '4'],
     inlongGroupId: '',
     inlongStreamId: '',
   });
-
+  const [inlongStreamID, setInlongStreamID] = useState('');
+  const [inlongGroupId, setInlongGroupId] = useState('');
   const { data: sourceData = [], run } = useRequest(
     {
       url: '/audit/listAll',
@@ -48,32 +52,49 @@ const Comp: React.FC<AuditProps> = ({ auditData }) => {
     },
     {
       refreshDeps: [query],
-      formatResult: result => result.sort((a, b) => (a.auditId - b.auditId > 0 
? 1 : -1)),
+      formatResult: result => {
+        const base = result.find(item2 => item2.auditId === 
query.auditIds[0].toString());
+        const compared = result.find(item2 => item2.auditId === 
query.auditIds[1].toString());
+        return [base, compared];
+      },
     },
   );
 
   const sourceDataMap = useMemo(() => {
-    const flatArr = sourceData.reduce(
-      (acc, cur) =>
-        acc.concat(
-          cur.auditSet.map(item => ({
-            ...item,
-            auditId: cur.auditId,
-          })),
-        ),
-      [],
-    );
-    const output = flatArr.reduce((acc, cur) => {
-      if (!acc[cur.ip]) {
-        acc[cur.ip] = {};
-      }
+    if (!sourceData) {
+      return {};
+    }
+    let baseData =
+      sourceData[0]?.auditSet?.length > sourceData[1]?.auditSet?.length
+        ? sourceData[0]
+        : sourceData[1];
+    const output = baseData?.auditSet?.reduce((acc, cur) => {
       acc[cur.ip] = {
-        ...acc[cur.ip],
-        [cur.auditId]: cur.count,
         ip: cur.ip,
+        base:
+          sourceData[0].auditId === baseData.auditId
+            ? cur.count
+            : sourceData[0].auditSet.find(item => (item.ip = cur.ip))
+            ? sourceData[0].auditSet.find(item => (item.ip = cur.ip)).count
+            : 0,
+        compared:
+          sourceData[1].auditId === baseData.auditId
+            ? cur.count
+            : sourceData[1].auditSet.find(item => (item.ip = cur.ip))
+            ? sourceData[1].auditSet.find(item => (item.ip = cur.ip)).count
+            : 0,
       };
       return acc;
     }, {});
+    if (output === undefined || output === null) {
+      return {};
+    }
+    Object.keys(output).forEach(key => {
+      output[key] = {
+        ...output[key],
+        subValue: output[key].compared - output[key].base,
+      };
+    });
     return output;
   }, [sourceData]);
 
@@ -86,28 +107,84 @@ const Comp: React.FC<AuditProps> = ({ auditData }) => {
     setQuery({
       ...query,
       ...keyword,
-      auditIds:
-        keyword.benchmark !== undefined && keyword.compared !== undefined
-          ? [keyword.benchmark, keyword.compared]
-          : ['3', '4'],
+      auditIds: [
+        keyword.benchmark !== undefined ? keyword.benchmark : 
query.auditIds[0],
+        keyword.compared !== undefined ? keyword.compared : query.auditIds[1],
+      ],
       inlongGroupId: keyword.inlongGroupId,
       inlongStreamId: keyword.inlongStreamId,
       startDate: +keyword.startDate.$d,
       endDate: keyword.endDate === undefined ? +keyword.startDate.$d : 
+keyword.endDate.$d,
     });
   };
+  const numToName = useCallback(
+    num => {
+      let obj = {};
+      obj = {
+        base: sourceData[0].auditName,
+        compared: sourceData[1].auditName,
+        ip: i18n.t('pages.ModuleAudit.config.Ip'),
+        subValue: i18n.t('pages.ModuleAudit.config.SubValue'),
+      };
+      return obj[num];
+    },
+    [sourceData],
+  );
 
+  const csvData = useMemo(() => {
+    const result = toTableData(sourceData, sourceDataMap).map(item => {
+      let obj = {};
+      Object.keys(item)
+        .filter(key => key !== 'logTs')
+        .forEach(key => {
+          obj = { ...obj, [numToName(key)]: item[key] };
+        });
+      return obj;
+    });
+    return result;
+  }, [sourceData, sourceDataMap]);
+  const [fileName, setFileName] = useState('metrics.csv');
+  useEffect(() => {
+    setFileName(`id_${inlongGroupId}_${inlongStreamID}.csv`);
+  }, [inlongGroupId, inlongStreamID]);
   return (
     <>
       <HighTable
         filterForm={{
-          content: getFormContent(query, onSearch, auditData),
+          style: { gap: '10px' },
+          content: getFormContent(
+            query,
+            onSearch,
+            auditData,
+            sourceData,
+            csvData,
+            setInlongGroupId,
+            setInlongStreamID,
+            fileName,
+          ),
           onFilter,
         }}
         table={{
           columns: getTableColumns(sourceData),
           dataSource: toTableData(sourceData, sourceDataMap),
           rowKey: 'logTs',
+          summary: () => (
+            <Table.Summary fixed>
+              <Table.Summary.Row>
+                <Table.Summary.Cell index={0}>
+                  {i18n.t('pages.GroupDetail.Audit.Total')}
+                </Table.Summary.Cell>
+                {sourceData.map((row, index) => (
+                  <Table.Summary.Cell key={index} index={index + 1}>
+                    {row.auditSet.reduce((total, item) => total + item.count, 
0).toLocaleString()}
+                  </Table.Summary.Cell>
+                ))}
+                <Table.Summary.Cell key={sourceData.length} 
index={sourceData.length + 1}>
+                  {sumSubValue(sourceDataMap).toLocaleString()}
+                </Table.Summary.Cell>
+              </Table.Summary.Row>
+            </Table.Summary>
+          ),
         }}
       />
     </>
diff --git a/inlong-dashboard/src/ui/pages/ModuleAudit/IpModule/config.tsx 
b/inlong-dashboard/src/ui/pages/ModuleAudit/IpModule/config.tsx
index 1b6dc36e88..9edc4a251e 100644
--- a/inlong-dashboard/src/ui/pages/ModuleAudit/IpModule/config.tsx
+++ b/inlong-dashboard/src/ui/pages/ModuleAudit/IpModule/config.tsx
@@ -22,7 +22,9 @@ import i18n from '@/i18n';
 import request from '@/core/utils/request';
 import { Button } from 'antd';
 import React from 'react';
-
+import { CSVLink } from 'react-csv';
+import { range } from 'lodash';
+import { SortOrder } from 'antd/es/table/interface';
 export const toChartData = (source, sourceDataMap) => {
   const xAxisData = Object.keys(sourceDataMap);
   return {
@@ -48,6 +50,9 @@ export const toChartData = (source, sourceDataMap) => {
 };
 
 export const toTableData = (source, sourceDataMap) => {
+  if (sourceDataMap === null || sourceDataMap === undefined) {
+    return [];
+  }
   return Object.keys(sourceDataMap)
     .reverse()
     .map(logTs => ({
@@ -56,10 +61,23 @@ export const toTableData = (source, sourceDataMap) => {
     }));
 };
 
-export const getFormContent = (initialValues, onSearch, auditData) => [
+export const getFormContent = (
+  initialValues,
+  onSearch,
+  auditData,
+  sourceData,
+  csvData,
+  setIp,
+  fileName,
+) => [
   {
     type: 'input',
     label: i18n.t('pages.ModuleAudit.config.Ip'),
+    props: {
+      onChange: (e: any) => {
+        setIp(e.target.value);
+      },
+    },
     name: 'ip',
   },
   {
@@ -70,18 +88,28 @@ export const getFormContent = (initialValues, onSearch, 
auditData) => [
     props: {
       allowClear: false,
       showTime: true,
-      format: 'YYYY-MM-DD HH:mm:ss',
+      format: 'YYYY-MM-DD HH:mm',
+      disabledTime: (date: dayjs.Dayjs, type, info: { from?: dayjs.Dayjs }) => 
{
+        return {
+          disabledSeconds: () => range(0, 60),
+        };
+      },
     },
   },
   {
     type: 'datepicker',
     label: i18n.t('pages.GroupDetail.Audit.EndDate'),
     name: 'endDate',
-    initialValues: dayjs(initialValues.endDate),
+    initialValues: dayjs().startOf('hour').valueOf(),
     props: {
       allowClear: false,
       showTime: true,
-      format: 'YYYY-MM-DD HH:mm:ss',
+      format: 'YYYY-MM-DD HH:mm',
+      disabledTime: (date: dayjs.Dayjs, type, info: { from?: dayjs.Dayjs }) => 
{
+        return {
+          disabledSeconds: () => range(0, 60),
+        };
+      },
     },
   },
   {
@@ -135,22 +163,74 @@ export const getFormContent = (initialValues, onSearch, 
auditData) => [
       </Button>
     ),
   },
+  {
+    type: (
+      <Button type="primary" disabled={!(sourceData.length > 0)}>
+        <CSVLink data={csvData} filename={fileName}>
+          {i18n.t('pages.GroupDetail.Audit.ExportCSV')}
+        </CSVLink>
+      </Button>
+    ),
+  },
 ];
-
+const baseSorter = (a, b) => {
+  return a.base - b.base;
+};
+const comparedSorter = (a, b) => {
+  return a.compared - b.compared;
+};
+const subValueSorter = (a, b) => {
+  return a.subValue - b.subValue;
+};
+const groupIdStrSorter = (a, b) => {
+  return a?.inlongGroupId.localeCompare(b.inlongGroupId);
+};
+const streamIdStrSorter = (a, b) => {
+  return a?.inlongStreamId.localeCompare(b?.inlongStreamId);
+};
+const sortOrder: SortOrder = 'descend';
 export const getTableColumns = source => {
   const data = source.map(item => ({
     title: item.auditName,
-    dataIndex: item.auditId,
+    dataIndex: source[0].auditId === item.auditId ? 'base' : 'compared',
+    key: source[0].auditId === item.auditId ? 'base' : 'compared',
+    sorter: {
+      compare: source[0].auditId === item.auditId ? baseSorter : 
comparedSorter,
+      multiple: source[0].auditId === item.auditId ? 3 : 4,
+    },
     render: text => text || 0,
   }));
   return [
     {
       title: i18n.t('pages.ModuleAudit.config.InlongGroupId'),
       dataIndex: 'inlongGroupId',
+      key: 'inlongGroupId',
+      sorter: {
+        compare: groupIdStrSorter,
+        multiple: 1,
+      },
     },
     {
       title: i18n.t('pages.ModuleAudit.config.InlongStreamId'),
       dataIndex: 'inlongStreamId',
+      key: 'inlongStreamId',
+      defaultSortOrder: sortOrder,
+      sorter: {
+        compare: streamIdStrSorter,
+        multiple: 2,
+      },
     },
-  ].concat(data);
+  ]
+    .concat(data)
+    .concat([
+      {
+        title: i18n.t('pages.ModuleAudit.config.SubValue'),
+        dataIndex: 'subValue',
+        key: 'subValue',
+        sorter: {
+          compare: subValueSorter,
+          multiple: 5,
+        },
+      },
+    ]);
 };
diff --git a/inlong-dashboard/src/ui/pages/ModuleAudit/IpModule/index.tsx 
b/inlong-dashboard/src/ui/pages/ModuleAudit/IpModule/index.tsx
index ef24a3e7dd..9342298464 100644
--- a/inlong-dashboard/src/ui/pages/ModuleAudit/IpModule/index.tsx
+++ b/inlong-dashboard/src/ui/pages/ModuleAudit/IpModule/index.tsx
@@ -17,21 +17,25 @@
  * under the License.
  */
 
-import React, { useMemo, useState } from 'react';
+import React, { useCallback, useEffect, useMemo, useState } from 'react';
 import { useForm } from '@/ui/components/FormGenerator';
 import HighTable from '@/ui/components/HighTable';
 import { useRequest } from '@/ui/hooks';
 import { timestampFormat } from '@/core/utils';
 import { getFormContent, toTableData, getTableColumns } from './config';
+import i18n from '@/i18n';
 import { AuditProps } from '@/ui/pages/ModuleAudit';
+import { Table } from 'antd';
+import { sumSubValue } from '@/ui/pages/ModuleAudit/IdModule/config';
+import dayjs from 'dayjs';
 
 export const ipModule = 'ip';
 const Comp: React.FC<AuditProps> = ({ auditData }) => {
   const [form] = useForm();
 
   const [query, setQuery] = useState({
-    startDate: +new Date(),
-    endDate: +new Date(),
+    startDate: dayjs().startOf('hour').valueOf(),
+    endDate: dayjs().startOf('hour').valueOf(),
     auditIds: ['3', '4'],
   });
 
@@ -47,33 +51,68 @@ const Comp: React.FC<AuditProps> = ({ auditData }) => {
     },
     {
       refreshDeps: [query],
-      formatResult: result => result.sort((a, b) => (a.auditId - b.auditId > 0 
? 1 : -1)),
+      formatResult: result => {
+        const base = result.find(item2 => item2.auditId === 
query.auditIds[0].toString());
+        const compared = result.find(item2 => item2.auditId === 
query.auditIds[1].toString());
+        return [base, compared];
+      },
     },
   );
 
   const sourceDataMap = useMemo(() => {
-    const flatArr = sourceData.reduce(
-      (acc, cur) =>
-        acc.concat(
-          cur.auditSet.map(item => ({
-            ...item,
-            auditId: cur.auditId,
-          })),
-        ),
-      [],
-    );
-    const output = flatArr.reduce((acc, cur) => {
-      if (!acc[cur.inlongStreamId]) {
-        acc[cur.inlongStreamId] = {};
-      }
-      acc[cur.inlongStreamId] = {
-        ...acc[cur.inlongStreamId],
-        [cur.auditId]: cur.count,
+    if (!sourceData) {
+      return {};
+    }
+    let baseData =
+      sourceData[0]?.auditSet?.length > sourceData[1]?.auditSet?.length
+        ? sourceData[0]
+        : sourceData[1];
+    const output = baseData?.auditSet?.reduce((acc, cur) => {
+      console.log('cur', cur, sourceData[0].auditId, baseData.auditId);
+      acc[cur.inlongGroupId + cur.inlongStreamId] = {
         inlongGroupId: cur.inlongGroupId,
         inlongStreamId: cur.inlongStreamId,
+        base:
+          sourceData[0].auditId === baseData.auditId
+            ? cur.count
+            : sourceData[0].auditSet.find(item => {
+                return (
+                  item.inlongGroupId + item.inlongStreamId ===
+                  cur.inlongGroupId + cur.inlongStreamId
+                );
+              })
+            ? sourceData[0].auditSet.find(
+                item =>
+                  item.inlongGroupId + item.inlongStreamId ===
+                  cur.inlongGroupId + cur.inlongStreamId,
+              ).count
+            : 0,
+        compared:
+          sourceData[1].auditId === baseData.auditId
+            ? cur.count
+            : sourceData[1].auditSet.find(
+                item =>
+                  item.inlongGroupId + item.inlongStreamId ===
+                  cur.inlongGroupId + cur.inlongStreamId,
+              )
+            ? sourceData[1].auditSet.find(
+                item =>
+                  item.inlongGroupId + item.inlongStreamId ===
+                  cur.inlongGroupId + cur.inlongStreamId,
+              ).count
+            : 0,
       };
       return acc;
     }, {});
+    if (output === undefined || output === null) {
+      return {};
+    }
+    Object.keys(output).forEach(key => {
+      output[key] = {
+        ...output[key],
+        subValue: output[key].compared - output[key].base,
+      };
+    });
     return output;
   }, [sourceData]);
 
@@ -87,26 +126,78 @@ const Comp: React.FC<AuditProps> = ({ auditData }) => {
       ...query,
       ...keyword,
       ip: keyword.ip,
-      auditIds:
-        keyword.benchmark !== undefined && keyword.compared !== undefined
-          ? [keyword.benchmark, keyword.compared]
-          : ['3', '4'],
+      auditIds: [
+        keyword.benchmark !== undefined ? keyword.benchmark : 
query.auditIds[0],
+        keyword.compared !== undefined ? keyword.compared : query.auditIds[1],
+      ],
       startDate: +keyword.startDate.$d,
       endDate: keyword.endDate === undefined ? +keyword.startDate.$d : 
+keyword.endDate.$d,
     });
   };
+  const numToName = useCallback(
+    num => {
+      let obj = {
+        inlongGroupId: i18n.t('pages.ModuleAudit.config.InlongGroupId'),
+        inlongStreamId: i18n.t('pages.ModuleAudit.config.InlongStreamId'),
+        subValue: i18n.t('pages.ModuleAudit.config.SubValue'),
+        base: sourceData[0].auditName,
+        compared: sourceData[1].auditName,
+      };
+      return obj[num];
+    },
+    [sourceData],
+  );
 
+  const csvData = useMemo(() => {
+    if (!sourceData) {
+      return {};
+    }
+    const result = toTableData(sourceData, sourceDataMap).map(item => {
+      let obj = {};
+      Object.keys(item)
+        .filter(key => key !== 'logTs')
+        .forEach(key => {
+          obj = { ...obj, [numToName(key)]: item[key] };
+        });
+      return obj;
+    });
+    return result;
+  }, [sourceData, sourceDataMap]);
+  const [ip, setIp] = useState('');
+  const [fileName, setFileName] = useState('ip.csv');
+  useEffect(() => {
+    setFileName('ip_' + ip + '.csv');
+  }, [ip]);
   return (
     <>
       <HighTable
         filterForm={{
-          content: getFormContent(query, onSearch, auditData),
+          style: { gap: '10px' },
+          content: getFormContent(query, onSearch, auditData, sourceData, 
csvData, setIp, fileName),
           onFilter,
         }}
         table={{
           columns: getTableColumns(sourceData),
           dataSource: toTableData(sourceData, sourceDataMap),
           rowKey: 'logTs',
+          summary: () => (
+            <Table.Summary fixed>
+              <Table.Summary.Row>
+                <Table.Summary.Cell index={0}>
+                  {i18n.t('pages.GroupDetail.Audit.Total')}
+                </Table.Summary.Cell>
+                <Table.Summary.Cell index={1}></Table.Summary.Cell>
+                {sourceData.map((row, index) => (
+                  <Table.Summary.Cell key={index} index={index + 2}>
+                    {row.auditSet.reduce((total, item) => total + item.count, 
0).toLocaleString()}
+                  </Table.Summary.Cell>
+                ))}
+                <Table.Summary.Cell key={sourceData.length} 
index={sourceData.length - 1}>
+                  {sumSubValue(sourceDataMap).toLocaleString()}
+                </Table.Summary.Cell>
+              </Table.Summary.Row>
+            </Table.Summary>
+          ),
         }}
       />
     </>


Reply via email to