From 67eec350ec6b8b89a7f86ac658d36c7dc83799c1 Mon Sep 17 00:00:00 2001 From: iSecloud <869820505@qq.com> Date: Mon, 21 Oct 2024 21:19:53 +0800 Subject: [PATCH] fix: bugs --- dbm-ui/backend/ticket/constants.py | 7 ++ dbm-ui/backend/ticket/filters.py | 31 ++++++- dbm-ui/backend/ticket/handler.py | 49 ++++++++++- dbm-ui/backend/ticket/serializers.py | 22 +++-- dbm-ui/backend/ticket/todos/__init__.py | 4 +- dbm-ui/backend/ticket/todos/pause_todo.py | 14 +--- dbm-ui/backend/ticket/todos/pipeline_todo.py | 9 +-- dbm-ui/backend/ticket/views.py | 85 ++++++-------------- 8 files changed, 127 insertions(+), 94 deletions(-) diff --git a/dbm-ui/backend/ticket/constants.py b/dbm-ui/backend/ticket/constants.py index 7c29046c2b..901579c4a8 100644 --- a/dbm-ui/backend/ticket/constants.py +++ b/dbm-ui/backend/ticket/constants.py @@ -100,6 +100,13 @@ class TicketStatus(str, StructuredEnum): TicketStatus.RUNNING, TicketStatus.TIMER, ] +TICKET_TODO_STATUS = [ + TicketStatus.APPROVE, + TicketStatus.TODO, + TicketStatus.RESOURCE_REPLENISH, + TicketStatus.FAILED, + TicketStatus.RUNNING, +] TICKET_FAILED_STATUS = [TicketStatus.REVOKED, TicketStatus.TERMINATED, TicketStatus.FAILED] diff --git a/dbm-ui/backend/ticket/filters.py b/dbm-ui/backend/ticket/filters.py index 4da4f00e72..939da4bd57 100644 --- a/dbm-ui/backend/ticket/filters.py +++ b/dbm-ui/backend/ticket/filters.py @@ -14,7 +14,7 @@ from backend.db_meta.models import Cluster from backend.ticket.constants import TODO_RUNNING_STATUS -from backend.ticket.models import ClusterOperateRecord, Ticket +from backend.ticket.models import ClusterOperateRecord, InstanceOperateRecord, Ticket class TicketListFilter(filters.FilterSet): @@ -51,3 +51,32 @@ def filter_todo(self, queryset, name, value): ) else: return queryset.filter(todo_of_ticket__done_by=user) + + +class OpRecordListFilter(filters.FilterSet): + start_time = filters.DateTimeFilter(field_name="create_at", lookup_expr="gte", label=_("开始时间")) + end_time = filters.DateTimeFilter(field_name="create_at", lookup_expr="lte", label=_("开始时间")) + op_type = filters.CharFilter(field_name="op_type", method="filter_op_type", label=_("操作类型")) + op_status = filters.CharFilter(field_name="op_status", method="filter_op_status", label=_("操作状态")) + + def filter_op_type(self, queryset, name, value): + return queryset.filter(ticket__ticket_type=value) + + def filter_op_status(self, queryset, name, value): + return queryset.filter(ticket__status=value) + + +class ClusterOpRecordListFilter(OpRecordListFilter): + cluster_id = filters.NumberFilter(field_name="cluster_id", lookup_expr="exact", label=_("集群ID")) + + class Meta: + model = ClusterOperateRecord + fields = ["start_time", "end_time", "op_type", "op_status"] + + +class InstanceOpRecordListFilter(OpRecordListFilter): + instance_id = filters.NumberFilter(field_name="instance_id", lookup_expr="exact", label=_("实例ID")) + + class Meta: + model = InstanceOperateRecord + fields = ["start_time", "end_time", "op_type", "op_status"] diff --git a/dbm-ui/backend/ticket/handler.py b/dbm-ui/backend/ticket/handler.py index b1e6190f1a..9700fd653c 100644 --- a/dbm-ui/backend/ticket/handler.py +++ b/dbm-ui/backend/ticket/handler.py @@ -31,17 +31,21 @@ FLOW_FINISHED_STATUS, ITSM_FIELD_NAME__ITSM_KEY, RUNNING_FLOW__TICKET_STATUS, + FlowType, FlowTypeConfig, OperateNodeActionType, TicketFlowStatus, TicketStatus, TicketType, + TodoStatus, + TodoType, ) from backend.ticket.exceptions import TicketFlowsConfigException from backend.ticket.flow_manager.manager import TicketFlowManager from backend.ticket.models import Flow, Ticket, TicketFlowsConfig, Todo from backend.ticket.serializers import TodoSerializer -from backend.ticket.todos import TodoActorFactory +from backend.ticket.todos import BaseTodoContext, TodoActorFactory +from backend.ticket.todos.itsm_todo import ItsmTodoContext logger = logging.getLogger("root") @@ -425,10 +429,10 @@ def ticket_status_standardization(cls): """ 旧单据状态标准化。TODO: 迁移后此段代码可删除 """ - # 标准化只针对running的单据,其他状态单据不影响 - running_tickets = Ticket.objects.filter(status=TicketStatus.RUNNING) batch = 50 + # 标准化只针对running的单据,其他状态单据不影响 + running_tickets = Ticket.objects.filter(status=TicketStatus.RUNNING) for current in range(0, running_tickets.count(), batch): for ticket in running_tickets[current : current + batch]: raw_status = ticket.status @@ -436,3 +440,42 @@ def ticket_status_standardization(cls): ticket.save() print(f"ticket[{ticket.id}] status {raw_status} ---> {ticket.status}") time.sleep(1) + + # 失败的单据要增加一条todo关联 + failed_tickets = Ticket.objects.prefetch_related("flows").filter(status=TicketStatus.FAILED) + for current in range(0, running_tickets.count(), batch): + for ticket in failed_tickets[current : current + batch]: + inner_flow = ticket.flows.filter(flow_type=FlowType.INNER_FLOW, status=TicketFlowStatus.FAILED).first() + if not inner_flow or inner_flow.todo_of_flow.exists(): + continue + Todo.objects.create( + name=_("【{}】单据任务执行失败,待处理").format(ticket.get_ticket_type_display()), + flow=inner_flow, + ticket=ticket, + type=TodoType.INNER_FAILED, + operators=[ticket.creator], + context=BaseTodoContext(inner_flow.id, ticket.id).to_dict(), + status=TodoStatus.TODO, + ) + print(f"ticket[{ticket.id}] add a failed todo") + time.sleep(1) + + # 待审批的单据要增加一条todo关联 + itsm_tickets = Ticket.objects.prefetch_related("flows").filter(status=TicketStatus.FAILED) + for current in range(0, running_tickets.count(), batch): + for ticket in itsm_tickets[current : current + batch]: + itsm_flow = ticket.flows.filter(flow_type=FlowType.BK_ITSM, status=TicketFlowStatus.RUNNING).first() + if not itsm_flow or itsm_flow.todo_of_flow.exists(): + continue + itsm_fields = {f["key"]: f["value"] for f in itsm_flow.details["fields"]} + operators = itsm_fields["approver"].split(",") + Todo.objects.create( + name=_("【{}】单据等待审批").format(ticket.get_ticket_type_display()), + flow=itsm_flow, + ticket=ticket, + type=TodoType.ITSM, + operators=operators, + context=ItsmTodoContext(itsm_flow.id, ticket.id).to_dict(), + ) + print(f"ticket[{ticket.id}] add a itsm todo") + time.sleep(1) diff --git a/dbm-ui/backend/ticket/serializers.py b/dbm-ui/backend/ticket/serializers.py index 1c0f229ad2..ffcc5374bc 100644 --- a/dbm-ui/backend/ticket/serializers.py +++ b/dbm-ui/backend/ticket/serializers.py @@ -274,20 +274,28 @@ class GetTodosSLZ(serializers.Serializer): ) -class ClusterModifyOpSerializer(serializers.Serializer): - cluster_id = serializers.IntegerField(help_text=_("集群ID")) +class OpRecordSerializer(serializers.Serializer): start_time = serializers.DateTimeField(help_text=_("查询起始时间"), required=False) end_time = serializers.DateTimeField(help_text=_("查询终止时间"), required=False) op_type = serializers.ChoiceField(help_text=_("操作类型"), choices=TicketType.get_choices(), required=False) op_status = serializers.ChoiceField(help_text=_("操作状态"), choices=TicketStatus.get_choices(), required=False) + def to_representation(self, instance): + return { + "create_at": instance.create_at, + "op_type": instance.ticket.ticket_type, + "op_status": instance.ticket.status, + "ticket_id": instance.ticket.id, + "creator": instance.creator, + } + + +class ClusterModifyOpSerializer(OpRecordSerializer): + cluster_id = serializers.IntegerField(help_text=_("集群ID")) + -class InstanceModifyOpSerializer(serializers.Serializer): +class InstanceModifyOpSerializer(OpRecordSerializer): instance_id = serializers.IntegerField(help_text=_("实例ID")) - start_time = serializers.DateTimeField(help_text=_("查询起始时间"), required=False) - end_time = serializers.DateTimeField(help_text=_("查询终止时间"), required=False) - op_type = serializers.ChoiceField(help_text=_("操作类型"), choices=TicketType.get_choices(), required=False) - op_status = serializers.ChoiceField(help_text=_("操作状态"), choices=TicketStatus.get_choices(), required=False) class QueryTicketFlowDescribeSerializer(serializers.Serializer): diff --git a/dbm-ui/backend/ticket/todos/__init__.py b/dbm-ui/backend/ticket/todos/__init__.py index ab1430c15c..6736d63332 100644 --- a/dbm-ui/backend/ticket/todos/__init__.py +++ b/dbm-ui/backend/ticket/todos/__init__.py @@ -14,6 +14,7 @@ from dataclasses import asdict, dataclass from typing import Callable +from blueapps.account.models import User from django.utils.translation import ugettext_lazy as _ from backend.constants import DEFAULT_SYSTEM_USER @@ -40,7 +41,8 @@ def name(cls): return cls.__name__ def process(self, username, action, params): - if username not in self.todo.operators and username != DEFAULT_SYSTEM_USER: + is_superuser = User.objects.get(username=username).is_superuser + if not is_superuser and username not in self.todo.operators and username != DEFAULT_SYSTEM_USER: raise TodoWrongOperatorException(_("{}不在处理人: {}中,无法处理").format(username, self.todo.operators)) self._process(username, action, params) diff --git a/dbm-ui/backend/ticket/todos/pause_todo.py b/dbm-ui/backend/ticket/todos/pause_todo.py index 10e8c412a8..9b44d8275a 100644 --- a/dbm-ui/backend/ticket/todos/pause_todo.py +++ b/dbm-ui/backend/ticket/todos/pause_todo.py @@ -10,13 +10,9 @@ """ from dataclasses import dataclass -from django.utils.translation import gettext as _ - -from backend.constants import DEFAULT_SYSTEM_USER from backend.db_meta.models.sqlserver_dts import DtsStatus, SqlserverDtsInfo from backend.ticket import todos from backend.ticket.constants import TicketFlowStatus, TicketType, TodoType -from backend.ticket.exceptions import TodoWrongOperatorException from backend.ticket.flow_manager import manager from backend.ticket.flow_manager.manager import TicketFlowManager from backend.ticket.todos import ActionType, BaseTodoContext @@ -37,11 +33,8 @@ class ResourceReplenishTodoContext(BaseTodoContext): class PauseTodo(todos.TodoActor): """来自主流程的待办""" - def process(self, username, action, params): + def _process(self, username, action, params): """确认/终止""" - if username not in self.todo.operators and username != DEFAULT_SYSTEM_USER: - raise TodoWrongOperatorException(_("{}不在处理人: {}中,无法处理").format(username, self.todo.operators)) - if action == ActionType.TERMINATE: self.todo.set_terminated(username, action) return @@ -63,11 +56,8 @@ def process(self, username, action, params): class ResourceReplenishTodo(todos.TodoActor): """资源补货的代办""" - def process(self, username, action, params): + def _process(self, username, action, params): """确认/终止""" - if username not in self.todo.operators and username != DEFAULT_SYSTEM_USER: - raise TodoWrongOperatorException(_("{}不在处理人: {}中,无法处理").format(username, self.todo.operators)) - # 终止单据 if action == ActionType.TERMINATE: self.todo.set_terminated(username, action) diff --git a/dbm-ui/backend/ticket/todos/pipeline_todo.py b/dbm-ui/backend/ticket/todos/pipeline_todo.py index 6139717998..72ce490e04 100644 --- a/dbm-ui/backend/ticket/todos/pipeline_todo.py +++ b/dbm-ui/backend/ticket/todos/pipeline_todo.py @@ -11,13 +11,9 @@ import logging from dataclasses import dataclass -from django.utils.translation import gettext as _ - -from backend.constants import DEFAULT_SYSTEM_USER from backend.flow.engine.bamboo.engine import BambooEngine from backend.ticket import todos from backend.ticket.constants import TodoType -from backend.ticket.exceptions import TodoWrongOperatorException from backend.ticket.models import TodoHistory from backend.ticket.todos import ActionType, BaseTodoContext @@ -34,11 +30,8 @@ class PipelineTodoContext(BaseTodoContext): class PipelineTodo(todos.TodoActor): """来自自动化流程中的待办""" - def process(self, username, action, params): + def _process(self, username, action, params): """确认/终止""" - if username not in self.todo.operators and username != DEFAULT_SYSTEM_USER: - raise TodoWrongOperatorException(_("{}不在处理人: {}中,无法处理").format(username, self.todo.operators)) - # 从todo的上下文获取pipeline节点信息 root_id, node_id = self.context.get("root_id"), self.context.get("node_id") engine = BambooEngine(root_id=root_id) diff --git a/dbm-ui/backend/ticket/views.py b/dbm-ui/backend/ticket/views.py index 59a457ca1a..83ca427794 100644 --- a/dbm-ui/backend/ticket/views.py +++ b/dbm-ui/backend/ticket/views.py @@ -41,15 +41,15 @@ from backend.ticket.builders.common.base import InfluxdbTicketFlowBuilderPatchMixin, fetch_cluster_ids from backend.ticket.constants import ( TICKET_RUNNING_STATUS, + TICKET_TODO_STATUS, TODO_RUNNING_STATUS, CountType, - TicketStatus, TicketType, TodoType, ) from backend.ticket.contexts import TicketContext from backend.ticket.exceptions import TicketDuplicationException -from backend.ticket.filters import TicketListFilter +from backend.ticket.filters import ClusterOpRecordListFilter, InstanceOpRecordListFilter, TicketListFilter from backend.ticket.flow_manager.manager import TicketFlowManager from backend.ticket.handler import TicketHandler from backend.ticket.models import ClusterOperateRecord, InstanceOperateRecord, Ticket, TicketFlowsConfig @@ -428,10 +428,9 @@ def get_tickets_count(self, request, *args, **kwargs): # 我的申请 count_map[CountType.MY_APPROVE] = tickets.filter(creator=user).count() # 我的代办 - todo_status = [TicketStatus.APPROVE, TicketStatus.TODO, TicketStatus.RESOURCE_REPLENISH, TicketStatus.FAILED] my_todo = ( tickets.filter( - status__in=todo_status, + status__in=TICKET_TODO_STATUS, todo_of_ticket__operators__contains=user, todo_of_ticket__status__in=TODO_RUNNING_STATUS, ) @@ -448,73 +447,35 @@ def get_tickets_count(self, request, *args, **kwargs): @common_swagger_auto_schema( operation_summary=_("查询集群变更单据事件"), - query_serializer=ClusterModifyOpSerializer(), tags=[TICKET_TAG], ) - @action(methods=["GET"], detail=False, serializer_class=ClusterModifyOpSerializer) + @action( + methods=["GET"], + detail=False, + serializer_class=ClusterModifyOpSerializer, + queryset=ClusterOperateRecord.objects.select_related("ticket").order_by("-create_at"), + filter_class=ClusterOpRecordListFilter, + ) def get_cluster_operate_records(self, request, *args, **kwargs): - validated_data = self.params_validate(self.get_serializer_class()) - op_filters = Q(cluster_id=validated_data["cluster_id"]) - if validated_data.get("start_time"): - op_filters &= Q(create_at__gte=validated_data.get("start_time")) - - if validated_data.get("end_time"): - op_filters &= Q(create_at__lte=validated_data.get("end_time")) - - if validated_data.get("op_type"): - op_filters &= Q(ticket__ticket_type=validated_data.get("op_type")) - - if validated_data.get("op_status"): - op_filters &= Q(ticket__status=validated_data.get("op_status")) - - op_records = ClusterOperateRecord.objects.select_related("ticket").filter(op_filters).order_by("-create_at") - op_records_info = [ - { - "create_at": record.create_at, - "op_type": TicketType.get_choice_label(record.ticket.ticket_type), - "op_status": record.ticket.status, - "ticket_id": record.ticket.id, - "creator": record.creator, - } - for record in op_records - ] - op_records_page = self.paginate_queryset(op_records_info) - return self.get_paginated_response(op_records_page) + op_records_page_qs = self.paginate_queryset(self.filter_queryset(self.queryset)) + op_records_page_data = self.serializer_class(op_records_page_qs, many=True).data + return self.get_paginated_response(data=op_records_page_data) @common_swagger_auto_schema( operation_summary=_("查询集群实例变更单据事件"), - query_serializer=InstanceModifyOpSerializer(), tags=[TICKET_TAG], ) - @action(methods=["GET"], detail=False, serializer_class=InstanceModifyOpSerializer) + @action( + methods=["GET"], + detail=False, + serializer_class=InstanceModifyOpSerializer, + queryset=InstanceOperateRecord.objects.select_related("ticket").order_by("-create_at"), + filter_class=InstanceOpRecordListFilter, + ) def get_instance_operate_records(self, request, *args, **kwargs): - validated_data = self.params_validate(self.get_serializer_class()) - op_filters = Q(instance_id=validated_data["instance_id"]) - if validated_data.get("start_time"): - op_filters &= Q(create_at__gte=validated_data.get("start_time")) - - if validated_data.get("end_time"): - op_filters &= Q(create_at__lte=validated_data.get("end_time")) - - if validated_data.get("op_type"): - op_filters &= Q(ticket__ticket_type=validated_data.get("op_type")) - - if validated_data.get("op_status"): - op_filters &= Q(ticket__status=validated_data.get("op_status")) - - op_records = InstanceOperateRecord.objects.select_related("ticket").filter(op_filters).order_by("-create_at") - op_records_info = [ - { - "create_at": record.create_at, - "op_type": TicketType.get_choice_label(record.ticket.ticket_type), - "op_status": record.ticket.status, - "ticket_id": record.ticket.id, - "creator": record.creator, - } - for record in op_records - ] - op_records_page = self.paginate_queryset(op_records_info) - return self.get_paginated_response(op_records_page) + op_records_page_qs = self.paginate_queryset(self.filter_queryset(self.queryset)) + op_records_page_data = self.serializer_class(op_records_page_qs, many=True).data + return self.get_paginated_response(data=op_records_page_data) @swagger_auto_schema( operation_summary=_("查询可编辑单据流程描述"),