Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(backend): 我的待办区分处理和协助页面 #8750 #8751

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions dbm-ui/backend/configuration/models/dba.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,11 @@ def get_biz_db_type_admins(cls, bk_biz_id: int, db_type: str) -> List[str]:
if db_type == admin["db_type"]:
return admin["users"] or DEFAULT_DB_ADMINISTRATORS
return DEFAULT_DB_ADMINISTRATORS

@classmethod
def get_dba_for_db_type(cls, bk_biz_id: int, db_type: str) -> List[str]:
"""获取主dba、备dba、二线dba人员"""
dba_list = cls.list_biz_admins(bk_biz_id)
dba_content = next((dba for dba in dba_list if dba["db_type"] == db_type), {"users": []})
users = dba_content.get("users", [])
return users[:1], users[1:2], users[2:]
13 changes: 12 additions & 1 deletion dbm-ui/backend/ticket/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class TicketListFilter(filters.FilterSet):
cluster = filters.CharFilter(field_name="cluster", method="filter_cluster", label=_("集群域名"))
todo = filters.CharFilter(field_name="todo", method="filter_todo", label=_("代办状态"))
ordering = filters.CharFilter(field_name="ordering", method="order_ticket", label=_("排序字段"))
is_assist = filters.BooleanFilter(field_name="is_assist", method="filter_is_assist", label=_("是否协助"))

class Meta:
model = Ticket
Expand All @@ -47,11 +48,21 @@ def filter_ids(self, queryset, name, value):
def filter_todo(self, queryset, name, value):
user = self.request.user.username
if value == "running":
todo_filter = Q(todo_of_ticket__operators__contains=user, todo_of_ticket__status__in=TODO_RUNNING_STATUS)
todo_filter = Q(
Q(todo_of_ticket__operators__contains=user) | Q(todo_of_ticket__helpers__contains=user),
todo_of_ticket__status__in=TODO_RUNNING_STATUS,
)
else:
todo_filter = Q(todo_of_ticket__done_by=user)
return queryset.filter(todo_filter).distinct()

def filter_is_assist(self, queryset, name, value):
user = self.request.user.username
# 根据 value 的值选择不同的字段
field = "helpers" if value else "operators"
todo_filter = Q(**{f"todo_of_ticket__{field}__contains": user}, todo_of_ticket__status__in=TODO_RUNNING_STATUS)
return queryset.filter(todo_filter).distinct()

def filter_status(self, queryset, name, value):
status = value.split(",")
status_filter = Q()
Expand Down
2 changes: 1 addition & 1 deletion dbm-ui/backend/ticket/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@
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 BaseTodoContext, TodoActorFactory
from backend.ticket.todos.itsm_todo import ItsmTodoContext

Expand Down Expand Up @@ -293,6 +292,7 @@ def batch_process_todo(cls, user, action, operations):
@param action 动作
@param operations: todo列表,每个item包含todo id和params
"""
from backend.ticket.serializers import TodoSerializer

results = []
for operation in operations:
Expand Down
18 changes: 18 additions & 0 deletions dbm-ui/backend/ticket/migrations/0013_todo_helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 3.2.25 on 2025-01-03 02:28

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("ticket", "0012_alter_ticket_remark"),
]

operations = [
migrations.AddField(
model_name="todo",
name="helpers",
field=models.JSONField(default=list, verbose_name="协助人"),
),
]
38 changes: 26 additions & 12 deletions dbm-ui/backend/ticket/models/todo.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,31 +31,44 @@ def exist_unfinished(self):
def get_operators(self, todo_type, ticket, operators):
# 获得提单人,dba,业务协助人. TODO: 后续还会细分主、备、二线DBA,以及明确区分协助人角色
creator = [ticket.creator]
dba = DBAdministrator.get_biz_db_type_admins(ticket.bk_biz_id, ticket.group)
# dba = DBAdministrator.get_biz_db_type_admins(ticket.bk_biz_id, ticket.group)
dba, second_dba, other_dba = DBAdministrator.get_dba_for_db_type(ticket.bk_biz_id, ticket.group)
biz_helpers = BizSettings.get_assistance(ticket.bk_biz_id)

# 构造单据状态与处理人之间的对应关系
# - 审批中:提单人可撤销,dba可处理,
# 考虑某些单据审批人是特定配置(数据导出 -- 运维审批),所以从ItsmBuilder获得审批人
# - 待执行:提单人 + 单据协助人
# - 待继续:dba + 提单人 + 单据协助人
# - 待补货:dba + 提单人 + 单据协助人
# - 已失败:dba + 提单人 + 单据协助人
# - 待执行:operators[提单人] + helpers[单据协助人]
# - 待继续:operators[提单人 + dba] + helpers[单据协助人 + second_dba + other_dba]
# - 待补货:operators[提单人 + dba] + helpers[单据协助人 + second_dba + other_dba]
# - 已失败:operators[提单人 + dba] + helpers[单据协助人 + second_dba + other_dba]
itsm_builder = BuilderFactory.get_builder_cls(ticket.ticket_type).itsm_flow_builder(ticket)
itsm_operators = itsm_builder.get_approvers().split(",")
todo_operators_map = {
TodoType.ITSM: itsm_builder.get_approvers().split(","),
TodoType.APPROVE: creator + biz_helpers,
TodoType.INNER_APPROVE: dba + creator + biz_helpers,
TodoType.RESOURCE_REPLENISH: dba + creator + biz_helpers,
TodoType.INNER_FAILED: dba + creator + biz_helpers,
TodoType.ITSM: itsm_operators[:1],
TodoType.APPROVE: creator,
TodoType.INNER_APPROVE: creator + dba,
TodoType.RESOURCE_REPLENISH: creator + dba,
TodoType.INNER_FAILED: creator + dba,
}
todo_helpers_map = {
TodoType.ITSM: itsm_operators[1:],
TodoType.APPROVE: biz_helpers,
TodoType.INNER_APPROVE: biz_helpers + second_dba + other_dba,
TodoType.RESOURCE_REPLENISH: biz_helpers + second_dba + other_dba,
TodoType.INNER_FAILED: biz_helpers + second_dba + other_dba,
}
# 按照顺序去重
operators = list(dict.fromkeys(operators + todo_operators_map.get(todo_type, [])))
return operators
helpers = [item for item in todo_helpers_map.get(todo_type, []) if item not in operators]
return creator, biz_helpers, helpers, operators

def create(self, **kwargs):
operators = self.get_operators(kwargs["type"], kwargs["ticket"], kwargs.get("operators", []))
creator, biz_helpers, helpers, operators = self.get_operators(
kwargs["type"], kwargs["ticket"], kwargs.get("operators", [])
)
kwargs["operators"] = operators
kwargs["helpers"] = helpers
todo = super().create(**kwargs)
return todo

Expand All @@ -69,6 +82,7 @@ class Todo(AuditedModel):
flow = models.ForeignKey("Flow", help_text=_("关联流程任务"), related_name="todo_of_flow", on_delete=models.CASCADE)
ticket = models.ForeignKey("Ticket", help_text=_("关联工单"), related_name="todo_of_ticket", on_delete=models.CASCADE)
operators = models.JSONField(_("待办人"), default=list)
helpers = models.JSONField(_("协助人"), default=list)
type = models.CharField(
_("待办类型"),
choices=TodoType.get_choices(),
Expand Down
8 changes: 6 additions & 2 deletions dbm-ui/backend/ticket/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ class TicketSerializer(AuditedSerializer, serializers.ModelSerializer):
details = TicketDetailsSerializer(help_text=_("单据详情"))
# 额外补充展示字段
todo_operators = serializers.SerializerMethodField(help_text=_("处理人列表"))
todo_helpers = serializers.SerializerMethodField(help_text=_("协助人列表"))
status = serializers.SerializerMethodField(help_text=_("状态"), read_only=True)
status_display = serializers.SerializerMethodField(help_text=_("状态名称"))
ticket_type_display = serializers.SerializerMethodField(help_text=_("单据类型名称"))
Expand Down Expand Up @@ -126,8 +127,12 @@ def get_todo_operators(self, obj):
obj.running_todos = [todo for todo in obj.todo_of_ticket.all() if todo.status == TodoStatus.TODO]
return obj.running_todos[0].operators if obj.running_todos else []

def get_todo_helpers(self, obj):
obj.running_todo_helpers = [todo for todo in obj.todo_of_ticket.all() if todo.status == TodoStatus.TODO]
return obj.running_todo_helpers[0].helpers if obj.running_todo_helpers else []

def get_status(self, obj):
if obj.status == TicketStatus.RUNNING and obj.running_todos:
if obj.status == TicketStatus.RUNNING and (obj.running_todos or obj.running_todo_helpers):
obj.status = TicketStatus.INNER_TODO
return obj.status

Expand Down Expand Up @@ -231,7 +236,6 @@ class TodoSerializer(serializers.ModelSerializer):
单据序列化
"""

operators = serializers.JSONField(help_text=_("待办人列表"))
cost_time = serializers.SerializerMethodField(help_text=_("耗时"))

def get_cost_time(self, obj):
Expand Down
2 changes: 1 addition & 1 deletion dbm-ui/backend/ticket/todos/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ def process(self, username, action, params):
return
# 允许超级用户和操作人确认
is_superuser = User.objects.get(username=username).is_superuser and self.allow_superuser_process
if not is_superuser and username not in self.todo.operators:
if not is_superuser and username not in self.todo.operators + self.todo.helpers:
raise TodoWrongOperatorException(_("{}不在处理人: {}中,无法处理").format(username, self.todo.operators))

# 执行确认操作
Expand Down
58 changes: 38 additions & 20 deletions dbm-ui/backend/ticket/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,12 @@ def _get_self_manage_tickets(cls, user):
Q(group=manage.db_type) & Q(bk_biz_id=manage.bk_biz_id) if manage.bk_biz_id else Q(group=manage.db_type)
for manage in DBAdministrator.objects.filter(users__contains=user.username)
]
ticket_filter = Q(creator=user.username) | reduce(operator.or_, manage_filters or [Q()])
return Ticket.objects.filter(ticket_filter)
# 除了user管理的单据合集,处理人及协助人也能管理自己的单据
todo_filters = Q(
Q(todo_of_ticket__operators__contains=user.username) | Q(todo_of_ticket__helpers__contains=user.username)
)
ticket_filter = Q(creator=user.username) | todo_filters | reduce(operator.or_, manage_filters or [Q()])
return Ticket.objects.filter(ticket_filter).prefetch_related("todo_of_ticket")

def filter_queryset(self, queryset):
"""filter_class可能导致预取的todo失效,这里重新取一次"""
Expand Down Expand Up @@ -446,29 +450,43 @@ def get_tickets_count(self, request, *args, **kwargs):
"""
user = request.user.username
tickets = self._get_self_manage_tickets(request.user)
count_map = {count_type: 0 for count_type in CountType.get_values()}
exclude_values = {"MY_APPROVE", "SELF_MANAGE", "DONE"}

# 初始化 count_map
def initialize_count_map():
return {count_type: 0 for count_type in CountType.get_values() if count_type not in exclude_values}

results = {}

# 通用的函数来计算待办和协助状态
def calculate_status_count(field_name, relation_name):
status_counts = (
tickets.filter(
status__in=TICKET_TODO_STATUS_SET,
**{f"{relation_name}__{field_name}__contains": user},
**{f"{relation_name}__status__in": TODO_RUNNING_STATUS},
)
.distinct()
.values_list("status", flat=True)
)
count_map = initialize_count_map()
for sts, count in Counter(status_counts).items():
sts = CountType.INNER_TODO.value if sts == "RUNNING" else sts
count_map[sts] = count
return count_map

# 计算我的代办
results["Pending"] = calculate_status_count("operators", "todo_of_ticket")
# 计算我的协助
results["to_help"] = calculate_status_count("helpers", "todo_of_ticket")
# 我负责的业务
count_map[CountType.SELF_MANAGE] = tickets.count()
results[CountType.SELF_MANAGE] = tickets.count()
# 我的申请
count_map[CountType.MY_APPROVE] = tickets.filter(creator=user).count()
# 我的代办
todo_status = (
tickets.filter(
status__in=TICKET_TODO_STATUS_SET,
todo_of_ticket__operators__contains=user,
todo_of_ticket__status__in=TODO_RUNNING_STATUS,
)
.distinct()
.values_list("status", flat=True)
)
for sts, count in Counter(todo_status).items():
sts = CountType.INNER_TODO.value if sts == "RUNNING" else sts
count_map[sts] = count
results[CountType.MY_APPROVE] = tickets.filter(creator=user).count()
# 我的已办
count_map[CountType.DONE] = tickets.filter(todo_of_ticket__done_by=user).count()
results[CountType.DONE] = tickets.filter(todo_of_ticket__done_by=user).count()

return Response(count_map)
return Response(results)

@common_swagger_auto_schema(
operation_summary=_("查询集群变更单据事件"),
Expand Down
Loading