From 5002ce34b11beef319f8845b2b4c3bbe45db56b3 Mon Sep 17 00:00:00 2001 From: Zoe Date: Wed, 17 Apr 2024 14:30:41 +0800 Subject: [PATCH] sync: hosted cloud (#1720) Signed-off-by: wangkailang Co-authored-by: Caedman Ziwen Lan Co-authored-by: Kilian Co-authored-by: Zoe <13400804+zoe-icu@users.noreply.github.com> Co-authored-by: chalme Co-authored-by: Aria Co-authored-by: XuKecheng Co-authored-by: xukecheng Co-authored-by: Chambers Co-authored-by: ziqiang Co-authored-by: Evie Wanmei Huang <37546178+wmEvie@users.noreply.github.com> Co-authored-by: jeremyyin Co-authored-by: William Chan Co-authored-by: wangkailang Co-authored-by: yanmingZhang <44792891+quppenge@users.noreply.github.com> Co-authored-by: Jover Co-authored-by: paylm penglong feng Co-authored-by: Caedman Ziwen Lan <12862508+ranglang@users.noreply.github.com> --- Makefile | 1 + .../controller/AutomationRobotController.java | 30 +- .../entity/AutomationActionTypeEntity.java | 126 +++++ .../entity/AutomationRunHistoryEntity.java | 87 ++++ .../enums/AutomationActionType.java | 41 ++ .../mapper/AutomationActionMapper.java | 7 + .../mapper/AutomationActionTypeMapper.java | 47 ++ .../mapper/AutomationRobotMapper.java | 8 + .../mapper/AutomationRunHistoryMapper.java | 65 +++ .../mapper/AutomationTriggerMapper.java | 11 +- .../apitable/automation/model/ActionVO.java | 5 +- .../model/AutomationRunHistoryDTO.java | 44 ++ .../automation/model/AutomationRunTaskVO.java | 67 +++ .../service/IAutomationActionService.java | 11 +- .../service/IAutomationActionTypeService.java | 36 ++ .../service/IAutomationRobotService.java | 8 + .../service/IAutomationRunHistoryService.java | 25 +- .../service/IAutomationTriggerService.java | 9 + .../impl/AutomationActionServiceImpl.java | 126 ++++- .../impl/AutomationActionTypeServiceImpl.java | 50 ++ .../impl/AutomationRobotServiceImpl.java | 14 +- .../impl/AutomationRunHistoryServiceImpl.java | 164 +++--- .../impl/AutomationTriggerServiceImpl.java | 5 + .../InternalNodePermissionController.java | 4 +- .../organization/dto/UnitMemberTeamDTO.java | 2 +- .../service/impl/MemberServiceImpl.java | 26 +- .../service/impl/RoleServiceImpl.java | 2 +- .../apitable/space/mapper/StaticsMapper.java | 2 +- .../apitable/space/service/ISpaceService.java | 8 + .../impl/SpaceInviteLinkServiceImpl.java | 8 +- .../space/service/impl/SpaceServiceImpl.java | 5 + .../service/impl/StaticsServiceImpl.java | 18 +- .../service/impl/TemplateServiceImpl.java | 2 + .../apitable/widget/mapper/WidgetMapper.java | 8 + .../widget/service/IWidgetService.java | 16 + .../service/impl/WidgetServiceImpl.java | 10 + .../workspace/controller/NodeController.java | 4 + .../mapper/DatasheetWidgetMapper.java | 8 + .../apitable/workspace/mapper/NodeMapper.java | 13 +- .../workspace/mapper/NodeRelMapper.java | 8 + .../workspace/service/INodeRelService.java | 8 + .../workspace/service/INodeService.java | 15 + .../service/impl/NodeRelServiceImpl.java | 17 + .../service/impl/NodeRubbishServiceImpl.java | 2 +- .../service/impl/NodeServiceImpl.java | 49 +- .../workspace/vo/NodeStatisticsVo.java | 11 + .../automation/AutomationActionMapper.xml | 6 + .../automation/AutomationActionTypeMapper.xml | 41 ++ .../automation/AutomationRobotMapper.xml | 10 + .../automation/AutomationRunHistoryMapper.xml | 49 ++ .../automation/AutomationTriggerMapper.xml | 10 + .../mapper/organization/MemberMapper.xml | 1 + .../resources/mapper/space/StaticsMapper.xml | 13 +- .../resources/mapper/widget/WidgetMapper.xml | 10 + .../workspace/DatasheetWidgetMapper.xml | 9 + .../resources/mapper/workspace/NodeMapper.xml | 15 +- .../mapper/workspace/NodeRelMapper.xml | 9 + .../src/main/resources/sysconfig/strings.json | 67 ++- .../com/apitable/AbstractIntegrationTest.java | 8 + .../impl/AutomationActionServiceImplTest.java | 24 + .../space/mapper/StaticsMapperTest.java | 5 +- .../service/impl/NodeServiceImplTest.java | 233 +++++++++ .../resources/file/email_action_input.json | 153 ++++++ package.json | 9 +- .../src/components/calendar/calendar.tsx | 42 +- .../src/components/text_input/text_input.tsx | 2 + .../core/src/config/stringkeys.interface.ts | 3 + .../core/src/config/system_config.source.json | 2 +- packages/core/src/config/timezones.ts | 11 +- packages/core/src/model/constants.ts | 2 +- .../selectors/resource/datasheet/calc.ts | 25 +- packages/core/src/sync/room.ts | 207 ++++---- packages/core/src/utils/jot.ts | 2 + packages/datasheet/package.json | 4 +- .../datasheet/public/custom/custom_config.js | 334 +++++++++--- .../public/file/langs/strings.de-DE.json | 5 +- .../public/file/langs/strings.en-US.json | 5 +- .../public/file/langs/strings.es-ES.json | 9 +- .../public/file/langs/strings.fr-FR.json | 5 +- .../public/file/langs/strings.it-IT.json | 7 +- .../public/file/langs/strings.ja-JP.json | 5 +- .../datasheet/public/file/langs/strings.json | 63 ++- .../public/file/langs/strings.ko-KR.json | 5 +- .../public/file/langs/strings.ru-RU.json | 5 +- .../public/file/langs/strings.zh-CN.json | 12 +- .../public/file/langs/strings.zh-HK.json | 5 +- .../modules/shared/apphook/hook_bindings.ts | 1 + .../src/modules/shared/shortcut_key/enum.ts | 1 + .../shared/shortcut_key/shortcut_key.ts | 9 +- .../src/pc/common/store_subscribe/keep_id.ts | 11 + .../components/address_list/address_list.tsx | 2 +- .../address_list/member_info/member_info.tsx | 4 +- .../address_list/member_list/member_list.tsx | 23 +- .../automation/run_history/modal/index.tsx | 6 + .../catalog/move_to/folder_item.tsx | 2 +- .../pc/components/catalog/move_to/move_to.tsx | 26 +- .../catalog/tree/node_icon/node_icon.tsx | 4 +- .../src/pc/components/common/emoji/emoji.less | 2 +- .../common/tree_view/tree_view/tree_view.tsx | 1 + .../workbench_side/workbench_side.tsx | 75 +-- .../data_source_selector.tsx | 23 +- .../data_source_selector/interface.ts | 1 + .../data_source_selector_for_node.tsx | 3 + .../checkbox_editor/checkbox_editor.tsx | 8 +- .../rating_editor/rating_editor_mobile.tsx | 10 +- .../editors/text_editor/text_editor.tsx | 3 +- .../expand_cascader/expand_cascader.tsx | 6 +- .../folder_showcase/folder_showcase.tsx | 6 +- .../form_container/form_container.tsx | 6 +- .../cell/cell_lookup/cell_lookup.tsx | 1 + .../cell/cell_rating/cell_rating.tsx | 10 +- .../multi_grid/context_menu/record_menu.tsx | 40 +- .../format/format_button/format_button.tsx | 44 +- .../multi_grid/format/format_checkbox.tsx | 9 +- .../no_permission/no_permission.tsx | 4 +- .../banner_alert/banner_alert.tsx | 7 +- .../workdoc_image/expand_workdoc_image.tsx | 1 + .../preview_file/workdoc_image/portal.tsx | 7 +- .../components/quick_search/search_base.tsx | 6 +- .../datasheet/src/pc/components/robot/api.ts | 2 +- .../robot_detail/action/robot_action.tsx | 18 +- .../fields/ObjectField/ObjectField.tsx | 2 +- .../components/widgets/PasswordWidget.tsx | 6 +- .../robot_run_history_item_detail.tsx | 10 +- .../robot_detail/trigger/robot_trigger.tsx | 3 + .../route_manager/router_provider.tsx | 5 +- .../setting_nickname/setting_nickname.tsx | 10 +- .../del_confirm_modal/del_confirm_modal.tsx | 1 + .../recover_space/recover_space.tsx | 14 +- .../space_manage/space_info/layout/cards.tsx | 2 +- .../template_choice/template_choice.tsx | 1 - .../src/pc/components/time_machine/index.tsx | 110 ++-- .../src/pc/components/tool_bar/find/find.tsx | 2 +- .../filter_conjunction/filter_conjunction.tsx | 2 +- .../send_to_dashboard/send_to_dashboard.tsx | 14 +- .../widget_panel/widget_item/widget_item.tsx | 12 +- .../src/pc/hooks/use_catalogtree_request.ts | 8 +- .../src/pc/utils/transform_node_tree_data.ts | 2 +- .../i18n-lang/src/config/strings.de-DE.json | 5 +- .../i18n-lang/src/config/strings.en-US.json | 5 +- .../i18n-lang/src/config/strings.es-ES.json | 9 +- .../i18n-lang/src/config/strings.fr-FR.json | 5 +- .../i18n-lang/src/config/strings.it-IT.json | 7 +- .../i18n-lang/src/config/strings.ja-JP.json | 5 +- packages/i18n-lang/src/config/strings.json | 63 ++- .../i18n-lang/src/config/strings.ko-KR.json | 5 +- .../i18n-lang/src/config/strings.ru-RU.json | 5 +- .../i18n-lang/src/config/strings.zh-CN.json | 12 +- .../i18n-lang/src/config/strings.zh-HK.json | 5 +- packages/l10n/base/strings.en-US.json | 7 +- packages/l10n/base/strings.es-ES.json | 4 +- packages/l10n/base/strings.it-IT.json | 2 +- packages/l10n/base/strings.zh-CN.json | 18 +- packages/room-server/package.json | 5 +- packages/room-server/src/app.environment.ts | 2 + .../repositories/datasheet.meta.repository.ts | 49 +- .../services/datasheet.record.service.ts | 393 +++++++------- .../src/fusion/field/date.time.field.spec.ts | 15 +- .../src/fusion/field/date.time.field.ts | 30 +- packages/widget-sdk/src/hooks/index.ts | 1 + .../widget-sdk/src/hooks/use_unit_info.ts | 9 + .../widget-sdk/src/ui/filter/filter_base.tsx | 8 +- packaging/Dockerfile.room-server | 2 + patches/mysql2@2.3.3.patch | 12 + pnpm-lock.yaml | 481 +++++++++++------- 165 files changed, 3318 insertions(+), 1097 deletions(-) create mode 100644 backend-server/application/src/main/java/com/apitable/automation/entity/AutomationActionTypeEntity.java create mode 100644 backend-server/application/src/main/java/com/apitable/automation/entity/AutomationRunHistoryEntity.java create mode 100644 backend-server/application/src/main/java/com/apitable/automation/enums/AutomationActionType.java create mode 100644 backend-server/application/src/main/java/com/apitable/automation/mapper/AutomationActionTypeMapper.java create mode 100644 backend-server/application/src/main/java/com/apitable/automation/mapper/AutomationRunHistoryMapper.java create mode 100644 backend-server/application/src/main/java/com/apitable/automation/model/AutomationRunHistoryDTO.java create mode 100644 backend-server/application/src/main/java/com/apitable/automation/model/AutomationRunTaskVO.java create mode 100644 backend-server/application/src/main/java/com/apitable/automation/service/IAutomationActionTypeService.java create mode 100644 backend-server/application/src/main/java/com/apitable/automation/service/impl/AutomationActionTypeServiceImpl.java create mode 100644 backend-server/application/src/main/resources/mapper/automation/AutomationActionTypeMapper.xml create mode 100644 backend-server/application/src/main/resources/mapper/automation/AutomationRunHistoryMapper.xml create mode 100644 backend-server/application/src/test/java/com/apitable/automation/service/impl/AutomationActionServiceImplTest.java create mode 100644 backend-server/application/src/test/resources/file/email_action_input.json create mode 100644 packages/widget-sdk/src/hooks/use_unit_info.ts create mode 100644 patches/mysql2@2.3.3.patch diff --git a/Makefile b/Makefile index 2f1bca6d71..c3c22cd102 100644 --- a/Makefile +++ b/Makefile @@ -188,6 +188,7 @@ test-ut-room-docker: -e MYSQL_HOST=test-mysql \ -e REDIS_HOST=test-redis \ -e RABBITMQ_HOST=test-rabbitmq \ + -e TZ=UTC \ unit-test-room pnpm run test:ut:room:cov @echo "${GREEN}finished unit test, clean up images...${RESET}" diff --git a/backend-server/application/src/main/java/com/apitable/automation/controller/AutomationRobotController.java b/backend-server/application/src/main/java/com/apitable/automation/controller/AutomationRobotController.java index b76379ab4a..1029486ecb 100644 --- a/backend-server/application/src/main/java/com/apitable/automation/controller/AutomationRobotController.java +++ b/backend-server/application/src/main/java/com/apitable/automation/controller/AutomationRobotController.java @@ -20,6 +20,7 @@ import cn.hutool.core.util.StrUtil; import com.apitable.automation.model.ActionVO; +import com.apitable.automation.model.AutomationRunTaskVO; import com.apitable.automation.model.AutomationSimpleVO; import com.apitable.automation.model.AutomationTaskSimpleVO; import com.apitable.automation.model.AutomationVO; @@ -47,6 +48,7 @@ import com.apitable.workspace.mapper.NodeDescMapper; import com.apitable.workspace.ro.NodeUpdateOpRo; import com.apitable.workspace.service.INodeService; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameters; @@ -215,13 +217,29 @@ public ResponseData deleteRobot(@PathVariable String resourceId, } /** - * Get automation run history. + * Get automation run history detail. * - * @param pageSize page query parameter - * @param pageNum page query parameter + * @param taskId task id + * @return {@link ResponseData} + */ + @GetResource(path = "/run-history/{taskId}", requiredPermission = false, requiredLogin = false) + @Parameter(name = "taskId", description = "task id", required = true, schema = @Schema(type = "string"), in = ParameterIn.PATH, example = "123****") + @Operation(summary = "Get automation run history task details") + @ApiResponses(@ApiResponse(responseCode = "200", useReturnTypeSchema = true)) + public ResponseData getRunHistoryTaskDetail( + @PathVariable String taskId) { + return ResponseData.success(iAutomationRunHistoryService.getByTaskDetail(taskId)); + } + + /** + * get automation run history. + * + * @param pageSize page size of automation run history + * @param pageNum page number + * @param shareId share id * @param resourceId resource id * @param robotId robot id - * @return {@link ResponseData} + * @return response */ @GetResource(path = "/{resourceId}/roots/{robotId}/run-history", requiredPermission = false, requiredLogin = false) @Parameters({ @@ -243,8 +261,10 @@ public ResponseData> getRunHistory( iPermissionService.checkPermissionBySessionOrShare(resourceId, shareId, NodePermission.READ_NODE, status -> ExceptionUtil.isTrue(status, PermissionException.NODE_OPERATION_DENIED)); + String spaceId = iNodeService.getSpaceIdByNodeId(resourceId); + Page page = Page.of(pageNum, pageSize); return ResponseData.success( - iAutomationRunHistoryService.getRobotRunHistory(robotId, pageSize, pageNum)); + iAutomationRunHistoryService.getRobotRunHistory(spaceId, robotId, page)); } /** diff --git a/backend-server/application/src/main/java/com/apitable/automation/entity/AutomationActionTypeEntity.java b/backend-server/application/src/main/java/com/apitable/automation/entity/AutomationActionTypeEntity.java new file mode 100644 index 0000000000..5151d4166c --- /dev/null +++ b/backend-server/application/src/main/java/com/apitable/automation/entity/AutomationActionTypeEntity.java @@ -0,0 +1,126 @@ +/* + * APITable Ltd. + * Copyright (C) 2022 APITable Ltd. + * + * This code file is part of APITable Enterprise Edition. + * + * It is subject to the APITable Commercial License and conditional on having a fully paid-up license from APITable. + * + * Access to this code file or other code files in this `enterprise` directory and its subdirectories does not constitute permission to use this code or APITable Enterprise Edition features. + * + * Unless otherwise noted, all files Copyright © 2022 APITable Ltd. + * + * For purchase of APITable Enterprise Edition license, please contact . + */ + +package com.apitable.automation.entity; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableLogic; +import com.baomidou.mybatisplus.annotation.TableName; +import java.io.Serializable; +import java.time.LocalDateTime; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +/** + *

+ * Automation - Action Type Table. + *

+ * + * @author Mybatis Generator Tool + */ +@Data +@Builder(toBuilder = true) +@NoArgsConstructor +@AllArgsConstructor +@Accessors(chain = true) +@EqualsAndHashCode +@TableName(keepGlobalPrefix = true, value = "automation_action_type") +public class AutomationActionTypeEntity implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * Primary Key. + */ + @TableId(value = "id", type = IdType.ASSIGN_ID) + private Long id; + + /** + * Service ID (link#xxxx_automation_service#service_id). + */ + private String serviceId; + + /** + * Custom action prototype ID. + */ + private String actionTypeId; + + /** + * Name. + */ + private String name; + + /** + * Description. + */ + private String description; + + /** + * Input JSON normal form. + */ + private String inputJsonSchema; + + /** + * Output JSON normal form. + */ + private String outputJsonSchema; + + /** + * Call interface. + */ + private String endpoint; + + /** + * Internationalized Language Pack. + */ + private String i18n; + + /** + * Delete Tag(0: No, 1: Yes). + */ + @TableLogic + private Integer isDeleted; + + /** + * Creator. + */ + @TableField(fill = FieldFill.INSERT) + private Long createdBy; + + /** + * Last Update By. + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private Long updatedBy; + + /** + * Create Time. + */ + private LocalDateTime createdAt; + + /** + * Update Time. + */ + private LocalDateTime updatedAt; + + +} diff --git a/backend-server/application/src/main/java/com/apitable/automation/entity/AutomationRunHistoryEntity.java b/backend-server/application/src/main/java/com/apitable/automation/entity/AutomationRunHistoryEntity.java new file mode 100644 index 0000000000..90247edd80 --- /dev/null +++ b/backend-server/application/src/main/java/com/apitable/automation/entity/AutomationRunHistoryEntity.java @@ -0,0 +1,87 @@ +/* + * APITable + * Copyright (C) 2022 APITable Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.apitable.automation.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import java.io.Serializable; +import java.math.BigInteger; +import java.time.LocalDateTime; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +/** + *

+ * Automation - Run history table. + *

+ * + * @author Mybatis Generator Tool + */ +@Data +@Builder(toBuilder = true) +@NoArgsConstructor +@AllArgsConstructor +@Accessors(chain = true) +@EqualsAndHashCode +@TableName(keepGlobalPrefix = true, value = "automation_run_history") +public class AutomationRunHistoryEntity implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * Primary Key. + */ + @TableId(value = "id", type = IdType.ASSIGN_ID) + private BigInteger id; + + /** + * Task ID. + */ + private String taskId; + + /** + * Custom Robot ID. + */ + private String robotId; + + /** + * Space ID. + */ + private String spaceId; + + /** + * Running status (0: Running, 1: Success, 2: Failure). + */ + private Integer status; + + /** + * Run Context Details. + */ + private String data; + + /** + * Create Time. + */ + private LocalDateTime createdAt; +} diff --git a/backend-server/application/src/main/java/com/apitable/automation/enums/AutomationActionType.java b/backend-server/application/src/main/java/com/apitable/automation/enums/AutomationActionType.java new file mode 100644 index 0000000000..e254495e22 --- /dev/null +++ b/backend-server/application/src/main/java/com/apitable/automation/enums/AutomationActionType.java @@ -0,0 +1,41 @@ +/* + * APITable + * Copyright (C) 2022 APITable Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + + +package com.apitable.automation.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * automation action type. + */ +@Getter +@AllArgsConstructor +public enum AutomationActionType { + + SEND_MAIL("sendMail"), + + SEND_REQUEST("sendRequest"), + + + ; + + + private final String type; +} diff --git a/backend-server/application/src/main/java/com/apitable/automation/mapper/AutomationActionMapper.java b/backend-server/application/src/main/java/com/apitable/automation/mapper/AutomationActionMapper.java index 8a94fb0537..46d8cd49cd 100644 --- a/backend-server/application/src/main/java/com/apitable/automation/mapper/AutomationActionMapper.java +++ b/backend-server/application/src/main/java/com/apitable/automation/mapper/AutomationActionMapper.java @@ -58,4 +58,11 @@ int updateActionTypeIdAndInputByRobotId(@Param("robotId") String robotId, String updatedActionTypeId, @Param("updatedInput") String updatedInput); + /** + * query by action id. + * + * @param actionId action id + * @return AutomationActionEntity + */ + AutomationActionEntity selectByActionId(@Param("actionId") String actionId); } diff --git a/backend-server/application/src/main/java/com/apitable/automation/mapper/AutomationActionTypeMapper.java b/backend-server/application/src/main/java/com/apitable/automation/mapper/AutomationActionTypeMapper.java new file mode 100644 index 0000000000..c605ba0f2c --- /dev/null +++ b/backend-server/application/src/main/java/com/apitable/automation/mapper/AutomationActionTypeMapper.java @@ -0,0 +1,47 @@ +/* + * APITable + * Copyright (C) 2022 APITable Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.apitable.automation.mapper; + +import com.apitable.automation.entity.AutomationActionTypeEntity; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Param; + +/** + * action type mapping. + */ +public interface AutomationActionTypeMapper extends BaseMapper { + + /** + * Get action type by endpoint. + * + * @param endpoint invocation interface + * @return action type + */ + String getActionTypeIdByEndpoint(@Param("endpoint") String endpoint); + + Long selectIdByActionTypeId(@Param("actionTypeId") String actionTypeId); + + /** + * query endpoint. + * + * @param actionTypeId action type id + * @return endpoint + */ + String selectEndpointByActionTypeId(@Param("actionTypeId") String actionTypeId); +} diff --git a/backend-server/application/src/main/java/com/apitable/automation/mapper/AutomationRobotMapper.java b/backend-server/application/src/main/java/com/apitable/automation/mapper/AutomationRobotMapper.java index d3161d8e4d..ae1e7c278a 100644 --- a/backend-server/application/src/main/java/com/apitable/automation/mapper/AutomationRobotMapper.java +++ b/backend-server/application/src/main/java/com/apitable/automation/mapper/AutomationRobotMapper.java @@ -122,4 +122,12 @@ List getRobotTriggers(@Param("seqId") String seqId, */ int updateUpdatedByRobotId(@Param("robotId") String robotId, @Param("updatedBy") Long updatedBy); + + /** + * query robot id exists. + * + * @param robotIds robot id + * @return resource id of robot + */ + List selectResourceIdsByRobotIds(@Param("robotIds") List robotIds); } diff --git a/backend-server/application/src/main/java/com/apitable/automation/mapper/AutomationRunHistoryMapper.java b/backend-server/application/src/main/java/com/apitable/automation/mapper/AutomationRunHistoryMapper.java new file mode 100644 index 0000000000..3718cdbb8c --- /dev/null +++ b/backend-server/application/src/main/java/com/apitable/automation/mapper/AutomationRunHistoryMapper.java @@ -0,0 +1,65 @@ +/* + * APITable + * Copyright (C) 2022 APITable Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.apitable.automation.mapper; + +import com.apitable.automation.entity.AutomationRunHistoryEntity; +import com.apitable.automation.model.AutomationRunHistoryDTO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import java.time.LocalDate; +import java.util.List; +import org.apache.ibatis.annotations.Param; + +/** + * automation history mapper. + */ +public interface AutomationRunHistoryMapper extends BaseMapper { + /** + * query id by robot id and created at. + * + * @param spaceId space id + * @param robotId robot id + * @param startAt start times + * @param endAt end times + * @param page page + * @return IPage NodeStatisticsDTO + */ + IPage selectIdByRobotIdAndSpaceIdAndBetweenWithPage(@Param("robotId") String robotId, + @Param("spaceId") String spaceId, + @Param("startAt") LocalDate startAt, + @Param("endAt") LocalDate endAt, + Page page); + + /** + * query base info. + * + * @param ids primary keys + * @return AutomationRunHistoryDTO + */ + List selectByIds(@Param("ids") List ids); + + /** + * query detail. + * + * @param taskId task id + * @return AutomationRunHistoryEntity + */ + AutomationRunHistoryEntity selectByTaskId(@Param("taskId") String taskId); +} diff --git a/backend-server/application/src/main/java/com/apitable/automation/mapper/AutomationTriggerMapper.java b/backend-server/application/src/main/java/com/apitable/automation/mapper/AutomationTriggerMapper.java index dbdc1923e2..7654e6dabd 100644 --- a/backend-server/application/src/main/java/com/apitable/automation/mapper/AutomationTriggerMapper.java +++ b/backend-server/application/src/main/java/com/apitable/automation/mapper/AutomationTriggerMapper.java @@ -92,7 +92,14 @@ void updateTriggerInputByRobotIdsAndTriggerType(@Param("robotIds") List * @param triggerId trigger * @return AutomationTriggerEntity */ - AutomationTriggerEntity selectByTriggerId( - @Param("triggerId") String triggerId); + AutomationTriggerEntity selectByTriggerId(@Param("triggerId") String triggerId); + + /** + * query trigger. + * + * @param resourceIds resource id + * @return AutomationTriggerDto + */ + List selectRobotIdByResourceIds(@Param("resourceIds") List resourceIds); } diff --git a/backend-server/application/src/main/java/com/apitable/automation/model/ActionVO.java b/backend-server/application/src/main/java/com/apitable/automation/model/ActionVO.java index c192bb829e..c5d3b4950c 100644 --- a/backend-server/application/src/main/java/com/apitable/automation/model/ActionVO.java +++ b/backend-server/application/src/main/java/com/apitable/automation/model/ActionVO.java @@ -19,7 +19,6 @@ package com.apitable.automation.model; import com.apitable.shared.support.serializer.NullObjectSerializer; -import com.apitable.shared.support.serializer.StringToJsonObjectSerializer; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; @@ -36,6 +35,6 @@ @Setter public class ActionVO extends ActionSimpleVO { @Schema(description = "Action input", type = "java.lang.String", example = "{}") - @JsonSerialize(nullsUsing = NullObjectSerializer.class, using = StringToJsonObjectSerializer.class) - private String input; + @JsonSerialize(nullsUsing = NullObjectSerializer.class) + private Object input; } diff --git a/backend-server/application/src/main/java/com/apitable/automation/model/AutomationRunHistoryDTO.java b/backend-server/application/src/main/java/com/apitable/automation/model/AutomationRunHistoryDTO.java new file mode 100644 index 0000000000..6a3b5b0ae0 --- /dev/null +++ b/backend-server/application/src/main/java/com/apitable/automation/model/AutomationRunHistoryDTO.java @@ -0,0 +1,44 @@ +/* + * APITable + * Copyright (C) 2022 APITable Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.apitable.automation.model; + +import java.time.LocalDateTime; +import lombok.Data; + +/** + * AutomationRunHistoryDTO base info. + */ +@Data +public class AutomationRunHistoryDTO { + + private String robotId; + + private String taskId; + + private Integer status; + + private LocalDateTime createdAt; + + private String actionIds; + + private String actionTypeIds; + + private String errorMessages; + +} diff --git a/backend-server/application/src/main/java/com/apitable/automation/model/AutomationRunTaskVO.java b/backend-server/application/src/main/java/com/apitable/automation/model/AutomationRunTaskVO.java new file mode 100644 index 0000000000..67386de78c --- /dev/null +++ b/backend-server/application/src/main/java/com/apitable/automation/model/AutomationRunTaskVO.java @@ -0,0 +1,67 @@ +/* + * APITable + * Copyright (C) 2022 APITable Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.apitable.automation.model; + +import com.apitable.shared.support.serializer.NullNumberSerializer; +import com.apitable.shared.support.serializer.NullObjectSerializer; +import com.apitable.shared.support.serializer.NullStringSerializer; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import io.swagger.v3.oas.annotations.media.Schema; +import java.time.LocalDateTime; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * AutomationRunTaskVO. + */ +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +public class AutomationRunTaskVO { + @Schema(description = "id", type = "java.lang.String") + @JsonSerialize(nullsUsing = NullStringSerializer.class) + private String id; + + @Schema(description = "taskId", type = "java.lang.String") + @JsonSerialize(nullsUsing = NullStringSerializer.class) + private String taskId; + + @Schema(description = "robotId", type = "java.lang.String") + @JsonSerialize(nullsUsing = NullStringSerializer.class) + private String robotId; + + @Schema(description = "spaceId", type = "java.lang.String") + @JsonSerialize(nullsUsing = NullStringSerializer.class) + private String spaceId; + + @Schema(description = "status", type = "java.lang.Integer") + @JsonSerialize(nullsUsing = NullNumberSerializer.class) + private Integer status; + + @Schema(description = "createdAt", type = "java.lang.String", example = "{}") + @JsonSerialize(nullsUsing = NullStringSerializer.class) + private LocalDateTime createdAt; + + @Schema(description = "Action input", type = "java.lang.String", example = "{}") + @JsonSerialize(nullsUsing = NullObjectSerializer.class) + private Object data; +} diff --git a/backend-server/application/src/main/java/com/apitable/automation/service/IAutomationActionService.java b/backend-server/application/src/main/java/com/apitable/automation/service/IAutomationActionService.java index 3e252176d1..4129fafbe8 100644 --- a/backend-server/application/src/main/java/com/apitable/automation/service/IAutomationActionService.java +++ b/backend-server/application/src/main/java/com/apitable/automation/service/IAutomationActionService.java @@ -18,6 +18,7 @@ package com.apitable.automation.service; +import cn.hutool.json.JSON; import com.apitable.automation.entity.AutomationActionEntity; import com.apitable.automation.model.ActionVO; import com.apitable.automation.model.CreateActionRO; @@ -83,4 +84,12 @@ public interface IAutomationActionService { * @param userId operator user id */ void deleteByDatabus(String robotId, String actionId, Long userId); -} + + /** + * handle action input, replace password. + * + * @param input input + * @return input + */ + JSON handleActionInput(String input); +} \ No newline at end of file diff --git a/backend-server/application/src/main/java/com/apitable/automation/service/IAutomationActionTypeService.java b/backend-server/application/src/main/java/com/apitable/automation/service/IAutomationActionTypeService.java new file mode 100644 index 0000000000..a542322156 --- /dev/null +++ b/backend-server/application/src/main/java/com/apitable/automation/service/IAutomationActionTypeService.java @@ -0,0 +1,36 @@ +/* + * APITable + * Copyright (C) 2022 APITable Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + + +package com.apitable.automation.service; + +/** + * interface for action type. + */ +public interface IAutomationActionTypeService { + + String getActionTypeIdByEndpoint(String endpoint); + + /** + * weather is send email action. + * + * @param actionTypeId action type + * @return boolean + */ + boolean isSendEmailAction(String actionTypeId); +} diff --git a/backend-server/application/src/main/java/com/apitable/automation/service/IAutomationRobotService.java b/backend-server/application/src/main/java/com/apitable/automation/service/IAutomationRobotService.java index b4e728529e..4a7770984d 100644 --- a/backend-server/application/src/main/java/com/apitable/automation/service/IAutomationRobotService.java +++ b/backend-server/application/src/main/java/com/apitable/automation/service/IAutomationRobotService.java @@ -154,4 +154,12 @@ void copyByDatabus(Long userId, List resourceIds, * @param robotId robot id */ void checkRobotExists(String robotId); + + /** + * get robot count. + * + * @param nodeIds dst id + * @return count + */ + boolean linkByOutsideAutomation(List nodeIds); } diff --git a/backend-server/application/src/main/java/com/apitable/automation/service/IAutomationRunHistoryService.java b/backend-server/application/src/main/java/com/apitable/automation/service/IAutomationRunHistoryService.java index a08eb46f56..fc6773bf0f 100644 --- a/backend-server/application/src/main/java/com/apitable/automation/service/IAutomationRunHistoryService.java +++ b/backend-server/application/src/main/java/com/apitable/automation/service/IAutomationRunHistoryService.java @@ -18,15 +18,34 @@ package com.apitable.automation.service; +import com.apitable.automation.entity.AutomationRunHistoryEntity; +import com.apitable.automation.model.AutomationRunTaskVO; import com.apitable.automation.model.AutomationTaskSimpleVO; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.IService; import java.util.List; /** * Automation run history service interface. */ -public interface IAutomationRunHistoryService { +public interface IAutomationRunHistoryService extends IService { - List getRobotRunHistory(String robotId, Integer pageSize, - Integer pageNum); + /** + * query the history with page. + * + * @param spaceId space id + * @param robotId robot id + * @param page page + * @return AutomationTaskSimpleVO + */ + List getRobotRunHistory(String spaceId, String robotId, + Page page); + /** + * query the history with task id. + * + * @param taskId task id + * @return AutomationTriggerEntity + */ + AutomationRunTaskVO getByTaskDetail(String taskId); } diff --git a/backend-server/application/src/main/java/com/apitable/automation/service/IAutomationTriggerService.java b/backend-server/application/src/main/java/com/apitable/automation/service/IAutomationTriggerService.java index 868a887830..650f44fcda 100644 --- a/backend-server/application/src/main/java/com/apitable/automation/service/IAutomationTriggerService.java +++ b/backend-server/application/src/main/java/com/apitable/automation/service/IAutomationTriggerService.java @@ -117,4 +117,13 @@ TriggerCopyResultDto copy(Long userId, AutomationCopyOptions options, */ void updateInputByRobotIdsAndTriggerTypeIds(List robotIds, String triggerTypeId, String input); + + /** + * get node linked robot id list. + * + * @param nodeIds node id + * @return robot id list + */ + List getRobotIdsByResourceIds(List nodeIds); + } diff --git a/backend-server/application/src/main/java/com/apitable/automation/service/impl/AutomationActionServiceImpl.java b/backend-server/application/src/main/java/com/apitable/automation/service/impl/AutomationActionServiceImpl.java index 13624a9bcc..83d9a5845e 100644 --- a/backend-server/application/src/main/java/com/apitable/automation/service/impl/AutomationActionServiceImpl.java +++ b/backend-server/application/src/main/java/com/apitable/automation/service/impl/AutomationActionServiceImpl.java @@ -24,6 +24,11 @@ import static java.util.stream.Collectors.toList; import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Dict; +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSON; +import cn.hutool.json.JSONArray; +import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; import com.apitable.automation.entity.AutomationActionEntity; import com.apitable.automation.mapper.AutomationActionMapper; @@ -32,6 +37,7 @@ import com.apitable.automation.model.TriggerCopyResultDto; import com.apitable.automation.model.UpdateActionRO; import com.apitable.automation.service.IAutomationActionService; +import com.apitable.automation.service.IAutomationActionTypeService; import com.apitable.core.util.ExceptionUtil; import com.apitable.shared.config.properties.LimitProperties; import com.apitable.shared.util.IdUtil; @@ -57,6 +63,8 @@ @Service public class AutomationActionServiceImpl implements IAutomationActionService { + public static final String EMAIL_SHOW_PASSWORD = "******"; + @Resource private AutomationActionMapper actionMapper; @@ -66,6 +74,9 @@ public class AutomationActionServiceImpl implements IAutomationActionService { @Resource private AutomationDaoApiApi automationDaoApiApi; + @Resource + private IAutomationActionTypeService iAutomationActionTypeService; + @Override public void create(AutomationActionEntity action) { actionMapper.insert(action); @@ -140,42 +151,90 @@ public List createByDatabus(Long userId, CreateActionRO data) { return new ArrayList<>(); } + @Override - public List updateByDatabus(String actionId, Long userId, UpdateActionRO data) { + public void deleteByDatabus(String robotId, String actionId, Long userId) { AutomationRobotActionRO ro = new AutomationRobotActionRO(); ro.setUserId(userId); - ro.setInput(JSONUtil.toJsonStr(data.getInput())); - ro.setPrevActionId(data.getPrevActionId()); - ro.setActionTypeId(data.getActionTypeId()); + ro.setIsDeleted(true); ro.setActionId(actionId); try { ApiResponseAutomationActionPO response = - automationDaoApiApi.daoCreateOrUpdateAutomationRobotAction(data.getRobotId(), ro); + automationDaoApiApi.daoCreateOrUpdateAutomationRobotAction(robotId, ro); ExceptionUtil.isFalse( AUTOMATION_ROBOT_NOT_EXIST.getCode().equals(response.getCode()), AUTOMATION_ROBOT_NOT_EXIST); - return formatVoFromDatabusResponse(response.getData()); } catch (RestClientException e) { - log.error("Robot update action: {}", data.getRobotId(), e); + log.error("Delete action: {}", actionId, e); } - return new ArrayList<>(); } @Override - public void deleteByDatabus(String robotId, String actionId, Long userId) { + public JSON handleActionInput(String input) { + if (null == input) { + return null; + } + JSONObject inputObj = JSONUtil.parseObj(input); + if (inputObj.containsKey("value")) { + JSONObject inputValue = JSONUtil.parseObj(inputObj.get("value")); + if (inputValue.containsKey("operands")) { + JSONArray operands = JSONUtil.parseArray(inputValue.get("operands")); + if (operands.contains("password")) { + int passwordIndex = operands.indexOf("password"); + Dict passwordValue = + new Dict().set("value", EMAIL_SHOW_PASSWORD).set("type", "Literal"); + operands.set(passwordIndex + 1, passwordValue); + } + inputValue.set("operands", operands); + } + inputObj.set("value", inputValue); + } + return inputObj; + } + + @Override + public List updateByDatabus(String actionId, Long userId, UpdateActionRO data) { + // handle input + String input = getActionInputStringFromRo(actionId, data.getInput()); AutomationRobotActionRO ro = new AutomationRobotActionRO(); ro.setUserId(userId); - ro.setIsDeleted(true); + ro.setInput(input); + ro.setPrevActionId(data.getPrevActionId()); + ro.setActionTypeId(data.getActionTypeId()); ro.setActionId(actionId); try { ApiResponseAutomationActionPO response = - automationDaoApiApi.daoCreateOrUpdateAutomationRobotAction(robotId, ro); + automationDaoApiApi.daoCreateOrUpdateAutomationRobotAction(data.getRobotId(), ro); ExceptionUtil.isFalse( AUTOMATION_ROBOT_NOT_EXIST.getCode().equals(response.getCode()), AUTOMATION_ROBOT_NOT_EXIST); + return formatVoFromDatabusResponse(response.getData()); } catch (RestClientException e) { - log.error("Delete action: {}", actionId, e); + log.error("Robot update action: {}", data.getRobotId(), e); + } + return new ArrayList<>(); + } + + private String getActionInputStringFromRo(String actionId, Object inputObj) { + String input = JSONUtil.toJsonStr(inputObj); + if (null == input) { + return null; + } + AutomationActionEntity action = actionMapper.selectByActionId(actionId); + if (null == action) { + return null; + } + // change send email input, should check password + if (!input.equals(JSONUtil.toJsonStr(JSONUtil.createObj())) + && iAutomationActionTypeService.isSendEmailAction(action.getActionTypeId())) { + String newPassword = getSendEmailActionPassword(input); + // should set old password to new password + if (EMAIL_SHOW_PASSWORD.equals(newPassword)) { + String oldPassword = getSendEmailActionPassword(action.getInput()); + return setSendEmailActionPassword(input, oldPassword); + } } + return input; } private List formatVoFromDatabusResponse(List data) { @@ -185,11 +244,52 @@ private List formatVoFromDatabusResponse(List data vo.setActionId(i.getActionId()); vo.setActionTypeId(i.getActionTypeId()); vo.setPrevActionId(i.getPrevActionId()); - vo.setInput(i.getInput()); + vo.setInput(handleActionInput(i.getInput())); return vo; }).sorted(actionComparator).collect(toList()); } return new ArrayList<>(); } + private String getSendEmailActionPassword(String input) { + if (null == input) { + return null; + } + JSONObject inputObj = JSONUtil.parseObj(input); + if (inputObj.containsKey("value")) { + JSONObject inputValue = JSONUtil.parseObj(inputObj.get("value")); + if (inputValue.containsKey("operands")) { + JSONArray operands = JSONUtil.parseArray(inputValue.get("operands")); + if (operands.contains("password")) { + int passwordIndex = operands.indexOf("password"); + JSONObject passwordValue = JSONUtil.parseObj(operands.get(passwordIndex + 1)); + return StrUtil.toString(passwordValue.get("value")); + } + } + } + return null; + } + + private String setSendEmailActionPassword(String input, String password) { + if (null == input) { + return null; + } + JSONObject inputObj = JSONUtil.parseObj(input); + if (inputObj.containsKey("value")) { + JSONObject inputValue = JSONUtil.parseObj(inputObj.get("value")); + if (inputValue.containsKey("operands")) { + JSONArray operands = JSONUtil.parseArray(inputValue.get("operands")); + if (operands.contains("password")) { + int passwordIndex = operands.indexOf("password"); + Dict passwordValue = + new Dict().set("value", password).set("type", "Literal"); + operands.set(passwordIndex + 1, passwordValue); + } + inputValue.set("operands", operands); + } + inputObj.set("value", inputValue); + } + return JSONUtil.toJsonStr(inputObj); + } + } diff --git a/backend-server/application/src/main/java/com/apitable/automation/service/impl/AutomationActionTypeServiceImpl.java b/backend-server/application/src/main/java/com/apitable/automation/service/impl/AutomationActionTypeServiceImpl.java new file mode 100644 index 0000000000..1f84f4de87 --- /dev/null +++ b/backend-server/application/src/main/java/com/apitable/automation/service/impl/AutomationActionTypeServiceImpl.java @@ -0,0 +1,50 @@ +/* + * APITable + * Copyright (C) 2022 APITable Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.apitable.automation.service.impl; + +import cn.hutool.core.util.StrUtil; +import com.apitable.automation.enums.AutomationActionType; +import com.apitable.automation.mapper.AutomationActionTypeMapper; +import com.apitable.automation.service.IAutomationActionTypeService; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +/** + * action type service implementation. + */ +@Slf4j +@Service +public class AutomationActionTypeServiceImpl implements IAutomationActionTypeService { + + @Resource + private AutomationActionTypeMapper actionTypeMapper; + + @Override + public String getActionTypeIdByEndpoint(String endpoint) { + return actionTypeMapper.getActionTypeIdByEndpoint(endpoint); + } + + @Override + public boolean isSendEmailAction(String actionTypeId) { + String endpoint = actionTypeMapper.selectEndpointByActionTypeId(actionTypeId); + return StrUtil.equals(endpoint, AutomationActionType.SEND_MAIL.getType()); + } + +} diff --git a/backend-server/application/src/main/java/com/apitable/automation/service/impl/AutomationRobotServiceImpl.java b/backend-server/application/src/main/java/com/apitable/automation/service/impl/AutomationRobotServiceImpl.java index 752151a431..2b77133148 100644 --- a/backend-server/application/src/main/java/com/apitable/automation/service/impl/AutomationRobotServiceImpl.java +++ b/backend-server/application/src/main/java/com/apitable/automation/service/impl/AutomationRobotServiceImpl.java @@ -75,6 +75,7 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; @@ -329,7 +330,7 @@ public AutomationVO getRobotByRobotId(String robotId) { List actions = Optional.of(automation.getActions()).orElse(new ArrayList<>()).stream().map(i -> { ActionVO action = new ActionVO(); - action.setInput(i.getInput()); + action.setInput(iAutomationActionService.handleActionInput(i.getInput())); action.setActionId(i.getActionId()); action.setPrevActionId(i.getPrevActionId()); action.setActionTypeId(i.getActionTypeId()); @@ -361,6 +362,7 @@ public void checkAutomationReference(List subNodeIds, List resou List referenceResourceIds = triggers.stream() .map(AutomationTriggerDto::getResourceId) .filter(StrUtil::isNotBlank).collect(Collectors.toList()); + referenceResourceIds = iNodeService.getExistNodeIdsBySelf(referenceResourceIds); Collection subtract = CollUtil.subtract(referenceResourceIds, subNodeIds); if (CollUtil.isEmpty(subtract)) { return; @@ -450,6 +452,16 @@ public void checkRobotExists(String robotId) { AUTOMATION_ROBOT_NOT_EXIST); } + @Override + public boolean linkByOutsideAutomation(List nodeIds) { + List robotIds = iAutomationTriggerService.getRobotIdsByResourceIds(nodeIds); + if (CollUtil.isNotEmpty(robotIds)) { + List resourceIds = robotMapper.selectResourceIdsByRobotIds(robotIds); + return !new HashSet<>(nodeIds).containsAll(resourceIds); + } + return false; + } + @Override public void updateUpdaterByRobotId(String robotId, Long updatedBy) { robotMapper.updateUpdatedByRobotId(robotId, updatedBy); diff --git a/backend-server/application/src/main/java/com/apitable/automation/service/impl/AutomationRunHistoryServiceImpl.java b/backend-server/application/src/main/java/com/apitable/automation/service/impl/AutomationRunHistoryServiceImpl.java index cbfaa38bac..27bd2d4358 100644 --- a/backend-server/application/src/main/java/com/apitable/automation/service/impl/AutomationRunHistoryServiceImpl.java +++ b/backend-server/application/src/main/java/com/apitable/automation/service/impl/AutomationRunHistoryServiceImpl.java @@ -18,21 +18,29 @@ package com.apitable.automation.service.impl; +import static com.apitable.automation.service.impl.AutomationActionServiceImpl.EMAIL_SHOW_PASSWORD; + import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.date.LocalDateTimeUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSON; +import cn.hutool.json.JSONArray; +import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; +import com.apitable.automation.entity.AutomationRunHistoryEntity; +import com.apitable.automation.mapper.AutomationRunHistoryMapper; +import com.apitable.automation.model.AutomationRunHistoryDTO; +import com.apitable.automation.model.AutomationRunTaskVO; import com.apitable.automation.model.AutomationTaskSimpleVO; import com.apitable.automation.service.IAutomationRunHistoryService; import com.apitable.shared.clock.spring.ClockManager; -import com.apitable.starter.databus.client.api.AutomationDaoApiApi; -import com.apitable.starter.databus.client.model.AutomationRunHistoryPO; import com.apitable.workspace.enums.IdRulePrefixEnum; -import jakarta.annotation.Resource; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import java.time.LocalDate; import java.util.ArrayList; import java.util.List; -import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -41,69 +49,105 @@ */ @Slf4j @Service -public class AutomationRunHistoryServiceImpl implements IAutomationRunHistoryService { - - @Resource - private AutomationDaoApiApi automationDaoApiApi; +public class AutomationRunHistoryServiceImpl + extends ServiceImpl + implements IAutomationRunHistoryService { + @Override + public List getRobotRunHistory(String spaceId, String robotId, + Page page) { + LocalDate date = LocalDate.now().plusDays(1); + IPage historyIds = + baseMapper.selectIdByRobotIdAndSpaceIdAndBetweenWithPage(robotId, spaceId, + getPreviousMonthFirstDay(date), date, page); + if (historyIds.getRecords().isEmpty()) { + return new ArrayList<>(); + } + List runHistories = + baseMapper.selectByIds(historyIds.getRecords()); + return runHistories.stream().map(this::formatTaskSimpleVo).toList(); + } @Override - public List getRobotRunHistory(String robotId, Integer pageSize, - Integer pageNum) { - return getAutomationRobotRunHistoryFromDatabus(robotId, pageSize, pageNum); + public AutomationRunTaskVO getByTaskDetail(String taskId) { + AutomationRunHistoryEntity task = baseMapper.selectByTaskId(taskId); + if (ObjectUtil.isNull(task)) { + return null; + } + AutomationRunTaskVO vo = new AutomationRunTaskVO(); + vo.setTaskId(task.getTaskId()); + vo.setId(task.getId().toString()); + vo.setId(task.getRobotId()); + vo.setStatus(task.getStatus()); + vo.setSpaceId(task.getSpaceId()); + vo.setRobotId(task.getRobotId()); + vo.setData(formatExecuteData(task.getData())); + return vo; + } + + private LocalDate getPreviousMonthFirstDay(LocalDate date) { + return date.minusMonths(1).withDayOfMonth(1); } - private List getAutomationRobotRunHistoryFromDatabus(String robotId, - Integer pageSize, - Integer pageNum) { - List results = new ArrayList<>(); - try { - List tasks = - automationDaoApiApi.daoGetAutomationRunHistory(pageSize, pageNum, robotId) - .getData(); - if (null == tasks) { - return results; + private AutomationTaskSimpleVO formatTaskSimpleVo(AutomationRunHistoryDTO history) { + AutomationTaskSimpleVO vo = new AutomationTaskSimpleVO(); + vo.setRobotId(history.getRobotId()); + vo.setStatus(history.getStatus()); + vo.setTaskId(history.getTaskId()); + vo.setCreatedAt( + history.getCreatedAt().atZone(ClockManager.me().getDefaultTimeZone()).toInstant() + .toEpochMilli()); + if (StrUtil.isNotBlank(history.getActionIds())) { + List executions = new ArrayList<>(); + List actionIds = JSONUtil.parseArray(history.getActionIds()); + List actionTypeIds = JSONUtil.parseArray(history.getActionTypeIds()); + List errorMessages = JSONUtil.parseArray(history.getErrorMessages()); + for (int i = 0; i < actionIds.size(); i++) { + AutomationTaskSimpleVO.ActionExecutionVO execution = + new AutomationTaskSimpleVO.ActionExecutionVO(); + // exclude trigger + if (!StrUtil.startWith(actionIds.get(i).toString(), + IdRulePrefixEnum.AUTOMATION_TRIGGER.getIdRulePrefixEnum())) { + execution.setActionId(actionIds.get(i).toString()); + execution.setActionTypeId(actionTypeIds.get(i).toString()); + execution.setSuccess(ObjectUtil.isNull(CollUtil.get(errorMessages, i))); + executions.add(execution); + } } - tasks.forEach(task -> { - AutomationTaskSimpleVO result = new AutomationTaskSimpleVO(); - result.setRobotId(task.getRobotId()); - result.setStatus(task.getStatus()); - result.setTaskId(task.getTaskId()); - result.setCreatedAt( - LocalDateTimeUtil.parse(task.getCreatedAt()) - .atZone(ClockManager.me().getDefaultTimeZone()).toInstant().toEpochMilli()); - result.setRobotId(task.getRobotId()); - // format action execution list - if (StrUtil.isNotBlank(task.getActionIds())) { - List executions = new ArrayList<>(); - List actionIds = - new ArrayList<>(JSONUtil.parseArray(task.getActionIds())); - List actionTypeIds = - new ArrayList<>(JSONUtil.parseArray(task.getActionTypeIds())); - List> errorMessages = - new ArrayList<>( - JSONUtil.parseArray(task.getErrorStacks())).stream().map( - JSONUtil::parseArray).collect(Collectors.toList()); - for (int i = 0; i < actionIds.size(); i++) { - AutomationTaskSimpleVO.ActionExecutionVO execution = - new AutomationTaskSimpleVO.ActionExecutionVO(); - // exclude trigger - if (!ObjectUtil.contains(actionIds.get(i), - IdRulePrefixEnum.AUTOMATION_TRIGGER.getIdRulePrefixEnum())) { - execution.setActionId(actionIds.get(i).toString()); - execution.setActionTypeId(actionTypeIds.get(i).toString()); - execution.setSuccess(CollUtil.get(errorMessages, i).isEmpty()); - executions.add(execution); - } + vo.setExecutedActions(executions); + } + return vo; + } + + private JSON formatExecuteData(String dataStr) { + JSONObject data = JSONUtil.parseObj(dataStr); + // handle multi triggers + JSONArray executedNodeIds = data.getJSONArray("executedNodeIds"); + if (null != executedNodeIds) { + executedNodeIds.removeIf(i -> { + int index = executedNodeIds.indexOf(i); + return 0 != index && i.toString() + .startsWith(IdRulePrefixEnum.AUTOMATION_TRIGGER.getIdRulePrefixEnum()); + }); + data.set("executedNodeIds", executedNodeIds); + } + // handle send mail action password + JSONObject nodeByIds = data.getJSONObject("nodeByIds"); + if (null != nodeByIds) { + for (Object executedNodeId : nodeByIds.keySet()) { + if (executedNodeId.toString() + .startsWith(IdRulePrefixEnum.AUTOMATION_ACTION.getIdRulePrefixEnum())) { + JSONObject executedAction = nodeByIds.getJSONObject(executedNodeId.toString()); + JSONObject actionInput = executedAction.getJSONObject("input"); + if (null != actionInput && actionInput.containsKey("password")) { + actionInput.set("password", EMAIL_SHOW_PASSWORD); + executedAction.set("input", actionInput); + nodeByIds.set(executedNodeId.toString(), executedAction); + data.set("nodeByIds", nodeByIds); } - result.setExecutedActions(executions); } - results.add(result); - }); - return results; - } catch (Exception e) { - log.error("Get automation history error: {}", robotId, e); - return results; + } } + return data; } } diff --git a/backend-server/application/src/main/java/com/apitable/automation/service/impl/AutomationTriggerServiceImpl.java b/backend-server/application/src/main/java/com/apitable/automation/service/impl/AutomationTriggerServiceImpl.java index 46584d5414..7898a86b9c 100644 --- a/backend-server/application/src/main/java/com/apitable/automation/service/impl/AutomationTriggerServiceImpl.java +++ b/backend-server/application/src/main/java/com/apitable/automation/service/impl/AutomationTriggerServiceImpl.java @@ -266,6 +266,11 @@ public void updateInputByRobotIdsAndTriggerTypeIds(List robotIds, String triggerMapper.updateTriggerInputByRobotIdsAndTriggerType(robotIds, triggerTypeId, input); } + @Override + public List getRobotIdsByResourceIds(List nodeIds) { + return triggerMapper.selectRobotIdByResourceIds(nodeIds); + } + private List handleTriggerResponse(List data) { if (null != data) { return data.stream().map(i -> { diff --git a/backend-server/application/src/main/java/com/apitable/internal/controller/InternalNodePermissionController.java b/backend-server/application/src/main/java/com/apitable/internal/controller/InternalNodePermissionController.java index 7fa59a03f2..071cd2ca22 100644 --- a/backend-server/application/src/main/java/com/apitable/internal/controller/InternalNodePermissionController.java +++ b/backend-server/application/src/main/java/com/apitable/internal/controller/InternalNodePermissionController.java @@ -20,6 +20,7 @@ import static com.apitable.workspace.enums.PermissionException.NODE_ACCESS_DENIED; +import cn.hutool.core.util.StrUtil; import com.apitable.control.annotation.ThirdPartControl; import com.apitable.core.exception.BusinessException; import com.apitable.core.support.ResponseData; @@ -87,7 +88,8 @@ public ResponseData getNodePermission( @RequestParam(value = "shareId", required = false) String shareId) { Long userId = SessionContext.getUserId(); // check private - if (!iNodeService.getIsTemplateByNodeIds(Collections.singletonList(nodeId)) + if (StrUtil.isBlank(shareId) + && !iNodeService.getIsTemplateByNodeIds(Collections.singletonList(nodeId)) && !iNodeService.privateNodeOperation(userId, nodeId)) { throw new BusinessException(NODE_ACCESS_DENIED); } diff --git a/backend-server/application/src/main/java/com/apitable/organization/dto/UnitMemberTeamDTO.java b/backend-server/application/src/main/java/com/apitable/organization/dto/UnitMemberTeamDTO.java index 5c42655e58..10aced4b8f 100644 --- a/backend-server/application/src/main/java/com/apitable/organization/dto/UnitMemberTeamDTO.java +++ b/backend-server/application/src/main/java/com/apitable/organization/dto/UnitMemberTeamDTO.java @@ -28,5 +28,5 @@ public class UnitMemberTeamDTO { private String teamName; - private Boolean isDeleted; + private boolean isDeleted; } diff --git a/backend-server/application/src/main/java/com/apitable/organization/service/impl/MemberServiceImpl.java b/backend-server/application/src/main/java/com/apitable/organization/service/impl/MemberServiceImpl.java index 72fd905053..2edbc8720f 100644 --- a/backend-server/application/src/main/java/com/apitable/organization/service/impl/MemberServiceImpl.java +++ b/backend-server/application/src/main/java/com/apitable/organization/service/impl/MemberServiceImpl.java @@ -1535,17 +1535,23 @@ public List getMemberBySpaceIdAndUserIds(String spaceId, Map memberUnitMap = iUnitService.getUnitBaseInfoByRefIds(memberIds).stream().collect( Collectors.toMap(UnitBaseInfoDTO::getUnitRefId, UnitBaseInfoDTO::getId)); - for (MemberDTO member : members) { + Map userMemberMap = + members.stream().collect(Collectors.toMap(MemberDTO::getUserId, i -> i)); + // maybe member doesn't bind any user. + for (Long userId : userIds) { UnitMemberTeamDTO unitMember = new UnitMemberTeamDTO(); - unitMember.setOpenId(member.getOpenId()); - unitMember.setIsDeleted(member.getIsDeleted()); - unitMember.setMemberId(member.getId()); - unitMember.setMemberName(member.getMemberName()); - unitMember.setUnitId(memberUnitMap.get(member.getId())); - String teamName = StrUtil.join(" & ", memberTeamMap.get(member.getId())); - unitMember.setTeamName(teamName); - UserEntity user = users.get(member.getUserId()); - unitMember.setUserId(member.getUserId()); + MemberDTO member = userMemberMap.get(userId); + if (null != member) { + unitMember.setOpenId(member.getOpenId()); + unitMember.setDeleted(member.getIsDeleted()); + unitMember.setMemberId(member.getId()); + unitMember.setMemberName(member.getMemberName()); + unitMember.setUnitId(memberUnitMap.get(member.getId())); + String teamName = StrUtil.join(" & ", memberTeamMap.get(member.getId())); + unitMember.setTeamName(teamName); + } + UserEntity user = users.get(userId); + unitMember.setUserId(userId); unitMember.setAvatar(user.getAvatar()); unitMember.setAvatarColor(user.getColor()); unitMembers.add(unitMember); diff --git a/backend-server/application/src/main/java/com/apitable/organization/service/impl/RoleServiceImpl.java b/backend-server/application/src/main/java/com/apitable/organization/service/impl/RoleServiceImpl.java index 663ccf8e8a..2732bf0e9d 100644 --- a/backend-server/application/src/main/java/com/apitable/organization/service/impl/RoleServiceImpl.java +++ b/backend-server/application/src/main/java/com/apitable/organization/service/impl/RoleServiceImpl.java @@ -286,7 +286,7 @@ public void checkRoleExistBySpaceId(String spaceId, Consumer consumer) @Override public List getRoleIdsByUnitIds(String spaceId, List unitIds) { - if (unitIds.isEmpty()) { + if (CollUtil.isEmpty(unitIds)) { return new ArrayList<>(); } return iUnitService.getUnitBaseInfoBySpaceIdAndUnitTypeAndUnitIds(spaceId, UnitType.ROLE, diff --git a/backend-server/application/src/main/java/com/apitable/space/mapper/StaticsMapper.java b/backend-server/application/src/main/java/com/apitable/space/mapper/StaticsMapper.java index 856c38ec4c..fbdbcd80b5 100644 --- a/backend-server/application/src/main/java/com/apitable/space/mapper/StaticsMapper.java +++ b/backend-server/application/src/main/java/com/apitable/space/mapper/StaticsMapper.java @@ -70,7 +70,7 @@ public interface StaticsMapper { * @param spaceId space id * @return total */ - Long countRecordsBySpaceId(@Param("spaceId") String spaceId); + List countRecordsBySpaceId(@Param("spaceId") String spaceId); /** * Count the rows of all tables in the space. diff --git a/backend-server/application/src/main/java/com/apitable/space/service/ISpaceService.java b/backend-server/application/src/main/java/com/apitable/space/service/ISpaceService.java index 0e3a96b372..365158dfe3 100644 --- a/backend-server/application/src/main/java/com/apitable/space/service/ISpaceService.java +++ b/backend-server/application/src/main/java/com/apitable/space/service/ISpaceService.java @@ -492,4 +492,12 @@ boolean checkSeatOverLimitAndSendNotify(List userIds, String spaceId, long */ String getSocialSuiteKeyByAppId(String appId); + /** + * check whether the space bind with a social connector. + * + * @param spaceId space id + * @return boolean + */ + boolean checkSocialBind(String spaceId); + } diff --git a/backend-server/application/src/main/java/com/apitable/space/service/impl/SpaceInviteLinkServiceImpl.java b/backend-server/application/src/main/java/com/apitable/space/service/impl/SpaceInviteLinkServiceImpl.java index 0defbc1168..4f7e44a73a 100644 --- a/backend-server/application/src/main/java/com/apitable/space/service/impl/SpaceInviteLinkServiceImpl.java +++ b/backend-server/application/src/main/java/com/apitable/space/service/impl/SpaceInviteLinkServiceImpl.java @@ -32,7 +32,6 @@ import com.apitable.core.exception.BusinessException; import com.apitable.core.util.ExceptionUtil; import com.apitable.core.util.HttpContextUtil; -import com.apitable.interfaces.social.facade.SocialServiceFacade; import com.apitable.interfaces.user.facade.UserServiceFacade; import com.apitable.organization.mapper.TeamMemberRelMapper; import com.apitable.organization.service.IMemberService; @@ -104,9 +103,6 @@ public class SpaceInviteLinkServiceImpl @Resource private RedisLockRegistry redisLockRegistry; - @Resource - private SocialServiceFacade socialServiceFacade; - @Resource private IInvitationService invitationService; @@ -118,7 +114,7 @@ public List getSpaceLinkVos(Long memberId) { @Override public String saveOrUpdate(String spaceId, Long teamId, Long memberId) { // whether a space can create an invitation link - boolean isBindSocial = socialServiceFacade.checkSocialBind(spaceId); + boolean isBindSocial = iSpaceService.checkSocialBind(spaceId); ExceptionUtil.isFalse(isBindSocial, NO_ALLOW_OPERATE); String teamSpaceId = iTeamService.getSpaceIdByTeamId(teamId); // Verify that the department exists and is in the same space @@ -231,7 +227,7 @@ public InvitationUserDTO invitedUserJoinSpaceByToken(Long userId, String token) INVITE_EXPIRE); iSpaceService.checkSeatOverLimit(dto.getSpaceId()); // Determine whether the space has a third party enabled - boolean isBoundSocial = socialServiceFacade.checkSocialBind(dto.getSpaceId()); + boolean isBoundSocial = iSpaceService.checkSocialBind(dto.getSpaceId()); ExceptionUtil.isFalse(isBoundSocial, INVITE_EXPIRE); // If the user has historical members in the space, the previous member ID can be reused directly; when the user is in the space but not in the designated department, he/she joins the department boolean isExist = this.joinTeamIfInSpace(userId, dto.getSpaceId(), dto.getTeamId()); diff --git a/backend-server/application/src/main/java/com/apitable/space/service/impl/SpaceServiceImpl.java b/backend-server/application/src/main/java/com/apitable/space/service/impl/SpaceServiceImpl.java index b0789cbd6c..d7b82200d1 100644 --- a/backend-server/application/src/main/java/com/apitable/space/service/impl/SpaceServiceImpl.java +++ b/backend-server/application/src/main/java/com/apitable/space/service/impl/SpaceServiceImpl.java @@ -1280,4 +1280,9 @@ public SocialConnectInfo getSocialConnectInfo(String spaceId) { public String getSocialSuiteKeyByAppId(String appId) { return socialServiceFacade.getSuiteKeyByDingtalkSuiteId(appId); } + + @Override + public boolean checkSocialBind(String spaceId) { + return socialServiceFacade.checkSocialBind(spaceId); + } } diff --git a/backend-server/application/src/main/java/com/apitable/space/service/impl/StaticsServiceImpl.java b/backend-server/application/src/main/java/com/apitable/space/service/impl/StaticsServiceImpl.java index 16ab01212e..367363b4d7 100644 --- a/backend-server/application/src/main/java/com/apitable/space/service/impl/StaticsServiceImpl.java +++ b/backend-server/application/src/main/java/com/apitable/space/service/impl/StaticsServiceImpl.java @@ -50,7 +50,6 @@ import com.apitable.workspace.dto.NodeStatisticsDTO; import com.apitable.workspace.enums.NodeType; import com.apitable.workspace.enums.ViewType; -import com.apitable.workspace.mapper.DatasheetMapper; import com.apitable.workspace.mapper.NodeMapper; import com.apitable.workspace.service.INodeService; import com.apitable.workspace.vo.NodeStatisticsVo; @@ -92,9 +91,6 @@ public class StaticsServiceImpl implements IStaticsService { @Resource private StaticsMapper staticsMapper; - @Resource - private DatasheetMapper datasheetMapper; - @Resource private RedisTemplate redisTemplate; @@ -265,14 +261,13 @@ public long getDatasheetRecordTotalCountBySpaceId(String spaceId) { if (null != recordCount) { return recordCount; } - List dstIds = datasheetMapper.selectDstIdBySpaceId(spaceId); - if (CollUtil.isEmpty(dstIds)) { + List recordIds = staticsMapper.countRecordsBySpaceId(spaceId); + if (CollUtil.isEmpty(recordIds)) { return 0L; } recordCount = 0L; - List> dstIdList = CollUtil.split(dstIds, 1000); - for (List item : dstIdList) { - recordCount += SqlTool.retCount(staticsMapper.countRecordsByDstIds(item)); + for (String item : recordIds) { + recordCount += JSONUtil.parseArray(item).size(); } // save in cache redisTemplate.opsForValue() @@ -474,7 +469,8 @@ public PageInfo getNodeStatistics(String spaceId, Page p return PageHelper.build(nodes.getCurrent(), nodes.getSize(), nodes.getTotal(), records); } List userIds = nodes.getRecords().stream().map( - NodeStatisticsDTO::getCreatedBy).filter(userId -> !userId.equals(0L)).toList(); + NodeStatisticsDTO::getCreatedBy).filter(userId -> !userId.equals(0L)).distinct() + .toList(); List members = iMemberService.getMemberBySpaceIdAndUserIds(spaceId, userIds); Map memberMap = members.stream() @@ -486,7 +482,7 @@ public PageInfo getNodeStatistics(String spaceId, Page p for (NodeStatisticsDTO node : nodes.getRecords()) { NodeStatisticsVo vo = new NodeStatisticsVo(); UnitMemberTeamDTO member = memberMap.get(node.getCreatedBy()); - vo.setMemberId(member.getMemberId().toString()); + vo.setMemberId(StrUtil.toStringOrNull(member.getMemberId())); vo.setMemberName(member.getMemberName()); vo.setAvatar(member.getAvatar()); vo.setAvatarColor(member.getAvatarColor()); diff --git a/backend-server/application/src/main/java/com/apitable/template/service/impl/TemplateServiceImpl.java b/backend-server/application/src/main/java/com/apitable/template/service/impl/TemplateServiceImpl.java index 216ed93aab..64e061b4aa 100644 --- a/backend-server/application/src/main/java/com/apitable/template/service/impl/TemplateServiceImpl.java +++ b/backend-server/application/src/main/java/com/apitable/template/service/impl/TemplateServiceImpl.java @@ -213,6 +213,8 @@ public void checkTemplateForeignNode(final Long memberId, iAutomationRobotService.checkAutomationReference(singletonNodeIds, singletonNodeIds); break; + case CUSTOM_PAGE: + break; default: throw new BusinessException(NOT_ALLOW); } diff --git a/backend-server/application/src/main/java/com/apitable/widget/mapper/WidgetMapper.java b/backend-server/application/src/main/java/com/apitable/widget/mapper/WidgetMapper.java index e48d06b5c9..90468d2d58 100644 --- a/backend-server/application/src/main/java/com/apitable/widget/mapper/WidgetMapper.java +++ b/backend-server/application/src/main/java/com/apitable/widget/mapper/WidgetMapper.java @@ -133,4 +133,12 @@ List selectInfoBySpaceIdAndNodeType(@Param("spaceId") String spaceId * @return count */ Long selectCountBySpaceId(@Param("spaceId") String spaceId); + + /** + * select node id by widget ids. + * + * @param widgetIds widget id list + * @return list of node ids + */ + List selectNodeIdsByWidgetIds(@Param("widgetIds") List widgetIds); } diff --git a/backend-server/application/src/main/java/com/apitable/widget/service/IWidgetService.java b/backend-server/application/src/main/java/com/apitable/widget/service/IWidgetService.java index df39494bfb..e1943293f9 100644 --- a/backend-server/application/src/main/java/com/apitable/widget/service/IWidgetService.java +++ b/backend-server/application/src/main/java/com/apitable/widget/service/IWidgetService.java @@ -137,4 +137,20 @@ void copyBatch(Long userId, String destSpaceId, Map newNodeMap, * @param widgetIds widget id list */ void checkWidgetReference(List subNodeIds, List widgetIds); + + /** + * get widget resources. + * + * @param widgetIds widget id list + * @return node ids + */ + List getWidgetNodeIds(List widgetIds); + + /** + * get node widgets. + * + * @param nodeIds node id. + * @return list of widget id + */ + List getNodeWidgetIds(List nodeIds); } diff --git a/backend-server/application/src/main/java/com/apitable/widget/service/impl/WidgetServiceImpl.java b/backend-server/application/src/main/java/com/apitable/widget/service/impl/WidgetServiceImpl.java index f339aac1f3..ee9e7c1214 100644 --- a/backend-server/application/src/main/java/com/apitable/widget/service/impl/WidgetServiceImpl.java +++ b/backend-server/application/src/main/java/com/apitable/widget/service/impl/WidgetServiceImpl.java @@ -484,4 +484,14 @@ public void checkWidgetReference(List subNodeIds, List widgetIds } } } + + @Override + public List getWidgetNodeIds(List widgetIds) { + return widgetMapper.selectNodeIdsByWidgetIds(widgetIds); + } + + @Override + public List getNodeWidgetIds(List nodeIds) { + return datasheetWidgetMapper.selectWidgetIdByDstIds(nodeIds); + } } diff --git a/backend-server/application/src/main/java/com/apitable/workspace/controller/NodeController.java b/backend-server/application/src/main/java/com/apitable/workspace/controller/NodeController.java index 4e41a5e23f..7bae5c3908 100644 --- a/backend-server/application/src/main/java/com/apitable/workspace/controller/NodeController.java +++ b/backend-server/application/src/main/java/com/apitable/workspace/controller/NodeController.java @@ -19,6 +19,7 @@ package com.apitable.workspace.controller; import static com.apitable.workspace.enums.NodeException.DUPLICATE_NODE_NAME; +import static com.apitable.workspace.enums.PermissionException.NODE_ACCESS_DENIED; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.ListUtil; @@ -491,6 +492,8 @@ public ResponseData position(@PathVariable("nodeId") String node // check node permissions controlTemplate.checkNodePermission(memberId, nodeId, NodePermission.READ_NODE, status -> ExceptionUtil.isTrue(status, PermissionException.NODE_OPERATION_DENIED)); + Long userId = SessionContext.getUserId(); + ExceptionUtil.isTrue(iNodeService.privateNodeOperation(userId, nodeId), NODE_ACCESS_DENIED); NodeInfoTreeVo treeVo = iNodeService.position(spaceId, memberId, nodeId); return ResponseData.success(treeVo); } @@ -662,6 +665,7 @@ public ResponseData> move(@RequestBody @Valid NodeMoveOpRo node Long userId = SessionContext.getUserId(); // if node is private check foreign link if (iNodeService.nodePrivate(nodeOpRo.getNodeId()) && null == nodeOpRo.getUnitId()) { + iNodeService.linkByOutsideResource(nodeOpRo.getNodeId()); iTemplateService.checkTemplateForeignNode(memberId, nodeOpRo.getNodeId()); } List nodeIds = iNodeService.move(userId, nodeOpRo); diff --git a/backend-server/application/src/main/java/com/apitable/workspace/mapper/DatasheetWidgetMapper.java b/backend-server/application/src/main/java/com/apitable/workspace/mapper/DatasheetWidgetMapper.java index 4d94dd3dc5..a6370c3989 100644 --- a/backend-server/application/src/main/java/com/apitable/workspace/mapper/DatasheetWidgetMapper.java +++ b/backend-server/application/src/main/java/com/apitable/workspace/mapper/DatasheetWidgetMapper.java @@ -61,4 +61,12 @@ public interface DatasheetWidgetMapper extends BaseMapper * @return affected rows */ int insertBatch(@Param("entities") List entities); + + /** + * query by dst id. + * + * @param dstIds dst id + * @return list of widget id + */ + List selectWidgetIdByDstIds(@Param("dstIds") List dstIds); } diff --git a/backend-server/application/src/main/java/com/apitable/workspace/mapper/NodeMapper.java b/backend-server/application/src/main/java/com/apitable/workspace/mapper/NodeMapper.java index 00a718e93b..e9ad1d3cf6 100644 --- a/backend-server/application/src/main/java/com/apitable/workspace/mapper/NodeMapper.java +++ b/backend-server/application/src/main/java/com/apitable/workspace/mapper/NodeMapper.java @@ -332,12 +332,10 @@ List selectNameList(@Param("parentId") String parentId, * @param parentId parent node id * @param preNodeId pre node id * @param name node name - * @param unitId unit id * @return affected rows */ int updateInfoByNodeId(@Param("nodeId") String nodeId, @Param("parentId") String parentId, - @Param("preNodeId") String preNodeId, @Param("name") String name, - @Param("unitId") Long unitId); + @Param("preNodeId") String preNodeId, @Param("name") String name); /** * (working directory delete node/rubbish recovery node). @@ -682,4 +680,13 @@ IPage selectCountBySpaceIdWithPage(@Param("spaceId") String s */ Long selectUnitIdByNodeId(@Param("nodeId") String nodeId); + /** + * update unit id. + * + * @param nodeIds node id + * @param unitId unit id + * @return rows + */ + int updateUnitIdByNodeIds(@Param("nodeIds") List nodeIds, @Param("unitId") Long unitId); + } diff --git a/backend-server/application/src/main/java/com/apitable/workspace/mapper/NodeRelMapper.java b/backend-server/application/src/main/java/com/apitable/workspace/mapper/NodeRelMapper.java index fa0f52ffd8..687b0a07e9 100644 --- a/backend-server/application/src/main/java/com/apitable/workspace/mapper/NodeRelMapper.java +++ b/backend-server/application/src/main/java/com/apitable/workspace/mapper/NodeRelMapper.java @@ -69,4 +69,12 @@ public interface NodeRelMapper extends BaseMapper { * @return affected rows */ int insertBatch(@Param("entities") List entities); + + /** + * get count by main node id. + * + * @param mainNodeIds main node id + * @return total amount + */ + List selectRelNodeIdsByMainNodeIds(@Param("mainNodeIds") List mainNodeIds); } diff --git a/backend-server/application/src/main/java/com/apitable/workspace/service/INodeRelService.java b/backend-server/application/src/main/java/com/apitable/workspace/service/INodeRelService.java index 7606ae0815..4a5839c081 100644 --- a/backend-server/application/src/main/java/com/apitable/workspace/service/INodeRelService.java +++ b/backend-server/application/src/main/java/com/apitable/workspace/service/INodeRelService.java @@ -84,4 +84,12 @@ List getRelationNodeInfoByNodeId(String nodeId, String viewId, Long me * @return NodeRelEntity */ NodeRelEntity getByRelNodeId(String relNodeId); + + /** + * check the rel. + * + * @param mainNodeIds main node id + * @return boolean + */ + boolean relInTheSameFolder(List mainNodeIds); } diff --git a/backend-server/application/src/main/java/com/apitable/workspace/service/INodeService.java b/backend-server/application/src/main/java/com/apitable/workspace/service/INodeService.java index 1d9d6040a1..34d6014411 100644 --- a/backend-server/application/src/main/java/com/apitable/workspace/service/INodeService.java +++ b/backend-server/application/src/main/java/com/apitable/workspace/service/INodeService.java @@ -726,4 +726,19 @@ void batchCreateDataSheet(NodeData data, List nodeEntities, * @return boolean */ boolean privateNodeOperation(Long userId, String nodeId); + + /** + * whether the node linked by outside widget. + * + * @param nodeIds node id node + * @return boolean + */ + boolean linkByOutsideWidgets(List nodeIds); + + /** + * whether the node linked by outside resource. + * + * @param nodeId node id node + */ + void linkByOutsideResource(String nodeId); } diff --git a/backend-server/application/src/main/java/com/apitable/workspace/service/impl/NodeRelServiceImpl.java b/backend-server/application/src/main/java/com/apitable/workspace/service/impl/NodeRelServiceImpl.java index 1e54608b7c..948183a2c7 100644 --- a/backend-server/application/src/main/java/com/apitable/workspace/service/impl/NodeRelServiceImpl.java +++ b/backend-server/application/src/main/java/com/apitable/workspace/service/impl/NodeRelServiceImpl.java @@ -26,6 +26,7 @@ import com.apitable.core.util.ExceptionUtil; import com.apitable.workspace.dto.NodeRelDTO; import com.apitable.workspace.entity.NodeRelEntity; +import com.apitable.workspace.enums.IdRulePrefixEnum; import com.apitable.workspace.mapper.NodeMapper; import com.apitable.workspace.mapper.NodeRelMapper; import com.apitable.workspace.service.INodeRelService; @@ -36,6 +37,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; @@ -156,4 +158,19 @@ public NodeRelEntity getByRelNodeId(String relNodeId) { log.info("Gets the node association relationship with the associated node [{}]", relNodeId); return nodeRelMapper.selectByRelNodeId(relNodeId); } + + @Override + public boolean relInTheSameFolder(List nodeIds) { + List mainNodeIds = + nodeIds.stream().filter(i -> i.startsWith(IdRulePrefixEnum.DST.getIdRulePrefixEnum())) + .toList(); + if (!mainNodeIds.isEmpty()) { + List relNodeIds = nodeRelMapper.selectRelNodeIdsByMainNodeIds(mainNodeIds); + if (!relNodeIds.isEmpty()) { + relNodeIds = nodeMapper.selectNodeIdByNodeIdIn(relNodeIds); + return new HashSet<>(nodeIds).containsAll(relNodeIds); + } + } + return true; + } } diff --git a/backend-server/application/src/main/java/com/apitable/workspace/service/impl/NodeRubbishServiceImpl.java b/backend-server/application/src/main/java/com/apitable/workspace/service/impl/NodeRubbishServiceImpl.java index 0af1a3b600..aa96e5a9f8 100644 --- a/backend-server/application/src/main/java/com/apitable/workspace/service/impl/NodeRubbishServiceImpl.java +++ b/backend-server/application/src/main/java/com/apitable/workspace/service/impl/NodeRubbishServiceImpl.java @@ -196,7 +196,7 @@ public void recoverRubbishNode(Long userId, String nodeId, String parentId) { null, null); // modify the information of the recovery node boolean flag = - SqlHelper.retBool(nodeMapper.updateInfoByNodeId(nodeId, parentId, null, name, null)); + SqlHelper.retBool(nodeMapper.updateInfoByNodeId(nodeId, parentId, null, name)); ExceptionUtil.isTrue(flag, DatabaseException.EDIT_ERROR); } diff --git a/backend-server/application/src/main/java/com/apitable/workspace/service/impl/NodeServiceImpl.java b/backend-server/application/src/main/java/com/apitable/workspace/service/impl/NodeServiceImpl.java index 5bff52b645..def9102693 100644 --- a/backend-server/application/src/main/java/com/apitable/workspace/service/impl/NodeServiceImpl.java +++ b/backend-server/application/src/main/java/com/apitable/workspace/service/impl/NodeServiceImpl.java @@ -20,6 +20,7 @@ import static com.apitable.core.constants.RedisConstants.getTemplateQuoteKey; import static com.apitable.shared.constants.AssetsPublicConstants.SPACE_PREFIX; +import static com.apitable.template.enums.TemplateException.NODE_LINK_FOREIGN_NODE; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollUtil; @@ -1035,8 +1036,12 @@ public List move(Long userId, NodeMoveOpRo opRo) { } // Update the information of this node (the ID of the previous // node may be updated to null, so update By id is not used) - baseMapper.updateInfoByNodeId(nodeEntity.getNodeId(), parentId, preNodeId, name, - NumberUtil.parseLong(opRo.getUnitId())); + baseMapper.updateInfoByNodeId(nodeEntity.getNodeId(), parentId, preNodeId, name); + List subNodeIds = getNodeIdsInNodeTree(nodeEntity.getNodeId(), -1); + if (!subNodeIds.isEmpty()) { + baseMapper.updateUnitIdByNodeIds(subNodeIds, + NumberUtil.parseLong(opRo.getUnitId())); + } } else { throw new BusinessException("Frequent operations"); } @@ -1990,11 +1995,17 @@ public Optional findSameNameInSameLevel(String parentNodeId, @Override public void deleteMembersNodes(List unitIds) { + if (unitIds.isEmpty()) { + return; + } baseMapper.updateIsDeletedByUnitIds(unitIds, true); } @Override public void restoreMembersNodes(List unitIds) { + if (unitIds.isEmpty()) { + return; + } baseMapper.updateIsDeletedByUnitIds(unitIds, false); } @@ -2032,6 +2043,40 @@ public boolean privateNodeOperation(Long userId, String nodeId) { return nodeUnit.equals(unitId); } + @Override + public boolean linkByOutsideWidgets(List nodeIds) { + List widgetIds = iWidgetService.getNodeWidgetIds(nodeIds); + if (!widgetIds.isEmpty()) { + List resourceIds = iWidgetService.getWidgetNodeIds(widgetIds); + if (!resourceIds.isEmpty()) { + resourceIds = getExistNodeIdsBySelf(resourceIds); + return !new HashSet<>(nodeIds).containsAll(resourceIds); + } + } + return false; + } + + @Override + public void linkByOutsideResource(String nodeId) { + List nodeIds = new ArrayList<>(); + if (nodeId.startsWith(IdRulePrefixEnum.FOD.getIdRulePrefixEnum())) { + nodeIds = getNodeIdsInNodeTree(nodeId, -1); + } + if (nodeId.startsWith(IdRulePrefixEnum.DST.getIdRulePrefixEnum())) { + nodeIds.add(nodeId); + } + if (nodeIds.isEmpty()) { + return; + } + // check mirror + ExceptionUtil.isTrue(iNodeRelService.relInTheSameFolder(nodeIds), NODE_LINK_FOREIGN_NODE); + // check widgets + ExceptionUtil.isFalse(linkByOutsideWidgets(nodeIds), NODE_LINK_FOREIGN_NODE); + // check automation + ExceptionUtil.isFalse(iAutomationRobotService.linkByOutsideAutomation(nodeIds), + NODE_LINK_FOREIGN_NODE); + } + private List formatNodeSearchResults(List nodeInfoList) { if (CollUtil.isEmpty(nodeInfoList)) { return new ArrayList<>(); diff --git a/backend-server/application/src/main/java/com/apitable/workspace/vo/NodeStatisticsVo.java b/backend-server/application/src/main/java/com/apitable/workspace/vo/NodeStatisticsVo.java index cef14c53ce..5eb91096d2 100644 --- a/backend-server/application/src/main/java/com/apitable/workspace/vo/NodeStatisticsVo.java +++ b/backend-server/application/src/main/java/com/apitable/workspace/vo/NodeStatisticsVo.java @@ -18,6 +18,9 @@ package com.apitable.workspace.vo; +import com.apitable.shared.support.serializer.NullNumberSerializer; +import com.apitable.shared.support.serializer.NullStringSerializer; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; @@ -29,26 +32,34 @@ public class NodeStatisticsVo { @Schema(description = "Member name") + @JsonSerialize(nullsUsing = NullStringSerializer.class) private String memberName; @Schema(description = "Member id") + @JsonSerialize(nullsUsing = NullStringSerializer.class) private String memberId; @Schema(description = "avatar") + @JsonSerialize(nullsUsing = NullStringSerializer.class) private String avatar; @Schema(description = "avatar color, used for empty avatar") + @JsonSerialize(nullsUsing = NullStringSerializer.class) private Integer avatarColor; @Schema(description = "team name, contact with & ") + @JsonSerialize(nullsUsing = NullStringSerializer.class) private String teamName; @Schema(description = "user's total Node counts") + @JsonSerialize(nullsUsing = NullNumberSerializer.class) private Integer totalNodeCount; @Schema(description = "user's private node counts") + @JsonSerialize(nullsUsing = NullNumberSerializer.class) private Integer privateNodeCount; @Schema(description = "team node counts, teamNodeCount = totalNodeCount - privateNodeCount") + @JsonSerialize(nullsUsing = NullNumberSerializer.class) private Integer teamNodeCount; } diff --git a/backend-server/application/src/main/resources/mapper/automation/AutomationActionMapper.xml b/backend-server/application/src/main/resources/mapper/automation/AutomationActionMapper.xml index c728ec3771..77434005d8 100644 --- a/backend-server/application/src/main/resources/mapper/automation/AutomationActionMapper.xml +++ b/backend-server/application/src/main/resources/mapper/automation/AutomationActionMapper.xml @@ -52,4 +52,10 @@ `input` = #{updatedInput} WHERE robot_id = #{robotId} + + diff --git a/backend-server/application/src/main/resources/mapper/automation/AutomationActionTypeMapper.xml b/backend-server/application/src/main/resources/mapper/automation/AutomationActionTypeMapper.xml new file mode 100644 index 0000000000..61c67ae330 --- /dev/null +++ b/backend-server/application/src/main/resources/mapper/automation/AutomationActionTypeMapper.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + diff --git a/backend-server/application/src/main/resources/mapper/automation/AutomationRobotMapper.xml b/backend-server/application/src/main/resources/mapper/automation/AutomationRobotMapper.xml index ed1232213f..01f3839f6d 100644 --- a/backend-server/application/src/main/resources/mapper/automation/AutomationRobotMapper.xml +++ b/backend-server/application/src/main/resources/mapper/automation/AutomationRobotMapper.xml @@ -118,4 +118,14 @@ WHERE `robot_id` = #{robotId} AND is_deleted = 0 + + diff --git a/backend-server/application/src/main/resources/mapper/automation/AutomationRunHistoryMapper.xml b/backend-server/application/src/main/resources/mapper/automation/AutomationRunHistoryMapper.xml new file mode 100644 index 0000000000..64a2d33643 --- /dev/null +++ b/backend-server/application/src/main/resources/mapper/automation/AutomationRunHistoryMapper.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + diff --git a/backend-server/application/src/main/resources/mapper/automation/AutomationTriggerMapper.xml b/backend-server/application/src/main/resources/mapper/automation/AutomationTriggerMapper.xml index 93e5cbb885..0d707830aa 100644 --- a/backend-server/application/src/main/resources/mapper/automation/AutomationTriggerMapper.xml +++ b/backend-server/application/src/main/resources/mapper/automation/AutomationTriggerMapper.xml @@ -100,4 +100,14 @@ WHERE trigger_id = #{triggerId} AND is_deleted = 0 + + diff --git a/backend-server/application/src/main/resources/mapper/organization/MemberMapper.xml b/backend-server/application/src/main/resources/mapper/organization/MemberMapper.xml index 954544dbcd..664f0c3cf2 100644 --- a/backend-server/application/src/main/resources/mapper/organization/MemberMapper.xml +++ b/backend-server/application/src/main/resources/mapper/organization/MemberMapper.xml @@ -773,6 +773,7 @@ #{item} GROUP BY user_id + ORDER BY created_at DESC - + SELECT meta_data -> '$.views[0].rows[*].recordId' + FROM ${tablePrefix}datasheet_meta vdm + WHERE exists(SELECT 1 FROM ${tablePrefix}datasheet vd WHERE vd.dst_id = vdm.dst_id AND vd.space_id = #{spaceId} + AND vd.is_deleted = 0) + + diff --git a/backend-server/application/src/main/resources/mapper/workspace/DatasheetWidgetMapper.xml b/backend-server/application/src/main/resources/mapper/workspace/DatasheetWidgetMapper.xml index 4724d7e824..aade304ca4 100644 --- a/backend-server/application/src/main/resources/mapper/workspace/DatasheetWidgetMapper.xml +++ b/backend-server/application/src/main/resources/mapper/workspace/DatasheetWidgetMapper.xml @@ -50,4 +50,13 @@ + + diff --git a/backend-server/application/src/main/resources/mapper/workspace/NodeMapper.xml b/backend-server/application/src/main/resources/mapper/workspace/NodeMapper.xml index 6e05f605ac..730856fd05 100644 --- a/backend-server/application/src/main/resources/mapper/workspace/NodeMapper.xml +++ b/backend-server/application/src/main/resources/mapper/workspace/NodeMapper.xml @@ -222,6 +222,7 @@ #{item} AND is_rubbish = 0 + AND is_deleted = 0 @@ -732,4 +732,15 @@ FROM ${tablePrefix}node WHERE node_id = #{nodeId} + + + UPDATE ${tablePrefix}node + SET unit_id = #{unitId} + WHERE node_id IN + + #{unitId} + + AND is_rubbish = 0 + AND is_deleted = 0 + diff --git a/backend-server/application/src/main/resources/mapper/workspace/NodeRelMapper.xml b/backend-server/application/src/main/resources/mapper/workspace/NodeRelMapper.xml index a8c8c3d898..9c5539f00b 100644 --- a/backend-server/application/src/main/resources/mapper/workspace/NodeRelMapper.xml +++ b/backend-server/application/src/main/resources/mapper/workspace/NodeRelMapper.xml @@ -61,4 +61,13 @@ + + diff --git a/backend-server/application/src/main/resources/sysconfig/strings.json b/backend-server/application/src/main/resources/sysconfig/strings.json index 6423d7f5f5..d131b1994f 100644 --- a/backend-server/application/src/main/resources/sysconfig/strings.json +++ b/backend-server/application/src/main/resources/sysconfig/strings.json @@ -1354,7 +1354,7 @@ "create_mirror_guide_content": "Die Spiegelfunktion bietet die Möglichkeit, bestimmte Daten auszublenden. Sie können in der ursprünglichen Datenblattansicht „Filterbedingungen“ und „ausgeblendete Felder“ festlegen, um zu steuern, welche Datensätze und Felder im Spiegel angezeigt werden.\n
\n
\nIn Verbindung mit der Funktion „Ansichtssperre“ kann sie andere daran hindern, Änderungen vorzunehmen.\n
\n
\nDarüber hinaus können Sie zu „Originaltabelle > Ausgeblendete Felder“ gehen, um die Konfiguration für „Alle Felder in Spiegeln anzeigen“ zu ändern.", "create_mirror_guide_title": "Spiegel blendet einige Datensätze und Felder aus", "create_new_button_field": "Erstellen Sie ein neues Schaltflächenspaltenfeld", - "create_private_node_tip": "Personal or temporary draft documents can be created here, ${link}", + "create_private_node_tip": "Personal or temporary draft documents can be created here ${link}", "create_public_invitation_link": "Öffentliche Einladungslinks erstellen", "create_space_sub_title": "Hallo, bitte geben Sie Ihrem Space einen Namen~", "create_team_fail": "Team erstellen fehlgeschlagen", @@ -1497,6 +1497,7 @@ "del_invitation_link": "Einladungslink löschen", "del_invitation_link_desc": "Der Link ist nach Löschung ungültig", "del_space_now": "Leerzeichen für immer löschen", + "del_space_now_confirm_tip": "For subscribers: Benefits attach to your Space. Removing it cancels those benefits. Be wary!", "del_space_now_tip": "Der Speicherplatz kann nach dem Löschen nicht wiederhergestellt werden. Alle Dateien und Anhänge werden gelöscht.", "del_space_res_tip": "Das Leerzeichen gelöscht", "del_team_success": "Teamerfolg löschen", @@ -3359,6 +3360,7 @@ "move_favorite_node_fail": "Knotenbewegung fehlgeschlagen. Das System aktualisiert die Liste automatisch.", "move_folder_link_warn": "Cannot be moved to Team Area because the files in the current folder are linked to files outside the folder (may be two-way links or linked by forms, etc.) We recommend that they be placed in a folder before moving", "move_node_modal_content": "Nach dem Verschieben kann die Sichtbarkeit der Datei durch den übergeordneten Ordner beeinträchtigt werden.", + "move_other_link_no_permission": "Unable to move to the corresponding folder because you do not have administrative rights to the folder", "move_other_link_warn": "It is not allowed to move ${node type} to Team Area alone, because it connects to the datasheet of Private Area, it is recommended to put them in a folder before moving them", "move_to": "Verschieben nach", "move_to_error_equal_parent": "Die Datei befindet sich unter dem aktuellen Ordner. Bitte wählen Sie einen anderen Ordner", @@ -5026,6 +5028,7 @@ "space_info": "Übersicht", "space_info_del_confirm1": "1. Wenn Sie diesen Bereich löschen, werden die folgenden Daten bereinigt:", "space_info_del_confirm2": "2. Der Space wird nach sieben Tagen vollständig gelöscht. Sie können den Space vorher wiederherstellen.", + "space_info_del_confirm3": "3. For subscribers: Benefits attach to your Space. Removing it cancels those benefits. Be wary!", "space_info_feishu_desc": "Sie verwenden eine Drittanbieterintegration. Um den Space zu löschen, deaktivieren Sie bitte zuerst die Integration von Drittanbietern.", "space_info_feishu_label": "Integrationen", "space_join_apply": " wurde angefordert, dem \"\" Raum beizutreten.", @@ -7406,7 +7409,7 @@ "create_mirror_guide_content": "The mirror function has the ability to hide certain data. You can set \"filter conditions\" and \"hidden fields\" in the original datasheet view to control which records and fields are displayed in the mirror.\n
\n
\nIf used in conjunction with the \"view lock\" function, it can prevent others from making modifications.\n
\n
\nIn addition, you can go to \"Original Table>Hidden Fields\" to modify the configuration for \"Show all fields in Mirrors\".", "create_mirror_guide_title": "Mirror hides some records and fields", "create_new_button_field": "Create a new button column field", - "create_private_node_tip": "Personal or temporary draft documents can be created here, ${link}", + "create_private_node_tip": "Personal or temporary draft documents can be created here ${link}", "create_public_invitation_link": "Create public invitation link(s)", "create_space_sub_title": "Hi, please give a name to your Space~", "create_team_fail": "Create team failed", @@ -7549,6 +7552,7 @@ "del_invitation_link": "Delete invitation link", "del_invitation_link_desc": "The link will be invalid after deletion", "del_space_now": "Delete Space forever", + "del_space_now_confirm_tip": "For subscribers: Benefits attach to your Space. Removing it cancels those benefits. Be wary!", "del_space_now_tip": "The Space can't be restored after deletion. All file nodes and attachments will be deleted.", "del_space_res_tip": "The Space deleted", "del_team_success": "Delete Team Success", @@ -9410,6 +9414,7 @@ "move_favorite_node_fail": "Node moving failed. The system will update the list automatically. ", "move_folder_link_warn": "Cannot be moved to Team Area because the files in the current folder are linked to files outside the folder (may be two-way links or linked by forms, etc.) We recommend that they be placed in a folder before moving", "move_node_modal_content": "After moving, the visibility of the file node may be affected by the parent folder.", + "move_other_link_no_permission": "Unable to move to the corresponding folder because you do not have administrative rights to the folder", "move_other_link_warn": "It is not allowed to move ${node type} to Team Area alone, because it connects to the datasheet of Private Area, it is recommended to put them in a folder before moving them", "move_to": "Move to", "move_to_error_equal_parent": "The file node is under the current folder. Please select another folder", @@ -11076,6 +11081,7 @@ "space_info": "Overview", "space_info_del_confirm1": "1. Deleting this Space will clean up the following data:", "space_info_del_confirm2": "2. The Space will be deleted completely after 7 days. You can restore the Space before then.", + "space_info_del_confirm3": "3. For subscribers: Benefits attach to your Space. Removing it cancels those benefits. Be wary!", "space_info_feishu_desc": "You are using a third-party integration. To delete the Space, please disable the third-party integration first. ", "space_info_feishu_label": "Integrations", "space_join_apply": " requested to join the \"\" Space.", @@ -13458,7 +13464,7 @@ "create_mirror_guide_content": "La función de espejo tiene la capacidad de ocultar ciertos datos. Puede establecer \"condiciones de filtro\" y \"campos ocultos\" en la vista de hoja de datos original para controlar qué registros y campos se muestran en el espejo.\n
\n
\nSi se usa junto con la función de \"bloqueo de vista\", puede evitar que otros realicen modificaciones.\n
\n
\nAdemás, puede ir a \"Tabla original>Campos ocultos\" para modificar la configuración de \"Mostrar todos los campos en espejos\".", "create_mirror_guide_title": "El espejo esconde algunos registros y campos", "create_new_button_field": "Crear un nuevo campo de columna de botón", - "create_private_node_tip": "Personal or temporary draft documents can be created here, ${link}", + "create_private_node_tip": "Personal or temporary draft documents can be created here ${link}", "create_public_invitation_link": "Crear enlaces de invitación pública", "create_space_sub_title": "Hola, por favor, nombra tu espacio compartido.", "create_team_fail": "Falló la creación del equipo", @@ -13601,6 +13607,7 @@ "del_invitation_link": "Eliminar el enlace de invitación", "del_invitation_link_desc": "El enlace no será válido después de la eliminación", "del_space_now": "Eliminar espacio para siempre", + "del_space_now_confirm_tip": "For subscribers: Benefits attach to your Space. Removing it cancels those benefits. Be wary!", "del_space_now_tip": "No se puede restaurar el espacio después de la eliminación. Todos los archivos y archivos adjuntos serán eliminados.", "del_space_res_tip": "Espacios eliminados", "del_team_success": "El equipo de eliminación fue exitoso", @@ -15463,6 +15470,7 @@ "move_favorite_node_fail": "El movimiento del nodo falló. El sistema actualizará automáticamente la lista.", "move_folder_link_warn": "Cannot be moved to Team Area because the files in the current folder are linked to files outside the folder (may be two-way links or linked by forms, etc.) We recommend that they be placed in a folder before moving", "move_node_modal_content": "Después de moverse, la visibilidad del archivo puede verse afectada por la carpeta padre.", + "move_other_link_no_permission": "Unable to move to the corresponding folder because you do not have administrative rights to the folder", "move_other_link_warn": "It is not allowed to move ${node type} to Team Area alone, because it connects to the datasheet of Private Area, it is recommended to put them in a folder before moving them", "move_to": "Mover a", "move_to_error_equal_parent": "El archivo se encuentra bajo la carpeta actual. Por favor, elija otra carpeta", @@ -17130,6 +17138,7 @@ "space_info": "Resumen", "space_info_del_confirm1": "1. eliminar este espacio compartido eliminará los siguientes datos:", "space_info_del_confirm2": "2. los espacios compartidos serán eliminados por completo después de 7 días. Antes de eso, puede restaurar el espacio.", + "space_info_del_confirm3": "3. For subscribers: Benefits attach to your Space. Removing it cancels those benefits. Be wary!", "space_info_feishu_desc": "Está utilizando integración de terceros. Para eliminar el espacio compartido, primero Deshabilite la integración de terceros.", "space_info_feishu_label": "Integración", "space_join_apply": "Se solicita la inclusión en el espacio \" < a class =\" spacename \"> < a > espacio.", @@ -17518,8 +17527,8 @@ "time_machine_action_title": "Historial de operaciones", "time_machine_unlimited": "Historia de la máquina del tiempo infinito", "time_zone_inconsistent_tips": "Cuando la zona es inconsistente en ese momento, por defecto se utilizará la zona horaria de la hora de inicio.", - "timemachine_add": "Añadido ${nombre}", - "timemachine_add_field": "se agregaron ${nombre} columna(s)", + "timemachine_add": "Añadido ${name}", + "timemachine_add_field": "se agregaron ${name} columna(s)", "timemachine_add_record": "se agregaron ${count} filas de registros", "timemachine_add_widget": "añadido un nuevo widget", "timemachine_delete_comment": "comentarios eliminados", @@ -19513,7 +19522,7 @@ "create_mirror_guide_content": "La fonction miroir a la capacité de masquer certaines données. Vous pouvez définir des \"conditions de filtre\" et des \"champs masqués\" dans la vue de feuille de données d'origine pour contrôler les enregistrements et les champs affichés dans le miroir.\n
\n
\nS'il est utilisé en conjonction avec la fonction \"verrouiller la vue\", il peut empêcher les autres d'apporter des modifications.\n
\n
\nDe plus, vous pouvez aller dans \"Table d'origine>Champs cachés\" pour modifier la configuration pour \"Afficher tous les champs dans les miroirs\".", "create_mirror_guide_title": "Le miroir masque certains enregistrements et champs", "create_new_button_field": "Créer un nouveau champ de colonne de bouton", - "create_private_node_tip": "Personal or temporary draft documents can be created here, ${link}", + "create_private_node_tip": "Personal or temporary draft documents can be created here ${link}", "create_public_invitation_link": "Créer le(s) lien(s) d'invitation publique", "create_space_sub_title": "Bonjour, veuillez donner un nom à votre espace~", "create_team_fail": "La création de l'équipe a échoué", @@ -19656,6 +19665,7 @@ "del_invitation_link": "Supprimer le lien d'invitation", "del_invitation_link_desc": "Le lien sera invalide après suppression", "del_space_now": "Supprimer l'espace pour toujours", + "del_space_now_confirm_tip": "For subscribers: Benefits attach to your Space. Removing it cancels those benefits. Be wary!", "del_space_now_tip": "L'espace ne peut pas être restauré après la suppression. Tous les fichiers et pièces jointes seront supprimés.", "del_space_res_tip": "L'espace a été supprimé", "del_team_success": "Suppression de l'équipe réussie", @@ -21518,6 +21528,7 @@ "move_favorite_node_fail": "Le déplacement du nœud a échoué. Le système mettra à jour la liste automatiquement. ", "move_folder_link_warn": "Cannot be moved to Team Area because the files in the current folder are linked to files outside the folder (may be two-way links or linked by forms, etc.) We recommend that they be placed in a folder before moving", "move_node_modal_content": "Après le déplacement, la visibilité du fichier peut être affectée par le dossier parent.", + "move_other_link_no_permission": "Unable to move to the corresponding folder because you do not have administrative rights to the folder", "move_other_link_warn": "It is not allowed to move ${node type} to Team Area alone, because it connects to the datasheet of Private Area, it is recommended to put them in a folder before moving them", "move_to": "Déplacer vers", "move_to_error_equal_parent": "Le fichier est sous le dossier actuel. Veuillez sélectionner un autre dossier", @@ -23185,6 +23196,7 @@ "space_info": "Aperçu", "space_info_del_confirm1": "1. La suppression de cet espace va nettoyer les données suivantes :", "space_info_del_confirm2": "2. L'espace sera supprimé complètement après 7 jours. Vous pouvez restaurer l'espace avant cette date.", + "space_info_del_confirm3": "3. For subscribers: Benefits attach to your Space. Removing it cancels those benefits. Be wary!", "space_info_feishu_desc": "Vous utilisez une intégration de tiers. Pour supprimer l'espace, veuillez d'abord désactiver l'intégration de tiers. ", "space_info_feishu_label": "Intégrations", "space_join_apply": " a demandé à rejoindre l'espace \".", @@ -25568,7 +25580,7 @@ "create_mirror_guide_content": "La funzione mirror ha la capacità di nascondere determinati dati. È possibile impostare \"condizioni di filtro\" e \"campi nascosti\" nella visualizzazione del foglio dati originale per controllare quali record e campi vengono visualizzati nel mirror.\n
\n
\nSe utilizzato insieme alla funzione \"Visualizza blocco\", può impedire ad altri di apportare modifiche.\n
\n
\nInoltre, puoi andare su \"Tabella originale>Campi nascosti\" per modificare la configurazione per \"Mostra tutti i campi nei mirror\".", "create_mirror_guide_title": "Specchio nasconde alcuni record e campi", "create_new_button_field": "Crea un nuovo campo colonna pulsante", - "create_private_node_tip": "Personal or temporary draft documents can be created here, ${link}", + "create_private_node_tip": "Personal or temporary draft documents can be created here ${link}", "create_public_invitation_link": "Crea link di invito pubblico", "create_space_sub_title": "Ciao, si prega di dare un nome al vostro spazio~", "create_team_fail": "Crea team fallito", @@ -25711,6 +25723,7 @@ "del_invitation_link": "Elimina link di invito", "del_invitation_link_desc": "Il link non sarà valido dopo la cancellazione", "del_space_now": "Elimina spazio per sempre", + "del_space_now_confirm_tip": "For subscribers: Benefits attach to your Space. Removing it cancels those benefits. Be wary!", "del_space_now_tip": "Lo spazio non può essere ripristinato dopo la cancellazione. Tutti i file e gli allegati verranno eliminati.", "del_space_res_tip": "Lo spazio eliminato", "del_team_success": "Elimina team di successo", @@ -27573,6 +27586,7 @@ "move_favorite_node_fail": "Spostamento nodo fallito. Il sistema aggiornerà automaticamente l'elenco.", "move_folder_link_warn": "Cannot be moved to Team Area because the files in the current folder are linked to files outside the folder (may be two-way links or linked by forms, etc.) We recommend that they be placed in a folder before moving", "move_node_modal_content": "Dopo lo spostamento, la visibilità del file potrebbe essere influenzata dalla cartella principale.", + "move_other_link_no_permission": "Unable to move to the corresponding folder because you do not have administrative rights to the folder", "move_other_link_warn": "It is not allowed to move ${node type} to Team Area alone, because it connects to the datasheet of Private Area, it is recommended to put them in a folder before moving them", "move_to": "Sposta a", "move_to_error_equal_parent": "Il file si trova sotto la cartella corrente. Seleziona un'altra cartella", @@ -29240,6 +29254,7 @@ "space_info": "Panoramica", "space_info_del_confirm1": "1. Eliminando questo spazio verranno eliminati i seguenti dati:", "space_info_del_confirm2": "2. Lo spazio verrà cancellato completamente dopo 7 giorni. Puoi ripristinare lo Spazio prima di allora.", + "space_info_del_confirm3": "3. For subscribers: Benefits attach to your Space. Removing it cancels those benefits. Be wary!", "space_info_feishu_desc": "Stai utilizzando un'integrazione di terze parti. Per eliminare lo spazio, disattivare prima l'integrazione di terze parti.", "space_info_feishu_label": "Integrazioni", "space_join_apply": " ha richiesto di unirsi allo spazio \"\".", @@ -29628,7 +29643,7 @@ "time_machine_action_title": "Storia delle operazioni", "time_machine_unlimited": "Storia illimitata delle macchine del tempo", "time_zone_inconsistent_tips": "Quando i fusi orari sono incoerenti, il fuso orario dell'ora di inizio verrà utilizzato per impostazione predefinita", - "timemachine_add": "Aggiunto ${nome}", + "timemachine_add": "Aggiunto ${name}", "timemachine_add_field": "aggiunte colonne ${name}", "timemachine_add_record": "aggiunte ${count} righe di record", "timemachine_add_widget": "aggiunto un nuovo widget", @@ -31623,7 +31638,7 @@ "create_mirror_guide_content": "ミラー機能には、特定のデータを非表示にする機能があります。 元のデータシート ビューで「フィルター条件」と「非表示フィールド」を設定して、ミラーに表示するレコードとフィールドを制御できます。\n
\n
\n「閲覧ロック」機能と併用すると、他人による改変を防ぐことができます。\n
\n
\nさらに、「元のテーブル > 非表示フィールド」に移動して、「ミラー内のすべてのフィールドを表示」の設定を変更できます。", "create_mirror_guide_title": "ミラーはいくつかのレコードとフィールドを非表示にします", "create_new_button_field": "新しいボタン列フィールドを作成する", - "create_private_node_tip": "Personal or temporary draft documents can be created here, ${link}", + "create_private_node_tip": "Personal or temporary draft documents can be created here ${link}", "create_public_invitation_link": "パブリック招待リンクの作成", "create_space_sub_title": "ハイ、あなたの共有スペースに名前を付けてください~", "create_team_fail": "チームの作成に失敗しました", @@ -31766,6 +31781,7 @@ "del_invitation_link": "招待リンクの削除", "del_invitation_link_desc": "削除するとリンクが無効になります", "del_space_now": "スペースを永遠に削除", + "del_space_now_confirm_tip": "For subscribers: Benefits attach to your Space. Removing it cancels those benefits. Be wary!", "del_space_now_tip": "削除後にスペースを復元できませんでした。すべてのファイルと添付ファイルが削除されます。", "del_space_res_tip": "削除されたスペース", "del_team_success": "チームの削除に成功しました", @@ -33628,6 +33644,7 @@ "move_favorite_node_fail": "ノードの移動に失敗しました。リストが自動的に更新されます。", "move_folder_link_warn": "Cannot be moved to Team Area because the files in the current folder are linked to files outside the folder (may be two-way links or linked by forms, etc.) We recommend that they be placed in a folder before moving", "move_node_modal_content": "移動後、ファイルの可視性は親フォルダーの影響を受ける可能性があります。", + "move_other_link_no_permission": "Unable to move to the corresponding folder because you do not have administrative rights to the folder", "move_other_link_warn": "It is not allowed to move ${node type} to Team Area alone, because it connects to the datasheet of Private Area, it is recommended to put them in a folder before moving them", "move_to": "移動先", "move_to_error_equal_parent": "ファイルは現在のフォルダの下にあります。他のフォルダを選択してください", @@ -35295,6 +35312,7 @@ "space_info": "概要", "space_info_del_confirm1": "1.この共有スペースを削除すると、次のデータが消去されます。", "space_info_del_confirm2": "2.共有スペースは7日後に完全に削除されます。その前に、スペースを復元できます。", + "space_info_del_confirm3": "3. For subscribers: Benefits attach to your Space. Removing it cancels those benefits. Be wary!", "space_info_feishu_desc": "サードパーティ統合を使用しています。共有スペースを削除するには、まずサードパーティ統合を無効にします。", "space_info_feishu_label": "統合", "space_join_apply": "「」スペースへの参加を要求します。", @@ -37678,7 +37696,7 @@ "create_mirror_guide_content": "미러 기능에는 특정 데이터를 숨길 수 있는 기능이 있습니다. 원본 데이터시트 보기에서 \"필터 조건\" 및 \"숨겨진 필드\"를 설정하여 미러에 표시되는 레코드와 필드를 제어할 수 있습니다.\n
\n
\n\"보기 잠금\" 기능과 함께 사용하면 다른 사람이 수정하는 것을 방지할 수 있습니다.\n
\n
\n또한 \"원래 테이블>숨겨진 필드\"로 이동하여 \"미러에 모든 필드 표시\" 구성을 수정할 수 있습니다.", "create_mirror_guide_title": "미러링은 일부 레코드 및 필드를 숨깁니다.", "create_new_button_field": "새 버튼 열 필드 만들기", - "create_private_node_tip": "Personal or temporary draft documents can be created here, ${link}", + "create_private_node_tip": "Personal or temporary draft documents can be created here ${link}", "create_public_invitation_link": "공개 초대 링크 만들기", "create_space_sub_title": "안녕하세요, 공유 공간의 이름을 지어주세요 ~", "create_team_fail": "팀 생성 실패", @@ -37821,6 +37839,7 @@ "del_invitation_link": "초대 링크 삭제", "del_invitation_link_desc": "삭제 후 링크가 유효하지 않습니다.", "del_space_now": "스페이스 영원히 삭제", + "del_space_now_confirm_tip": "For subscribers: Benefits attach to your Space. Removing it cancels those benefits. Be wary!", "del_space_now_tip": "삭제 후 공간을 복원할 수 없습니다.모든 파일과 첨부 파일이 삭제됩니다.", "del_space_res_tip": "삭제된 공간", "del_team_success": "팀 삭제 성공", @@ -39683,6 +39702,7 @@ "move_favorite_node_fail": "노드 이동에 실패했습니다.목록이 자동으로 업데이트됩니다.", "move_folder_link_warn": "Cannot be moved to Team Area because the files in the current folder are linked to files outside the folder (may be two-way links or linked by forms, etc.) We recommend that they be placed in a folder before moving", "move_node_modal_content": "이동하면 상위 폴더의 파일 가시성에 영향을 받을 수 있습니다.", + "move_other_link_no_permission": "Unable to move to the corresponding folder because you do not have administrative rights to the folder", "move_other_link_warn": "It is not allowed to move ${node type} to Team Area alone, because it connects to the datasheet of Private Area, it is recommended to put them in a folder before moving them", "move_to": "다음으로 이동", "move_to_error_equal_parent": "파일은 현재 폴더 아래에 있습니다.다른 폴더를 선택하십시오.", @@ -41350,6 +41370,7 @@ "space_info": "개요", "space_info_del_confirm1": "1. 이 공유 공간을 삭제하면 다음 데이터가 지워집니다.", "space_info_del_confirm2": "2. 공유 공간은 7일 후에 완전히 삭제됩니다.그 전에 공간을 복원할 수 있습니다.", + "space_info_del_confirm3": "3. For subscribers: Benefits attach to your Space. Removing it cancels those benefits. Be wary!", "space_info_feishu_desc": "타사 통합을 사용하고 있습니다.공유 공간을 삭제하려면 먼저 타사 통합을 비활성화합니다.", "space_info_feishu_label": "통합", "space_join_apply": "\"\" 공간을 요청합니다.", @@ -43733,7 +43754,7 @@ "create_mirror_guide_content": "Функция зеркала имеет возможность скрывать определенные данные. Вы можете установить «условия фильтрации» и «скрытые поля» в исходном представлении таблицы данных, чтобы контролировать, какие записи и поля отображаются в зеркале.\n
\n
\nПри использовании в сочетании с функцией «блокировки просмотра» она может помешать другим пользователям вносить изменения.\n
\n
\nКроме того, вы можете перейти в «Исходная таблица> Скрытые поля», чтобы изменить конфигурацию «Показать все поля в зеркалах».", "create_mirror_guide_title": "Зеркало скрывает некоторые записи и поля", "create_new_button_field": "Создайте новое поле столбца кнопки", - "create_private_node_tip": "Personal or temporary draft documents can be created here, ${link}", + "create_private_node_tip": "Personal or temporary draft documents can be created here ${link}", "create_public_invitation_link": "Создать ссылку на открытое приглашение", "create_space_sub_title": "Эй, пожалуйста, назовите свое общее пространство.", "create_team_fail": "Ошибка создания команды", @@ -43876,6 +43897,7 @@ "del_invitation_link": "Удалить ссылку приглашения", "del_invitation_link_desc": "После удаления ссылка будет недействительной", "del_space_now": "Удалить пространство навсегда", + "del_space_now_confirm_tip": "For subscribers: Benefits attach to your Space. Removing it cancels those benefits. Be wary!", "del_space_now_tip": "Невозможно восстановить пространство после удаления. Все документы и приложения будут удалены.", "del_space_res_tip": "Удалить пространство", "del_team_success": "Удалить команду успешно", @@ -45738,6 +45760,7 @@ "move_favorite_node_fail": "Ошибка перемещения узла. Система автоматически обновляет список.", "move_folder_link_warn": "Cannot be moved to Team Area because the files in the current folder are linked to files outside the folder (may be two-way links or linked by forms, etc.) We recommend that they be placed in a folder before moving", "move_node_modal_content": "После перемещения на видимость файла может повлиять родительская папка.", + "move_other_link_no_permission": "Unable to move to the corresponding folder because you do not have administrative rights to the folder", "move_other_link_warn": "It is not allowed to move ${node type} to Team Area alone, because it connects to the datasheet of Private Area, it is recommended to put them in a folder before moving them", "move_to": "Перейти на", "move_to_error_equal_parent": "Файл находится в текущей папке. Выберите другую папку", @@ -47405,6 +47428,7 @@ "space_info": "Общий обзор", "space_info_del_confirm1": "Удаление этого общего пространства очистит следующие данные:", "space_info_del_confirm2": "Общее пространство будет полностью удалено через семь дней. До этого времени вы можете восстановить пространство.", + "space_info_del_confirm3": "3. For subscribers: Benefits attach to your Space. Removing it cancels those benefits. Be wary!", "space_info_feishu_desc": "Вы используете стороннюю интеграцию. Чтобы удалить общее пространство, сначала отключите стороннюю интеграцию.", "space_info_feishu_label": "Интеграция", "space_join_apply": "Запрос на включение пространства \"a class =\" spaceName \"< / a >.", @@ -49788,7 +49812,7 @@ "create_mirror_guide_content": "镜像具有隐藏部分数据的功能,你可以在原表的视图中设置“筛选条件”和“隐藏字段”来控制镜像中需要展示的记录和字段。\n
\n
\n如果配合「视图锁」功能一起使用,可以防止其他人修改。\n
\n
\n另外,前往「原表>隐藏字段」可以修改“镜像中允许查看隐藏字段”的配置", "create_mirror_guide_title": "镜像将隐藏部分记录和字段", "create_new_button_field": "新建一个按钮列字段", - "create_private_node_tip": "个人或者临时类的草稿文件可以在这里创建,${link}", + "create_private_node_tip": "个人或者临时类的草稿文件可以在这里创建 ${link}", "create_public_invitation_link": "创建公开的邀请链接", "create_space_sub_title": "Hi,给你的空间站起个名字吧", "create_team_fail": "添加小组失败", @@ -49852,7 +49876,7 @@ "custom_enterprise": "企业级空间站支持自定义人数、时长,灵活又强大", "custom_function_development": "定制功能开发", "custom_grade_desc": "提供原厂私有化安装部署,您可以获得企业级咨询服务、专家技术支持和定制化专业服务", - "custom_page_setting_title": "Add a custom page", + "custom_page_setting_title": "新建自定义页面", "custom_picture": "自定义图片", "custom_style": "样式", "custom_upload": "自定义上传", @@ -49933,7 +49957,8 @@ "del_invitation_link": "确定删除邀请链接", "del_invitation_link_desc": "删除后已生成的分享链接将会失效", "del_space_now": "彻底删除空间站", - "del_space_now_tip": "请注意:空间站删除后将不可恢复,包含所有表格、附件都会被彻底删除", + "del_space_now_confirm_tip": "再次提醒:如果空间站已订阅付费,删除空间站将会一并移除付费权益,该权益无法转移,请慎重操作!", + "del_space_now_tip": "请注意:空间站删除后将不可恢复,包含所有表格、附件都会被彻底删除。", "del_space_res_tip": "删除空间站成功", "del_team_success": "删除小组成功", "del_view_content": "确认要删除视图「${view_name}」吗?", @@ -51416,8 +51441,8 @@ "lark_integration_sync_tip": "通讯录同步中", "lark_login": "飞书登录", "lark_org_manage_reject_msg": "成员列表会实时跟飞书通讯录的组织架构保持同步,请在飞书侧进行此操作(企业管理 > 添加团队成员)", - "lark_single_record_comment_mentioned": "", - "lark_single_record_comment_mentioned_title": "", + "lark_single_record_comment_mentioned": "**维格表: **{nodeName}\n**记录: **{recordTitle}\n\n\"{commentContent}\"", + "lark_single_record_comment_mentioned_title": "**有人在评论中提及你**", "lark_single_record_member_mention": "**记录: **{recordTitle}\n**提及人: **{memberName}\n**维格表: **{nodeName}", "lark_single_record_member_mention_title": "**有人在记录中提及你**", "lark_subscribed_record_cell_updated": "**记录:** {recordTitle}\n**原内容:** {oldDisplayValue}\n**修改后的内容:** {newDisplayValue}\n**修改人:** {memberName}\n**维格表:** {nodeName}", @@ -51796,6 +51821,7 @@ "move_favorite_node_fail": "移动节点失败,系统将会自动更新列表", "move_folder_link_warn": "无法移到团队工作区,因为当前文件夹内的文件与文件夹外的文件有连接关系(可能是关联关系或者被表单等所连接)建议将它们放在一个文件夹里再进行移动", "move_node_modal_content": "移动后,文件的可见性可能会受到上级文件夹的影响", + "move_other_link_no_permission": "你无法移动文件至目标文件夹,因为你缺少该文件夹的管理权限", "move_other_link_warn": "无法单独将${nodeType}移动至团队工作区,因为它连接了个人工作区的表,建议将它们放在一个文件夹里进行移动", "move_to": "移动至", "move_to_error_equal_parent": "文件已在当前文件夹下方,请选择其他文件夹", @@ -51827,8 +51853,7 @@ "new_a_line": "Shift+Enter 换行", "new_automation": "新建自动化", "new_caledonia": "新喀里多尼亚", - "new_custom_page": "New custom page", - "new_custome_page": "新建自定义页面", + "new_custom_page": "新建自定义页面", "new_datasheet": "新建表格", "new_folder": "新建文件夹", "new_folder_btn_title": "新建文件夹", @@ -53464,6 +53489,7 @@ "space_info": "空间站驾驶舱", "space_info_del_confirm1": "1. 删除空间站后,以下数据将被全部清除:", "space_info_del_confirm2": "2. 空间站将会在七天后自动彻底删除。在此之前,你可以随时撤销删除操作", + "space_info_del_confirm3": "3. 再次提醒:如果空间站已订阅付费,删除空间站将会一并移除付费权益,该权益无法转移,请慎重操作!", "space_info_feishu_desc": "使用了第三方集成,如需删除空间站请先关闭第三方集成。", "space_info_feishu_label": "第三方应用集成", "space_join_apply": "申请加入空间站「」", @@ -55846,7 +55872,7 @@ "create_mirror_guide_content": "鏡像功能具有隱藏某些數據的能力。 您可以在原始數據表視圖中設置“過濾條件”和“隱藏字段”來控制在鏡像中顯示哪些記錄和字段。\n
\n
\n如果與“視圖鎖定”功能配合使用,可以防止他人進行修改。\n
\n
\n另外,您可以在“原始表>隱藏字段”中修改“在鏡像中顯示所有字段”的配置。", "create_mirror_guide_title": "鏡像隱藏了一些記錄和字段", "create_new_button_field": "建立一個新的按鈕列字段", - "create_private_node_tip": "Personal or temporary draft documents can be created here, ${link}", + "create_private_node_tip": "Personal or temporary draft documents can be created here ${link}", "create_public_invitation_link": "創建公開的邀請鏈接", "create_space_sub_title": "Hi,給你的空間站起個名字吧", "create_team_fail": "添加小組失敗", @@ -55989,6 +56015,7 @@ "del_invitation_link": "確定刪除邀請鏈接", "del_invitation_link_desc": "刪除後已生成的分享鏈接將會失效", "del_space_now": "徹底刪除空間站", + "del_space_now_confirm_tip": "For subscribers: Benefits attach to your Space. Removing it cancels those benefits. Be wary!", "del_space_now_tip": "請注意:空間站刪除後將不可恢復,包含所有表格、附件都會被徹底刪除", "del_space_res_tip": "刪除空間站成功", "del_team_success": "刪除小組成功", @@ -57851,6 +57878,7 @@ "move_favorite_node_fail": "移動節點失敗,系統將會自動更新列表", "move_folder_link_warn": "Cannot be moved to Team Area because the files in the current folder are linked to files outside the folder (may be two-way links or linked by forms, etc.) We recommend that they be placed in a folder before moving", "move_node_modal_content": "移動後,文件的可見性可能會受到上級文件夾的影響", + "move_other_link_no_permission": "Unable to move to the corresponding folder because you do not have administrative rights to the folder", "move_other_link_warn": "It is not allowed to move ${node type} to Team Area alone, because it connects to the datasheet of Private Area, it is recommended to put them in a folder before moving them", "move_to": "搬去", "move_to_error_equal_parent": "該文件位於當前文件夾下。 請選擇另一個文件夾", @@ -59518,6 +59546,7 @@ "space_info": "空間站駕駛艙", "space_info_del_confirm1": "1. 刪除空間站後,以下數據將被全部清除:", "space_info_del_confirm2": "2. 空間站將會在七天后自動徹底刪除。在此之前,你可以隨時撤銷刪除操作", + "space_info_del_confirm3": "3. For subscribers: Benefits attach to your Space. Removing it cancels those benefits. Be wary!", "space_info_feishu_desc": "使用了第三方集成,如需刪除空間站請先關閉第三方集成。", "space_info_feishu_label": "第三方應用集成", "space_join_apply": "申請加入空間站「」", diff --git a/backend-server/application/src/test/java/com/apitable/AbstractIntegrationTest.java b/backend-server/application/src/test/java/com/apitable/AbstractIntegrationTest.java index 597f4a5311..d6a8e4ba48 100644 --- a/backend-server/application/src/test/java/com/apitable/AbstractIntegrationTest.java +++ b/backend-server/application/src/test/java/com/apitable/AbstractIntegrationTest.java @@ -24,6 +24,7 @@ import com.apitable.asset.service.IAssetUploadTokenService; import com.apitable.asset.task.AssetTask; import com.apitable.auth.service.IAuthService; +import com.apitable.automation.service.IAutomationActionService; import com.apitable.automation.service.IAutomationRobotService; import com.apitable.automation.service.IAutomationTriggerService; import com.apitable.automation.service.IAutomationTriggerTypeService; @@ -71,6 +72,7 @@ import com.apitable.user.service.IUserService; import com.apitable.user.task.UserTasks; import com.apitable.widget.service.IWidgetPackageService; +import com.apitable.widget.service.IWidgetService; import com.apitable.widget.service.IWidgetUploadService; import com.apitable.workspace.dto.CreateNodeDto; import com.apitable.workspace.entity.NodeEntity; @@ -229,6 +231,9 @@ public abstract class AbstractIntegrationTest extends TestSuiteWithDB { @Autowired protected IWidgetPackageService iWidgetPackageService; + @Autowired + protected IWidgetService iWidgetService; + @Autowired protected IFieldRoleService iFieldRoleService; @@ -259,6 +264,9 @@ public abstract class AbstractIntegrationTest extends TestSuiteWithDB { @Autowired protected IAutomationTriggerTypeService iAutomationTriggerTypeService; + @Autowired + protected IAutomationActionService iAutomationActionService; + @Autowired protected NodeBundleService nodeBundleService; diff --git a/backend-server/application/src/test/java/com/apitable/automation/service/impl/AutomationActionServiceImplTest.java b/backend-server/application/src/test/java/com/apitable/automation/service/impl/AutomationActionServiceImplTest.java new file mode 100644 index 0000000000..ffe57ec940 --- /dev/null +++ b/backend-server/application/src/test/java/com/apitable/automation/service/impl/AutomationActionServiceImplTest.java @@ -0,0 +1,24 @@ +package com.apitable.automation.service.impl; + +import static org.assertj.core.api.Assertions.assertThat; + +import cn.hutool.core.io.IoUtil; +import cn.hutool.json.JSON; +import com.apitable.AbstractIntegrationTest; +import com.apitable.FileHelper; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import org.junit.jupiter.api.Test; + +public class AutomationActionServiceImplTest extends AbstractIntegrationTest { + + @Test + public void testHandActionInput() { + InputStream inputStream = FileHelper.getInputStreamFromResource( + "file/email_action_input.json"); + String jsonString = IoUtil.read(inputStream, StandardCharsets.UTF_8); + JSON data = iAutomationActionService.handleActionInput(jsonString); + assertThat(data).isNotNull(); + } + +} diff --git a/backend-server/application/src/test/java/com/apitable/space/mapper/StaticsMapperTest.java b/backend-server/application/src/test/java/com/apitable/space/mapper/StaticsMapperTest.java index f56599a01d..6f02d87108 100644 --- a/backend-server/application/src/test/java/com/apitable/space/mapper/StaticsMapperTest.java +++ b/backend-server/application/src/test/java/com/apitable/space/mapper/StaticsMapperTest.java @@ -20,6 +20,7 @@ import static org.assertj.core.api.Assertions.assertThat; +import cn.hutool.json.JSONUtil; import com.apitable.AbstractMyBatisMapperTest; import com.apitable.space.dto.NodeStaticsDTO; import com.apitable.space.dto.NodeTypeStaticsDTO; @@ -68,8 +69,8 @@ void testCountTeamBySpaceId() { @Test @Sql({"/sql/datasheet-meta-data.sql", "/sql/datasheet-data.sql"}) void testCountRecordsBySpaceId() { - Long count = staticsMapper.countRecordsBySpaceId("spc41"); - assertThat(count).isEqualTo(3); + List count = staticsMapper.countRecordsBySpaceId("spc41"); + assertThat(3).isEqualTo(JSONUtil.parseArray(count.get(0)).size()); } @Test diff --git a/backend-server/application/src/test/java/com/apitable/workspace/service/impl/NodeServiceImplTest.java b/backend-server/application/src/test/java/com/apitable/workspace/service/impl/NodeServiceImplTest.java index 817753cc98..1fa3c4cd32 100644 --- a/backend-server/application/src/test/java/com/apitable/workspace/service/impl/NodeServiceImplTest.java +++ b/backend-server/application/src/test/java/com/apitable/workspace/service/impl/NodeServiceImplTest.java @@ -23,6 +23,7 @@ import static org.assertj.core.api.Assertions.assertThatNoException; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.util.Lists.list; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -31,6 +32,8 @@ import cn.hutool.json.JSONUtil; import com.apitable.AbstractIntegrationTest; import com.apitable.FileHelper; +import com.apitable.automation.entity.AutomationRobotEntity; +import com.apitable.automation.entity.AutomationTriggerEntity; import com.apitable.core.exception.BusinessException; import com.apitable.interfaces.billing.facade.EntitlementServiceFacade; import com.apitable.interfaces.billing.model.DefaultSubscriptionFeature; @@ -39,6 +42,7 @@ import com.apitable.mock.bean.MockSubscriptionInfo; import com.apitable.mock.bean.MockUserSpace; import com.apitable.organization.enums.UnitType; +import com.apitable.shared.util.IdUtil; import com.apitable.shared.util.page.PageHelper; import com.apitable.shared.util.page.PageInfo; import com.apitable.space.vo.SpaceGlobalFeature; @@ -981,6 +985,68 @@ void testMoveNodeToSpaceWorkspace() { assertThat(node.getParentId()).isEqualTo(folderId); } + @Test + void testMovePrivateFolderToTeamWorkspace() { + // mock user + MockUserSpace userSpace = createUserSpaceForFreeSubscription(); + Long unitId = iUnitService.getUnitIdByRefId(userSpace.getMemberId()); + // create node + String rootNodeId = iNodeService.getRootNodeIdBySpaceId(userSpace.getSpaceId()); + NodeOpRo ro = new NodeOpRo().toBuilder() + .parentId(rootNodeId) + .unitId(unitId.toString()) + .type(NodeType.FOLDER.getNodeType()) + .build(); + String folderId = + iNodeService.createNode(userSpace.getUserId(), userSpace.getSpaceId(), ro); + ro.setType(NodeType.DATASHEET.getNodeType()); + ro.setParentId(folderId); + iNodeService.createNode(userSpace.getUserId(), userSpace.getSpaceId(), ro); + NodeMoveOpRo moveOpRo = new NodeMoveOpRo(); + moveOpRo.setParentId(rootNodeId); + moveOpRo.setNodeId(folderId); + iNodeService.move(userSpace.getUserId(), moveOpRo); + List childNodes = + iNodeService.getChildNodesByNodeId(userSpace.getSpaceId(), userSpace.getMemberId(), + folderId, null); + assertThat(childNodes.get(0).getNodePrivate()).isFalse(); + } + + + @Test + void testMovePrivateFolderToTeamWorkspace2() { + // mock user + MockUserSpace userSpace = createUserSpaceForFreeSubscription(); + Long unitId = iUnitService.getUnitIdByRefId(userSpace.getMemberId()); + // create node + String rootNodeId = iNodeService.getRootNodeIdBySpaceId(userSpace.getSpaceId()); + NodeOpRo ro = new NodeOpRo().toBuilder() + .parentId(rootNodeId) + .unitId(unitId.toString()) + .type(NodeType.FOLDER.getNodeType()) + .build(); + String folderId1 = + iNodeService.createNode(userSpace.getUserId(), userSpace.getSpaceId(), ro); + ro.setParentId(folderId1); + String folderId2 = + iNodeService.createNode(userSpace.getUserId(), userSpace.getSpaceId(), ro); + ro.setType(NodeType.DATASHEET.getNodeType()); + ro.setParentId(folderId2); + iNodeService.createNode(userSpace.getUserId(), userSpace.getSpaceId(), ro); + NodeMoveOpRo moveOpRo = new NodeMoveOpRo(); + moveOpRo.setParentId(rootNodeId); + moveOpRo.setNodeId(folderId1); + iNodeService.move(userSpace.getUserId(), moveOpRo); + List childNodes1 = + iNodeService.getChildNodesByNodeId(userSpace.getSpaceId(), userSpace.getMemberId(), + folderId1, null); + assertThat(childNodes1.get(0).getHasChildren()).isTrue(); + List childNodes2 = + iNodeService.getChildNodesByNodeId(userSpace.getSpaceId(), userSpace.getMemberId(), + folderId1, null); + assertThat(childNodes2.get(0).getNodePrivate()).isFalse(); + } + @Test void testCopyPrivateNode() { // mock user @@ -1414,6 +1480,173 @@ void testCreatePrivateMirrorNodeInPrivateFolder() { assertThat(mirrorId).isNotNull(); } + @Test + public void testLinkOutsideAutomationWithInside() { + MockUserSpace user = createUserSpaceForFreeSubscription(); + String rootNodeId = iNodeService.getRootNodeIdBySpaceId(user.getSpaceId()); + // create node + NodeOpRo ro = new NodeOpRo().toBuilder() + .parentId(rootNodeId) + .type(NodeType.DATASHEET.getNodeType()) + .build(); + String nodeId = + iNodeService.createNode(user.getUserId(), user.getSpaceId(), ro); + AutomationRobotEntity robot = AutomationRobotEntity.builder() + .robotId(IdUtil.createAutomationRobotId()) + .resourceId(nodeId) + .build(); + iAutomationRobotService.create(robot); + AutomationTriggerEntity trigger = + AutomationTriggerEntity.builder().robotId(robot.getRobotId()).resourceId(nodeId) + .triggerTypeId(IdUtil.createAutomationTriggerTypeId()) + .triggerId(IdUtil.createAutomationTriggerId()).build(); + iAutomationTriggerService.create(trigger); + assertDoesNotThrow(() -> iNodeService.linkByOutsideResource(nodeId)); + } + + @Test + public void testLinkOutsideAutomationWithAutomationResource() { + MockUserSpace user = createUserSpaceForFreeSubscription(); + String rootNodeId = iNodeService.getRootNodeIdBySpaceId(user.getSpaceId()); + // create node + NodeOpRo ro = new NodeOpRo().toBuilder() + .parentId(rootNodeId) + .type(NodeType.AUTOMATION.getNodeType()) + .build(); + String automationId = + iNodeService.createNode(user.getUserId(), user.getSpaceId(), ro); + ro.setType(NodeType.DATASHEET.getNodeType()); + String dstId = + iNodeService.createNode(user.getUserId(), user.getSpaceId(), ro); + AutomationRobotEntity robot = AutomationRobotEntity.builder() + .robotId(IdUtil.createAutomationRobotId()) + .resourceId(automationId) + .build(); + iAutomationRobotService.create(robot); + AutomationTriggerEntity trigger = + AutomationTriggerEntity.builder().robotId(robot.getRobotId()).resourceId(dstId) + .triggerTypeId(IdUtil.createAutomationTriggerTypeId()) + .triggerId(IdUtil.createAutomationTriggerId()).build(); + iAutomationTriggerService.create(trigger); + BusinessException exception = + assertThrows(BusinessException.class, + () -> iNodeService.linkByOutsideResource(dstId)); + assertEquals(430, exception.getCode()); + } + + @Test + public void testMoveOriginalDstInFolderLinkedByOutsideMirror() { + MockUserSpace user = createUserSpaceForFreeSubscription(); + Long unitId = iUnitService.getUnitIdByRefId(user.getMemberId()); + String rootNodeId = iNodeService.getRootNodeIdBySpaceId(user.getSpaceId()); + // create node + NodeOpRo ro = new NodeOpRo().toBuilder() + .unitId(unitId.toString()) + .parentId(rootNodeId) + .type(NodeType.FOLDER.getNodeType()) + .build(); + String folderId = + iNodeService.createNode(user.getUserId(), user.getSpaceId(), ro); + ro.setType(NodeType.DATASHEET.getNodeType()); + ro.setParentId(folderId); + String dstId = + iNodeService.createNode(user.getUserId(), user.getSpaceId(), ro); + NodeRelRo relRo = new NodeRelRo(); + relRo.setDatasheetId(dstId); + relRo.setViewId(IdUtil.createViewId()); + ro.setType(NodeType.MIRROR.getNodeType()); + ro.setExtra(relRo); + ro.setParentId(rootNodeId); + iNodeService.createNode(user.getUserId(), user.getSpaceId(), ro); + BusinessException exception = + assertThrows(BusinessException.class, + () -> iNodeService.linkByOutsideResource(folderId)); + assertEquals(430, exception.getCode()); + } + + @Test + public void testMoveOriginalDstInAndMirrorInSameFolder() { + MockUserSpace user = createUserSpaceForFreeSubscription(); + Long unitId = iUnitService.getUnitIdByRefId(user.getMemberId()); + String rootNodeId = iNodeService.getRootNodeIdBySpaceId(user.getSpaceId()); + // create node + NodeOpRo ro = new NodeOpRo().toBuilder() + .unitId(unitId.toString()) + .parentId(rootNodeId) + .type(NodeType.FOLDER.getNodeType()) + .build(); + String folderId = + iNodeService.createNode(user.getUserId(), user.getSpaceId(), ro); + ro.setType(NodeType.DATASHEET.getNodeType()); + ro.setParentId(folderId); + String dstId = + iNodeService.createNode(user.getUserId(), user.getSpaceId(), ro); + NodeRelRo relRo = new NodeRelRo(); + relRo.setDatasheetId(dstId); + relRo.setViewId(IdUtil.createViewId()); + ro.setType(NodeType.MIRROR.getNodeType()); + ro.setExtra(relRo); + iNodeService.createNode(user.getUserId(), user.getSpaceId(), ro); + assertDoesNotThrow(() -> iNodeService.linkByOutsideResource(folderId)); + } + + + @Test + public void testMoveOriginalDstInAndFormInSameFolder() { + MockUserSpace user = createUserSpaceForFreeSubscription(); + Long unitId = iUnitService.getUnitIdByRefId(user.getMemberId()); + String rootNodeId = iNodeService.getRootNodeIdBySpaceId(user.getSpaceId()); + // create node + NodeOpRo ro = new NodeOpRo().toBuilder() + .unitId(unitId.toString()) + .parentId(rootNodeId) + .type(NodeType.FOLDER.getNodeType()) + .build(); + String folderId = + iNodeService.createNode(user.getUserId(), user.getSpaceId(), ro); + ro.setType(NodeType.DATASHEET.getNodeType()); + ro.setParentId(folderId); + String dstId = + iNodeService.createNode(user.getUserId(), user.getSpaceId(), ro); + NodeRelRo relRo = new NodeRelRo(); + relRo.setDatasheetId(dstId); + relRo.setViewId(IdUtil.createViewId()); + ro.setType(NodeType.FORM.getNodeType()); + ro.setExtra(relRo); + iNodeService.createNode(user.getUserId(), user.getSpaceId(), ro); + assertDoesNotThrow(() -> iNodeService.linkByOutsideResource(folderId)); + } + + @Test + public void testMoveOriginalDstInAndFormNotInSameFolder() { + MockUserSpace user = createUserSpaceForFreeSubscription(); + Long unitId = iUnitService.getUnitIdByRefId(user.getMemberId()); + String rootNodeId = iNodeService.getRootNodeIdBySpaceId(user.getSpaceId()); + // create node + NodeOpRo ro = new NodeOpRo().toBuilder() + .unitId(unitId.toString()) + .parentId(rootNodeId) + .type(NodeType.FOLDER.getNodeType()) + .build(); + String folderId = + iNodeService.createNode(user.getUserId(), user.getSpaceId(), ro); + ro.setType(NodeType.DATASHEET.getNodeType()); + ro.setParentId(folderId); + String dstId = + iNodeService.createNode(user.getUserId(), user.getSpaceId(), ro); + NodeRelRo relRo = new NodeRelRo(); + relRo.setDatasheetId(dstId); + relRo.setViewId(IdUtil.createViewId()); + ro.setType(NodeType.FORM.getNodeType()); + ro.setExtra(relRo); + ro.setParentId(rootNodeId); + iNodeService.createNode(user.getUserId(), user.getSpaceId(), ro); + BusinessException exception = + assertThrows(BusinessException.class, + () -> iNodeService.linkByOutsideResource(folderId)); + assertEquals(430, exception.getCode()); + } + private MockUserSpace createUserSpaceForFreeSubscription() { // mock user MockUserSpace userSpace = createSingleUserAndSpace(); diff --git a/backend-server/application/src/test/resources/file/email_action_input.json b/backend-server/application/src/test/resources/file/email_action_input.json new file mode 100644 index 0000000000..1f5581fc70 --- /dev/null +++ b/backend-server/application/src/test/resources/file/email_action_input.json @@ -0,0 +1,153 @@ +{ + "type": "Expression", + "value": { + "operands": [ + "template", + { + "type": "Literal", + "value": " " + }, + "mailServer", + { + "type": "Expression", + "value": { + "operands": [ + "domain", + { + "type": "Expression", + "value": { + "operands": [ + { + "type": "Expression", + "value": { + "operands": [ + { + "type": "Literal", + "value": "smptp.qq.com" + } + ], + "operator": "concatString" + } + } + ], + "operator": "concatParagraph" + } + }, + "port", + { + "type": "Expression", + "value": { + "operands": [ + { + "type": "Expression", + "value": { + "operands": [ + { + "type": "Literal", + "value": "456" + } + ], + "operator": "concatString" + } + } + ], + "operator": "concatParagraph" + } + } + ], + "operator": "newObject" + } + }, + "account", + { + "type": "Expression", + "value": { + "operands": [ + { + "type": "Expression", + "value": { + "operands": [ + { + "type": "Literal", + "value": "test@aa.com" + } + ], + "operator": "concatString" + } + } + ], + "operator": "concatParagraph" + } + }, + "password", + { + "type": "Literal", + "value": "123456" + }, + "to", + { + "type": "Expression", + "value": { + "operands": [ + { + "type": "Expression", + "value": { + "operands": [ + { + "type": "Literal", + "value": "test@qq.com" + } + ], + "operator": "concatString" + } + } + ], + "operator": "concatParagraph" + } + }, + "subject", + { + "type": "Expression", + "value": { + "operands": [ + { + "type": "Expression", + "value": { + "operands": [ + { + "type": "Literal", + "value": "test" + } + ], + "operator": "concatString" + } + } + ], + "operator": "concatParagraph" + } + }, + "message", + { + "type": "Expression", + "value": { + "operands": [ + { + "type": "Expression", + "value": { + "operands": [ + { + "type": "Literal", + "value": "test" + } + ], + "operator": "concatString" + } + } + ], + "operator": "concatParagraph" + } + } + ], + "operator": "newObject" + } +} \ No newline at end of file diff --git a/package.json b/package.json index dc839d342d..eb1a80cf0b 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,6 @@ "devDependencies": { "@types/jest": "^29.5.11", "eslint-config-prettier": "^8", - "git-format-staged": "^3.0.0", "@babel/core": "7.22.20", "@commitlint/cli": "17.7.1", "@commitlint/config-conventional": "17.7.0", @@ -94,6 +93,7 @@ "eslint-plugin-react": "^7.33.2", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-vika": "0.0.1", + "git-format-staged": "^3.0.0", "husky": "8.0.3", "lint-staged": "14.0.1", "lodash": "^4.17.21", @@ -103,8 +103,8 @@ "quicktype": "^23.0.75", "svgo": "^3.0.2", "ts-node": "^10.9.1", - "typescript": "4.8.2", - "ts-pnp": "1.2.0" + "ts-pnp": "1.2.0", + "typescript": "4.8.2" }, "resolutions": { "node-gyp": "9.3.1", @@ -132,6 +132,9 @@ "node-gyp": "*" } } + }, + "patchedDependencies": { + "mysql2@2.3.3": "patches/mysql2@2.3.3.patch" } }, "dependencies": { diff --git a/packages/components/src/components/calendar/calendar.tsx b/packages/components/src/components/calendar/calendar.tsx index 5db2abb172..4c37c797af 100644 --- a/packages/components/src/components/calendar/calendar.tsx +++ b/packages/components/src/components/calendar/calendar.tsx @@ -32,8 +32,9 @@ import classNames from 'classnames'; import { configResponsive, useResponsive } from 'ahooks'; import { useTouch, Direction } from '../../hooks/use-touch'; import format from 'date-fns/format'; +import { isValid } from 'date-fns'; -export const Calendar:FC> = props => { +export const Calendar: FC> = (props) => { const { defaultDate, monthPicker, ...rest } = props; configResponsive({ middle: 768, @@ -41,19 +42,16 @@ export const Calendar:FC> = props => { const responsive = useResponsive(); const isMobile = !responsive.middle; const [step, setStep] = useState(0); - const defaultDate2Month = defaultDate && format(defaultDate, FORMAT_MONTH); + const defaultDate2Month = defaultDate && isValid(defaultDate) ? format(defaultDate, FORMAT_MONTH) : ''; // Update of annual and monthly changes useEffect(() => { if (defaultDate2Month) { - const currStep = differenceInMonths(date2Month(defaultDate), date2Month(new Date())); + const currStep = differenceInMonths(date2Month(defaultDate!), date2Month(new Date())); setStep(currStep); } - // eslint-disable-next-line + // eslint-disable-next-line }, [defaultDate2Month]); - const { year, month } = useMemo(() => - getPanelData(step), - [step] - ); + const { year, month } = useMemo(() => getPanelData(step), [step]); const touch = useTouch(); const [isChangeMonth, setChangeMonth] = useState(false); @@ -74,13 +72,21 @@ export const Calendar:FC> = props => { - {isMobile ? : + {isMobile ? ( - } + ) : ( + + + + )} {monthPicker ? monthPicker(formatDate(year, month, lang)) : {formatDate(year, month, lang)}} - {isMobile ? : + {isMobile ? ( - } + ) : ( + + + + )} diff --git a/packages/datasheet/src/pc/components/preview_file/workdoc_image/expand_workdoc_image.tsx b/packages/datasheet/src/pc/components/preview_file/workdoc_image/expand_workdoc_image.tsx index 2272a33a5a..f36085f6b4 100644 --- a/packages/datasheet/src/pc/components/preview_file/workdoc_image/expand_workdoc_image.tsx +++ b/packages/datasheet/src/pc/components/preview_file/workdoc_image/expand_workdoc_image.tsx @@ -8,6 +8,7 @@ import { WorkdocImage } from './workdoc_image'; interface IExpandWorkdocImage { file: any; onDelete: () => void; + isEditable: boolean; } export const expandWorkdocImage = (props: IExpandWorkdocImage) => { diff --git a/packages/datasheet/src/pc/components/preview_file/workdoc_image/portal.tsx b/packages/datasheet/src/pc/components/preview_file/workdoc_image/portal.tsx index 63cb358f30..e3c397891d 100644 --- a/packages/datasheet/src/pc/components/preview_file/workdoc_image/portal.tsx +++ b/packages/datasheet/src/pc/components/preview_file/workdoc_image/portal.tsx @@ -19,9 +19,10 @@ export interface IWorkdocImage { onClose: () => void; file: any; onDelete: () => void; + isEditable: boolean; } -export const WorkdocImagePortal = ({ onClose, file, onDelete }: IWorkdocImage) => { +export const WorkdocImagePortal = ({ onClose, file, onDelete, isEditable }: IWorkdocImage) => { const [transformInfo, setTransformInfo] = useFrameSetState(initTransformInfo); const imgContainerRef = useRef(null); useClickAway((e) => { @@ -86,13 +87,13 @@ export const WorkdocImagePortal = ({ onClose, file, onDelete }: IWorkdocImage) = const blob = await getFile(file.token); FileSaver.saveAs(blob, file.name); }} - readonly={false} + readonly={!isEditable} onRotate={onRotate} previewEnable isDocType={false} officePreviewUrl={''} onZoom={onZoom} - disabledDownload={false} + disabledDownload={!isEditable} isFullScreen={false} /> diff --git a/packages/datasheet/src/pc/components/quick_search/search_base.tsx b/packages/datasheet/src/pc/components/quick_search/search_base.tsx index 36feef4a46..489fd07473 100644 --- a/packages/datasheet/src/pc/components/quick_search/search_base.tsx +++ b/packages/datasheet/src/pc/components/quick_search/search_base.tsx @@ -2,13 +2,13 @@ import type { InputRef } from 'antd'; import { Form } from 'antd'; import classnames from 'classnames'; import throttle from 'lodash/throttle'; +import { ShortcutActionManager, ShortcutActionName } from 'modules/shared/shortcut_key'; +import { getShortcutKeyString } from 'modules/shared/shortcut_key/keybinding_config'; import Image from 'next/image'; import * as React from 'react'; import { FC, useEffect, useRef, useState } from 'react'; import { useThemeColors, ThemeName, TextInput, Typography } from '@apitable/components'; import { Api, getArrayLoopIndex, Navigation, StoreActions, Strings, t, ConfigConstant } from '@apitable/core'; -import { ShortcutActionManager, ShortcutActionName } from 'modules/shared/shortcut_key'; -import { getShortcutKeyString } from 'modules/shared/shortcut_key/keybinding_config'; import { ScreenSize } from 'pc/components/common/component_display'; import { Router } from 'pc/components/route_manager/router'; import { useResponsive } from 'pc/hooks'; @@ -60,7 +60,7 @@ export const SearchBase: FC> = ({ classNam [ ShortcutActionName.QuickSearchEnter, () => { - jumpNode(nodeList[currentIndex + 1].nodeId); + jumpNode(nodeList[currentIndex].nodeId); }, ], [ diff --git a/packages/datasheet/src/pc/components/robot/api.ts b/packages/datasheet/src/pc/components/robot/api.ts index 7233f51867..37e5594cf4 100644 --- a/packages/datasheet/src/pc/components/robot/api.ts +++ b/packages/datasheet/src/pc/components/robot/api.ts @@ -206,7 +206,7 @@ export const getRobotTrigger = (url: string): Promise }; export const getAutomationRunHistoryDetail = (taskId: string): Promise => { - return nestReq.get(`/automation/run-history/${taskId}`).then((res) => { + return axios.get(`/automation/run-history/${taskId}`).then((res) => { const taskDetail: undefined | IRobotHistoryTask = res?.data?.data; return taskDetail; }); diff --git a/packages/datasheet/src/pc/components/robot/robot_detail/action/robot_action.tsx b/packages/datasheet/src/pc/components/robot/robot_detail/action/robot_action.tsx index 50dc63fcbf..196cd63d8d 100644 --- a/packages/datasheet/src/pc/components/robot/robot_detail/action/robot_action.tsx +++ b/packages/datasheet/src/pc/components/robot/robot_detail/action/robot_action.tsx @@ -19,7 +19,7 @@ import cx from 'classnames'; import produce from 'immer'; import { useAtom } from 'jotai'; -import { isEqual, isString } from 'lodash'; +import { identity, isEqual, isEqualWith, isNil, isString, pickBy } from 'lodash'; import * as React from 'react'; import { FC, memo, ReactNode, useCallback, useContext, useEffect, useMemo } from 'react'; import { shallowEqual, useSelector } from 'react-redux'; @@ -58,6 +58,20 @@ export interface IRobotActionProps { editType?: EditType; } +export const customizer = (objValue, othValue) => { + if (isNil(objValue) && isNil(othValue)) { + return true; + } + if(objValue === '******' || othValue === '******') { + return true; + } + const l = pickBy(objValue, identity); + const r = pickBy(othValue, identity); + if (isEqual(l, r)) { + return true; + } + return undefined; +}; export const RobotAction = memo((props: IRobotActionProps) => { const { editType, action, robotId, index = 0 } = props; const permissions = useAutomationResourcePermission(); @@ -217,7 +231,7 @@ export const RobotAction = memo((props: IRobotActionProps) => { const mapFormData = map.get(action.id!); const modified = useMemo(() => { - return mapFormData != null && !isEqual(action.input, mapFormData); + return mapFormData != null && !isEqualWith(action.input, mapFormData, customizer); }, [mapFormData, action.input]); const handleUpdate = useCallback( (e: IChangeEvent) => { diff --git a/packages/datasheet/src/pc/components/robot/robot_detail/node_form/core/components/fields/ObjectField/ObjectField.tsx b/packages/datasheet/src/pc/components/robot/robot_detail/node_form/core/components/fields/ObjectField/ObjectField.tsx index 3a1e4c2f33..20ffe2aa98 100644 --- a/packages/datasheet/src/pc/components/robot/robot_detail/node_form/core/components/fields/ObjectField/ObjectField.tsx +++ b/packages/datasheet/src/pc/components/robot/robot_detail/node_form/core/components/fields/ObjectField/ObjectField.tsx @@ -61,7 +61,7 @@ const ObjectField = (props: IFieldProps) => { type: 'Expression', value: { operator: 'newObject', - operands: objectCombOperand([...transObject.value.operands, name, value]), + operands: objectCombOperand([...(transObject.value?.operands ?? []), name, value]), }, }; // console.log('onPropertyChange', _newFormData); diff --git a/packages/datasheet/src/pc/components/robot/robot_detail/node_form/core/components/widgets/PasswordWidget.tsx b/packages/datasheet/src/pc/components/robot/robot_detail/node_form/core/components/widgets/PasswordWidget.tsx index 6855768086..a132eb9e8b 100644 --- a/packages/datasheet/src/pc/components/robot/robot_detail/node_form/core/components/widgets/PasswordWidget.tsx +++ b/packages/datasheet/src/pc/components/robot/robot_detail/node_form/core/components/widgets/PasswordWidget.tsx @@ -72,7 +72,11 @@ function PasswordWidget(props: IBaseInputProps & any) { list={schema.examples ? `examples_${inputProps.id}` : null} onChange={_onChange} onBlur={onBlur && ((event) => props.onChange(literal2Operand(event.target.value || null)))} - onFocus={onFocus && ((event) => onFocus(inputProps.id, literal2Operand(event.target.value)))} + onFocus={(event) => { + setInputValue(options?.emptyValue ?? ''); + props.onChange?.(literal2Operand('')); + onFocus && onFocus(inputProps.id, literal2Operand(event.target.value)); + }} placeholder={map2Text[placeholderKey]} type={'password'} style={{ diff --git a/packages/datasheet/src/pc/components/robot/robot_detail/robot_run_history/robot_run_history_item_detail.tsx b/packages/datasheet/src/pc/components/robot/robot_detail/robot_run_history/robot_run_history_item_detail.tsx index 0d1fc2979a..f976ccf04a 100644 --- a/packages/datasheet/src/pc/components/robot/robot_detail/robot_run_history/robot_run_history_item_detail.tsx +++ b/packages/datasheet/src/pc/components/robot/robot_detail/robot_run_history/robot_run_history_item_detail.tsx @@ -18,7 +18,7 @@ import axios from 'axios'; import useSWR from 'swr'; -import { Box, useTheme, Typography } from '@apitable/components'; +import { Box, Typography } from '@apitable/components'; import { Strings, t } from '@apitable/core'; import { useNodeTypeByIds } from '../../hooks'; import { IRobotHistoryTask } from '../../interface'; @@ -28,11 +28,11 @@ import { RobotRunHistoryNodeWrapper } from './robot_run_history_item_detail_node import { RobotRunHistoryTriggerDetail } from './robot_run_history_item_detail_trigger'; const nestReq = axios.create({ - baseURL: '/nest/v1/', + baseURL: '/api/v1/', }); interface IRobotRunHistoryItemDetailProps { - taskId: string; + taskId: string; } export const useRunTaskDetail = (taskId: string) => { @@ -91,9 +91,9 @@ export const RobotRunHistoryItemDetail = (props: IRobotRunHistoryItemDetailProps nodeDetail={nodeDetail} > {isTrigger ? ( - + ) : ( - + )} ); diff --git a/packages/datasheet/src/pc/components/robot/robot_detail/trigger/robot_trigger.tsx b/packages/datasheet/src/pc/components/robot/robot_detail/trigger/robot_trigger.tsx index 94eec37e8a..d93fecc580 100644 --- a/packages/datasheet/src/pc/components/robot/robot_detail/trigger/robot_trigger.tsx +++ b/packages/datasheet/src/pc/components/robot/robot_detail/trigger/robot_trigger.tsx @@ -116,6 +116,9 @@ export const customizer = (objValue, othValue) => { if (isNil(objValue) && isNil(othValue)) { return true; } + if(objValue === '******' || othValue === '******') { + return true; + } const l = pickBy(objValue, identity); const r = pickBy(othValue, identity); if (isEqual(l, r)) { diff --git a/packages/datasheet/src/pc/components/route_manager/router_provider.tsx b/packages/datasheet/src/pc/components/route_manager/router_provider.tsx index 7e7e5d9e65..bea41b754a 100644 --- a/packages/datasheet/src/pc/components/route_manager/router_provider.tsx +++ b/packages/datasheet/src/pc/components/route_manager/router_provider.tsx @@ -177,7 +177,7 @@ const RouterProvider = ({ children }: any) => { const [locale, setLocale] = React.useState(); const getLocale = async (_lang) => { - const _locale = await import(`antd/es/locale/${_lang}`).then(module => module.default); + const _locale = await import(`antd/es/locale/${_lang}`).then((module) => module.default); setLocale(_locale); }; @@ -185,6 +185,9 @@ const RouterProvider = ({ children }: any) => { getLocale(lang); }, [lang]); + // 由于设置语言,会导致子组件 mount 两次,useEffect 中如果有请求也会发生两次,微信登录在这种情况下会报错 + if (!locale) return null; + return ( diff --git a/packages/datasheet/src/pc/components/setting_nickname/setting_nickname.tsx b/packages/datasheet/src/pc/components/setting_nickname/setting_nickname.tsx index c630e28ef3..05b854cdfe 100644 --- a/packages/datasheet/src/pc/components/setting_nickname/setting_nickname.tsx +++ b/packages/datasheet/src/pc/components/setting_nickname/setting_nickname.tsx @@ -22,14 +22,15 @@ import * as React from 'react'; import { FC, useEffect, useState } from 'react'; import { useDispatch } from 'react-redux'; import { Button, useThemeColors } from '@apitable/components'; -import { Api, IReduxState, IUnitValue, Navigation, StoreActions, Strings, t } from '@apitable/core'; +import { Api, ConfigConstant, IReduxState, IUnitValue, Navigation, StoreActions, Strings, t } from '@apitable/core'; import { EditOutlined } from '@apitable/icons'; -import { Avatar, AvatarSize, ButtonBase, Emoji, ImageCropUpload, ISelectInfo, IUploadType, Wrapper } from 'pc/components/common'; +import { Avatar, AvatarSize, ButtonBase, ImageCropUpload, ISelectInfo, IUploadType, Wrapper } from 'pc/components/common'; import { Router } from 'pc/components/route_manager/router'; import { useRequest, useUserRequest } from 'pc/hooks'; import { useAppSelector } from 'pc/store/react-redux'; import { isLocalSite } from 'pc/utils'; import { useQuery } from '../../hooks/use_home'; +import { getNodeIcon } from '../catalog/tree/node_icon'; import { defaultAvatars } from '../navigation/account_center_modal/basic_setting/default_avatar'; import styles from './style.module.less'; @@ -228,7 +229,10 @@ const SettingNickname: FC> = () => {
{error ? error.msg : ''}
diff --git a/packages/datasheet/src/pc/components/space_manage/space_info/components/del_confirm_modal/del_confirm_modal.tsx b/packages/datasheet/src/pc/components/space_manage/space_info/components/del_confirm_modal/del_confirm_modal.tsx index 09e5007398..6090fb2f2a 100644 --- a/packages/datasheet/src/pc/components/space_manage/space_info/components/del_confirm_modal/del_confirm_modal.tsx +++ b/packages/datasheet/src/pc/components/space_manage/space_info/components/del_confirm_modal/del_confirm_modal.tsx @@ -91,6 +91,7 @@ export const DelConfirmModal: FC>
  • {t(Strings.attachment_data)}
  • {t(Strings.space_info_del_confirm2)}
    +
    {t(Strings.space_info_del_confirm3)}