diff --git a/release.md b/release.md index 8175ed41..ed2c219b 100644 --- a/release.md +++ b/release.md @@ -1,6 +1,8 @@ ### V1.15.0 版本更新日志 -[TODO] +### 新增 + +- [ 新增 ] 通知组内新增变量 ### V1.14.0 版本更新日志 diff --git a/src/backend/apps/notice/constants.py b/src/backend/apps/notice/constants.py index d7430cb0..cb07c6c1 100644 --- a/src/backend/apps/notice/constants.py +++ b/src/backend/apps/notice/constants.py @@ -15,6 +15,7 @@ We undertake not to change the open source license (MIT license) applicable to the current version of the project delivered to anyone in the future. """ +from typing import Optional from django.utils.translation import gettext_lazy @@ -59,3 +60,20 @@ class RelateType(TextChoices): RISK = "risk", gettext_lazy("风险") ERROR = "error", gettext_lazy("异常") + + +class MemberVariable(TextChoices): + """ + 成员变量 + """ + + OPERATOR = "$OPERATOR", gettext_lazy("责任人") + OPERATOR_LEADER = "$OPERATOR_LEADER", gettext_lazy("责任人上级") + + @classmethod + def match(cls, key: str) -> Optional["MemberVariable"]: + """ + 匹配成员变量 + """ + + return cls._value2member_map_.get(key) diff --git a/src/backend/apps/notice/handlers/error.py b/src/backend/apps/notice/handlers/error.py index 4778ffb4..969aa920 100644 --- a/src/backend/apps/notice/handlers/error.py +++ b/src/backend/apps/notice/handlers/error.py @@ -25,6 +25,7 @@ from apps.notice.constants import ADMIN_NOTICE_GROUP_ID, RelateType from apps.notice.models import NoticeContent, NoticeContentConfig, NoticeGroup +from apps.notice.parser import IgnoreMemberVariableParser class ErrorMsgHandler: @@ -38,16 +39,17 @@ def __init__(self, title, content): def send(self): notice_group: NoticeGroup = NoticeGroup.objects.get(group_id=ADMIN_NOTICE_GROUP_ID) + receivers = IgnoreMemberVariableParser().parse_group(notice_group) resource.notice.send_notice( relate_type=RelateType.ERROR, relate_id=self.get_current_trace_id(self.__class__.__name__), agg_key=None, msg_type=[c.get("msg_type") for c in notice_group.notice_config if "msg_type" in c], - receivers=notice_group.group_member, + receivers=receivers, title=self.title, content=self.content.to_string(), ) - logger.info("[SendErrorMsgDone] NoticeGroup => %s; Members => %s", notice_group.pk, notice_group.group_member) + logger.info("[SendErrorMsgDone] NoticeGroup => %s; Members => %s", notice_group.pk, receivers) def build_content(self, content: str) -> NoticeContent: return NoticeContent( diff --git a/src/backend/apps/notice/parser.py b/src/backend/apps/notice/parser.py new file mode 100644 index 00000000..b0aefceb --- /dev/null +++ b/src/backend/apps/notice/parser.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- +import abc +from typing import List, Union + +from bk_resource import resource +from django.db.models import QuerySet + +from apps.notice.constants import MemberVariable +from apps.notice.models import NoticeGroup + + +class MemberVariableParserBase(abc.ABC): + def is_skip(self, member: str) -> bool: + """默认跳过变量""" + return MemberVariable.match(member) is not None + + @abc.abstractmethod + def parse_member(self, member: Union[str, MemberVariable]) -> str: + """解析成员""" + raise NotImplementedError() + + def parse_group(self, group: NoticeGroup) -> List[str]: + """ + 处理组成员 + """ + + parsed_members = set() + for member in group.group_member: + if self.is_skip(member): + continue + member = self.parse_member(member) + if member: + parsed_members.add(member) + return list(parsed_members) + + def parse_groups(self, groups: Union[QuerySet["NoticeGroup"], List["NoticeGroup"]]) -> List[str]: + """ + 解析处理组成员 + """ + + return list({member for group in groups for member in self.parse_group(group)}) + + +class MemberVariableParser(MemberVariableParserBase): + """ + 成员变量解析器 + """ + + def __init__(self, operator: str): + """ + :param operator: 责任人 + """ + + self.operator = operator + + def parse_member(self, member: Union[str, MemberVariable]) -> str: + match member: + case MemberVariable.OPERATOR: + return self.operator + case MemberVariable.OPERATOR_LEADER: + return resource.user_manage.retrieve_leader(id=self.operator) + return member + + +class IgnoreMemberVariableParser(MemberVariableParserBase): + """ + 忽略成员变量解析器 + """ + + def parse_member(self, member: Union[str, MemberVariable]) -> str: + return member diff --git a/src/backend/apps/notice/resources.py b/src/backend/apps/notice/resources.py index 22c896cd..64129577 100644 --- a/src/backend/apps/notice/resources.py +++ b/src/backend/apps/notice/resources.py @@ -25,13 +25,14 @@ from django.utils.translation import gettext_lazy from apps.audit.resources import AuditMixinResource -from apps.notice.constants import MsgType +from apps.notice.constants import MemberVariable, MsgType from apps.notice.models import NoticeGroup, NoticeLogV2 from apps.notice.serializers import ( CreateNoticeGroupRequestSerializer, CreateNoticeGroupResponseSerializer, DeleteNoticeGroupRequestSerializer, GetMsgTypeResponseSerializer, + GetNoticeCommonResponseSerializer, ListAllNoticeGroupResponseSerializer, ListNoticeGroupRequestSerializer, ListNoticeGroupResponseSerializer, @@ -45,6 +46,7 @@ from apps.permission.handlers.actions import ActionEnum from apps.permission.handlers.permission import Permission from apps.permission.handlers.resource_types import ResourceEnum +from core.utils.tools import choices_to_dict class NoticeMeta(AuditMixinResource, abc.ABC): @@ -171,3 +173,13 @@ class SendNotice(NoticeMeta): def perform_request(self, validated_request_data): NoticeLogV2.objects.create(**validated_request_data) + + +class GetNoticeCommon(NoticeMeta): + name = gettext_lazy("Get Notice Common") + ResponseSerializer = GetNoticeCommonResponseSerializer + + def perform_request(self, validated_request_data): + return { + "member_variable": choices_to_dict(MemberVariable, val="value", name="label"), + } diff --git a/src/backend/apps/notice/serializers.py b/src/backend/apps/notice/serializers.py index 5bcde2fc..5b664381 100644 --- a/src/backend/apps/notice/serializers.py +++ b/src/backend/apps/notice/serializers.py @@ -31,6 +31,7 @@ ) from apps.notice.exceptions import NoticeGroupNameDuplicate from apps.notice.models import NoticeGroup, NoticeLogV2 +from core.serializers import ChoiceListSerializer class GetMsgTypeResponseSerializer(serializers.Serializer): @@ -214,3 +215,11 @@ def validate_receivers(self, receivers: List[str]) -> List[str]: if not whitelist_users: return receivers return [u for u in receivers if u in whitelist_users] + + +class GetNoticeCommonResponseSerializer(serializers.Serializer): + """ + Get Notice Common + """ + + member_variable = ChoiceListSerializer(label=gettext_lazy("Member Variable"), many=True) diff --git a/src/backend/apps/notice/views.py b/src/backend/apps/notice/views.py index 7766d4e6..75af0b8e 100644 --- a/src/backend/apps/notice/views.py +++ b/src/backend/apps/notice/views.py @@ -35,6 +35,7 @@ class NoticeViewSet(ResourceViewSet): resource_routes = [ ResourceRoute("GET", resource.notice.get_msg_type, endpoint="msg_type"), + ResourceRoute("GET", resource.notice.get_notice_common, endpoint="common"), ] diff --git a/src/backend/core/serializers.py b/src/backend/core/serializers.py new file mode 100644 index 00000000..2255cf4a --- /dev/null +++ b/src/backend/core/serializers.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +from django.utils.translation import gettext_lazy +from rest_framework import serializers + + +class ChoiceListSerializer(serializers.Serializer): + """ + Choice List + """ + + label = serializers.CharField(label=gettext_lazy("Label"), allow_blank=True, allow_null=True) + value = serializers.CharField(label=gettext_lazy("Value")) + config = serializers.JSONField(label=gettext_lazy("Config"), required=False) diff --git a/src/backend/locale/en/LC_MESSAGES/django.po b/src/backend/locale/en/LC_MESSAGES/django.po index 10cc1b19..ed20763a 100644 --- a/src/backend/locale/en/LC_MESSAGES/django.po +++ b/src/backend/locale/en/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-08-26 10:55+0800\n" +"POT-Creation-Date: 2024-09-12 13:19+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -1506,6 +1506,9 @@ msgstr "Risk" msgid "异常" msgstr "Error" +msgid "责任人上级" +msgstr "Person Superior" + msgid "通知组名称重复" msgstr "Notice Group Name Duplicate" @@ -1608,6 +1611,9 @@ msgstr "Create Notice Group" msgid "更新通知组" msgstr "Update Notice Group" +msgid "Get Notice Common" +msgstr "Get Strategy Common" + msgid "此为系统邮件,由蓝鲸审计中心自动发送,请勿回复" msgstr "Sent by Blueking Audit Center" @@ -1630,6 +1636,9 @@ msgstr "Notice Group Name Invalid with %s" msgid "内置通知组禁止删除" msgstr "Build in notice group is not allowed to delete" +msgid "Member Variable" +msgstr "Member Variable" + msgid "权限中心异常" msgstr "IAM Error" @@ -1946,6 +1955,15 @@ msgstr "Admin Required" msgid "Token Invalid" msgstr "Token Invalid" +msgid "Label" +msgstr "Label" + +msgid "Value" +msgstr "Value" + +msgid "Config" +msgstr "Config" + msgid "数据拉取" msgstr "Data Pull" @@ -3469,9 +3487,6 @@ msgstr "Strategy Count" msgid "Key" msgstr "Key" -msgid "Value" -msgstr "Value" - msgid "Method" msgstr "Method" @@ -3502,9 +3517,6 @@ msgstr "Detects" msgid "System ID" msgstr "System ID" -msgid "Label" -msgstr "Label" - msgid "Children" msgstr "Children" @@ -3514,9 +3526,6 @@ msgstr "Action ID" msgid "Field Type" msgstr "Field Type" -msgid "Config" -msgstr "Config" - msgid "Strategy Operator" msgstr "Strategy Operator" diff --git a/src/backend/locale/zh_CN/LC_MESSAGES/django.po b/src/backend/locale/zh_CN/LC_MESSAGES/django.po index 45522ada..f6e99bc5 100644 --- a/src/backend/locale/zh_CN/LC_MESSAGES/django.po +++ b/src/backend/locale/zh_CN/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-08-26 10:55+0800\n" +"POT-Creation-Date: 2024-09-12 13:19+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -1506,6 +1506,9 @@ msgstr "风险" msgid "异常" msgstr "异常" +msgid "责任人上级" +msgstr "责任人上级" + msgid "通知组名称重复" msgstr "通知组名称重复" @@ -1608,6 +1611,9 @@ msgstr "创建通知组" msgid "更新通知组" msgstr "更新通知组" +msgid "Get Notice Common" +msgstr "获取策略通用参数" + msgid "此为系统邮件,由蓝鲸审计中心自动发送,请勿回复" msgstr "此为系统邮件,由蓝鲸审计中心自动发送,请勿回复" @@ -1630,6 +1636,9 @@ msgstr "通知组名称不能包含特殊字符 %s" msgid "内置通知组禁止删除" msgstr "内置通知组禁止删除" +msgid "Member Variable" +msgstr "成员变量" + msgid "权限中心异常" msgstr "权限中心异常" @@ -1946,6 +1955,15 @@ msgstr "管理员请求" msgid "Token Invalid" msgstr "Token验证失败" +msgid "Label" +msgstr "标签" + +msgid "Value" +msgstr "值" + +msgid "Config" +msgstr "配置" + msgid "数据拉取" msgstr "数据拉取" @@ -3469,9 +3487,6 @@ msgstr "策略数量" msgid "Key" msgstr "键" -msgid "Value" -msgstr "值" - msgid "Method" msgstr "方法" @@ -3502,9 +3517,6 @@ msgstr "识别条件" msgid "System ID" msgstr "系统ID" -msgid "Label" -msgstr "标签" - msgid "Children" msgstr "子数据" @@ -3514,9 +3526,6 @@ msgstr "操作ID" msgid "Field Type" msgstr "字段类型" -msgid "Config" -msgstr "配置" - msgid "Strategy Operator" msgstr "策略操作符" diff --git a/src/backend/services/web/analyze/tasks.py b/src/backend/services/web/analyze/tasks.py index ed7442e4..2f55970b 100644 --- a/src/backend/services/web/analyze/tasks.py +++ b/src/backend/services/web/analyze/tasks.py @@ -31,6 +31,7 @@ from apps.notice.constants import MsgType from apps.notice.handlers import ErrorMsgHandler from apps.notice.models import NoticeGroup +from apps.notice.parser import IgnoreMemberVariableParser from core.lock import lock from services.web.analyze.constants import ( BKBASE_ERROR_LOG_LEVEL, @@ -163,7 +164,10 @@ def toggle_monitor(strategy_id: int, is_active: bool): # 获取通知组并判定关键参数存在 notice_groups: List[NoticeGroup] = list(NoticeGroup.objects.filter(group_id__in=strategy.notice_groups)) - receivers = [{"receiver_type": "user", "username": member} for member in NoticeGroup.parse_members(notice_groups)] + receivers = [ + {"receiver_type": "user", "username": member} + for member in IgnoreMemberVariableParser().parse_groups(notice_groups) + ] notify_config = list( { config["msg_type"] diff --git a/src/backend/services/web/risk/handlers/risk.py b/src/backend/services/web/risk/handlers/risk.py index bf9ff7b5..9fb8b819 100644 --- a/src/backend/services/web/risk/handlers/risk.py +++ b/src/backend/services/web/risk/handlers/risk.py @@ -45,6 +45,7 @@ ) from services.web.risk.handlers import EventHandler from services.web.risk.models import Risk +from services.web.risk.parser import RiskNoticeParser from services.web.risk.render import RiskTitleUndefined from services.web.risk.serializers import CreateRiskSerializer from services.web.strategy_v2.models import Strategy, StrategyTag @@ -220,7 +221,7 @@ def send_risk_notice(self, risk: Risk) -> None: self.send_notice(risk=risk, notice_groups=notice_groups, is_todo=False) # 更新风险的通知人员名单 - risk.notice_users = NoticeGroup.parse_members(notice_groups) + risk.notice_users = RiskNoticeParser(risk=risk).parse_groups(notice_groups) risk.save(update_fields=["notice_users"]) @classmethod @@ -236,5 +237,5 @@ def send_notice(cls, risk: Risk, notice_groups: Union[QuerySet, List[NoticeGroup relate_id=risk.pk, agg_key=f"notice_group:{notice_group.group_id}::strategy:{risk.strategy_id}::is_todo:{is_todo}", msg_type=[c.get("msg_type") for c in notice_group.notice_config if "msg_type" in c], - receivers=notice_group.group_member, + receivers=RiskNoticeParser(risk=risk).parse_group(notice_group), ) diff --git a/src/backend/services/web/risk/parser.py b/src/backend/services/web/risk/parser.py new file mode 100644 index 00000000..540b4828 --- /dev/null +++ b/src/backend/services/web/risk/parser.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +from apps.notice.parser import MemberVariableParser +from services.web.risk.models import Risk + + +class RiskNoticeParser(MemberVariableParser): + def is_skip(self, member: str) -> bool: + """对所有变量进行处理""" + return False + + def __init__(self, risk: Risk): + super().__init__(operator=risk.operator[0] if len(risk.operator) else "") diff --git a/src/backend/services/web/strategy_v2/serializers.py b/src/backend/services/web/strategy_v2/serializers.py index c24196ea..d80de4cb 100644 --- a/src/backend/services/web/strategy_v2/serializers.py +++ b/src/backend/services/web/strategy_v2/serializers.py @@ -22,6 +22,7 @@ from rest_framework import serializers from apps.meta.constants import OrderTypeChoices +from core.serializers import ChoiceListSerializer from services.web.analyze.constants import ( FilterConnector, FilterOperator, @@ -365,16 +366,6 @@ class ListStrategyFieldsResponseSerializer(serializers.Serializer): is_dimension = serializers.BooleanField(label=gettext_lazy("Dimension")) -class ChoiceListSerializer(serializers.Serializer): - """ - Choice List - """ - - label = serializers.CharField(label=gettext_lazy("Label"), allow_blank=True, allow_null=True) - value = serializers.CharField(label=gettext_lazy("Value")) - config = serializers.JSONField(label=gettext_lazy("Config"), required=False) - - class GetStrategyCommonResponseSerializer(serializers.Serializer): """ Get Strategy Common diff --git a/src/backend/version_md/V1.15.0_20240920_en.md b/src/backend/version_md/V1.15.0_20240920_en.md index b1332724..09573581 100644 --- a/src/backend/version_md/V1.15.0_20240920_en.md +++ b/src/backend/version_md/V1.15.0_20240920_en.md @@ -2,4 +2,6 @@ --- -[TODO] +### Feature + +- [ Feature ] Add variables in group \ No newline at end of file diff --git a/src/backend/version_md/V1.15.0_20240920_zh-cn.md b/src/backend/version_md/V1.15.0_20240920_zh-cn.md index 11aca136..420b86a9 100644 --- a/src/backend/version_md/V1.15.0_20240920_zh-cn.md +++ b/src/backend/version_md/V1.15.0_20240920_zh-cn.md @@ -2,4 +2,6 @@ --- -[TODO] +### 新增 + +- [ 新增 ] 通知组内新增变量 \ No newline at end of file