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 2345624b7 [INLONG-7313][Dashboard] Automatically generate a unique ID 
and depth key for the menu (#7314)
2345624b7 is described below

commit 2345624b78c8bf0d5802756464f07dd3db65fe18
Author: Daniel <lee...@apache.org>
AuthorDate: Thu Feb 2 18:57:37 2023 +0800

    [INLONG-7313][Dashboard] Automatically generate a unique ID and depth key 
for the menu (#7314)
---
 inlong-dashboard/scripts/sync.js                 | 138 +++++++++++++++++++++++
 inlong-dashboard/scripts/utils.js                |  71 ++++++++++++
 inlong-dashboard/src/components/Layout/index.tsx |   4 +-
 inlong-dashboard/src/configs/locales/index.ts    |   6 +-
 inlong-dashboard/src/configs/menus/index.ts      |  26 ++++-
 inlong-dashboard/src/metas/DataWithBackend.ts    |   8 ++
 inlong-dashboard/src/models/index.ts             |   9 +-
 inlong-dashboard/src/router.tsx                  |   4 +-
 inlong-dashboard/src/utils/index.ts              |  35 ++++++
 9 files changed, 285 insertions(+), 16 deletions(-)

diff --git a/inlong-dashboard/scripts/sync.js b/inlong-dashboard/scripts/sync.js
new file mode 100644
index 000000000..6d48ce25b
--- /dev/null
+++ b/inlong-dashboard/scripts/sync.js
@@ -0,0 +1,138 @@
+/*
+ * 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.
+ */
+
+const path = require('path');
+const readline = require('readline');
+const { readFile, wirteFile, getBanner } = require('./utils');
+
+const getMetaDir = (metaType, namespace, saveAs) => {
+  const dirMap = {
+    group: [
+      // from:
+      path.resolve(
+        __dirname,
+        
`../../inlong-manager/manager-pojo/src/main/java/org/apache/inlong/manager/pojo/group`,
+        `./${namespace.toLowerCase()}/Inlong${namespace}Request.java`,
+      ),
+      // to:
+      path.resolve(__dirname, `../src/metas/groups/${saveAs}`, 
`${namespace}.ts`),
+    ],
+    consume: [
+      // from:
+      path.resolve(
+        __dirname,
+        
`../../inlong-manager/manager-pojo/src/main/java/org/apache/inlong/manager/pojo/consume`,
+        `./${namespace.toLowerCase()}/Consume${namespace}Request.java`,
+      ),
+      // to:
+      path.resolve(__dirname, `../src/metas/consumes/${saveAs}`, 
`${namespace}.ts`),
+    ],
+    node: [
+      // from:
+      path.resolve(
+        __dirname,
+        
`../../inlong-manager/manager-pojo/src/main/java/org/apache/inlong/manager/pojo/node`,
+        `./es/${namespace}DataNodeRequest.java`, // TODO
+      ),
+      // to:
+      path.resolve(__dirname, `../src/metas/nodes/${saveAs}`, 
`${namespace}.ts`),
+    ],
+  };
+
+  if (!dirMap[metaType]) {
+    throw new Error(`[Error] MetaType: ${metaType} not exist.`);
+  }
+
+  return dirMap[metaType];
+};
+
+const genMetaData = (metaType, namespace, list) => {
+  const MetaType = `${metaType[0].toUpperCase()}${metaType.slice(1)}`;
+  const BasicInfoName = `${MetaType}Info`;
+  const str = `\
+
+import { DataWithBackend } from '@/metas/DataWithBackend';
+import { RenderRow } from '@/metas/RenderRow';
+import { RenderList } from '@/metas/RenderList';
+import { ${BasicInfoName} } from '../common/${BasicInfoName}';
+
+const { I18n } = DataWithBackend;
+const { FieldDecorator } = RenderRow;
+
+export default class ${namespace}${MetaType}
+  extends ${BasicInfoName}
+  implements DataWithBackend, RenderRow, RenderList
+{
+  ${list
+    .map(item => {
+      const { dataType, key, defaultValue } = item;
+      return `
+  @FieldDecorator({
+    type: 'input',
+    initialValue: ${defaultValue},
+    props: {
+    },
+  })
+  @I18n('${key}')
+  ${key}: ${dataType};
+  `;
+    })
+    .join('')}
+}
+  `;
+  return str;
+};
+
+const runSync = () => {
+  const rl = readline.createInterface({
+    input: process.stdin,
+    output: process.stdout,
+  });
+
+  rl.question(`What's your metaType? (Support: group/consume): `, metaType => {
+    rl.question(`What's your namespace? (For example: Kafka): `, namespace => {
+      rl.question(`Save as defaults or extends? `, saveAs => {
+        rl.close();
+
+        const [backendFilePath, outputFilePath] = getMetaDir(metaType, 
namespace, saveAs);
+        const dataText = readFile(backendFilePath);
+        const beginIndex = dataText.indexOf('class');
+
+        if (beginIndex !== -1) {
+          const arr = dataText.slice(beginIndex).match(/private.+;/g) || [];
+          const list = arr.map(item => {
+            if (item[item.length - 1] === ';') item = item.slice(0, -1);
+            const [, dataType, key, , defaultValue] = item.split(' ');
+            return { dataType, key, defaultValue };
+          });
+
+          const banner = getBanner();
+          const text = genMetaData(metaType, namespace, list);
+          const buffer = Buffer.from(`${banner}${text}`);
+
+          wirteFile(outputFilePath, buffer);
+          console.log(`Build ${metaType}: ${namespace} successfully! Saved at 
${outputFilePath}`);
+        }
+      });
+      rl.write('defaults');
+    });
+  });
+};
+
+runSync();
diff --git a/inlong-dashboard/scripts/utils.js 
b/inlong-dashboard/scripts/utils.js
new file mode 100644
index 000000000..7544bd37e
--- /dev/null
+++ b/inlong-dashboard/scripts/utils.js
@@ -0,0 +1,71 @@
+/*
+ * 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.
+ */
+
+const fs = require('fs');
+
+const readFile = path => {
+  try {
+    const result = fs.readFileSync(path);
+    return result.toString();
+  } catch (err) {
+    console.log('[FS readFile]', err);
+    return;
+  }
+};
+
+const wirteFile = (path, buffer, options) => {
+  try {
+    fs.writeFileSync(path, buffer, {
+      encoding: 'utf8',
+      flag: 'w',
+      ...options,
+    });
+  } catch (err) {
+    console.log('[FS wirteFile]', err);
+  }
+};
+
+const getBanner = () => {
+  return `
+/*
+ * 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.
+ */
+  `;
+};
+
+module.exports = {
+  readFile,
+  wirteFile,
+  getBanner,
+};
diff --git a/inlong-dashboard/src/components/Layout/index.tsx 
b/inlong-dashboard/src/components/Layout/index.tsx
index eb40fdc2e..29b964616 100644
--- a/inlong-dashboard/src/components/Layout/index.tsx
+++ b/inlong-dashboard/src/components/Layout/index.tsx
@@ -18,7 +18,7 @@
  */
 
 import { config } from '@/configs/default';
-import menus from '@/configs/menus';
+import menusTree from '@/configs/menus';
 import defaultSettings from '@/defaultSettings';
 import { useLocation, useSelector } from '@/hooks';
 import { isDevelopEnv } from '@/utils';
@@ -49,7 +49,7 @@ const BasicLayout: React.FC = props => {
   const { pathname } = location;
   const roles = useSelector<State, State['roles']>(state => state.roles);
   const { breadcrumbMap, menuData } = useMemo(() => {
-    const _menus = menus.filter(
+    const _menus = menusTree.filter(
       item => (item.isAdmin && roles?.includes('ADMIN')) || !item.isAdmin,
     );
     return getMenuData(_menus);
diff --git a/inlong-dashboard/src/configs/locales/index.ts 
b/inlong-dashboard/src/configs/locales/index.ts
index acf6c67b8..e4c48f3ed 100644
--- a/inlong-dashboard/src/configs/locales/index.ts
+++ b/inlong-dashboard/src/configs/locales/index.ts
@@ -20,7 +20,7 @@
 interface LocalesType {
   [key: string]: {
     label: string;
-    antdPath: string;
+    uiComponentPath: string;
     dayjsPath: string;
   };
 }
@@ -28,12 +28,12 @@ interface LocalesType {
 export const localesConfig: LocalesType = {
   cn: {
     label: '简体中文',
-    antdPath: 'zh_CN',
+    uiComponentPath: 'zh_CN',
     dayjsPath: 'zh-cn',
   },
   en: {
     label: 'English',
-    antdPath: 'en_US',
+    uiComponentPath: 'en_US',
     dayjsPath: 'en',
   },
 };
diff --git a/inlong-dashboard/src/configs/menus/index.ts 
b/inlong-dashboard/src/configs/menus/index.ts
index ed6c28c86..25d011bcd 100644
--- a/inlong-dashboard/src/configs/menus/index.ts
+++ b/inlong-dashboard/src/configs/menus/index.ts
@@ -18,15 +18,33 @@
  */
 
 import i18n from '@/i18n';
+import { treeToArray } from '@/utils';
 
 export interface MenuItemType {
   name: string;
+  key?: string; // auto generate
+  deepKey?: string; // auto generate
   children?: MenuItemType[];
   path?: string;
   isAdmin?: boolean;
 }
 
-const menus: MenuItemType[] = [
+const genMenuKey = (array: Omit<MenuItemType, 'key'>[], parentKey = ''): 
MenuItemType[] => {
+  return array.map((item, index) => {
+    let obj = { ...item };
+    const num = index + 1 < 10 ? `0${index + 1}` : (index + 1).toString();
+    const currentKey = `${parentKey}${num}`;
+    if (obj.children) {
+      obj.children = genMenuKey(obj.children, currentKey);
+    }
+    return {
+      ...obj,
+      key: currentKey,
+    };
+  });
+};
+
+const menusTree: MenuItemType[] = genMenuKey([
   {
     path: '/group',
     name: i18n.t('configs.menus.Groups'),
@@ -70,6 +88,8 @@ const menus: MenuItemType[] = [
       },
     ],
   },
-];
+]);
+
+export const menuArrays: Omit<MenuItemType, 'children'>[] = 
treeToArray(menusTree, 'key', 'pKey');
 
-export default menus;
+export default menusTree;
diff --git a/inlong-dashboard/src/metas/DataWithBackend.ts 
b/inlong-dashboard/src/metas/DataWithBackend.ts
index 8104b17d0..8a07f6114 100644
--- a/inlong-dashboard/src/metas/DataWithBackend.ts
+++ b/inlong-dashboard/src/metas/DataWithBackend.ts
@@ -23,4 +23,12 @@ export abstract class DataWithBackend extends DataStatic {
   abstract parse<T, K>(data: T): K;
 
   abstract stringify<T, K>(data: T): K;
+
+  abstract post?<T, K>(data: T): Promise<K>;
+
+  abstract delete?<T, K>(data: T): Promise<K>;
+
+  abstract put?<T, K>(data: T): Promise<K>;
+
+  abstract get?<T, K>(data: T): Promise<K>;
 }
diff --git a/inlong-dashboard/src/models/index.ts 
b/inlong-dashboard/src/models/index.ts
index fed19ff82..09eab7b58 100644
--- a/inlong-dashboard/src/models/index.ts
+++ b/inlong-dashboard/src/models/index.ts
@@ -19,7 +19,7 @@
 
 import { createStore } from 'redux';
 import { pathToRegexp } from 'path-to-regexp';
-import menus from '@/configs/menus';
+import { menuArrays, MenuItemType } from '@/configs/menus';
 import { getPathnameExist } from '@/configs/routes';
 import { getCurrentLocale } from '@/configs/locales';
 
@@ -28,10 +28,7 @@ export interface State {
   userName: string;
   userId: number;
   roles: string[];
-  currentMenu: null | {
-    name: string;
-    path: string;
-  };
+  currentMenu: null | Omit<MenuItemType, 'children'>;
 }
 
 const state: State = {
@@ -65,7 +62,7 @@ const reducers = {
 
     // Find the selected route
     let currentMenu = null;
-    for (const item of menus) {
+    for (const item of menuArrays) {
       if (
         item.path &&
         // The route in the menu || is not in the menu, but belongs to a 
sub-route under a menu
diff --git a/inlong-dashboard/src/router.tsx b/inlong-dashboard/src/router.tsx
index e44f8d335..80fe60d8c 100644
--- a/inlong-dashboard/src/router.tsx
+++ b/inlong-dashboard/src/router.tsx
@@ -104,7 +104,7 @@ const App = () => {
   const importLocale = useCallback(async locale => {
     if (!localesConfig[locale]) return;
 
-    const { antdPath, dayjsPath } = localesConfig[locale];
+    const { uiComponentPath, dayjsPath } = localesConfig[locale];
     const [messagesDefault, messagesExtends, antdMessages] = await 
Promise.all([
       import(
         /* webpackChunkName: 'default-locales-[request]' */
@@ -117,7 +117,7 @@ const App = () => {
       import(
         /* webpackInclude: /(zh_CN|en_US)\.js$/ */
         /* webpackChunkName: 'antd-locales-[request]' */
-        `antd/es/locale/${antdPath}.js`
+        `antd/es/locale/${uiComponentPath}.js`
       ),
       import(
         /* webpackInclude: /(zh-cn|en)\.js$/ */
diff --git a/inlong-dashboard/src/utils/index.ts 
b/inlong-dashboard/src/utils/index.ts
index 59a94fa64..ee05e361b 100644
--- a/inlong-dashboard/src/utils/index.ts
+++ b/inlong-dashboard/src/utils/index.ts
@@ -17,6 +17,8 @@
  * under the License.
  */
 
+import cloneDeep from 'lodash/cloneDeep';
+
 export function isDevelopEnv() {
   if (process.env.NODE_ENV === 'development') {
     return true;
@@ -206,3 +208,36 @@ export function getStrByteLen(str: string): number {
   }
   return len;
 }
+
+function treeToArrayHelper(
+  tree: any[],
+  id: string,
+  pid: string,
+  children: string,
+  parent: any = null,
+): any[] {
+  let data = cloneDeep(tree);
+  return data.reduce((accumulator, item) => {
+    const { [children]: itemChildren } = item;
+    let result;
+    if (parent && !item[pid]) {
+      item[pid] = parent[id];
+    }
+    if (parent) {
+      item.deepKey = parent.deepKey.concat(item[id]);
+    } else {
+      item.deepKey = [item[id]];
+    }
+    if (itemChildren) {
+      result = accumulator.concat(item, treeToArrayHelper(itemChildren, id, 
pid, children, item));
+      delete item[children];
+    } else {
+      result = accumulator.concat(item);
+    }
+    return result;
+  }, []);
+}
+
+export function treeToArray(tree: any[], id = 'id', pid = 'pid', children = 
'children') {
+  return treeToArrayHelper(tree, id, pid, children);
+}

Reply via email to