From 3ea51bd23dbf20815a22ad53dcd8956b26808bd7 Mon Sep 17 00:00:00 2001 From: benero Date: Thu, 26 Sep 2024 17:30:06 +0800 Subject: [PATCH 1/9] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=9C=8D=E5=8A=A1?= =?UTF-8?q?=E5=8F=AF=E8=A7=81=E8=8C=83=E5=9B=B4=E6=9C=AA=E7=94=9F=E6=95=88?= =?UTF-8?q?=E9=97=AE=E9=A2=98=20--story=3D131422807?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- itsm/ticket/permissions.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/itsm/ticket/permissions.py b/itsm/ticket/permissions.py index b0df2e0e..ef7a7b8a 100644 --- a/itsm/ticket/permissions.py +++ b/itsm/ticket/permissions.py @@ -22,6 +22,8 @@ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ +import operator +from functools import reduce from django.utils.translation import ugettext as _ from django.conf import settings @@ -60,6 +62,20 @@ class TicketPermissionValidate(permissions.BasePermission): def __init__(self): self.message = _("抱歉,您无权查看该单据") + + def has_permission(self, request, view): + """工单创建权限""" + if view.action != "create": + return True + + service_id = request.data.get("service_id") + if not service_id: + return False + + queryset = Service.objects.filter(pk=service_id, is_valid=True) + conditions = Service.permission_filter(request.user.username) + queryset = queryset.filter(reduce(operator.or_, conditions)) + return queryset.exists() def has_object_permission(self, request, view, obj): From 7569f652855b017362f75a66bad84da5b3905b3e Mon Sep 17 00:00:00 2001 From: benero Date: Thu, 26 Sep 2024 17:40:41 +0800 Subject: [PATCH 2/9] =?UTF-8?q?fix:=20=E4=BC=98=E5=8C=96=E9=94=99=E8=AF=AF?= =?UTF-8?q?=E5=BC=82=E5=B8=B8=E6=8F=90=E7=A4=BA=20--bug=3D131430245?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- itsm/component/generics.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/itsm/component/generics.py b/itsm/component/generics.py index 75477175..66dfe520 100644 --- a/itsm/component/generics.py +++ b/itsm/component/generics.py @@ -120,12 +120,12 @@ def exception_handler(exc, context): else: # 调试模式 logger.error(traceback.format_exc()) - print(traceback.format_exc()) - if settings.RUN_MODE != 'PRODUCT': - raise exc # 正式环境,屏蔽500 data.update( - {'code': ResponseCodeStatus.SERVER_500_ERROR, 'message': exc.message, } + { + 'code': ResponseCodeStatus.SERVER_500_ERROR, + 'message': getattr(exc, "message", str(exc)) + } ) return Response(data, status=status.HTTP_200_OK) From 751e0334fe8e90ade0830dbda42c1e8fd654bc68 Mon Sep 17 00:00:00 2001 From: benero Date: Thu, 26 Sep 2024 18:14:23 +0800 Subject: [PATCH 3/9] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E8=AF=84=E8=AE=BA?= =?UTF-8?q?=E8=B6=8A=E6=9D=83=E9=97=AE=E9=A2=98=20--bug=3D131430245?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- itsm/ticket/views/misc.py | 3 +++ itsm/ticket/views/ticket_remark.py | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/itsm/ticket/views/misc.py b/itsm/ticket/views/misc.py index 0aec0eb2..d8ff2e23 100644 --- a/itsm/ticket/views/misc.py +++ b/itsm/ticket/views/misc.py @@ -108,6 +108,9 @@ class CommentViewSet(component_viewsets.NormalModelViewSet): "stars": ["exact"], } ordering_fields = "__all__" + + def list(self, request, *args, **kwargs): + return Response() @action(detail=False, methods=["get"]) def get_comment(self, request): diff --git a/itsm/ticket/views/ticket_remark.py b/itsm/ticket/views/ticket_remark.py index 2a0ebcd4..c1fdcb91 100644 --- a/itsm/ticket/views/ticket_remark.py +++ b/itsm/ticket/views/ticket_remark.py @@ -1,13 +1,17 @@ # -*- coding: utf-8 -*- + +from django.utils.translation import ugettext as _ from rest_framework.decorators import action from rest_framework.response import Response from itsm.component.drf import viewsets as component_viewsets +from itsm.component.exceptions import ValidateError from itsm.ticket.models import TicketRemark, Ticket from itsm.ticket.permissions import RemarkPermissionValidate from itsm.ticket.serializers import TicketRemarkSerializer + class ModelViewSet(component_viewsets.ModelViewSet): """按需改造DRF默认的ModelViewSet类""" @@ -34,6 +38,8 @@ class TicketRemarkModelViewSet(ModelViewSet): def list(self, request, *args, **kwargs): # 后面这个接口要重构一部分 ticket_id = request.query_params.get("ticket_id", "") + if not ticket_id: + raise ValidateError(_("ticket_id 不能为空")) show_type = request.query_params.get("show_type", "PUBLIC") ticket = Ticket.objects.get(id=ticket_id) From 84e1e3a324c70437b10f7b9c6e7e91d9ba7a484f Mon Sep 17 00:00:00 2001 From: benero Date: Thu, 26 Sep 2024 20:28:26 +0800 Subject: [PATCH 4/9] =?UTF-8?q?minor:=20=E9=87=8D=E5=A4=8D=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E4=BC=98=E5=8C=96=20--story=3D119593627?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- itsm/component/drf/viewsets.py | 2 +- itsm/ticket/views/ticket_remark.py | 19 +------------------ 2 files changed, 2 insertions(+), 19 deletions(-) diff --git a/itsm/component/drf/viewsets.py b/itsm/component/drf/viewsets.py index 0cec158f..f24fdae3 100644 --- a/itsm/component/drf/viewsets.py +++ b/itsm/component/drf/viewsets.py @@ -53,7 +53,7 @@ def perform_create(self, serializer): user = serializer.context.get('request').user username = getattr(user, 'username', 'guest') - serializer.save(creator=username, updated_by=username) + return serializer.save(creator=username, updated_by=username) def perform_update(self, serializer): """更新时补充基础Model中的字段 diff --git a/itsm/ticket/views/ticket_remark.py b/itsm/ticket/views/ticket_remark.py index c1fdcb91..60df4e3f 100644 --- a/itsm/ticket/views/ticket_remark.py +++ b/itsm/ticket/views/ticket_remark.py @@ -4,30 +4,13 @@ from rest_framework.decorators import action from rest_framework.response import Response -from itsm.component.drf import viewsets as component_viewsets +from itsm.component.drf.viewsets import ModelViewSet from itsm.component.exceptions import ValidateError from itsm.ticket.models import TicketRemark, Ticket from itsm.ticket.permissions import RemarkPermissionValidate from itsm.ticket.serializers import TicketRemarkSerializer - -class ModelViewSet(component_viewsets.ModelViewSet): - """按需改造DRF默认的ModelViewSet类""" - - def perform_create(self, serializer): - """创建时补充基础Model中的字段""" - user = serializer.context.get("request").user - username = getattr(user, "username", "guest") - return serializer.save(creator=username, updated_by=username) - - def perform_update(self, serializer): - """更新时补充基础Model中的字段""" - user = serializer.context.get("request").user - username = getattr(user, "username", "guest") - serializer.save(updated_by=username) - - class TicketRemarkModelViewSet(ModelViewSet): queryset = TicketRemark.objects.filter(is_deleted=False).order_by( "-create_at", "level" From 3799b67912e82d2ad206556ea17d6ca580495d91 Mon Sep 17 00:00:00 2001 From: benero Date: Thu, 26 Sep 2024 20:39:12 +0800 Subject: [PATCH 5/9] =?UTF-8?q?minor:=20=E9=80=9A=E7=9F=A5=E5=BC=82?= =?UTF-8?q?=E5=B8=B8=E6=97=B6=E8=AE=B0=E5=BD=95=E5=B7=A5=E5=8D=95=E4=BF=A1?= =?UTF-8?q?=E6=81=AF=20--bug=3D131427363?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- itsm/ticket/views/ticket.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/itsm/ticket/views/ticket.py b/itsm/ticket/views/ticket.py index 82343815..acd9dd54 100644 --- a/itsm/ticket/views/ticket.py +++ b/itsm/ticket/views/ticket.py @@ -631,7 +631,7 @@ def send_email(self, request, *args, **kwargs): } ) except ComponentCallError as error: - logger.warning("send email execption: %s" % error) + logger.warning(f"[send email] ticket_url=>{ticket_url}, execption=>{error}") return Response( { "result": False, From 566ae8f05d4d0d8de870531287e592d98380815d Mon Sep 17 00:00:00 2001 From: benero Date: Thu, 26 Sep 2024 21:08:26 +0800 Subject: [PATCH 6/9] =?UTF-8?q?minor:=20=E5=85=BC=E5=AE=B9=E7=88=B6?= =?UTF-8?q?=E7=BA=A7=E8=AF=84=E8=AE=BA=E5=88=A0=E9=99=A4=E6=83=85=E5=86=B5?= =?UTF-8?q?=20#1387?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- itsm/ticket/views/ticket_remark.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/itsm/ticket/views/ticket_remark.py b/itsm/ticket/views/ticket_remark.py index 60df4e3f..938e7adf 100644 --- a/itsm/ticket/views/ticket_remark.py +++ b/itsm/ticket/views/ticket_remark.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- - +from django.http import Http404 from django.utils.translation import ugettext as _ from rest_framework.decorators import action from rest_framework.response import Response @@ -48,7 +48,14 @@ def list(self, request, *args, **kwargs): return self.get_paginated_response(serializer.data) serializer = self.get_serializer(queryset, many=True) return Response(serializer.data) - + + def retrieve(self, request, *args, **kwargs): + try: + return super().retrieve(request, *args, **kwargs) + except Http404: + """兼容父级评论删除情况""" + return Response([]) + @action(detail=False, methods=["get"]) def tree_view(self, request): """评论视图""" From 47d65203da937fd168bc24c0a6a4764565d576b9 Mon Sep 17 00:00:00 2001 From: benero Date: Thu, 26 Sep 2024 21:39:54 +0800 Subject: [PATCH 7/9] =?UTF-8?q?fix:=20=E8=BF=90=E8=90=A5=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E6=A0=B9=E6=8D=AE=E6=97=B6=E9=97=B4=E8=BF=87=E6=BB=A4=E4=BB=8A?= =?UTF-8?q?=E5=A4=A9=E6=95=B0=E6=8D=AE=E5=8F=82=E6=95=B0=E6=9C=89=E8=AF=AF?= =?UTF-8?q?=20#1406?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- itsm/ticket/views/operational.py | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/itsm/ticket/views/operational.py b/itsm/ticket/views/operational.py index 41b658ff..e34723b6 100644 --- a/itsm/ticket/views/operational.py +++ b/itsm/ticket/views/operational.py @@ -23,7 +23,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -from datetime import datetime, timedelta +from datetime import datetime, timedelta, time from functools import reduce from django.db import connection @@ -231,7 +231,7 @@ def service_statistics(self, request): project_key = request.query_params.get("project_key", None) filter_serializer = StatisticsSerializer(data=request.query_params) filter_serializer.is_valid(raise_exception=True) - kwargs = filter_serializer.validated_data + kwargs = self.combine_date(filter_serializer.validated_data) services = Service.objects.filter() service_name = kwargs.pop("service_name", "") @@ -292,7 +292,7 @@ def biz_statistics(self, request): project_key = request.query_params.get("project_key", None) filter_serializer = StatisticsSerializer(data=request.query_params) filter_serializer.is_valid(raise_exception=True) - kwargs = filter_serializer.validated_data + kwargs = self.combine_date(filter_serializer.validated_data) biz_id = kwargs.pop("biz_id", "") biz_names = get_biz_names() order = kwargs.pop("order_by") @@ -328,7 +328,7 @@ def category_statistics(self, request): project_key = request.query_params.get("project_key", None) filter_serializer = StatisticsSerializer(data=request.query_params) filter_serializer.is_valid(raise_exception=True) - kwargs = filter_serializer.validated_data + kwargs = self.combine_date(filter_serializer.validated_data) order = kwargs.pop("order_by") service_category = ServiceCategory.objects.all().values("key", "name") category_dict = { @@ -358,7 +358,7 @@ def status_statistics(self, request): project_key = request.query_params.get("project_key", None) filter_serializer = BaseFilterSerializer(data=request.query_params) filter_serializer.is_valid(raise_exception=True) - kwargs = filter_serializer.validated_data + kwargs = self.combine_date(filter_serializer.validated_data) not_running_status = ["FINISHED", "TERMINATED", "REVOKED"] ticket_info = ( self.queryset.filter(**kwargs) @@ -431,7 +431,7 @@ def top_creator_statistics(self, request): project_key = request.query_params.get("project_key", None) filter_serializer = ServiceStatisticsFilterSerializer(data=request.query_params) filter_serializer.is_valid(raise_exception=True) - kwargs = filter_serializer.validated_data + kwargs = self.combine_date(filter_serializer.validated_data) kwargs.pop("timedelta") max_ids = ( TicketOrganization.objects.filter( @@ -481,7 +481,8 @@ def top_creator_statistics(self, request): def distribute_statistics(self, request): filter_serializer = TicketOrganizationSerializer(data=request.query_params) filter_serializer.is_valid(raise_exception=True) - kwargs = filter_serializer.validated_data + kwargs = self.combine_date(filter_serializer.validated_data) + level_dict = { 1: ("first_level_id", "first_level_name"), 2: ("second_level_id", "second_level_name"), @@ -1161,3 +1162,9 @@ def new_tickets(self, request, *args, **kwargs): filter_result.sort(key=lambda x: x["day"]) return Response(filter_result) + + @staticmethod + def combine_date(kwargs): + if kwargs.get("create_at__lte"): + kwargs["create_at__lte"] = datetime.combine(kwargs["create_at__lte"], time(23, 59, 59)) + return kwargs From 6dc9c9ea72677c7f5411b40a8d63ecc4273f2418 Mon Sep 17 00:00:00 2001 From: benero Date: Fri, 27 Sep 2024 00:34:10 +0800 Subject: [PATCH 8/9] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=A4=9A=E4=B8=AA?= =?UTF-8?q?api=E8=8A=82=E7=82=B9=E5=B9=B6=E8=A1=8C=E6=89=A7=E8=A1=8C?= =?UTF-8?q?=E6=97=B6=E5=87=BA=E7=8E=B0=E6=AD=BB=E9=94=81=EF=BC=8C=E5=AF=BC?= =?UTF-8?q?=E8=87=B4=E6=B5=81=E7=A8=8B=E5=81=87=E6=AD=BB=20#1350?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- itsm/ticket/managers.py | 3 ++- itsm/ticket/models/ticket.py | 12 ++---------- itsm/ticket/tasks.py | 14 +++++++++++++- itsm/workflow/models/workflow.py | 2 +- pipeline/engine/models/core.py | 8 +++++--- 5 files changed, 23 insertions(+), 16 deletions(-) diff --git a/itsm/ticket/managers.py b/itsm/ticket/managers.py index 2024fd32..ce0db4c4 100644 --- a/itsm/ticket/managers.py +++ b/itsm/ticket/managers.py @@ -854,7 +854,8 @@ def create_log( to_state_id=to_state_id, ) - ticket.set_history_operators(log_operator) + from itsm.ticket.tasks import ticket_set_history_operators + ticket_set_history_operators.delay(ticket.id, log_operator) return log diff --git a/itsm/ticket/models/ticket.py b/itsm/ticket/models/ticket.py index 00364772..32f65d35 100644 --- a/itsm/ticket/models/ticket.py +++ b/itsm/ticket/models/ticket.py @@ -3828,14 +3828,6 @@ def _formatted(processors_type, processors): slave.current_processors = node_processors bulk_update(slaves, update_fields=["current_processors"]) - def set_history_operators(self, current_operator): - """设置历史处理人""" - history_operators = [user for user in self.updated_by.split(",") if user] - if current_operator not in history_operators: - history_operators.append(current_operator) - self.updated_by = dotted_name(",".join(set(history_operators))) - self.save(update_fields=("updated_by",)) - def do_in_sign_state(self, node_status, fields, operator, source): """ In sign node action @@ -3904,8 +3896,8 @@ def do_in_sign_state(self, node_status, fields, operator, source): # Update ticket priority, processors, history operators self.update_priority() - # self.set_current_processors() - self.set_history_operators(operator) + from itsm.ticket.tasks import ticket_set_history_operators + ticket_set_history_operators.delay(self.id, operator) # Update sla task # if self._sla_tasks: diff --git a/itsm/ticket/tasks.py b/itsm/ticket/tasks.py index d5fd9e1d..03086696 100644 --- a/itsm/ticket/tasks.py +++ b/itsm/ticket/tasks.py @@ -35,7 +35,7 @@ from celery.schedules import crontab from celery.task import periodic_task, task from django.db.models import Q -from django.db import connection +from django.db import connection, transaction from django.utils.translation import ugettext as _ from common.log import logger @@ -519,3 +519,15 @@ def consume_notify(): for item in range(1, end): user = email_notify.lpop("notify_queue") send_message(user, queryset) + + +@task +def ticket_set_history_operators(ticket_id, current_operator): + """设置历史处理人""" + with transaction.atomic(): + ticket = Ticket.objects.select_for_update().get(pk=ticket_id) + history_operators = [user for user in ticket.updated_by.split(",") if user] + if current_operator not in history_operators: + history_operators.append(current_operator) + ticket.updated_by = dotted_name(",".join(set(history_operators))) + ticket.save(update_fields=("updated_by",)) diff --git a/itsm/workflow/models/workflow.py b/itsm/workflow/models/workflow.py index 72b72420..450eee87 100644 --- a/itsm/workflow/models/workflow.py +++ b/itsm/workflow/models/workflow.py @@ -771,7 +771,7 @@ def get_state_fields(self, state_id): state = self.get_state(state_id) all_fields = map(lambda field_id: self.get_field(field_id), state["fields"]) - return list(filter(lambda f: f["is_valid"], all_fields)) + return list(filter(lambda f: f and f["is_valid"], all_fields)) def get_first_state_fields(self): return self.get_state_fields(self.first_state["id"]) diff --git a/pipeline/engine/models/core.py b/pipeline/engine/models/core.py index 51d4ad67..06aacab0 100644 --- a/pipeline/engine/models/core.py +++ b/pipeline/engine/models/core.py @@ -496,9 +496,11 @@ def destroy_and_wake_up_parent(self, destination_id): data_service.set_object(self._context_key(), self.top_pipeline.context) data_service.set_object(self._data_key(), self.top_pipeline.data) - self.__class__.objects.filter(id=self.parent_id).update( - ack_num=models.F("ack_num") + 1 - ) + with transaction.atomic(): + parent = self.__class__.objects.select_for_update().get(id=self.parent_id) + parent.ack_num += 1 + parent.save() + can_wake_up = False with transaction.atomic(): From 7494fb51b8a6bde3515cbb8e5aee64be61de34f2 Mon Sep 17 00:00:00 2001 From: benero Date: Fri, 27 Sep 2024 00:43:36 +0800 Subject: [PATCH 9/9] =?UTF-8?q?docs:=20=E6=96=B0=E5=A2=9E=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E6=97=A5=E5=BF=97=20--story=3D119593627?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/RELEASE.md | 10 +++++++++- docs/RELEASE_EN.md | 7 +++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/docs/RELEASE.md b/docs/RELEASE.md index 3032608e..8339e97c 100644 --- a/docs/RELEASE.md +++ b/docs/RELEASE.md @@ -1,5 +1,6 @@ # Changelog ## [Version: 2.7.0] - 2024-08-29 +【新增】通知人员黑名单过滤 【优化】esb鉴权信息迁移到header,支持版本 open-paas/esb >= 2.12.20 【优化】导航栏中“我的经办”改为“我的已办” 【优化】流程中新增【审批节点】时调整默认项 @@ -8,7 +9,14 @@ 【修复】修复单据列表在非首页搜索报错的问题 【修复】任务模板编辑点击上一步返回路径优化 【修复】tooltips组件XSS问题修复 -【修复】修复部分模块鉴权异常的问题 +【修复】修复部分模块鉴权异常的问题 +【修复】修复 APM 部署无数据问题 +【修复】修复部分模块鉴权异常的问题 +【修复】修复服务可见范围未生效的问题 +【修复】修复工单评论父级评论显示异常的问题 +【修复】修复运营分析结束时间不准确的问题 +【修复】修复并行网关死锁问题 + ## [Version: 2.6.30] - 2024-08-15 【修复】项目初始化时登录后重定向url异常修复 diff --git a/docs/RELEASE_EN.md b/docs/RELEASE_EN.md index 28825b39..0cf057ae 100644 --- a/docs/RELEASE_EN.md +++ b/docs/RELEASE_EN.md @@ -1,5 +1,6 @@ # Changelog ## [Version: 2.7.0] - 2024-08-29 +【Feature】Notification recipient blacklist filtering. 【Improved】Migrated the authentication information to header when calling esb, support open-paas/esb >= 2.12.20 【Improved】Adjust default items when adding an Approval Node in the process. 【Fixed】The navigation bar style has been standardized according to the design specifications. @@ -8,6 +9,12 @@ 【Fixed】Optimized the return path when clicking ‘Previous’ during task template editing. 【Fixed】Fixed XSS vulnerability in tooltips component. 【Fixed】Fix the issue with authentication anomalies in certain modules. +【Fixed】Resolved the issue of APM deployment without data. +【Fixed】Resolved authentication exceptions in some modules. +【Fixed】Resolved the issue where the service visibility range was not effective. +【Fixed】Resolved the issue of abnormal display of parent comments in the work order comments. +【Fixed】Resolved the issue of inaccurate end time in operation analysis. +【Fixed】Resolved the deadlock issue in the parallel gateway. ## [Version: 2.6.30] - 2024-08-15