This is an automated email from the ASF dual-hosted git repository.
dockerzhang 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 22d9a33a00 [INLONG-11510][Dashboard] Add page to dirty data query
(#11512)
22d9a33a00 is described below
commit 22d9a33a00ea25e576dd986103e7be9009de18b7
Author: kamianlaida <[email protected]>
AuthorDate: Wed Nov 20 22:04:59 2024 +0800
[INLONG-11510][Dashboard] Add page to dirty data query (#11512)
---
inlong-dashboard/src/ui/locales/cn.json | 33 +-
inlong-dashboard/src/ui/locales/en.json | 34 +-
.../src/ui/pages/GroupDetail/DataStorage/index.tsx | 24 +-
.../ui/pages/SynchronizeDetail/SyncSink/index.tsx | 24 +-
.../src/ui/pages/common/DirtyModal/conf.tsx | 47 ++
.../src/ui/pages/common/DirtyModal/index.tsx | 593 +++++++++++++++++++++
6 files changed, 751 insertions(+), 4 deletions(-)
diff --git a/inlong-dashboard/src/ui/locales/cn.json
b/inlong-dashboard/src/ui/locales/cn.json
index 535063ee00..4f631bb151 100644
--- a/inlong-dashboard/src/ui/locales/cn.json
+++ b/inlong-dashboard/src/ui/locales/cn.json
@@ -1,5 +1,6 @@
{
"basic.Edit": "编辑",
+ "basic.Search": "搜索",
"basic.Detail": "详情",
"basic.Operating": "操作",
"basic.OperatingSuccess": "操作成功",
@@ -1020,5 +1021,35 @@
"pages.GroupDataTemplate.VisibleRange.InCharges":"责任人",
"pages.GroupDataTemplate.VisibleRange.Tenant":"租户",
"miscellaneous.total": "... 共",
- "miscellaneous.tenants": "个租户"
+ "miscellaneous.tenants": "个租户",
+ "meta.Sinks.DirtyData.DirtyDataPartition": "脏数据分区",
+ "meta.Sinks.DirtyData.DataFlowId": "数据目标Id",
+ "meta.Sinks.DirtyData.GroupId": "数据组Id",
+ "meta.Sinks.DirtyData.StreamId": "数据流Id",
+ "meta.Sinks.DirtyData.ReportTime": "上报时间",
+ "meta.Sinks.DirtyData.DataTime": "数据时间",
+ "meta.Sinks.DirtyData.ServerType": "服务类型",
+ "meta.Sinks.DirtyData.DirtyType": "脏数据类型",
+ "meta.Sinks.DirtyData.DirtyMessage": "脏数据信息",
+ "meta.Sinks.DirtyData.ExtInfo": "额外信息",
+ "meta.Sinks.DirtyData.DirtyData": "脏数据",
+ "meta.Sinks.DirtyData.DirtyDetailWarning": "脏数据任务正在运行,请稍后再试",
+ "meta.Sinks.DirtyData.DirtyTrendWarning": "脏数据趋势任务正在运行,请稍后再试",
+ "meta.Sinks.DirtyData.DataCount": "脏数据条数",
+ "meta.Sinks.DirtyData.Search.DirtyType": "脏数据类型",
+ "meta.Sinks.DirtyData.Search.ServerType": "服务类型",
+ "meta.Sinks.DirtyData.StartTimeError": "开始时间不能大于当前时间",
+ "meta.Sinks.DirtyData.endTimeNotGreaterThanStartTime": "结束时间不能大于当前时间",
+ "meta.Sinks.DirtyData.TimeIntervalError": "时间间隔不能超过七天",
+ "meta.Sinks.DirtyTrend.DataTimeUnit":"时间单位",
+ "meta.Sinks.DirtyTrend.Day":"天",
+ "meta.Sinks.DirtyTrend.Hour":"小时",
+ "meta.Sinks.DirtyData.Detail":"详情",
+ "meta.Sinks.DirtyData.Trend":"趋势",
+ "meta.Sinks.DirtyData":"脏数据查询",
+ "meta.Sinks.DirtyData.DirtyType.DeserializeError":"反序列化错误",
+ "meta.Sinks.DirtyData.DirtyType.FieldMappingError":"字段映射错误",
+ "meta.Sinks.DirtyData.DirtyType.LoadError":"加载错误",
+ "meta.Sinks.DirtyData.Search.KeyWordHelp":"请输入关键字",
+ "meta.Sinks.DirtyData.Search.KeyWord":"关键字"
}
diff --git a/inlong-dashboard/src/ui/locales/en.json
b/inlong-dashboard/src/ui/locales/en.json
index b74f54e58e..b7e60d316b 100644
--- a/inlong-dashboard/src/ui/locales/en.json
+++ b/inlong-dashboard/src/ui/locales/en.json
@@ -1,5 +1,6 @@
{
"basic.Edit": "Edit",
+ "basic.Search": "Search",
"basic.Detail": "Detail",
"basic.Operating": "Operation",
"basic.OperatingSuccess": "Operating success",
@@ -374,6 +375,7 @@
"meta.Sinks.Cls.Tag": "Tag",
"meta.Sinks.Cls.Tokenizer": "Tokenizer rule",
"meta.Sinks.Cls.IsMetaField": "Is meta field",
+
"meta.Group.InlongGroupId": "Inlong group id",
"meta.Group.InlongGroupIdRules": "Only English letters, numbers, dots(.),
minus(-), and underscores(_)",
"meta.Group.InlongGroupName": "Inlong group name",
@@ -1020,5 +1022,35 @@
"pages.GroupDataTemplate.VisibleRange.InCharges":"Owner",
"pages.GroupDataTemplate.VisibleRange.Tenant":"Tenant",
"miscellaneous.total": "... total ",
- "miscellaneous.tenants": " tenants"
+ "miscellaneous.tenants": " tenants",
+ "meta.Sinks.DirtyData.DirtyDataPartition": "Dirty Data Partition",
+ "meta.Sinks.DirtyData.DataFlowId": "Data Flow Id",
+ "meta.Sinks.DirtyData.GroupId": "Group Id",
+ "meta.Sinks.DirtyData.StreamId": "Stream Id",
+ "meta.Sinks.DirtyData.ReportTime": "Report Time",
+ "meta.Sinks.DirtyData.DataTime": "Data Time",
+ "meta.Sinks.DirtyData.ServerType": "Server Type",
+ "meta.Sinks.DirtyData.DirtyType": "Dirty Type",
+ "meta.Sinks.DirtyData.DirtyMessage": "Dirty Message",
+ "meta.Sinks.DirtyData.ExtInfo": "Ext Info",
+ "meta.Sinks.DirtyData.DirtyData": "Dirty Data",
+ "meta.Sinks.DirtyData.DirtyDetailWarning": "The dirty data task is running,
please try again later",
+ "meta.Sinks.DirtyData.DirtyTrendWarning": "The dirty data trending task is
running, try again later",
+ "meta.Sinks.DirtyData.DataCount": "Data Count",
+ "meta.Sinks.DirtyData.Search.DirtyType": "Dirty Type",
+ "meta.Sinks.DirtyData.Search.ServerType": "Server Type",
+ "meta.Sinks.DirtyData.StartTimeError": "The start time cannot be greater
than the current time",
+ "meta.Sinks.DirtyData.endTimeNotGreaterThanStartTime": "The end time cannot
be greater than the current time",
+ "meta.Sinks.DirtyData.TimeIntervalError": "The time interval cannot be more
than seven days",
+ "meta.Sinks.DirtyTrend.DataTimeUnit":"DataTime Unit",
+ "meta.Sinks.DirtyTrend.Day":"Day",
+ "meta.Sinks.DirtyTrend.Hour":"Hour",
+ "meta.Sinks.DirtyData.Detail":"Detail",
+ "meta.Sinks.DirtyData.Trend":"Trend",
+ "meta.Sinks.DirtyData":"Dirty Data Query",
+ "meta.Sinks.DirtyData.DirtyType.DeserializeError":"Deserialize Error",
+ "meta.Sinks.DirtyData.DirtyType.FieldMappingError":"Field Mapping Error",
+ "meta.Sinks.DirtyData.DirtyType.LoadError":"Load Error",
+ "meta.Sinks.DirtyData.Search.KeyWordHelp":"Please enter a keyword",
+ "meta.Sinks.DirtyData.Search.KeyWord":"Key word"
}
diff --git a/inlong-dashboard/src/ui/pages/GroupDetail/DataStorage/index.tsx
b/inlong-dashboard/src/ui/pages/GroupDetail/DataStorage/index.tsx
index b91cac850d..5f1086d91e 100644
--- a/inlong-dashboard/src/ui/pages/GroupDetail/DataStorage/index.tsx
+++ b/inlong-dashboard/src/ui/pages/GroupDetail/DataStorage/index.tsx
@@ -25,6 +25,7 @@ import {
TableOutlined,
EditOutlined,
DeleteOutlined,
+ AreaChartOutlined,
} from '@ant-design/icons';
import HighTable from '@/ui/components/HighTable';
import { defaultSize } from '@/configs/pagination';
@@ -36,6 +37,7 @@ import request from '@/core/utils/request';
import { pickObjectArray } from '@/core/utils';
import { CommonInterface } from '../common';
import { sinks } from '@/plugins/sinks';
+import DirtyModal from '@/ui/pages/common/DirtyModal';
interface Props extends CommonInterface {
inlongStreamId?: string;
@@ -58,7 +60,12 @@ const Comp = ({ inlongGroupId, inlongStreamId, readonly }:
Props, ref) => {
const [createModal, setCreateModal] = useState<Record<string, unknown>>({
open: false,
});
-
+ const [dirtyModal, setDirtyModal] = useState<Record<string, unknown>>({
+ open: false,
+ });
+ const onOpenDirtyModal = useCallback(({ id }) => {
+ setDirtyModal({ open: true, id });
+ }, []);
const {
data,
loading,
@@ -173,6 +180,9 @@ const Comp = ({ inlongGroupId, inlongStreamId, readonly }:
Props, ref) => {
<Button type="link" onClick={() => onDelete(record)}>
{i18n.t('basic.Delete')}
</Button>
+ <Button type="link" onClick={() => onOpenDirtyModal(record)}>
+ {i18n.t('meta.Sinks.DirtyData')}
+ </Button>
</>
),
} as any,
@@ -238,6 +248,9 @@ const Comp = ({ inlongGroupId, inlongStreamId, readonly }:
Props, ref) => {
<Button key="del" type="link" onClick={() => onDelete(item)}>
<DeleteOutlined />
</Button>,
+ <Button type="link" onClick={() => onOpenDirtyModal(item)}>
+ <AreaChartOutlined />
+ </Button>,
]}
>
<span>
@@ -277,6 +290,15 @@ const Comp = ({ inlongGroupId, inlongStreamId, readonly }:
Props, ref) => {
}}
onCancel={() => setCreateModal({ open: false })}
/>
+ <DirtyModal
+ {...dirtyModal}
+ open={dirtyModal.open as boolean}
+ onOk={async () => {
+ await getList();
+ setDirtyModal({ open: false });
+ }}
+ onCancel={() => setDirtyModal({ open: false })}
+ />
</>
);
};
diff --git a/inlong-dashboard/src/ui/pages/SynchronizeDetail/SyncSink/index.tsx
b/inlong-dashboard/src/ui/pages/SynchronizeDetail/SyncSink/index.tsx
index 9e7c243630..2d5f2a761a 100644
--- a/inlong-dashboard/src/ui/pages/SynchronizeDetail/SyncSink/index.tsx
+++ b/inlong-dashboard/src/ui/pages/SynchronizeDetail/SyncSink/index.tsx
@@ -25,6 +25,7 @@ import {
TableOutlined,
EditOutlined,
DeleteOutlined,
+ AreaChartOutlined,
} from '@ant-design/icons';
import HighTable from '@/ui/components/HighTable';
import { defaultSize } from '@/configs/pagination';
@@ -36,6 +37,7 @@ import request from '@/core/utils/request';
import { pickObjectArray } from '@/core/utils';
import { sinks } from '@/plugins/sinks';
import { CommonInterface } from '../common';
+import DirtyModal from '@/ui/pages/common/DirtyModal';
interface Props extends CommonInterface {
inlongStreamId: string;
@@ -58,7 +60,12 @@ const Comp = ({ inlongGroupId, inlongStreamId,
sinkMultipleEnable, readonly }: P
const [createModal, setCreateModal] = useState<Record<string, unknown>>({
open: false,
});
-
+ const [dirtyModal, setDirtyModal] = useState<Record<string, unknown>>({
+ open: false,
+ });
+ const onOpenDirtyModal = useCallback(({ id }) => {
+ setDirtyModal({ open: true, id });
+ }, []);
const {
data,
loading,
@@ -179,6 +186,9 @@ const Comp = ({ inlongGroupId, inlongStreamId,
sinkMultipleEnable, readonly }: P
<Button type="link" onClick={() => onDelete(record)}>
{i18n.t('basic.Delete')}
</Button>
+ <Button type="link" onClick={() => onOpenDirtyModal(record)}>
+ {i18n.t('meta.Sinks.DirtyData')}
+ </Button>
</>
),
} as any,
@@ -249,6 +259,9 @@ const Comp = ({ inlongGroupId, inlongStreamId,
sinkMultipleEnable, readonly }: P
<Button key="del" type="link" onClick={() => onDelete(item)}>
<DeleteOutlined />
</Button>,
+ <Button type="link" onClick={() => onOpenDirtyModal(item)}>
+ <AreaChartOutlined />
+ </Button>,
]}
>
<span>
@@ -289,6 +302,15 @@ const Comp = ({ inlongGroupId, inlongStreamId,
sinkMultipleEnable, readonly }: P
}}
onCancel={() => setCreateModal({ open: false })}
/>
+ <DirtyModal
+ {...dirtyModal}
+ open={dirtyModal.open as boolean}
+ onOk={async () => {
+ await getList();
+ setDirtyModal({ open: false });
+ }}
+ onCancel={() => setDirtyModal({ open: false })}
+ />
</>
);
};
diff --git a/inlong-dashboard/src/ui/pages/common/DirtyModal/conf.tsx
b/inlong-dashboard/src/ui/pages/common/DirtyModal/conf.tsx
new file mode 100644
index 0000000000..ce18eceb8a
--- /dev/null
+++ b/inlong-dashboard/src/ui/pages/common/DirtyModal/conf.tsx
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 i18n from '@/i18n';
+
+export const statusList = [
+ {
+ label: i18n.t('meta.Sinks.DirtyData.DirtyType.DeserializeError'),
+ value: 'DeserializeError',
+ },
+ {
+ label: i18n.t('meta.Sinks.DirtyData.DirtyType.FieldMappingError'),
+ value: 'FieldMappingError',
+ },
+ {
+ label: i18n.t('meta.Sinks.DirtyData.DirtyType.LoadError'),
+ value: 'LoadError',
+ },
+];
+
+export const statusMap = statusList.reduce(
+ (acc, cur) => ({
+ ...acc,
+ [cur.value]: cur,
+ }),
+ {},
+);
+
+export const genStatusTag = value => {
+ return statusMap[value];
+};
diff --git a/inlong-dashboard/src/ui/pages/common/DirtyModal/index.tsx
b/inlong-dashboard/src/ui/pages/common/DirtyModal/index.tsx
new file mode 100644
index 0000000000..d77d5829fc
--- /dev/null
+++ b/inlong-dashboard/src/ui/pages/common/DirtyModal/index.tsx
@@ -0,0 +1,593 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 React, { useEffect, useState } from 'react';
+import { Button, message, Modal, Tabs, TabsProps } from 'antd';
+import { ModalProps } from 'antd/es/modal';
+import i18n from '@/i18n';
+import HighTable from '@/ui/components/HighTable';
+
+import dayjs from 'dayjs';
+import request from '@/core/utils/request';
+import { useForm } from 'antd/es/form/Form';
+import FormGenerator from '@/ui/components/FormGenerator';
+import Charts from '@/ui/components/Charts';
+import { genStatusTag } from '@/ui/pages/common/DirtyModal/conf';
+
+export interface Props extends ModalProps {
+ id?: number;
+}
+const Comp: React.FC<Props> = ({ ...modalProps }) => {
+ const [form1] = useForm();
+ const [form2] = useForm();
+ const [loading, setLoading] = useState(false);
+ const getColumns = [
+ {
+ title: i18n.t('meta.Sinks.DirtyData.DataFlowId'),
+ dataIndex: 'dataFlowId',
+ width: 90,
+ },
+ {
+ title: i18n.t('meta.Sinks.DirtyData.GroupId'),
+ dataIndex: 'groupId',
+ width: 90,
+ },
+ {
+ title: i18n.t('meta.Sinks.DirtyData.StreamId'),
+ dataIndex: 'streamId',
+ width: 90,
+ },
+ {
+ title: i18n.t('meta.Sinks.DirtyData.ReportTime'),
+ dataIndex: 'reportTime',
+ width: 90,
+ },
+ {
+ title: i18n.t('meta.Sinks.DirtyData.DataTime'),
+ dataIndex: 'dataTime',
+ width: 90,
+ },
+ {
+ title: i18n.t('meta.Sinks.DirtyData.ServerType'),
+ dataIndex: 'serverType',
+ width: 90,
+ },
+ {
+ title: i18n.t('meta.Sinks.DirtyData.DirtyType'),
+ dataIndex: 'dirtyType',
+ width: 90,
+ },
+ {
+ title: i18n.t('meta.Sinks.DirtyData.DirtyMessage'),
+ dataIndex: 'dirtyMessage',
+ width: 90,
+ },
+ {
+ title: i18n.t('meta.Sinks.DirtyData.ExtInfo'),
+ dataIndex: 'extInfo',
+ width: 90,
+ },
+ {
+ title: i18n.t('meta.Sinks.DirtyData.DirtyData'),
+ dataIndex: 'dirtyData',
+ width: 90,
+ },
+ {
+ title: i18n.t('meta.Sinks.DirtyData.DirtyDataPartition'),
+ dataIndex: 'dirtyDataPartition',
+ width: 90,
+ },
+ ];
+
+ const defaultDetailOptions = {
+ keyword: '',
+ dataCount: 10,
+ dirtyType: '',
+ serverType: '',
+ startTime: dayjs().format('YYYYMMDD'),
+ endTime: dayjs().format('YYYYMMDD'),
+ };
+ const defaultTrendOptions = {
+ dataTimeUnit: 'D',
+ dirtyType: '',
+ serverType: '',
+ startTime: dayjs().format('YYYYMMDD'),
+ endTime: dayjs().format('YYYYMMDD'),
+ };
+ const [options, setOptions] = useState(defaultDetailOptions);
+ const [trendOptions, setTrendOption] = useState(defaultTrendOptions);
+ const [data, setData] = useState([]);
+ const [trendData, setTrendData] = useState([]);
+ const [tabValue, setTabValue] = useState('detail');
+ useEffect(() => {
+ if (modalProps.open) {
+ if (tabValue === 'detail') {
+ setOptions(defaultDetailOptions);
+ form1.resetFields();
+ getTaskResult().then(item => {
+ setData(item);
+ });
+ }
+ if (tabValue === 'trend') {
+ setTrendOption(defaultTrendOptions);
+ form2.resetFields();
+ form2.setFieldsValue({
+ dataTimeUnit: 'D',
+ });
+ getTrendData().then(item => {
+ setTrendData(item);
+ });
+ }
+ }
+ }, [modalProps.open, tabValue]);
+ const [messageApi, contextHolder] = message.useMessage();
+ const warning = () => {
+ messageApi.open({
+ type: 'warning',
+ content:
+ tabValue === 'detail'
+ ? i18n.t('meta.Sinks.DirtyData.DirtyDetailWarning')
+ : i18n.t('meta.Sinks.DirtyData.DirtyTrendWarning'),
+ });
+ };
+ const getTaskResult = async () => {
+ setLoading(true);
+ const taskId = await getTaskId();
+ const status = await request({
+ url: '/sink/SqlTaskStatus/' + taskId,
+ method: 'GET',
+ });
+ if (status === 'success') {
+ const data = await request({
+ url: '/sink/getDirtyData/' + taskId,
+ method: 'GET',
+ });
+ setLoading(false);
+ return data;
+ } else {
+ setLoading(false);
+ warning();
+ }
+ return [];
+ };
+ const getTaskId = async () => {
+ const data = await request({
+ url: '/sink/listDirtyData',
+ method: 'POST',
+ data: {
+ ...options,
+ startTime: options.startTime ?
dayjs(options.startTime).format('YYYYMMDD') : '',
+ endTime: options.endTime ? dayjs(options.endTime).format('YYYYMMDD') :
'',
+ dataCount: form1.getFieldValue('dataCount') || 10,
+ keyword: form1.getFieldValue('keyword') || '',
+ sinkIdList: [modalProps.id],
+ },
+ });
+ return data.taskId;
+ };
+
+ const getTrendData = async () => {
+ const taskId = await getTrendTaskId();
+ const status = await request({
+ url: '/sink/SqlTaskStatus/' + taskId,
+ method: 'GET',
+ });
+
+ if (status === 'success') {
+ const data = await request({
+ url: '/sink/getDirtyDataTrend/' + taskId,
+ method: 'GET',
+ });
+ return data;
+ } else {
+ warning();
+ }
+ return [];
+ };
+
+ const getTrendTaskId = async () => {
+ const data = await request({
+ url: '/sink/listDirtyDataTrend',
+ method: 'POST',
+ data: {
+ ...trendOptions,
+ sinkIdList: [modalProps.id],
+ },
+ });
+ return data.taskId;
+ };
+ const onSearch = async () => {
+ await form1.validateFields();
+ await getTaskResult().then(item => {
+ setData(item);
+ });
+ };
+ const onTrendSearch = async () => {
+ await form2.validateFields();
+ await getTrendData().then(item => {
+ setTrendData(item);
+ });
+ };
+
+ const getDetailFilterFormContent = defaultValues => [
+ {
+ type: 'input',
+ label: i18n.t('meta.Sinks.DirtyData.Search.KeyWord'),
+ name: 'keyword',
+ props: {
+ placeholder: i18n.t('meta.Sinks.DirtyData.Search.KeyWordHelp'),
+ },
+ },
+ {
+ label: i18n.t('meta.Sinks.DirtyData.DataCount'),
+ type: 'inputnumber',
+ name: 'dataCount',
+ },
+ {
+ label: i18n.t('meta.Sinks.DirtyData.Search.DirtyType'),
+ type: 'select',
+ name: 'dirtyType',
+ props: {
+ allowClear: true,
+ options: [
+ {
+ label: i18n.t('meta.Sinks.DirtyData.DirtyType.DeserializeError'),
+ value: 'DeserializeError',
+ },
+ {
+ label: i18n.t('meta.Sinks.DirtyData.DirtyType.FieldMappingError'),
+ value: 'FieldMappingError',
+ },
+ {
+ label: i18n.t('meta.Sinks.DirtyData.DirtyType.LoadError'),
+ value: 'LoadError',
+ },
+ ],
+ },
+ },
+ {
+ label: i18n.t('meta.Sinks.DirtyData.Search.ServerType'),
+ type: 'select',
+ name: 'serverType',
+ props: {
+ allowClear: true,
+ options: [
+ {
+ label: 'TubeMQ',
+ value: 'TubeMQ',
+ },
+ {
+ label: 'Iceberg',
+ value: 'Iceberg',
+ },
+ ],
+ },
+ },
+ {
+ type: 'datepicker',
+ label: i18n.t('pages.GroupDetail.Audit.StartDate'),
+ name: 'startTime',
+ initialValue: dayjs(options.startTime),
+ props: {
+ allowClear: true,
+ format: 'YYYYMMDD',
+ },
+ rules: [
+ { required: true },
+ ({ getFieldValue }) => ({
+ validator(_, value) {
+ if (Boolean(value)) {
+ if (value.isAfter(dayjs())) {
+ return Promise.reject(new
Error(i18n.t('meta.Sinks.DirtyData.StartTimeError')));
+ }
+ }
+ return Promise.resolve();
+ },
+ }),
+ ],
+ },
+ {
+ type: 'datepicker',
+ label: i18n.t('pages.GroupDetail.Audit.EndDate'),
+ name: 'endTime',
+ initialValue: dayjs(options.endTime),
+ props: values => {
+ return {
+ allowClear: true,
+ format: 'YYYYMMDD',
+ };
+ },
+ rules: [
+ { required: true },
+ ({ getFieldValue }) => ({
+ validator(_, value) {
+ if (Boolean(value)) {
+ if (value.isAfter(dayjs())) {
+ return Promise.reject(new
Error(i18n.t('endTimeNotGreaterThanStartTime')));
+ }
+ const timeDiff = value.diff(getFieldValue('startDate'), 'day');
+ if (timeDiff <= 7) {
+ return Promise.resolve();
+ }
+ return Promise.reject(new
Error(i18n.t('meta.Sinks.DirtyData.TimeIntervalError')));
+ }
+ return Promise.resolve();
+ },
+ }),
+ ],
+ },
+ {
+ type: (
+ <Button type="primary" onClick={onSearch}>
+ {i18n.t('basic.Search')}
+ </Button>
+ ),
+ },
+ ];
+ const getTendFilterFormContent = defaultValues => [
+ {
+ label: i18n.t('meta.Sinks.DirtyData.Search.DirtyType'),
+ type: 'select',
+ name: 'dirtyType',
+ props: {
+ allowClear: true,
+ options: [
+ {
+ label: 'DeserializeError',
+ value: 'DeserializeError',
+ },
+ {
+ label: 'FieldMappingError',
+ value: 'FieldMappingError',
+ },
+ {
+ label: 'LoadError',
+ value: 'LoadError',
+ },
+ ],
+ },
+ },
+ {
+ label: i18n.t('meta.Sinks.DirtyData.Search.ServerType'),
+ type: 'select',
+ name: 'serverType',
+ props: {
+ allowClear: true,
+ options: [
+ {
+ label: 'TubeMQ',
+ value: 'TubeMQ',
+ },
+ {
+ label: 'Iceberg',
+ value: 'Iceberg',
+ },
+ ],
+ },
+ },
+ {
+ label: i18n.t('meta.Sinks.DirtyTrend.DataTimeUnit'),
+ type: 'select',
+ name: 'dataTimeUnit',
+ initialValue: 'D',
+ props: {
+ options: [
+ {
+ label: i18n.t('meta.Sinks.DirtyTrend.Day'),
+ value: 'D',
+ },
+ {
+ label: i18n.t('meta.Sinks.DirtyTrend.Hour'),
+ value: 'H',
+ },
+ ],
+ },
+ },
+
+ {
+ type: 'datepicker',
+ label: i18n.t('pages.GroupDetail.Audit.StartDate'),
+ name: 'startTime',
+ props: values => {
+ return {
+ allowClear: true,
+ showTime: values.dataTimeUnit === 'H',
+ format: values.dataTimeUnit === 'D' ? 'YYYYMMDD' : 'YYYYMMDDHH',
+ };
+ },
+ initialValue: dayjs(trendOptions.startTime),
+ rules: [
+ { required: true },
+ ({ getFieldValue }) => ({
+ validator(_, value) {
+ if (Boolean(value)) {
+ if (value.isAfter(dayjs())) {
+ return Promise.reject(new
Error(i18n.t('meta.Sinks.DirtyData.StartTimeError')));
+ }
+ }
+ return Promise.resolve();
+ },
+ }),
+ ],
+ },
+ {
+ type: 'datepicker',
+ label: i18n.t('pages.GroupDetail.Audit.EndDate'),
+ name: 'endTime',
+ initialValue: dayjs(trendOptions.endTime),
+ props: values => {
+ return {
+ allowClear: true,
+ showTime: values.dataTimeUnit === 'H',
+ format: values.dataTimeUnit === 'D' ? 'YYYYMMDD' : 'YYYYMMDDHH',
+ };
+ },
+ rules: [
+ { required: true },
+ ({ getFieldValue }) => ({
+ validator(_, value) {
+ if (Boolean(value)) {
+ if (value.isAfter(dayjs())) {
+ return Promise.reject(
+ new
Error(i18n.t('meta.Sinks.DirtyData.endTimeNotGreaterThanStartTime')),
+ );
+ }
+ const timeDiff = value.diff(getFieldValue('startTime'), 'day');
+ if (timeDiff <= 7) {
+ return Promise.resolve();
+ }
+ return Promise.reject(new
Error(i18n.t('meta.Sinks.DirtyData.TimeIntervalError')));
+ }
+ return Promise.resolve();
+ },
+ }),
+ ],
+ },
+ {
+ type: (
+ <Button type="primary" onClick={onTrendSearch}>
+ {i18n.t('basic.Search')}
+ </Button>
+ ),
+ },
+ ];
+
+ const onFilter = allValues => {
+ setOptions(prev => ({
+ ...prev,
+ ...allValues,
+ startTime: allValues.startTime ? +allValues.startTime.$d : '',
+ endTime: allValues.endTime ? +allValues.endTime.$d : '',
+ }));
+ };
+
+ const onTrendFilter = allValues => {
+ setTrendOption(prev => ({
+ ...prev,
+ ...allValues,
+ startTime: allValues.startTime
+ ? allValues.dataTimeUnit === 'H'
+ ? dayjs(allValues.startTime.$d).format('YYYYMMDDHH')
+ : dayjs(allValues.startTime.$d).format('YYYYMMDD')
+ : '',
+ endTime: allValues.endTime
+ ? allValues.dataTimeUnit === 'H'
+ ? dayjs(allValues.endTime.$d).format('YYYYMMDDHH')
+ : dayjs(allValues.endTime.$d).format('YYYYMMDD')
+ : '',
+ }));
+ };
+ const scroll = { x: 2000 };
+ const toChartData = trendData => {
+ return {
+ legend: {
+ data: trendData.map(item => item.reportTime),
+ },
+ tooltip: {
+ trigger: 'axis',
+ },
+ xAxis: {
+ type: 'category',
+ data: trendData.map(item => item.reportTime),
+ },
+ yAxis: {
+ type: 'value',
+ },
+ series: trendData.map(item => ({
+ name: item.reportTime,
+ type: 'line',
+ data: trendData.map(item => item.count),
+ })),
+ };
+ };
+ const items: TabsProps['items'] = [
+ {
+ key: 'detail',
+ label: i18n.t('meta.Sinks.DirtyData.Detail'),
+ children: (
+ <>
+ <FormGenerator
+ form={form1}
+ layout="inline"
+ content={getDetailFilterFormContent(options)}
+ style={{ gap: 10 }}
+ onFilter={onFilter}
+ />
+ <HighTable
+ table={{
+ columns: getColumns,
+ rowKey: 'id',
+ size: 'small',
+ dataSource: data,
+ scroll: scroll,
+ loading,
+ }}
+ />
+ </>
+ ),
+ },
+ {
+ key: 'trend',
+ label: i18n.t('meta.Sinks.DirtyData.Trend'),
+ children: (
+ <>
+ <FormGenerator
+ form={form2}
+ layout="inline"
+ content={getTendFilterFormContent(trendOptions)}
+ style={{ gap: 10 }}
+ onFilter={onTrendFilter}
+ />
+ <Charts height={400} option={toChartData(trendData)}
forceUpdate={true} />
+ </>
+ ),
+ },
+ ];
+ const onTabChange = (key: string) => {
+ setTabValue(key);
+ };
+ useEffect(() => {
+ onTabChange('detail');
+ }, [modalProps.open]);
+ return (
+ <>
+ {contextHolder}
+ <Modal
+ {...modalProps}
+ title={i18n.t('meta.Sinks.DirtyData')}
+ width={1200}
+ footer={null}
+ afterClose={() => {
+ onTabChange('detail');
+ }}
+ >
+ <div style={{ marginBottom: 40 }}>
+ <Tabs
+ defaultActiveKey="detail"
+ activeKey={tabValue}
+ items={items}
+ onChange={onTabChange}
+ />
+ </div>
+ </Modal>
+ </>
+ );
+};
+
+export default Comp;