From 90c518ccbac3a60873cab01008238328e3f934e1 Mon Sep 17 00:00:00 2001 From: MactavishCui Date: Fri, 24 Jan 2025 00:33:59 +0800 Subject: [PATCH] Catalog Table/view drop --- .../dinky/controller/StudioController.java | 26 ++++ .../java/org/dinky/service/StudioService.java | 2 + .../dinky/service/impl/StudioServiceImpl.java | 32 ++++ .../java/org/dinky/data/enums/Status.java | 7 +- .../resources/i18n/messages_en_US.properties | 5 +- .../resources/i18n/messages_zh_CN.properties | 3 + dinky-web/src/locales/en-US/pages.ts | 4 + dinky-web/src/locales/zh-CN/pages.ts | 3 + .../Toolbar/Catalog/RightContext.tsx | 140 ++++++++++++++++++ .../DataStudio/Toolbar/Catalog/constant.tsx | 30 ++++ .../pages/DataStudio/Toolbar/Catalog/data.tsx | 14 ++ .../DataStudio/Toolbar/Catalog/index.tsx | 51 +++++-- .../DataStudio/Toolbar/Catalog/service.tsx | 5 + dinky-web/src/services/endpoints.tsx | 1 + 14 files changed, 308 insertions(+), 15 deletions(-) create mode 100644 dinky-web/src/pages/DataStudio/Toolbar/Catalog/RightContext.tsx create mode 100644 dinky-web/src/pages/DataStudio/Toolbar/Catalog/constant.tsx diff --git a/dinky-admin/src/main/java/org/dinky/controller/StudioController.java b/dinky-admin/src/main/java/org/dinky/controller/StudioController.java index fe5a2bbe5a..1642515e2e 100644 --- a/dinky-admin/src/main/java/org/dinky/controller/StudioController.java +++ b/dinky-admin/src/main/java/org/dinky/controller/StudioController.java @@ -167,4 +167,30 @@ public Result getMSSchemaInfo(@RequestBody StudioMetaStoreDTO studioMeta public Result> getMSColumns(@RequestBody StudioMetaStoreDTO studioMetaStoreDTO) { return Result.succeed(studioService.getMSColumns(studioMetaStoreDTO)); } + + /** Drop Meta Store Flink Table */ + @PostMapping("/dropMSTable") + @ApiOperation("Drop Flink Table") + @ApiImplicitParams({ + @ApiImplicitParam(name = "envId", value = "envId", required = true, dataType = "Integer", paramType = "query"), + @ApiImplicitParam( + name = "catalog", + value = "catalog", + required = true, + dataType = "String", + paramType = "query"), + @ApiImplicitParam( + name = "database", + value = "database", + required = true, + dataType = "String", + paramType = "query"), + @ApiImplicitParam(name = "table", value = "table", required = true, dataType = "String", paramType = "query") + }) + public Result> dropMSTable(@RequestBody StudioMetaStoreDTO studioMetaStoreDTO) { + if (studioService.dropMSTable(studioMetaStoreDTO)) { + return Result.succeed(); + } + return Result.failed(); + } } diff --git a/dinky-admin/src/main/java/org/dinky/service/StudioService.java b/dinky-admin/src/main/java/org/dinky/service/StudioService.java index b74d4387fb..dc0d89cf3f 100644 --- a/dinky-admin/src/main/java/org/dinky/service/StudioService.java +++ b/dinky-admin/src/main/java/org/dinky/service/StudioService.java @@ -53,4 +53,6 @@ public interface StudioService { Schema getMSSchemaInfo(StudioMetaStoreDTO studioMetaStoreDTO); List getMSColumns(StudioMetaStoreDTO studioMetaStoreDTO); + + boolean dropMSTable(StudioMetaStoreDTO studioMetaStoreDTO); } diff --git a/dinky-admin/src/main/java/org/dinky/service/impl/StudioServiceImpl.java b/dinky-admin/src/main/java/org/dinky/service/impl/StudioServiceImpl.java index 6f516974d2..4ed0b5381a 100644 --- a/dinky-admin/src/main/java/org/dinky/service/impl/StudioServiceImpl.java +++ b/dinky-admin/src/main/java/org/dinky/service/impl/StudioServiceImpl.java @@ -26,6 +26,8 @@ import org.dinky.data.dto.StudioLineageDTO; import org.dinky.data.dto.StudioMetaStoreDTO; import org.dinky.data.dto.TaskDTO; +import org.dinky.data.enums.Status; +import org.dinky.data.exception.BusException; import org.dinky.data.model.Catalog; import org.dinky.data.model.ClusterInstance; import org.dinky.data.model.Column; @@ -49,9 +51,13 @@ import org.dinky.utils.FlinkTableMetadataUtil; import org.dinky.utils.RunTimeUtil; +import org.apache.flink.table.catalog.ObjectPath; +import org.apache.flink.table.catalog.exceptions.TableNotExistException; + import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Optional; import org.springframework.stereotype.Service; @@ -209,6 +215,32 @@ public List getMSColumns(StudioMetaStoreDTO studioMetaStoreDTO) { return columns; } + @Override + public boolean dropMSTable(StudioMetaStoreDTO studioMetaStoreDTO) { + String catalogName = studioMetaStoreDTO.getCatalog(); + String database = studioMetaStoreDTO.getDatabase(); + String tableName = studioMetaStoreDTO.getTable(); + if (Dialect.isCommonSql(studioMetaStoreDTO.getDialect())) { + throw new BusException(Status.SYS_CATALOG_ONLY_SUPPORT_FLINK_SQL_OPERATION); + } else { + String envSql = taskService.buildEnvSql(studioMetaStoreDTO); + JobManager jobManager = getJobManager(studioMetaStoreDTO, envSql); + CustomTableEnvironment customTableEnvironment = + jobManager.getExecutor().getCustomTableEnvironment(); + Optional catalogOptional = + customTableEnvironment.getCatalogManager().getCatalog(catalogName); + if (catalogOptional.isPresent()) { + try { + catalogOptional.get().dropTable(new ObjectPath(database, tableName), true); + return true; + } catch (TableNotExistException e) { + log.error("Drop table {}.{}.{} error, detail {}", catalogName, database, tableName, e); + } + } + } + return false; + } + private JobManager getJobManager(StudioMetaStoreDTO studioMetaStoreDTO, String envSql) { JobManager jobManager = jobManagerCache.get(envSql, () -> { JobConfig config = studioMetaStoreDTO.getJobConfig(); diff --git a/dinky-common/src/main/java/org/dinky/data/enums/Status.java b/dinky-common/src/main/java/org/dinky/data/enums/Status.java index 8ca9e31a1f..2ebff4aac1 100644 --- a/dinky-common/src/main/java/org/dinky/data/enums/Status.java +++ b/dinky-common/src/main/java/org/dinky/data/enums/Status.java @@ -480,7 +480,12 @@ public enum Status { SYS_APPROVAL_SETTINGS_TASK_REVIEWER_ROLES(210, "sys.approval.settings.taskReviewerRoles"), SYS_APPROVAL_SETTINGS_TASK_REVIEWER_ROLES_NOTE(211, "sys.approval.settings.taskReviewerRoles.note"), SYS_APPROVAL_TASK_NOT_APPROVED(212, "sys.approval.taskNotApproved"), - SYS_APPROVAL_DUPLICATE_APPROVAL_IN_PROCESS(213, "sys.approval.duplicateInProcess"); + SYS_APPROVAL_DUPLICATE_APPROVAL_IN_PROCESS(213, "sys.approval.duplicateInProcess"), + /** + * Catalog + */ + SYS_CATALOG_ONLY_SUPPORT_FLINK_SQL_OPERATION(214, "sys.catalog.operationOnlySupportedOnFlinkSql"); + private final int code; private final String key; diff --git a/dinky-common/src/main/resources/i18n/messages_en_US.properties b/dinky-common/src/main/resources/i18n/messages_en_US.properties index 7c07e0ae10..16ec74bf4a 100644 --- a/dinky-common/src/main/resources/i18n/messages_en_US.properties +++ b/dinky-common/src/main/resources/i18n/messages_en_US.properties @@ -333,4 +333,7 @@ sys.approval.settings.enforceCrossReview.note=Submitter of an approval are not a sys.approval.settings.taskReviewerRoles=Reviewer Role Codes sys.approval.settings.taskReviewerRoles.note=Roles who can review tasks, different role codes should be divided by comma. For example: SuperAdmin,Reviewer sys.approval.taskNotApproved=Current task is not published or published version is not approved, please try again after task being published and approved -sys.approval.duplicateInProcess=Already have an approval in process, please do not submit again \ No newline at end of file +sys.approval.duplicateInProcess=Already have an approval in process, please do not submit again + +# catalog +sys.catalog.operationOnlySupportedOnFlinkSql=Only supports operations on the catalog of Flink SQL jobs \ No newline at end of file diff --git a/dinky-common/src/main/resources/i18n/messages_zh_CN.properties b/dinky-common/src/main/resources/i18n/messages_zh_CN.properties index 28ab3f6238..76cd66110a 100644 --- a/dinky-common/src/main/resources/i18n/messages_zh_CN.properties +++ b/dinky-common/src/main/resources/i18n/messages_zh_CN.properties @@ -335,3 +335,6 @@ sys.approval.settings.taskReviewerRoles=具有审批权限的角色编码 sys.approval.settings.taskReviewerRoles.note=具有审批权限的角色编码,多个角色用英文逗号隔开,例如:SuperAdmin,Reviewer sys.approval.taskNotApproved=当前作业未发布或发布版本未通过审核,不允许运行,请在任务发布且发布版本通过审核后重试 sys.approval.duplicateInProcess=存在仍在进行的审批流程,请勿重复提交 + +# catalog +sys.catalog.operationOnlySupportedOnFlinkSql=仅允许对FlinkSql作业的Catalog进行操作 diff --git a/dinky-web/src/locales/en-US/pages.ts b/dinky-web/src/locales/en-US/pages.ts index 7ca95718a0..4622cb9b2c 100644 --- a/dinky-web/src/locales/en-US/pages.ts +++ b/dinky-web/src/locales/en-US/pages.ts @@ -166,6 +166,10 @@ export default { 'datastudio.sqlTask.flinkJar.args.tip': 'Please enter the program running parameters (args)', 'datastudio.sqlTask.flinkJar.allowNonRestoredState': 'Ignore undeclared state (allowNonRestoredState)', + 'datastudio.project.delete.table': 'Drop [{catalog}.{database}.{table}]', + 'datastudio.project.delete.table.confirm': + 'Drop statement will be called to delete the table. \nPlease operate with caution! This operation is irreversible!!! \n\t\t\t\tConfirm to delete?', + /** * * devops diff --git a/dinky-web/src/locales/zh-CN/pages.ts b/dinky-web/src/locales/zh-CN/pages.ts index 65c9608f1c..2f19a368f7 100644 --- a/dinky-web/src/locales/zh-CN/pages.ts +++ b/dinky-web/src/locales/zh-CN/pages.ts @@ -152,6 +152,9 @@ export default { 'datastudio.sqlTask.flinkJar.args': '程序运行参数(args)', 'datastudio.sqlTask.flinkJar.args.tip': '请输入程序运行参数(args)', 'datastudio.sqlTask.flinkJar.allowNonRestoredState': '忽略未声明状态(allowNonRestoredState)', + 'datastudio.project.delete.table': '删除 [{catalog}.{database}.{table}]', + 'datastudio.project.delete.table.confirm': + '此操作将执行Drop语句来删除当前表单.\n\t\t\t\t请谨慎操作! 该操作不可逆!!!\n\t\t\t\t\t确认删除吗?', /** * * devops diff --git a/dinky-web/src/pages/DataStudio/Toolbar/Catalog/RightContext.tsx b/dinky-web/src/pages/DataStudio/Toolbar/Catalog/RightContext.tsx new file mode 100644 index 0000000000..5bad7f635c --- /dev/null +++ b/dinky-web/src/pages/DataStudio/Toolbar/Catalog/RightContext.tsx @@ -0,0 +1,140 @@ +/* + * + * 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 { MenuInfo } from 'rc-menu/es/interface'; +import React, { useEffect, useState } from 'react'; +import { InitProjectState } from '@/types/Studio/init.d'; +import { ProjectState } from '@/types/Studio/state'; +import { Modal, Typography } from 'antd'; +import { RightContextMenuState } from '@/pages/DataStudio/data.d'; +import { InitContextMenuPosition } from '@/pages/DataStudio/function'; +import RightContextMenu from '@/pages/DataStudio/RightContextMenu'; +import { l } from '@/utils/intl'; +import { dropMSTable } from "@/pages/DataStudio/Toolbar/Catalog/service"; +import { CatalogState } from "@/pages/DataStudio/Toolbar/Catalog/data"; +import { TABLE_RIGHT_MENU } from "@/pages/DataStudio/Toolbar/Catalog/constant"; + +const {Text} = Typography; +export type RightContextProps = { + refreshMetaStoreTables: () => Promise; + catalogState: CatalogState | undefined; +}; +export const useRightContext = (props: RightContextProps) => { + const { + refreshMetaStoreTables, + catalogState: CatalogState + } = props; + + const [rightContextMenuState, setRightContextMenuState] = useState({ + show: false, + position: InitContextMenuPosition + }); + const [projectState, setProjectState] = useState(InitProjectState); + useEffect(() => { + setProjectState((prevState) => ({ + ...prevState, + menuItems: [] + })); + }, [projectState.isCut, projectState.cutId]); + /** + * the right click event + * @param info + */ + const handleProjectRightClick = (info: any) => { + const { + node: {isLeaf, key, fullInfo, isTable, isView}, + node, + event + } = info; + setProjectState((prevState) => ({ + ...prevState, + isLeaf: isLeaf, + menuItems: isTable || isView ? TABLE_RIGHT_MENU() : [], + contextMenuOpen: true, + rightClickedNode: {...node, ...fullInfo}, + value: fullInfo + })); + }; + + const handleContextCancel = () => { + setProjectState((prevState) => ({ + ...prevState, + contextMenuOpen: false + })); + }; + + const handleDeleteSubmit = async () => { + const {name, type, key: table, catalog, schema: database} = projectState.rightClickedNode; + const {envId, dialect} = props.catalogState; + + handleContextCancel(); + console.log(projectState.rightClickedNode); + console.log(props.catalogState) + Modal.confirm({ + title: l('datastudio.project.delete.table', '', {catalog, database, table}), + width: '30%', + content: ( + + {l('datastudio.project.delete.table.confirm')} + + ), + okText: l('button.confirm'), + cancelText: l('button.cancel'), + onOk: async () => { + await dropMSTable({ + envId, + catalog, + database, + table, + dialect + }); + await refreshMetaStoreTables(); + } + }); + }; + const handleMenuClick = async (node: MenuInfo) => { + setProjectState((prevState) => ({...prevState, rightActiveKey: node.key})); + switch (node.key) { + case 'delete': + await handleDeleteSubmit(); + break + default: + handleContextCancel(); + break; + } + }; + + return { + RightContent: ( + <> + + setRightContextMenuState((prevState) => ({...prevState, show: false})) + } + items={projectState.menuItems} + onClick={handleMenuClick} + /> + + ), + setRightContextMenuState, + handleProjectRightClick + }; +}; diff --git a/dinky-web/src/pages/DataStudio/Toolbar/Catalog/constant.tsx b/dinky-web/src/pages/DataStudio/Toolbar/Catalog/constant.tsx new file mode 100644 index 0000000000..83b8e39fd4 --- /dev/null +++ b/dinky-web/src/pages/DataStudio/Toolbar/Catalog/constant.tsx @@ -0,0 +1,30 @@ +/* + * + * 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 { MenuItemType } from "antd/es/menu/interface"; +import { DeleteTwoTone } from "@ant-design/icons"; +import { l } from "@/utils/intl"; + +export const TABLE_RIGHT_MENU = (): MenuItemType[] => [ + { + key: 'delete', + icon: , + label: l('button.delete') + } +]; diff --git a/dinky-web/src/pages/DataStudio/Toolbar/Catalog/data.tsx b/dinky-web/src/pages/DataStudio/Toolbar/Catalog/data.tsx index 74b29281e1..82cd28ee0e 100644 --- a/dinky-web/src/pages/DataStudio/Toolbar/Catalog/data.tsx +++ b/dinky-web/src/pages/DataStudio/Toolbar/Catalog/data.tsx @@ -35,3 +35,17 @@ export type TableDataNode = { isTable: boolean; } & DataNode & DataSources.Table; + +export type ViewDataNode = { + isView: boolean; + schema: string; + catalog: string; +} & DataNode; + +export type CatalogState = { + envId?: number; + databaseId?: number; + dialect?: string; + fragment?: boolean; + engine?: string; +}; diff --git a/dinky-web/src/pages/DataStudio/Toolbar/Catalog/index.tsx b/dinky-web/src/pages/DataStudio/Toolbar/Catalog/index.tsx index 780cdc030c..2608644ecc 100644 --- a/dinky-web/src/pages/DataStudio/Toolbar/Catalog/index.tsx +++ b/dinky-web/src/pages/DataStudio/Toolbar/Catalog/index.tsx @@ -38,17 +38,12 @@ import { useAsyncEffect } from 'ahooks'; import { CenterTab, DataStudioState } from '@/pages/DataStudio/model'; import { mapDispatchToProps } from '@/pages/DataStudio/DvaFunction'; import { isSql } from '@/pages/DataStudio/utils'; -import { TableDataNode } from '@/pages/DataStudio/Toolbar/Catalog/data'; +import { CatalogState, TableDataNode, ViewDataNode } from '@/pages/DataStudio/Toolbar/Catalog/data'; import { DataStudioActionType } from '@/pages/DataStudio/data.d'; import Search from "antd/es/input/Search"; - -type CatalogState = { - envId?: number; - databaseId?: number; - dialect?: string; - fragment?: boolean; - engine?: string; -}; +import { useRightContext } from "@/pages/DataStudio/Toolbar/Catalog/RightContext"; +import { handleRightClick } from "@/pages/DataStudio/function"; +import type { Key } from 'rc-tree/lib/interface'; const Catalog = (props: { tabs: CenterTab[]; @@ -67,6 +62,8 @@ const Catalog = (props: { const [loading, setLoading] = useState(false); const [currentState, setCurrentState] = useState(); const [searchValue, setSearchValue] = useState(''); + const [selectKeys, setSelectKeys] = useState([]); + const [expandKeys, setExpandKeys] = useState([]) const currentData = tabs.find((tab) => activeTab == tab.id); @@ -206,14 +203,17 @@ const Catalog = (props: { children: tablesData }); - const viewsData: DataNode[] = []; + const viewsData: ViewDataNode[] = []; if (res.views) { for (let i = 0; i < res.views.length; i++) { viewsData.push({ title: res.views[i], key: res.views[i], icon: , - isLeaf: true + isLeaf: true, + isView: true, + schema: databaseTmp, + catalog: catalog }); } } @@ -353,6 +353,26 @@ const Catalog = (props: { [searchValue] ); + const onSelect = (keys, info: any) => { + setSelectKeys(keys); + openColumnInfo(info.node); + }; + + const onExpand = (keys, info: any) => { + setExpandKeys(keys); + }; + + const { RightContent, setRightContextMenuState, handleProjectRightClick } = useRightContext({ + refreshMetaStoreTables, + catalogState: currentState + }); + + const rightContextMenuHandle = (e: any) => handleRightClick(e, setRightContextMenuState); + + const onRightClick = (info: any) => { + handleProjectRightClick(info); + }; + // ; return ( @@ -383,8 +403,12 @@ const Catalog = (props: { switcherIcon={} className={'treeList'} treeData={buildCatalogTree(treeData, searchValue)} - onRightClick={({ node }: any) => openColumnInfo(node)} - onSelect={(_, info: any) => openColumnInfo(info.node)} + onRightClick={onRightClick} + onContextMenu={rightContextMenuHandle} + onSelect={onSelect} + onExpand={onExpand} + selectedKeys={selectKeys} + expandedKeys={expandKeys} /> ) : ( @@ -411,6 +435,7 @@ const Catalog = (props: { > + {RightContent} ); }; diff --git a/dinky-web/src/pages/DataStudio/Toolbar/Catalog/service.tsx b/dinky-web/src/pages/DataStudio/Toolbar/Catalog/service.tsx index c7f424513c..2bded17fdc 100644 --- a/dinky-web/src/pages/DataStudio/Toolbar/Catalog/service.tsx +++ b/dinky-web/src/pages/DataStudio/Toolbar/Catalog/service.tsx @@ -20,6 +20,8 @@ import { postAll } from '@/services/api'; import { API_CONSTANTS } from '@/services/endpoints'; import { StudioMetaStoreParam } from '@/pages/DataStudio/Toolbar/Catalog/data'; +import { handleOption } from "@/services/BusinessCrud"; +import { l } from "@/utils/intl"; export async function getMSSchemaInfo(params: StudioMetaStoreParam) { return (await postAll(API_CONSTANTS.STUDIO_GET_MSSCHEMA_INFO, params)).data; @@ -30,3 +32,6 @@ export async function getMSCatalogs(params: StudioMetaStoreParam) { export async function getMSColumns(params: StudioMetaStoreParam) { return (await postAll(API_CONSTANTS.STUDIO_GET_MSCOLUMNS, params)).data; } +export async function dropMSTable(params: StudioMetaStoreParam) { + return (await handleOption(API_CONSTANTS.STUDIO_DROP_MSTABLE, l('right.menu.delete'), params)) +} diff --git a/dinky-web/src/services/endpoints.tsx b/dinky-web/src/services/endpoints.tsx index 5a89046467..decf9ae0ef 100644 --- a/dinky-web/src/services/endpoints.tsx +++ b/dinky-web/src/services/endpoints.tsx @@ -218,6 +218,7 @@ export enum API_CONSTANTS { STUDIO_GET_MSSCHEMA_INFO = '/api/studio/getMSSchemaInfo', STUDIO_GET_MSCATALOGS = '/api/studio/getMSCatalogs', STUDIO_GET_MSCOLUMNS = '/api/studio/getMSColumns', + STUDIO_DROP_MSTABLE = '/api/studio/dropMSTable', // ------------------------------------ savepoints ------------------------------------ GET_SAVEPOINT_LIST_BY_TASK_ID = '/api/savepoints/listSavepointsByTaskId',