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 38d2cdb437 [INLONG-10779][Dashboard] Data synchronization adds offline 
synchronization configuration (#10782)
38d2cdb437 is described below

commit 38d2cdb4374951ee20bf2513d27daffff06faa7e
Author: kamianlaida <165994047+wohainilao...@users.noreply.github.com>
AuthorDate: Tue Aug 20 10:52:45 2024 +0800

    [INLONG-10779][Dashboard] Data synchronization adds offline synchronization 
configuration (#10782)
    
    Co-authored-by: Charles Zhang <dockerzh...@apache.org>
---
 .../src/plugins/sync/common/SyncDefaultInfo.ts     | 170 +++++++++++++++++++++
 .../src/plugins/sync/common/SyncType.ts            |  36 +++++
 inlong-dashboard/src/ui/locales/cn.json            |  28 +++-
 inlong-dashboard/src/ui/locales/en.json            |  26 ++++
 .../src/ui/pages/SynchronizeDashboard/index.tsx    |  19 ++-
 .../src/ui/pages/SynchronizeDetail/Info/config.tsx |  77 +++++++++-
 .../src/ui/pages/SynchronizeDetail/Info/index.tsx  |  38 ++++-
 7 files changed, 384 insertions(+), 10 deletions(-)

diff --git a/inlong-dashboard/src/plugins/sync/common/SyncDefaultInfo.ts 
b/inlong-dashboard/src/plugins/sync/common/SyncDefaultInfo.ts
index 9e81013208..4e229a73f0 100644
--- a/inlong-dashboard/src/plugins/sync/common/SyncDefaultInfo.ts
+++ b/inlong-dashboard/src/plugins/sync/common/SyncDefaultInfo.ts
@@ -24,11 +24,18 @@ import i18n from '@/i18n';
 import UserSelect from '@/ui/components/UserSelect';
 import { genStatusTag, statusList } from './status';
 import { timestampFormat } from '@/core/utils';
+import { DatePicker, TimePicker } from 'antd';
+import dayjs from 'dayjs';
+import { inlongGroupModeList } from '@/plugins/sync/common/SyncType';
+import { range } from 'lodash';
 
 const { I18nMap, I18n } = DataWithBackend;
 const { FieldList, FieldDecorator } = RenderRow;
 const { ColumnList, ColumnDecorator } = RenderList;
 
+const format = 'HH:mm';
+const conventionalTimeFormat = 'YYYY-MM-DD HH:mm';
+
 export class SyncDefaultInfo implements DataWithBackend, RenderRow, RenderList 
{
   static I18nMap = I18nMap;
   static FieldList = FieldList;
@@ -87,7 +94,170 @@ export class SyncDefaultInfo implements DataWithBackend, 
RenderRow, RenderList {
   })
   @I18n('meta.Synchronize.SinkMultipleEnable')
   sinkMultipleEnable: boolean;
+  @FieldDecorator({
+    type: 'radio',
+    initialValue: 1,
+    rules: [{ required: true }],
+    props: {
+      options: [
+        {
+          label: i18n.t('meta.Synchronize.RealTime'),
+          value: 1,
+        },
+        {
+          label: i18n.t('meta.Synchronize.Offline'),
+          value: 2,
+        },
+      ],
+    },
+  })
+  @ColumnDecorator({
+    width: 200,
+    render: text => inlongGroupModeList?.filter(item => item.value === 
text)?.[0]?.label,
+  })
+  @I18n('meta.Synchronize.SyncType')
+  inlongGroupMode: Number;
 
+  @FieldDecorator({
+    type: 'radio',
+    initialValue: 0,
+    visible: values => values.inlongGroupMode === 2,
+    rules: [{ required: true }],
+    props: {
+      options: [
+        {
+          label: i18n.t('meta.Synchronize.Conventional'),
+          value: 0,
+        },
+        {
+          label: i18n.t('meta.Synchronize.Crontab'),
+          value: 1,
+        },
+      ],
+    },
+  })
+  @I18n('meta.Synchronize.ScheduleType')
+  scheduleType: Number;
+  @FieldDecorator({
+    visible: values => values.inlongGroupMode === 2 && values.scheduleType === 
0,
+    type: 'select',
+    initialValue: 'H',
+    name: 'scheduleUnit',
+    rules: [{ required: true }],
+    props: {
+      options: [
+        {
+          label: i18n.t('meta.Synchronize.ScheduleUnit.Year'),
+          value: 'Y',
+        },
+        {
+          label: i18n.t('meta.Synchronize.ScheduleUnit.Month'),
+          value: 'M',
+        },
+        {
+          label: i18n.t('meta.Synchronize.ScheduleUnit.Day'),
+          value: 'D',
+        },
+        {
+          label: i18n.t('meta.Synchronize.ScheduleUnit.Hours'),
+          value: 'H',
+        },
+        {
+          label: i18n.t('meta.Synchronize.ScheduleUnit.Minute'),
+          value: 'I',
+        },
+        {
+          label: i18n.t('meta.Synchronize.ScheduleUnit.Single'),
+          value: 'O',
+        },
+      ],
+    },
+  })
+  @I18n('meta.Synchronize.ScheduleUnit')
+  scheduleUnit: String;
+
+  @FieldDecorator({
+    type: 'input',
+    initialValue: 0,
+    rules: [{ required: true, pattern: new RegExp(/^[0-9]\d*$/, 'g') }],
+    visible: values => values.inlongGroupMode === 2 && values.scheduleType === 
0,
+    props: values => ({
+      suffix: values.scheduleUnit,
+    }),
+  })
+  @I18n('meta.Synchronize.ScheduleInterval')
+  scheduleInterval: number;
+
+  @FieldDecorator({
+    visible: values => values.inlongGroupMode === 2 && values.scheduleType === 
0,
+    type: TimePicker,
+    initialValue: dayjs('00:00', format),
+    name: 'delayTime',
+    rules: [{ required: true }],
+    props: {
+      format: format,
+    },
+  })
+  @I18n('meta.Synchronize.DelayTime')
+  delayTime: dayjs.Dayjs;
+
+  @FieldDecorator({
+    type: DatePicker.RangePicker,
+    props: values => ({
+      format: conventionalTimeFormat,
+      showTime: true,
+      disabledTime: (date: dayjs.Dayjs, type, info: { from?: dayjs.Dayjs }) => 
{
+        return {
+          disabledSeconds: () => range(0, 60),
+        };
+      },
+    }),
+    visible: values =>
+      values.inlongGroupMode === 2 && (values.scheduleType === 0 || 
values.scheduleType === 1),
+    rules: [{ required: true }],
+  })
+  @I18n('meta.Synchronize.ValidTime')
+  time: dayjs.Dayjs[];
+  @FieldDecorator({
+    visible: values => values.inlongGroupMode === 2 && values.scheduleType === 
1,
+    type: 'input',
+    rules: [{ required: true }],
+    props: {},
+  })
+  @I18n('meta.Synchronize.CronExpression')
+  crontabExpression: string;
+
+  @FieldDecorator({
+    type: 'radio',
+    visible: values => values.inlongGroupMode === 2,
+    initialValue: 0,
+    rules: [{ required: true }],
+    props: {
+      options: [
+        {
+          label: i18n.t('meta.Synchronize.Alone'),
+          value: 0,
+        },
+        {
+          label: i18n.t('meta.Synchronize.Self'),
+          value: 1,
+        },
+        {
+          label: i18n.t('meta.Synchronize.Parallel'),
+          value: 2,
+        },
+      ],
+    },
+  })
+  @I18n('meta.Synchronize.SelfDependence')
+  selfDepend: Number;
+  @FieldDecorator({
+    type: 'input',
+    rules: [{ required: true }],
+    visible: values => values.selfDepend === 2 && values.inlongGroupMode === 2,
+  })
+  @I18n('meta.Synchronize.TaskParallelism')
+  taskParallelism: string;
   @FieldDecorator({
     type: 'textarea',
     props: {
diff --git a/inlong-dashboard/src/plugins/sync/common/SyncType.ts 
b/inlong-dashboard/src/plugins/sync/common/SyncType.ts
new file mode 100644
index 0000000000..1c51e7e1d1
--- /dev/null
+++ b/inlong-dashboard/src/plugins/sync/common/SyncType.ts
@@ -0,0 +1,36 @@
+/*
+ * 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';
+
+type TypeProp = {
+  label: string;
+  value: number;
+};
+
+export const inlongGroupModeList: TypeProp[] = [
+  {
+    label: i18n.t('meta.Synchronize.RealTimeSync'),
+    value: 1,
+  },
+  {
+    label: i18n.t('meta.Synchronize.OfflineSync'),
+    value: 2,
+  },
+];
diff --git a/inlong-dashboard/src/ui/locales/cn.json 
b/inlong-dashboard/src/ui/locales/cn.json
index 59c69445c7..2c00ea1dc0 100644
--- a/inlong-dashboard/src/ui/locales/cn.json
+++ b/inlong-dashboard/src/ui/locales/cn.json
@@ -417,7 +417,33 @@
   "meta.Synchronize.GroupIdHelp": "数据流组 ID 与数据流 ID 默认相同",
   "meta.Synchronize.InlongGroupOwnersExtra": "责任人,可查看、修改数据同步信息",
   "meta.Synchronize.GroupOwners": "责任人",
-  "meta.Stream.StreamId": "数据流 ID",
+  "meta.Synchronize.SyncType": "同步类型",
+  "meta.Synchronize.RealTimeSync": "实时同步",
+  "meta.Synchronize.RealTime": "实时",
+  "meta.Synchronize.Offline": "离线",
+  "meta.Synchronize.OfflineSync": "离线同步",
+  "meta.Synchronize.SchedulingRules": "调度规则",
+  "meta.Synchronize.ScheduleType": "调度类型",
+  "meta.Synchronize.Conventional": "常规",
+  "meta.Synchronize.Crontab": "Crontab",
+  "meta.Synchronize.DelayTime": "延迟时间",
+  "meta.Synchronize.ScheduleUnit": "调度单位",
+  "meta.Synchronize.ScheduleUnit.Year": "年",
+  "meta.Synchronize.ScheduleUnit.Month": "月",
+  "meta.Synchronize.ScheduleUnit.Day": "天",
+  "meta.Synchronize.ScheduleUnit.Minute": "分钟",
+  "meta.Synchronize.ScheduleUnit.Hours": "小时",
+  "meta.Synchronize.ScheduleUnit.Single": "单次",
+  "meta.Synchronize.ScheduleInterval": "调度周期",
+  "meta.Synchronize.ValidTime": "有效时间",
+  "meta.Synchronize.CronExpression": "Crontab 表达式",
+  "meta.Synchronize.DependConfiguration": "依赖配置",
+  "meta.Synchronize.SelfDependence": "自身依赖",
+  "meta.Synchronize.Alone": "单实例运行",
+  "meta.Synchronize.Self": "自依赖",
+  "meta.Synchronize.Parallel": "并行",
+  "meta.Synchronize.TaskParallelism": "最大并行个数",
+  "meta.Stream.StreamId":"数据流 ID",
   "meta.Stream.StreamIdRules": "只能包含英文字母、数字、点号(.)、中划线(-)、下划线(_)",
   "meta.Stream.DataSeparator": "源数据字段分割符",
   "meta.Stream.DataSeparator.Space": "空格",
diff --git a/inlong-dashboard/src/ui/locales/en.json 
b/inlong-dashboard/src/ui/locales/en.json
index 82bb47c46b..7546f2e399 100644
--- a/inlong-dashboard/src/ui/locales/en.json
+++ b/inlong-dashboard/src/ui/locales/en.json
@@ -417,6 +417,32 @@
   "meta.Synchronize.GroupIdHelp": "Group ID and stream ID are the same by 
default",
   "meta.Synchronize.InlongGroupOwnersExtra": "Can view, modify data 
synchronization info",
   "meta.Synchronize.GroupOwners": "Group owners",
+  "meta.Synchronize.SyncType": "Sync Type",
+  "meta.Synchronize.RealTimeSync": "RealTime Sync",
+  "meta.Synchronize.RealTime": "RealTime",
+  "meta.Synchronize.Offline": "Offline",
+  "meta.Synchronize.OfflineSync": "Offline Sync",
+  "meta.Synchronize.SchedulingRules": "Scheduling Rules",
+  "meta.Synchronize.ScheduleType": "Schedule Type",
+  "meta.Synchronize.Conventional": "Conventional",
+  "meta.Synchronize.Crontab": "Crontab",
+  "meta.Synchronize.DelayTime": "Delay Time",
+  "meta.Synchronize.ScheduleUnit": "Schedule Unit",
+  "meta.Synchronize.ScheduleUnit.Year": "Year",
+  "meta.Synchronize.ScheduleUnit.Month": "Month",
+  "meta.Synchronize.ScheduleUnit.Day": "Day",
+  "meta.Synchronize.ScheduleUnit.Minute": "Minute",
+  "meta.Synchronize.ScheduleUnit.Hours": "Hours",
+  "meta.Synchronize.ScheduleUnit.Single": "Single",
+  "meta.Synchronize.ScheduleInterval": "Schedule Interval",
+  "meta.Synchronize.ValidTime": "Valid Time",
+  "meta.Synchronize.CronExpression": "Cron Expression",
+  "meta.Synchronize.DependConfiguration": "Depend Configuration",
+  "meta.Synchronize.SelfDependence": "Self Dependence",
+  "meta.Synchronize.Alone": "Alone",
+  "meta.Synchronize.Self": "Self",
+  "meta.Synchronize.Parallel": "Parallel",
+  "meta.Synchronize.TaskParallelism": "Task Parallelism",
   "meta.Stream.StreamId": "Stream id",
   "meta.Stream.StreamIdRules": "Only English letters, numbers, dots(.), 
minus(-), and underscores(_)",
   "meta.Stream.DataSeparator": "Source data separator",
diff --git a/inlong-dashboard/src/ui/pages/SynchronizeDashboard/index.tsx 
b/inlong-dashboard/src/ui/pages/SynchronizeDashboard/index.tsx
index 2dfad1d5e4..b096f9957f 100644
--- a/inlong-dashboard/src/ui/pages/SynchronizeDashboard/index.tsx
+++ b/inlong-dashboard/src/ui/pages/SynchronizeDashboard/index.tsx
@@ -30,6 +30,7 @@ import { GroupLogs } from '@/ui/components/GroupLogs';
 import { dashCardList, useColumns } from './config';
 import { statusList } from '@/plugins/groups/common/status';
 import { useDefaultMeta } from '@/plugins';
+import { inlongGroupModeList } from '@/plugins/sync/common/SyncType';
 
 const Comp: React.FC = () => {
   const { options: groups } = useDefaultMeta('group');
@@ -50,7 +51,7 @@ const Comp: React.FC = () => {
   const { data: summary = {} } = useRequest({
     url: '/group/countByStatus',
     params: {
-      inlongGroupMode: 1,
+      inlongGroupMode: options.inlongGroupMode,
     },
   });
 
@@ -171,6 +172,17 @@ const Comp: React.FC = () => {
           dropdownMatchSelectWidth: false,
         },
       },
+      {
+        type: 'select',
+        name: 'inlongGroupMode',
+        label: i18n.t('meta.Synchronize.SyncType'),
+        initialValue: defaultValues.inlongGroupMode,
+        props: {
+          allowClear: true,
+          options: inlongGroupModeList,
+          dropdownMatchSelectWidth: false,
+        },
+      },
     ],
     [groups],
   );
@@ -196,7 +208,10 @@ const Comp: React.FC = () => {
             table={{
               columns,
               rowKey: 'id',
-              dataSource: data?.list,
+              dataSource: data?.list?.map(item => ({
+                ...item,
+                inlongGroupMode: options.inlongGroupMode,
+              })),
               pagination,
               loading,
               onChange,
diff --git a/inlong-dashboard/src/ui/pages/SynchronizeDetail/Info/config.tsx 
b/inlong-dashboard/src/ui/pages/SynchronizeDetail/Info/config.tsx
index 1b9e83fccf..78aac3df65 100644
--- a/inlong-dashboard/src/ui/pages/SynchronizeDetail/Info/config.tsx
+++ b/inlong-dashboard/src/ui/pages/SynchronizeDetail/Info/config.tsx
@@ -17,12 +17,14 @@
  * under the License.
  */
 
-import React, { useMemo } from 'react';
+import React, { useCallback, useMemo } from 'react';
 import { Divider } from 'antd';
 import i18n from '@/i18n';
 import { useLoadMeta, SyncMetaType } from '@/plugins';
 import { excludeObjectArray } from '@/core/utils';
-
+import dayjs from 'dayjs';
+import { range } from 'lodash';
+const conventionalTimeFormat = 'YYYY-MM-DD HH:mm';
 export const useFormContent = ({ mqType, editing, isCreate, isUpdate }) => {
   const { Entity } = useLoadMeta<SyncMetaType>('sync', mqType);
 
@@ -48,6 +50,21 @@ export const useFormContent = ({ mqType, editing, isCreate, 
isUpdate }) => {
       })
     : fields.map(item => {
         const t = transType(editing, item);
+        if (item.name === 'time' || item.name === 'delayTime') {
+          return {
+            ...item,
+            props: values => ({
+              format: conventionalTimeFormat,
+              showTime: true,
+              disabledTime: (date: dayjs.Dayjs, type, info: { from?: 
dayjs.Dayjs }) => {
+                return {
+                  disabledSeconds: () => range(0, 60),
+                };
+              },
+              disabled: !editing,
+            }),
+          };
+        }
         return {
           ...item,
           type: t,
@@ -62,13 +79,58 @@ export const useFormContent = ({ mqType, editing, isCreate, 
isUpdate }) => {
           rules: t === 'text' ? undefined : item.rules,
         };
       });
+  const isScKey = useCallback(
+    formName => {
+      const defaultGroupKeysI18nMap = Entity?.I18nMap || {};
+      return (
+        !defaultGroupKeysI18nMap[formName] ||
+        [
+          'scheduleType',
+          'time',
+          'crontabExpression',
+          'scheduledCycle',
+          'scheduleUnit',
+          'scheduleInterval',
+          'delayTime',
+        ].includes(formName)
+      );
+    },
+    [Entity?.I18nMap],
+  );
+
+  const isSrKey = useCallback(
+    formName => {
+      const defaultGroupKeysI18nMap = Entity?.I18nMap || {};
+      return (
+        !defaultGroupKeysI18nMap[formName] || ['selfDepend', 
'taskParallelism'].includes(formName)
+      );
+    },
+    [Entity?.I18nMap],
+  );
+
+  const groupFormContent = formContent.filter(item => !isScKey(item.name));
+  const baseFormContent = groupFormContent.filter(item => !isSrKey(item.name));
+  const scFormContent = formContent.filter(item => isScKey(item.name));
+  const srFormContent = formContent.filter(item => isSrKey(item.name));
 
   return [
     {
       type: <Divider 
orientation="left">{i18n.t('pages.GroupDetail.Info.Basic')}</Divider>,
       col: 24,
     },
-    ...formContent,
+    ...baseFormContent,
+    {
+      visible: values => values.inlongGroupMode === 2,
+      type: <Divider 
orientation="left">{i18n.t('meta.Synchronize.SchedulingRules')}</Divider>,
+      col: 24,
+    },
+    ...scFormContent,
+    {
+      visible: values => values.inlongGroupMode === 2,
+      type: <Divider 
orientation="left">{i18n.t('meta.Synchronize.DependConfiguration')}</Divider>,
+      col: 24,
+    },
+    ...srFormContent,
   ];
 };
 
@@ -87,6 +149,12 @@ function transType(editing: boolean, conf) {
         'ttl',
         'retentionTime',
         'retentionSize',
+        'scheduleType',
+        'scheduleUnit',
+        'scheduleInterval',
+        'crontabExpression',
+        'selfDepend',
+        'taskParallelism',
       ],
       as: 'text',
       active: !editing,
@@ -100,6 +168,9 @@ function transType(editing: boolean, conf) {
     const item = map.get(conf.name);
     return item.active ? item.as : conf.type;
   }
+  if (conf.name === 'time' || conf.name === 'delayTime') {
+    return conf.type;
+  }
 
   return 'text';
 }
diff --git a/inlong-dashboard/src/ui/pages/SynchronizeDetail/Info/index.tsx 
b/inlong-dashboard/src/ui/pages/SynchronizeDetail/Info/index.tsx
index e6251dcad4..28784c7826 100644
--- a/inlong-dashboard/src/ui/pages/SynchronizeDetail/Info/index.tsx
+++ b/inlong-dashboard/src/ui/pages/SynchronizeDetail/Info/index.tsx
@@ -27,13 +27,14 @@ import request from '@/core/utils/request';
 import { useFormContent } from './config';
 import { CommonInterface } from '../common';
 import { State } from '@/core/stores';
+import dayjs from 'dayjs';
 
 type Props = CommonInterface;
 
 const Comp = ({ inlongGroupId, inlongStreamId, readonly, isCreate }: Props, 
ref) => {
   const { t } = useTranslation();
   const [editing, { setTrue, setFalse }] = useBoolean(isCreate);
-
+  const conventionalTimeFormat = 'YYYY-MM-DD HH:mm';
   const { defaultValue } = useDefaultMeta('sync');
 
   const { userName } = useSelector<State, State>(state => state);
@@ -46,6 +47,13 @@ const Comp = ({ inlongGroupId, inlongStreamId, readonly, 
isCreate }: Props, ref)
     return !!inlongGroupId;
   }, [inlongGroupId]);
 
+  useEffect(() => {
+    if (isCreate) {
+      form.setFieldValue('scheduleType', 0);
+      form.setFieldValue('scheduleUnit', 'H');
+    }
+  }, [form, isCreate]);
+
   const isUpdateStream = useMemo(() => {
     return !!inlongStreamId;
   }, [inlongStreamId]);
@@ -56,6 +64,11 @@ const Comp = ({ inlongGroupId, inlongStreamId, readonly, 
isCreate }: Props, ref)
     formatResult: data => ({
       ...data,
       inCharges: data.inCharges.split(','),
+      time: [
+        dayjs(dayjs(data?.startTime), conventionalTimeFormat),
+        dayjs(dayjs(data?.endTime), conventionalTimeFormat),
+      ],
+      delayTime: convertMinutesToDelayTime(data.delayTime),
     }),
     onSuccess: data => {
       setMqType(data.mqType);
@@ -77,6 +90,16 @@ const Comp = ({ inlongGroupId, inlongStreamId, readonly, 
isCreate }: Props, ref)
       },
     },
   );
+  const convertTimeToMinutes = timeString => {
+    const time = dayjs(timeString, 'HH:mm');
+    return time.hour() * 60 + time.minute();
+  };
+
+  const convertMinutesToDelayTime = totalMinutes => {
+    const hours = Math.floor(totalMinutes / 60);
+    const minutes = totalMinutes % 60;
+    return dayjs().hour(hours).minute(minutes);
+  };
   const { data: streamData, run: getDataStream } = useRequest(
     {
       url: '/stream/list',
@@ -99,13 +122,20 @@ const Comp = ({ inlongGroupId, inlongStreamId, readonly, 
isCreate }: Props, ref)
   const onOk = async () => {
     const values = await form.validateFields();
 
-    const submitData = {
+    let submitData = {
       ...values,
       version: data?.version,
       inCharges: values.inCharges?.join(','),
-      inlongGroupMode: 1,
     };
-
+    if (values.inlongGroupMode === 2) {
+      submitData = {
+        ...submitData,
+        delayTime: convertTimeToMinutes(values?.delayTime?.format('HH:mm')),
+        startTime: 
dayjs(values?.time?.[0]?.format(conventionalTimeFormat)).valueOf(),
+        endTime: 
dayjs(values?.time?.[1]?.format(conventionalTimeFormat)).valueOf(),
+      };
+    }
+    delete submitData.time;
     const submitDataStream = {
       inlongGroupId: values.inlongGroupId,
       inlongStreamId: values.inlongGroupId,

Reply via email to