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); +}