From e8566818b0a65f6ee436c2270df600a389839052 Mon Sep 17 00:00:00 2001 From: xiangzihao <460888207@qq.com> Date: Wed, 26 Jun 2024 21:08:48 +0800 Subject: [PATCH] [DSIP-50][UDF Manage] Remove unused udf manage function (#16212) * Remove unused udf manage function --- .github/workflows/e2e.yml | 4 - docs/configs/docsdev.js | 8 - docs/docs/en/architecture/metadata.md | 2 - docs/docs/en/architecture/task-structure.md | 2 - .../en/contribute/frontend-development.md | 3 - docs/docs/en/guide/resource/configuration.md | 2 +- docs/docs/en/guide/resource/intro.md | 2 +- docs/docs/en/guide/resource/udf-manage.md | 45 -- docs/docs/en/guide/security/security.md | 6 +- docs/docs/en/guide/task/sql.md | 7 +- docs/docs/en/guide/upgrade/incompatible.md | 4 + docs/docs/zh/architecture/metadata.md | 2 - docs/docs/zh/architecture/task-structure.md | 2 - .../zh/contribute/frontend-development.md | 3 - docs/docs/zh/guide/resource/configuration.md | 2 +- docs/docs/zh/guide/resource/intro.md | 2 +- docs/docs/zh/guide/resource/udf-manage.md | 46 -- docs/docs/zh/guide/security/security.md | 6 +- docs/docs/zh/guide/task/sql.md | 2 - docs/docs/zh/guide/upgrade/incompatible.md | 4 + docs/img/new_ui/dev/resource/create-udf.png | Bin 80525 -> 0 bytes .../new_ui/dev/resource/demo/udf-demo01.png | Bin 16514 -> 0 bytes .../new_ui/dev/resource/demo/udf-demo02.png | Bin 83500 -> 0 bytes .../new_ui/dev/resource/demo/udf-demo03.png | Bin 135663 -> 0 bytes .../api/audit/OperatorUtils.java | 13 +- .../audit/constants/AuditLogConstants.java | 1 - .../api/audit/enums/AuditType.java | 11 - .../impl/UdfFunctionAuditOperatorImpl.java | 46 -- .../ApiFuncIdentificationConstant.java | 12 - .../api/controller/ResourcesController.java | 193 --------- .../api/controller/UsersController.java | 86 ++-- .../dolphinscheduler/api/enums/Status.java | 15 +- .../ResourcePermissionCheckServiceImpl.java | 30 +- .../api/service/UdfFuncService.java | 118 ------ .../api/service/UsersService.java | 10 - .../api/service/impl/BaseServiceImpl.java | 4 +- .../api/service/impl/UdfFuncServiceImpl.java | 399 ------------------ .../api/service/impl/UsersServiceImpl.java | 72 +--- .../main/resources/i18n/messages.properties | 16 +- .../resources/i18n/messages_en_US.properties | 16 +- .../resources/i18n/messages_zh_CN.properties | 16 +- .../controller/ResourcesControllerTest.java | 178 -------- .../api/controller/UsersControllerTest.java | 18 - .../UdfFuncPermissionCheckTest.java | 94 ----- .../api/service/UdfFuncServiceTest.java | 306 -------------- .../api/service/UsersServiceTest.java | 31 -- .../common/constants/Constants.java | 6 +- .../common/enums/AuditModelType.java | 3 - .../common/enums/AuthorizationType.java | 1 - .../common/enums/UdfType.java | 59 --- .../dolphinscheduler/dao/entity/UDFUser.java | 61 --- .../dolphinscheduler/dao/entity/UdfFunc.java | 157 ------- .../dao/mapper/DataSourceMapper.java | 4 +- .../dao/mapper/UDFUserMapper.java | 44 -- .../dao/mapper/UdfFuncMapper.java | 118 ------ .../dao/mapper/UDFUserMapper.xml | 29 -- .../dao/mapper/UdfFuncMapper.xml | 189 --------- .../dao/entity/UdfFuncTest.java | 50 --- .../dao/mapper/UDFUserMapperTest.java | 184 -------- .../dao/mapper/UdfFuncMapperTest.java | 280 ------------ .../e2e/cases/FunctionManageE2ETest.java | 188 --------- .../e2e/cases/UdfManageE2ETest.java | 229 ---------- .../pages/resource/FunctionManagePage.java | 201 --------- .../e2e/pages/resource/ResourcePage.java | 22 - .../e2e/pages/resource/UdfManagePage.java | 197 --------- .../runner/TaskExecutionContextFactory.java | 19 +- .../service/process/ProcessService.java | 3 - .../service/process/ProcessServiceImpl.java | 79 ++-- .../service/process/ProcessServiceTest.java | 2 +- .../spi/enums/ResourceType.java | 18 +- .../storage/api/AbstractStorageOperator.java | 5 +- .../hdfs/LocalStorageOperatorTest.java | 7 - .../task/api/SQLTaskExecutionContext.java | 16 +- .../plugin/task/api/enums/ResourceType.java | 2 +- .../plugin/task/api/enums/UdfType.java | 56 --- .../task/api/parameters/SqlParameters.java | 38 -- .../resource/AbstractResourceParameters.java | 3 +- .../resource/UdfFuncParameters.java | 133 ------ .../api/parameters/SqlParametersTest.java | 3 - .../task/remoteshell/RemoteShellTaskTest.java | 2 +- .../plugin/task/sql/SqlTask.java | 95 +---- .../resource/MigrateResourceService.java | 20 +- .../content/components/sidebar/index.tsx | 1 - .../content/components/user/use-dropdown.ts | 1 - .../src/layouts/content/use-dataList.ts | 16 - dolphinscheduler-ui/src/locales/en_US/menu.ts | 1 - .../src/locales/en_US/project.ts | 4 +- .../src/locales/en_US/resource.ts | 19 +- .../src/locales/en_US/security.ts | 4 - dolphinscheduler-ui/src/locales/zh_CN/menu.ts | 1 - .../src/locales/zh_CN/project.ts | 3 +- .../src/locales/zh_CN/resource.ts | 17 +- .../src/locales/zh_CN/security.ts | 4 - .../src/router/modules/resources.ts | 34 -- .../src/service/modules/resources/index.ts | 108 +---- .../src/service/modules/resources/types.ts | 15 +- .../src/service/modules/users/index.ts | 9 - .../src/service/modules/users/types.ts | 5 - dolphinscheduler-ui/src/service/service.ts | 1 - dolphinscheduler-ui/src/store/user/types.ts | 1 - dolphinscheduler-ui/src/store/user/user.ts | 7 - .../src/views/login/use-login.ts | 4 - .../src/views/password/use-update.ts | 1 - .../parameter/components/parameter-modal.tsx | 20 +- .../parameter/components/use-modal.ts | 2 +- .../src/views/projects/parameter/data_type.ts | 66 +-- .../src/views/projects/parameter/index.tsx | 18 +- .../task/components/node/fields/use-sql.ts | 2 - .../task/components/node/fields/use-udfs.ts | 62 --- .../task/components/node/format-data.ts | 11 - .../task/components/node/tasks/use-sql.ts | 1 - .../projects/task/components/node/types.ts | 3 - .../components/dag/dag-save-modal.tsx | 35 +- .../definition/components/start-modal.tsx | 116 ++--- .../resource/components/resource/index.tsx | 27 +- .../resource/table/table-action.tsx | 4 +- .../components/resource/table/use-table.ts | 9 +- .../resource/components/resource/types.ts | 2 +- .../function/components/function-modal.tsx | 284 ------------- .../udf/function/components/use-form.ts | 113 ----- .../udf/function/components/use-modal.ts | 175 -------- .../resource/udf/function/index.module.scss | 43 -- .../src/views/resource/udf/function/index.tsx | 159 ------- .../src/views/resource/udf/function/types.ts | 39 -- .../views/resource/udf/function/use-table.ts | 215 ---------- .../src/views/resource/udf/resource/index.tsx | 27 -- .../components/authorize-modal.tsx | 48 --- .../user-manage/components/use-authorize.ts | 142 +------ .../src/views/security/user-manage/types.ts | 2 - .../views/security/user-manage/use-columns.ts | 5 - 130 files changed, 282 insertions(+), 5966 deletions(-) delete mode 100644 docs/docs/en/guide/resource/udf-manage.md delete mode 100644 docs/docs/zh/guide/resource/udf-manage.md delete mode 100644 docs/img/new_ui/dev/resource/create-udf.png delete mode 100644 docs/img/new_ui/dev/resource/demo/udf-demo01.png delete mode 100644 docs/img/new_ui/dev/resource/demo/udf-demo02.png delete mode 100644 docs/img/new_ui/dev/resource/demo/udf-demo03.png delete mode 100644 dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/audit/operator/impl/UdfFunctionAuditOperatorImpl.java delete mode 100644 dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/UdfFuncService.java delete mode 100644 dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/UdfFuncServiceImpl.java delete mode 100644 dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/permission/UdfFuncPermissionCheckTest.java delete mode 100644 dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/UdfFuncServiceTest.java delete mode 100644 dolphinscheduler-common/src/main/java/org/apache/dolphinscheduler/common/enums/UdfType.java delete mode 100644 dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/entity/UDFUser.java delete mode 100644 dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/entity/UdfFunc.java delete mode 100644 dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/mapper/UDFUserMapper.java delete mode 100644 dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/mapper/UdfFuncMapper.java delete mode 100644 dolphinscheduler-dao/src/main/resources/org/apache/dolphinscheduler/dao/mapper/UDFUserMapper.xml delete mode 100644 dolphinscheduler-dao/src/main/resources/org/apache/dolphinscheduler/dao/mapper/UdfFuncMapper.xml delete mode 100644 dolphinscheduler-dao/src/test/java/org/apache/dolphinscheduler/dao/entity/UdfFuncTest.java delete mode 100644 dolphinscheduler-dao/src/test/java/org/apache/dolphinscheduler/dao/mapper/UDFUserMapperTest.java delete mode 100644 dolphinscheduler-dao/src/test/java/org/apache/dolphinscheduler/dao/mapper/UdfFuncMapperTest.java delete mode 100644 dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/FunctionManageE2ETest.java delete mode 100644 dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/UdfManageE2ETest.java delete mode 100644 dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/resource/FunctionManagePage.java delete mode 100644 dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/resource/UdfManagePage.java delete mode 100644 dolphinscheduler-task-plugin/dolphinscheduler-task-api/src/main/java/org/apache/dolphinscheduler/plugin/task/api/enums/UdfType.java delete mode 100644 dolphinscheduler-task-plugin/dolphinscheduler-task-api/src/main/java/org/apache/dolphinscheduler/plugin/task/api/parameters/resource/UdfFuncParameters.java delete mode 100644 dolphinscheduler-ui/src/views/projects/task/components/node/fields/use-udfs.ts delete mode 100644 dolphinscheduler-ui/src/views/resource/udf/function/components/function-modal.tsx delete mode 100644 dolphinscheduler-ui/src/views/resource/udf/function/components/use-form.ts delete mode 100644 dolphinscheduler-ui/src/views/resource/udf/function/components/use-modal.ts delete mode 100644 dolphinscheduler-ui/src/views/resource/udf/function/index.module.scss delete mode 100644 dolphinscheduler-ui/src/views/resource/udf/function/index.tsx delete mode 100644 dolphinscheduler-ui/src/views/resource/udf/function/types.ts delete mode 100644 dolphinscheduler-ui/src/views/resource/udf/function/use-table.ts delete mode 100644 dolphinscheduler-ui/src/views/resource/udf/resource/index.tsx diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 60a2f44865..5601dbbb3f 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -114,10 +114,6 @@ jobs: # class: org.apache.dolphinscheduler.e2e.cases.WorkflowSwitchE2ETest - name: FileManage class: org.apache.dolphinscheduler.e2e.cases.FileManageE2ETest - - name: UdfManage - class: org.apache.dolphinscheduler.e2e.cases.UdfManageE2ETest - - name: FunctionManage - class: org.apache.dolphinscheduler.e2e.cases.FunctionManageE2ETest - name: MysqlDataSource class: org.apache.dolphinscheduler.e2e.cases.MysqlDataSourceE2ETest - name: ClickhouseDataSource diff --git a/docs/configs/docsdev.js b/docs/configs/docsdev.js index 323e8b21d9..8039aa41f3 100644 --- a/docs/configs/docsdev.js +++ b/docs/configs/docsdev.js @@ -432,10 +432,6 @@ export default { title: 'File Manage', link: '/en-us/docs/dev/user_doc/guide/resource/file-manage.html' }, - { - title: 'UDF Manage', - link: '/en-us/docs/dev/user_doc/guide/resource/udf-manage.html' - }, { title: 'Task Group Manage', link: '/en-us/docs/dev/user_doc/guide/resource/task-group.html' @@ -1153,10 +1149,6 @@ export default { title: '文件管理', link: '/zh-cn/docs/dev/user_doc/guide/resource/file-manage.html' }, - { - title: 'UDF 管理', - link: '/zh-cn/docs/dev/user_doc/guide/resource/udf-manage.html' - }, { title: '任务组管理', link: '/zh-cn/docs/dev/user_doc/guide/resource/task-group.html' diff --git a/docs/docs/en/architecture/metadata.md b/docs/docs/en/architecture/metadata.md index b4633707f5..d2d11a8323 100644 --- a/docs/docs/en/architecture/metadata.md +++ b/docs/docs/en/architecture/metadata.md @@ -22,8 +22,6 @@ see sql files in `dolphinscheduler/dolphinscheduler-dao/src/main/resources/sql` - User can have multiple projects, user project authorization completes the relationship binding using `project_id` and `user_id` in `t_ds_relation_project_user` table. - The `user_id` in the `t_ds_projcet` table represents the user who create the project, and the `user_id` in the `t_ds_relation_project_user` table represents users who have permission to the project. -- The `user_id` in the `t_ds_resources` table represents the user who create the resource, and the `user_id` in `t_ds_relation_resources_user` represents the user who has permissions to the resource. -- The `user_id` in the `t_ds_udfs` table represents the user who create the UDF, and the `user_id` in the `t_ds_relation_udfs_user` table represents a user who has permission to the UDF. ### Project - Tenant - ProcessDefinition - Schedule diff --git a/docs/docs/en/architecture/task-structure.md b/docs/docs/en/architecture/task-structure.md index dc0b9d520a..041d753403 100644 --- a/docs/docs/en/architecture/task-structure.md +++ b/docs/docs/en/architecture/task-structure.md @@ -146,7 +146,6 @@ No.|parameter name||type|description |note 5| |type |String |database type 6| |datasource |Int |datasource id 7| |sql |String |query SQL statement -8| |udfs | String| udf functions|specify UDF function ids, separate by comma 9| |sqlType | String| SQL node type |0 for query and 1 for none-query SQL 10| |title |String | mail title 11| |receivers |String |receivers @@ -180,7 +179,6 @@ No.|parameter name||type|description |note "type":"MYSQL", "datasource":1, "sql":"select id , namge , age from emp where id = ${id}", - "udfs":"", "sqlType":"0", "title":"xxxx@xxx.com", "receivers":"xxxx@xxx.com", diff --git a/docs/docs/en/contribute/frontend-development.md b/docs/docs/en/contribute/frontend-development.md index 50f8c91fb4..47276caeaf 100644 --- a/docs/docs/en/contribute/frontend-development.md +++ b/docs/docs/en/contribute/frontend-development.md @@ -163,9 +163,6 @@ Resource Management => `http://localhost:8888/#/resource/file` ``` | File Management -| udf Management - - Resource Management - - Function management ``` Data Source Management => `http://localhost:8888/#/datasource/list` diff --git a/docs/docs/en/guide/resource/configuration.md b/docs/docs/en/guide/resource/configuration.md index 1c5b389302..4ff2ee9d8d 100644 --- a/docs/docs/en/guide/resource/configuration.md +++ b/docs/docs/en/guide/resource/configuration.md @@ -1,6 +1,6 @@ # Resource Center Configuration -- You could use `Resource Center` to upload text files, UDFs and other task-related files. +- You could use `Resource Center` to upload text files and other task-related files. - You could configure `Resource Center` to use distributed file system like [Hadoop](https://hadoop.apache.org/docs/r2.7.0/) (2.6+), [MinIO](https://github.com/minio/minio) cluster or remote storage products like [AWS S3](https://aws.amazon.com/s3/), [Alibaba Cloud OSS](https://www.aliyun.com/product/oss), [Huawei Cloud OBS](https://support.huaweicloud.com/obs/index.html) etc. - You could configure `Resource Center` to use local file system. If you deploy `DolphinScheduler` in `Standalone` mode, you could configure it to use local file system for `Resource Center` without the need of an external `HDFS` system or `S3`. - Furthermore, if you deploy `DolphinScheduler` in `Cluster` mode, you could use [S3FS-FUSE](https://github.com/s3fs-fuse/s3fs-fuse) to mount `S3` or [JINDO-FUSE](https://help.aliyun.com/document_detail/187410.html) to mount `OSS` to your machines and use the local file system for `Resource Center`. In this way, you could operate remote files as if on your local machines. diff --git a/docs/docs/en/guide/resource/intro.md b/docs/docs/en/guide/resource/intro.md index 786d71e834..fbb6c2cf43 100644 --- a/docs/docs/en/guide/resource/intro.md +++ b/docs/docs/en/guide/resource/intro.md @@ -1,5 +1,5 @@ # Resource Center Introduction -The Resource Center is typically used for uploading files, UDF functions, and task group management. For a stand-alone +The Resource Center is typically used for uploading files and task group management. For a stand-alone environment, you can select the local file directory as the upload folder (**this operation does not require Hadoop or HDFS deployment**). Of course, you can also choose to upload to Hadoop or MinIO cluster. In this case, you need to have Hadoop (2.6+) or MinIO and other related environments. diff --git a/docs/docs/en/guide/resource/udf-manage.md b/docs/docs/en/guide/resource/udf-manage.md deleted file mode 100644 index c7235900cc..0000000000 --- a/docs/docs/en/guide/resource/udf-manage.md +++ /dev/null @@ -1,45 +0,0 @@ -# UDF Manage - -## Resource Management - -- The resource management and file management functions are similar. The difference is that the resource management is the UDF upload function, and the file management uploads the user programs, scripts and configuration files. -- It mainly includes the following operations: rename, download, delete, etc. -- Upload UDF resources: Same as uploading files. - -## Function Management - -### Create UDF function - -Click `Create UDF Function`, enter the UDF function parameters, select the UDF resource, and click `Submit` to create the UDF function. -Currently only temporary UDF functions for HIVE are supported. - -- UDF function name: Enter the name of the UDF function. -- Package name Class name: Enter the full path of the UDF function. -- UDF resource: Set the resource file corresponding to the created UDF function. - -![create-udf](../../../../img/new_ui/dev/resource/create-udf.png) - -## Example - -### Write UDF functions - -Users can customize the desired UDF function according to actual production requirements. Here's a function that appends "HelloWorld" to the end of any string. As shown below: - -![code-udf](../../../../img/new_ui/dev/resource/demo/udf-demo01.png) - -### Configure the UDF function - -Before configuring UDF functions, you need to upload the required function jar package through resource management. Then enter the function management and configure the relevant information. As shown below: - -![conf-udf](../../../../img/new_ui/dev/resource/demo/udf-demo02.png) - -### Use UDF functions - -In the process of using UDF functions, users only need to pay attention to the specific function writing, and upload the configuration through the resource center. The system will automatically configure the create function statement, refer to the following: [SqlTask](https://github.com/apache/dolphinscheduler/blob/923f3f38e3271d7f1d22b3abc3497cecb6957e4a/dolphinscheduler-task-plugin/dolphinscheduler-task-sql/src/main/java/org/apache/dolphinscheduler/plugin/task/sql/SqlTask.java#L507-L531) - -Enter the workflow to define an SQL node, the data source type is HIVE, and the data source instance type is HIVE/IMPALA. - -- SQL statement: `select HwUdf("abc");` This function is used in the same way as the built-in functions, and can be accessed directly using the function name. -- UDF function: Select the one configured for the resource center. - -![use-udf](../../../../img/new_ui/dev/resource/demo/udf-demo03.png) diff --git a/docs/docs/en/guide/security/security.md b/docs/docs/en/guide/security/security.md index d892571b29..646243a964 100644 --- a/docs/docs/en/guide/security/security.md +++ b/docs/docs/en/guide/security/security.md @@ -103,8 +103,8 @@ public void doPOSTParam()throws Exception{ ## Granted Permissions -* Granted permissions include project permissions, resource permissions, data source permissions, and UDF function permissions. -* Administrators can authorize projects, resources, data sources, and UDF functions that ordinary users do not create. Because the authorization methods of projects, resources, data sources and UDF functions are all the same, the project authorization is used as an example to introduce. +* Granted permissions include project permissions, resource permissions, data source permissions. +* Administrators can authorize projects, resources, data sources that ordinary users do not create. Because the authorization methods of projects, resources, data sources are all the same, the project authorization is used as an example to introduce. * Note: For projects created by the user, the user has all permissions. Therefore, permission changes to projects created by users themselves are not valid. - The administrator enters the `Security Center -> User Management` page, and clicks the "Authorize" button of the user to be authorized, as shown in the following figure: @@ -118,7 +118,7 @@ public void doPOSTParam()throws Exception{ ![no-permission-error](../../../../img/new_ui/dev/security/no-permission-error.png) -- Resources, data sources, and UDF function authorization are the same as project authorization. +- Resources, data sources authorization are the same as project authorization. ## Worker Grouping diff --git a/docs/docs/en/guide/task/sql.md b/docs/docs/en/guide/task/sql.md index 0cf2d98f5e..08f4e92392 100644 --- a/docs/docs/en/guide/task/sql.md +++ b/docs/docs/en/guide/task/sql.md @@ -21,12 +21,11 @@ Refer to [datasource-setting](../howto/datasource-setting.md) `DataSource Center - Please refer to [DolphinScheduler Task Parameters Appendix](appendix.md) `Default Task Parameters` section for default parameters. | **Parameter** | **Description** | -|-------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +|-------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---| | Data source | Select the corresponding DataSource. | | SQL type | Supports query and non-query. | | SQL parameter | The input parameter format is `key1=value1;key2=value2...`. | -| SQL statement | SQL statement. | -| UDF function | For Hive DataSources, you can refer to UDF functions created in the resource center, but other DataSource do not support UDF functions. | +| SQL statement | SQL statement. | | | Custom parameters | SQL task type, and stored procedure is a custom parameter order, to set customized parameter type and data type for the method is the same as the stored procedure task type. The difference is that the custom parameter of the SQL task type replaces the `${variable}` in the SQL statement. | | Pre-SQL | Pre-SQL executes before the SQL statement. | | Post-SQL | Post-SQL executes after the SQL statement. | @@ -57,5 +56,3 @@ Table created in the Pre-SQL, after use in the SQL statement, cleaned in the Pos ## Note Pay attention to the selection of SQL type. If it is an insert operation, need to change to "Non-Query" type. - -To compatible with long session,UDF function are created by the syntax(CREATE OR REPLACE) diff --git a/docs/docs/en/guide/upgrade/incompatible.md b/docs/docs/en/guide/upgrade/incompatible.md index f45af712c3..a134cd5fd3 100644 --- a/docs/docs/en/guide/upgrade/incompatible.md +++ b/docs/docs/en/guide/upgrade/incompatible.md @@ -26,3 +26,7 @@ This document records the incompatible updates between each version. You need to * Add required field `database` in /datasources/tables && /datasources/tableColumns Api [#14406](https://github.com/apache/dolphinscheduler/pull/14406) +## 3.3.0 + +* Remove the `udf-manage` function from the `resource center` ([#16209]) + diff --git a/docs/docs/zh/architecture/metadata.md b/docs/docs/zh/architecture/metadata.md index 7f82a73609..14999e0b55 100644 --- a/docs/docs/zh/architecture/metadata.md +++ b/docs/docs/zh/architecture/metadata.md @@ -20,8 +20,6 @@ - 一个用户可以有多个项目,用户项目授权通过`t_ds_relation_project_user`表完成project_id和user_id的关系绑定;
- `t_ds_projcet`表中的`user_id`表示创建该项目的用户,`t_ds_relation_project_user`表中的`user_id`表示对项目有权限的用户;
-- `t_ds_resources`表中的`user_id`表示创建该资源的用户,`t_ds_relation_resources_user`中的`user_id`表示对资源有权限的用户;
-- `t_ds_udfs`表中的`user_id`表示创建该UDF的用户,`t_ds_relation_udfs_user`表中的`user_id`表示对UDF有权限的用户;
### 项目 - 租户 - 工作流定义 - 定时 diff --git a/docs/docs/zh/architecture/task-structure.md b/docs/docs/zh/architecture/task-structure.md index aad8454a19..656e57eed7 100644 --- a/docs/docs/zh/architecture/task-structure.md +++ b/docs/docs/zh/architecture/task-structure.md @@ -145,7 +145,6 @@ 5| |type |String | 数据库类型 6| |datasource |Int | 数据源id 7| |sql |String | 查询SQL语句 -8| |udfs | String| udf函数|UDF函数id,以逗号分隔. 9| |sqlType | String| SQL节点类型 |0 查询 , 1 非查询 10| |title |String | 邮件标题 11| |receivers |String | 收件人 @@ -179,7 +178,6 @@ "type":"MYSQL", "datasource":1, "sql":"select id , namge , age from emp where id = ${id}", - "udfs":"", "sqlType":"0", "title":"xxxx@xxx.com", "receivers":"xxxx@xxx.com", diff --git a/docs/docs/zh/contribute/frontend-development.md b/docs/docs/zh/contribute/frontend-development.md index 249b17d546..46d2e9c82d 100644 --- a/docs/docs/zh/contribute/frontend-development.md +++ b/docs/docs/zh/contribute/frontend-development.md @@ -163,9 +163,6 @@ npm install node-sass --unsafe-perm #单独安装node-sass依赖 ``` | 文件管理 -| UDF管理 - - 资源管理 - - 函数管理 ``` 数据源管理 => `http://localhost:8888/#/datasource/list` diff --git a/docs/docs/zh/guide/resource/configuration.md b/docs/docs/zh/guide/resource/configuration.md index 735c75da97..62c8cf135e 100644 --- a/docs/docs/zh/guide/resource/configuration.md +++ b/docs/docs/zh/guide/resource/configuration.md @@ -1,6 +1,6 @@ # 资源中心配置详情 -- 资源中心通常用于上传文件、UDF 函数,以及任务组管理等操作。 +- 资源中心通常用于上传文件以及任务组管理等操作。 - 资源中心可以对接分布式的文件存储系统,如[Hadoop](https://hadoop.apache.org/docs/r2.7.0/)(2.6+)或者[MinIO](https://github.com/minio/minio)集群,也可以对接远端的对象存储,如[AWS S3](https://aws.amazon.com/s3/)或者[阿里云 OSS](https://www.aliyun.com/product/oss),[华为云 OBS](https://support.huaweicloud.com/obs/index.html) 等。 - 资源中心也可以直接对接本地文件系统。在单机模式下,您无需依赖`Hadoop`或`S3`一类的外部存储系统,可以方便地对接本地文件系统进行体验。 - 除此之外,对于集群模式下的部署,您可以通过使用[S3FS-FUSE](https://github.com/s3fs-fuse/s3fs-fuse)将`S3`挂载到本地,或者使用[JINDO-FUSE](https://help.aliyun.com/document_detail/187410.html)将`OSS`挂载到本地等,再用资源中心对接本地文件系统方式来操作远端对象存储中的文件。 diff --git a/docs/docs/zh/guide/resource/intro.md b/docs/docs/zh/guide/resource/intro.md index fd691ec32c..f045cc8328 100644 --- a/docs/docs/zh/guide/resource/intro.md +++ b/docs/docs/zh/guide/resource/intro.md @@ -1,4 +1,4 @@ # 资源中心简介 -资源中心通常用于上传文件、UDF 函数和任务组管理。 对于 standalone 环境,可以选择本地文件目录作为上传文件夹(此操作不需要Hadoop部署)。当然,你也可以 +资源中心通常用于上传文件和任务组管理。 对于 standalone 环境,可以选择本地文件目录作为上传文件夹(此操作不需要Hadoop部署)。当然,你也可以 选择上传到 Hadoop 或者 MinIO 集群。 在这种情况下,您需要有 Hadoop(2.6+)或 MinIO 等相关环境。 diff --git a/docs/docs/zh/guide/resource/udf-manage.md b/docs/docs/zh/guide/resource/udf-manage.md deleted file mode 100644 index cc7c77a0ab..0000000000 --- a/docs/docs/zh/guide/resource/udf-manage.md +++ /dev/null @@ -1,46 +0,0 @@ -# UDF 管理 - -- 资源管理和文件管理功能类似,不同之处是资源管理是上传的 UDF 函数,文件管理上传的是用户程序,脚本及配置文件。 -- 主要包括以下操作:重命名、下载、删除等。 -* 上传 UDF 资源 - -> 和上传文件相同。 - -## 函数管理 - -* 创建 UDF 函数 - - > 点击“创建 UDF 函数”,输入 UDF 函数参数,选择udf资源,点击“提交”,创建 UDF 函数。 - > 目前只支持 HIVE 的临时 UDF 函数 - -- UDF 函数名称:输入 UDF 函数时的名称 -- 包名类名:输入 UDF 函数的全路径 -- UDF 资源:设置创建的 UDF 对应的资源文件 - -![create-udf](../../../../img/new_ui/dev/resource/create-udf.png) - -## 任务样例 - -### 编写 UDF 函数 - -用户可以根据实际生产需求,自定义想要的 UDF 函数。这里编写一个在任意字符串的末尾添加 "HelloWorld" 的函数。如下图所示: - -![code-udf](../../../../img/new_ui/dev/resource/demo/udf-demo01.png) - -### 配置 UDF 函数 - -配置 UDF 函数前,需要先通过资源管理上传所需的函数 jar 包。然后进入函数管理,配置相关信息即可。如下图所示: - -![conf-udf](../../../../img/new_ui/dev/resource/demo/udf-demo02.png) - -### 使用 UDF 函数 - -在使用 UDF 函数过程中,用户只需关注具体的函数编写,通过资源中心上传配置完成即可。系统会自动配置 create function 语句,参考如下:[SqlTask](https://github.com/apache/dolphinscheduler/blob/923f3f38e3271d7f1d22b3abc3497cecb6957e4a/dolphinscheduler-task-plugin/dolphinscheduler-task-sql/src/main/java/org/apache/dolphinscheduler/plugin/task/sql/SqlTask.java#L507-L531) - -进入工作流定义一个 SQL 节点,数据源类型选择为 HIVE,数据源实例类型为 HIVE/IMPALA。 - -- SQL 语句:`select HwUdf("abc");` 该函数与内置函数使用方式一样,直接使用函数名称即可访问。 -- UDF 函数:选择资源中心所配置的即可。 - -![use-udf](../../../../img/new_ui/dev/resource/demo/udf-demo03.png) - diff --git a/docs/docs/zh/guide/security/security.md b/docs/docs/zh/guide/security/security.md index 5004d69e0e..a48954cfec 100644 --- a/docs/docs/zh/guide/security/security.md +++ b/docs/docs/zh/guide/security/security.md @@ -97,8 +97,8 @@ ## 授予权限 -* 授予权限包括项目权限,资源权限,数据源权限,UDF函数权限,k8s命名空间。 -* 管理员可以对普通用户进行非其创建的项目、资源、数据源、UDF函数、k8s命名空间。因为项目、资源、数据源、UDF函数、k8s命名空间授权方式都是一样的,所以以项目授权为例介绍。 +* 授予权限包括项目权限,数据源权限和k8s命名空间。 +* 管理员可以对普通用户进行非其创建的项目、数据源k8s命名空间。因为项目、数据源、k8s命名空间授权方式都是一样的,所以以项目授权为例介绍。 * 注意:对于用户自己创建的项目,该用户默认拥有所有的权限,因此对用户自己创建的项目进行权限变更是无效的。 - 管理员进入`安全中心->用户管理页面`,点击需授权用户的“授权”按钮,如下图所示: @@ -112,7 +112,7 @@ ![no-permission-error](../../../../img/new_ui/dev/security/no-permission-error.png) -- 资源、数据源、UDF 函数授权同项目授权。 +- 数据源授权同项目授权。 ## Worker 分组 diff --git a/docs/docs/zh/guide/task/sql.md b/docs/docs/zh/guide/task/sql.md index b71f44e416..2c97130c96 100644 --- a/docs/docs/zh/guide/task/sql.md +++ b/docs/docs/zh/guide/task/sql.md @@ -26,7 +26,6 @@ SQL任务类型,用于连接数据库并执行相应SQL。 - 默认采用`;\n`作为SQL分隔符,拆分成多段SQL语句执行。Hive的JDBC不支持一次执行多段SQL语句,请不要使用`;\n`。 - sql参数:输入参数格式为key1=value1;key2=value2… - sql语句:SQL语句 -- UDF函数:对于HIVE类型的数据源,可以引用资源中心中创建的UDF函数,其他类型的数据源暂不支持UDF函数。 - 自定义参数:SQL任务类型,而存储过程是自定义参数顺序,给方法设置值自定义参数类型和数据类型,同存储过程任务类型一样。区别在于SQL任务类型自定义参数会替换sql语句中${变量}。 - 前置sql:前置sql在sql语句之前执行。 - 后置sql:后置sql在sql语句之后执行。 @@ -57,5 +56,4 @@ SQL任务类型,用于连接数据库并执行相应SQL。 ## 注意事项 * 注意SQL类型的选择,如果是INSERT等操作需要选择非查询类型。 -* 为了兼容长会话情况,UDF函数的创建是通过CREATE OR REPLACE语句 diff --git a/docs/docs/zh/guide/upgrade/incompatible.md b/docs/docs/zh/guide/upgrade/incompatible.md index dd47fa8de8..b25d35b1e3 100644 --- a/docs/docs/zh/guide/upgrade/incompatible.md +++ b/docs/docs/zh/guide/upgrade/incompatible.md @@ -24,3 +24,7 @@ * 在 /datasources/tables && /datasources/tableColumns 接口中添加了必选字段`database` [#14406](https://github.com/apache/dolphinscheduler/pull/14406) +## 3.3.0 + +* 从 `资源中心` 中移除了 `udf-manage` 功能 ([#16209]) + diff --git a/docs/img/new_ui/dev/resource/create-udf.png b/docs/img/new_ui/dev/resource/create-udf.png deleted file mode 100644 index f570b7f84db020cb5ce75b4d8625ba0703b9af11..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 80525 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 diff --git a/docs/img/new_ui/dev/resource/demo/udf-demo01.png b/docs/img/new_ui/dev/resource/demo/udf-demo01.png deleted file mode 100644 index d1e0ee5c60811baf09a65991d18126ecaf613cd1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16514 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 diff --git a/docs/img/new_ui/dev/resource/demo/udf-demo02.png b/docs/img/new_ui/dev/resource/demo/udf-demo02.png deleted file mode 100644 index f20a247aa22a79379cda4a2ecfe6ace3bfc1ea10..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 83500 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 YZEa2IymB-KGU&Z`@7+b)apcUu0PgC&82|tP diff --git a/docs/img/new_ui/dev/resource/demo/udf-demo03.png b/docs/img/new_ui/dev/resource/demo/udf-demo03.png deleted file mode 100644 index 49c932b395f39fb30017d3c38c5cf2f75bde0d57..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 135663 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

bzZ+}>%T?A)J0H1R%} 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

<$h`1Awc9<3499>zjIWxDf_SR27p-2Fo2 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 {}OoYOYIz07Fa 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 paramsMap) { - ResourceType resourceType = (ResourceType) paramsMap.get(Constants.STRING_PLUGIN_PARAM_TYPE); - return resourceType != null && resourceType.equals(ResourceType.UDF); - } - public static boolean isFolder(String name) { return name != null && name.endsWith("/"); } public static String getFileAuditObject(AuditType auditType, Map paramsMap, String name) { - boolean isUdfResource = isUdfResource(paramsMap); boolean isFolder = auditType == AuditType.FOLDER_CREATE || isFolder(name); - if (isUdfResource) { - return isFolder ? AuditModelType.UDF_FOLDER.getName() : AuditModelType.UDF_FILE.getName(); - } else { - return isFolder ? AuditModelType.FOLDER.getName() : AuditModelType.FILE.getName(); - } + return isFolder ? AuditModelType.FOLDER.getName() : AuditModelType.FILE.getName(); } } diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/audit/constants/AuditLogConstants.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/audit/constants/AuditLogConstants.java index 67df788f1e..f0f749aea7 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/audit/constants/AuditLogConstants.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/audit/constants/AuditLogConstants.java @@ -43,6 +43,5 @@ public final class AuditLogConstants { public static final String FILE_NAME = "fileName"; public static final String FULL_NAME = "fullName"; public static final String FUNC_NAME = "funcName"; - public static final String UDF_FUNC_ID = "udfFuncId"; } diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/audit/enums/AuditType.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/audit/enums/AuditType.java index cba510180e..59cca4279f 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/audit/enums/AuditType.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/audit/enums/AuditType.java @@ -24,7 +24,6 @@ import static org.apache.dolphinscheduler.api.audit.constants.AuditLogConstants. import static org.apache.dolphinscheduler.api.audit.constants.AuditLogConstants.ENVIRONMENT_CODE; import static org.apache.dolphinscheduler.api.audit.constants.AuditLogConstants.FILE_NAME; import static org.apache.dolphinscheduler.api.audit.constants.AuditLogConstants.FULL_NAME; -import static org.apache.dolphinscheduler.api.audit.constants.AuditLogConstants.FUNC_NAME; import static org.apache.dolphinscheduler.api.audit.constants.AuditLogConstants.ID; import static org.apache.dolphinscheduler.api.audit.constants.AuditLogConstants.NAME; import static org.apache.dolphinscheduler.api.audit.constants.AuditLogConstants.PRIORITY; @@ -34,7 +33,6 @@ import static org.apache.dolphinscheduler.api.audit.constants.AuditLogConstants. import static org.apache.dolphinscheduler.api.audit.constants.AuditLogConstants.PROCESS_INSTANCE_IDS; import static org.apache.dolphinscheduler.api.audit.constants.AuditLogConstants.QUEUE_ID; import static org.apache.dolphinscheduler.api.audit.constants.AuditLogConstants.TYPE; -import static org.apache.dolphinscheduler.api.audit.constants.AuditLogConstants.UDF_FUNC_ID; import static org.apache.dolphinscheduler.api.audit.constants.AuditLogConstants.USER_ID; import static org.apache.dolphinscheduler.api.audit.constants.AuditLogConstants.VERSION; import static org.apache.dolphinscheduler.api.audit.constants.AuditLogConstants.WORKFLOW_DEFINITION_CODE; @@ -55,7 +53,6 @@ import static org.apache.dolphinscheduler.common.enums.AuditModelType.TASK_GROUP import static org.apache.dolphinscheduler.common.enums.AuditModelType.TASK_INSTANCE; import static org.apache.dolphinscheduler.common.enums.AuditModelType.TENANT; import static org.apache.dolphinscheduler.common.enums.AuditModelType.TOKEN; -import static org.apache.dolphinscheduler.common.enums.AuditModelType.UDF_FUNCTION; import static org.apache.dolphinscheduler.common.enums.AuditModelType.USER; import static org.apache.dolphinscheduler.common.enums.AuditModelType.WORKER_GROUP; import static org.apache.dolphinscheduler.common.enums.AuditModelType.YARN_QUEUE; @@ -96,7 +93,6 @@ import org.apache.dolphinscheduler.api.audit.operator.impl.TaskGroupAuditOperato import org.apache.dolphinscheduler.api.audit.operator.impl.TaskInstancesAuditOperatorImpl; import org.apache.dolphinscheduler.api.audit.operator.impl.TenantAuditOperatorImpl; import org.apache.dolphinscheduler.api.audit.operator.impl.TokenAuditOperatorImpl; -import org.apache.dolphinscheduler.api.audit.operator.impl.UdfFunctionAuditOperatorImpl; import org.apache.dolphinscheduler.api.audit.operator.impl.UserAuditOperatorImpl; import org.apache.dolphinscheduler.api.audit.operator.impl.WorkerGroupAuditOperatorImpl; import org.apache.dolphinscheduler.api.audit.operator.impl.YarnQueueAuditOperatorImpl; @@ -163,13 +159,6 @@ public enum AuditType { FILE_UPDATE(FILE, UPDATE, ResourceAuditOperatorImpl.class, new String[]{TYPE, FILE_NAME, ALIAS}, new String[]{}), FILE_DELETE(FILE, DELETE, ResourceAuditOperatorImpl.class, new String[]{FULL_NAME}, new String[]{}), - UDF_FUNCTION_CREATE(UDF_FUNCTION, CREATE, UdfFunctionAuditOperatorImpl.class, new String[]{FUNC_NAME}, - new String[]{}), - UDF_FUNCTION_UPDATE(UDF_FUNCTION, UPDATE, UdfFunctionAuditOperatorImpl.class, new String[]{FUNC_NAME}, - new String[]{}), - UDF_FUNCTION_DELETE(UDF_FUNCTION, DELETE, UdfFunctionAuditOperatorImpl.class, new String[]{UDF_FUNC_ID}, - new String[]{}), - TASK_GROUP_CREATE(TASK_GROUP, CREATE, TaskGroupAuditOperatorImpl.class, new String[]{NAME}, new String[]{}), TASK_GROUP_UPDATE(TASK_GROUP, UPDATE, TaskGroupAuditOperatorImpl.class, new String[]{}, new String[]{ID}), TASK_GROUP_CLOSE(TASK_GROUP, CLOSE, TaskGroupAuditOperatorImpl.class, new String[]{ID}, new String[]{}), diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/audit/operator/impl/UdfFunctionAuditOperatorImpl.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/audit/operator/impl/UdfFunctionAuditOperatorImpl.java deleted file mode 100644 index 2e7b9ae274..0000000000 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/audit/operator/impl/UdfFunctionAuditOperatorImpl.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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. - */ - -package org.apache.dolphinscheduler.api.audit.operator.impl; - -import org.apache.dolphinscheduler.api.audit.operator.BaseAuditOperator; -import org.apache.dolphinscheduler.dao.entity.UdfFunc; -import org.apache.dolphinscheduler.dao.mapper.UdfFuncMapper; - -import org.apache.commons.lang3.math.NumberUtils; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -@Service -public class UdfFunctionAuditOperatorImpl extends BaseAuditOperator { - - @Autowired - private UdfFuncMapper udfFuncMapper; - - @Override - protected String getObjectNameFromIdentity(Object identity) { - int objId = NumberUtils.toInt(identity.toString(), -1); - if (objId == -1) { - return ""; - } - - UdfFunc obj = udfFuncMapper.selectUdfById(objId); - return obj == null ? "" : obj.getFuncName(); - } - -} diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/constants/ApiFuncIdentificationConstant.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/constants/ApiFuncIdentificationConstant.java index 480272bdc6..12a0bee291 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/constants/ApiFuncIdentificationConstant.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/constants/ApiFuncIdentificationConstant.java @@ -128,18 +128,6 @@ public class ApiFuncIdentificationConstant { public static final String FILE_DOWNLOAD = "resources:file:download"; public static final String FILE_DELETE = "resources:file:delete"; - public static final String UDF_FILE_VIEW = "resources:udf:view"; - public static final String UDF_FOLDER_ONLINE_CREATE = "resources:udf:create"; - public static final String UDF_UPLOAD = "resources:udf:upload"; - public static final String UDF_UPDATE = "resources:udf:edit"; - public static final String UDF_DOWNLOAD = "resources:udf:download"; - public static final String UDF_DELETE = "resources:udf:delete"; - - public static final String UDF_FUNCTION_VIEW = "resources:udf-func:view"; - public static final String UDF_FUNCTION_CREATE = "resources:udf-func:create"; - public static final String UDF_FUNCTION_UPDATE = "resources:udf-func:update"; - public static final String UDF_FUNCTION_DELETE = "resources:udf-func:delete"; - public static final String TASK_GROUP_VIEW = "resources:task-group:view"; public static final String TASK_GROUP_CREATE = "resources:task-group:create"; public static final String TASK_GROUP_CLOSE = "resources:task-group:close"; diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/controller/ResourcesController.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/controller/ResourcesController.java index 620993d61b..0c9a34ee53 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/controller/ResourcesController.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/controller/ResourcesController.java @@ -19,21 +19,14 @@ package org.apache.dolphinscheduler.api.controller; import static org.apache.dolphinscheduler.api.enums.Status.CREATE_RESOURCE_ERROR; import static org.apache.dolphinscheduler.api.enums.Status.CREATE_RESOURCE_FILE_ON_LINE_ERROR; -import static org.apache.dolphinscheduler.api.enums.Status.CREATE_UDF_FUNCTION_ERROR; import static org.apache.dolphinscheduler.api.enums.Status.DELETE_RESOURCE_ERROR; -import static org.apache.dolphinscheduler.api.enums.Status.DELETE_UDF_FUNCTION_ERROR; import static org.apache.dolphinscheduler.api.enums.Status.DOWNLOAD_RESOURCE_FILE_ERROR; import static org.apache.dolphinscheduler.api.enums.Status.EDIT_RESOURCE_FILE_ON_LINE_ERROR; -import static org.apache.dolphinscheduler.api.enums.Status.QUERY_DATASOURCE_BY_TYPE_ERROR; import static org.apache.dolphinscheduler.api.enums.Status.QUERY_RESOURCES_LIST_ERROR; import static org.apache.dolphinscheduler.api.enums.Status.QUERY_RESOURCES_LIST_PAGING; -import static org.apache.dolphinscheduler.api.enums.Status.QUERY_UDF_FUNCTION_LIST_PAGING_ERROR; import static org.apache.dolphinscheduler.api.enums.Status.RESOURCE_NOT_EXIST; import static org.apache.dolphinscheduler.api.enums.Status.UPDATE_RESOURCE_ERROR; -import static org.apache.dolphinscheduler.api.enums.Status.UPDATE_UDF_FUNCTION_ERROR; -import static org.apache.dolphinscheduler.api.enums.Status.VERIFY_UDF_FUNCTION_NAME_ERROR; import static org.apache.dolphinscheduler.api.enums.Status.VIEW_RESOURCE_FILE_ON_LINE_ERROR; -import static org.apache.dolphinscheduler.api.enums.Status.VIEW_UDF_FUNCTION_ERROR; import org.apache.dolphinscheduler.api.audit.OperatorLog; import org.apache.dolphinscheduler.api.audit.enums.AuditType; @@ -51,13 +44,11 @@ import org.apache.dolphinscheduler.api.dto.resources.UpdateFileFromContentReques import org.apache.dolphinscheduler.api.dto.resources.UpdateFileRequest; import org.apache.dolphinscheduler.api.exceptions.ApiException; import org.apache.dolphinscheduler.api.service.ResourcesService; -import org.apache.dolphinscheduler.api.service.UdfFuncService; import org.apache.dolphinscheduler.api.utils.PageInfo; import org.apache.dolphinscheduler.api.utils.Result; import org.apache.dolphinscheduler.api.vo.ResourceItemVO; import org.apache.dolphinscheduler.api.vo.resources.FetchFileContentResponse; import org.apache.dolphinscheduler.common.constants.Constants; -import org.apache.dolphinscheduler.common.enums.UdfType; import org.apache.dolphinscheduler.dao.entity.User; import org.apache.dolphinscheduler.plugin.task.api.utils.ParameterUtils; import org.apache.dolphinscheduler.spi.enums.ResourceType; @@ -74,7 +65,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestAttribute; @@ -102,9 +92,6 @@ public class ResourcesController extends BaseController { @Autowired private ResourcesService resourceService; - @Autowired - private UdfFuncService udfFuncService; - @Operation(summary = "createDirectory", description = "CREATE_RESOURCE_NOTES") @Parameters({ @Parameter(name = "type", description = "RESOURCE_TYPE", required = true, schema = @Schema(implementation = ResourceType.class)), @@ -273,19 +260,6 @@ public class ResourcesController extends BaseController { return Result.success(resourceService.pagingResourceItem(pagingResourceItemRequest)); } - // todo: this api is used for udf, we should remove it - @Operation(summary = "queryResourceList", description = "QUERY_RESOURCE_LIST_NOTES") - @Parameters({ - @Parameter(name = "type", description = "RESOURCE_TYPE", required = true, schema = @Schema(implementation = ResourceType.class)), - @Parameter(name = "fullName", description = "RESOURCE_FULLNAME", required = true, schema = @Schema(implementation = String.class))}) - @GetMapping(value = "/list") - @ResponseStatus(HttpStatus.OK) - @ApiException(QUERY_RESOURCES_LIST_ERROR) - public Result> queryResourceList(@Parameter(hidden = true) @RequestAttribute(value = Constants.SESSION_USER) User loginUser, - @RequestParam(value = "type") ResourceType type) { - return Result.success(resourceService.queryResourceFiles(loginUser, type)); - } - @Operation(summary = "deleteResource", description = "DELETE_RESOURCE_BY_ID_NOTES") @Parameters({ @Parameter(name = "fullName", description = "RESOURCE_FULLNAME", required = true, schema = @Schema(implementation = String.class, example = "file:////tmp/dolphinscheduler/storage/default/resources/demo.sql")) @@ -354,173 +328,6 @@ public class ResourcesController extends BaseController { resourceService.downloadResource(response, downloadFileRequest); } - @Operation(summary = "createUdfFunc", description = "CREATE_UDF_FUNCTION_NOTES") - @Parameters({ - @Parameter(name = "type", description = "UDF_TYPE", required = true, schema = @Schema(implementation = UdfType.class)), - @Parameter(name = "funcName", description = "FUNC_NAME", required = true, schema = @Schema(implementation = String.class)), - @Parameter(name = "className", description = "CLASS_NAME", required = true, schema = @Schema(implementation = String.class)), - @Parameter(name = "argTypes", description = "ARG_TYPES", schema = @Schema(implementation = String.class)), - @Parameter(name = "database", description = "DATABASE_NAME", schema = @Schema(implementation = String.class)), - @Parameter(name = "description", description = "UDF_DESC", schema = @Schema(implementation = String.class)), - @Parameter(name = "resourceId", description = "RESOURCE_ID", required = true, schema = @Schema(implementation = int.class, example = "100")) - - }) - @PostMapping(value = "/udf-func") - @ResponseStatus(HttpStatus.CREATED) - @ApiException(CREATE_UDF_FUNCTION_ERROR) - @OperatorLog(auditType = AuditType.UDF_FUNCTION_CREATE) - public Result createUdfFunc(@Parameter(hidden = true) @RequestAttribute(value = Constants.SESSION_USER) User loginUser, - @RequestParam(value = "type") UdfType type, - @RequestParam(value = "funcName") String funcName, - @RequestParam(value = "className") String className, - @RequestParam(value = "fullName") String fullName, - @RequestParam(value = "argTypes", required = false) String argTypes, - @RequestParam(value = "database", required = false) String database, - @RequestParam(value = "description", required = false) String description) { - // todo verify the sourceName - return udfFuncService.createUdfFunction(loginUser, funcName, className, fullName, argTypes, database, - description, type); - } - - /** - * view udf function - * - * @param loginUser login user - * @param id udf function id - * @return udf function detail - */ - @Operation(summary = "viewUIUdfFunction", description = "VIEW_UDF_FUNCTION_NOTES") - @Parameters({ - @Parameter(name = "id", description = "RESOURCE_ID", required = true, schema = @Schema(implementation = int.class, example = "100")) - - }) - @GetMapping(value = "/{id}/udf-func") - @ResponseStatus(HttpStatus.OK) - @ApiException(VIEW_UDF_FUNCTION_ERROR) - public Result viewUIUdfFunction(@Parameter(hidden = true) @RequestAttribute(value = Constants.SESSION_USER) User loginUser, - @PathVariable("id") int id) { - return udfFuncService.queryUdfFuncDetail(loginUser, id); - } - - /** - * update udf function - * - * @param loginUser login user - * @param type resource type - * @param funcName function name - * @param argTypes argument types - * @param database data base - * @param description description - * @param className class name - * @param udfFuncId udf function id - * @return update result code - */ - @Operation(summary = "updateUdfFunc", description = "UPDATE_UDF_FUNCTION_NOTES") - @Parameters({ - @Parameter(name = "id", description = "UDF_ID", required = true, schema = @Schema(implementation = int.class)), - @Parameter(name = "type", description = "UDF_TYPE", required = true, schema = @Schema(implementation = UdfType.class)), - @Parameter(name = "funcName", description = "FUNC_NAME", required = true, schema = @Schema(implementation = String.class)), - @Parameter(name = "className", description = "CLASS_NAME", required = true, schema = @Schema(implementation = String.class)), - @Parameter(name = "argTypes", description = "ARG_TYPES", schema = @Schema(implementation = String.class)), - @Parameter(name = "database", description = "DATABASE_NAME", schema = @Schema(implementation = String.class)), - @Parameter(name = "description", description = "UDF_DESC", schema = @Schema(implementation = String.class))}) - @PutMapping(value = "/udf-func/{id}") - @ApiException(UPDATE_UDF_FUNCTION_ERROR) - @OperatorLog(auditType = AuditType.UDF_FUNCTION_UPDATE) - public Result updateUdfFunc(@Parameter(hidden = true) @RequestAttribute(value = Constants.SESSION_USER) User loginUser, - @PathVariable(value = "id") int udfFuncId, @RequestParam(value = "type") UdfType type, - @RequestParam(value = "funcName") String funcName, - @RequestParam(value = "className") String className, - @RequestParam(value = "argTypes", required = false) String argTypes, - @RequestParam(value = "database", required = false) String database, - @RequestParam(value = "description", required = false) String description, - @RequestParam(value = "fullName") String fullName) { - return udfFuncService.updateUdfFunc(loginUser, udfFuncId, funcName, className, argTypes, database, description, - type, fullName); - } - - /** - * query udf function list paging - * - * @param loginUser login user - * @param searchVal search value - * @param pageNo page number - * @param pageSize page size - * @return udf function list page - */ - @Operation(summary = "queryUdfFuncListPaging", description = "QUERY_UDF_FUNCTION_LIST_PAGING_NOTES") - @Parameters({ - @Parameter(name = "searchVal", description = "SEARCH_VAL", schema = @Schema(implementation = String.class)), - @Parameter(name = "pageNo", description = "PAGE_NO", required = true, schema = @Schema(implementation = int.class, example = "1")), - @Parameter(name = "pageSize", description = "PAGE_SIZE", required = true, schema = @Schema(implementation = int.class, example = "20"))}) - @GetMapping(value = "/udf-func") - @ResponseStatus(HttpStatus.OK) - @ApiException(QUERY_UDF_FUNCTION_LIST_PAGING_ERROR) - public Result queryUdfFuncListPaging(@Parameter(hidden = true) @RequestAttribute(value = Constants.SESSION_USER) User loginUser, - @RequestParam("pageNo") Integer pageNo, - @RequestParam(value = "searchVal", required = false) String searchVal, - @RequestParam("pageSize") Integer pageSize) { - checkPageParams(pageNo, pageSize); - return udfFuncService.queryUdfFuncListPaging(loginUser, searchVal, pageNo, pageSize); - } - - /** - * query udf func list by type - * - * @param loginUser login user - * @param type resource type - * @return resource list - */ - @Operation(summary = "queryUdfFuncList", description = "QUERY_UDF_FUNC_LIST_NOTES") - @Parameters({ - @Parameter(name = "type", description = "UDF_TYPE", required = true, schema = @Schema(implementation = UdfType.class))}) - @GetMapping(value = "/udf-func/list") - @ResponseStatus(HttpStatus.OK) - @ApiException(QUERY_DATASOURCE_BY_TYPE_ERROR) - public Result queryUdfFuncList(@Parameter(hidden = true) @RequestAttribute(value = Constants.SESSION_USER) User loginUser, - @RequestParam("type") UdfType type) { - return udfFuncService.queryUdfFuncList(loginUser, type.getCode()); - } - - /** - * verify udf function name can use or not - * - * @param loginUser login user - * @param name name - * @return true if the name can user, otherwise return false - */ - @Operation(summary = "verifyUdfFuncName", description = "VERIFY_UDF_FUNCTION_NAME_NOTES") - @Parameters({ - @Parameter(name = "name", description = "FUNC_NAME", required = true, schema = @Schema(implementation = String.class)) - - }) - @GetMapping(value = "/udf-func/verify-name") - @ResponseStatus(HttpStatus.OK) - @ApiException(VERIFY_UDF_FUNCTION_NAME_ERROR) - public Result verifyUdfFuncName(@Parameter(hidden = true) @RequestAttribute(value = Constants.SESSION_USER) User loginUser, - @RequestParam(value = "name") String name) { - return udfFuncService.verifyUdfFuncByName(loginUser, name); - } - - /** - * delete udf function - * - * @param loginUser login user - * @param udfFuncId udf function id - * @return delete result code - */ - @Operation(summary = "deleteUdfFunc", description = "DELETE_UDF_FUNCTION_NOTES") - @Parameters({ - @Parameter(name = "id", description = "UDF_FUNC_ID", required = true, schema = @Schema(implementation = int.class, example = "100"))}) - @DeleteMapping(value = "/udf-func/{id}") - @ResponseStatus(HttpStatus.OK) - @ApiException(DELETE_UDF_FUNCTION_ERROR) - @OperatorLog(auditType = AuditType.UDF_FUNCTION_DELETE) - public Result deleteUdfFunc(@Parameter(hidden = true) @RequestAttribute(value = Constants.SESSION_USER) User loginUser, - @PathVariable(value = "id") int udfFuncId) { - return udfFuncService.delete(loginUser, udfFuncId); - } - @Operation(summary = "queryResourceBaseDir", description = "QUERY_RESOURCE_BASE_DIR") @Parameters({ @Parameter(name = "type", description = "RESOURCE_TYPE", required = true, schema = @Schema(implementation = ResourceType.class))}) diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/controller/UsersController.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/controller/UsersController.java index ac940dde55..546ec675c9 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/controller/UsersController.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/controller/UsersController.java @@ -24,7 +24,6 @@ import static org.apache.dolphinscheduler.api.enums.Status.GET_USER_INFO_ERROR; import static org.apache.dolphinscheduler.api.enums.Status.GRANT_DATASOURCE_ERROR; import static org.apache.dolphinscheduler.api.enums.Status.GRANT_K8S_NAMESPACE_ERROR; import static org.apache.dolphinscheduler.api.enums.Status.GRANT_PROJECT_ERROR; -import static org.apache.dolphinscheduler.api.enums.Status.GRANT_UDF_FUNCTION_ERROR; import static org.apache.dolphinscheduler.api.enums.Status.QUERY_USER_LIST_PAGING_ERROR; import static org.apache.dolphinscheduler.api.enums.Status.REVOKE_PROJECT_ERROR; import static org.apache.dolphinscheduler.api.enums.Status.UNAUTHORIZED_USER_ERROR; @@ -112,9 +111,9 @@ public class UsersController extends BaseController { * query user list paging * * @param loginUser login user - * @param pageNo page number + * @param pageNo page number * @param searchVal search avlue - * @param pageSize page size + * @param pageSize page size * @return user list page */ @Operation(summary = "queryUserList", description = "QUERY_USER_LIST_NOTES") @@ -138,14 +137,14 @@ public class UsersController extends BaseController { /** * update user * - * @param loginUser login user - * @param id user id - * @param userName user name + * @param loginUser login user + * @param id user id + * @param userName user name * @param userPassword user password - * @param email email - * @param tenantId tennat id - * @param phone phone - * @param queue queue + * @param email email + * @param tenantId tennat id + * @param phone phone + * @param queue queue * @return update result code */ @Operation(summary = "updateUser", description = "UPDATE_USER_NOTES") @@ -190,7 +189,7 @@ public class UsersController extends BaseController { * delete user by id * * @param loginUser login user - * @param id user id + * @param id user id * @return delete result code */ @Operation(summary = "delUserById", description = "DELETE_USER_BY_ID_NOTES") @@ -210,8 +209,8 @@ public class UsersController extends BaseController { /** * revoke project By Id * - * @param loginUser login user - * @param userId user id + * @param loginUser login user + * @param userId user id * @param projectIds project id array * @return revoke result code */ @@ -233,8 +232,8 @@ public class UsersController extends BaseController { /** * grant project with read permission * - * @param loginUser login user - * @param userId user id + * @param loginUser login user + * @param userId user id * @param projectIds project id array * @return grant result code */ @@ -256,8 +255,8 @@ public class UsersController extends BaseController { /** * grant project * - * @param loginUser login user - * @param userId user id + * @param loginUser login user + * @param userId user id * @param projectIds project id array * @return grant result code */ @@ -279,8 +278,8 @@ public class UsersController extends BaseController { /** * grant project by code * - * @param loginUser login user - * @param userId user id + * @param loginUser login user + * @param userId user id * @param projectCode project code * @return grant result code */ @@ -302,9 +301,9 @@ public class UsersController extends BaseController { /** * revoke project * - * @param loginUser login user - * @param userId user id - * @param projectCode project code + * @param loginUser login user + * @param userId user id + * @param projectCode project code * @return revoke result code */ @Operation(summary = "revokeProject", description = "REVOKE_PROJECT_NOTES") @@ -322,34 +321,11 @@ public class UsersController extends BaseController { return returnDataList(result); } - /** - * grant udf function - * - * @param loginUser login user - * @param userId user id - * @param udfIds udf id array - * @return grant result code - */ - @Operation(summary = "grantUDFFunc", description = "GRANT_UDF_FUNC_NOTES") - @Parameters({ - @Parameter(name = "userId", description = "USER_ID", required = true, schema = @Schema(implementation = int.class, example = "100")), - @Parameter(name = "udfIds", description = "UDF_IDS", required = true, schema = @Schema(implementation = String.class)) - }) - @PostMapping(value = "/grant-udf-func") - @ResponseStatus(HttpStatus.OK) - @ApiException(GRANT_UDF_FUNCTION_ERROR) - public Result grantUDFFunc(@Parameter(hidden = true) @RequestAttribute(value = Constants.SESSION_USER) User loginUser, - @RequestParam(value = "userId") int userId, - @RequestParam(value = "udfIds") String udfIds) { - Map result = usersService.grantUDFFunction(loginUser, userId, udfIds); - return returnDataList(result); - } - /** * grant namespace * - * @param loginUser login user - * @param userId user id + * @param loginUser login user + * @param userId user id * @param namespaceIds namespace id array * @return grant result code */ @@ -371,8 +347,8 @@ public class UsersController extends BaseController { /** * grant datasource * - * @param loginUser login user - * @param userId user id + * @param loginUser login user + * @param userId user id * @param datasourceIds data source id array * @return grant result code */ @@ -439,7 +415,7 @@ public class UsersController extends BaseController { * verify username * * @param loginUser login user - * @param userName user name + * @param userName user name * @return true if user name not exists, otherwise return false */ @Operation(summary = "verifyUserName", description = "VERIFY_USER_NAME_NOTES") @@ -457,7 +433,7 @@ public class UsersController extends BaseController { /** * unauthorized user * - * @param loginUser login user + * @param loginUser login user * @param alertgroupId alert group id * @return unauthorize result code */ @@ -477,7 +453,7 @@ public class UsersController extends BaseController { /** * authorized user * - * @param loginUser login user + * @param loginUser login user * @param alertgroupId alert group id * @return authorized result code */ @@ -502,10 +478,10 @@ public class UsersController extends BaseController { /** * user registry * - * @param userName user name - * @param userPassword user password + * @param userName user name + * @param userPassword user password * @param repeatPassword repeat password - * @param email user email + * @param email user email */ @Operation(summary = "registerUser", description = "REGISTER_USER_NOTES") @Parameters({ diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/enums/Status.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/enums/Status.java index 9dc12d9ba0..243b554aaf 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/enums/Status.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/enums/Status.java @@ -93,17 +93,9 @@ public enum Status { RESOURCE_FILE_IS_EMPTY(10062, "resource file is empty", "资源文件内容不能为空"), EDIT_RESOURCE_FILE_ON_LINE_ERROR(10063, "edit resource file online error", "更新资源文件错误"), DOWNLOAD_RESOURCE_FILE_ERROR(10064, "download resource file error", "下载资源文件错误"), - CREATE_UDF_FUNCTION_ERROR(10065, "create udf function error", "创建UDF函数错误"), - VIEW_UDF_FUNCTION_ERROR(10066, "view udf function error", "查询UDF函数错误"), - UPDATE_UDF_FUNCTION_ERROR(10067, "update udf function error", "更新UDF函数错误"), - QUERY_UDF_FUNCTION_LIST_PAGING_ERROR(10068, "query udf function list paging error", "分页查询UDF函数列表错误"), QUERY_DATASOURCE_BY_TYPE_ERROR(10069, "query datasource by type error", "查询数据源信息错误"), - VERIFY_UDF_FUNCTION_NAME_ERROR(10070, "verify udf function name error", "UDF函数名称验证错误"), - DELETE_UDF_FUNCTION_ERROR(10071, "delete udf function error", "删除UDF函数错误"), AUTHORIZED_FILE_RESOURCE_ERROR(10072, "authorized file resource error", "授权资源文件错误"), AUTHORIZE_RESOURCE_TREE(10073, "authorize resource tree display error", "授权资源目录树错误"), - UNAUTHORIZED_UDF_FUNCTION_ERROR(10074, "unauthorized udf function error", "查询未授权UDF函数错误"), - AUTHORIZED_UDF_FUNCTION_ERROR(10075, "authorized udf function error", "授权UDF函数错误"), CREATE_SCHEDULE_ERROR(10076, "create schedule error", "创建调度配置错误"), UPDATE_SCHEDULE_ERROR(10077, "update schedule error", "更新调度配置错误"), PUBLISH_SCHEDULE_ONLINE_ERROR(10078, "publish schedule online error", "上线调度配置错误"), @@ -124,7 +116,6 @@ public enum Status { DELETE_USER_BY_ID_ERROR(10093, "delete user by id error", "删除用户错误"), GRANT_PROJECT_ERROR(10094, "grant project error", "授权项目错误"), GRANT_RESOURCE_ERROR(10095, "grant resource error", "授权资源错误"), - GRANT_UDF_FUNCTION_ERROR(10096, "grant udf function error", "授权UDF函数错误"), GRANT_DATASOURCE_ERROR(10097, "grant datasource error", "授权数据源错误"), GET_USER_INFO_ERROR(10098, "get user info error", "获取用户信息错误"), USER_LIST_ERROR(10099, "user list error", "查询用户列表错误"), @@ -298,19 +289,17 @@ public enum Status { QUERY_PROJECT_PREFERENCE_ERROR(10302, "query project preference error", "查询项目偏好设置错误"), UPDATE_PROJECT_PREFERENCE_STATE_ERROR(10303, "Failed to update the state of the project preference", "更新项目偏好设置错误"), - UDF_FUNCTION_NOT_EXIST(20001, "UDF function not found", "UDF函数不存在"), - UDF_FUNCTION_EXISTS(20002, "UDF function already exists", "UDF函数已存在"), RESOURCE_NOT_EXIST(20004, "resource not exist", "资源不存在"), RESOURCE_EXIST(20005, "resource already exists", "资源已存在"), RESOURCE_SUFFIX_NOT_SUPPORT_VIEW(20006, "resource suffix do not support online viewing", "资源文件后缀不支持查看"), RESOURCE_SIZE_EXCEED_LIMIT(20007, "upload resource file size exceeds limit", "上传资源文件大小超过限制"), RESOURCE_SUFFIX_FORBID_CHANGE(20008, "resource suffix not allowed to be modified", "资源文件后缀不支持修改"), - UDF_RESOURCE_SUFFIX_NOT_JAR(20009, "UDF resource suffix name must be jar", "UDF资源文件后缀名只支持[jar]"), + HDFS_COPY_FAIL(20010, "hdfs copy {0} -> {1} fail", "hdfs复制失败:[{0}] -> [{1}]"), RESOURCE_FILE_EXIST(20011, "resource file {0} already exists in hdfs,please delete it or change name!", "资源文件[{0}]在hdfs中已存在,请删除或修改资源名"), RESOURCE_FILE_NOT_EXIST(20012, "resource file {0} not exists !", "资源文件[{0}]不存在"), - UDF_RESOURCE_IS_BOUND(20013, "udf resource file is bound by UDF functions:{0}", "udf函数绑定了资源文件[{0}]"), + RESOURCE_IS_USED(20014, "resource file is used by process definition", "资源文件被上线的流程定义使用了"), PARENT_RESOURCE_NOT_EXIST(20015, "parent resource not exist", "父资源文件不存在"), diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/permission/ResourcePermissionCheckServiceImpl.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/permission/ResourcePermissionCheckServiceImpl.java index 355c2257b9..50e5bd4195 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/permission/ResourcePermissionCheckServiceImpl.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/permission/ResourcePermissionCheckServiceImpl.java @@ -46,7 +46,6 @@ import org.apache.dolphinscheduler.dao.entity.Project; import org.apache.dolphinscheduler.dao.entity.Queue; import org.apache.dolphinscheduler.dao.entity.TaskGroup; import org.apache.dolphinscheduler.dao.entity.Tenant; -import org.apache.dolphinscheduler.dao.entity.UdfFunc; import org.apache.dolphinscheduler.dao.entity.User; import org.apache.dolphinscheduler.dao.entity.WorkerGroup; import org.apache.dolphinscheduler.dao.mapper.AccessTokenMapper; @@ -59,7 +58,6 @@ import org.apache.dolphinscheduler.dao.mapper.ProjectMapper; import org.apache.dolphinscheduler.dao.mapper.QueueMapper; import org.apache.dolphinscheduler.dao.mapper.TaskGroupMapper; import org.apache.dolphinscheduler.dao.mapper.TenantMapper; -import org.apache.dolphinscheduler.dao.mapper.UdfFuncMapper; import org.apache.dolphinscheduler.dao.mapper.WorkerGroupMapper; import org.apache.dolphinscheduler.service.process.ProcessService; @@ -201,32 +199,6 @@ public class ResourcePermissionCheckServiceImpl } } - @Component - public static class UdfFuncPermissionCheck implements ResourceAcquisitionAndPermissionCheck { - - private final UdfFuncMapper udfFuncMapper; - - public UdfFuncPermissionCheck(UdfFuncMapper udfFuncMapper) { - this.udfFuncMapper = udfFuncMapper; - } - - @Override - public List authorizationTypes() { - return Collections.singletonList(AuthorizationType.UDF); - } - - @Override - public Set listAuthorizedResourceIds(int userId, Logger logger) { - List udfFuncList = udfFuncMapper.listAuthorizedUdfByUserId(userId); - return udfFuncList.stream().map(UdfFunc::getId).collect(toSet()); - } - - @Override - public boolean permissionCheck(int userId, String permissionKey, Logger logger) { - return true; - } - } - @Component public static class TaskGroupPermissionCheck implements ResourceAcquisitionAndPermissionCheck { @@ -481,6 +453,7 @@ public class ResourcePermissionCheckServiceImpl /** * authorization types + * * @return */ List authorizationTypes(); @@ -495,6 +468,7 @@ public class ResourcePermissionCheckServiceImpl /** * permission check + * * @param userId * @return */ diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/UdfFuncService.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/UdfFuncService.java deleted file mode 100644 index 35be4066d8..0000000000 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/UdfFuncService.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * 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. - */ - -package org.apache.dolphinscheduler.api.service; - -import org.apache.dolphinscheduler.api.utils.Result; -import org.apache.dolphinscheduler.common.enums.UdfType; -import org.apache.dolphinscheduler.dao.entity.User; - -/** - * udf func service - */ -public interface UdfFuncService { - - /** - * create udf function - * - * @param loginUser login user - * @param type udf type - * @param funcName function name - * @param argTypes argument types - * @param database database - * @param desc description - * @param className class name - * @return create result code - */ - Result createUdfFunction(User loginUser, - String funcName, - String className, - String fullName, - String argTypes, - String database, - String desc, - UdfType type); - - /** - * query udf function - * - * @param id udf function id - * @return udf function detail - */ - Result queryUdfFuncDetail(User loginUser, int id); - - /** - * updateProcessInstance udf function - * - * @param udfFuncId udf function id - * @param type resource type - * @param funcName function name - * @param argTypes argument types - * @param database data base - * @param desc description - * @param resourceId resource id - * @param fullName resource full name - * @param className class name - * @return update result code - */ - Result updateUdfFunc(User loginUser, - int udfFuncId, - String funcName, - String className, - String argTypes, - String database, - String desc, - UdfType type, - String fullName); - - /** - * query udf function list paging - * - * @param loginUser login user - * @param pageNo page number - * @param pageSize page size - * @param searchVal search value - * @return udf function list page - */ - Result queryUdfFuncListPaging(User loginUser, String searchVal, Integer pageNo, Integer pageSize); - - /** - * query udf list - * - * @param loginUser login user - * @param type udf type - * @return udf func list - */ - Result queryUdfFuncList(User loginUser, Integer type); - - /** - * delete udf function - * - * @param id udf function id - * @return delete result code - */ - Result delete(User loginUser, int id); - - /** - * verify udf function by name - * - * @param name name - * @return true if the name can user, otherwise return false - */ - Result verifyUdfFuncByName(User loginUser, String name); - -} diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/UsersService.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/UsersService.java index 45e5964825..61f6a138ba 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/UsersService.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/UsersService.java @@ -183,16 +183,6 @@ public interface UsersService { */ Map revokeProject(User loginUser, int userId, long projectCode); - /** - * grant udf function - * - * @param loginUser login user - * @param userId user id - * @param udfIds udf id array - * @return grant result code - */ - Map grantUDFFunction(User loginUser, int userId, String udfIds); - /** * grant namespace * diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/BaseServiceImpl.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/BaseServiceImpl.java index 26dcbc5449..428b9527bc 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/BaseServiceImpl.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/BaseServiceImpl.java @@ -133,10 +133,8 @@ public class BaseServiceImpl implements BaseService { // @Override // public void createTenantDirIfNotExists(String tenantCode) throws IOException { // String resourcePath = HadoopUtils.getHdfsResDir(tenantCode); - // String udfsPath = HadoopUtils.getHdfsUdfDir(tenantCode); - // // init resource path and udf path + // // init resource path // HadoopUtils.getInstance().mkdir(tenantCode,resourcePath); - // HadoopUtils.getInstance().mkdir(tenantCode,udfsPath); // } /** diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/UdfFuncServiceImpl.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/UdfFuncServiceImpl.java deleted file mode 100644 index 7cf16b3567..0000000000 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/UdfFuncServiceImpl.java +++ /dev/null @@ -1,399 +0,0 @@ -/* - * 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. - */ - -package org.apache.dolphinscheduler.api.service.impl; - -import org.apache.dolphinscheduler.api.constants.ApiFuncIdentificationConstant; -import org.apache.dolphinscheduler.api.enums.Status; -import org.apache.dolphinscheduler.api.service.UdfFuncService; -import org.apache.dolphinscheduler.api.utils.PageInfo; -import org.apache.dolphinscheduler.api.utils.Result; -import org.apache.dolphinscheduler.common.enums.AuthorizationType; -import org.apache.dolphinscheduler.common.enums.UdfType; -import org.apache.dolphinscheduler.dao.entity.UdfFunc; -import org.apache.dolphinscheduler.dao.entity.User; -import org.apache.dolphinscheduler.dao.mapper.UDFUserMapper; -import org.apache.dolphinscheduler.dao.mapper.UdfFuncMapper; -import org.apache.dolphinscheduler.plugin.storage.api.StorageOperator; - -import org.apache.commons.lang3.StringUtils; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Date; -import java.util.List; -import java.util.Set; - -import lombok.extern.slf4j.Slf4j; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import com.baomidou.mybatisplus.core.metadata.IPage; -import com.baomidou.mybatisplus.extension.plugins.pagination.Page; - -/** - * udf func service impl - */ -@Service -@Slf4j -public class UdfFuncServiceImpl extends BaseServiceImpl implements UdfFuncService { - - @Autowired - private UdfFuncMapper udfFuncMapper; - - @Autowired - private UDFUserMapper udfUserMapper; - - @Autowired(required = false) - private StorageOperator storageOperator; - - /** - * create udf function - * - * @param loginUser login user - * @param type udf type - * @param funcName function name - * @param argTypes argument types - * @param database database - * @param desc description - * @param className class name - * @return create result code - */ - @Override - @Transactional - public Result createUdfFunction(User loginUser, - String funcName, - String className, - String fullName, - String argTypes, - String database, - String desc, - UdfType type) { - Result result = new Result<>(); - - boolean canOperatorPermissions = canOperatorPermissions(loginUser, null, AuthorizationType.UDF, - ApiFuncIdentificationConstant.UDF_FUNCTION_CREATE); - if (!canOperatorPermissions) { - putMsg(result, Status.NO_CURRENT_OPERATING_PERMISSION); - return result; - } - if (checkDescriptionLength(desc)) { - log.warn("Parameter description is too long."); - putMsg(result, Status.DESCRIPTION_TOO_LONG_ERROR); - return result; - } - - // verify udf func name exist - if (checkUdfFuncNameExists(funcName)) { - log.warn("Udf function with the same name already exists."); - putMsg(result, Status.UDF_FUNCTION_EXISTS); - return result; - } - - boolean existResource = storageOperator.exists(fullName); - - if (!existResource) { - log.error("resource full name {} is not exist", fullName); - putMsg(result, Status.RESOURCE_NOT_EXIST); - return result; - } - - // save data - UdfFunc udf = new UdfFunc(); - Date now = new Date(); - udf.setUserId(loginUser.getId()); - udf.setFuncName(funcName); - udf.setClassName(className); - if (!StringUtils.isEmpty(argTypes)) { - udf.setArgTypes(argTypes); - } - if (!StringUtils.isEmpty(database)) { - udf.setDatabase(database); - } - udf.setDescription(desc); - // set resourceId to -1 because we do not store resource to db anymore, instead we use fullName - udf.setResourceId(-1); - udf.setResourceName(fullName); - udf.setType(type); - - udf.setCreateTime(now); - udf.setUpdateTime(now); - - udfFuncMapper.insert(udf); - log.info("UDF function create complete, udfFuncName:{}.", udf.getFuncName()); - putMsg(result, Status.SUCCESS); - return result; - } - - /** - * - * @param name name - * @return check result code - */ - private boolean checkUdfFuncNameExists(String name) { - List resource = udfFuncMapper.queryUdfByIdStr(null, name); - return resource != null && !resource.isEmpty(); - } - - /** - * query udf function - * - * @param id udf function id - * @return udf function detail - */ - @Override - public Result queryUdfFuncDetail(User loginUser, int id) { - Result result = new Result<>(); - boolean canOperatorPermissions = canOperatorPermissions(loginUser, new Object[]{id}, AuthorizationType.UDF, - ApiFuncIdentificationConstant.UDF_FUNCTION_VIEW); - if (!canOperatorPermissions) { - putMsg(result, Status.NO_CURRENT_OPERATING_PERMISSION); - return result; - } - UdfFunc udfFunc = udfFuncMapper.selectById(id); - if (udfFunc == null) { - log.error("Resource does not exist, udf func id:{}.", id); - putMsg(result, Status.RESOURCE_NOT_EXIST); - return result; - } - result.setData(udfFunc); - putMsg(result, Status.SUCCESS); - return result; - } - - /** - * updateProcessInstance udf function - * - * @param udfFuncId udf function id - * @param type resource type - * @param funcName function name - * @param argTypes argument types - * @param database data base - * @param desc description - * @param fullName resource full name - * @param className class name - * @return update result code - */ - @Override - public Result updateUdfFunc(User loginUser, - int udfFuncId, - String funcName, - String className, - String argTypes, - String database, - String desc, - UdfType type, - String fullName) { - Result result = new Result<>(); - - boolean canOperatorPermissions = canOperatorPermissions(loginUser, new Object[]{udfFuncId}, - AuthorizationType.UDF, ApiFuncIdentificationConstant.UDF_FUNCTION_UPDATE); - if (!canOperatorPermissions) { - putMsg(result, Status.NO_CURRENT_OPERATING_PERMISSION); - return result; - } - if (checkDescriptionLength(desc)) { - log.warn("Parameter description is too long."); - putMsg(result, Status.DESCRIPTION_TOO_LONG_ERROR); - return result; - } - // verify udfFunc is exist - UdfFunc udf = udfFuncMapper.selectUdfById(udfFuncId); - - if (udf == null) { - log.error("UDF function does not exist, udfFuncId:{}.", udfFuncId); - result.setCode(Status.UDF_FUNCTION_NOT_EXIST.getCode()); - result.setMsg(Status.UDF_FUNCTION_NOT_EXIST.getMsg()); - return result; - } - - // verify udfFuncName is exist - if (!funcName.equals(udf.getFuncName())) { - if (checkUdfFuncNameExists(funcName)) { - log.warn("Udf function exists, can not create again, udfFuncName:{}.", funcName); - result.setCode(Status.UDF_FUNCTION_EXISTS.getCode()); - result.setMsg(Status.UDF_FUNCTION_EXISTS.getMsg()); - return result; - } - } - - Boolean doesResExist = false; - try { - doesResExist = storageOperator.exists(fullName); - } catch (Exception e) { - log.error("udf resource :{} checking error", fullName, e); - result.setCode(Status.RESOURCE_NOT_EXIST.getCode()); - result.setMsg(Status.RESOURCE_NOT_EXIST.getMsg()); - return result; - } - - if (!doesResExist) { - log.error("resource full name {} is not exist", fullName); - result.setCode(Status.RESOURCE_NOT_EXIST.getCode()); - result.setMsg(Status.RESOURCE_NOT_EXIST.getMsg()); - return result; - } - - Date now = new Date(); - udf.setFuncName(funcName); - udf.setClassName(className); - udf.setArgTypes(argTypes); - if (!StringUtils.isEmpty(database)) { - udf.setDatabase(database); - } - udf.setDescription(desc); - // set resourceId to -1 because we do not store resource to db anymore, instead we use fullName - udf.setResourceId(-1); - udf.setResourceName(fullName); - udf.setType(type); - - udf.setUpdateTime(now); - - udfFuncMapper.updateById(udf); - log.info("UDF function update complete, udfFuncId:{}, udfFuncName:{}.", udfFuncId, funcName); - putMsg(result, Status.SUCCESS); - return result; - } - - /** - * query udf function list paging - * - * @param loginUser login user - * @param pageNo page number - * @param pageSize page size - * @param searchVal search value - * @return udf function list page - */ - @Override - public Result queryUdfFuncListPaging(User loginUser, String searchVal, Integer pageNo, Integer pageSize) { - Result result = new Result(); - boolean canOperatorPermissions = canOperatorPermissions(loginUser, null, AuthorizationType.UDF, - ApiFuncIdentificationConstant.UDF_FUNCTION_VIEW); - if (!canOperatorPermissions) { - putMsg(result, Status.NO_CURRENT_OPERATING_PERMISSION); - return result; - } - PageInfo pageInfo = new PageInfo<>(pageNo, pageSize); - IPage udfFuncList = getUdfFuncsPage(loginUser, searchVal, pageSize, pageNo); - pageInfo.setTotal((int) udfFuncList.getTotal()); - pageInfo.setTotalList(udfFuncList.getRecords()); - result.setData(pageInfo); - putMsg(result, Status.SUCCESS); - return result; - } - - /** - * get udf functions - * - * @param loginUser login user - * @param searchVal search value - * @param pageSize page size - * @param pageNo page number - * @return udf function list page - */ - private IPage getUdfFuncsPage(User loginUser, String searchVal, Integer pageSize, int pageNo) { - Set udfFuncIds = resourcePermissionCheckService.userOwnedResourceIdsAcquisition(AuthorizationType.UDF, - loginUser.getId(), log); - Page page = new Page<>(pageNo, pageSize); - if (udfFuncIds.isEmpty()) { - return page; - } - return udfFuncMapper.queryUdfFuncPaging(page, new ArrayList<>(udfFuncIds), searchVal); - } - - /** - * query udf list - * - * @param loginUser login user - * @param type udf type - * @return udf func list - */ - @Override - public Result queryUdfFuncList(User loginUser, Integer type) { - Result result = new Result<>(); - - boolean canOperatorPermissions = canOperatorPermissions(loginUser, null, AuthorizationType.UDF, - ApiFuncIdentificationConstant.UDF_FUNCTION_VIEW); - if (!canOperatorPermissions) { - putMsg(result, Status.NO_CURRENT_OPERATING_PERMISSION); - return result; - } - Set udfFuncIds = resourcePermissionCheckService.userOwnedResourceIdsAcquisition(AuthorizationType.UDF, - loginUser.getId(), log); - if (udfFuncIds.isEmpty()) { - result.setData(Collections.emptyList()); - putMsg(result, Status.SUCCESS); - return result; - } - List udfFuncList = udfFuncMapper.getUdfFuncByType(new ArrayList<>(udfFuncIds), type); - - result.setData(udfFuncList); - putMsg(result, Status.SUCCESS); - return result; - } - - /** - * delete udf function - * - * @param id udf function id - * @return delete result code - */ - @Override - @Transactional - public Result delete(User loginUser, int id) { - Result result = new Result<>(); - - boolean canOperatorPermissions = canOperatorPermissions(loginUser, new Object[]{id}, AuthorizationType.UDF, - ApiFuncIdentificationConstant.UDF_FUNCTION_DELETE); - if (!canOperatorPermissions) { - putMsg(result, Status.NO_CURRENT_OPERATING_PERMISSION); - return result; - } - udfFuncMapper.deleteById(id); - udfUserMapper.deleteByUdfFuncId(id); - log.info("UDF function delete complete, udfFuncId:{}.", id); - putMsg(result, Status.SUCCESS); - return result; - } - - /** - * verify udf function by name - * - * @param name name - * @return true if the name can user, otherwise return false - */ - @Override - public Result verifyUdfFuncByName(User loginUser, String name) { - Result result = new Result<>(); - boolean canOperatorPermissions = canOperatorPermissions(loginUser, null, AuthorizationType.UDF, - ApiFuncIdentificationConstant.UDF_FUNCTION_VIEW); - if (!canOperatorPermissions) { - putMsg(result, Status.NO_CURRENT_OPERATING_PERMISSION); - return result; - } - - if (checkUdfFuncNameExists(name)) { - log.warn("Udf function with the same already exists."); - putMsg(result, Status.UDF_FUNCTION_EXISTS); - } else { - putMsg(result, Status.SUCCESS); - } - return result; - } -} diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/UsersServiceImpl.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/UsersServiceImpl.java index 0e91dc582d..9b8e56c7b3 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/UsersServiceImpl.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/UsersServiceImpl.java @@ -38,7 +38,6 @@ import org.apache.dolphinscheduler.dao.entity.K8sNamespaceUser; import org.apache.dolphinscheduler.dao.entity.Project; import org.apache.dolphinscheduler.dao.entity.ProjectUser; import org.apache.dolphinscheduler.dao.entity.Tenant; -import org.apache.dolphinscheduler.dao.entity.UDFUser; import org.apache.dolphinscheduler.dao.entity.User; import org.apache.dolphinscheduler.dao.mapper.AccessTokenMapper; import org.apache.dolphinscheduler.dao.mapper.AlertGroupMapper; @@ -47,7 +46,6 @@ import org.apache.dolphinscheduler.dao.mapper.K8sNamespaceUserMapper; import org.apache.dolphinscheduler.dao.mapper.ProjectMapper; import org.apache.dolphinscheduler.dao.mapper.ProjectUserMapper; import org.apache.dolphinscheduler.dao.mapper.TenantMapper; -import org.apache.dolphinscheduler.dao.mapper.UDFUserMapper; import org.apache.dolphinscheduler.dao.mapper.UserMapper; import org.apache.dolphinscheduler.plugin.storage.api.StorageOperator; @@ -99,9 +97,6 @@ public class UsersServiceImpl extends BaseServiceImpl implements UsersService { @Autowired private DataSourceUserMapper datasourceUserMapper; - @Autowired - private UDFUserMapper udfUserMapper; - @Autowired private AlertGroupMapper alertGroupMapper; @@ -492,9 +487,10 @@ public class UsersServiceImpl extends BaseServiceImpl implements UsersService { /** * revoke the project permission for specified user by id - * @param loginUser Login user - * @param userId User id - * @param projectIds project id array + * + * @param loginUser Login user + * @param userId User id + * @param projectIds project id array * @return */ @Override @@ -537,8 +533,8 @@ public class UsersServiceImpl extends BaseServiceImpl implements UsersService { /** * grant project with read permission * - * @param loginUser login user - * @param userId user id + * @param loginUser login user + * @param userId user id * @param projectIds project id array * @return grant result code */ @@ -747,62 +743,6 @@ public class UsersServiceImpl extends BaseServiceImpl implements UsersService { return result; } - /** - * grant udf function - * - * @param loginUser login user - * @param userId user id - * @param udfIds udf id array - * @return grant result code - */ - @Override - @Transactional - public Map grantUDFFunction(User loginUser, int userId, String udfIds) { - Map result = new HashMap<>(); - - if (resourcePermissionCheckService.functionDisabled()) { - putMsg(result, Status.FUNCTION_DISABLED); - return result; - } - User user = userMapper.selectById(userId); - if (user == null) { - log.error("User does not exist, userId:{}.", userId); - putMsg(result, Status.USER_NOT_EXIST, userId); - return result; - } - - if (!isAdmin(loginUser)) { - putMsg(result, Status.NO_CURRENT_OPERATING_PERMISSION); - return result; - } - - udfUserMapper.deleteByUserId(userId); - - if (check(result, StringUtils.isEmpty(udfIds), Status.SUCCESS)) { - log.warn("Parameter udfIds is empty."); - return result; - } - - String[] resourcesIdArr = udfIds.split(","); - - for (String udfId : resourcesIdArr) { - Date now = new Date(); - UDFUser udfUser = new UDFUser(); - udfUser.setUserId(userId); - udfUser.setUdfId(Integer.parseInt(udfId)); - udfUser.setPerm(Constants.AUTHORIZE_WRITABLE_PERM); - udfUser.setCreateTime(now); - udfUser.setUpdateTime(now); - udfUserMapper.insert(udfUser); - } - - log.info("User is granted permission for UDF, userName:{}.", user.getUserName()); - - putMsg(result, Status.SUCCESS); - - return result; - } - /** * grant namespace * diff --git a/dolphinscheduler-api/src/main/resources/i18n/messages.properties b/dolphinscheduler-api/src/main/resources/i18n/messages.properties index 0a5e6b0ca0..dc287b87c1 100644 --- a/dolphinscheduler-api/src/main/resources/i18n/messages.properties +++ b/dolphinscheduler-api/src/main/resources/i18n/messages.properties @@ -117,21 +117,9 @@ SUFFIX=resource file suffix CONTENT=resource file content UPDATE_RESOURCE_NOTES=edit resource file online DOWNLOAD_RESOURCE_NOTES=download resource file -CREATE_UDF_FUNCTION_NOTES=create udf function -UDF_TYPE=UDF type FUNC_NAME=function name CLASS_NAME=package and class name ARG_TYPES=arguments -UDF_DESC=udf desc -VIEW_UDF_FUNCTION_NOTES=view udf function -UPDATE_UDF_FUNCTION_NOTES=update udf function -QUERY_UDF_FUNCTION_LIST_PAGING_NOTES=query udf function list paging -VERIFY_UDF_FUNCTION_NAME_NOTES=verify udf function name -DELETE_UDF_FUNCTION_NOTES=delete udf function -AUTHORIZED_FILE_NOTES=authorized file -UNAUTHORIZED_FILE_NOTES=unauthorized file -AUTHORIZED_UDF_FUNC_NOTES=authorized udf func -UNAUTHORIZED_UDF_FUNC_NOTES=unauthorized udf func VERIFY_QUEUE_NOTES=verify queue TENANT_TAG=tenant related operation CREATE_TENANT_NOTES=create tenant @@ -259,8 +247,6 @@ UNAUTHORIZED_USER_NOTES=cancel authorization ALERT_GROUP_ID=alert group id ALERT_INSTANCE_IDS=alert instance ids(string format, multiple instances separated by ",") AUTHORIZED_USER_NOTES=authorized user -GRANT_UDF_FUNC_NOTES=grant udf function -UDF_IDS=udf ids(string format, multiple udf functions separated by ",") GRANT_DATASOURCE_NOTES=grant datasource DATASOURCE_IDS=datasource ids(string format, multiple datasources separated by ",") QUERY_SUBPROCESS_INSTANCE_BY_TASK_ID_NOTES=query subprocess instance by task instance id @@ -454,4 +440,4 @@ UPDATE_PROJECT_PREFERENCE_NOTES=update project preference UPDATE_PROJECT_PREFERENCE_STATE_NOTES=update the state of the project preference PROJECT_PREFERENCES_STATE= the state of the project preference PROJECT_PREFERENCES=project preferences -QUERY_PROJECT_PREFERENCE_NOTES=query project preference \ No newline at end of file +QUERY_PROJECT_PREFERENCE_NOTES=query project preference diff --git a/dolphinscheduler-api/src/main/resources/i18n/messages_en_US.properties b/dolphinscheduler-api/src/main/resources/i18n/messages_en_US.properties index c4ac93620c..032da35838 100644 --- a/dolphinscheduler-api/src/main/resources/i18n/messages_en_US.properties +++ b/dolphinscheduler-api/src/main/resources/i18n/messages_en_US.properties @@ -122,21 +122,11 @@ SUFFIX=resource file suffix CONTENT=resource file content UPDATE_RESOURCE_NOTES=edit resource file online DOWNLOAD_RESOURCE_NOTES=download resource file -CREATE_UDF_FUNCTION_NOTES=create udf function -UDF_TYPE=UDF type FUNC_NAME=function name CLASS_NAME=package and class name ARG_TYPES=arguments -UDF_DESC=udf desc -VIEW_UDF_FUNCTION_NOTES=view udf function -UPDATE_UDF_FUNCTION_NOTES=update udf function -QUERY_UDF_FUNCTION_LIST_PAGING_NOTES=query udf function list paging -VERIFY_UDF_FUNCTION_NAME_NOTES=verify udf function name -DELETE_UDF_FUNCTION_NOTES=delete udf function AUTHORIZED_FILE_NOTES=authorized file UNAUTHORIZED_FILE_NOTES=unauthorized file -AUTHORIZED_UDF_FUNC_NOTES=authorized udf func -UNAUTHORIZED_UDF_FUNC_NOTES=unauthorized udf func VERIFY_QUEUE_NOTES=verify queue TENANT_TAG=tenant related operation CREATE_TENANT_NOTES=create tenant @@ -251,7 +241,6 @@ QUERY_WORKER_ADDRESS_LIST_NOTES=query worker address list QUERY_WORKFLOW_LINEAGE_BY_IDS_NOTES=query workflow lineage by ids QUERY_WORKFLOW_LINEAGE_BY_NAME_NOTES=query workflow lineage by name VIEW_TREE_NOTES=view tree -UDF_ID=udf id GET_NODE_LIST_BY_DEFINITION_ID_NOTES=get task node list by process definition id GET_NODE_LIST_BY_DEFINITION_CODE_NOTES=get node list by definition code QUERY_PROCESS_DEFINITION_BY_NAME_NOTES=query process definition by name @@ -301,10 +290,7 @@ QUERY_RESOURCE_LIST_PAGING_NOTES=query resource list paging RESOURCE_PID=parent directory ID of the current resource RESOURCE_FULL_NAME=resource full name QUERY_BY_RESOURCE_NAME=query by resource name -QUERY_UDF_FUNC_LIST_NOTES=query udf funciton list VERIFY_RESOURCE_NAME_NOTES=verify resource name -GRANT_UDF_FUNC_NOTES=grant udf function -UDF_IDS=udf ids(string format, multiple udf functions separated by ",") GRANT_DATASOURCE_NOTES=grant datasource DATASOURCE_IDS=datasource ids(string format, multiple datasources separated by ",") QUERY_SUBPROCESS_INSTANCE_BY_TASK_ID_NOTES=query subprocess instance by task instance id @@ -486,4 +472,4 @@ UPDATE_PROJECT_PARAMETER_NOTES=update project parameter PROJECT_PARAMETER_CODE=project parameter code DELETE_PROJECT_PARAMETER_NOTES=delete project parameter QUERY_PROJECT_PARAMETER_LIST_PAGING_NOTES=query project parameter list paging -QUERY_PROJECT_PARAMETER_NOTES=query project parameter \ No newline at end of file +QUERY_PROJECT_PARAMETER_NOTES=query project parameter diff --git a/dolphinscheduler-api/src/main/resources/i18n/messages_zh_CN.properties b/dolphinscheduler-api/src/main/resources/i18n/messages_zh_CN.properties index e5d10aaf7d..f1aabee824 100644 --- a/dolphinscheduler-api/src/main/resources/i18n/messages_zh_CN.properties +++ b/dolphinscheduler-api/src/main/resources/i18n/messages_zh_CN.properties @@ -102,7 +102,6 @@ RESOURCE_FILE=资源文件 RESOURCE_ID=资源ID QUERY_RESOURCE_LIST_NOTES=查询资源列表 QUERY_BY_RESOURCE_NAME=通过资源名称查询 -QUERY_UDF_FUNC_LIST_NOTES=查询UDF函数列表 VERIFY_RESOURCE_NAME_NOTES=验证资源名称 DELETE_RESOURCE_BY_ID_NOTES=通过ID删除资源 VIEW_RESOURCE_BY_ID_NOTES=通过ID浏览资源 @@ -111,21 +110,11 @@ SUFFIX=资源文件后缀 CONTENT=资源文件内容 UPDATE_RESOURCE_NOTES=在线更新资源文件 DOWNLOAD_RESOURCE_NOTES=下载资源文件 -CREATE_UDF_FUNCTION_NOTES=创建UDF函数 -UDF_TYPE=UDF类型 FUNC_NAME=函数名称 CLASS_NAME=包名类名 ARG_TYPES=参数 -UDF_DESC=udf描述,使用说明 -VIEW_UDF_FUNCTION_NOTES=查看udf函数 -UPDATE_UDF_FUNCTION_NOTES=更新udf函数 -QUERY_UDF_FUNCTION_LIST_PAGING_NOTES=分页查询udf函数列表 -VERIFY_UDF_FUNCTION_NAME_NOTES=验证udf函数名 -DELETE_UDF_FUNCTION_NOTES=删除UDF函数 AUTHORIZED_FILE_NOTES=授权文件 UNAUTHORIZED_FILE_NOTES=取消授权文件 -AUTHORIZED_UDF_FUNC_NOTES=授权udf函数 -UNAUTHORIZED_UDF_FUNC_NOTES=取消udf函数授权 VERIFY_QUEUE_NOTES=验证队列 TENANT_TAG=租户相关操作 CREATE_TENANT_NOTES=创建租户 @@ -231,7 +220,6 @@ PLUGIN_ID=插件ID USER_ID=用户ID PAGE_SIZE=页大小 LIMIT=显示多少条 -UDF_ID=udf ID AUTHORIZE_RESOURCE_TREE_NOTES=授权资源树 RESOURCE_CURRENTDIR=当前资源目录 RESOURCE_PID=资源父目录ID @@ -285,8 +273,6 @@ UNAUTHORIZED_USER_NOTES=取消授权 ALERT_GROUP_ID=告警组ID ALERT_INSTANCE_IDS=告警实例ID列表(字符串格式,多个告警实例ID以","分割) AUTHORIZED_USER_NOTES=授权用户 -GRANT_UDF_FUNC_NOTES=授权udf函数 -UDF_IDS=udf函数id列表(字符串格式,多个udf函数ID以","分割) GRANT_DATASOURCE_NOTES=授权数据源 DATASOURCE_IDS=数据源ID列表(字符串格式,多个数据源ID以","分割) QUERY_SUBPROCESS_INSTANCE_BY_TASK_ID_NOTES=通过任务实例ID查询子流程实例 @@ -483,4 +469,4 @@ UPDATE_PROJECT_PARAMETER_NOTES=更新项目参数 PROJECT_PARAMETER_CODE=项目参数code DELETE_PROJECT_PARAMETER_NOTES=删除项目参数 QUERY_PROJECT_PARAMETER_LIST_PAGING_NOTES=分页查询项目参数 -QUERY_PROJECT_PARAMETER_NOTES=查询项目参数 \ No newline at end of file +QUERY_PROJECT_PARAMETER_NOTES=查询项目参数 diff --git a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/controller/ResourcesControllerTest.java b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/controller/ResourcesControllerTest.java index 01a2d47998..2d4effd7ad 100644 --- a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/controller/ResourcesControllerTest.java +++ b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/controller/ResourcesControllerTest.java @@ -27,10 +27,8 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import org.apache.dolphinscheduler.api.enums.Status; import org.apache.dolphinscheduler.api.service.ResourcesService; -import org.apache.dolphinscheduler.api.service.UdfFuncService; import org.apache.dolphinscheduler.api.utils.Result; import org.apache.dolphinscheduler.api.vo.resources.FetchFileContentResponse; -import org.apache.dolphinscheduler.common.enums.UdfType; import org.apache.dolphinscheduler.common.utils.JSONUtils; import org.apache.dolphinscheduler.spi.enums.ResourceType; @@ -54,9 +52,6 @@ public class ResourcesControllerTest extends AbstractControllerTest { @MockBean(name = "resourcesServiceImpl") private ResourcesService resourcesService; - @MockBean(name = "udfFuncServiceImpl") - private UdfFuncService udfFuncService; - @Test public void testQueryResourceListPaging() throws Exception { Result mockResult = new Result<>(); @@ -183,179 +178,6 @@ public class ResourcesControllerTest extends AbstractControllerTest { Assertions.assertNotNull(mvcResult); } - @Test - public void testCreateUdfFunc() throws Exception { - Result mockResult = new Result<>(); - mockResult.setCode(Status.TENANT_NOT_EXIST.getCode()); - Mockito.when(udfFuncService - .createUdfFunction(Mockito.any(), Mockito.anyString(), Mockito.anyString(), Mockito.anyString(), - Mockito.anyString(), Mockito.anyString(), Mockito.anyString(), Mockito.any())) - .thenReturn(mockResult); - - MultiValueMap paramsMap = new LinkedMultiValueMap<>(); - paramsMap.add("type", String.valueOf(UdfType.HIVE)); - paramsMap.add("funcName", "test_udf"); - paramsMap.add("className", "com.test.word.contWord"); - paramsMap.add("argTypes", "argTypes"); - paramsMap.add("database", "database"); - paramsMap.add("description", "description"); - paramsMap.add("resourceId", "1"); - paramsMap.add("fullName", "dolphinscheduler/resourcePath"); - - MvcResult mvcResult = mockMvc.perform(post("/resources/udf-func") - .header(SESSION_ID, sessionId) - .params(paramsMap)) - .andExpect(status().isCreated()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON)) - .andReturn(); - - Result result = JSONUtils.parseObject(mvcResult.getResponse().getContentAsString(), Result.class); - - assertEquals(Status.TENANT_NOT_EXIST.getCode(), result.getCode().intValue()); - logger.info(mvcResult.getResponse().getContentAsString()); - } - - @Test - public void testViewUIUdfFunction() throws Exception { - Result mockResult = new Result<>(); - putMsg(mockResult, Status.TENANT_NOT_EXIST); - Mockito.when(udfFuncService - .queryUdfFuncDetail(Mockito.any(), Mockito.anyInt())) - .thenReturn(mockResult); - - MvcResult mvcResult = mockMvc.perform(get("/resources/{id}/udf-func", "123") - .header(SESSION_ID, sessionId)) - .andExpect(status().isOk()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON)) - .andReturn(); - - Result result = JSONUtils.parseObject(mvcResult.getResponse().getContentAsString(), Result.class); - - assertEquals(Status.TENANT_NOT_EXIST.getCode(), result.getCode().intValue()); - logger.info(mvcResult.getResponse().getContentAsString()); - } - - @Test - public void testUpdateUdfFunc() throws Exception { - Result mockResult = new Result<>(); - mockResult.setCode(Status.TENANT_NOT_EXIST.getCode()); - Mockito.when(udfFuncService - .updateUdfFunc(Mockito.any(), Mockito.anyInt(), Mockito.anyString(), Mockito.anyString(), - Mockito.anyString(), Mockito.anyString(), Mockito.anyString(), Mockito.any(), - Mockito.anyString())) - .thenReturn(mockResult); - - MultiValueMap paramsMap = new LinkedMultiValueMap<>(); - paramsMap.add("id", "1"); - paramsMap.add("type", String.valueOf(UdfType.HIVE)); - paramsMap.add("funcName", "update_duf"); - paramsMap.add("className", "com.test.word.contWord"); - paramsMap.add("argTypes", "argTypes"); - paramsMap.add("database", "database"); - paramsMap.add("description", "description"); - paramsMap.add("resourceId", "1"); - paramsMap.add("fullName", "dolphinscheduler/resourcePath"); - - MvcResult mvcResult = mockMvc.perform(put("/resources/udf-func/{id}", "456") - .header(SESSION_ID, sessionId) - .params(paramsMap)) - .andExpect(status().isOk()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON)) - .andReturn(); - - Result result = JSONUtils.parseObject(mvcResult.getResponse().getContentAsString(), Result.class); - - assertEquals(Status.TENANT_NOT_EXIST.getCode(), result.getCode().intValue()); - logger.info(mvcResult.getResponse().getContentAsString()); - } - - @Test - public void testQueryUdfFuncList() throws Exception { - Result mockResult = new Result<>(); - mockResult.setCode(Status.SUCCESS.getCode()); - Mockito.when(udfFuncService.queryUdfFuncListPaging(Mockito.any(), Mockito.anyString(), Mockito.anyInt(), - Mockito.anyInt())).thenReturn(mockResult); - - MultiValueMap paramsMap = new LinkedMultiValueMap<>(); - paramsMap.add("pageNo", "1"); - paramsMap.add("searchVal", "udf"); - paramsMap.add("pageSize", "1"); - - MvcResult mvcResult = mockMvc.perform(get("/resources/udf-func") - .header(SESSION_ID, sessionId) - .params(paramsMap)) - .andExpect(status().isOk()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON)) - .andReturn(); - - Result result = JSONUtils.parseObject(mvcResult.getResponse().getContentAsString(), Result.class); - - assertEquals(Status.SUCCESS.getCode(), result.getCode().intValue()); - logger.info(mvcResult.getResponse().getContentAsString()); - } - - @Test - public void testQueryResourceList() throws Exception { - Result mockResult = new Result<>(); - mockResult.setCode(Status.SUCCESS.getCode()); - Mockito.when(udfFuncService.queryUdfFuncList(Mockito.any(), Mockito.anyInt())).thenReturn(mockResult); - - MultiValueMap paramsMap = new LinkedMultiValueMap<>(); - paramsMap.add("type", String.valueOf(UdfType.HIVE)); - - MvcResult mvcResult = mockMvc.perform(get("/resources/udf-func/list") - .header(SESSION_ID, sessionId) - .params(paramsMap)) - .andExpect(status().isOk()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON)) - .andReturn(); - - Result result = JSONUtils.parseObject(mvcResult.getResponse().getContentAsString(), Result.class); - - assertEquals(Status.SUCCESS.getCode(), result.getCode().intValue()); - logger.info(mvcResult.getResponse().getContentAsString()); - } - - @Test - public void testVerifyUdfFuncName() throws Exception { - Result mockResult = new Result<>(); - mockResult.setCode(Status.SUCCESS.getCode()); - Mockito.when(udfFuncService.verifyUdfFuncByName(Mockito.any(), Mockito.anyString())).thenReturn(mockResult); - - MultiValueMap paramsMap = new LinkedMultiValueMap<>(); - paramsMap.add("name", "test"); - - MvcResult mvcResult = mockMvc.perform(get("/resources/udf-func/verify-name") - .header(SESSION_ID, sessionId) - .params(paramsMap)) - .andExpect(status().isOk()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON)) - .andReturn(); - - Result result = JSONUtils.parseObject(mvcResult.getResponse().getContentAsString(), Result.class); - - assertEquals(Status.SUCCESS.getCode(), result.getCode().intValue()); - logger.info(mvcResult.getResponse().getContentAsString()); - } - - @Test - public void testDeleteUdfFunc() throws Exception { - Result mockResult = new Result<>(); - mockResult.setCode(Status.SUCCESS.getCode()); - Mockito.when(udfFuncService.delete(Mockito.any(), Mockito.anyInt())).thenReturn(mockResult); - - MvcResult mvcResult = mockMvc.perform(delete("/resources/udf-func/{id}", "123") - .header(SESSION_ID, sessionId)) - .andExpect(status().isOk()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON)) - .andReturn(); - - Result result = JSONUtils.parseObject(mvcResult.getResponse().getContentAsString(), Result.class); - - assertEquals(Status.SUCCESS.getCode(), result.getCode().intValue()); - logger.info(mvcResult.getResponse().getContentAsString()); - } - @Test public void testDeleteResource() throws Exception { Mockito.doNothing().when(resourcesService).delete(Mockito.any()); diff --git a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/controller/UsersControllerTest.java b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/controller/UsersControllerTest.java index 1f6eca0572..303d4735bc 100644 --- a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/controller/UsersControllerTest.java +++ b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/controller/UsersControllerTest.java @@ -145,24 +145,6 @@ public class UsersControllerTest extends AbstractControllerTest { logger.info(mvcResult.getResponse().getContentAsString()); } - @Test - public void testGrantUDFFunc() throws Exception { - MultiValueMap paramsMap = new LinkedMultiValueMap<>(); - paramsMap.add("userId", "32"); - paramsMap.add("udfIds", "5"); - - MvcResult mvcResult = mockMvc.perform(post("/users/grant-udf-func") - .header(SESSION_ID, sessionId) - .params(paramsMap)) - .andExpect(status().isOk()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON)) - .andReturn(); - - Result result = JSONUtils.parseObject(mvcResult.getResponse().getContentAsString(), Result.class); - Assertions.assertEquals(Status.USER_NOT_EXIST.getCode(), result.getCode().intValue()); - logger.info(mvcResult.getResponse().getContentAsString()); - } - @Test public void testGrantDataSource() throws Exception { MultiValueMap paramsMap = new LinkedMultiValueMap<>(); diff --git a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/permission/UdfFuncPermissionCheckTest.java b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/permission/UdfFuncPermissionCheckTest.java deleted file mode 100644 index 23a01c8f0a..0000000000 --- a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/permission/UdfFuncPermissionCheckTest.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * 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. - */ - -package org.apache.dolphinscheduler.api.permission; - -import org.apache.dolphinscheduler.common.enums.AuthorizationType; -import org.apache.dolphinscheduler.common.enums.UserType; -import org.apache.dolphinscheduler.dao.entity.Project; -import org.apache.dolphinscheduler.dao.entity.UdfFunc; -import org.apache.dolphinscheduler.dao.entity.User; -import org.apache.dolphinscheduler.dao.mapper.UdfFuncMapper; - -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.junit.jupiter.MockitoExtension; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -@ExtendWith(MockitoExtension.class) -public class UdfFuncPermissionCheckTest { - - private static final Logger logger = LoggerFactory.getLogger(UdfFuncPermissionCheckTest.class); - @InjectMocks - private ResourcePermissionCheckServiceImpl.UdfFuncPermissionCheck udfFuncPermissionCheck; - - @Mock - private UdfFuncMapper udfFuncMapper; - - @Test - public void testPermissionCheck() { - User user = getLoginUser(); - Assertions.assertTrue(udfFuncPermissionCheck.permissionCheck(user.getId(), null, logger)); - } - - @Test - public void testAuthorizationTypes() { - List authorizationTypes = udfFuncPermissionCheck.authorizationTypes(); - Assertions.assertEquals(Collections.singletonList(AuthorizationType.UDF), authorizationTypes); - } - - @Test - public void testListAuthorizedResourceIds() { - User user = getLoginUser(); - UdfFunc udfFunc = new UdfFunc(); - Set ids = new HashSet(); - ids.add(udfFunc.getId()); - List udfFuncs = Arrays.asList(udfFunc); - - Mockito.when(udfFuncMapper.listAuthorizedUdfByUserId(user.getId())).thenReturn(udfFuncs); - - Assertions.assertEquals(ids, udfFuncPermissionCheck.listAuthorizedResourceIds(user.getId(), logger)); - } - - private User getLoginUser() { - User loginUser = new User(); - loginUser.setUserType(UserType.GENERAL_USER); - loginUser.setUserName("test"); - loginUser.setId(1); - return loginUser; - } - - private Project getProject() { - Project project = new Project(); - project.setCode(1L); - project.setId(1); - project.setName("projectName"); - project.setUserId(1); - return project; - } -} diff --git a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/UdfFuncServiceTest.java b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/UdfFuncServiceTest.java deleted file mode 100644 index 5c7bba5821..0000000000 --- a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/UdfFuncServiceTest.java +++ /dev/null @@ -1,306 +0,0 @@ -/* - * 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. - */ - -package org.apache.dolphinscheduler.api.service; - -import org.apache.dolphinscheduler.api.constants.ApiFuncIdentificationConstant; -import org.apache.dolphinscheduler.api.enums.Status; -import org.apache.dolphinscheduler.api.permission.ResourcePermissionCheckService; -import org.apache.dolphinscheduler.api.service.impl.BaseServiceImpl; -import org.apache.dolphinscheduler.api.service.impl.UdfFuncServiceImpl; -import org.apache.dolphinscheduler.api.utils.PageInfo; -import org.apache.dolphinscheduler.api.utils.Result; -import org.apache.dolphinscheduler.common.enums.AuthorizationType; -import org.apache.dolphinscheduler.common.enums.UdfType; -import org.apache.dolphinscheduler.common.enums.UserType; -import org.apache.dolphinscheduler.common.utils.PropertyUtils; -import org.apache.dolphinscheduler.dao.entity.UdfFunc; -import org.apache.dolphinscheduler.dao.entity.User; -import org.apache.dolphinscheduler.dao.mapper.UDFUserMapper; -import org.apache.dolphinscheduler.dao.mapper.UdfFuncMapper; -import org.apache.dolphinscheduler.plugin.storage.api.StorageOperator; - -import org.apache.commons.collections4.CollectionUtils; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Date; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.MockedStatic; -import org.mockito.Mockito; -import org.mockito.junit.jupiter.MockitoExtension; -import org.mockito.junit.jupiter.MockitoSettings; -import org.mockito.quality.Strictness; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.baomidou.mybatisplus.core.metadata.IPage; -import com.baomidou.mybatisplus.extension.plugins.pagination.Page; - -/** - * udf func service test - */ -@ExtendWith(MockitoExtension.class) -@MockitoSettings(strictness = Strictness.LENIENT) -public class UdfFuncServiceTest { - - private static final Logger logger = LoggerFactory.getLogger(UdfFuncServiceTest.class); - - private MockedStatic mockedStaticPropertyUtils; - - @InjectMocks - private UdfFuncServiceImpl udfFuncService; - - @Mock - private UdfFuncMapper udfFuncMapper; - - @Mock - private UDFUserMapper udfUserMapper; - - @Mock - private StorageOperator storageOperator; - - @BeforeEach - public void setUp() { - mockedStaticPropertyUtils = Mockito.mockStatic(PropertyUtils.class); - } - - @Mock - private ResourcePermissionCheckService resourcePermissionCheckService; - - private static final Logger serviceLogger = LoggerFactory.getLogger(BaseServiceImpl.class); - private static final Logger udfLogger = LoggerFactory.getLogger(UdfFuncServiceImpl.class); - - @Test - public void testCreateUdfFunction() { - - Mockito.when(resourcePermissionCheckService.operationPermissionCheck(AuthorizationType.UDF, 1, - ApiFuncIdentificationConstant.UDF_FUNCTION_CREATE, serviceLogger)).thenReturn(true); - Mockito.when( - resourcePermissionCheckService.resourcePermissionCheck(AuthorizationType.UDF, null, 0, serviceLogger)) - .thenReturn(true); - // resource not exist - Result result = udfFuncService.createUdfFunction(getLoginUser(), "UdfFuncServiceTest", - "org.apache.dolphinscheduler.api.service.UdfFuncServiceTest", "String", - "UdfFuncServiceTest", "UdfFuncServiceTest", "", UdfType.HIVE); - logger.info(result.toString()); - Assertions.assertEquals(Status.RESOURCE_NOT_EXIST.getMsg(), result.getMsg()); - // success - Mockito.when(storageOperator.exists("String")).thenReturn(true); - - result = udfFuncService.createUdfFunction(getLoginUser(), "UdfFuncServiceTest", - "org.apache.dolphinscheduler.api.service.UdfFuncServiceTest", "String", - "UdfFuncServiceTest", "UdfFuncServiceTest", "", UdfType.HIVE); - logger.info(result.toString()); - Assertions.assertEquals(Status.SUCCESS.getMsg(), result.getMsg()); - } - - @Test - public void testQueryUdfFuncDetail() { - - Mockito.when(resourcePermissionCheckService.operationPermissionCheck(AuthorizationType.UDF, 1, - ApiFuncIdentificationConstant.UDF_FUNCTION_VIEW, serviceLogger)).thenReturn(true); - Mockito.when(resourcePermissionCheckService.resourcePermissionCheck(AuthorizationType.UDF, new Object[]{2}, 0, - serviceLogger)).thenReturn(true); - Mockito.when(udfFuncMapper.selectById(1)).thenReturn(getUdfFunc()); - // resource not exist - Result result = udfFuncService.queryUdfFuncDetail(getLoginUser(), 2); - logger.info(result.toString()); - Assertions.assertTrue(Status.RESOURCE_NOT_EXIST.getCode() == result.getCode()); - // success - Mockito.when(resourcePermissionCheckService.operationPermissionCheck(AuthorizationType.UDF, 1, - ApiFuncIdentificationConstant.UDF_FUNCTION_VIEW, serviceLogger)).thenReturn(true); - Mockito.when(resourcePermissionCheckService.resourcePermissionCheck(AuthorizationType.UDF, new Object[]{1}, 0, - serviceLogger)).thenReturn(true); - result = udfFuncService.queryUdfFuncDetail(getLoginUser(), 1); - logger.info(result.toString()); - Assertions.assertTrue(Status.SUCCESS.getCode() == result.getCode()); - } - - @Test - public void testUpdateUdfFunc() { - Mockito.when(udfFuncMapper.selectUdfById(1)).thenReturn(getUdfFunc()); - - // UDF_FUNCTION_NOT_EXIST - Mockito.when(resourcePermissionCheckService.operationPermissionCheck(AuthorizationType.UDF, 1, - ApiFuncIdentificationConstant.UDF_FUNCTION_UPDATE, serviceLogger)).thenReturn(true); - Mockito.when(resourcePermissionCheckService.resourcePermissionCheck(AuthorizationType.UDF, new Object[]{12}, 0, - serviceLogger)).thenReturn(true); - Result result = udfFuncService.updateUdfFunc(getLoginUser(), 12, "UdfFuncServiceTest", - "org.apache.dolphinscheduler.api.service.UdfFuncServiceTest", "String", - "UdfFuncServiceTest", "UdfFuncServiceTest", UdfType.HIVE, ""); - logger.info(result.toString()); - Assertions.assertTrue(Status.UDF_FUNCTION_NOT_EXIST.getCode() == result.getCode()); - - // RESOURCE_NOT_EXIST - Mockito.when(resourcePermissionCheckService.operationPermissionCheck(AuthorizationType.UDF, 1, - ApiFuncIdentificationConstant.UDF_FUNCTION_UPDATE, serviceLogger)).thenReturn(true); - Mockito.when(resourcePermissionCheckService.resourcePermissionCheck(AuthorizationType.UDF, new Object[]{11}, 0, - serviceLogger)).thenReturn(true); - Mockito.when(udfFuncMapper.selectUdfById(11)).thenReturn(getUdfFunc()); - result = udfFuncService.updateUdfFunc(getLoginUser(), 11, "UdfFuncServiceTest", - "org.apache.dolphinscheduler.api.service.UdfFuncServiceTest", "String", - "UdfFuncServiceTest", "UdfFuncServiceTest", UdfType.HIVE, ""); - logger.info(result.toString()); - Assertions.assertTrue(Status.RESOURCE_NOT_EXIST.getCode() == result.getCode()); - - // success - Mockito.when(resourcePermissionCheckService.operationPermissionCheck(AuthorizationType.UDF, 1, - ApiFuncIdentificationConstant.UDF_FUNCTION_UPDATE, serviceLogger)).thenReturn(true); - Mockito.when(storageOperator.exists("")).thenReturn(true); - - result = udfFuncService.updateUdfFunc(getLoginUser(), 11, "UdfFuncServiceTest", - "org.apache.dolphinscheduler.api.service.UdfFuncServiceTest", "String", - "UdfFuncServiceTest", "UdfFuncServiceTest", UdfType.HIVE, ""); - logger.info(result.toString()); - Assertions.assertTrue(Status.SUCCESS.getCode() == result.getCode()); - - } - - @Test - public void testQueryUdfFuncListPaging() { - - Mockito.when(resourcePermissionCheckService.operationPermissionCheck(AuthorizationType.UDF, 1, - ApiFuncIdentificationConstant.UDF_FUNCTION_VIEW, serviceLogger)).thenReturn(true); - Mockito.when( - resourcePermissionCheckService.resourcePermissionCheck(AuthorizationType.UDF, null, 0, serviceLogger)) - .thenReturn(true); - Mockito.when( - resourcePermissionCheckService.userOwnedResourceIdsAcquisition(AuthorizationType.UDF, 1, udfLogger)) - .thenReturn(getSetIds()); - IPage page = new Page<>(1, 10); - page.setTotal(1L); - page.setRecords(getList()); - Mockito.when(udfFuncMapper.queryUdfFuncPaging(Mockito.any(Page.class), Mockito.anyList(), Mockito.eq("test"))) - .thenReturn(page); - Result result = udfFuncService.queryUdfFuncListPaging(getLoginUser(), "test", 1, 10); - logger.info(result.toString()); - PageInfo pageInfo = (PageInfo) result.getData(); - Assertions.assertTrue(CollectionUtils.isNotEmpty(pageInfo.getTotalList())); - } - - @Test - public void testQueryUdfFuncList() { - Mockito.when(resourcePermissionCheckService.operationPermissionCheck(AuthorizationType.UDF, 1, - ApiFuncIdentificationConstant.UDF_FUNCTION_VIEW, serviceLogger)).thenReturn(true); - Mockito.when( - resourcePermissionCheckService.resourcePermissionCheck(AuthorizationType.UDF, null, 1, serviceLogger)) - .thenReturn(true); - Mockito.when( - resourcePermissionCheckService.userOwnedResourceIdsAcquisition(AuthorizationType.UDF, 1, udfLogger)) - .thenReturn(getSetIds()); - - User user = getLoginUser(); - user.setUserType(UserType.GENERAL_USER); - user.setId(1); - Mockito.when(udfFuncMapper.getUdfFuncByType(Collections.singletonList(1), UdfType.HIVE.ordinal())) - .thenReturn(getList()); - Result result = udfFuncService.queryUdfFuncList(user, UdfType.HIVE.ordinal()); - logger.info(result.toString()); - Assertions.assertTrue(Status.SUCCESS.getCode() == result.getCode()); - List udfFuncList = (List) result.getData(); - Assertions.assertTrue(CollectionUtils.isNotEmpty(udfFuncList)); - } - - @Test - public void testDelete() { - Mockito.when(resourcePermissionCheckService.operationPermissionCheck(AuthorizationType.UDF, 1, - ApiFuncIdentificationConstant.UDF_FUNCTION_DELETE, serviceLogger)).thenReturn(true); - Mockito.when(resourcePermissionCheckService.resourcePermissionCheck(AuthorizationType.UDF, new Object[]{122}, 0, - serviceLogger)).thenReturn(true); - - Mockito.when(udfFuncMapper.deleteById(Mockito.anyInt())).thenReturn(1); - Mockito.when(udfUserMapper.deleteByUdfFuncId(Mockito.anyInt())).thenReturn(1); - Result result = udfFuncService.delete(getLoginUser(), 122); - logger.info(result.toString()); - Assertions.assertEquals(Status.SUCCESS.getMsg(), result.getMsg()); - } - - @Test - public void testVerifyUdfFuncByName() { - - Mockito.when(resourcePermissionCheckService.operationPermissionCheck(AuthorizationType.UDF, 1, - ApiFuncIdentificationConstant.UDF_FUNCTION_VIEW, serviceLogger)).thenReturn(true); - Mockito.when( - resourcePermissionCheckService.resourcePermissionCheck(AuthorizationType.UDF, null, 0, serviceLogger)) - .thenReturn(true); - // success - Mockito.when(udfFuncMapper.queryUdfByIdStr(null, "UdfFuncServiceTest")).thenReturn(getList()); - Result result = udfFuncService.verifyUdfFuncByName(getLoginUser(), "test"); - logger.info(result.toString()); - Assertions.assertEquals(Status.SUCCESS.getMsg(), result.getMsg()); - // exist - result = udfFuncService.verifyUdfFuncByName(getLoginUser(), "UdfFuncServiceTest"); - logger.info(result.toString()); - Assertions.assertEquals(Status.UDF_FUNCTION_EXISTS.getMsg(), result.getMsg()); - } - - private Set getSetIds() { - Set set = new HashSet(); - set.add(1); - return set; - } - - /** - * create admin user - * @return - */ - private User getLoginUser() { - - User loginUser = new User(); - loginUser.setUserType(UserType.ADMIN_USER); - loginUser.setId(1); - return loginUser; - } - - private List getList() { - List udfFuncList = new ArrayList<>(); - udfFuncList.add(getUdfFunc()); - return udfFuncList; - } - - /** - * get UdfFuncRequest id - */ - private UdfFunc getUdfFunc() { - UdfFunc udfFunc = new UdfFunc(); - udfFunc.setFuncName("UdfFuncServiceTest"); - udfFunc.setClassName("org.apache.dolphinscheduler.api.service.UdfFuncServiceTest"); - udfFunc.setResourceId(0); - udfFunc.setResourceName("UdfFuncServiceTest"); - udfFunc.setCreateTime(new Date()); - udfFunc.setDatabase("database"); - udfFunc.setUpdateTime(new Date()); - udfFunc.setType(UdfType.HIVE); - return udfFunc; - } - - @AfterEach - public void after() { - mockedStaticPropertyUtils.close(); - } -} diff --git a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/UsersServiceTest.java b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/UsersServiceTest.java index 9e2a359667..290ffefe26 100644 --- a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/UsersServiceTest.java +++ b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/UsersServiceTest.java @@ -46,7 +46,6 @@ import org.apache.dolphinscheduler.dao.mapper.K8sNamespaceUserMapper; import org.apache.dolphinscheduler.dao.mapper.ProjectMapper; import org.apache.dolphinscheduler.dao.mapper.ProjectUserMapper; import org.apache.dolphinscheduler.dao.mapper.TenantMapper; -import org.apache.dolphinscheduler.dao.mapper.UDFUserMapper; import org.apache.dolphinscheduler.dao.mapper.UserMapper; import org.apache.dolphinscheduler.plugin.storage.api.StorageOperator; @@ -107,9 +106,6 @@ public class UsersServiceTest { @Mock private MetricsCleanUpService metricsCleanUpService; - @Mock - private UDFUserMapper udfUserMapper; - @Mock private K8sNamespaceUserMapper k8sNamespaceUserMapper; @@ -532,33 +528,6 @@ public class UsersServiceTest { Assertions.assertEquals(Status.SUCCESS, result.get(Constants.STATUS)); } - @Test - public void testGrantUDFFunction() { - String udfIds = "100000,120000"; - when(userMapper.selectById(1)).thenReturn(getUser()); - User loginUser = new User(); - - // user not exist - loginUser.setUserType(UserType.ADMIN_USER); - Map result = usersService.grantUDFFunction(loginUser, 2, udfIds); - logger.info(result.toString()); - Assertions.assertEquals(Status.USER_NOT_EXIST, result.get(Constants.STATUS)); - - // success - when(udfUserMapper.deleteByUserId(1)).thenReturn(1); - result = usersService.grantUDFFunction(loginUser, 1, udfIds); - logger.info(result.toString()); - Assertions.assertEquals(Status.SUCCESS, result.get(Constants.STATUS)); - - // ERROR: NO_CURRENT_OPERATING_PERMISSION - loginUser.setId(2); - loginUser.setUserType(UserType.GENERAL_USER); - when(userMapper.selectById(2)).thenReturn(loginUser); - result = this.usersService.grantUDFFunction(loginUser, 2, udfIds); - logger.info(result.toString()); - Assertions.assertEquals(Status.NO_CURRENT_OPERATING_PERMISSION, result.get(Constants.STATUS)); - } - @Test public void testGrantNamespaces() { String namespaceIds = "100000,120000"; diff --git a/dolphinscheduler-common/src/main/java/org/apache/dolphinscheduler/common/constants/Constants.java b/dolphinscheduler-common/src/main/java/org/apache/dolphinscheduler/common/constants/Constants.java index 4800bff7f9..c7d771796f 100644 --- a/dolphinscheduler-common/src/main/java/org/apache/dolphinscheduler/common/constants/Constants.java +++ b/dolphinscheduler-common/src/main/java/org/apache/dolphinscheduler/common/constants/Constants.java @@ -45,8 +45,6 @@ public final class Constants { public static final String RESOURCE_TYPE_FILE = "resources"; - public static final String RESOURCE_TYPE_UDF = "udfs"; - public static final String EMPTY_STRING = ""; /** @@ -506,7 +504,9 @@ public final class Constants { * session timeout */ public static final int SESSION_TIME_OUT = 7200; - public static final String UDF = "UDF"; + + public static final int MAX_FILE_SIZE = 1024 * 1024 * 1024; + public static final String CLASS = "class"; /** diff --git a/dolphinscheduler-common/src/main/java/org/apache/dolphinscheduler/common/enums/AuditModelType.java b/dolphinscheduler-common/src/main/java/org/apache/dolphinscheduler/common/enums/AuditModelType.java index 5f046882c1..8216caa6b6 100644 --- a/dolphinscheduler-common/src/main/java/org/apache/dolphinscheduler/common/enums/AuditModelType.java +++ b/dolphinscheduler-common/src/main/java/org/apache/dolphinscheduler/common/enums/AuditModelType.java @@ -39,9 +39,6 @@ public enum AuditModelType { RESOURCE("Resource", null), FOLDER("Folder", RESOURCE), FILE("File", FOLDER), - UDF_FOLDER("UDFFolder", RESOURCE), - UDF_FILE("UDFFile", UDF_FOLDER), - UDF_FUNCTION("UDFFunction", RESOURCE), TASK_GROUP("TaskGroup", RESOURCE), DATASOURCE("Datasource", null), diff --git a/dolphinscheduler-common/src/main/java/org/apache/dolphinscheduler/common/enums/AuthorizationType.java b/dolphinscheduler-common/src/main/java/org/apache/dolphinscheduler/common/enums/AuthorizationType.java index 79ef986cd3..f987bdb1a2 100644 --- a/dolphinscheduler-common/src/main/java/org/apache/dolphinscheduler/common/enums/AuthorizationType.java +++ b/dolphinscheduler-common/src/main/java/org/apache/dolphinscheduler/common/enums/AuthorizationType.java @@ -51,7 +51,6 @@ public enum AuthorizationType { @Deprecated UDF_FILE(2, "udf file"), DATASOURCE(3, "data source"), - UDF(4, "udf function"), PROJECTS(5, "projects"), WORKER_GROUP(6, "worker group"), ALERT_GROUP(7, "alert group"), diff --git a/dolphinscheduler-common/src/main/java/org/apache/dolphinscheduler/common/enums/UdfType.java b/dolphinscheduler-common/src/main/java/org/apache/dolphinscheduler/common/enums/UdfType.java deleted file mode 100644 index 9c4266bf7a..0000000000 --- a/dolphinscheduler-common/src/main/java/org/apache/dolphinscheduler/common/enums/UdfType.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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. - */ - -package org.apache.dolphinscheduler.common.enums; - -import com.baomidou.mybatisplus.annotation.EnumValue; - -/** - * UDF type - */ -public enum UdfType { - - /** - * 0 hive; 1 spark - */ - HIVE(0, "hive"), - SPARK(1, "spark"); - - UdfType(int code, String descp) { - this.code = code; - this.descp = descp; - } - - @EnumValue - private final int code; - private final String descp; - - public int getCode() { - return code; - } - - public String getDescp() { - return descp; - } - - public static UdfType of(int type) { - for (UdfType ut : values()) { - if (ut.getCode() == type) { - return ut; - } - } - throw new IllegalArgumentException("invalid type : " + type); - } - -} diff --git a/dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/entity/UDFUser.java b/dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/entity/UDFUser.java deleted file mode 100644 index 44901a797c..0000000000 --- a/dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/entity/UDFUser.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * 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. - */ -package org.apache.dolphinscheduler.dao.entity; - -import java.util.Date; - -import lombok.Data; - -import com.baomidou.mybatisplus.annotation.IdType; -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; - -@Data -@TableName("t_ds_relation_udfs_user") -public class UDFUser { - - /** - * id - */ - @TableId(value = "id", type = IdType.AUTO) - private Integer id; - - /** - * id - */ - private int userId; - - /** - * udf id - */ - private int udfId; - - /** - * permission - */ - private int perm; - - /** - * create time - */ - private Date createTime; - - /** - * update time - */ - private Date updateTime; -} diff --git a/dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/entity/UdfFunc.java b/dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/entity/UdfFunc.java deleted file mode 100644 index ab452cb1ff..0000000000 --- a/dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/entity/UdfFunc.java +++ /dev/null @@ -1,157 +0,0 @@ -/* - * 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. - */ - -package org.apache.dolphinscheduler.dao.entity; - -import org.apache.dolphinscheduler.common.enums.UdfType; -import org.apache.dolphinscheduler.common.utils.JSONUtils; - -import java.io.IOException; -import java.util.Date; -import java.util.Objects; - -import lombok.Data; - -import com.baomidou.mybatisplus.annotation.IdType; -import com.baomidou.mybatisplus.annotation.TableField; -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.KeyDeserializer; -import com.google.common.base.Strings; - -@Data -@TableName("t_ds_udfs") -public class UdfFunc { - - /** - * id - */ - @TableId(value = "id", type = IdType.AUTO) - private Integer id; - /** - * user id - */ - private int userId; - - public String getResourceType() { - return resourceType; - } - - public void setResourceType(String resourceType) { - this.resourceType = "UDF"; - } - - @TableField(exist = false) - private String resourceType = "UDF"; - /** - * udf function name - */ - private String funcName; - - /** - * udf class name - */ - private String className; - - /** - * udf argument types - */ - private String argTypes; - - /** - * udf data base - */ - private String database; - - /** - * udf description - */ - private String description; - - /** - * resource id - */ - private int resourceId; - - /** - * resource name - */ - private String resourceName; - - /** - * udf function type: hive / spark - */ - private UdfType type; - - /** - * create time - */ - private Date createTime; - - /** - * update time - */ - private Date updateTime; - - /** - * user name - */ - @TableField(exist = false) - private String userName; - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - UdfFunc udfFunc = (UdfFunc) o; - - if (!Objects.equals(id, udfFunc.id)) { - return false; - } - return !(funcName != null ? !funcName.equals(udfFunc.funcName) : udfFunc.funcName != null); - - } - - @Override - public int hashCode() { - int result = id; - result = 31 * result + (funcName != null ? funcName.hashCode() : 0); - return result; - } - - @Override - public String toString() { - return JSONUtils.toJsonString(this); - } - - public static class UdfFuncDeserializer extends KeyDeserializer { - - @Override - public Object deserializeKey(String key, DeserializationContext ctxt) throws IOException { - if (Strings.isNullOrEmpty(key)) { - return null; - } - return JSONUtils.parseObject(key, UdfFunc.class); - } - } -} diff --git a/dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/mapper/DataSourceMapper.java b/dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/mapper/DataSourceMapper.java index e26fe77ae4..0eb87a9dcb 100644 --- a/dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/mapper/DataSourceMapper.java +++ b/dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/mapper/DataSourceMapper.java @@ -80,12 +80,12 @@ public interface DataSourceMapper extends BaseMapper { List listAllDataSourceByType(@Param("type") Integer type); /** - * list authorized UDF function + * list authorized datasource * * @param userId userId * @param dataSourceIds data source id array * @param T - * @return UDF function list + * @return datasource list */ List listAuthorizedDataSource(@Param("userId") int userId, @Param("dataSourceIds") T[] dataSourceIds); diff --git a/dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/mapper/UDFUserMapper.java b/dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/mapper/UDFUserMapper.java deleted file mode 100644 index 2a5bc6a417..0000000000 --- a/dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/mapper/UDFUserMapper.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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. - */ -package org.apache.dolphinscheduler.dao.mapper; - -import org.apache.dolphinscheduler.dao.entity.UDFUser; - -import org.apache.ibatis.annotations.Param; - -import com.baomidou.mybatisplus.core.mapper.BaseMapper; - -/** - * udf user realtion mapper interface - */ -public interface UDFUserMapper extends BaseMapper { - - /** - * delete udf user realtion by userId - * @param userId userId - * @return delete result - */ - int deleteByUserId(@Param("userId") int userId); - - /** - * delete udf user realtion by function id - * @param udfFuncId udfFuncId - * @return delete result - */ - int deleteByUdfFuncId(@Param("udfFuncId") int udfFuncId); - -} diff --git a/dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/mapper/UdfFuncMapper.java b/dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/mapper/UdfFuncMapper.java deleted file mode 100644 index abc12f9aa1..0000000000 --- a/dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/mapper/UdfFuncMapper.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * 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. - */ -package org.apache.dolphinscheduler.dao.mapper; - -import org.apache.dolphinscheduler.dao.entity.UdfFunc; - -import org.apache.ibatis.annotations.Param; - -import java.util.List; - -import com.baomidou.mybatisplus.core.mapper.BaseMapper; -import com.baomidou.mybatisplus.core.metadata.IPage; - -/** - * udf function mapper interface - */ -public interface UdfFuncMapper extends BaseMapper { - - /** - * select udf by id - * @param id udf id - * @return UdfFunc - */ - UdfFunc selectUdfById(@Param("id") int id); - - /** - * query udf function by ids and function name - * @param ids ids - * @param funcNames funcNames - * @return udf function list - */ - List queryUdfByIdStr(@Param("ids") Integer[] ids, - @Param("funcNames") String funcNames); - - /** - * udf function page - * @param page page - * @param ids userId - * @param searchVal searchVal - * @return udf function IPage - */ - IPage queryUdfFuncPaging(IPage page, - @Param("ids") List ids, - @Param("searchVal") String searchVal); - - /** - * query udf function by type - * @param ids userId - * @param type type - * @return udf function list - */ - List getUdfFuncByType(@Param("ids") List ids, - @Param("type") Integer type); - - /** - * query udf function except userId - * @param userId userId - * @return udf function list - */ - List queryUdfFuncExceptUserId(@Param("userId") int userId); - - /** - * query authed udf function - * @param userId userId - * @return udf function list - */ - List queryAuthedUdfFunc(@Param("userId") int userId); - - /** - * list authorized UDF function - * @param userId userId - * @param udfIds UDF function id array - * @return UDF function list - */ - List listAuthorizedUdfFunc(@Param("userId") int userId, @Param("udfIds") T[] udfIds); - - /** - * list UDF by resource id - * @param resourceIds resource id array - * @return UDF function list - */ - List listUdfByResourceId(@Param("resourceIds") Integer[] resourceIds); - - /** - * list UDF by resource fullName - * @param resourceFullNames resource fullName array - * @return UDF function list - */ - List listUdfByResourceFullName(@Param("resourceFullNames") String[] resourceFullNames); - - /** - * list authorized UDF by resource id - * @param resourceIds resource id array - * @return UDF function list - */ - List listAuthorizedUdfByResourceId(@Param("userId") int userId, @Param("resourceIds") int[] resourceIds); - - /** - * listAuthorizedUdfByUserId - * @param userId - * @return - */ - List listAuthorizedUdfByUserId(@Param("userId") int userId); -} diff --git a/dolphinscheduler-dao/src/main/resources/org/apache/dolphinscheduler/dao/mapper/UDFUserMapper.xml b/dolphinscheduler-dao/src/main/resources/org/apache/dolphinscheduler/dao/mapper/UDFUserMapper.xml deleted file mode 100644 index 61b4e2c372..0000000000 --- a/dolphinscheduler-dao/src/main/resources/org/apache/dolphinscheduler/dao/mapper/UDFUserMapper.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - delete from t_ds_relation_udfs_user - where user_id = #{userId} - - - delete from t_ds_relation_udfs_user - where udf_id = #{udfFuncId} - - \ No newline at end of file diff --git a/dolphinscheduler-dao/src/main/resources/org/apache/dolphinscheduler/dao/mapper/UdfFuncMapper.xml b/dolphinscheduler-dao/src/main/resources/org/apache/dolphinscheduler/dao/mapper/UdfFuncMapper.xml deleted file mode 100644 index 84f9fd9708..0000000000 --- a/dolphinscheduler-dao/src/main/resources/org/apache/dolphinscheduler/dao/mapper/UdfFuncMapper.xml +++ /dev/null @@ -1,189 +0,0 @@ - - - - - - - - ${alias}.id, ${alias}.user_id, ${alias}.func_name, ${alias}.class_name, ${alias}.type, ${alias}.arg_types, - ${alias}.database, ${alias}.description, ${alias}.resource_id, ${alias}.resource_name, ${alias}.create_time, ${alias}.update_time - - - - - - - - - - - - - - - - - diff --git a/dolphinscheduler-dao/src/test/java/org/apache/dolphinscheduler/dao/entity/UdfFuncTest.java b/dolphinscheduler-dao/src/test/java/org/apache/dolphinscheduler/dao/entity/UdfFuncTest.java deleted file mode 100644 index a4017f356f..0000000000 --- a/dolphinscheduler-dao/src/test/java/org/apache/dolphinscheduler/dao/entity/UdfFuncTest.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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. - */ - -package org.apache.dolphinscheduler.dao.entity; - -import org.apache.dolphinscheduler.dao.entity.UdfFunc.UdfFuncDeserializer; - -import java.io.IOException; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -public class UdfFuncTest { - - /** - * test UdfFuncDeserializer.deserializeKey - * - * @throws IOException - */ - @Test - public void testUdfFuncDeserializer() throws IOException { - - // UdfFuncDeserializer.deserializeKey key is null - UdfFuncDeserializer udfFuncDeserializer = new UdfFuncDeserializer(); - Assertions.assertNull(udfFuncDeserializer.deserializeKey(null, null)); - - // - UdfFunc udfFunc = new UdfFunc(); - udfFunc.setResourceName("dolphin_resource_update"); - udfFunc.setResourceId(2); - udfFunc.setClassName("org.apache.dolphinscheduler.test.mrUpdate"); - - Assertions.assertNotNull(udfFuncDeserializer.deserializeKey(udfFunc.toString(), null)); - } - -} diff --git a/dolphinscheduler-dao/src/test/java/org/apache/dolphinscheduler/dao/mapper/UDFUserMapperTest.java b/dolphinscheduler-dao/src/test/java/org/apache/dolphinscheduler/dao/mapper/UDFUserMapperTest.java deleted file mode 100644 index c726e61385..0000000000 --- a/dolphinscheduler-dao/src/test/java/org/apache/dolphinscheduler/dao/mapper/UDFUserMapperTest.java +++ /dev/null @@ -1,184 +0,0 @@ -/* - * 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. - */ -package org.apache.dolphinscheduler.dao.mapper; - -import org.apache.dolphinscheduler.common.enums.UdfType; -import org.apache.dolphinscheduler.common.enums.UserType; -import org.apache.dolphinscheduler.dao.BaseDaoTest; -import org.apache.dolphinscheduler.dao.entity.UDFUser; -import org.apache.dolphinscheduler.dao.entity.UdfFunc; -import org.apache.dolphinscheduler.dao.entity.User; - -import java.util.Date; -import java.util.List; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; - -public class UDFUserMapperTest extends BaseDaoTest { - - @Autowired - private UDFUserMapper udfUserMapper; - - @Autowired - private UserMapper userMapper; - - @Autowired - private UdfFuncMapper udfFuncMapper; - - /** - * insert - * @return UDFUser - */ - private UDFUser insertOne() { - UDFUser udfUser = new UDFUser(); - udfUser.setUdfId(1); - udfUser.setUserId(1); - udfUser.setCreateTime(new Date()); - udfUser.setUpdateTime(new Date()); - udfUserMapper.insert(udfUser); - return udfUser; - } - - /** - * insert UDFUser - * @param user user - * @param udfFunc udfFunc - * @return UDFUser - */ - private UDFUser insertOne(User user, UdfFunc udfFunc) { - UDFUser udfUser = new UDFUser(); - udfUser.setUdfId(udfFunc.getId()); - udfUser.setUserId(user.getId()); - udfUser.setCreateTime(new Date()); - udfUser.setUpdateTime(new Date()); - udfUserMapper.insert(udfUser); - return udfUser; - } - - /** - * insert one user - * @return User - */ - private User insertOneUser() { - User user = new User(); - user.setUserName("user1"); - user.setUserPassword("1"); - user.setEmail("xx@123.com"); - user.setUserType(UserType.GENERAL_USER); - user.setCreateTime(new Date()); - user.setTenantId(1); - user.setQueue("dolphin"); - user.setUpdateTime(new Date()); - userMapper.insert(user); - return user; - } - - /** - * insert one udf - * @return UdfFunc - */ - private UdfFunc insertOneUdfFunc() { - UdfFunc udfFunc = new UdfFunc(); - udfFunc.setFuncName("dolphin_udf_func"); - udfFunc.setClassName("org.apache.dolphinscheduler.test.mr"); - udfFunc.setType(UdfType.HIVE); - udfFunc.setResourceId(1); - udfFunc.setResourceName("dolphin_resource"); - udfFunc.setCreateTime(new Date()); - udfFunc.setUpdateTime(new Date()); - udfFuncMapper.insert(udfFunc); - return udfFunc; - } - - /** - * test update - */ - @Test - public void testUpdate() { - // insertOneUser - User user = insertOneUser(); - // insertOneUdfFunc - UdfFunc udfFunc = insertOneUdfFunc(); - // insertOne - UDFUser udfUser = insertOne(user, udfFunc); - udfUser.setUserId(2); - udfUser.setUdfId(2); - int update = udfUserMapper.updateById(udfUser); - Assertions.assertEquals(update, 1); - - } - - /** - * test delete - */ - @Test - public void testDelete() { - // insertOneUser - User user = insertOneUser(); - // insertOneUdfFunc - UdfFunc udfFunc = insertOneUdfFunc(); - // insertOne - UDFUser udfUser = insertOne(user, udfFunc); - int delete = udfUserMapper.deleteById(udfUser.getId()); - Assertions.assertEquals(delete, 1); - } - - /** - * test query - */ - @Test - public void testQuery() { - // insertOne - UDFUser udfUser = insertOne(); - // query - List udfUserList = udfUserMapper.selectList(null); - Assertions.assertNotEquals(0, udfUserList.size()); - } - - /** - * test delete by userId - */ - @Test - public void testDeleteByUserId() { - // insertOneUser - User user = insertOneUser(); - // insertOneUdfFunc - UdfFunc udfFunc = insertOneUdfFunc(); - // insertOne - UDFUser udfUser = insertOne(user, udfFunc); - int delete = udfUserMapper.deleteByUserId(user.getId()); - Assertions.assertEquals(1, delete); - - } - - /** - * test delete by udffuncId - */ - @Test - public void testDeleteByUdfFuncId() { - // insertOneUser - User user = insertOneUser(); - // insertOneUdfFunc - UdfFunc udfFunc = insertOneUdfFunc(); - // insertOne - UDFUser udfUser = insertOne(user, udfFunc); - int delete = udfUserMapper.deleteByUdfFuncId(udfFunc.getId()); - Assertions.assertEquals(1, delete); - } -} diff --git a/dolphinscheduler-dao/src/test/java/org/apache/dolphinscheduler/dao/mapper/UdfFuncMapperTest.java b/dolphinscheduler-dao/src/test/java/org/apache/dolphinscheduler/dao/mapper/UdfFuncMapperTest.java deleted file mode 100644 index 5d04a022a7..0000000000 --- a/dolphinscheduler-dao/src/test/java/org/apache/dolphinscheduler/dao/mapper/UdfFuncMapperTest.java +++ /dev/null @@ -1,280 +0,0 @@ -/* - * 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. - */ -package org.apache.dolphinscheduler.dao.mapper; - -import static java.util.stream.Collectors.toList; - -import org.apache.dolphinscheduler.common.enums.UdfType; -import org.apache.dolphinscheduler.common.enums.UserType; -import org.apache.dolphinscheduler.dao.BaseDaoTest; -import org.apache.dolphinscheduler.dao.entity.UDFUser; -import org.apache.dolphinscheduler.dao.entity.UdfFunc; -import org.apache.dolphinscheduler.dao.entity.User; - -import java.util.Arrays; -import java.util.Collections; -import java.util.Date; -import java.util.List; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; - -import com.baomidou.mybatisplus.core.metadata.IPage; -import com.baomidou.mybatisplus.extension.plugins.pagination.Page; - -public class UdfFuncMapperTest extends BaseDaoTest { - - @Autowired - private UserMapper userMapper; - - @Autowired - private UdfFuncMapper udfFuncMapper; - - @Autowired - private UDFUserMapper udfUserMapper; - - /** - * insert one udf - * - * @return UdfFunc - */ - private UdfFunc insertOne(String funcName) { - UdfFunc udfFunc = new UdfFunc(); - udfFunc.setUserId(1); - udfFunc.setFuncName(funcName); - udfFunc.setClassName("org.apache.dolphinscheduler.test.mr"); - udfFunc.setType(UdfType.HIVE); - udfFunc.setResourceId(1); - udfFunc.setResourceName("dolphin_resource"); - udfFunc.setCreateTime(new Date()); - udfFunc.setUpdateTime(new Date()); - udfFuncMapper.insert(udfFunc); - return udfFunc; - } - - /** - * insert one udf - * - * @return - */ - private UdfFunc insertOne(User user) { - UdfFunc udfFunc = new UdfFunc(); - udfFunc.setUserId(user.getId()); - udfFunc.setFuncName("dolphin_udf_func" + user.getUserName()); - udfFunc.setClassName("org.apache.dolphinscheduler.test.mr"); - udfFunc.setType(UdfType.HIVE); - udfFunc.setResourceId(1); - udfFunc.setResourceName("dolphin_resource"); - udfFunc.setCreateTime(new Date()); - udfFunc.setUpdateTime(new Date()); - udfFuncMapper.insert(udfFunc); - return udfFunc; - } - - /** - * insert one user - * - * @return User - */ - private User insertOneUser() { - return insertOneUser("user1"); - } - - /** - * insert one user - * - * @return User - */ - private User insertOneUser(String userName) { - return createGeneralUser(userName); - } - - /** - * create general user - * - * @return User - */ - private User createGeneralUser(String userName) { - User user = new User(); - user.setUserName(userName); - user.setUserPassword("1"); - user.setEmail("xx@123.com"); - user.setUserType(UserType.GENERAL_USER); - user.setCreateTime(new Date()); - user.setTenantId(1); - user.setUpdateTime(new Date()); - userMapper.insert(user); - return user; - } - - /** - * insert UDFUser - * - * @param user user - * @param udfFunc udf func - * @return UDFUser - */ - private UDFUser insertOneUDFUser(User user, UdfFunc udfFunc) { - UDFUser udfUser = new UDFUser(); - udfUser.setUdfId(udfFunc.getId()); - udfUser.setUserId(user.getId()); - udfUser.setCreateTime(new Date()); - udfUser.setUpdateTime(new Date()); - udfUserMapper.insert(udfUser); - return udfUser; - } - - /** - * test update - */ - @Test - public void testUpdate() { - // insertOne - UdfFunc udfFunc = insertOne("func1"); - udfFunc.setResourceName("dolphin_resource_update"); - udfFunc.setResourceId(2); - udfFunc.setClassName("org.apache.dolphinscheduler.test.mrUpdate"); - udfFunc.setUpdateTime(new Date()); - // update - int update = udfFuncMapper.updateById(udfFunc); - Assertions.assertEquals(update, 1); - - } - - /** - * test delete - */ - @Test - public void testDelete() { - // insertOne - UdfFunc udfFunc = insertOne("func2"); - // delete - int delete = udfFuncMapper.deleteById(udfFunc.getId()); - Assertions.assertEquals(delete, 1); - } - - /** - * test query udf by ids - */ - @Test - public void testQueryUdfByIdStr() { - // insertOne - UdfFunc udfFunc = insertOne("func3"); - // insertOne - UdfFunc udfFunc1 = insertOne("func4"); - Integer[] idArray = new Integer[]{udfFunc.getId(), udfFunc1.getId()}; - // queryUdfByIdStr - List udfFuncList = udfFuncMapper.queryUdfByIdStr(idArray, ""); - Assertions.assertNotEquals(0, udfFuncList.size()); - } - - /** - * test page - */ - @Test - public void testQueryUdfFuncPaging() { - // insertOneUser - User user = insertOneUser(); - // insertOne - UdfFunc udfFunc = insertOne(user); - // queryUdfFuncPaging - Page page = new Page(1, 3); - - IPage udfFuncIPage = - udfFuncMapper.queryUdfFuncPaging(page, Collections.singletonList(udfFunc.getId()), ""); - Assertions.assertNotEquals(0, udfFuncIPage.getTotal()); - - } - - /** - * test get udffunc by type - */ - @Test - public void testGetUdfFuncByType() { - // insertOneUser - User user = insertOneUser(); - // insertOne - UdfFunc udfFunc = insertOne(user); - // getUdfFuncByType - List udfFuncList = - udfFuncMapper.getUdfFuncByType(Collections.singletonList(udfFunc.getId()), udfFunc.getType().ordinal()); - Assertions.assertNotEquals(0, udfFuncList.size()); - - } - - /** - * test query udffunc expect userId - */ - @Test - public void testQueryUdfFuncExceptUserId() { - // insertOneUser - User user1 = insertOneUser(); - User user2 = insertOneUser("user2"); - // insertOne - UdfFunc udfFunc1 = insertOne(user1); - UdfFunc udfFunc2 = insertOne(user2); - List udfFuncList = udfFuncMapper.queryUdfFuncExceptUserId(user1.getId()); - Assertions.assertNotEquals(0, udfFuncList.size()); - - } - - /** - * test query authed udffunc - */ - @Test - public void testQueryAuthedUdfFunc() { - // insertOneUser - User user = insertOneUser(); - - // insertOne - UdfFunc udfFunc = insertOne(user); - - // insertOneUDFUser - UDFUser udfUser = insertOneUDFUser(user, udfFunc); - // queryAuthedUdfFunc - List udfFuncList = udfFuncMapper.queryAuthedUdfFunc(user.getId()); - Assertions.assertNotEquals(0, udfFuncList.size()); - } - - @Test - public void testListAuthorizedUdfFunc() { - // create general user - User generalUser1 = createGeneralUser("user1"); - User generalUser2 = createGeneralUser("user2"); - - // create udf function - UdfFunc udfFunc = insertOne(generalUser1); - UdfFunc unauthorizdUdfFunc = insertOne(generalUser2); - - // udf function ids - Integer[] udfFuncIds = new Integer[]{udfFunc.getId(), unauthorizdUdfFunc.getId()}; - - List authorizedUdfFunc = udfFuncMapper.listAuthorizedUdfFunc(generalUser1.getId(), udfFuncIds); - - Assertions.assertEquals(generalUser1.getId().intValue(), udfFunc.getUserId()); - Assertions.assertNotEquals(generalUser1.getId().intValue(), unauthorizdUdfFunc.getUserId()); - Assertions.assertFalse(authorizedUdfFunc.stream().map(t -> t.getId()).collect(toList()) - .containsAll(Arrays.asList(udfFuncIds))); - - // authorize object unauthorizdUdfFunc to generalUser1 - insertOneUDFUser(generalUser1, unauthorizdUdfFunc); - authorizedUdfFunc = udfFuncMapper.listAuthorizedUdfFunc(generalUser1.getId(), udfFuncIds); - Assertions.assertTrue(authorizedUdfFunc.stream().map(t -> t.getId()).collect(toList()) - .containsAll(Arrays.asList(udfFuncIds))); - } -} diff --git a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/FunctionManageE2ETest.java b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/FunctionManageE2ETest.java deleted file mode 100644 index 144c430eee..0000000000 --- a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/FunctionManageE2ETest.java +++ /dev/null @@ -1,188 +0,0 @@ -/* - * 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. - * - */ - -package org.apache.dolphinscheduler.e2e.cases; - -import lombok.SneakyThrows; -import static org.assertj.core.api.Assertions.assertThat; - -import org.apache.dolphinscheduler.e2e.core.Constants; -import org.apache.dolphinscheduler.e2e.core.DolphinScheduler; -import org.apache.dolphinscheduler.e2e.pages.LoginPage; -import org.apache.dolphinscheduler.e2e.pages.resource.FunctionManagePage; -import org.apache.dolphinscheduler.e2e.pages.resource.ResourcePage; -import org.apache.dolphinscheduler.e2e.pages.resource.UdfManagePage; -import org.apache.dolphinscheduler.e2e.pages.security.SecurityPage; -import org.apache.dolphinscheduler.e2e.pages.security.TenantPage; -import org.apache.dolphinscheduler.e2e.pages.security.UserPage; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.InputStream; -import java.net.URL; -import java.net.URLConnection; -import java.nio.file.Files; -import java.nio.file.Path; -import java.time.Duration; -import java.util.Comparator; - -import org.testcontainers.shaded.org.awaitility.Awaitility; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Order; -import org.junit.jupiter.api.Test; -import org.openqa.selenium.By; -import org.openqa.selenium.WebElement; -import org.openqa.selenium.remote.RemoteWebDriver; -import org.openqa.selenium.support.ui.ExpectedConditions; -import org.openqa.selenium.support.ui.WebDriverWait; - -@DolphinScheduler(composeFiles = "docker/file-manage/docker-compose.yaml") -public class FunctionManageE2ETest { - private static RemoteWebDriver browser; - - private static final String tenant = System.getProperty("user.name"); - - private static final String user = "admin"; - - private static final String password = "dolphinscheduler123"; - - private static final String email = "admin@gmail.com"; - - private static final String phone = "15800000000"; - - private static final String testUdfFunctionName = "test_function"; - - private static final String testRenameUdfFunctionName = "test_rename_function"; - - private static final String testUploadUdfFileName = "hive-jdbc-3.1.2.jar"; - - private static final String testClassName = "org.dolphinscheduler.UdfTest"; - - private static final String testDescription = "test_description"; - - private static final Path testUploadUdfFilePath = Constants.HOST_TMP_PATH.resolve(testUploadUdfFileName); - - @BeforeAll - @SneakyThrows - public static void setup() { - TenantPage tenantPage = new LoginPage(browser) - .login(user, password) - .goToNav(SecurityPage.class) - .goToTab(TenantPage.class) - .create(tenant); - - Awaitility.await().untilAsserted(() -> assertThat(tenantPage.tenantList()) - .as("Tenant list should contain newly-created tenant") - .extracting(WebElement::getText) - .anyMatch(it -> it.contains(tenant))); - - downloadFile("https://repo1.maven.org/maven2/org/apache/hive/hive-jdbc/3.1.2/hive-jdbc-3.1.2.jar", testUploadUdfFilePath.toFile().getAbsolutePath()); - - UserPage userPage = tenantPage.goToNav(SecurityPage.class) - .goToTab(UserPage.class); - - new WebDriverWait(userPage.driver(), Duration.ofSeconds(20)).until(ExpectedConditions.visibilityOfElementLocated( - new By.ByClassName("name"))); - - UdfManagePage udfManagePage = userPage.update(user, user, email, phone, tenant) - .goToNav(ResourcePage.class) - .goToTab(UdfManagePage.class) - .uploadFile(testUploadUdfFilePath.toFile().getAbsolutePath()); - - udfManagePage.goToNav(ResourcePage.class) - .goToTab(FunctionManagePage.class); - } - - @AfterAll - @SneakyThrows - public static void cleanup() { - Files.walk(Constants.HOST_CHROME_DOWNLOAD_PATH) - .sorted(Comparator.reverseOrder()) - .map(Path::toFile) - .forEach(File::delete); - - Files.deleteIfExists(testUploadUdfFilePath); - } - - static void downloadFile(String downloadUrl, String filePath) throws Exception { - int byteRead; - - URL url = new URL(downloadUrl); - - URLConnection conn = url.openConnection(); - InputStream inStream = conn.getInputStream(); - FileOutputStream fs = new FileOutputStream(filePath); - - byte[] buffer = new byte[1024]; - while ((byteRead = inStream.read(buffer)) != -1) { - fs.write(buffer, 0, byteRead); - } - - inStream.close(); - fs.close(); - } - - @Test - @Order(10) - void testCreateUdfFunction() { - FunctionManagePage page = new FunctionManagePage(browser); - - page.createUdfFunction(testUdfFunctionName, testClassName, testUploadUdfFileName, testDescription); - - Awaitility.await().untilAsserted(() -> assertThat(page.functionList()) - .as("Function list should contain newly-created file") - .extracting(WebElement::getText) - .anyMatch(it -> it.contains(testUdfFunctionName))); - } - - @Test - @Order(20) - void testRenameUdfFunction() { - FunctionManagePage page = new FunctionManagePage(browser); - - browser.navigate().refresh(); - - page.renameUdfFunction(testUdfFunctionName, testRenameUdfFunctionName); - - Awaitility.await().pollDelay(Duration.ofSeconds(2)).untilAsserted(() -> assertThat(page.functionList()) - .as("Function list should contain newly-created file") - .extracting(WebElement::getText) - .anyMatch(it -> it.contains(testRenameUdfFunctionName))); - } - - @Test - @Order(30) - void testDeleteUdfFunction() { - FunctionManagePage page = new FunctionManagePage(browser); - - page.deleteUdfFunction(testRenameUdfFunctionName); - - Awaitility.await().untilAsserted(() -> { - browser.navigate().refresh(); - - assertThat( - page.functionList() - ).noneMatch( - it -> it.getText().contains(testRenameUdfFunctionName) - ); - }); - } -} diff --git a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/UdfManageE2ETest.java b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/UdfManageE2ETest.java deleted file mode 100644 index 07610f9b12..0000000000 --- a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/UdfManageE2ETest.java +++ /dev/null @@ -1,229 +0,0 @@ -/* - * 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. - * - */ - -package org.apache.dolphinscheduler.e2e.cases; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.apache.dolphinscheduler.e2e.core.Constants; -import org.apache.dolphinscheduler.e2e.core.DolphinScheduler; -import org.apache.dolphinscheduler.e2e.pages.LoginPage; -import org.apache.dolphinscheduler.e2e.pages.resource.ResourcePage; -import org.apache.dolphinscheduler.e2e.pages.resource.UdfManagePage; -import org.apache.dolphinscheduler.e2e.pages.security.SecurityPage; -import org.apache.dolphinscheduler.e2e.pages.security.TenantPage; -import org.apache.dolphinscheduler.e2e.pages.security.UserPage; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.InputStream; -import java.net.URL; -import java.net.URLConnection; -import java.nio.file.Files; -import java.nio.file.Path; -import java.time.Duration; -import java.util.Comparator; - -import org.testcontainers.shaded.org.awaitility.Awaitility; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Order; -import org.junit.jupiter.api.Test; -import org.openqa.selenium.By; -import org.openqa.selenium.WebElement; -import org.openqa.selenium.remote.RemoteWebDriver; -import org.openqa.selenium.support.ui.ExpectedConditions; -import org.openqa.selenium.support.ui.WebDriverWait; - -import lombok.SneakyThrows; - -@DolphinScheduler(composeFiles = "docker/file-manage/docker-compose.yaml") -public class UdfManageE2ETest { - private static RemoteWebDriver browser; - - private static final String tenant = System.getProperty("user.name"); - - private static final String user = "admin"; - - private static final String password = "dolphinscheduler123"; - - private static final String email = "admin@gmail.com"; - - private static final String phone = "15800000000"; - - private static final String testDirectoryName = "test_directory"; - - private static final String testRenameDirectoryName = "test_rename_directory"; - - private static final String testUploadUdfFileName = "hive-jdbc-3.1.2.jar"; - - private static final Path testUploadUdfFilePath = Constants.HOST_TMP_PATH.resolve(testUploadUdfFileName); - - private static final String testUploadUdfRenameFileName = "hive-jdbc.jar"; - - @BeforeAll - public static void setup() { - TenantPage tenantPage = new LoginPage(browser) - .login(user, password) - .goToNav(SecurityPage.class) - .goToTab(TenantPage.class) - .create(tenant); - - Awaitility.await().untilAsserted(() -> assertThat(tenantPage.tenantList()) - .as("Tenant list should contain newly-created tenant") - .extracting(WebElement::getText) - .anyMatch(it -> it.contains(tenant))); - - UserPage userPage = tenantPage.goToNav(SecurityPage.class) - .goToTab(UserPage.class); - - new WebDriverWait(userPage.driver(), Duration.ofSeconds(20)).until(ExpectedConditions.visibilityOfElementLocated( - new By.ByClassName("name"))); - - userPage.update(user, user, email, phone, tenant) - .goToNav(ResourcePage.class) - .goToTab(UdfManagePage.class); - } - - @AfterAll - @SneakyThrows - public static void cleanup() { - Files.walk(Constants.HOST_CHROME_DOWNLOAD_PATH) - .sorted(Comparator.reverseOrder()) - .map(Path::toFile) - .forEach(File::delete); - - Files.deleteIfExists(testUploadUdfFilePath); - } - - @Test - @Order(10) - void testCreateDirectory() { - final UdfManagePage page = new UdfManagePage(browser); - - new WebDriverWait(page.driver(), Duration.ofSeconds(20)) - .until(ExpectedConditions.urlContains("/resource-manage")); - page.createDirectory(testDirectoryName); - Awaitility.await().untilAsserted(() -> assertThat(page.udfList()) - .as("File list should contain newly-created file") - .extracting(WebElement::getText) - .anyMatch(it -> it.contains(testDirectoryName))); - } - -//when s3 the directory cannot be renamed -// @Test -// @Order(20) -// void testRenameDirectory() { -// final UdfManagePage page = new UdfManagePage(browser); -// -// page.rename(testDirectoryName, testRenameDirectoryName); -// -// await().untilAsserted(() -> { -// browser.navigate().refresh(); -// -// assertThat(page.udfList()) -// .as("File list should contain newly-created file") -// .extracting(WebElement::getText) -// .anyMatch(it -> it.contains(testRenameDirectoryName)); -// }); -// } - - @Test - @Order(30) - void testDeleteDirectory() { - final UdfManagePage page = new UdfManagePage(browser); - page.delete(testDirectoryName); - - Awaitility.await().untilAsserted(() -> { - browser.navigate().refresh(); - - assertThat( - page.udfList() - ).noneMatch( - it -> it.getText().contains(testDirectoryName) - ); - }); - } - - @Test - @Order(40) - @SneakyThrows - void testUploadUdf() { - final UdfManagePage page = new UdfManagePage(browser); - - downloadFile("https://repo1.maven.org/maven2/org/apache/hive/hive-jdbc/3.1.2/hive-jdbc-3.1.2.jar", testUploadUdfFilePath.toFile().getAbsolutePath()); - page.uploadFile(testUploadUdfFilePath.toFile().getAbsolutePath()); - Awaitility.await().untilAsserted(() -> { - assertThat(page.udfList()) - .as("File list should contain newly-created file") - .extracting(WebElement::getText) - .anyMatch(it -> it.contains(testUploadUdfFileName)); - }); - } - - void downloadFile(String downloadUrl, String filePath) throws Exception { - int byteRead; - - URL url = new URL(downloadUrl); - - URLConnection conn = url.openConnection(); - InputStream inStream = conn.getInputStream(); - FileOutputStream fs = new FileOutputStream(filePath); - - byte[] buffer = new byte[1024]; - while ((byteRead = inStream.read(buffer)) != -1) { - fs.write(buffer, 0, byteRead); - } - - inStream.close(); - fs.close(); - } - - @Test - @Order(60) - void testRenameUdf() { - final UdfManagePage page = new UdfManagePage(browser); - page.rename(testUploadUdfFileName, testUploadUdfRenameFileName); - - Awaitility.await().untilAsserted(() -> { - assertThat(page.udfList()) - .as("File list should contain newly-created file") - .extracting(WebElement::getText) - .anyMatch(it -> it.contains(testUploadUdfRenameFileName)); - }); - } - - @Test - @Order(70) - void testDeleteUdf() { - final UdfManagePage page = new UdfManagePage(browser); - page.delete(testUploadUdfRenameFileName); - - Awaitility.await().untilAsserted(() -> { - browser.navigate().refresh(); - - assertThat( - page.udfList() - ).noneMatch( - it -> it.getText().contains(testUploadUdfRenameFileName) - ); - }); - } -} diff --git a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/resource/FunctionManagePage.java b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/resource/FunctionManagePage.java deleted file mode 100644 index fd9596e420..0000000000 --- a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/resource/FunctionManagePage.java +++ /dev/null @@ -1,201 +0,0 @@ -/* - * 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. - * - */ - -package org.apache.dolphinscheduler.e2e.pages.resource; - -import lombok.Getter; - -import org.apache.dolphinscheduler.e2e.pages.common.NavBarPage; - -import java.util.List; - -import org.openqa.selenium.By; -import org.openqa.selenium.JavascriptExecutor; -import org.openqa.selenium.Keys; -import org.openqa.selenium.WebElement; -import org.openqa.selenium.remote.RemoteWebDriver; -import org.openqa.selenium.support.FindBy; -import org.openqa.selenium.support.FindBys; -import org.openqa.selenium.support.PageFactory; -import org.openqa.selenium.support.ui.ExpectedConditions; -import org.openqa.selenium.support.ui.WebDriverWait; - -@Getter -public class FunctionManagePage extends NavBarPage implements ResourcePage.Tab { - @FindBy(className = "btn-create-udf-function") - private WebElement buttonCreateUdfFunction; - - @FindBy(className = "items") - private List functionList; - - @FindBys({ - @FindBy(className = "n-popconfirm__action"), - @FindBy(className = "n-button--primary-type"), - }) - private WebElement buttonConfirm; - - private CreateUdfFunctionBox createUdfFunctionBox; - - private RenameUdfFunctionBox renameUdfFunctionBox; - - public FunctionManagePage(RemoteWebDriver driver) { - super(driver); - - createUdfFunctionBox = new CreateUdfFunctionBox(); - - renameUdfFunctionBox = new RenameUdfFunctionBox(); - } - - public FunctionManagePage createUdfFunction(String udfFunctionName, String className, String udfResourceName, String description) { - buttonCreateUdfFunction().click(); - - ((JavascriptExecutor) driver).executeScript("arguments[0].click();", createUdfFunctionBox().radioFunctionType()); - - createUdfFunctionBox().inputFunctionName().sendKeys(udfFunctionName); - - createUdfFunctionBox().inputClassName().sendKeys(className); - - createUdfFunctionBox().inputDescription().sendKeys(description); - - createUdfFunctionBox().buttonUdfResourceDropDown().click(); - - createUdfFunctionBox().selectUdfResource() - .stream() - .filter(it -> it.getAttribute("innerHTML").contains(udfResourceName)) - .findFirst() - .orElseThrow(() -> new RuntimeException(String.format("No %s in udf resource list", udfResourceName))) - .click(); - - createUdfFunctionBox().buttonSubmit().click(); - - return this; - } - - public FunctionManagePage renameUdfFunction(String currentName, String afterName) { - functionList() - .stream() - .filter(it -> it.getText().contains(currentName)) - .flatMap(it -> it.findElements(By.className("btn-edit")).stream()) - .filter(WebElement::isDisplayed) - .findFirst() - .orElseThrow(() -> new RuntimeException("No rename button in function manage list")) - .click(); - - renameUdfFunctionBox().inputFunctionName().sendKeys(Keys.CONTROL + "a"); - renameUdfFunctionBox().inputFunctionName().sendKeys(Keys.BACK_SPACE); - renameUdfFunctionBox().inputFunctionName().sendKeys(afterName); - - renameUdfFunctionBox.buttonSubmit().click(); - - return this; - } - - public FunctionManagePage deleteUdfFunction(String udfFunctionName) { - functionList() - .stream() - .filter(it -> it.getText().contains(udfFunctionName)) - .flatMap(it -> it.findElements(By.className("btn-delete")).stream()) - .filter(WebElement::isDisplayed) - .findFirst() - .orElseThrow(() -> new RuntimeException("No delete button in udf resource list")) - .click(); - - ((JavascriptExecutor) driver).executeScript("arguments[0].click();", buttonConfirm()); - - return this; - } - - @Getter - public class CreateUdfFunctionBox { - CreateUdfFunctionBox() { - PageFactory.initElements(driver, this); - } - - @FindBys({ - @FindBy(className = "radio-function-type"), - @FindBy(tagName = "input"), - }) - private WebElement radioFunctionType; - - @FindBys({ - @FindBy(className = "input-function-name"), - @FindBy(tagName = "input"), - }) - private WebElement inputFunctionName; - - @FindBys({ - @FindBy(className = "input-class-name"), - @FindBy(tagName = "input"), - }) - private WebElement inputClassName; - - @FindBys({ - @FindBy(className = "btn-udf-resource-dropdown"), - @FindBy(className = "n-base-selection"), - }) - private WebElement buttonUdfResourceDropDown; - - @FindBy(className = "n-tree-node-content__text") - private List selectUdfResource; - - @FindBys({ - @FindBy(className = "input-description"), - @FindBy(tagName = "textarea"), - }) - private WebElement inputDescription; - - @FindBy(className = "btn-submit") - private WebElement buttonSubmit; - - @FindBy(className = "btn-cancel") - private WebElement buttonCancel; - } - - @Getter - public class RenameUdfFunctionBox { - RenameUdfFunctionBox() { - PageFactory.initElements(driver, this); - } - - @FindBys({ - @FindBy(className = "input-function-name"), - @FindBy(tagName = "input"), - }) - private WebElement inputFunctionName; - - @FindBys({ - @FindBy(className = "input-class-name"), - @FindBy(tagName = "input"), - }) - private WebElement inputClassName; - - @FindBys({ - @FindBy(className = "input-description"), - @FindBy(tagName = "textarea"), - }) - private WebElement inputDescription; - - @FindBy(className = "btn-submit") - private WebElement buttonSubmit; - - @FindBy(className = "btn-cancel") - private WebElement buttonCancel; - } -} diff --git a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/resource/ResourcePage.java b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/resource/ResourcePage.java index 377af2e1ac..933a6df85e 100644 --- a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/resource/ResourcePage.java +++ b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/resource/ResourcePage.java @@ -38,12 +38,6 @@ public class ResourcePage extends NavBarPage implements NavBarPage.NavBarItem { @FindBy(css = ".tab-vertical > .n-menu-item:nth-child(1) > .n-menu-item-content") private WebElement fileManageTab; - @FindBy(css = ".tab-vertical .n-submenu:nth-of-type(2) > .n-submenu-children > .n-menu-item:nth-of-type(1) > .n-menu-item-content") - private WebElement udfManageTab; - - @FindBy(css = ".tab-vertical .n-submenu:nth-of-type(2) > .n-submenu-children > .n-menu-item:nth-of-type(2) > .n-menu-item-content") - private WebElement functionManageTab; - public ResourcePage(RemoteWebDriver driver) { super(driver); } @@ -57,22 +51,6 @@ public class ResourcePage extends NavBarPage implements NavBarPage.NavBarItem { return tab.cast(new FileManagePage(driver)); } - if (tab == UdfManagePage.class) { - new WebDriverWait(driver, Duration.ofSeconds(20)).until(ExpectedConditions.urlContains("/resource")); - new WebDriverWait(driver, Duration.ofSeconds(20)).until(ExpectedConditions.elementToBeClickable(udfManageTab)); - ((JavascriptExecutor) driver).executeScript("arguments[0].click();", udfManageTab()); - new WebDriverWait(driver, Duration.ofSeconds(20)).until(ExpectedConditions.urlContains("/resource-manage")); - return tab.cast(new UdfManagePage(driver)); - } - - if (tab == FunctionManagePage.class) { - new WebDriverWait(driver, Duration.ofSeconds(20)).until(ExpectedConditions.urlContains("/resource")); - new WebDriverWait(driver, Duration.ofSeconds(20)).until(ExpectedConditions.elementToBeClickable(functionManageTab)); - ((JavascriptExecutor) driver).executeScript("arguments[0].click();", functionManageTab()); - new WebDriverWait(driver, Duration.ofSeconds(20)).until(ExpectedConditions.urlContains("/function-manage")); - return tab.cast(new FunctionManagePage(driver)); - } - throw new UnsupportedOperationException("Unknown tab: " + tab.getName()); } diff --git a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/resource/UdfManagePage.java b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/resource/UdfManagePage.java deleted file mode 100644 index 37e07dec75..0000000000 --- a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/resource/UdfManagePage.java +++ /dev/null @@ -1,197 +0,0 @@ -/* - * 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. - * - */ - -package org.apache.dolphinscheduler.e2e.pages.resource; - -import lombok.Getter; - -import org.apache.dolphinscheduler.e2e.pages.common.NavBarPage; - -import java.time.Duration; -import java.util.List; - -import org.openqa.selenium.By; -import org.openqa.selenium.JavascriptExecutor; -import org.openqa.selenium.WebElement; -import org.openqa.selenium.remote.LocalFileDetector; -import org.openqa.selenium.remote.RemoteWebDriver; -import org.openqa.selenium.support.FindBy; -import org.openqa.selenium.support.FindBys; -import org.openqa.selenium.support.PageFactory; -import org.openqa.selenium.support.ui.ExpectedConditions; -import org.openqa.selenium.support.ui.WebDriverWait; - -@Getter -public class UdfManagePage extends NavBarPage implements ResourcePage.Tab { - @FindBy(className = "btn-create-directory") - private WebElement buttonCreateDirectory; - - @FindBy(className = "btn-upload-resource") - private WebElement buttonUploadUdf; - - @FindBy(className = "items") - private List udfList; - - @FindBys({ - @FindBy(className = "n-popconfirm__action"), - @FindBy(className = "n-button--primary-type"), - }) - private WebElement buttonConfirm; - - private final UploadFileBox uploadFileBox; - - private final RenameBox renameBox; - - private final CreateDirectoryBox createDirectoryBox; - - public UdfManagePage(RemoteWebDriver driver) { - super(driver); - - uploadFileBox = new UploadFileBox(); - - renameBox = new RenameBox(); - - createDirectoryBox = new CreateDirectoryBox(); - } - - public UdfManagePage createDirectory(String name) { - buttonCreateDirectory().click(); - - createDirectoryBox().inputDirectoryName().sendKeys(name); - createDirectoryBox().buttonSubmit().click(); - - return this; - } - - public UdfManagePage uploadFile(String filePath) { - new WebDriverWait(driver, Duration.ofSeconds(20)).until(ExpectedConditions.elementToBeClickable(buttonUploadUdf)); - - buttonUploadUdf().click(); - - driver.setFileDetector(new LocalFileDetector()); - - uploadFileBox().buttonUpload().sendKeys(filePath); - uploadFileBox().buttonSubmit().click(); - - return this; - } - - public UdfManagePage downloadFile(String fileName) { - udfList() - .stream() - .filter(it -> it.getText().contains(fileName)) - .flatMap(it -> it.findElements(By.className("btn-download")).stream()) - .filter(WebElement::isDisplayed) - .findFirst() - .orElseThrow(() -> new RuntimeException("No download button in udf manage list")) - .click(); - - return this; - } - - public UdfManagePage rename(String currentName, String AfterName) { - udfList() - .stream() - .filter(it -> it.getText().contains(currentName)) - .flatMap(it -> it.findElements(By.className("btn-rename")).stream()) - .filter(WebElement::isDisplayed) - .findFirst() - .orElseThrow(() -> new RuntimeException("No rename button in udf manage list")) - .click(); - - renameBox().inputName().clear(); - renameBox().inputName().sendKeys(AfterName); - renameBox().buttonSubmit().click(); - - return this; - } - - public UdfManagePage delete(String name) { - udfList() - .stream() - .filter(it -> it.getText().contains(name)) - .flatMap(it -> it.findElements(By.className("btn-delete")).stream()) - .filter(WebElement::isDisplayed) - .findFirst() - .orElseThrow(() -> new RuntimeException("No delete button in udf manage list")) - .click(); - - ((JavascriptExecutor) driver).executeScript("arguments[0].click();", buttonConfirm()); - - return this; - } - - @Getter - public class RenameBox { - RenameBox() { - PageFactory.initElements(driver, this); - } - - @FindBys({ - @FindBy(className = "input-name"), - @FindBy(tagName = "input"), - }) - private WebElement inputName; - - @FindBy(className = "btn-submit") - private WebElement buttonSubmit; - - @FindBy(className = "btn-cancel") - private WebElement buttonCancel; - } - - @Getter - public class UploadFileBox { - UploadFileBox() { - PageFactory.initElements(driver, this); - } - - @FindBys({ - @FindBy(className = "btn-upload"), - @FindBy(tagName = "input"), - }) - private WebElement buttonUpload; - - @FindBy(className = "btn-submit") - private WebElement buttonSubmit; - - @FindBy(className = "btn-cancel") - private WebElement buttonCancel; - } - - @Getter - public class CreateDirectoryBox { - CreateDirectoryBox() { - PageFactory.initElements(driver, this); - } - - @FindBys({ - @FindBy(className = "input-directory-name"), - @FindBy(tagName = "input"), - }) - private WebElement inputDirectoryName; - - @FindBy(className = "btn-submit") - private WebElement buttonSubmit; - - @FindBy(className = "btn-cancel") - private WebElement buttonCancel; - } -} diff --git a/dolphinscheduler-master/src/main/java/org/apache/dolphinscheduler/server/master/runner/TaskExecutionContextFactory.java b/dolphinscheduler-master/src/main/java/org/apache/dolphinscheduler/server/master/runner/TaskExecutionContextFactory.java index da4f5308d0..aec31f5abc 100644 --- a/dolphinscheduler-master/src/main/java/org/apache/dolphinscheduler/server/master/runner/TaskExecutionContextFactory.java +++ b/dolphinscheduler-master/src/main/java/org/apache/dolphinscheduler/server/master/runner/TaskExecutionContextFactory.java @@ -38,7 +38,6 @@ import org.apache.dolphinscheduler.dao.entity.DqRuleExecuteSql; import org.apache.dolphinscheduler.dao.entity.DqRuleInputEntry; import org.apache.dolphinscheduler.dao.entity.ProcessInstance; import org.apache.dolphinscheduler.dao.entity.TaskInstance; -import org.apache.dolphinscheduler.dao.entity.UdfFunc; import org.apache.dolphinscheduler.plugin.task.api.DataQualityTaskExecutionContext; import org.apache.dolphinscheduler.plugin.task.api.K8sTaskExecutionContext; import org.apache.dolphinscheduler.plugin.task.api.TaskExecutionContext; @@ -53,7 +52,6 @@ import org.apache.dolphinscheduler.plugin.task.api.parameters.dataquality.DataQu import org.apache.dolphinscheduler.plugin.task.api.parameters.resource.AbstractResourceParameters; import org.apache.dolphinscheduler.plugin.task.api.parameters.resource.DataSourceParameters; import org.apache.dolphinscheduler.plugin.task.api.parameters.resource.ResourceParametersHelper; -import org.apache.dolphinscheduler.plugin.task.api.parameters.resource.UdfFuncParameters; import org.apache.dolphinscheduler.plugin.task.api.utils.JdbcUrlParser; import org.apache.dolphinscheduler.plugin.task.api.utils.MapUtils; import org.apache.dolphinscheduler.plugin.task.spark.SparkParameters; @@ -152,9 +150,6 @@ public class TaskExecutionContextFactory { case DATASOURCE: setTaskDataSourceResourceInfo(map); break; - case UDF: - setTaskUdfFuncResourceInfo(map); - break; default: break; } @@ -178,19 +173,6 @@ public class TaskExecutionContextFactory { }); } - private void setTaskUdfFuncResourceInfo(Map map) { - if (MapUtils.isEmpty(map)) { - return; - } - List udfFuncList = processService.queryUdfFunListByIds(map.keySet().toArray(new Integer[map.size()])); - - udfFuncList.forEach(udfFunc -> { - UdfFuncParameters udfFuncParameters = - JSONUtils.parseObject(JSONUtils.toJsonString(udfFunc), UdfFuncParameters.class); - map.put(udfFunc.getId(), udfFuncParameters); - }); - } - private void setDataQualityTaskRelation(DataQualityTaskExecutionContext dataQualityTaskExecutionContext, TaskInstance taskInstance, String tenantCode) { DataQualityParameters dataQualityParameters = @@ -406,6 +388,7 @@ public class TaskExecutionContextFactory { /** * The StatisticsValueWriterConfig will be used in DataQualityApplication that * writes the statistics value into dolphin scheduler datasource + * * @param dataQualityTaskExecutionContext */ private void setStatisticsValueWriterConfig(DataQualityTaskExecutionContext dataQualityTaskExecutionContext) { diff --git a/dolphinscheduler-service/src/main/java/org/apache/dolphinscheduler/service/process/ProcessService.java b/dolphinscheduler-service/src/main/java/org/apache/dolphinscheduler/service/process/ProcessService.java index 9a5692b4c0..b9e4d811f3 100644 --- a/dolphinscheduler-service/src/main/java/org/apache/dolphinscheduler/service/process/ProcessService.java +++ b/dolphinscheduler-service/src/main/java/org/apache/dolphinscheduler/service/process/ProcessService.java @@ -43,7 +43,6 @@ import org.apache.dolphinscheduler.dao.entity.TaskDefinition; import org.apache.dolphinscheduler.dao.entity.TaskDefinitionLog; import org.apache.dolphinscheduler.dao.entity.TaskGroupQueue; import org.apache.dolphinscheduler.dao.entity.TaskInstance; -import org.apache.dolphinscheduler.dao.entity.UdfFunc; import org.apache.dolphinscheduler.dao.entity.User; import org.apache.dolphinscheduler.service.exceptions.CronParseException; import org.apache.dolphinscheduler.service.model.TaskNode; @@ -120,8 +119,6 @@ public interface ProcessService { DataSource findDataSourceById(int id); - List queryUdfFunListByIds(Integer[] ids); - ProjectUser queryProjectWithUserByProcessInstanceId(int processInstanceId); List listUnauthorized(int userId, T[] needChecks, AuthorizationType authorizationType); diff --git a/dolphinscheduler-service/src/main/java/org/apache/dolphinscheduler/service/process/ProcessServiceImpl.java b/dolphinscheduler-service/src/main/java/org/apache/dolphinscheduler/service/process/ProcessServiceImpl.java index cfc1fc3a8c..2b0a8e073c 100644 --- a/dolphinscheduler-service/src/main/java/org/apache/dolphinscheduler/service/process/ProcessServiceImpl.java +++ b/dolphinscheduler-service/src/main/java/org/apache/dolphinscheduler/service/process/ProcessServiceImpl.java @@ -72,7 +72,6 @@ import org.apache.dolphinscheduler.dao.entity.TaskDefinitionLog; import org.apache.dolphinscheduler.dao.entity.TaskGroupQueue; import org.apache.dolphinscheduler.dao.entity.TaskInstance; import org.apache.dolphinscheduler.dao.entity.Tenant; -import org.apache.dolphinscheduler.dao.entity.UdfFunc; import org.apache.dolphinscheduler.dao.entity.User; import org.apache.dolphinscheduler.dao.mapper.ClusterMapper; import org.apache.dolphinscheduler.dao.mapper.CommandMapper; @@ -98,7 +97,6 @@ import org.apache.dolphinscheduler.dao.mapper.TaskGroupMapper; import org.apache.dolphinscheduler.dao.mapper.TaskGroupQueueMapper; import org.apache.dolphinscheduler.dao.mapper.TaskInstanceMapper; import org.apache.dolphinscheduler.dao.mapper.TenantMapper; -import org.apache.dolphinscheduler.dao.mapper.UdfFuncMapper; import org.apache.dolphinscheduler.dao.mapper.UserMapper; import org.apache.dolphinscheduler.dao.mapper.WorkFlowLineageMapper; import org.apache.dolphinscheduler.dao.repository.ProcessInstanceDao; @@ -213,9 +211,6 @@ public class ProcessServiceImpl implements ProcessService { @Autowired private ScheduleMapper scheduleMapper; - @Autowired - private UdfFuncMapper udfFuncMapper; - @Autowired private TenantMapper tenantMapper; @@ -1502,33 +1497,50 @@ public class ProcessServiceImpl implements ProcessService { } /** - * find udf function list by id list string + * query project name and user name by processInstanceId. * - * @param ids ids - * @return udf function list + * @param processInstanceId processInstanceId + * @return projectName and userName */ @Override - public List queryUdfFunListByIds(Integer[] ids) { - return udfFuncMapper.queryUdfByIdStr(ids, null); + public ProjectUser queryProjectWithUserByProcessInstanceId(int processInstanceId) { + return projectMapper.queryProjectWithUserByProcessInstanceId(processInstanceId); } /** - * query project name and user name by processInstanceId. + * get user by user id * - * @param processInstanceId processInstanceId - * @return projectName and userName + * @param userId user id + * @return User */ @Override - public ProjectUser queryProjectWithUserByProcessInstanceId(int processInstanceId) { - return projectMapper.queryProjectWithUserByProcessInstanceId(processInstanceId); + public User getUserById(int userId) { + return userMapper.selectById(userId); } /** - * list unauthorized udf function + * format task app id in task instance + */ + @Override + public String formatTaskAppId(TaskInstance taskInstance) { + ProcessInstance processInstance = findProcessInstanceById(taskInstance.getProcessInstanceId()); + if (processInstance == null) { + return ""; + } + ProcessDefinition definition = findProcessDefinition(processInstance.getProcessDefinitionCode(), + processInstance.getProcessDefinitionVersion()); + if (definition == null) { + return ""; + } + return String.format("%s_%s_%s", definition.getId(), processInstance.getId(), taskInstance.getId()); + } + + /** + * list unauthorized * * @param userId user id * @param needChecks data source id array - * @return unauthorized udf function list + * @return unauthorized */ @Override public List listUnauthorized(int userId, T[] needChecks, AuthorizationType authorizationType) { @@ -1543,11 +1555,6 @@ public class ProcessServiceImpl implements ProcessService { .stream().map(DataSource::getId).collect(toSet()); originResSet.removeAll(authorizedDatasources); break; - case UDF: - Set authorizedUdfs = udfFuncMapper.listAuthorizedUdfFunc(userId, needChecks).stream() - .map(UdfFunc::getId).collect(toSet()); - originResSet.removeAll(authorizedUdfs); - break; default: break; } @@ -1558,34 +1565,6 @@ public class ProcessServiceImpl implements ProcessService { return resultList; } - /** - * get user by user id - * - * @param userId user id - * @return User - */ - @Override - public User getUserById(int userId) { - return userMapper.selectById(userId); - } - - /** - * format task app id in task instance - */ - @Override - public String formatTaskAppId(TaskInstance taskInstance) { - ProcessInstance processInstance = findProcessInstanceById(taskInstance.getProcessInstanceId()); - if (processInstance == null) { - return ""; - } - ProcessDefinition definition = findProcessDefinition(processInstance.getProcessDefinitionCode(), - processInstance.getProcessDefinitionVersion()); - if (definition == null) { - return ""; - } - return String.format("%s_%s_%s", definition.getId(), processInstance.getId(), taskInstance.getId()); - } - /** * switch process definition version to process definition log version */ diff --git a/dolphinscheduler-service/src/test/java/org/apache/dolphinscheduler/service/process/ProcessServiceTest.java b/dolphinscheduler-service/src/test/java/org/apache/dolphinscheduler/service/process/ProcessServiceTest.java index 5c998e1f9f..a8b0f44ae9 100644 --- a/dolphinscheduler-service/src/test/java/org/apache/dolphinscheduler/service/process/ProcessServiceTest.java +++ b/dolphinscheduler-service/src/test/java/org/apache/dolphinscheduler/service/process/ProcessServiceTest.java @@ -736,7 +736,7 @@ public class ProcessServiceTest { processInstance.setId(62); taskInstance.setVarPool("[{\"direct\":\"OUT\",\"prop\":\"test1\",\"type\":\"VARCHAR\",\"value\":\"\"}]"); taskInstance.setTaskParams("{\"type\":\"MYSQL\",\"datasource\":1,\"sql\":\"select id from tb_test limit 1\"," - + "\"udfs\":\"\",\"sqlType\":\"0\",\"sendEmail\":false,\"displayRows\":10,\"title\":\"\"," + + "\"sqlType\":\"0\",\"sendEmail\":false,\"displayRows\":10,\"title\":\"\"," + "\"groupId\":null,\"localParams\":[{\"prop\":\"test1\",\"direct\":\"OUT\",\"type\":\"VARCHAR\",\"value\":\"12\"}]," + "\"connParams\":\"\",\"preStatements\":[],\"postStatements\":[],\"conditionResult\":\"{\\\"successNode\\\":[\\\"\\\"]," + "\\\"failedNode\\\":[\\\"\\\"]}\",\"dependence\":\"{}\"}"); diff --git a/dolphinscheduler-spi/src/main/java/org/apache/dolphinscheduler/spi/enums/ResourceType.java b/dolphinscheduler-spi/src/main/java/org/apache/dolphinscheduler/spi/enums/ResourceType.java index 2dd7ca0671..09132ebac9 100644 --- a/dolphinscheduler-spi/src/main/java/org/apache/dolphinscheduler/spi/enums/ResourceType.java +++ b/dolphinscheduler-spi/src/main/java/org/apache/dolphinscheduler/spi/enums/ResourceType.java @@ -17,36 +17,30 @@ package org.apache.dolphinscheduler.spi.enums; +import lombok.Getter; + import com.baomidou.mybatisplus.annotation.EnumValue; /** * resource type */ +@Getter public enum ResourceType { /** * 0 file, 1 udf */ FILE(0, "file"), - UDF(1, "udf"), ALL(2, "all"); - ResourceType(int code, String descp) { + ResourceType(int code, String desc) { this.code = code; - this.descp = descp; + this.desc = desc; } @EnumValue private final int code; - private final String descp; - - public int getCode() { - return code; - } - - public String getDescp() { - return descp; - } + private final String desc; public static ResourceType getResourceType(int code) { for (ResourceType resourceType : ResourceType.values()) { diff --git a/dolphinscheduler-storage-plugin/dolphinscheduler-storage-api/src/main/java/org/apache/dolphinscheduler/plugin/storage/api/AbstractStorageOperator.java b/dolphinscheduler-storage-plugin/dolphinscheduler-storage-api/src/main/java/org/apache/dolphinscheduler/plugin/storage/api/AbstractStorageOperator.java index 1ad94e47d9..34a4e464b5 100644 --- a/dolphinscheduler-storage-plugin/dolphinscheduler-storage-api/src/main/java/org/apache/dolphinscheduler/plugin/storage/api/AbstractStorageOperator.java +++ b/dolphinscheduler-storage-plugin/dolphinscheduler-storage-api/src/main/java/org/apache/dolphinscheduler/plugin/storage/api/AbstractStorageOperator.java @@ -51,7 +51,7 @@ public abstract class AbstractStorageOperator implements StorageOperator { .resourceBaseDirectory(storageBaseDirectory) .isDirectory(Files.getFileExtension(resourceAbsolutePath).isEmpty()) .tenant(segments[0]) - .resourceType(segments[1].equals(FILE_FOLDER_NAME) ? ResourceType.FILE : ResourceType.UDF) + .resourceType(ResourceType.FILE) .resourceRelativePath(segments.length == 2 ? "/" : segments[2]) .resourceParentAbsolutePath(StringUtils.substringBeforeLast(resourceAbsolutePath, File.separator)) .build(); @@ -83,9 +83,6 @@ public abstract class AbstractStorageOperator implements StorageOperator { case FILE: resourceBaseDirectory = FileUtils.concatFilePath(tenantBaseDirectory, FILE_FOLDER_NAME); break; - case UDF: - resourceBaseDirectory = FileUtils.concatFilePath(tenantBaseDirectory, UDF_FOLDER_NAME); - break; case ALL: resourceBaseDirectory = tenantBaseDirectory; break; diff --git a/dolphinscheduler-storage-plugin/dolphinscheduler-storage-hdfs/src/test/java/org/apache/dolphinscheduler/plugin/storage/hdfs/LocalStorageOperatorTest.java b/dolphinscheduler-storage-plugin/dolphinscheduler-storage-hdfs/src/test/java/org/apache/dolphinscheduler/plugin/storage/hdfs/LocalStorageOperatorTest.java index f6ed76e265..8c20d92566 100644 --- a/dolphinscheduler-storage-plugin/dolphinscheduler-storage-hdfs/src/test/java/org/apache/dolphinscheduler/plugin/storage/hdfs/LocalStorageOperatorTest.java +++ b/dolphinscheduler-storage-plugin/dolphinscheduler-storage-hdfs/src/test/java/org/apache/dolphinscheduler/plugin/storage/hdfs/LocalStorageOperatorTest.java @@ -118,13 +118,6 @@ class LocalStorageOperatorTest { .isEqualTo("file:" + Paths.get(resourceBaseDir, tenantCode, Constants.RESOURCE_TYPE_FILE)); } - @Test - public void testGetStorageBaseDirectory_withTenant_withResourceTypeUdf() { - String storageBaseDirectory = storageOperator.getStorageBaseDirectory("default", ResourceType.UDF); - assertThat(storageBaseDirectory) - .isEqualTo("file:" + Paths.get(resourceBaseDir, tenantCode, Constants.RESOURCE_TYPE_UDF)); - } - @Test public void testGetStorageBaseDirectory_withTenant_withResourceTypeAll() { String storageBaseDirectory = storageOperator.getStorageBaseDirectory("default", ResourceType.ALL); diff --git a/dolphinscheduler-task-plugin/dolphinscheduler-task-api/src/main/java/org/apache/dolphinscheduler/plugin/task/api/SQLTaskExecutionContext.java b/dolphinscheduler-task-plugin/dolphinscheduler-task-api/src/main/java/org/apache/dolphinscheduler/plugin/task/api/SQLTaskExecutionContext.java index e1d6432999..40d9c5632e 100644 --- a/dolphinscheduler-task-plugin/dolphinscheduler-task-api/src/main/java/org/apache/dolphinscheduler/plugin/task/api/SQLTaskExecutionContext.java +++ b/dolphinscheduler-task-plugin/dolphinscheduler-task-api/src/main/java/org/apache/dolphinscheduler/plugin/task/api/SQLTaskExecutionContext.java @@ -17,13 +17,10 @@ package org.apache.dolphinscheduler.plugin.task.api; -import org.apache.dolphinscheduler.plugin.task.api.parameters.resource.UdfFuncParameters; - import java.io.Serializable; -import java.util.List; /** - * SQL Task ExecutionContext + * SQL Task ExecutionContext */ public class SQLTaskExecutionContext implements Serializable { @@ -37,8 +34,6 @@ public class SQLTaskExecutionContext implements Serializable { */ private String connectionParams; - private List udfFuncParametersList; - /** * DefaultFS */ @@ -52,14 +47,6 @@ public class SQLTaskExecutionContext implements Serializable { this.warningGroupId = warningGroupId; } - public List getUdfFuncParametersList() { - return udfFuncParametersList; - } - - public void setUdfFuncParametersList(List udfFuncParametersList) { - this.udfFuncParametersList = udfFuncParametersList; - } - public String getConnectionParams() { return connectionParams; } @@ -81,7 +68,6 @@ public class SQLTaskExecutionContext implements Serializable { return "SQLTaskExecutionContext{" + "warningGroupId=" + warningGroupId + ", connectionParams='" + connectionParams + '\'' - + ", udfFuncParametersList=" + udfFuncParametersList + ", defaultFS='" + defaultFS + '\'' + '}'; } } diff --git a/dolphinscheduler-task-plugin/dolphinscheduler-task-api/src/main/java/org/apache/dolphinscheduler/plugin/task/api/enums/ResourceType.java b/dolphinscheduler-task-plugin/dolphinscheduler-task-api/src/main/java/org/apache/dolphinscheduler/plugin/task/api/enums/ResourceType.java index 62424aacdb..75281ae256 100644 --- a/dolphinscheduler-task-plugin/dolphinscheduler-task-api/src/main/java/org/apache/dolphinscheduler/plugin/task/api/enums/ResourceType.java +++ b/dolphinscheduler-task-plugin/dolphinscheduler-task-api/src/main/java/org/apache/dolphinscheduler/plugin/task/api/enums/ResourceType.java @@ -20,5 +20,5 @@ package org.apache.dolphinscheduler.plugin.task.api.enums; public enum ResourceType { DATASOURCE, - UDF; + ; } diff --git a/dolphinscheduler-task-plugin/dolphinscheduler-task-api/src/main/java/org/apache/dolphinscheduler/plugin/task/api/enums/UdfType.java b/dolphinscheduler-task-plugin/dolphinscheduler-task-api/src/main/java/org/apache/dolphinscheduler/plugin/task/api/enums/UdfType.java deleted file mode 100644 index b65dc07152..0000000000 --- a/dolphinscheduler-task-plugin/dolphinscheduler-task-api/src/main/java/org/apache/dolphinscheduler/plugin/task/api/enums/UdfType.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * 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. - */ - -package org.apache.dolphinscheduler.plugin.task.api.enums; - -/** - * UDF type - */ -public enum UdfType { - - /** - * 0 hive; 1 spark - */ - HIVE(0, "hive"), - SPARK(1, "spark"); - - UdfType(int code, String descp) { - this.code = code; - this.descp = descp; - } - - private final int code; - private final String descp; - - public int getCode() { - return code; - } - - public String getDescp() { - return descp; - } - - public static UdfType of(int type) { - for (UdfType ut : values()) { - if (ut.getCode() == type) { - return ut; - } - } - throw new IllegalArgumentException("invalid type : " + type); - } - -} diff --git a/dolphinscheduler-task-plugin/dolphinscheduler-task-api/src/main/java/org/apache/dolphinscheduler/plugin/task/api/parameters/SqlParameters.java b/dolphinscheduler-task-plugin/dolphinscheduler-task-api/src/main/java/org/apache/dolphinscheduler/plugin/task/api/parameters/SqlParameters.java index 75ebe6c9cd..8864943bac 100644 --- a/dolphinscheduler-task-plugin/dolphinscheduler-task-api/src/main/java/org/apache/dolphinscheduler/plugin/task/api/parameters/SqlParameters.java +++ b/dolphinscheduler-task-plugin/dolphinscheduler-task-api/src/main/java/org/apache/dolphinscheduler/plugin/task/api/parameters/SqlParameters.java @@ -21,12 +21,10 @@ import org.apache.dolphinscheduler.common.utils.JSONUtils; import org.apache.dolphinscheduler.plugin.task.api.SQLTaskExecutionContext; import org.apache.dolphinscheduler.plugin.task.api.enums.DataType; import org.apache.dolphinscheduler.plugin.task.api.enums.ResourceType; -import org.apache.dolphinscheduler.plugin.task.api.enums.UdfType; import org.apache.dolphinscheduler.plugin.task.api.model.Property; import org.apache.dolphinscheduler.plugin.task.api.model.ResourceInfo; import org.apache.dolphinscheduler.plugin.task.api.parameters.resource.DataSourceParameters; import org.apache.dolphinscheduler.plugin.task.api.parameters.resource.ResourceParametersHelper; -import org.apache.dolphinscheduler.plugin.task.api.parameters.resource.UdfFuncParameters; import org.apache.dolphinscheduler.plugin.task.api.utils.VarPoolUtils; import org.apache.commons.collections4.CollectionUtils; @@ -37,10 +35,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.stream.Collectors; -import com.google.common.base.Enums; -import com.google.common.base.Strings; import com.google.common.collect.Lists; /** @@ -80,10 +75,6 @@ public class SqlParameters extends AbstractParameters { */ private int displayRows; - /** - * udf list - */ - private String udfs; /** * show type * 0 TABLE @@ -148,14 +139,6 @@ public class SqlParameters extends AbstractParameters { this.sql = sql; } - public String getUdfs() { - return udfs; - } - - public void setUdfs(String udfs) { - this.udfs = udfs; - } - public int getSqlType() { return sqlType; } @@ -293,7 +276,6 @@ public class SqlParameters extends AbstractParameters { + ", sendEmail=" + sendEmail + ", displayRows=" + displayRows + ", limit=" + limit - + ", udfs='" + udfs + '\'' + ", showType='" + showType + '\'' + ", connParams='" + connParams + '\'' + ", groupId='" + groupId + '\'' @@ -308,16 +290,6 @@ public class SqlParameters extends AbstractParameters { ResourceParametersHelper resources = super.getResources(); resources.put(ResourceType.DATASOURCE, datasource); - // whether udf type - boolean udfTypeFlag = Enums.getIfPresent(UdfType.class, Strings.nullToEmpty(this.getType())).isPresent() - && !StringUtils.isEmpty(this.getUdfs()); - - if (udfTypeFlag) { - String[] udfFunIds = this.getUdfs().split(","); - for (int i = 0; i < udfFunIds.length; i++) { - resources.put(ResourceType.UDF, Integer.parseInt(udfFunIds[i])); - } - } return resources; } @@ -334,16 +306,6 @@ public class SqlParameters extends AbstractParameters { (DataSourceParameters) parametersHelper.getResourceParameters(ResourceType.DATASOURCE, datasource); sqlTaskExecutionContext.setConnectionParams(dbSource.getConnectionParams()); - // whether udf type - boolean udfTypeFlag = Enums.getIfPresent(UdfType.class, Strings.nullToEmpty(this.getType())).isPresent() - && !StringUtils.isEmpty(this.getUdfs()); - - if (udfTypeFlag) { - List collect = parametersHelper.getResourceMap(ResourceType.UDF).entrySet().stream() - .map(entry -> (UdfFuncParameters) entry.getValue()).collect(Collectors.toList()); - sqlTaskExecutionContext.setUdfFuncParametersList(collect); - } - return sqlTaskExecutionContext; } } diff --git a/dolphinscheduler-task-plugin/dolphinscheduler-task-api/src/main/java/org/apache/dolphinscheduler/plugin/task/api/parameters/resource/AbstractResourceParameters.java b/dolphinscheduler-task-plugin/dolphinscheduler-task-api/src/main/java/org/apache/dolphinscheduler/plugin/task/api/parameters/resource/AbstractResourceParameters.java index 4d3ec17954..c61851133b 100644 --- a/dolphinscheduler-task-plugin/dolphinscheduler-task-api/src/main/java/org/apache/dolphinscheduler/plugin/task/api/parameters/resource/AbstractResourceParameters.java +++ b/dolphinscheduler-task-plugin/dolphinscheduler-task-api/src/main/java/org/apache/dolphinscheduler/plugin/task/api/parameters/resource/AbstractResourceParameters.java @@ -23,8 +23,7 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo; @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, visible = true, property = "resourceType") @JsonSubTypes({ - @Type(value = DataSourceParameters.class, name = "DATASOURCE"), - @Type(value = UdfFuncParameters.class, name = "UDF") + @Type(value = DataSourceParameters.class, name = "DATASOURCE") }) public abstract class AbstractResourceParameters { diff --git a/dolphinscheduler-task-plugin/dolphinscheduler-task-api/src/main/java/org/apache/dolphinscheduler/plugin/task/api/parameters/resource/UdfFuncParameters.java b/dolphinscheduler-task-plugin/dolphinscheduler-task-api/src/main/java/org/apache/dolphinscheduler/plugin/task/api/parameters/resource/UdfFuncParameters.java deleted file mode 100644 index a36c8d84b5..0000000000 --- a/dolphinscheduler-task-plugin/dolphinscheduler-task-api/src/main/java/org/apache/dolphinscheduler/plugin/task/api/parameters/resource/UdfFuncParameters.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * 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. - */ - -package org.apache.dolphinscheduler.plugin.task.api.parameters.resource; - -import org.apache.dolphinscheduler.common.utils.JSONUtils; -import org.apache.dolphinscheduler.plugin.task.api.enums.UdfType; - -import java.util.Date; - -import lombok.Data; - -import com.fasterxml.jackson.annotation.JsonProperty; - -@Data -public class UdfFuncParameters extends AbstractResourceParameters { - - /** - * id - */ - private int id; - - public String getResourceType() { - return resourceType; - } - - public void setResourceType(String resourceType) { - this.resourceType = resourceType; - } - - @JsonProperty(value = "UDF") - private String resourceType; - - /** - * user id - */ - private int userId; - - /** - * udf function name - */ - private String funcName; - - /** - * udf class name - */ - private String className; - - /** - * udf argument types - */ - private String argTypes; - - /** - * udf data base - */ - private String database; - - /** - * udf description - */ - private String description; - - /** - * resource id - */ - private int resourceId; - - /** - * resource name - */ - private String resourceName; - - /** - * udf function type: hive / spark - */ - private UdfType type; - - /** - * create time - */ - private Date createTime; - - /** - * update time - */ - private Date updateTime; - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - UdfFuncParameters udfFuncRequest = (UdfFuncParameters) o; - - if (id != udfFuncRequest.id) { - return false; - } - return !(funcName != null ? !funcName.equals(udfFuncRequest.funcName) : udfFuncRequest.funcName != null); - - } - - @Override - public int hashCode() { - int result = id; - result = 31 * result + (funcName != null ? funcName.hashCode() : 0); - return result; - } - - @Override - public String toString() { - return JSONUtils.toJsonString(this); - } - -} diff --git a/dolphinscheduler-task-plugin/dolphinscheduler-task-api/src/test/java/org/apache/dolphinscheduler/plugin/task/api/parameters/SqlParametersTest.java b/dolphinscheduler-task-plugin/dolphinscheduler-task-api/src/test/java/org/apache/dolphinscheduler/plugin/task/api/parameters/SqlParametersTest.java index 83fe739f47..8f1ee76560 100644 --- a/dolphinscheduler-task-plugin/dolphinscheduler-task-api/src/test/java/org/apache/dolphinscheduler/plugin/task/api/parameters/SqlParametersTest.java +++ b/dolphinscheduler-task-plugin/dolphinscheduler-task-api/src/test/java/org/apache/dolphinscheduler/plugin/task/api/parameters/SqlParametersTest.java @@ -33,7 +33,6 @@ public class SqlParametersTest { private final String type = "MYSQL"; private final String sql = "select * from t_ds_user"; - private final String udfs = "test-udfs-1.0.0-SNAPSHOT.jar"; private final int datasource = 1; private final int sqlType = 0; private final Boolean sendEmail = true; @@ -57,7 +56,6 @@ public class SqlParametersTest { sqlParameters.setType(type); sqlParameters.setSql(sql); - sqlParameters.setUdfs(udfs); sqlParameters.setDatasource(datasource); sqlParameters.setSqlType(sqlType); sqlParameters.setSendEmail(sendEmail); @@ -68,7 +66,6 @@ public class SqlParametersTest { Assertions.assertEquals(type, sqlParameters.getType()); Assertions.assertEquals(sql, sqlParameters.getSql()); - Assertions.assertEquals(udfs, sqlParameters.getUdfs()); Assertions.assertEquals(datasource, sqlParameters.getDatasource()); Assertions.assertEquals(sqlType, sqlParameters.getSqlType()); Assertions.assertEquals(sendEmail, sqlParameters.getSendEmail()); diff --git a/dolphinscheduler-task-plugin/dolphinscheduler-task-remoteshell/src/test/java/org/apache/dolphinscheduler/plugin/task/remoteshell/RemoteShellTaskTest.java b/dolphinscheduler-task-plugin/dolphinscheduler-task-remoteshell/src/test/java/org/apache/dolphinscheduler/plugin/task/remoteshell/RemoteShellTaskTest.java index 19bde417a6..6a41f48de2 100644 --- a/dolphinscheduler-task-plugin/dolphinscheduler-task-remoteshell/src/test/java/org/apache/dolphinscheduler/plugin/task/remoteshell/RemoteShellTaskTest.java +++ b/dolphinscheduler-task-plugin/dolphinscheduler-task-remoteshell/src/test/java/org/apache/dolphinscheduler/plugin/task/remoteshell/RemoteShellTaskTest.java @@ -68,7 +68,7 @@ public class RemoteShellTaskTest { TaskExecutionContext taskExecutionContext = new TaskExecutionContext(); taskExecutionContext.setTaskAppId("1"); taskExecutionContext - .setTaskParams("{\"localParams\":[],\"rawScript\":\"echo 1\",\"resourceList\":[],\"udfList\":[]}"); + .setTaskParams("{\"localParams\":[],\"rawScript\":\"echo 1\",\"resourceList\":[]}"); taskExecutionContext.setExecutePath("/tmp"); taskExecutionContext.setEnvironmentConfig("export PATH=/opt/anaconda3/bin:$PATH"); RemoteShellTask remoteShellTask = spy(new RemoteShellTask(taskExecutionContext)); diff --git a/dolphinscheduler-task-plugin/dolphinscheduler-task-sql/src/main/java/org/apache/dolphinscheduler/plugin/task/sql/SqlTask.java b/dolphinscheduler-task-plugin/dolphinscheduler-task-sql/src/main/java/org/apache/dolphinscheduler/plugin/task/sql/SqlTask.java index 1c8fd4519f..586cee1c01 100644 --- a/dolphinscheduler-task-plugin/dolphinscheduler-task-sql/src/main/java/org/apache/dolphinscheduler/plugin/task/sql/SqlTask.java +++ b/dolphinscheduler-task-plugin/dolphinscheduler-task-sql/src/main/java/org/apache/dolphinscheduler/plugin/task/sql/SqlTask.java @@ -35,13 +35,10 @@ import org.apache.dolphinscheduler.plugin.task.api.model.Property; import org.apache.dolphinscheduler.plugin.task.api.model.TaskAlertInfo; import org.apache.dolphinscheduler.plugin.task.api.parameters.AbstractParameters; import org.apache.dolphinscheduler.plugin.task.api.parameters.SqlParameters; -import org.apache.dolphinscheduler.plugin.task.api.parameters.resource.UdfFuncParameters; -import org.apache.dolphinscheduler.plugin.task.api.resource.ResourceContext; import org.apache.dolphinscheduler.plugin.task.api.utils.ParameterUtils; import org.apache.dolphinscheduler.spi.datasource.BaseConnectionParam; import org.apache.dolphinscheduler.spi.enums.DbType; -import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import java.sql.Connection; @@ -49,8 +46,6 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; -import java.sql.Statement; -import java.text.MessageFormat; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -118,12 +113,11 @@ public class SqlTask extends AbstractTask { public void handle(TaskCallBack taskCallBack) throws TaskException { log.info("Full sql parameters: {}", sqlParameters); log.info( - "sql type : {}, datasource : {}, sql : {} , localParams : {},udfs : {},showType : {},connParams : {},varPool : {} ,query max result limit {}", + "sql type : {}, datasource : {}, sql : {} , localParams : {},showType : {},connParams : {},varPool : {} ,query max result limit {}", sqlParameters.getType(), sqlParameters.getDatasource(), sqlParameters.getSql(), sqlParameters.getLocalParams(), - sqlParameters.getUdfs(), sqlParameters.getShowType(), sqlParameters.getConnParams(), sqlParameters.getVarPool(), @@ -153,10 +147,8 @@ public class SqlTask extends AbstractTask { .map(this::getSqlAndSqlParamsMap) .collect(Collectors.toList()); - List createFuncs = createFuncs(sqlTaskExecutionContext.getUdfFuncParametersList()); - // execute sql task - executeFuncAndSql(mainStatementSqlBinds, preStatementSqlBinds, postStatementSqlBinds, createFuncs); + executeFuncAndSql(mainStatementSqlBinds, preStatementSqlBinds, postStatementSqlBinds); setExitStatusCode(TaskConstants.EXIT_CODE_SUCCESS); @@ -176,24 +168,17 @@ public class SqlTask extends AbstractTask { * execute function and sql * * @param mainStatementsBinds main statements binds - * @param preStatementsBinds pre statements binds + * @param preStatementsBinds pre statements binds * @param postStatementsBinds post statements binds - * @param createFuncs create functions */ public void executeFuncAndSql(List mainStatementsBinds, List preStatementsBinds, - List postStatementsBinds, - List createFuncs) throws Exception { + List postStatementsBinds) throws Exception { try ( Connection connection = DataSourceClientProvider.getAdHocConnection(DbType.valueOf(sqlParameters.getType()), baseConnectionParam)) { - // create temp function - if (CollectionUtils.isNotEmpty(createFuncs)) { - createTempFunction(connection, createFuncs); - } - // pre execute executeUpdate(connection, preStatementsBinds, "pre"); @@ -299,7 +284,7 @@ public class SqlTask extends AbstractTask { /** * send alert as an attachment * - * @param title title + * @param title title * @param content content */ private void sendAttachment(int groupId, String title, String content) { @@ -332,22 +317,6 @@ public class SqlTask extends AbstractTask { return String.valueOf(result); } - /** - * create temp function - * - * @param connection connection - * @param createFuncs createFuncs - */ - private void createTempFunction(Connection connection, - List createFuncs) throws Exception { - try (Statement funcStmt = connection.createStatement()) { - for (String createFunc : createFuncs) { - log.info("hive create function sql: {}", createFunc); - funcStmt.execute(createFunc); - } - } - } - /** * close jdbc resource * @@ -367,7 +336,7 @@ public class SqlTask extends AbstractTask { * preparedStatement bind * * @param connection connection - * @param sqlBinds sqlBinds + * @param sqlBinds sqlBinds * @return PreparedStatement * @throws Exception Exception */ @@ -400,9 +369,9 @@ public class SqlTask extends AbstractTask { /** * print replace sql * - * @param content content - * @param formatSql format sql - * @param rgex rgex + * @param content content + * @param formatSql format sql + * @param rgex rgex * @param sqlParamsMap sql params map */ private void printReplacedSql(String content, String formatSql, String rgex, Map sqlParamsMap) { @@ -477,50 +446,4 @@ public class SqlTask extends AbstractTask { return content; } - /** - * create function list - * - * @param udfFuncParameters udfFuncParameters - * @return - */ - private List createFuncs(List udfFuncParameters) { - - if (CollectionUtils.isEmpty(udfFuncParameters)) { - log.info("can't find udf function resource"); - return null; - } - // build jar sql - List funcList = buildJarSql(udfFuncParameters); - - // build temp function sql - List tempFuncList = buildTempFuncSql(udfFuncParameters); - funcList.addAll(tempFuncList); - return funcList; - } - - /** - * build temp function sql - * @param udfFuncParameters udfFuncParameters - * @return - */ - private List buildTempFuncSql(List udfFuncParameters) { - return udfFuncParameters.stream().map(value -> MessageFormat - .format(CREATE_OR_REPLACE_FUNCTION_FORMAT, value.getFuncName(), value.getClassName())) - .collect(Collectors.toList()); - } - - /** - * build jar sql - * @param udfFuncParameters udfFuncParameters - * @return - */ - private List buildJarSql(List udfFuncParameters) { - return udfFuncParameters.stream().map(value -> { - String resourceFullName = value.getResourceName(); - ResourceContext resourceContext = taskExecutionContext.getResourceContext(); - return String.format("add jar %s", - resourceContext.getResourceItem(resourceFullName).getResourceAbsolutePathInLocal()); - }).collect(Collectors.toList()); - } - } diff --git a/dolphinscheduler-tools/src/main/java/org/apache/dolphinscheduler/tools/resource/MigrateResourceService.java b/dolphinscheduler-tools/src/main/java/org/apache/dolphinscheduler/tools/resource/MigrateResourceService.java index 26e06ec6fe..a6b2e995dc 100644 --- a/dolphinscheduler-tools/src/main/java/org/apache/dolphinscheduler/tools/resource/MigrateResourceService.java +++ b/dolphinscheduler-tools/src/main/java/org/apache/dolphinscheduler/tools/resource/MigrateResourceService.java @@ -19,9 +19,7 @@ package org.apache.dolphinscheduler.tools.resource; import static org.apache.dolphinscheduler.common.constants.Constants.FORMAT_S_S; -import org.apache.dolphinscheduler.dao.entity.UdfFunc; import org.apache.dolphinscheduler.dao.mapper.TenantMapper; -import org.apache.dolphinscheduler.dao.mapper.UdfFuncMapper; import org.apache.dolphinscheduler.plugin.storage.api.StorageOperator; import org.apache.dolphinscheduler.spi.enums.ResourceType; @@ -54,9 +52,6 @@ public class MigrateResourceService { @Autowired private TenantMapper tenantMapper; - @Autowired - private UdfFuncMapper udfFuncMapper; - @Autowired private DataSource dataSource; @@ -69,11 +64,10 @@ public class MigrateResourceService { } String resMigrateBasePath = createMigrateDirByType(targetTenantCode, ResourceType.FILE); - String udfMigrateBasePath = createMigrateDirByType(targetTenantCode, ResourceType.UDF); - if (StringUtils.isEmpty(resMigrateBasePath) || StringUtils.isEmpty(udfMigrateBasePath)) { + if (StringUtils.isEmpty(resMigrateBasePath)) { return; } - // migrate all unmanaged resources and udfs once + // migrate all unmanaged resources once List> resources = getAllResources(); for (Map item : resources) { String oriFullName = (String) item.get("full_name"); @@ -84,16 +78,6 @@ public class MigrateResourceService { if (ResourceType.FILE.getCode() == type) { storageOperator.copy(oriFullName, String.format(FORMAT_S_S, resMigrateBasePath, oriFullName), true, true); - } else if (ResourceType.UDF.getCode() == type) { - String fullName = String.format(FORMAT_S_S, udfMigrateBasePath, oriFullName); - storageOperator.copy(oriFullName, fullName, true, true); - - // change relative udfs resourceName - List udfs = udfFuncMapper.listUdfByResourceId(new Integer[]{id}); - udfs.forEach(udf -> { - udf.setResourceName(fullName); - udfFuncMapper.updateById(udf); - }); } } catch (Exception e) { logger.error("Migrate resource: {} failed: {}", item, e); diff --git a/dolphinscheduler-ui/src/layouts/content/components/sidebar/index.tsx b/dolphinscheduler-ui/src/layouts/content/components/sidebar/index.tsx index 782b966609..7c6e0713f8 100644 --- a/dolphinscheduler-ui/src/layouts/content/components/sidebar/index.tsx +++ b/dolphinscheduler-ui/src/layouts/content/components/sidebar/index.tsx @@ -36,7 +36,6 @@ const Sidebar = defineComponent({ const defaultExpandedKeys = [ 'workflow', 'task', - 'udf-manage', 'service-manage', 'statistical-manage', 'task-group-manage' diff --git a/dolphinscheduler-ui/src/layouts/content/components/user/use-dropdown.ts b/dolphinscheduler-ui/src/layouts/content/components/user/use-dropdown.ts index 2ef301f5de..5dd3030509 100644 --- a/dolphinscheduler-ui/src/layouts/content/components/user/use-dropdown.ts +++ b/dolphinscheduler-ui/src/layouts/content/components/user/use-dropdown.ts @@ -42,7 +42,6 @@ export function useDropDown() { userStore.setSecurityConfigType('') userStore.setUserInfo({}) userStore.setBaseResDir('') - userStore.setBaseUdfDir('') cookies.remove('sessionId') router.push({ path: '/login' }) diff --git a/dolphinscheduler-ui/src/layouts/content/use-dataList.ts b/dolphinscheduler-ui/src/layouts/content/use-dataList.ts index facf521db8..6742559f99 100644 --- a/dolphinscheduler-ui/src/layouts/content/use-dataList.ts +++ b/dolphinscheduler-ui/src/layouts/content/use-dataList.ts @@ -31,7 +31,6 @@ import { PartitionOutlined, SettingOutlined, FileSearchOutlined, - RobotOutlined, AppstoreOutlined, UsergroupAddOutlined, UserAddOutlined, @@ -180,21 +179,6 @@ export function useDataList() { key: '/resource/file-manage', icon: renderIcon(FileSearchOutlined) }, - { - label: t('menu.udf_manage'), - key: 'udf-manage', - icon: renderIcon(RobotOutlined), - children: [ - { - label: t('menu.resource_manage'), - key: '/resource/resource-manage' - }, - { - label: t('menu.function_manage'), - key: '/resource/function-manage' - } - ] - }, { label: t('menu.task_group_manage'), key: 'task-group-manage', diff --git a/dolphinscheduler-ui/src/locales/en_US/menu.ts b/dolphinscheduler-ui/src/locales/en_US/menu.ts index d71f382ec0..47c185d7d0 100644 --- a/dolphinscheduler-ui/src/locales/en_US/menu.ts +++ b/dolphinscheduler-ui/src/locales/en_US/menu.ts @@ -34,7 +34,6 @@ export default { task_instance: 'Task Instance', task_definition: 'Task Definition', file_manage: 'File Manage', - udf_manage: 'UDF Manage', resource_manage: 'Resource Manage', function_manage: 'Function Manage', service_manage: 'Service Manage', diff --git a/dolphinscheduler-ui/src/locales/en_US/project.ts b/dolphinscheduler-ui/src/locales/en_US/project.ts index 68eb7b3102..bd3b626518 100644 --- a/dolphinscheduler-ui/src/locales/en_US/project.ts +++ b/dolphinscheduler-ui/src/locales/en_US/project.ts @@ -250,7 +250,8 @@ export default { 'The downstream dependent tasks exists. You can not delete the task.', warning_delete_scheduler_dependent_tasks_desc: 'The downstream dependent tasks exists. Are you sure to delete the scheduler?', - warning_too_large_parallelism_number: 'The parallelism number is too large. It is better not to be over 10.' + warning_too_large_parallelism_number: + 'The parallelism number is too large. It is better not to be over 10.' }, task: { on_line: 'Online', @@ -839,7 +840,6 @@ export default { integer_tips: 'Please enter a positive integer', sql_parameter: 'SQL Parameter', format_tips: 'Please enter format', - udf_function: 'UDF Function', unlimited: 'unlimited', please_select_source_connector_type: 'Please select source connector type', please_select_source_datasource_id: 'Please select source datasource id', diff --git a/dolphinscheduler-ui/src/locales/en_US/resource.ts b/dolphinscheduler-ui/src/locales/en_US/resource.ts index 9df7d6fce2..938b330018 100644 --- a/dolphinscheduler-ui/src/locales/en_US/resource.ts +++ b/dolphinscheduler-ui/src/locales/en_US/resource.ts @@ -55,17 +55,7 @@ export default { return: 'Return', save: 'Save' }, - udf: { - udf_resources: 'UDF resources', - upload_udf_resources: 'Upload UDF Resources', - udf_source_name: 'UDF Resource Name', - user_name: 'Resource userName' - }, function: { - udf_function: 'UDF Function', - create_udf_function: 'Create UDF Function', - edit_udf_function: 'Edit UDF Function', - udf_function_name: 'UDF Function Name', user_name: 'Resource userName', class_name: 'Class Name', type: 'Type', @@ -78,22 +68,15 @@ export default { delete: 'Delete', success: 'Success', package_name: 'Package Name', - udf_resources: 'UDF Resources', instructions: 'Instructions', upload_resources: 'Upload Resources', - udf_resources_directory: 'UDF resources directory', delete_confirm: 'Delete?', enter_keyword_tips: 'Please enter keyword', - enter_udf_unction_name_tips: 'Please enter a UDF function name', enter_package_name_tips: 'Please enter a Package name', - enter_select_udf_resources_tips: 'Please select UDF resources', - enter_select_udf_resources_directory_tips: - 'Please select UDF resources directory', enter_instructions_tips: 'Please enter a instructions', enter_name_tips: 'Please enter name', enter_description_tips: 'Please enter description', - upload: 'Upload', - upload_udf_resources: 'Upload UDF Resources' + upload: 'Upload' }, task_group_option: { manage: 'Task group manage', diff --git a/dolphinscheduler-ui/src/locales/en_US/security.ts b/dolphinscheduler-ui/src/locales/en_US/security.ts index 4faa58d51a..9cc7b0fb31 100644 --- a/dolphinscheduler-ui/src/locales/en_US/security.ts +++ b/dolphinscheduler-ui/src/locales/en_US/security.ts @@ -150,18 +150,14 @@ export default { project: 'Project', resource: 'Resource', file_resource: 'File Resource', - udf_resource: 'UDF Resource', datasource: 'Datasource', - udf: 'UDF Function', namespace: 'Namespace', revoke_auth: 'Revoke', grant_read: 'Grant Read', grant_all: 'Grant All', authorize_project: 'Project Authorize', - authorize_resource: 'Resource Authorize', authorize_namespace: 'Namespace Authorize', authorize_datasource: 'Datasource Authorize', - authorize_udf: 'UDF Function Authorize', username: 'Username', username_exists: 'The username already exists', username_tips: 'Please enter username', diff --git a/dolphinscheduler-ui/src/locales/zh_CN/menu.ts b/dolphinscheduler-ui/src/locales/zh_CN/menu.ts index 257541f3ad..ec892987c1 100644 --- a/dolphinscheduler-ui/src/locales/zh_CN/menu.ts +++ b/dolphinscheduler-ui/src/locales/zh_CN/menu.ts @@ -35,7 +35,6 @@ export default { task_instance: '任务实例', task_definition: '任务定义', file_manage: '文件管理', - udf_manage: 'UDF管理', resource_manage: '资源管理', function_manage: '函数管理', service_manage: '服务管理', diff --git a/dolphinscheduler-ui/src/locales/zh_CN/project.ts b/dolphinscheduler-ui/src/locales/zh_CN/project.ts index e1bfd6f672..98494495d2 100644 --- a/dolphinscheduler-ui/src/locales/zh_CN/project.ts +++ b/dolphinscheduler-ui/src/locales/zh_CN/project.ts @@ -247,7 +247,7 @@ export default { '下游存在依赖,你不能删除该任务.', warning_delete_scheduler_dependent_tasks_desc: '下游存在依赖, 删除定时可能会对下游任务产生影响. 你确定要删除该定时嘛?', - warning_too_large_parallelism_number: '并行度设置太大了, 最好不要超过10.', + warning_too_large_parallelism_number: '并行度设置太大了, 最好不要超过10.' }, task: { on_line: '线上', @@ -814,7 +814,6 @@ export default { integer_tips: '请输入一个正整数', sql_parameter: 'sql参数', format_tips: '请输入格式为', - udf_function: 'UDF函数', unlimited: '不限制', please_select_source_connector_type: '请选择源数据类型', please_select_source_datasource_id: '请选择源数据源', diff --git a/dolphinscheduler-ui/src/locales/zh_CN/resource.ts b/dolphinscheduler-ui/src/locales/zh_CN/resource.ts index ac9dffa482..d7eba07c97 100644 --- a/dolphinscheduler-ui/src/locales/zh_CN/resource.ts +++ b/dolphinscheduler-ui/src/locales/zh_CN/resource.ts @@ -55,16 +55,7 @@ export default { return: '返回', save: '保存' }, - udf: { - udf_resources: 'UDF资源', - upload_udf_resources: '上传UDF资源', - udf_source_name: 'UDF资源名称' - }, function: { - udf_function: 'UDF函数', - create_udf_function: '创建UDF函数', - edit_udf_function: '编辑UDF函数', - udf_function_name: 'UDF函数名称', user_name: '所属用户', class_name: '类名', type: '类型', @@ -77,21 +68,15 @@ export default { delete: '删除', success: '成功', package_name: '包名类名', - udf_resources: 'UDF资源', instructions: '使用说明', upload_resources: '上传资源', - udf_resources_directory: 'UDF资源目录', delete_confirm: '确定删除吗?', enter_keyword_tips: '请输入关键词', - enter_udf_unction_name_tips: '请输入UDF函数名称', enter_package_name_tips: '请输入包名类名', - enter_select_udf_resources_tips: '请选择UDF资源', - enter_select_udf_resources_directory_tips: '请选择UDF资源目录', enter_instructions_tips: '请输入使用说明', enter_name_tips: '请输入名称', enter_description_tips: '请输入描述', - upload: '上传', - upload_udf_resources: '上传UDF资源' + upload: '上传' }, task_group_option: { manage: '任务组管理', diff --git a/dolphinscheduler-ui/src/locales/zh_CN/security.ts b/dolphinscheduler-ui/src/locales/zh_CN/security.ts index 08ada5a3a7..1e982720ef 100644 --- a/dolphinscheduler-ui/src/locales/zh_CN/security.ts +++ b/dolphinscheduler-ui/src/locales/zh_CN/security.ts @@ -148,18 +148,14 @@ export default { project: '项目', resource: '资源', file_resource: '文件资源', - udf_resource: 'UDF资源', datasource: '数据源', - udf: 'UDF函数', namespace: '命名空间', revoke_auth: '撤销权限', grant_read: '授予读权限', grant_all: '授予所有权限', authorize_project: '项目授权', - authorize_resource: '资源授权', authorize_namespace: '命名空间授权', authorize_datasource: '数据源授权', - authorize_udf: 'UDF函数授权', username: '用户名', username_exists: '用户名已存在', username_tips: '请输入用户名', diff --git a/dolphinscheduler-ui/src/router/modules/resources.ts b/dolphinscheduler-ui/src/router/modules/resources.ts index 0e33de52bd..93a421352a 100644 --- a/dolphinscheduler-ui/src/router/modules/resources.ts +++ b/dolphinscheduler-ui/src/router/modules/resources.ts @@ -100,40 +100,6 @@ export default { auth: [] } }, - { - path: '/resource/resource-manage', - name: 'resource-manage', - component: components['resource-udf-resource'], - meta: { - title: '资源管理', - activeMenu: 'resource', - showSide: true, - auth: [] - } - }, - { - path: '/resource/resource-manage', - name: 'resource-sub-manage', - component: components['resource-udf-resource'], - meta: { - title: '资源管理', - activeMenu: 'resource', - activeSide: '/resource/resource-manage', - showSide: true, - auth: [] - } - }, - { - path: '/resource/function-manage', - name: 'function-manage', - component: components['resource-udf-function'], - meta: { - title: '函数管理', - activeMenu: 'resource', - showSide: true, - auth: [] - } - }, { path: '/resource/task-group-option', name: 'task-group-option', diff --git a/dolphinscheduler-ui/src/service/modules/resources/index.ts b/dolphinscheduler-ui/src/service/modules/resources/index.ts index b68e4e9191..19896441ab 100644 --- a/dolphinscheduler-ui/src/service/modules/resources/index.ts +++ b/dolphinscheduler-ui/src/service/modules/resources/index.ts @@ -23,16 +23,13 @@ import { FileNameReq, FullNameReq, TenantCodeReq, - IdReq, ContentReq, DescriptionReq, CreateReq, - UserIdReq, OnlineCreateReq, ProgramTypeReq, ListReq, - ViewResourceReq, - UdfFuncReq + ViewResourceReq } from './types' export function queryResourceListPaging( @@ -64,30 +61,6 @@ export function createResource( }) } -export function authorizedFile(params: UserIdReq): any { - return axios({ - url: '/resources/authed-file', - method: 'get', - params - }) -} - -export function authorizeResourceTree(params: UserIdReq): any { - return axios({ - url: '/resources/authed-resource-tree', - method: 'get', - params - }) -} - -export function authUDFFunc(params: UserIdReq): any { - return axios({ - url: '/resources/authed-udf-func', - method: 'get', - params - }) -} - export function createDirectory( data: CreateReq & NameReq & ResourceTypeReq ): any { @@ -126,62 +99,6 @@ export function queryResourceByProgramType( }) } -export function queryUdfFuncListPaging(params: ListReq): any { - return axios({ - url: '/resources/udf-func', - method: 'get', - params - }) -} - -export function queryUdfFuncList(params: { type: 'HIVE' | 'SPARK' }): any { - return axios({ - url: '/resources/udf-func/list', - method: 'get', - params - }) -} - -export function verifyUdfFuncName(params: NameReq): any { - return axios({ - url: '/resources/udf-func/verify-name', - method: 'get', - params - }) -} - -export function deleteUdfFunc(id: number, params: FullNameReq): any { - return axios({ - url: `/resources/udf-func/${id}`, - method: 'delete', - params - }) -} - -export function unAuthUDFFunc(params: UserIdReq): any { - return axios({ - url: '/resources/unauth-udf-func', - method: 'get', - params - }) -} - -export function verifyResourceName(params: FullNameReq & ResourceTypeReq): any { - return axios({ - url: '/resources/verify-name', - method: 'get', - params - }) -} - -export function doesResourceExist(params: FullNameReq & ResourceTypeReq): any { - return axios({ - url: '/resources/verify-name', - method: 'get', - params - }) -} - export function updateResource( data: NameReq & ResourceTypeReq & DescriptionReq & FullNameReq & TenantCodeReq ): any { @@ -204,13 +121,6 @@ export function downloadResource(params: FullNameReq): void { utils.downloadFile('resources/download', params) } -export function viewUIUdfFunction(id: IdReq): any { - return axios({ - url: `/resources/${id}/udf-func`, - method: 'get' - }) -} - export function updateResourceContent( data: ContentReq & TenantCodeReq & FullNameReq ): any { @@ -230,19 +140,3 @@ export function viewResource( params }) } - -export function createUdfFunc(data: UdfFuncReq): any { - return axios({ - url: '/resources/udf-func', - method: 'post', - data - }) -} - -export function updateUdfFunc(data: UdfFuncReq, id: number): any { - return axios({ - url: `/resources/udf-func/${id}`, - method: 'put', - data - }) -} diff --git a/dolphinscheduler-ui/src/service/modules/resources/types.ts b/dolphinscheduler-ui/src/service/modules/resources/types.ts index ef0cafa685..dfcadd19df 100644 --- a/dolphinscheduler-ui/src/service/modules/resources/types.ts +++ b/dolphinscheduler-ui/src/service/modules/resources/types.ts @@ -20,14 +20,10 @@ interface FileReq { } interface ResourceTypeReq { - type: 'FILE' | 'UDF' + type: 'FILE' programType?: string } -interface UdfTypeReq { - type: 'HIVE' | 'SPARK' -} - interface NameReq { name: string } @@ -84,13 +80,6 @@ interface ViewResourceReq { skipLineNum: number } -interface UdfFuncReq extends UdfTypeReq, DescriptionReq, FullNameReq { - className: string - funcName: string - argTypes?: string - database?: string -} - interface ResourceFile { id: number pid: number @@ -122,7 +111,6 @@ interface ResourceViewRes { export { FileReq, ResourceTypeReq, - UdfTypeReq, NameReq, FileNameReq, FullNameReq, @@ -136,7 +124,6 @@ export { ProgramTypeReq, ListReq, ViewResourceReq, - UdfFuncReq, ResourceListRes, ResourceViewRes, ResourceFile diff --git a/dolphinscheduler-ui/src/service/modules/users/index.ts b/dolphinscheduler-ui/src/service/modules/users/index.ts index ce3806e4af..bdb5c158a8 100644 --- a/dolphinscheduler-ui/src/service/modules/users/index.ts +++ b/dolphinscheduler-ui/src/service/modules/users/index.ts @@ -27,7 +27,6 @@ import { GrantResourceReq, GrantProject, ProjectCodeReq, - GrantUDFReq, GrantNamespaceReq, ListAllReq, ListReq, @@ -129,14 +128,6 @@ export function grantProjectByCode(data: ProjectCodeReq & UserIdReq): any { }) } -export function grantUDFFunc(data: GrantUDFReq & UserIdReq) { - return axios({ - url: '/users/grant-udf-func', - method: 'post', - data - }) -} - export function grantNamespaceFunc(data: GrantNamespaceReq & UserIdReq) { return axios({ url: '/users/grant-namespace', diff --git a/dolphinscheduler-ui/src/service/modules/users/types.ts b/dolphinscheduler-ui/src/service/modules/users/types.ts index 33de77e215..2d64a3e9ea 100644 --- a/dolphinscheduler-ui/src/service/modules/users/types.ts +++ b/dolphinscheduler-ui/src/service/modules/users/types.ts @@ -62,10 +62,6 @@ interface ProjectCodeReq { projectCode: string } -interface GrantUDFReq { - udfIds: string -} - interface GrantNamespaceReq { namespaceIds: string } @@ -130,7 +126,6 @@ export { GrantResourceReq, GrantProject, ProjectCodeReq, - GrantUDFReq, GrantNamespaceReq, ListAllReq, ListReq, diff --git a/dolphinscheduler-ui/src/service/service.ts b/dolphinscheduler-ui/src/service/service.ts index fe02a94761..4d80278874 100644 --- a/dolphinscheduler-ui/src/service/service.ts +++ b/dolphinscheduler-ui/src/service/service.ts @@ -66,7 +66,6 @@ const err = (err: AxiosError): Promise => { userStore.setSecurityConfigType('') userStore.setUserInfo({}) userStore.setBaseResDir('') - userStore.setBaseUdfDir('') router.push({ path: '/login' }) } diff --git a/dolphinscheduler-ui/src/store/user/types.ts b/dolphinscheduler-ui/src/store/user/types.ts index 3b0b09da72..4877402e41 100644 --- a/dolphinscheduler-ui/src/store/user/types.ts +++ b/dolphinscheduler-ui/src/store/user/types.ts @@ -21,7 +21,6 @@ interface UserState { sessionId: string securityConfigType: string baseResDir: string - baseUdfDir: string userInfo: UserInfoRes | {} } diff --git a/dolphinscheduler-ui/src/store/user/user.ts b/dolphinscheduler-ui/src/store/user/user.ts index 5405c2d687..6b10571dc1 100644 --- a/dolphinscheduler-ui/src/store/user/user.ts +++ b/dolphinscheduler-ui/src/store/user/user.ts @@ -25,7 +25,6 @@ export const useUserStore = defineStore({ sessionId: '', securityConfigType: '', baseResDir: '', - baseUdfDir: '', userInfo: {} }), persist: true, @@ -41,9 +40,6 @@ export const useUserStore = defineStore({ }, getBaseResDir(): string { return this.baseResDir - }, - getBaseUdfDir(): string { - return this.baseUdfDir } }, actions: { @@ -58,9 +54,6 @@ export const useUserStore = defineStore({ }, setBaseResDir(baseResDir: string): void { this.baseResDir = baseResDir - }, - setBaseUdfDir(baseUdfDir: string): void { - this.baseUdfDir = baseUdfDir } } }) diff --git a/dolphinscheduler-ui/src/views/login/use-login.ts b/dolphinscheduler-ui/src/views/login/use-login.ts index 4200888f6b..bac91ff7e8 100644 --- a/dolphinscheduler-ui/src/views/login/use-login.ts +++ b/dolphinscheduler-ui/src/views/login/use-login.ts @@ -48,11 +48,7 @@ export function useLogin(state: any) { const baseResDir = await queryBaseDir({ type: 'FILE' }) - const baseUdfDir = await queryBaseDir({ - type: 'UDF' - }) await userStore.setBaseResDir(baseResDir) - await userStore.setBaseUdfDir(baseUdfDir) const timezone = userInfoRes.timeZone ? userInfoRes.timeZone : 'UTC' await timezoneStore.setTimezone(timezone) diff --git a/dolphinscheduler-ui/src/views/password/use-update.ts b/dolphinscheduler-ui/src/views/password/use-update.ts index 896188f346..afadc387d6 100644 --- a/dolphinscheduler-ui/src/views/password/use-update.ts +++ b/dolphinscheduler-ui/src/views/password/use-update.ts @@ -43,7 +43,6 @@ export function useUpdate(state: any) { await userStore.setSecurityConfigType('') await userStore.setUserInfo({}) await userStore.setBaseResDir('') - await userStore.setBaseUdfDir('') await router.push({ path: 'login' }) } }) diff --git a/dolphinscheduler-ui/src/views/projects/parameter/components/parameter-modal.tsx b/dolphinscheduler-ui/src/views/projects/parameter/components/parameter-modal.tsx index d0e20569d1..6b6b827a3b 100644 --- a/dolphinscheduler-ui/src/views/projects/parameter/components/parameter-modal.tsx +++ b/dolphinscheduler-ui/src/views/projects/parameter/components/parameter-modal.tsx @@ -26,7 +26,10 @@ import Modal from '@/components/modal' import { NForm, NFormItem, NInput, NSelect } from 'naive-ui' import { useModal } from './use-modal' import { useI18n } from 'vue-i18n' -import { DATA_TYPES_MAP, DEFAULT_DATA_TYPE } from "@/views/projects/parameter/data_type" +import { + DATA_TYPES_MAP, + DEFAULT_DATA_TYPE +} from '@/views/projects/parameter/data_type' const ParameterModal = defineComponent({ name: 'ParameterModal', @@ -142,13 +145,16 @@ const ParameterModal = defineComponent({ v-model={[this.model.projectParameterValue, 'value']} /> - + { - return { value: item, label: item } - })} - v-model={[this.model.projectParameterDataType, 'value']} + placeholder={t('project.parameter.data_type_tips')} + options={Object.keys(DATA_TYPES_MAP).map((item) => { + return { value: item, label: item } + })} + v-model={[this.model.projectParameterDataType, 'value']} /> diff --git a/dolphinscheduler-ui/src/views/projects/parameter/components/use-modal.ts b/dolphinscheduler-ui/src/views/projects/parameter/components/use-modal.ts index 50dae38a49..5d6dcbcb19 100644 --- a/dolphinscheduler-ui/src/views/projects/parameter/components/use-modal.ts +++ b/dolphinscheduler-ui/src/views/projects/parameter/components/use-modal.ts @@ -27,7 +27,7 @@ import { UpdateProjectParameterReq } from '@/service/modules/projects-parameter/types' import { useRouter } from 'vue-router' -import { DEFAULT_DATA_TYPE } from "@/views/projects/parameter/data_type"; +import { DEFAULT_DATA_TYPE } from '@/views/projects/parameter/data_type' export function useModal( props: any, diff --git a/dolphinscheduler-ui/src/views/projects/parameter/data_type.ts b/dolphinscheduler-ui/src/views/projects/parameter/data_type.ts index 1d90384436..5c8e208431 100644 --- a/dolphinscheduler-ui/src/views/projects/parameter/data_type.ts +++ b/dolphinscheduler-ui/src/views/projects/parameter/data_type.ts @@ -16,39 +16,39 @@ */ export const DATA_TYPES_MAP = { - VARCHAR: { - alias: 'VARCHAR' - }, - INTEGER: { - alias: 'INTEGER' - }, - LONG: { - alias: 'LONG' - }, - FLOAT: { - alias: 'FLOAT' - }, - DOUBLE: { - alias: 'DOUBLE' - }, - DATE: { - alias: 'DATE' - }, - TIME: { - alias: 'TIME' - }, - TIMESTAMP: { - alias: 'TIMESTAMP' - }, - BOOLEAN: { - alias: 'BOOLEAN' - }, - LIST: { - alias: 'LIST' - }, - FILE: { - alias: 'FILE' - } + VARCHAR: { + alias: 'VARCHAR' + }, + INTEGER: { + alias: 'INTEGER' + }, + LONG: { + alias: 'LONG' + }, + FLOAT: { + alias: 'FLOAT' + }, + DOUBLE: { + alias: 'DOUBLE' + }, + DATE: { + alias: 'DATE' + }, + TIME: { + alias: 'TIME' + }, + TIMESTAMP: { + alias: 'TIMESTAMP' + }, + BOOLEAN: { + alias: 'BOOLEAN' + }, + LIST: { + alias: 'LIST' + }, + FILE: { + alias: 'FILE' + } } export const DEFAULT_DATA_TYPE = 'VARCHAR' diff --git a/dolphinscheduler-ui/src/views/projects/parameter/index.tsx b/dolphinscheduler-ui/src/views/projects/parameter/index.tsx index 6b9519830b..f7daa71c40 100644 --- a/dolphinscheduler-ui/src/views/projects/parameter/index.tsx +++ b/dolphinscheduler-ui/src/views/projects/parameter/index.tsx @@ -30,7 +30,7 @@ import { useTable } from '@/views/projects/parameter/use-table' import Card from '@/components/card' import ParameterModal from '@/views/projects/parameter/components/parameter-modal' import { SearchOutlined } from '@vicons/antd' -import { DATA_TYPES_MAP } from "@/views/projects/parameter/data_type" +import { DATA_TYPES_MAP } from '@/views/projects/parameter/data_type' export default defineComponent({ name: 'ProjectParameterList', @@ -120,14 +120,14 @@ export default defineComponent({ placeholder={t('project.parameter.name')} /> { - return { value: item, label: item } - })} - placeholder={t('project.parameter.data_type_tips')} - style={{ width: '180px' }} - clearable + v-model={[this.projectParameterDataType, 'value']} + size='small' + options={Object.keys(DATA_TYPES_MAP).map((item) => { + return { value: item, label: item } + })} + placeholder={t('project.parameter.data_type_tips')} + style={{ width: '180px' }} + clearable /> diff --git a/dolphinscheduler-ui/src/views/projects/task/components/node/fields/use-sql.ts b/dolphinscheduler-ui/src/views/projects/task/components/node/fields/use-sql.ts index 46e489d40e..2299d4569b 100644 --- a/dolphinscheduler-ui/src/views/projects/task/components/node/fields/use-sql.ts +++ b/dolphinscheduler-ui/src/views/projects/task/components/node/fields/use-sql.ts @@ -17,7 +17,6 @@ import { computed } from 'vue' import { useI18n } from 'vue-i18n' import { useCustomParams } from '.' -import { useUdfs } from './use-udfs' import type { IJsonItem } from '../types' export function useSql(model: { [field: string]: any }): IJsonItem[] { @@ -48,7 +47,6 @@ export function useSql(model: { [field: string]: any }): IJsonItem[] { language: 'sql' } }, - useUdfs(model), ...useCustomParams({ model, field: 'localParams', isSimple: false }), { type: 'multi-input', diff --git a/dolphinscheduler-ui/src/views/projects/task/components/node/fields/use-udfs.ts b/dolphinscheduler-ui/src/views/projects/task/components/node/fields/use-udfs.ts deleted file mode 100644 index 3c2f444354..0000000000 --- a/dolphinscheduler-ui/src/views/projects/task/components/node/fields/use-udfs.ts +++ /dev/null @@ -1,62 +0,0 @@ -/* - * 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 { ref, watch, computed } from 'vue' -import { useI18n } from 'vue-i18n' -import { queryUdfFuncList } from '@/service/modules/resources' -import type { IJsonItem } from '../types' - -export function useUdfs(model: { [field: string]: any }): IJsonItem { - const { t } = useI18n() - const options = ref([]) - const loading = ref(false) - const span = computed(() => - ['HIVE', 'SPARK', 'KYUUBI'].includes(model.type) ? 24 : 0 - ) - - const getUdfs = async () => { - if (loading.value) return - loading.value = true - const type = model.type === 'KYUUBI' ? 'HIVE' : model.type - const res = await queryUdfFuncList({ type }) - options.value = res.map((udf: { id: number; funcName: string }) => ({ - value: String(udf.id), - label: udf.funcName - })) - loading.value = false - } - - watch( - () => model.type, - (value) => { - if (['HIVE', 'SPARK', 'KYUUBI'].includes(value)) { - getUdfs() - } - } - ) - - return { - type: 'select', - field: 'udfs', - options: options, - name: t('project.node.udf_function'), - props: { - multiple: true, - loading - }, - span - } -} diff --git a/dolphinscheduler-ui/src/views/projects/task/components/node/format-data.ts b/dolphinscheduler-ui/src/views/projects/task/components/node/format-data.ts index 2ab1712b6f..9a9dad5476 100644 --- a/dolphinscheduler-ui/src/views/projects/task/components/node/format-data.ts +++ b/dolphinscheduler-ui/src/views/projects/task/components/node/format-data.ts @@ -194,14 +194,6 @@ export function formatParams(data: INodeData): { taskParams.title = data.title taskParams.groupId = data.groupId } - if (data.type === 'HIVE') { - if (data.udfs) taskParams.udfs = data.udfs.join(',') - taskParams.connParams = data.connParams - } - - if (data.type === 'KYUUBI') { - if (data.udfs) taskParams.udfs = data.udfs.join(',') - } } if (data.taskType === 'PROCEDURE') { @@ -743,9 +735,6 @@ export function formatModel(data: ITaskData) { if (data.taskParams?.conditionResult?.failedNode?.length) { params.failedBranch = data.taskParams.conditionResult.failedNode[0] } - if (data.taskParams?.udfs) { - params.udfs = data.taskParams.udfs?.split(',') - } if (data.taskParams?.customConfig !== void 0) { params.customConfig = data.taskParams.customConfig === 1 ? true : false } diff --git a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-sql.ts b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-sql.ts index e733387c1d..26db7b96dc 100644 --- a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-sql.ts +++ b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-sql.ts @@ -50,7 +50,6 @@ export function useSql({ sqlType: '0', preStatements: [], postStatements: [], - udfs: [], timeoutNotifyStrategy: ['WARN'] } as INodeData) diff --git a/dolphinscheduler-ui/src/views/projects/task/components/node/types.ts b/dolphinscheduler-ui/src/views/projects/task/components/node/types.ts index 606339b16f..514b0d032a 100644 --- a/dolphinscheduler-ui/src/views/projects/task/components/node/types.ts +++ b/dolphinscheduler-ui/src/views/projects/task/components/node/types.ts @@ -370,7 +370,6 @@ interface ITaskParams { successNode?: number[] failedNode?: number[] } - udfs?: string connParams?: string targetJobName?: string cluster?: string @@ -466,7 +465,6 @@ interface INodeData | 'dependence' | 'sparkParameters' | 'conditionResult' - | 'udfs' | 'customConfig' >, ISqoopTargetData, @@ -507,7 +505,6 @@ interface INodeData definition?: object successBranch?: number failedBranch?: number - udfs?: string[] customConfig?: boolean mapping_columns?: object[] taskExecuteType?: TaskExecuteType diff --git a/dolphinscheduler-ui/src/views/projects/workflow/components/dag/dag-save-modal.tsx b/dolphinscheduler-ui/src/views/projects/workflow/components/dag/dag-save-modal.tsx index bfd76e79b1..81d7f54019 100644 --- a/dolphinscheduler-ui/src/views/projects/workflow/components/dag/dag-save-modal.tsx +++ b/dolphinscheduler-ui/src/views/projects/workflow/components/dag/dag-save-modal.tsx @@ -248,7 +248,12 @@ export default defineComponent({ > {{ default: (param: { - value: { key: string; direct: string; type: string; value: string } + value: { + key: string + direct: string + type: string + value: string + } }) => ( @@ -269,20 +274,20 @@ export default defineComponent({ diff --git a/dolphinscheduler-ui/src/views/projects/workflow/definition/components/start-modal.tsx b/dolphinscheduler-ui/src/views/projects/workflow/definition/components/start-modal.tsx index 2bce9764d4..40fd41a975 100644 --- a/dolphinscheduler-ui/src/views/projects/workflow/definition/components/start-modal.tsx +++ b/dolphinscheduler-ui/src/views/projects/workflow/definition/components/start-modal.tsx @@ -49,10 +49,7 @@ import { NGrid, NGridItem } from 'naive-ui' -import { - ArrowDownOutlined, - ArrowUpOutlined -} from '@vicons/antd' +import { ArrowDownOutlined, ArrowUpOutlined } from '@vicons/antd' import { IDefinitionData } from '../types' import styles from '../index.module.scss' import { queryProjectPreferenceByProjectCode } from '@/service/modules/projects-preference' @@ -581,63 +578,68 @@ export default defineComponent({ path='startup_parameter' > { - return { - key: '', - direct: 'IN', - type: 'VARCHAR', - value: '' - } - }} - class='input-startup-params' + v-model:value={this.startParamsList} + onCreate={() => { + return { + key: '', + direct: 'IN', + type: 'VARCHAR', + value: '' + } + }} + class='input-startup-params' > {{ default: (param: { - value: { prop: string; direct: string; type: string; value: string } + value: { + prop: string + direct: string + type: string + value: string + } }) => ( - - - - - - - - - - - - - - + + + + + + + + + + + + + + ) }} diff --git a/dolphinscheduler-ui/src/views/resource/components/resource/index.tsx b/dolphinscheduler-ui/src/views/resource/components/resource/index.tsx index 9166af4ba8..7bab240cbd 100644 --- a/dolphinscheduler-ui/src/views/resource/components/resource/index.tsx +++ b/dolphinscheduler-ui/src/views/resource/components/resource/index.tsx @@ -154,21 +154,15 @@ export default defineComponent({ } const goBread = (fullName: string) => { - const { resourceType, tenantCode } = variables - const baseDir = - resourceType === 'UDF' - ? userStore.getBaseUdfDir - : userStore.getBaseResDir + const { tenantCode } = variables + const baseDir = userStore.getBaseResDir if (fullName === '' || !fullName.startsWith(baseDir)) { router.push({ - name: resourceType === 'UDF' ? 'resource-manage' : 'file-manage' + name: 'file-manage' }) } else { router.push({ - name: - resourceType === 'UDF' - ? 'resource-sub-manage' - : 'resource-file-subdirectory', + name: 'resource-file-subdirectory', query: { prefix: fullName, tenantCode: tenantCode } }) } @@ -204,10 +198,7 @@ export default defineComponent({ handleUploadFile, tableWidth } = this - const manageTitle = - this.resourceType === 'UDF' - ? t('resource.udf.udf_resources') - : t('resource.file.file_manage') + const manageTitle = t('resource.file.file_manage') return ( @@ -221,15 +212,13 @@ export default defineComponent({ > {t('resource.file.create_folder')} - {this.resourceType !== 'UDF' && ( + { {t('resource.file.create_file')} - )} + } - {this.resourceType === 'UDF' - ? t('resource.udf.upload_udf_resources') - : t('resource.file.upload_files')} + {t('resource.file.upload_files')} diff --git a/dolphinscheduler-ui/src/views/resource/components/resource/table/table-action.tsx b/dolphinscheduler-ui/src/views/resource/components/resource/table/table-action.tsx index d1d0576429..4bfab4bc53 100644 --- a/dolphinscheduler-ui/src/views/resource/components/resource/table/table-action.tsx +++ b/dolphinscheduler-ui/src/views/resource/components/resource/table/table-action.tsx @@ -121,7 +121,7 @@ export default defineComponent({ const { t } = useI18n() return ( - {this.row.type !== 'UDF' && ( + { {{ default: () => t('resource.file.edit'), @@ -149,7 +149,7 @@ export default defineComponent({ ) }} - )} + } {{ default: () => t('resource.file.reupload'), diff --git a/dolphinscheduler-ui/src/views/resource/components/resource/table/use-table.ts b/dolphinscheduler-ui/src/views/resource/components/resource/table/use-table.ts index 6897f19996..771afad180 100644 --- a/dolphinscheduler-ui/src/views/resource/components/resource/table/use-table.ts +++ b/dolphinscheduler-ui/src/views/resource/components/resource/table/use-table.ts @@ -35,10 +35,7 @@ import { defineStore } from 'pinia' const goSubFolder = (router: Router, item: any) => { if (item.directory) { router.push({ - name: - item.type === 'UDF' - ? 'resource-sub-manage' - : 'resource-file-subdirectory', + name: 'resource-file-subdirectory', query: { prefix: item.fullName, tenantCode: item.user_name } }) } else if (item.type === 'FILE') { @@ -160,9 +157,7 @@ export function useTable() { renameResource(name, description, fullName, user_name), onUpdateList: () => updateList() }), - ...COLUMN_WIDTH_CONFIG['operation']( - variables.resourceType === 'UDF' ? 4 : 5 - ) + ...COLUMN_WIDTH_CONFIG['operation'](5) } ] } diff --git a/dolphinscheduler-ui/src/views/resource/components/resource/types.ts b/dolphinscheduler-ui/src/views/resource/components/resource/types.ts index f208380178..a689f48fbd 100644 --- a/dolphinscheduler-ui/src/views/resource/components/resource/types.ts +++ b/dolphinscheduler-ui/src/views/resource/components/resource/types.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -export type ResourceType = 'FILE' | 'UDF' +export type ResourceType = 'FILE' export interface ResourceFileTableData { name: string diff --git a/dolphinscheduler-ui/src/views/resource/udf/function/components/function-modal.tsx b/dolphinscheduler-ui/src/views/resource/udf/function/components/function-modal.tsx deleted file mode 100644 index 5f19ec2b71..0000000000 --- a/dolphinscheduler-ui/src/views/resource/udf/function/components/function-modal.tsx +++ /dev/null @@ -1,284 +0,0 @@ -/* - * 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 { - defineComponent, - toRefs, - PropType, - watch, - onMounted, - ref, - getCurrentInstance -} from 'vue' -import { - NUpload, - NIcon, - NForm, - NFormItem, - NInput, - NInputGroup, - NRadio, - NTreeSelect, - NButton, - NRadioGroup -} from 'naive-ui' -import { useI18n } from 'vue-i18n' -import { CloudUploadOutlined } from '@vicons/antd' -import Modal from '@/components/modal' -import { useForm } from './use-form' -import { useModal } from './use-modal' -import type { IUdf } from '../types' - -const props = { - row: { - type: Object as PropType, - default: {} - }, - show: { - type: Boolean as PropType, - default: false - } -} - -export default defineComponent({ - name: 'ResourceFileFolder', - props, - emits: ['update:show', 'updateList'], - setup(props, ctx) { - const treeRef = ref() - const { state, uploadState } = useForm() - - const { - variables, - handleCreateFunc, - handleRenameFunc, - getUdfList, - handleUploadFile - } = useModal(state, uploadState, ctx) - - const hideModal = () => { - ctx.emit('update:show') - } - - const handleCreate = () => { - handleCreateFunc() - } - - const handleRename = () => { - handleRenameFunc(props.row.id) - } - - const handleUpload = () => { - uploadState.uploadForm.currentDir = `/${treeRef.value.selectedOption?.fullName}` - handleUploadFile() - } - - const customRequest = ({ file }: any) => { - uploadState.uploadForm.name = file.name - uploadState.uploadForm.file = file.file - } - - const trim = getCurrentInstance()?.appContext.config.globalProperties.trim - - onMounted(() => { - getUdfList() - }) - - watch( - () => props.row, - () => { - variables.uploadShow = false - state.functionForm.type = props.row.type || 'HIVE' - state.functionForm.funcName = props.row.funcName - state.functionForm.className = props.row.className - state.functionForm.resourceId = props.row.resourceId || -1 - state.functionForm.fullName = props.row.resourceName || '' - state.functionForm.description = props.row.description - } - ) - return { - treeRef, - hideModal, - handleCreate, - handleRename, - customRequest, - handleUpload, - ...toRefs(state), - ...toRefs(uploadState), - ...toRefs(variables), - trim - } - }, - render() { - const { t } = useI18n() - return ( - - - - - HIVE UDF - - - - - - - - - - - - (this.uploadShow = !this.uploadShow)} - > - {t('resource.function.upload_resources')} - - - - {this.uploadShow && ( - - - - - - - - - - {t('resource.function.upload')} - - - - - - - - - - - - - {t('resource.function.upload_udf_resources')} - - - - )} - - - - - - - ) - } -}) diff --git a/dolphinscheduler-ui/src/views/resource/udf/function/components/use-form.ts b/dolphinscheduler-ui/src/views/resource/udf/function/components/use-form.ts deleted file mode 100644 index 2d4d067c65..0000000000 --- a/dolphinscheduler-ui/src/views/resource/udf/function/components/use-form.ts +++ /dev/null @@ -1,113 +0,0 @@ -/* - * 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 { reactive, ref } from 'vue' -import { useI18n } from 'vue-i18n' -import type { FormRules } from 'naive-ui' - -export const useForm = () => { - const { t } = useI18n() - - const state = reactive({ - functionFormRef: ref(), - functionForm: { - type: 'HIVE', - funcName: '', - className: '', - argTypes: '', - database: '', - description: '', - resourceId: -1, - fullName: '' - }, - saving: false, - rules: { - type: { - required: true, - trigger: ['input', 'blur'], - validator() { - if (!state.functionForm.type) { - return new Error(t('resource.function.enter_name_tips')) - } - } - }, - funcName: { - required: true, - trigger: ['input', 'blur'], - validator() { - if (!state.functionForm.funcName) { - return new Error(t('resource.function.enter_name_tips')) - } - } - }, - className: { - required: true, - trigger: ['input', 'blur'], - validator() { - if (!state.functionForm.className) { - return new Error(t('resource.function.enter_name_tips')) - } - } - }, - resourceId: { - required: false, - trigger: ['input', 'blur'], - validator() { - if (state.functionForm.resourceId === -1) { - return new Error(t('resource.function.enter_name_tips')) - } - } - }, - fullName: { - required: true, - trigger: ['input', 'blur'], - validator() { - if (state.functionForm.fullName == '') { - return new Error(t('resource.function.enter_name_tips')) - } - } - } - } as FormRules - }) - - const uploadState = reactive({ - uploadFormRef: ref(), - uploadForm: { - name: '', - file: '', - description: '', - pid: -1, - currentDir: '/' - }, - uploadRules: { - pid: { - required: true, - trigger: ['input', 'blur'], - validator() { - if (uploadState.uploadForm.pid === -1) { - return new Error(t('resource.function.enter_name_tips')) - } - } - } - } as FormRules - }) - - return { - state, - uploadState - } -} diff --git a/dolphinscheduler-ui/src/views/resource/udf/function/components/use-modal.ts b/dolphinscheduler-ui/src/views/resource/udf/function/components/use-modal.ts deleted file mode 100644 index dc60ff4207..0000000000 --- a/dolphinscheduler-ui/src/views/resource/udf/function/components/use-modal.ts +++ /dev/null @@ -1,175 +0,0 @@ -/* - * 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 _ from 'lodash' -import { ref, reactive, SetupContext } from 'vue' -import { useI18n } from 'vue-i18n' -import { - createResource, - createUdfFunc, - queryResourceList, - updateUdfFunc -} from '@/service/modules/resources' -import { useAsyncState } from '@vueuse/core' - -export function useModal( - state: any, - uploadState: any, - ctx: SetupContext<('update:show' | 'updateList')[]> -) { - const { t } = useI18n() - - const handleCreateFunc = async () => { - submitRequest( - async () => - await createUdfFunc({ - ...state.functionForm - }) - ) - } - - const handleRenameFunc = async (id: number) => { - submitRequest(async () => { - await updateUdfFunc( - { - ...state.functionForm, - id - }, - id - ) - }) - } - - const submitRequest = async (serviceHandle: any) => { - await state.functionFormRef.validate() - - if (state.saving) return - state.saving = true - - try { - await serviceHandle() - window.$message.success(t('resource.function.success')) - state.saving = false - ctx.emit('updateList') - ctx.emit('update:show') - } catch (err) { - state.saving = false - } - } - - const variables = reactive({ - uploadShow: ref(false), - udfResourceList: [], - udfResourceDirList: [] - }) - - const filterEmptyDirectory = (list: any) => { - for (const item of list) { - if (item.children) { - filterEmptyDirectory(item.children) - } - } - return list.filter( - (n: any) => - (/\.jar$/.test(n.name) && n.children.length === 0) || - (!/\.jar$/.test(n.name) && n.children.length > 0) - ) - } - - // filterJarFile - const filterJarFile = (array: any) => { - for (const item of array) { - if (item.children) { - item.children = filterJarFile(item.children) - } - } - return array.filter((n: any) => !/\.jar$/.test(n.name)) - } - - // recursiveTree - const recursiveTree = (item: any) => { - // Recursive convenience tree structure - item.forEach((item: any) => { - item.children === '' || - item.children === undefined || - item.children === null || - item.children.length === 0 - ? delete item.children - : recursiveTree(item.children) - }) - } - - const getUdfList = () => { - const { state } = useAsyncState( - queryResourceList({ type: 'UDF', fullName: '' }).then((res: any) => { - let item = res - let item1 = _.cloneDeep(res) - - filterEmptyDirectory(item) - item = filterEmptyDirectory(item) - recursiveTree(item) - recursiveTree(filterJarFile(item1)) - item1 = item1.filter((item: any) => { - if (item.dirctory) { - return item - } - }) - variables.udfResourceList = item - variables.udfResourceDirList = item1 - }), - {} - ) - return state - } - - const resetUploadForm = () => { - uploadState.uploadForm.name = '' - uploadState.uploadForm.file = '' - uploadState.uploadForm.pid = -1 - uploadState.uploadForm.currentDir = '/' - uploadState.uploadForm.description = '' - } - - const handleUploadFile = () => { - uploadState.uploadFormRef.validate(async (valid: any) => { - if (!valid) { - const formData = new FormData() - formData.append('file', uploadState.uploadForm.file) - formData.append('type', 'UDF') - formData.append('name', uploadState.uploadForm.name) - formData.append('pid', uploadState.uploadForm.pid) - formData.append('currentDir', uploadState.uploadForm.currentDir) - formData.append('description', uploadState.uploadForm.description) - - const res = await createResource(formData as any) - window.$message.success(t('resource.function.success')) - variables.uploadShow = false - resetUploadForm() - getUdfList() - state.functionForm.resourceId = res.id - } - }) - } - - return { - variables, - getUdfList, - handleCreateFunc, - handleRenameFunc, - handleUploadFile - } -} diff --git a/dolphinscheduler-ui/src/views/resource/udf/function/index.module.scss b/dolphinscheduler-ui/src/views/resource/udf/function/index.module.scss deleted file mode 100644 index f717654df8..0000000000 --- a/dolphinscheduler-ui/src/views/resource/udf/function/index.module.scss +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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. - */ - -.table { - table { - width: 100%; - tr { - height: 40px; - font-size: 12px; - th, - td { - &:nth-child(1) { - width: 50px; - text-align: center; - } - } - th { - &:nth-child(1) { - width: 60px; - text-align: center; - } - > span { - font-size: 12px; - color: #555; - } - } - } - } -} diff --git a/dolphinscheduler-ui/src/views/resource/udf/function/index.tsx b/dolphinscheduler-ui/src/views/resource/udf/function/index.tsx deleted file mode 100644 index 06ef343430..0000000000 --- a/dolphinscheduler-ui/src/views/resource/udf/function/index.tsx +++ /dev/null @@ -1,159 +0,0 @@ -/* - * 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 { - defineComponent, - Ref, - toRefs, - onMounted, - toRef, - watch, - getCurrentInstance -} from 'vue' -import { NIcon, NSpace, NDataTable, NButton, NPagination } from 'naive-ui' -import { useI18n } from 'vue-i18n' -import { SearchOutlined } from '@vicons/antd' -import { useTable } from './use-table' -import Card from '@/components/card' -import FolderModal from './components/function-modal' -import Search from '@/components/input-search' -import styles from './index.module.scss' - -export default defineComponent({ - name: 'function-manage', - setup() { - const { variables, createColumns, getTableData } = useTable() - - const requestData = () => { - getTableData({ - id: variables.id, - fullName: variables.fullName, - pageSize: variables.pageSize, - pageNo: variables.page, - searchVal: variables.searchVal - }) - } - - const handleUpdateList = () => { - requestData() - } - - const handleChangePageSize = () => { - variables.page = 1 - requestData() - } - - const handleSearch = () => { - variables.page = 1 - requestData() - } - - const handleShowModal = (showRef: Ref) => { - showRef.value = true - } - - const handleCreateFolder = () => { - variables.row = {} - handleShowModal(toRef(variables, 'showRef')) - } - - const trim = getCurrentInstance()?.appContext.config.globalProperties.trim - - watch(useI18n().locale, () => { - createColumns(variables) - }) - - onMounted(() => { - createColumns(variables) - requestData() - }) - - return { - requestData, - handleSearch, - handleUpdateList, - handleCreateFolder, - handleChangePageSize, - ...toRefs(variables), - trim - } - }, - render() { - const { t } = useI18n() - const { loadingRef } = this - - return ( - - - - - {t('resource.function.create_udf_function')} - - - - - - - - - - - - - - - - - - - - - - ) - } -}) diff --git a/dolphinscheduler-ui/src/views/resource/udf/function/types.ts b/dolphinscheduler-ui/src/views/resource/udf/function/types.ts deleted file mode 100644 index 0e507676d9..0000000000 --- a/dolphinscheduler-ui/src/views/resource/udf/function/types.ts +++ /dev/null @@ -1,39 +0,0 @@ -/* - * 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. - */ - -export interface IUdfFunctionParam { - fullName: string - id: number - pageSize: number - pageNo: number - searchVal: string | undefined -} - -export interface IUdf { - id: number - userId: number - type: 'HIVE' - funcName: string - className: string - resourceId: number - resourceName: string - argTypes: string - database: string - description: string - createTime: string - updateTime: string -} diff --git a/dolphinscheduler-ui/src/views/resource/udf/function/use-table.ts b/dolphinscheduler-ui/src/views/resource/udf/function/use-table.ts deleted file mode 100644 index 75fc406e38..0000000000 --- a/dolphinscheduler-ui/src/views/resource/udf/function/use-table.ts +++ /dev/null @@ -1,215 +0,0 @@ -/* - * 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 { h, ref, reactive } from 'vue' -import { useI18n } from 'vue-i18n' -import { useRouter } from 'vue-router' -import type { Router } from 'vue-router' -import type { TableColumns } from 'naive-ui/es/data-table/src/interface' -import { NSpace, NTooltip, NButton, NPopconfirm } from 'naive-ui' -import { EditOutlined, DeleteOutlined } from '@vicons/antd' -import { useAsyncState } from '@vueuse/core' -import { - queryUdfFuncListPaging, - deleteUdfFunc -} from '@/service/modules/resources' -import { - COLUMN_WIDTH_CONFIG, - calculateTableWidth, - DefaultTableWidth -} from '@/common/column-width-config' -import type { IUdfFunctionParam } from './types' - -export function useTable() { - const { t } = useI18n() - const router: Router = useRouter() - - const variables = reactive({ - columns: [], - tableWidth: DefaultTableWidth, - row: {}, - tableData: [], - // here is id not prefix because udf function is still stored in db - id: ref(Number(router.currentRoute.value.params.id) || -1), - fullName: ref(String(router.currentRoute.value.query.prefix || '')), - page: ref(1), - pageSize: ref(10), - searchVal: ref(), - totalPage: ref(1), - showRef: ref(false), - loadingRef: ref(false) - }) - - const createColumns = (variables: any) => { - variables.columns = [ - { - title: '#', - key: 'id', - render: (_row, index) => index + 1, - ...COLUMN_WIDTH_CONFIG['index'] - }, - { - title: t('resource.function.udf_function_name'), - key: 'funcName', - ...COLUMN_WIDTH_CONFIG['name'] - }, - { - title: t('resource.function.user_name'), - ...COLUMN_WIDTH_CONFIG['userName'], - key: 'userName' - }, - { - title: t('resource.function.class_name'), - key: 'className', - ...COLUMN_WIDTH_CONFIG['name'] - }, - { - title: t('resource.function.type'), - key: 'type', - ...COLUMN_WIDTH_CONFIG['type'] - }, - { - title: t('resource.function.description'), - key: 'description', - ...COLUMN_WIDTH_CONFIG['note'] - }, - { - title: t('resource.function.jar_package'), - key: 'resourceName', - ...COLUMN_WIDTH_CONFIG['name'] - }, - { - title: t('resource.function.update_time'), - key: 'updateTime', - ...COLUMN_WIDTH_CONFIG['time'] - }, - { - title: t('resource.function.operation'), - key: 'operation', - ...COLUMN_WIDTH_CONFIG['operation'](2), - render: (row) => { - return h(NSpace, null, { - default: () => [ - h( - NTooltip, - {}, - { - trigger: () => - h( - NButton, - { - circle: true, - type: 'info', - size: 'tiny', - class: 'btn-edit', - onClick: () => { - handleEdit(row) - } - }, - { - icon: () => h(EditOutlined) - } - ), - default: () => t('resource.function.edit') - } - ), - h( - NPopconfirm, - { - onPositiveClick: () => { - handleDelete(row.id, row.fullName) - } - }, - { - trigger: () => - h( - NTooltip, - {}, - { - trigger: () => - h( - NButton, - { - circle: true, - type: 'error', - size: 'tiny', - class: 'btn-delete' - }, - { - icon: () => h(DeleteOutlined) - } - ), - default: () => t('resource.function.delete') - } - ), - default: () => t('resource.function.delete_confirm') - } - ) - ] - }) - } - } - ] as TableColumns - if (variables.tableWidth) { - variables.tableWidth = calculateTableWidth(variables.columns) - } - } - - const getTableData = (params: IUdfFunctionParam) => { - if (variables.loadingRef) return - variables.loadingRef = true - const { state } = useAsyncState( - queryUdfFuncListPaging({ ...params }).then((res: any) => { - variables.totalPage = res.totalPage - variables.tableData = res.totalList.map((item: any) => { - return { ...item } - }) - variables.loadingRef = false - }), - { total: 0, table: [] } - ) - return state - } - - const handleEdit = (row: any) => { - variables.showRef = true - variables.row = row - } - - const handleDelete = (id: number, fullName: string) => { - /* after deleting data from the current page, you need to jump forward when the page is empty. */ - if (variables.tableData.length === 1 && variables.page > 1) { - variables.page -= 1 - } - - deleteUdfFunc(id, { fullName: fullName }).then(() => - getTableData({ - fullName: variables.fullName, - id: variables.id, - pageSize: variables.pageSize, - pageNo: variables.page, - searchVal: variables.searchVal - }) - ) - } - - return { - variables, - createColumns, - getTableData - } -} diff --git a/dolphinscheduler-ui/src/views/resource/udf/resource/index.tsx b/dolphinscheduler-ui/src/views/resource/udf/resource/index.tsx deleted file mode 100644 index 0d78ade01f..0000000000 --- a/dolphinscheduler-ui/src/views/resource/udf/resource/index.tsx +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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 { defineComponent } from 'vue' -import ResourceListModal from '../../components/resource' - -export default defineComponent({ - name: 'resource-manage', - setup() {}, - render() { - return - } -}) diff --git a/dolphinscheduler-ui/src/views/security/user-manage/components/authorize-modal.tsx b/dolphinscheduler-ui/src/views/security/user-manage/components/authorize-modal.tsx index 8490a3f6b9..ee4b3b2635 100644 --- a/dolphinscheduler-ui/src/views/security/user-manage/components/authorize-modal.tsx +++ b/dolphinscheduler-ui/src/views/security/user-manage/components/authorize-modal.tsx @@ -23,9 +23,6 @@ import { NIcon, NTransfer, NSpace, - NRadioGroup, - NRadioButton, - NTreeSelect, NDataTable, NPagination } from 'naive-ui' @@ -201,51 +198,6 @@ export const AuthorizeModal = defineComponent({ class={styles.transfer} /> )} - {type === 'authorize_udf' && ( - - )} - {type === 'authorize_resource' && ( - - - - {t('security.user.file_resource')} - - - {t('security.user.udf_resource')} - - - - - - )} {type === 'authorize_namespace' && ( { - if (state.loading) return - state.loading = true - const udfs = await Promise.all([ - authUDFFunc({ userId }), - unAuthUDFFunc({ userId }) - ]) - state.loading = false - state.authorizedUdfs = udfs[0].map( - (item: { funcName: string; id: number }) => item.id - ) - state.unauthorizedUdfs = [...udfs[0], ...udfs[1]].map( - (item: { funcName: string; id: number }) => ({ - label: item.funcName, - value: item.id - }) - ) - } - - const getResources = async (userId: number) => { - if (state.loading) return - state.loading = true - const resources = await Promise.all([ - authorizeResourceTree({ userId }), - authorizedFile({ userId }) - ]) - state.loading = false - utils.removeUselessChildren(resources[0]) - const udfResources = [] as IResourceOption[] - const fileResources = [] as IResourceOption[] - resources[0].forEach((item: IResourceOption) => { - item.type === 'FILE' ? fileResources.push(item) : udfResources.push(item) - }) - const udfTargets = [] as number[] - const fileTargets = [] as number[] - resources[1].forEach((item: { type: string; id: number }) => { - item.type === 'FILE' - ? fileTargets.push(item.id) - : udfTargets.push(item.id) - }) - state.fileResources = fileResources - state.udfResources = udfResources - state.authorizedFileResources = fileTargets - state.authorizedUdfResources = udfTargets - } - const getNamespaces = async (userId: number) => { if (state.loading) return state.loading = true @@ -223,12 +163,6 @@ export function useAuthorize() { if (type === 'authorize_datasource') { getDatasources(userId) } - if (type === 'authorize_udf') { - getUdfs(userId) - } - if (type === 'authorize_resource') { - getResources(userId) - } if (type === 'authorize_namespace') { getNamespaces(userId) } @@ -237,33 +171,6 @@ export function useAuthorize() { /* getParent */ - const getParent = (data2: Array, nodeId2: number) => { - let arrRes: Array = [] - if (data2.length === 0) { - if (nodeId2) { - arrRes.unshift(data2) - } - return arrRes - } - const rev = (data: Array, nodeId: number) => { - for (let i = 0, length = data.length; i < length; i++) { - const node = data[i] - if (node.id === nodeId) { - arrRes.unshift(node) - rev(data2, node.pid) - break - } else { - if (node.children) { - rev(node.children, nodeId) - } - } - } - return arrRes - } - arrRes = rev(data2, nodeId2) - return arrRes - } - const onSave = async (type: TAuthType, userId: number) => { if (state.saving) return false state.saving = true @@ -273,49 +180,6 @@ export function useAuthorize() { datasourceIds: state.authorizedDatasources.join(',') }) } - if (type === 'authorize_udf') { - await grantUDFFunc({ - userId, - udfIds: state.authorizedUdfs.join(',') - }) - } - if (type === 'authorize_resource') { - let fullPathFileId = [] - const pathFileId: Array = [] - state.authorizedFileResources.forEach((v: number) => { - state.fileResources.forEach((v1: any) => { - const arr = [] - arr[0] = v1 - if (getParent(arr, v).length > 0) { - fullPathFileId = getParent(arr, v).map((v2: any) => { - return v2.id - }) - pathFileId.push(fullPathFileId.join('-')) - } - }) - }) - - let fullPathUdfId = [] - const pathUdfId: Array = [] - state.authorizedUdfResources.forEach((v: number) => { - state.udfResources.forEach((v1: any) => { - const arr = [] - arr[0] = v1 - if (getParent(arr, v).length > 0) { - fullPathUdfId = getParent(arr, v).map((v2: any) => { - return v2.id - }) - pathUdfId.push(fullPathUdfId.join('-')) - } - }) - }) - - const allPathId = pathFileId.concat(pathUdfId) - await grantResource({ - userId, - resourceIds: allPathId.join(',') - }) - } if (type === 'authorize_namespace') { await grantNamespaceFunc({ userId, diff --git a/dolphinscheduler-ui/src/views/security/user-manage/types.ts b/dolphinscheduler-ui/src/views/security/user-manage/types.ts index bf51fe3947..34aaca48b7 100644 --- a/dolphinscheduler-ui/src/views/security/user-manage/types.ts +++ b/dolphinscheduler-ui/src/views/security/user-manage/types.ts @@ -24,9 +24,7 @@ export type { UserInfoRes } from '@/service/modules/users/types' type TUserType = 'GENERAL_USER' | '' type TAuthType = | 'authorize_project' - | 'authorize_resource' | 'authorize_datasource' - | 'authorize_udf' | 'authorize_namespace' interface IRecord { diff --git a/dolphinscheduler-ui/src/views/security/user-manage/use-columns.ts b/dolphinscheduler-ui/src/views/security/user-manage/use-columns.ts index 812e104e9d..3efc2c1670 100644 --- a/dolphinscheduler-ui/src/views/security/user-manage/use-columns.ts +++ b/dolphinscheduler-ui/src/views/security/user-manage/use-columns.ts @@ -140,15 +140,10 @@ export function useColumns(onCallback: Function) { label: t('security.user.project'), key: 'authorize_project' }, - { - label: t('security.user.resource'), - key: 'authorize_resource' - }, { label: t('security.user.datasource'), key: 'authorize_datasource' }, - { label: t('security.user.udf'), key: 'authorize_udf' }, { label: t('security.user.namespace'), key: 'authorize_namespace'