From d9df1448003049caa2298eabf179f9ea52b13643 Mon Sep 17 00:00:00 2001 From: hanshuaikang <1758504262@qq.com> Date: Mon, 16 Oct 2023 16:47:28 +0800 Subject: [PATCH 1/3] =?UTF-8?q?minor:=20=E8=8D=89=E7=A8=BF=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apis/drf/serilaziers/task_template.py | 5 +- .../core/apis/drf/viewsets/task_template.py | 112 ++++++++++++++---- .../template_base/domains/template_manager.py | 76 ++++++++++-- gcloud/template_base/models.py | 44 +++++-- 4 files changed, 191 insertions(+), 46 deletions(-) diff --git a/gcloud/core/apis/drf/serilaziers/task_template.py b/gcloud/core/apis/drf/serilaziers/task_template.py index bd48492363..c53a9a645b 100644 --- a/gcloud/core/apis/drf/serilaziers/task_template.py +++ b/gcloud/core/apis/drf/serilaziers/task_template.py @@ -57,8 +57,11 @@ class TopCollectionTaskTemplateSerializer(TaskTemplateSerializer): collection_id = serializers.IntegerField(read_only=True, help_text="收藏ID") -class CreateTaskTemplateSerializer(BaseTaskTemplateSerializer): +class UpdateDraftPipelineTreeSerializer(serializers.Serializer): + pipeline_tree = serializers.JSONField(required=True) + +class CreateTaskTemplateSerializer(BaseTaskTemplateSerializer): name = serializers.CharField(help_text="流程模板名称") category = serializers.ChoiceField(choices=TASK_CATEGORY, help_text="模板分类") time_out = serializers.IntegerField(help_text="超时时间", required=False) diff --git a/gcloud/core/apis/drf/viewsets/task_template.py b/gcloud/core/apis/drf/viewsets/task_template.py index f3fe3ff153..d8cf276d84 100644 --- a/gcloud/core/apis/drf/viewsets/task_template.py +++ b/gcloud/core/apis/drf/viewsets/task_template.py @@ -13,45 +13,43 @@ import json import logging +from django.contrib.auth import get_user_model +from django.db import transaction from django.db.models import BooleanField, ExpressionWrapper, Q +from django.utils.translation import ugettext_lazy as _ +from django_filters import CharFilter from drf_yasg.utils import swagger_auto_schema +from pipeline.models import TemplateRelationship, TemplateScheme +from rest_framework import permissions, status from rest_framework.decorators import action -from rest_framework.response import Response -from rest_framework.pagination import LimitOffsetPagination from rest_framework.exceptions import ErrorDetail -from rest_framework import status, permissions -from django.contrib.auth import get_user_model -from django.db import transaction -from django_filters import CharFilter +from rest_framework.pagination import LimitOffsetPagination +from rest_framework.response import Response from gcloud import err_code -from pipeline.models import TemplateRelationship, TemplateScheme - from gcloud.contrib.collection.models import Collection -from gcloud.core.apis.drf.viewsets.base import GcloudModelViewSet -from gcloud.label.models import TemplateLabelRelation, Label -from gcloud.tasktmpl3.signals import post_template_save_commit -from gcloud.taskflow3.models import TaskTemplate, TaskConfig +from gcloud.contrib.operate_record.constants import OperateSource, OperateType, RecordType +from gcloud.contrib.operate_record.signal import operate_record_signal +from gcloud.core.apis.drf.filters import BooleanPropertyFilter +from gcloud.core.apis.drf.filtersets import PropertyFilterSet +from gcloud.core.apis.drf.permission import HAS_OBJECT_PERMISSION, IamPermission, IamPermissionInfo +from gcloud.core.apis.drf.resource_helpers import ViewSetResourceHelper from gcloud.core.apis.drf.serilaziers.task_template import ( + CreateTaskTemplateSerializer, + ProjectFilterQuerySerializer, + ProjectInfoQuerySerializer, TaskTemplateListSerializer, TaskTemplateSerializer, - CreateTaskTemplateSerializer, TopCollectionTaskTemplateSerializer, - ProjectInfoQuerySerializer, - ProjectFilterQuerySerializer, + UpdateDraftPipelineTreeSerializer, ) -from gcloud.core.apis.drf.resource_helpers import ViewSetResourceHelper -from gcloud.iam_auth import res_factory -from gcloud.iam_auth import IAMMeta +from gcloud.core.apis.drf.viewsets.base import GcloudModelViewSet +from gcloud.iam_auth import IAMMeta, res_factory +from gcloud.label.models import Label, TemplateLabelRelation +from gcloud.taskflow3.models import TaskConfig, TaskTemplate +from gcloud.tasktmpl3.signals import post_template_save_commit from gcloud.template_base.domains.template_manager import TemplateManager -from gcloud.core.apis.drf.filtersets import PropertyFilterSet -from gcloud.core.apis.drf.filters import BooleanPropertyFilter -from gcloud.contrib.operate_record.signal import operate_record_signal -from gcloud.contrib.operate_record.constants import OperateType, OperateSource, RecordType -from gcloud.core.apis.drf.permission import HAS_OBJECT_PERMISSION, IamPermission, IamPermissionInfo from gcloud.user_custom_config.constants import TASKTMPL_ORDERBY_OPTIONS -from django.utils.translation import ugettext_lazy as _ - logger = logging.getLogger("root") manager = TemplateManager(template_model_cls=TaskTemplate) @@ -68,9 +66,16 @@ class TaskTemplatePermission(IamPermission): "retrieve": IamPermissionInfo( IAMMeta.FLOW_VIEW_ACTION, res_factory.resources_for_flow_obj, HAS_OBJECT_PERMISSION ), + "draft": IamPermissionInfo(IAMMeta.FLOW_VIEW_ACTION, res_factory.resources_for_flow_obj, HAS_OBJECT_PERMISSION), "destroy": IamPermissionInfo( IAMMeta.FLOW_DELETE_ACTION, res_factory.resources_for_flow_obj, HAS_OBJECT_PERMISSION ), + "update_draft": IamPermissionInfo( + IAMMeta.FLOW_EDIT_ACTION, res_factory.resources_for_flow_obj, HAS_OBJECT_PERMISSION + ), + "publish_draft": IamPermissionInfo( + IAMMeta.FLOW_EDIT_ACTION, res_factory.resources_for_flow_obj, HAS_OBJECT_PERMISSION + ), "update": IamPermissionInfo( IAMMeta.FLOW_EDIT_ACTION, res_factory.resources_for_flow_obj, HAS_OBJECT_PERMISSION ), @@ -339,3 +344,60 @@ def common_info(self, request, *args, **kwargs): schemes = TemplateScheme.objects.filter(template=template.pipeline_template).values_list("id", "name") schemes_info = [{"id": scheme_id, "name": scheme_name} for scheme_id, scheme_name in schemes] return Response({"name": template.name, "schemes": schemes_info}) + + @swagger_auto_schema(method="GET", operation_summary="获取流程的草稿信息") + @action(methods=["GET"], detail=True) + def draft(self, request, *args, **kwargs): + """ + 获取草稿内容 + """ + task_template = self.get_object() + # 说明该模板还没有生成快照 + if task_template.draft_template_id is None: + manager.create_draft(template=task_template, editor=request.user.username) + return Response( + { + "editor": task_template.draft_template.editor, + "pipeline_tree": task_template.draft_pipeline_tree, + "edit_time": task_template.draft_template.edit_time, + } + ) + + @swagger_auto_schema(method="post", operation_summary="更新流程草稿信息", request_body=UpdateDraftPipelineTreeSerializer) + @action(methods=["POST"], detail=True) + def update_draft(self, request, *args, **kwargs): + """ + 更新草稿 + """ + task_template = self.get_object() + serializer = UpdateDraftPipelineTreeSerializer(data=request.data) + serializer.is_valid(raise_exception=True) + pipeline_tree = serializer.validated_data["pipeline_tree"] + + result = manager.update_draft_pipeline(task_template.draft_template, request.user.username, pipeline_tree) + if not result["result"]: + message = result["message"] + logger.error(message) + return Response({"detail": ErrorDetail(message, err_code.REQUEST_PARAM_INVALID.code)}, exception=True) + + return Response( + { + "editor": task_template.draft_template.editor, + "pipeline_tree": task_template.draft_pipeline_tree, + "edit_time": task_template.draft_template.edit_time, + } + ) + + @swagger_auto_schema(method="post", operation_summary="发布该流程") + @action(methods=["POST"], detail=True) + def publish_draft(self, request, *args, **kwargs): + """ + 发布草稿 + """ + task_template = self.get_object() + result = manager.publish_draft_pipeline(template=task_template, editor=request.user.username) + if not result["result"]: + message = result["message"] + logger.error(message) + return Response({"detail": ErrorDetail(message, err_code.REQUEST_PARAM_INVALID.code)}, exception=True) + return Response() diff --git a/gcloud/template_base/domains/template_manager.py b/gcloud/template_base/domains/template_manager.py index 7d9f6f6417..9ae7dfd86b 100644 --- a/gcloud/template_base/domains/template_manager.py +++ b/gcloud/template_base/domains/template_manager.py @@ -10,20 +10,23 @@ an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific lan """ +import hashlib +import json +import logging import traceback +from django.db import transaction +from django.utils.translation import ugettext_lazy as _ from pipeline.exceptions import PipelineException +from pipeline.models import PipelineTemplate, Snapshot, TemplateRelationship from gcloud.constants import TEMPLATE_NODE_NAME_MAX_LENGTH +from gcloud.template_base.models import DraftTemplate from gcloud.template_base.utils import replace_template_id from gcloud.utils.strings import standardize_name, standardize_pipeline_node_name - -from pipeline.models import PipelineTemplate, TemplateRelationship from pipeline_web.core.models import NodeInTemplate -from pipeline_web.parser.validator import validate_web_pipeline_tree from pipeline_web.parser.clean import PipelineWebTreeCleaner -from django.utils.translation import ugettext_lazy as _ -import logging +from pipeline_web.parser.validator import validate_web_pipeline_tree logger = logging.getLogger("root") @@ -32,7 +35,13 @@ class TemplateManager: def __init__(self, template_model_cls): self.template_model_cls = template_model_cls - def create_pipeline(self, name: str, creator: str, pipeline_tree: dict, description: str = "",) -> dict: + def create_pipeline( + self, + name: str, + creator: str, + pipeline_tree: dict, + description: str = "", + ) -> dict: """ 创建 pipeline 层模板 @@ -86,7 +95,12 @@ def create_pipeline(self, name: str, creator: str, pipeline_tree: dict, descript return {"result": True, "data": pipeline_template, "message": "success", "verbose_message": "success"} def create( - self, name: str, creator: str, pipeline_tree: dict, template_kwargs: dict, description: str = "", + self, + name: str, + creator: str, + pipeline_tree: dict, + template_kwargs: dict, + description: str = "", ) -> dict: """ 创建 template 层模板 @@ -203,8 +217,54 @@ def update_pipeline( return {"result": True, "data": pipeline_template, "message": "success", "verbose_message": "success"} + def create_draft(self, template, editor): + snapshot_id = Snapshot.objects.create_snapshot(template.pipeline_template.data).id + + draft_template = DraftTemplate.objects.create(editor=editor, snapshot_id=snapshot_id) + template.draft_template_id = draft_template.id + template.save(update_fields=["draft_template_id"]) + + @transaction.atomic() + def update_draft_pipeline(self, draft_template, editor, pipeline_tree): + # 草稿更新不会触发流程合法性校验 + standardize_pipeline_node_name(pipeline_tree) + replace_template_id(self.template_model_cls, pipeline_tree) + pipeline_web_tree = PipelineWebTreeCleaner(pipeline_tree) + pipeline_web_tree.clean() + + draft_template.editor = editor + draft_template.save() + + snapshot = Snapshot.objects.get(id=draft_template.snapshot_id) + h = hashlib.md5() + h.update(json.dumps(pipeline_tree).encode("utf-8")) + snapshot.md5sum = h.hexdigest() + snapshot.data = pipeline_tree + snapshot.save() + + return {"result": True, "data": None, "message": "success", "verbose_message": "success"} + + def publish_draft_pipeline(self, template, editor): + # 发布草稿 + + # 如果模板处于已发布状态, 此时应该更新 + result = self.update_pipeline( + pipeline_template=template.pipeline_template, editor=editor, pipeline_tree=template.draft_pipeline_tree + ) + if result["result"] and not template.published: + template.published = True + template.save(update_fields=["published"]) + + result["data"] = None + return result + def update( - self, template: object, editor: str, name: str = "", pipeline_tree: str = None, description: str = "", + self, + template: object, + editor: str, + name: str = "", + pipeline_tree: str = None, + description: str = "", ) -> dict: """ 更新 template 层模板 diff --git a/gcloud/template_base/models.py b/gcloud/template_base/models.py index 008e96f818..a2bd357710 100644 --- a/gcloud/template_base/models.py +++ b/gcloud/template_base/models.py @@ -18,22 +18,12 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ from pipeline.exceptions import SubprocessExpiredError -from pipeline.models import ( - PipelineTemplate, - TemplateCurrentVersion, - TemplateRelationship, -) +from pipeline.models import PipelineTemplate, Snapshot, TemplateCurrentVersion, TemplateRelationship from gcloud import err_code from gcloud.clocked_task.models import ClockedTask from gcloud.conf import settings -from gcloud.constants import ( - CLOCKED_TASK_NOT_STARTED, - COMMON, - PROJECT, - TASK_CATEGORY, - TEMPLATE_EXPORTER_VERSION, -) +from gcloud.constants import CLOCKED_TASK_NOT_STARTED, COMMON, PROJECT, TASK_CATEGORY, TEMPLATE_EXPORTER_VERSION from gcloud.core.utils import convert_readable_username from gcloud.exceptions import FlowExportError from gcloud.iam_auth.resource_creator_action.signals import batch_create @@ -300,6 +290,12 @@ def check_templates_subprocess_expired(self, tmpl_and_pipeline_id): return [tmpl_id_map[pid] for pid in subproc_expired_templ] +class DraftTemplate(models.Model): + snapshot_id = models.IntegerField(_("对应的快照id")) + editor = models.CharField(_("修改者"), max_length=32, null=True, blank=True) + edit_time = models.DateTimeField(_("修改时间"), auto_now=True, db_index=True) + + class BaseTemplate(models.Model): """ @summary: base abstract template,without containing business info @@ -309,6 +305,9 @@ class BaseTemplate(models.Model): pipeline_template = models.ForeignKey( PipelineTemplate, blank=True, null=True, on_delete=models.SET_NULL, to_field="template_id" ) + # 草稿对应的快照ID + draft_template_id = models.IntegerField(_("草稿对应的模板id"), null=True) + published = models.BooleanField(_("是否是已发布状态"), default=True) collector = models.ManyToManyField(settings.AUTH_USER_MODEL, verbose_name=_("收藏模板的人"), blank=True) notify_type = models.CharField(_("流程事件通知方式"), max_length=128, default="[]") # 形如 json.dumps({'receiver_group': ['Maintainers'], 'more_receiver': 'username1,username2'}) @@ -369,6 +368,27 @@ def pipeline_tree(self): pipeline_web_clean.to_web(nodes_attr) return tree + @property + def draft_template(self): + if self.draft_template_id: + return DraftTemplate.objects.get(id=self.draft_template_id) + + @property + def draft_pipeline_tree(self): + if self.draft_template_id is None: + return {} + + draft_template = DraftTemplate.objects.get(id=self.draft_template_id) + draft_snapshot_id = draft_template.snapshot_id + tree = Snapshot.objects.get(id=draft_snapshot_id).data + replace_template_id(self.__class__, tree, reverse=True) + # add nodes attr + pipeline_web_clean = PipelineWebTreeCleaner(tree) + nodes = NodeInTemplate.objects.filter(template_id=self.pipeline_template.template_id, version=self.version) + nodes_attr = NodeAttr.get_nodes_attr(nodes, "template") + pipeline_web_clean.to_web(nodes_attr) + return tree + @property def template_id(self): return self.id From ed102d23903b12e8315239b4ebe6bf2f8d18db0a Mon Sep 17 00:00:00 2001 From: hanshuaikang <1758504262@qq.com> Date: Fri, 3 Nov 2023 20:09:46 +0800 Subject: [PATCH 2/3] =?UTF-8?q?minor:=20=E8=8D=89=E7=A8=BF=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migrations/0016_bk_sops_202311031103.py | 18 + config/default.py | 2 + gcloud/apigw/views/create_and_start_task.py | 20 +- gcloud/apigw/views/create_clocked_task.py | 27 +- gcloud/apigw/views/create_periodic_task.py | 29 +- gcloud/apigw/views/create_task.py | 26 +- .../apigw/views/get_common_template_info.py | 10 +- .../apigw/views/get_common_template_list.py | 14 +- gcloud/apigw/views/get_template_info.py | 16 +- gcloud/apigw/views/get_template_list.py | 18 +- gcloud/apigw/views/get_template_schemes.py | 23 +- .../migrations/0009_auto_20231011_2108.py | 23 + .../apis/drf/serilaziers/common_template.py | 13 +- .../apis/drf/serilaziers/task_template.py | 14 + .../apis/drf/serilaziers/taskflow_instance.py | 4 + .../core/apis/drf/viewsets/common_template.py | 101 ++- .../core/apis/drf/viewsets/draft_template.py | 33 + .../core/apis/drf/viewsets/task_template.py | 79 +-- gcloud/iam_auth/conf.py | 5 +- gcloud/iam_auth/resource_api/mini_app.py | 15 +- .../migrations/0020_auto_20231011_2108.py | 23 + .../template_base/domains/template_manager.py | 43 +- .../migrations/0002_drafttemplate.py | 28 + gcloud/template_base/models.py | 5 +- gcloud/user_custom_config/constants.py | 84 ++- .../iam/16_add_publish_draft_actions.json | 606 ++++++++++++++++++ 26 files changed, 1108 insertions(+), 171 deletions(-) create mode 100644 bksops_iam_migrations/migrations/0016_bk_sops_202311031103.py create mode 100644 gcloud/common_template/migrations/0009_auto_20231011_2108.py create mode 100644 gcloud/core/apis/drf/viewsets/draft_template.py create mode 100644 gcloud/tasktmpl3/migrations/0020_auto_20231011_2108.py create mode 100644 gcloud/template_base/migrations/0002_drafttemplate.py create mode 100644 support-files/iam/16_add_publish_draft_actions.json diff --git a/bksops_iam_migrations/migrations/0016_bk_sops_202311031103.py b/bksops_iam_migrations/migrations/0016_bk_sops_202311031103.py new file mode 100644 index 0000000000..2b9644743f --- /dev/null +++ b/bksops_iam_migrations/migrations/0016_bk_sops_202311031103.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +# -*- coding: utf-8 -*- + +from django.db import migrations +from iam.contrib.iam_migration.migrator import IAMMigrator + + +def forward_func(apps, schema_editor): + migrator = IAMMigrator(Migration.migration_json) + migrator.migrate() + + +class Migration(migrations.Migration): + migration_json = "16_add_publish_draft_actions.json" + + dependencies = [("bksops_iam_migrations", "0015_bk_sops_202212122120")] + + operations = [migrations.RunPython(forward_func)] diff --git a/config/default.py b/config/default.py index 20386f5bb9..95aaa50a5f 100644 --- a/config/default.py +++ b/config/default.py @@ -86,6 +86,7 @@ "pipeline.engine", "pipeline.log", "pipeline.contrib.statistics", + "pipeline.contrib.rollback", "pipeline.contrib.periodic_task", "pipeline.contrib.external_plugins", "pipeline.contrib.engine_admin", @@ -676,6 +677,7 @@ def monitor_report_config(): from bk_monitor_report import MonitorReporter # noqa from bk_monitor_report.contrib.celery import MonitorReportStep # noqa + from blueapps.core.celery import celery_app # noqa reporter = MonitorReporter( diff --git a/gcloud/apigw/views/create_and_start_task.py b/gcloud/apigw/views/create_and_start_task.py index 63adeb0cf5..4dfaaf1bfd 100644 --- a/gcloud/apigw/views/create_and_start_task.py +++ b/gcloud/apigw/views/create_and_start_task.py @@ -20,22 +20,14 @@ import env from gcloud import err_code -from gcloud.apigw.decorators import ( - mark_request_whether_is_trust, - project_inject, - return_json_response, -) +from gcloud.apigw.decorators import mark_request_whether_is_trust, project_inject, return_json_response from gcloud.apigw.schemas import APIGW_CREATE_AND_START_TASK_PARAMS from gcloud.apigw.validators import CreateTaskValidator from gcloud.apigw.views.utils import logger from gcloud.common_template.models import CommonTemplate from gcloud.conf import settings from gcloud.constants import BUSINESS, COMMON, TaskCreateMethod -from gcloud.contrib.operate_record.constants import ( - OperateSource, - OperateType, - RecordType, -) +from gcloud.contrib.operate_record.constants import OperateSource, OperateType, RecordType from gcloud.contrib.operate_record.decorators import record_operation from gcloud.core.models import EngineConfig from gcloud.iam_auth.intercept import iam_intercept @@ -102,6 +94,14 @@ def create_and_start_task(request, template_id, project_id): } return result + if not tmpl.published: + result = { + "result": False, + "message": "the template is not published", + "code": err_code.CONTENT_NOT_EXIST.code, + } + return result + # 检查app_code是否存在 app_code = getattr(request.app, settings.APIGW_MANAGER_APP_CODE_KEY) if not app_code: diff --git a/gcloud/apigw/views/create_clocked_task.py b/gcloud/apigw/views/create_clocked_task.py index ab88809296..e887859e84 100644 --- a/gcloud/apigw/views/create_clocked_task.py +++ b/gcloud/apigw/views/create_clocked_task.py @@ -12,24 +12,23 @@ """ import ujson as json +from apigw_manager.apigw.decorators import apigw_require +from blueapps.account.decorators import login_exempt from django.views.decorators.csrf import csrf_exempt from django.views.decorators.http import require_POST from rest_framework.exceptions import ValidationError -from blueapps.account.decorators import login_exempt from gcloud import err_code -from gcloud.apigw.decorators import mark_request_whether_is_trust, return_json_response -from gcloud.apigw.decorators import project_inject +from gcloud.apigw.decorators import mark_request_whether_is_trust, project_inject, return_json_response +from gcloud.apigw.validators import CreateTaskValidator +from gcloud.apigw.views.utils import logger from gcloud.clocked_task.models import ClockedTask from gcloud.clocked_task.serializer import ClockedTaskSerializer from gcloud.constants import PROJECT +from gcloud.iam_auth.intercept import iam_intercept from gcloud.iam_auth.view_interceptors.apigw.create_clocked_task import CreateClockedTaskInterceptor from gcloud.tasktmpl3.models import TaskTemplate -from gcloud.apigw.views.utils import logger -from gcloud.apigw.validators import CreateTaskValidator from gcloud.utils.decorators import request_validate -from gcloud.iam_auth.intercept import iam_intercept -from apigw_manager.apigw.decorators import apigw_require @login_exempt @@ -59,7 +58,19 @@ def create_clocked_task(request, template_id, project_id): result = { "result": False, "message": "template[id={template_id}] of project[project_id={project_id} , biz_id{biz_id}] " - "does not exist".format(template_id=template_id, project_id=project.id, biz_id=project.bk_biz_id,), + "does not exist".format( + template_id=template_id, + project_id=project.id, + biz_id=project.bk_biz_id, + ), + "code": err_code.CONTENT_NOT_EXIST.code, + } + return result + + if not template.published: + result = { + "result": False, + "message": "the template is not published", "code": err_code.CONTENT_NOT_EXIST.code, } return result diff --git a/gcloud/apigw/views/create_periodic_task.py b/gcloud/apigw/views/create_periodic_task.py index 4e14fa6c9d..114b38ee15 100644 --- a/gcloud/apigw/views/create_periodic_task.py +++ b/gcloud/apigw/views/create_periodic_task.py @@ -13,29 +13,26 @@ import jsonschema import ujson as json +from apigw_manager.apigw.decorators import apigw_require +from blueapps.account.decorators import login_exempt from django.views.decorators.csrf import csrf_exempt from django.views.decorators.http import require_POST import env -from blueapps.account.decorators import login_exempt from gcloud import err_code -from gcloud.apigw.decorators import mark_request_whether_is_trust, return_json_response -from gcloud.apigw.decorators import project_inject +from gcloud.apigw.decorators import mark_request_whether_is_trust, project_inject, return_json_response from gcloud.apigw.schemas import APIGW_CREATE_PERIODIC_TASK_PARAMS +from gcloud.apigw.validators import CreatePriodicTaskValidator +from gcloud.apigw.views.utils import info_data_from_period_task, logger from gcloud.common_template.models import CommonTemplate -from gcloud.template_base.utils import replace_template_id -from gcloud.constants import PROJECT +from gcloud.constants import NON_COMMON_TEMPLATE_TYPES, PROJECT from gcloud.core.models import ProjectConfig +from gcloud.iam_auth.intercept import iam_intercept +from gcloud.iam_auth.view_interceptors.apigw import CreatePeriodicTaskInterceptor from gcloud.periodictask.models import PeriodicTask -from gcloud.constants import NON_COMMON_TEMPLATE_TYPES from gcloud.tasktmpl3.models import TaskTemplate -from gcloud.apigw.views.utils import logger, info_data_from_period_task -from gcloud.apigw.validators import CreatePriodicTaskValidator +from gcloud.template_base.utils import replace_template_id from gcloud.utils.decorators import request_validate -from gcloud.iam_auth.intercept import iam_intercept -from gcloud.iam_auth.view_interceptors.apigw import CreatePeriodicTaskInterceptor -from apigw_manager.apigw.decorators import apigw_require - from pipeline_web.preview_base import PipelineTemplateWebPreviewer @@ -93,6 +90,14 @@ def create_periodic_task(request, template_id, project_id): } return result + if not template.published: + result = { + "result": False, + "message": "the template is not published", + "code": err_code.CONTENT_NOT_EXIST.code, + } + return result + params.setdefault("constants", {}) params.setdefault("exclude_task_nodes_id", []) try: diff --git a/gcloud/apigw/views/create_task.py b/gcloud/apigw/views/create_task.py index 30335a038c..7b5e2080ab 100644 --- a/gcloud/apigw/views/create_task.py +++ b/gcloud/apigw/views/create_task.py @@ -23,32 +23,20 @@ from pipeline.exceptions import PipelineException from gcloud import err_code -from gcloud.apigw.decorators import ( - mark_request_whether_is_trust, - project_inject, - return_json_response, -) +from gcloud.apigw.decorators import mark_request_whether_is_trust, project_inject, return_json_response from gcloud.apigw.schemas import APIGW_CREATE_TASK_PARAMS from gcloud.apigw.validators import CreateTaskValidator from gcloud.apigw.views.utils import logger from gcloud.common_template.models import CommonTemplate from gcloud.conf import settings from gcloud.constants import NON_COMMON_TEMPLATE_TYPES, PROJECT, TaskCreateMethod -from gcloud.contrib.operate_record.constants import ( - OperateSource, - OperateType, - RecordType, -) +from gcloud.contrib.operate_record.constants import OperateSource, OperateType, RecordType from gcloud.contrib.operate_record.decorators import record_operation from gcloud.core.models import EngineConfig from gcloud.iam_auth.intercept import iam_intercept from gcloud.iam_auth.view_interceptors.apigw import CreateTaskInterceptor from gcloud.taskflow3.domains.auto_retry import AutoRetryNodeStrategyCreator -from gcloud.taskflow3.models import ( - TaskCallBackRecord, - TaskFlowInstance, - TimeoutNodeConfig, -) +from gcloud.taskflow3.models import TaskCallBackRecord, TaskFlowInstance, TimeoutNodeConfig from gcloud.tasktmpl3.models import TaskTemplate from gcloud.utils.decorators import request_validate from gcloud.utils.strings import standardize_pipeline_node_name @@ -128,6 +116,14 @@ def create_task(request, template_id, project_id): } return result + if not tmpl.published: + result = { + "result": False, + "message": "the template is not published", + "code": err_code.CONTENT_NOT_EXIST.code, + } + return result + app_code = getattr(request.app, settings.APIGW_MANAGER_APP_CODE_KEY) if not app_code: message = "app_code cannot be empty, make sure api gateway has sent correct params" diff --git a/gcloud/apigw/views/get_common_template_info.py b/gcloud/apigw/views/get_common_template_info.py index 50488efe47..67ddb85d99 100644 --- a/gcloud/apigw/views/get_common_template_info.py +++ b/gcloud/apigw/views/get_common_template_info.py @@ -12,16 +12,16 @@ """ +from apigw_manager.apigw.decorators import apigw_require +from blueapps.account.decorators import login_exempt from django.views.decorators.http import require_GET -from blueapps.account.decorators import login_exempt from gcloud import err_code from gcloud.apigw.decorators import mark_request_whether_is_trust, return_json_response -from gcloud.common_template.models import CommonTemplate from gcloud.apigw.views.utils import format_template_data +from gcloud.common_template.models import CommonTemplate from gcloud.iam_auth.intercept import iam_intercept from gcloud.iam_auth.view_interceptors.apigw import CommonFlowViewInterceptor -from apigw_manager.apigw.decorators import apigw_require @login_exempt @@ -32,7 +32,9 @@ @iam_intercept(CommonFlowViewInterceptor()) def get_common_template_info(request, template_id): try: - tmpl = CommonTemplate.objects.select_related("pipeline_template").get(id=template_id, is_deleted=False) + tmpl = CommonTemplate.objects.select_related("pipeline_template").get( + id=template_id, is_deleted=False, published=True + ) except CommonTemplate.DoesNotExist: result = { "result": False, diff --git a/gcloud/apigw/views/get_common_template_list.py b/gcloud/apigw/views/get_common_template_list.py index f198ba940b..80b1415052 100644 --- a/gcloud/apigw/views/get_common_template_list.py +++ b/gcloud/apigw/views/get_common_template_list.py @@ -10,16 +10,16 @@ an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ +from apigw_manager.apigw.decorators import apigw_require +from blueapps.account.decorators import login_exempt from django.views.decorators.http import require_GET -from blueapps.account.decorators import login_exempt from gcloud import err_code -from gcloud.apigw.decorators import mark_request_whether_is_trust, timezone_inject, return_json_response -from gcloud.common_template.models import CommonTemplate +from gcloud.apigw.decorators import mark_request_whether_is_trust, return_json_response, timezone_inject from gcloud.apigw.views.utils import format_template_list_data +from gcloud.common_template.models import CommonTemplate from gcloud.iam_auth.conf import COMMON_FLOW_ACTIONS from gcloud.iam_auth.utils import get_common_flow_allowed_actions_for_user -from apigw_manager.apigw.decorators import apigw_require @login_exempt @@ -29,11 +29,13 @@ @mark_request_whether_is_trust @timezone_inject def get_common_template_list(request): - templates = CommonTemplate.objects.select_related("pipeline_template").filter(is_deleted=False) + templates = CommonTemplate.objects.select_related("pipeline_template").filter(is_deleted=False, published=True) templates_data, common_templates_id_list = format_template_list_data(templates, return_id_list=True, tz=request.tz) # 注入用户有权限的actions common_templates_allowed_actions = get_common_flow_allowed_actions_for_user( - request.user.username, COMMON_FLOW_ACTIONS, common_templates_id_list, + request.user.username, + COMMON_FLOW_ACTIONS, + common_templates_id_list, ) for template in templates_data: template_id = template["id"] diff --git a/gcloud/apigw/views/get_template_info.py b/gcloud/apigw/views/get_template_info.py index d6ed13cbea..729ecbef1d 100644 --- a/gcloud/apigw/views/get_template_info.py +++ b/gcloud/apigw/views/get_template_info.py @@ -12,20 +12,18 @@ """ +from apigw_manager.apigw.decorators import apigw_require +from blueapps.account.decorators import login_exempt from django.views.decorators.http import require_GET -from blueapps.account.decorators import login_exempt from gcloud import err_code -from gcloud.apigw.decorators import mark_request_whether_is_trust, return_json_response -from gcloud.apigw.decorators import project_inject -from gcloud.common_template.models import CommonTemplate -from gcloud.constants import PROJECT -from gcloud.constants import NON_COMMON_TEMPLATE_TYPES -from gcloud.tasktmpl3.models import TaskTemplate +from gcloud.apigw.decorators import mark_request_whether_is_trust, project_inject, return_json_response from gcloud.apigw.views.utils import format_template_data +from gcloud.common_template.models import CommonTemplate +from gcloud.constants import NON_COMMON_TEMPLATE_TYPES, PROJECT from gcloud.iam_auth.intercept import iam_intercept from gcloud.iam_auth.view_interceptors.apigw import GetTemplateInfoInterceptor -from apigw_manager.apigw.decorators import apigw_require +from gcloud.tasktmpl3.models import TaskTemplate @login_exempt @@ -41,7 +39,7 @@ def get_template_info(request, template_id, project_id): if template_source in NON_COMMON_TEMPLATE_TYPES: try: tmpl = TaskTemplate.objects.select_related("pipeline_template").get( - id=template_id, project_id=project.id, is_deleted=False + id=template_id, project_id=project.id, is_deleted=False, published=True ) except TaskTemplate.DoesNotExist: result = { diff --git a/gcloud/apigw/views/get_template_list.py b/gcloud/apigw/views/get_template_list.py index 654b35028a..e6a1cd3912 100644 --- a/gcloud/apigw/views/get_template_list.py +++ b/gcloud/apigw/views/get_template_list.py @@ -10,22 +10,20 @@ an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ +from apigw_manager.apigw.decorators import apigw_require +from blueapps.account.decorators import login_exempt from django.views.decorators.http import require_GET -from blueapps.account.decorators import login_exempt from gcloud import err_code -from gcloud.apigw.decorators import mark_request_whether_is_trust, timezone_inject, return_json_response -from gcloud.apigw.decorators import project_inject +from gcloud.apigw.decorators import mark_request_whether_is_trust, project_inject, return_json_response, timezone_inject +from gcloud.apigw.views.utils import format_template_list_data, logger from gcloud.common_template.models import CommonTemplate -from gcloud.constants import PROJECT -from gcloud.constants import NON_COMMON_TEMPLATE_TYPES -from gcloud.tasktmpl3.models import TaskTemplate -from gcloud.apigw.views.utils import logger, format_template_list_data -from gcloud.iam_auth.intercept import iam_intercept +from gcloud.constants import NON_COMMON_TEMPLATE_TYPES, PROJECT from gcloud.iam_auth.conf import FLOW_ACTIONS +from gcloud.iam_auth.intercept import iam_intercept from gcloud.iam_auth.utils import get_flow_allowed_actions_for_user from gcloud.iam_auth.view_interceptors.apigw import ProjectViewInterceptor -from apigw_manager.apigw.decorators import apigw_require +from gcloud.tasktmpl3.models import TaskTemplate @login_exempt @@ -48,7 +46,7 @@ def get_template_list(request, project_id): id_in = None logger.exception("[API] id_in[{}] resolve fail, ignore.".format(id_in)) - filter_kwargs = dict(is_deleted=False) + filter_kwargs = dict(is_deleted=False, published=True) if id_in: filter_kwargs["id__in"] = id_in if name_keyword and name_keyword != "": diff --git a/gcloud/apigw/views/get_template_schemes.py b/gcloud/apigw/views/get_template_schemes.py index 45139604a8..0edf3518ec 100644 --- a/gcloud/apigw/views/get_template_schemes.py +++ b/gcloud/apigw/views/get_template_schemes.py @@ -11,18 +11,16 @@ specific language governing permissions and limitations under the License. """ - +from apigw_manager.apigw.decorators import apigw_require +from blueapps.account.decorators import login_exempt from django.views.decorators.http import require_GET +from pipeline.models import TemplateScheme -from blueapps.account.decorators import login_exempt from gcloud import err_code -from gcloud.apigw.decorators import mark_request_whether_is_trust, return_json_response -from gcloud.apigw.decorators import project_inject -from gcloud.tasktmpl3.models import TaskTemplate -from pipeline.models import TemplateScheme +from gcloud.apigw.decorators import mark_request_whether_is_trust, project_inject, return_json_response from gcloud.iam_auth.intercept import iam_intercept from gcloud.iam_auth.view_interceptors.apigw import FlowViewInterceptor -from apigw_manager.apigw.decorators import apigw_require +from gcloud.tasktmpl3.models import TaskTemplate @login_exempt @@ -33,7 +31,16 @@ @project_inject @iam_intercept(FlowViewInterceptor()) def get_template_schemes(request, project_id, template_id): - template = TaskTemplate.objects.get(project_id=request.project.id, id=template_id) + try: + template = TaskTemplate.objects.get(project_id=request.project.id, id=template_id, is_deleted=False) + except TaskTemplate.DoesNotExist: + result = { + "result": False, + "message": "template[id={template_id}] of project[project_id={project_id}] " + "does not exist".format(template_id=template_id, project_id=project_id), + "code": err_code.CONTENT_NOT_EXIST.code, + } + return result schemes = TemplateScheme.objects.filter(template__id=template.pipeline_template.id) diff --git a/gcloud/common_template/migrations/0009_auto_20231011_2108.py b/gcloud/common_template/migrations/0009_auto_20231011_2108.py new file mode 100644 index 0000000000..2510651251 --- /dev/null +++ b/gcloud/common_template/migrations/0009_auto_20231011_2108.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.13 on 2023-10-11 13:08 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("template", "0008_auto_20210319_1756"), + ] + + operations = [ + migrations.AddField( + model_name="commontemplate", + name="draft_template_id", + field=models.IntegerField(null=True, verbose_name="草稿对应的模板id"), + ), + migrations.AddField( + model_name="commontemplate", + name="published", + field=models.BooleanField(default=True, verbose_name="是否是已发布状态"), + ), + ] diff --git a/gcloud/core/apis/drf/serilaziers/common_template.py b/gcloud/core/apis/drf/serilaziers/common_template.py index 8c5ca4f705..7a3c263de8 100644 --- a/gcloud/core/apis/drf/serilaziers/common_template.py +++ b/gcloud/core/apis/drf/serilaziers/common_template.py @@ -11,10 +11,11 @@ specific language governing permissions and limitations under the License. """ import json + from rest_framework import serializers -from gcloud.constants import TASK_CATEGORY, DATETIME_FORMAT from gcloud.common_template.models import CommonTemplate +from gcloud.constants import DATETIME_FORMAT, TASK_CATEGORY from gcloud.core.apis.drf.serilaziers.template import BaseTemplateSerializer @@ -92,3 +93,13 @@ class Meta: "version", "pipeline_template", ] + + +class UpdateCommonTemplateSerializer(BaseTemplateSerializer): + category = serializers.ChoiceField(choices=TASK_CATEGORY, help_text="模板分类") + time_out = serializers.IntegerField(help_text="超时时间", required=False) + executor_proxy = serializers.CharField(help_text="执行代理", allow_blank=True, required=False) + + class Meta: + model = CommonTemplate + fields = "__all__" diff --git a/gcloud/core/apis/drf/serilaziers/task_template.py b/gcloud/core/apis/drf/serilaziers/task_template.py index c53a9a645b..3bdeb8c44e 100644 --- a/gcloud/core/apis/drf/serilaziers/task_template.py +++ b/gcloud/core/apis/drf/serilaziers/task_template.py @@ -58,6 +58,9 @@ class TopCollectionTaskTemplateSerializer(TaskTemplateSerializer): class UpdateDraftPipelineTreeSerializer(serializers.Serializer): + name = serializers.CharField(required=True) + labels = serializers.ListField(required=False) + description = serializers.CharField(required=False, allow_null=True) pipeline_tree = serializers.JSONField(required=True) @@ -97,6 +100,17 @@ class Meta: ] +class UpdateTaskTemplateSerializer(BaseTemplateSerializer): + category = serializers.ChoiceField(choices=TASK_CATEGORY, help_text="模板分类") + time_out = serializers.IntegerField(help_text="超时时间", required=False) + executor_proxy = serializers.CharField(help_text="执行代理", allow_blank=True, required=False) + default_flow_type = serializers.CharField(help_text="默认流程类型") + + class Meta: + model = TaskTemplate + fields = "__all__" + + class ProjectInfoQuerySerializer(serializers.Serializer): project_id = serializers.IntegerField(help_text="项目ID") diff --git a/gcloud/core/apis/drf/serilaziers/taskflow_instance.py b/gcloud/core/apis/drf/serilaziers/taskflow_instance.py index d5410edb63..1e3f2f2ba4 100644 --- a/gcloud/core/apis/drf/serilaziers/taskflow_instance.py +++ b/gcloud/core/apis/drf/serilaziers/taskflow_instance.py @@ -166,6 +166,10 @@ def validate_template(self, value): template = model_cls.objects.get(id=value) except model_cls.DoesNotExist: raise serializers.ValidationError(f"id={value}的模板不存在") + + # 如果该模板没有发布,则不允许创建任务 + if not template.published: + raise serializers.ValidationError(f"id={value}的模板处于草稿态,不允许发布任务") return template def validate_create_method(self, value): diff --git a/gcloud/core/apis/drf/viewsets/common_template.py b/gcloud/core/apis/drf/viewsets/common_template.py index dcbd423440..afefc1e676 100644 --- a/gcloud/core/apis/drf/viewsets/common_template.py +++ b/gcloud/core/apis/drf/viewsets/common_template.py @@ -29,30 +29,26 @@ from gcloud.common_template.models import CommonTemplate from gcloud.common_template.signals import post_template_save_commit from gcloud.contrib.collection.models import Collection -from gcloud.contrib.operate_record.constants import ( - OperateSource, - OperateType, - RecordType, -) +from gcloud.contrib.operate_record.constants import OperateSource, OperateType, RecordType from gcloud.contrib.operate_record.signal import operate_record_signal from gcloud.core.apis.drf.filters import BooleanPropertyFilter from gcloud.core.apis.drf.filtersets import PropertyFilterSet -from gcloud.core.apis.drf.permission import ( - HAS_OBJECT_PERMISSION, - IamPermission, - IamPermissionInfo, -) +from gcloud.core.apis.drf.permission import HAS_OBJECT_PERMISSION, IamPermission, IamPermissionInfo from gcloud.core.apis.drf.resource_helpers import ViewSetResourceHelper +from gcloud.core.apis.drf.serilaziers import UpdateDraftPipelineTreeSerializer from gcloud.core.apis.drf.serilaziers.common_template import ( CommonTemplateListSerializer, CommonTemplateSerializer, CreateCommonTemplateSerializer, TopCollectionCommonTemplateSerializer, + UpdateCommonTemplateSerializer, ) +from gcloud.core.apis.drf.viewsets import DraftTemplateViewSetMixin from gcloud.core.apis.drf.viewsets.base import GcloudModelViewSet from gcloud.iam_auth import IAMMeta, get_iam_client, res_factory from gcloud.taskflow3.models import TaskConfig from gcloud.template_base.domains.template_manager import TemplateManager +from gcloud.user_custom_config.constants import DEFAULT_PIPELINE_TREE logger = logging.getLogger("root") manager = TemplateManager(template_model_cls=CommonTemplate) @@ -68,6 +64,15 @@ class CommonTemplatePermission(IamPermission): "destroy": IamPermissionInfo( IAMMeta.COMMON_FLOW_DELETE_ACTION, res_factory.resources_for_common_flow_obj, HAS_OBJECT_PERMISSION ), + "draft": IamPermissionInfo( + IAMMeta.COMMON_FLOW_VIEW_ACTION, res_factory.resources_for_common_flow_obj, HAS_OBJECT_PERMISSION + ), + "update_draft": IamPermissionInfo( + IAMMeta.COMMON_FLOW_PUBLISH_DRAFT_ACTION, res_factory.resources_for_common_flow_obj, HAS_OBJECT_PERMISSION + ), + "publish_draft": IamPermissionInfo( + IAMMeta.COMMON_FLOW_EDIT_ACTION, res_factory.resources_for_common_flow_obj, HAS_OBJECT_PERMISSION + ), "update": IamPermissionInfo( IAMMeta.COMMON_FLOW_EDIT_ACTION, res_factory.resources_for_common_flow_obj, HAS_OBJECT_PERMISSION ), @@ -93,7 +98,7 @@ class Meta: property_fields = [("subprocess_has_update", BooleanPropertyFilter, ["exact"])] -class CommonTemplateViewSet(GcloudModelViewSet): +class CommonTemplateViewSet(GcloudModelViewSet, DraftTemplateViewSetMixin): queryset = CommonTemplate.objects.filter(pipeline_template__isnull=False, is_deleted=False) pagination_class = LimitOffsetPagination iam_resource_helper = ViewSetResourceHelper( @@ -189,8 +194,9 @@ def create(self, request, *args, **kwargs): message = str(e) return Response({"detail": ErrorDetail(message, err_code.REQUEST_PARAM_INVALID.code)}, exception=True) with transaction.atomic(): + # 创建一份模板,该模板不会被使用,未发布 result = manager.create_pipeline( - name=name, creator=creator, pipeline_tree=pipeline_tree, description=description + name=name, creator=creator, pipeline_tree=DEFAULT_PIPELINE_TREE, description=description ) if not result["result"]: @@ -199,7 +205,13 @@ def create(self, request, *args, **kwargs): return Response({"detail": ErrorDetail(message, err_code.REQUEST_PARAM_INVALID.code)}, exception=True) serializer.validated_data["pipeline_template"] = result["data"] + # 创建快照 + draft_template_id = manager.create_draft_without_template( + pipeline_tree, request.user.username, name, description, [] + ) + serializer.validated_data["published"] = False + serializer.validated_data["draft_template_id"] = draft_template_id self.perform_create(serializer) # 发送信号 post_template_save_commit.send(sender=CommonTemplate, template_id=serializer.instance.id, is_deleted=False) @@ -219,28 +231,11 @@ def create(self, request, *args, **kwargs): def update(self, request, *args, **kwargs): partial = kwargs.pop("partial", False) template = self.get_object() - serializer = CreateCommonTemplateSerializer(template, data=request.data, partial=partial) + serializer = UpdateCommonTemplateSerializer(template, data=request.data, partial=partial) serializer.is_valid(raise_exception=True) # update pipeline_template - name = serializer.validated_data.pop("name") editor = request.user.username - pipeline_tree = json.loads(serializer.validated_data.pop("pipeline_tree")) - description = serializer.validated_data.pop("description", "") with transaction.atomic(): - result = manager.update_pipeline( - pipeline_template=template.pipeline_template, - editor=editor, - name=name, - pipeline_tree=pipeline_tree, - description=description, - ) - - if not result["result"]: - message = result["message"] - logger.error(message) - return Response({"detail": ErrorDetail(message, err_code.REQUEST_PARAM_INVALID.code)}, exception=True) - - serializer.validated_data["pipeline_template"] = template.pipeline_template self.perform_update(serializer) # 发送信号 post_template_save_commit.send(sender=CommonTemplate, template_id=serializer.instance.id, is_deleted=False) @@ -290,3 +285,49 @@ def common_info(self, request, *args, **kwargs): schemes = TemplateScheme.objects.filter(template=template.pipeline_template).values_list("id", "name") schemes_info = [{"id": scheme_id, "name": scheme_name} for scheme_id, scheme_name in schemes] return Response({"name": template.name, "schemes": schemes_info}) + + @swagger_auto_schema(method="GET", operation_summary="获取公共流程的草稿信息") + @action(methods=["GET"], detail=True) + def draft(self, request, *args, **kwargs): + """ + 获取草稿内容 + """ + return Response(self.get_draft(manager, self.get_object(), request.user.username)) + + @swagger_auto_schema(method="post", operation_summary="更新公共流程草稿信息", request_body=UpdateDraftPipelineTreeSerializer) + @action(methods=["POST"], detail=True) + def update_draft(self, request, *args, **kwargs): + """ + 更新草稿 + """ + common_template = self.get_object() + result = self.update_template_draft(manager, common_template, request) + if not result["result"]: + message = result["message"] + logger.error(message) + return Response({"detail": ErrorDetail(message, err_code.REQUEST_PARAM_INVALID.code)}, exception=True) + + return Response( + { + "name": common_template.draft_template.name, + "labels": common_template.draft_template.labels, + "description": common_template.draft_template.description, + "editor": common_template.draft_template.editor, + "pipeline_tree": common_template.draft_pipeline_tree, + "edit_time": common_template.draft_template.edit_time, + } + ) + + @swagger_auto_schema(method="post", operation_summary="发布公共流程") + @action(methods=["POST"], detail=True) + def publish_draft(self, request, *args, **kwargs): + """ + 发布草稿 + """ + common_template = self.get_object() + result = self.publish_template_draft(manager, common_template, request.user.username) + if not result["result"]: + message = result["message"] + logger.error(message) + return Response({"detail": ErrorDetail(message, err_code.REQUEST_PARAM_INVALID.code)}, exception=True) + return Response() diff --git a/gcloud/core/apis/drf/viewsets/draft_template.py b/gcloud/core/apis/drf/viewsets/draft_template.py new file mode 100644 index 0000000000..0101387583 --- /dev/null +++ b/gcloud/core/apis/drf/viewsets/draft_template.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +import logging + +from gcloud.core.apis.drf.serilaziers import UpdateDraftPipelineTreeSerializer + +logger = logging.getLogger("root") + + +class DraftTemplateViewSetMixin: + def get_draft(self, manager, template, username): + # 如果模板draft_template_id为空,说明该模板还没有生成草稿,此时需要先创建一个草稿 + if template.draft_template_id is None: + manager.create_draft(template=template, editor=username) + return { + "name": template.draft_template.name, + "labels": template.draft_template.labels, + "description": template.draft_template.description, + "editor": template.draft_template.editor, + "pipeline_tree": template.draft_pipeline_tree, + "edit_time": template.draft_template.edit_time, + } + + def update_template_draft(self, manager, template, request): + serializer = UpdateDraftPipelineTreeSerializer(data=request.data) + serializer.is_valid(raise_exception=True) + result = manager.update_draft_pipeline( + template.draft_template, request.user.username, serializer.validated_data + ) + return result + + def publish_template_draft(self, manager, template, username): + result = manager.publish_draft_pipeline(template=template, editor=username) + return result diff --git a/gcloud/core/apis/drf/viewsets/task_template.py b/gcloud/core/apis/drf/viewsets/task_template.py index d8cf276d84..23199d7587 100644 --- a/gcloud/core/apis/drf/viewsets/task_template.py +++ b/gcloud/core/apis/drf/viewsets/task_template.py @@ -42,14 +42,16 @@ TaskTemplateSerializer, TopCollectionTaskTemplateSerializer, UpdateDraftPipelineTreeSerializer, + UpdateTaskTemplateSerializer, ) from gcloud.core.apis.drf.viewsets.base import GcloudModelViewSet +from gcloud.core.apis.drf.viewsets.draft_template import DraftTemplateViewSetMixin from gcloud.iam_auth import IAMMeta, res_factory from gcloud.label.models import Label, TemplateLabelRelation from gcloud.taskflow3.models import TaskConfig, TaskTemplate from gcloud.tasktmpl3.signals import post_template_save_commit from gcloud.template_base.domains.template_manager import TemplateManager -from gcloud.user_custom_config.constants import TASKTMPL_ORDERBY_OPTIONS +from gcloud.user_custom_config.constants import DEFAULT_PIPELINE_TREE, TASKTMPL_ORDERBY_OPTIONS logger = logging.getLogger("root") manager = TemplateManager(template_model_cls=TaskTemplate) @@ -74,7 +76,7 @@ class TaskTemplatePermission(IamPermission): IAMMeta.FLOW_EDIT_ACTION, res_factory.resources_for_flow_obj, HAS_OBJECT_PERMISSION ), "publish_draft": IamPermissionInfo( - IAMMeta.FLOW_EDIT_ACTION, res_factory.resources_for_flow_obj, HAS_OBJECT_PERMISSION + IAMMeta.FLOW_PUBLISH_DRAFT_ACTION, res_factory.resources_for_flow_obj, HAS_OBJECT_PERMISSION ), "update": IamPermissionInfo( IAMMeta.FLOW_EDIT_ACTION, res_factory.resources_for_flow_obj, HAS_OBJECT_PERMISSION @@ -104,6 +106,7 @@ class Meta: "pipeline_template__edit_time": ["gte", "lte"], "pipeline_template__create_time": ["gte", "lte"], "project__id": ["exact"], + "published": ["exact"], } property_fields = [("subprocess_has_update", BooleanPropertyFilter, ["exact"])] @@ -116,7 +119,7 @@ def filter_by_label_ids(self, query, name, value): return query.filter(**condition) -class TaskTemplateViewSet(GcloudModelViewSet): +class TaskTemplateViewSet(GcloudModelViewSet, DraftTemplateViewSetMixin): queryset = TaskTemplate.objects.filter(pipeline_template__isnull=False, is_deleted=False) pagination_class = LimitOffsetPagination filterset_class = TaskTemplateFilter @@ -213,13 +216,14 @@ def retrieve(self, request, *args, **kwargs): def create(self, request, *args, **kwargs): serializer = CreateTaskTemplateSerializer(data=request.data) serializer.is_valid(raise_exception=True) - name = serializer.validated_data.pop("name") creator = request.user.username - pipeline_tree = json.loads(serializer.validated_data.pop("pipeline_tree")) - description = serializer.validated_data.pop("description", "") with transaction.atomic(): + description = serializer.validated_data.pop("description", "") + name = serializer.validated_data.pop("name", "") + labels = serializer.validated_data.pop("template_labels", []) + # 创建一份模板,该模板不会被使用,未发布 result = manager.create_pipeline( - name=name, creator=creator, pipeline_tree=pipeline_tree, description=description + name=name, creator=creator, pipeline_tree=DEFAULT_PIPELINE_TREE, description=description ) if not result["result"]: @@ -228,14 +232,22 @@ def create(self, request, *args, **kwargs): return Response({"detail": ErrorDetail(message, err_code.REQUEST_PARAM_INVALID.code)}, exception=True) serializer.validated_data["pipeline_template_id"] = result["data"].template_id - template_labels = serializer.validated_data.pop("template_labels") + + # 创建快照 + pipeline_tree = json.loads(serializer.validated_data.pop("pipeline_tree")) + draft_template_id = manager.create_draft_without_template( + pipeline_tree, request.user.username, name, description, labels + ) + + serializer.validated_data["published"] = False + serializer.validated_data["draft_template_id"] = draft_template_id self.perform_create(serializer) - self._sync_template_lables(serializer.instance.id, template_labels) headers = self.get_success_headers(serializer.data) + # 发送信号 post_template_save_commit.send( sender=TaskTemplate, - project_id=serializer.instance.project_id, + project_id=serializer.instance.project.id, template_id=serializer.instance.id, is_deleted=False, ) @@ -255,31 +267,12 @@ def create(self, request, *args, **kwargs): def update(self, request, *args, **kwargs): partial = kwargs.pop("partial", False) template = self.get_object() - serializer = CreateTaskTemplateSerializer(template, data=request.data, partial=partial) + serializer = UpdateTaskTemplateSerializer(template, data=request.data, partial=partial) serializer.is_valid(raise_exception=True) - # update pipeline_template - name = serializer.validated_data.pop("name") + # 更新时将只允许更新流程的通知等全局信息 editor = request.user.username - pipeline_tree = json.loads(serializer.validated_data.pop("pipeline_tree")) - description = serializer.validated_data.pop("description", "") with transaction.atomic(): - result = manager.update_pipeline( - pipeline_template=template.pipeline_template, - editor=editor, - name=name, - pipeline_tree=pipeline_tree, - description=description, - ) - - if not result["result"]: - message = result["message"] - logger.error(message) - return Response({"detail": ErrorDetail(message, err_code.REQUEST_PARAM_INVALID.code)}, exception=True) - - serializer.validated_data["pipeline_template"] = template.pipeline_template - template_labels = serializer.validated_data.pop("template_labels") self.perform_update(serializer) - self._sync_template_lables(serializer.instance.id, template_labels) # 发送信号 post_template_save_commit.send( sender=TaskTemplate, @@ -351,17 +344,7 @@ def draft(self, request, *args, **kwargs): """ 获取草稿内容 """ - task_template = self.get_object() - # 说明该模板还没有生成快照 - if task_template.draft_template_id is None: - manager.create_draft(template=task_template, editor=request.user.username) - return Response( - { - "editor": task_template.draft_template.editor, - "pipeline_tree": task_template.draft_pipeline_tree, - "edit_time": task_template.draft_template.edit_time, - } - ) + return Response(self.get_draft(manager, self.get_object(), request.user.username)) @swagger_auto_schema(method="post", operation_summary="更新流程草稿信息", request_body=UpdateDraftPipelineTreeSerializer) @action(methods=["POST"], detail=True) @@ -370,11 +353,7 @@ def update_draft(self, request, *args, **kwargs): 更新草稿 """ task_template = self.get_object() - serializer = UpdateDraftPipelineTreeSerializer(data=request.data) - serializer.is_valid(raise_exception=True) - pipeline_tree = serializer.validated_data["pipeline_tree"] - - result = manager.update_draft_pipeline(task_template.draft_template, request.user.username, pipeline_tree) + result = self.update_template_draft(manager, task_template, request) if not result["result"]: message = result["message"] logger.error(message) @@ -382,6 +361,9 @@ def update_draft(self, request, *args, **kwargs): return Response( { + "name": task_template.draft_template.name, + "labels": task_template.draft_template.labels, + "description": task_template.draft_template.description, "editor": task_template.draft_template.editor, "pipeline_tree": task_template.draft_pipeline_tree, "edit_time": task_template.draft_template.edit_time, @@ -395,9 +377,10 @@ def publish_draft(self, request, *args, **kwargs): 发布草稿 """ task_template = self.get_object() - result = manager.publish_draft_pipeline(template=task_template, editor=request.user.username) + result = self.publish_template_draft(manager, task_template, request.user.username) if not result["result"]: message = result["message"] logger.error(message) return Response({"detail": ErrorDetail(message, err_code.REQUEST_PARAM_INVALID.code)}, exception=True) + self._sync_template_lables(task_template.id, task_template.draft_template.labels) return Response() diff --git a/gcloud/iam_auth/conf.py b/gcloud/iam_auth/conf.py index 05c2ce3154..7022a83b1d 100644 --- a/gcloud/iam_auth/conf.py +++ b/gcloud/iam_auth/conf.py @@ -13,7 +13,6 @@ from django.conf import settings from django.utils.translation import ugettext_lazy as _ - from iam import meta SYSTEM_ID = settings.BK_IAM_SYSTEM_ID @@ -300,6 +299,7 @@ class IAMMeta(object): FLOW_CREATE_ACTION = "flow_create" FLOW_VIEW_ACTION = "flow_view" FLOW_EDIT_ACTION = "flow_edit" + FLOW_PUBLISH_DRAFT_ACTION = "flow_publish_draft" FLOW_DELETE_ACTION = "flow_delete" FLOW_CREATE_TASK_ACTION = "flow_create_task" FLOW_CREATE_MINI_APP_ACTION = "flow_create_mini_app" @@ -317,6 +317,7 @@ class IAMMeta(object): COMMON_FLOW_CREATE_ACTION = "common_flow_create" COMMON_FLOW_VIEW_ACTION = "common_flow_view" COMMON_FLOW_EDIT_ACTION = "common_flow_edit" + COMMON_FLOW_PUBLISH_DRAFT_ACTION = "common_flow_publish_draft" COMMON_FLOW_DELETE_ACTION = "common_flow_delete" COMMON_FLOW_CREATE_PERIODIC_TASK_ACTION = "common_flow_create_periodic_task" @@ -356,6 +357,7 @@ class IAMMeta(object): IAMMeta.COMMON_FLOW_DELETE_ACTION, IAMMeta.COMMON_FLOW_VIEW_ACTION, IAMMeta.COMMON_FLOW_CREATE_ACTION, + IAMMeta.COMMON_FLOW_PUBLISH_DRAFT_ACTION, IAMMeta.COMMON_FLOW_CREATE_PERIODIC_TASK_ACTION, ] @@ -381,6 +383,7 @@ class IAMMeta(object): IAMMeta.FLOW_DELETE_ACTION, IAMMeta.FLOW_CREATE_TASK_ACTION, IAMMeta.FLOW_CREATE_MINI_APP_ACTION, + IAMMeta.FLOW_PUBLISH_DRAFT_ACTION, IAMMeta.FLOW_CREATE_PERIODIC_TASK_ACTION, IAMMeta.FLOW_CREATE_CLOCKED_TASK_ACTION, ] diff --git a/gcloud/iam_auth/resource_api/mini_app.py b/gcloud/iam_auth/resource_api/mini_app.py index e4723f5a78..6dbb2c3869 100644 --- a/gcloud/iam_auth/resource_api/mini_app.py +++ b/gcloud/iam_auth/resource_api/mini_app.py @@ -12,14 +12,13 @@ """ from django.core.cache import cache from django.db.models import Q - -from gcloud.iam_auth.conf import SEARCH_INSTANCE_CACHE_TIME from iam import PathEqDjangoQuerySetConverter from iam.contrib.django.dispatcher import InvalidPageException from iam.resource.provider import ListResult, ResourceProvider from gcloud.contrib.appmaker.models import AppMaker from gcloud.core.models import Project +from gcloud.iam_auth.conf import SEARCH_INSTANCE_CACHE_TIME def mini_app_path_value_hook(value): @@ -160,3 +159,15 @@ def list_instance_by_policy(self, filter, page, **options): ] return ListResult(results=results, count=count) + + def initiate_approval(self, username, action, resource, ticket_content, callback_data): + """ + @param username: 创建人 + @param action: 操作id + @param resource: 资源实例, 包含资源以及拓扑资源的信息和属性 + @param ticket_content: 单据内容 + @param callback_data: 回调内容 + @return: + """ + + return True diff --git a/gcloud/tasktmpl3/migrations/0020_auto_20231011_2108.py b/gcloud/tasktmpl3/migrations/0020_auto_20231011_2108.py new file mode 100644 index 0000000000..2738974f91 --- /dev/null +++ b/gcloud/tasktmpl3/migrations/0020_auto_20231011_2108.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.13 on 2023-10-11 13:08 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("tasktmpl3", "0019_tasktemplate_default_flow_type"), + ] + + operations = [ + migrations.AddField( + model_name="tasktemplate", + name="draft_template_id", + field=models.IntegerField(null=True, verbose_name="草稿对应的模板id"), + ), + migrations.AddField( + model_name="tasktemplate", + name="published", + field=models.BooleanField(default=True, verbose_name="是否是已发布状态"), + ), + ] diff --git a/gcloud/template_base/domains/template_manager.py b/gcloud/template_base/domains/template_manager.py index 9ae7dfd86b..b1a623cf6a 100644 --- a/gcloud/template_base/domains/template_manager.py +++ b/gcloud/template_base/domains/template_manager.py @@ -21,6 +21,7 @@ from pipeline.models import PipelineTemplate, Snapshot, TemplateRelationship from gcloud.constants import TEMPLATE_NODE_NAME_MAX_LENGTH +from gcloud.label.models import TemplateLabelRelation from gcloud.template_base.models import DraftTemplate from gcloud.template_base.utils import replace_template_id from gcloud.utils.strings import standardize_name, standardize_pipeline_node_name @@ -217,24 +218,52 @@ def update_pipeline( return {"result": True, "data": pipeline_template, "message": "success", "verbose_message": "success"} + def create_draft_without_template(self, pipeline_tree, editor, name, description, labels): + + snapshot_id = Snapshot.objects.create_snapshot(pipeline_tree).id + draft_template = DraftTemplate.objects.create( + editor=editor, snapshot_id=snapshot_id, name=name, description=description, labels=list(labels) + ) + + return draft_template.id + def create_draft(self, template, editor): + """ + 创建一个草稿 + @param template: 模板 + @param editor: 编辑人 + @return: + """ snapshot_id = Snapshot.objects.create_snapshot(template.pipeline_template.data).id - - draft_template = DraftTemplate.objects.create(editor=editor, snapshot_id=snapshot_id) + labels = TemplateLabelRelation.objects.filter(template_id=template.id).values_list("label_id", flat=True) + draft_template = DraftTemplate.objects.create( + editor=editor, + snapshot_id=snapshot_id, + name=template.pipeline_template.name, + description=template.pipeline_template.description, + labels=list(labels), + ) template.draft_template_id = draft_template.id template.save(update_fields=["draft_template_id"]) @transaction.atomic() - def update_draft_pipeline(self, draft_template, editor, pipeline_tree): + def update_draft_pipeline(self, draft_template, editor, data): + + pipeline_tree = data.pop("pipeline_tree") # 草稿更新不会触发流程合法性校验 standardize_pipeline_node_name(pipeline_tree) replace_template_id(self.template_model_cls, pipeline_tree) pipeline_web_tree = PipelineWebTreeCleaner(pipeline_tree) pipeline_web_tree.clean() + for key, value in data.items(): + # 更新值 + setattr(draft_template, key, value) + draft_template.editor = editor draft_template.save() + # 更新快照 snapshot = Snapshot.objects.get(id=draft_template.snapshot_id) h = hashlib.md5() h.update(json.dumps(pipeline_tree).encode("utf-8")) @@ -245,11 +274,15 @@ def update_draft_pipeline(self, draft_template, editor, pipeline_tree): return {"result": True, "data": None, "message": "success", "verbose_message": "success"} def publish_draft_pipeline(self, template, editor): - # 发布草稿 + draft_template = template.draft_template # 如果模板处于已发布状态, 此时应该更新 result = self.update_pipeline( - pipeline_template=template.pipeline_template, editor=editor, pipeline_tree=template.draft_pipeline_tree + pipeline_template=template.pipeline_template, + editor=editor, + name=draft_template.name, + description=draft_template.description, + pipeline_tree=template.draft_pipeline_tree, ) if result["result"] and not template.published: template.published = True diff --git a/gcloud/template_base/migrations/0002_drafttemplate.py b/gcloud/template_base/migrations/0002_drafttemplate.py new file mode 100644 index 0000000000..533633ff79 --- /dev/null +++ b/gcloud/template_base/migrations/0002_drafttemplate.py @@ -0,0 +1,28 @@ +# Generated by Django 3.2.13 on 2023-11-03 12:04 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("template_base", "0001_initial"), + ] + + operations = [ + migrations.CreateModel( + name="DraftTemplate", + fields=[ + ("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ( + "name", + models.CharField(db_index=True, default="default_template", max_length=128, verbose_name="模板名称"), + ), + ("snapshot_id", models.IntegerField(db_index=True, verbose_name="对应的快照id")), + ("labels", models.JSONField(default=[], verbose_name="流程的tag信息")), + ("description", models.TextField(blank=True, null=True, verbose_name="描述")), + ("editor", models.CharField(blank=True, max_length=32, null=True, verbose_name="修改者")), + ("edit_time", models.DateTimeField(auto_now=True, db_index=True, verbose_name="修改时间")), + ], + ), + ] diff --git a/gcloud/template_base/models.py b/gcloud/template_base/models.py index a2bd357710..ac92682571 100644 --- a/gcloud/template_base/models.py +++ b/gcloud/template_base/models.py @@ -291,7 +291,10 @@ def check_templates_subprocess_expired(self, tmpl_and_pipeline_id): class DraftTemplate(models.Model): - snapshot_id = models.IntegerField(_("对应的快照id")) + name = models.CharField(_("模板名称"), max_length=128, default="default_template", db_index=True) + snapshot_id = models.IntegerField(_("对应的快照id"), db_index=True) + labels = models.JSONField(_("流程的tag信息"), default=[]) + description = models.TextField(_("描述"), null=True, blank=True) editor = models.CharField(_("修改者"), max_length=32, null=True, blank=True) edit_time = models.DateTimeField(_("修改时间"), auto_now=True, db_index=True) diff --git a/gcloud/user_custom_config/constants.py b/gcloud/user_custom_config/constants.py index a6f2a6a58d..b5de7eab19 100644 --- a/gcloud/user_custom_config/constants.py +++ b/gcloud/user_custom_config/constants.py @@ -12,7 +12,6 @@ """ from django.utils.translation import ugettext_lazy as _ - TASKTMPL_ORDERBY_OPTIONS = [ {"name": _("模板ID"), "value": "id"}, {"name": _("创建时间"), "value": "pipeline_template__create_time"}, @@ -24,6 +23,89 @@ "task_template_ordering": TASKTMPL_ORDERBY_OPTIONS, } +DEFAULT_PIPELINE_TREE = { + "activities": { + "node233c44ed19f816e75f47fa28ee9e": { + "component": { + "code": "bk_display", + "data": {"bk_display_message": {"hook": False, "need_render": True, "value": ""}}, + "version": "1.0", + }, + "error_ignorable": False, + "id": "node233c44ed19f816e75f47fa28ee9e", + "incoming": ["line93675a1e1fe437772fe3a9b71812"], + "loop": None, + "name": "消息展示", + "optional": True, + "outgoing": "line8d2551a42247463bfb31bc02f80c", + "stage_name": "", + "type": "ServiceActivity", + "retryable": True, + "skippable": True, + "auto_retry": {"enable": False, "interval": 0, "times": 1}, + "timeout_config": {"enable": False, "seconds": 10, "action": "forced_fail"}, + "labels": [], + } + }, + "constants": {}, + "end_event": { + "id": "noded8e797885448a9917a7ede435020", + "incoming": ["line8d2551a42247463bfb31bc02f80c"], + "name": "", + "outgoing": "", + "type": "EmptyEndEvent", + }, + "flows": { + "line93675a1e1fe437772fe3a9b71812": { + "id": "line93675a1e1fe437772fe3a9b71812", + "is_default": False, + "source": "noded9effddb1a805561b925afdfc755", + "target": "node233c44ed19f816e75f47fa28ee9e", + }, + "line8d2551a42247463bfb31bc02f80c": { + "id": "line8d2551a42247463bfb31bc02f80c", + "is_default": False, + "source": "node233c44ed19f816e75f47fa28ee9e", + "target": "noded8e797885448a9917a7ede435020", + }, + }, + "gateways": {}, + "line": [ + { + "id": "line93675a1e1fe437772fe3a9b71812", + "source": {"arrow": "Right", "id": "noded9effddb1a805561b925afdfc755"}, + "target": {"arrow": "Left", "id": "node233c44ed19f816e75f47fa28ee9e"}, + }, + { + "id": "line8d2551a42247463bfb31bc02f80c", + "source": {"arrow": "Right", "id": "node233c44ed19f816e75f47fa28ee9e"}, + "target": {"arrow": "Left", "id": "noded8e797885448a9917a7ede435020"}, + }, + ], + "location": [ + {"id": "noded9effddb1a805561b925afdfc755", "type": "startpoint", "x": 40, "y": 150}, + { + "id": "node233c44ed19f816e75f47fa28ee9e", + "type": "tasknode", + "name": "消息展示", + "stage_name": "", + "x": 240, + "y": 140, + "group": "蓝鲸服务(BK)", + "icon": "", + }, + {"id": "noded8e797885448a9917a7ede435020", "type": "endpoint", "x": 540, "y": 150}, + ], + "outputs": [], + "start_event": { + "id": "noded9effddb1a805561b925afdfc755", + "incoming": "", + "name": "", + "outgoing": "line93675a1e1fe437772fe3a9b71812", + "type": "EmptyStartEvent", + }, +} + def get_options_by_fileds(configs=None): data = {} diff --git a/support-files/iam/16_add_publish_draft_actions.json b/support-files/iam/16_add_publish_draft_actions.json new file mode 100644 index 0000000000..e6d77765da --- /dev/null +++ b/support-files/iam/16_add_publish_draft_actions.json @@ -0,0 +1,606 @@ +{ + "system_id": "bk_sops", + "operations": [ + { + "operation": "upsert_action", + "data": { + "id": "flow_publish_draft", + "name": "流程发布草稿", + "name_en": "Flow Draft Publish", + "description": "", + "description_en": "", + "type": "edit", + "related_resource_types": [ + { + "system_id": "bk_sops", + "id": "flow", + "related_instance_selections": [ + { + "system_id": "bk_sops", + "id": "flow" + } + ] + } + ], + "version": 1 + } + }, + { + "operation": "upsert_action", + "data": { + "id": "common_flow_publish_draft", + "name": "公共流程发布草稿", + "name_en": "Common Flow Draft Publish", + "description": "", + "description_en": "", + "type": "edit", + "related_resource_types": [ + { + "system_id": "bk_sops", + "id": "common_flow", + "related_instance_selections": [ + { + "system_id": "bk_sops", + "id": "common_flow" + } + ] + } + ], + "version": 1 + } + }, + { + "operation": "upsert_resource_creator_actions", + "data": { + "config": [ + { + "id": "project", + "actions": [ + { + "id": "project_fast_create_task", + "required": false + }, + { + "id": "flow_create", + "required": false + }, + { + "id": "project_edit", + "required": false + }, + { + "id": "project_view", + "required": false + } + ], + "sub_resource_types": [ + { + "id": "flow", + "actions": [ + { + "id": "flow_create_periodic_task", + "required": false + }, + { + "id": "flow_create_clocked_task", + "required": false + }, + { + "id": "flow_create_mini_app", + "required": false + }, + { + "id": "flow_create_task", + "required": false + }, + { + "id": "flow_publish_draft", + "required": false + }, + { + "id": "flow_delete", + "required": false + }, + { + "id": "flow_edit", + "required": false + }, + { + "id": "flow_view", + "required": false + } + ] + }, + { + "id": "mini_app", + "actions": [ + { + "id": "mini_app_create_task", + "required": false + }, + { + "id": "mini_app_delete", + "required": false + }, + { + "id": "mini_app_edit", + "required": false + }, + { + "id": "mini_app_view", + "required": false + } + ] + }, + { + "id": "periodic_task", + "actions": [ + { + "id": "periodic_task_view", + "required": false + }, + { + "id": "periodic_task_edit", + "required": false + }, + { + "id": "periodic_task_delete", + "required": false + } + ] + }, + { + "id": "clocked_task", + "actions": [ + { + "id": "clocked_task_view", + "required": false + }, + { + "id": "clocked_task_edit", + "required": false + }, + { + "id": "clocked_task_delete", + "required": false + } + ] + }, + { + "id": "task", + "actions": [ + { + "id": "task_view", + "required": false + }, + { + "id": "task_edit", + "required": false + }, + { + "id": "task_operate", + "required": false + }, + { + "id": "task_claim", + "required": false + }, + { + "id": "task_delete", + "required": false + }, + { + "id": "task_clone", + "required": false + } + ] + } + ] + }, + { + "id": "common_flow", + "actions": [ + { + "id": "common_flow_view", + "required": false + }, + { + "id": "common_flow_edit", + "required": false + }, + { + "id": "common_flow_delete", + "required": false + }, + { + "id": "common_flow_publish_draft", + "required": false + } + ] + } + ] + } + }, + { + "operation": "upsert_common_actions", + "data": [ + { + "name": "使用流程创建任务", + "name_en": "Create task by flow", + "actions": [ + { + "id": "project_view" + }, + { + "id": "flow_view" + }, + { + "id": "flow_create_task" + } + ] + }, + { + "name": "使用轻应用", + "name_en": "Use mini app", + "actions": [ + { + "id": "project_view" + }, + { + "id": "flow_view" + }, + { + "id": "mini_app_view" + }, + { + "id": "mini_app_create_task" + } + ] + }, + { + "name": "使用公共流程创建任务", + "name_en": "Create task by common flow", + "actions": [ + { + "id": "project_view" + }, + { + "id": "common_flow_view" + }, + { + "id": "common_flow_create_task" + } + ] + }, + { + "name": "业务运维", + "name_en": "Operator", + "actions": [ + { + "id": "project_view" + }, + { + "id": "project_edit" + }, + { + "id": "flow_create" + }, + { + "id": "flow_view" + }, + { + "id": "flow_edit" + }, + { + "id": "flow_publish_draft" + }, + { + "id": "flow_delete" + }, + { + "id": "flow_create_task" + }, + { + "id": "flow_create_mini_app" + }, + { + "id": "flow_create_periodic_task" + }, + { + "id": "flow_create_clocked_task" + }, + { + "id": "common_flow_view" + }, + { + "id": "common_flow_publish_draft" + }, + { + "id": "common_flow_create_task" + }, + { + "id": "task_view" + }, + { + "id": "task_edit" + }, + { + "id": "task_operate" + }, + { + "id": "task_delete" + }, + { + "id": "task_clone" + }, + { + "id": "mini_app_edit" + }, + { + "id": "mini_app_view" + }, + { + "id": "mini_app_delete" + }, + { + "id": "mini_app_create_task" + }, + { + "id": "periodic_task_view" + }, + { + "id": "periodic_task_edit" + }, + { + "id": "periodic_task_delete" + }, + { + "id": "clocked_task_view" + }, + { + "id": "clocked_task_edit" + }, + { + "id": "clocked_task_delete" + } + ] + }, + { + "name": "管理员权限", + "name_en": "Admin Permission", + "actions": [ + { + "id": "admin_view" + }, + { + "id": "admin_edit" + }, + { + "id": "audit_view" + }, + { + "id": "statistics_view" + }, + { + "id": "project_create" + } + ] + }, + { + "name": "业务只读", + "name_en": "Only Read", + "actions": [ + { + "id": "project_view" + }, + { + "id": "flow_view" + }, + { + "id": "task_view" + }, + { + "id": "mini_app_view" + } + ] + } + ] + }, + { + "operation": "upsert_action_groups", + "data": [ + { + "name": "项目", + "name_en": "Project", + "actions": [ + { + "id": "project_create" + }, + { + "id": "project_view" + }, + { + "id": "project_edit" + }, + { + "id": "project_fast_create_task" + } + ], + "sub_groups": [ + { + "name": "流程模板", + "name_en": "Flow", + "actions": [ + { + "id": "flow_create" + }, + { + "id": "flow_view" + }, + { + "id": "flow_edit" + }, + { + "id": "flow_delete" + }, + { + "id": "flow_create_task" + }, + { + "id": "flow_publish_draft" + }, + { + "id": "flow_create_mini_app" + }, + { + "id": "flow_create_periodic_task" + }, + { + "id": "flow_create_clocked_task" + } + ] + }, + { + "name": "任务实例", + "name_en": "Task", + "actions": [ + { + "id": "task_view" + }, + { + "id": "task_operate" + }, + { + "id": "task_edit" + }, + { + "id": "task_claim" + }, + { + "id": "task_delete" + }, + { + "id": "task_clone" + } + ] + }, + { + "name": "轻应用", + "name_en": "Mini App", + "actions": [ + { + "id": "mini_app_view" + }, + { + "id": "mini_app_edit" + }, + { + "id": "mini_app_delete" + }, + { + "id": "mini_app_create_task" + } + ] + }, + { + "name": "周期任务", + "name_en": "Periodic Task", + "actions": [ + { + "id": "periodic_task_view" + }, + { + "id": "periodic_task_edit" + }, + { + "id": "periodic_task_delete" + } + ] + }, + { + "name": "计划任务", + "name_en": "Clocked Task", + "actions": [ + { + "id": "clocked_task_view" + }, + { + "id": "clocked_task_edit" + }, + { + "id": "clocked_task_delete" + } + ] + } + ] + }, + { + "name": "公共流程", + "name_en": "Common Flow", + "actions": [ + { + "id": "common_flow_create" + }, + { + "id": "common_flow_view" + }, + { + "id": "common_flow_edit" + }, + { + "id": "common_flow_delete" + }, + { + "id": "common_flow_create_task" + }, + { + "id": "common_flow_publish_draft" + }, + { + "id": "common_flow_create_periodic_task" + } + ] + }, + { + "name": "管理员", + "name_en": "Admin", + "actions": [ + { + "id": "admin_view" + }, + { + "id": "admin_edit" + } + ] + }, + { + "name": "审计", + "name_en": "Audit", + "actions": [ + { + "id": "audit_view" + } + ] + }, + { + "name": "职能化", + "name_en": "function", + "actions": [ + { + "id": "function_view" + } + ] + }, + { + "name": "数据统计", + "name_en": "Statistics", + "actions": [ + { + "id": "statistics_view" + } + ] + } + ] + } + ] +} \ No newline at end of file From 58a4624fed2a7a57a05d6faff57dec7e0894ef73 Mon Sep 17 00:00:00 2001 From: hanshuaikang <1758504262@qq.com> Date: Mon, 6 Nov 2023 16:26:39 +0800 Subject: [PATCH 3/3] =?UTF-8?q?minor:=20=E5=8E=BB=E9=99=A4=E6=97=A0?= =?UTF-8?q?=E5=85=B3=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/default.py | 1 - 1 file changed, 1 deletion(-) diff --git a/config/default.py b/config/default.py index 95aaa50a5f..edd99e6bca 100644 --- a/config/default.py +++ b/config/default.py @@ -86,7 +86,6 @@ "pipeline.engine", "pipeline.log", "pipeline.contrib.statistics", - "pipeline.contrib.rollback", "pipeline.contrib.periodic_task", "pipeline.contrib.external_plugins", "pipeline.contrib.engine_admin",