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..065d63a43e 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; @@ -209,6 +211,22 @@ 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(); + return FlinkTableMetadataUtil.dropTable(customTableEnvironment, catalogName, database, tableName); + } + } + private JobManager getJobManager(StudioMetaStoreDTO studioMetaStoreDTO, String envSql) { JobManager jobManager = jobManagerCache.get(envSql, () -> { JobConfig config = studioMetaStoreDTO.getJobConfig(); diff --git a/dinky-client/dinky-client-base/src/main/java/org/dinky/utils/FlinkTableMetadataUtil.java b/dinky-client/dinky-client-base/src/main/java/org/dinky/utils/FlinkTableMetadataUtil.java index 7863e78176..6ef3dfda3b 100644 --- a/dinky-client/dinky-client-base/src/main/java/org/dinky/utils/FlinkTableMetadataUtil.java +++ b/dinky-client/dinky-client-base/src/main/java/org/dinky/utils/FlinkTableMetadataUtil.java @@ -29,6 +29,7 @@ import org.apache.flink.table.catalog.CatalogBaseTable; import org.apache.flink.table.catalog.ObjectIdentifier; import org.apache.flink.table.catalog.ObjectPath; +import org.apache.flink.table.catalog.exceptions.TableNotExistException; import org.apache.flink.table.types.logical.DecimalType; import org.apache.flink.table.types.logical.LogicalType; import org.apache.flink.table.types.logical.TimestampType; @@ -37,6 +38,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Optional; import java.util.concurrent.atomic.AtomicBoolean; public class FlinkTableMetadataUtil { @@ -131,4 +133,19 @@ public static List getColumnList( }); return columns; } + + public static boolean dropTable( + CustomTableEnvironment customTableEnvironment, String catalogName, String database, String tableName) { + Optional catalogOptional = + customTableEnvironment.getCatalogManager().getCatalog(catalogName); + if (catalogOptional.isPresent()) { + try { + catalogOptional.get().dropTable(new ObjectPath(database, tableName), true); + return true; + } catch (TableNotExistException e) { + return false; + } + } + return false; + } } 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..f582562544 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.catalog.delete.table': 'Drop [{catalog}.{database}.{table}]', + 'datastudio.catalog.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..ef91753a72 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.catalog.delete.table': '删除 [{catalog}.{database}.{table}]', + 'datastudio.catalog.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..1d361bae9c --- /dev/null +++ b/dinky-web/src/pages/DataStudio/Toolbar/Catalog/RightContext.tsx @@ -0,0 +1,137 @@ +/* + * + * 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 { InitCatalogTreeState } from '@/types/Studio/init.d'; +import { CatalogTreeState } 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 + } = props; + + const [rightContextMenuState, setRightContextMenuState] = useState({ + show: false, + position: InitContextMenuPosition + }); + const [catalogTreeState, setCatalogTreeState] = useState(InitCatalogTreeState); + useEffect(() => { + setCatalogTreeState((prevState) => ({ + ...prevState, + menuItems: [] + })); + }, []); + /** + * the right click event + * @param info + */ + const handleCatalogRightClick = (info: any) => { + const { + node: {isLeaf, fullInfo, isTable, isView}, + node + } = info; + setCatalogTreeState((prevState) => ({ + ...prevState, + isLeaf: isLeaf, + menuItems: isTable || isView ? TABLE_RIGHT_MENU() : [], + contextMenuOpen: true, + rightClickedNode: {...node, ...fullInfo}, + value: fullInfo + })); + }; + + const handleContextCancel = () => { + setCatalogTreeState((prevState) => ({ + ...prevState, + contextMenuOpen: false + })); + }; + + const handleDeleteSubmit = async () => { + const { key: table, catalog, schema: database } = catalogTreeState.rightClickedNode; + const { envId, dialect } = catalogState ?? {}; + + handleContextCancel(); + Modal.confirm({ + title: l('datastudio.catalog.delete.table', '', {catalog, database, table}), + width: '30%', + content: ( + + {l('datastudio.catalog.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) => { + setCatalogTreeState((prevState) => ({...prevState, rightActiveKey: node.key})); + switch (node.key) { + case 'delete': + await handleDeleteSubmit(); + break + default: + handleContextCancel(); + break; + } + }; + + return { + RightContent: ( + <> + + setRightContextMenuState((prevState) => ({...prevState, show: false})) + } + items={catalogTreeState.menuItems} + onClick={handleMenuClick} + /> + + ), + setRightContextMenuState, + handleCatalogRightClick + }; +}; 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..dfeda9dc90 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: Key[], info: any) => { + setSelectKeys(keys); + openColumnInfo(info.node); + }; + + const onExpand = (keys: Key[], info: any) => { + setExpandKeys(keys); + }; + + const { RightContent, setRightContextMenuState, handleCatalogRightClick } = useRightContext({ + refreshMetaStoreTables, + catalogState: currentState + }); + + const rightContextMenuHandle = (e: any) => handleRightClick(e, setRightContextMenuState); + + const onRightClick = (info: any) => { + handleCatalogRightClick(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', diff --git a/dinky-web/src/types/Studio/init.d.ts b/dinky-web/src/types/Studio/init.d.ts index 1fa0d0165f..9960812e15 100644 --- a/dinky-web/src/types/Studio/init.d.ts +++ b/dinky-web/src/types/Studio/init.d.ts @@ -19,7 +19,7 @@ import { InitContextMenuPosition } from '@/types/Public/state.d'; import { PushDolphinParams } from '@/types/Studio/data'; -import { CateLogState, ProjectState } from '@/types/Studio/state.d'; +import { CatalogTreeState, CateLogState, ProjectState } from '@/types/Studio/state.d'; export const InitProjectState: ProjectState = { rightActiveKey: '', @@ -69,3 +69,13 @@ export const InitCateLogState: CateLogState = { loading: false, columnData: [] }; + +export const InitCatalogTreeState: CatalogTreeState = { + rightActiveKey: '', + contextMenuPosition: InitContextMenuPosition, + contextMenuOpen: false, + menuItems: [], + isLeaf: false, + rightClickedNode: {}, + value: {} +}; diff --git a/dinky-web/src/types/Studio/state.d.ts b/dinky-web/src/types/Studio/state.d.ts index 2c123a1610..c98a5aaa2d 100644 --- a/dinky-web/src/types/Studio/state.d.ts +++ b/dinky-web/src/types/Studio/state.d.ts @@ -35,3 +35,13 @@ export interface ProjectState { isCut: boolean; value: any; } + +export interface CatalogTreeState { + rightActiveKey: string; + contextMenuPosition: ContextMenuPosition; + contextMenuOpen: boolean; + menuItems: MenuItemType[]; + isLeaf: boolean; + rightClickedNode?: any; + value: any; +}