trigger.py 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679
  1. # coding=utf-8
  2. """
  3. @project: MaxKB
  4. @Author:niu
  5. @file: trigger.py
  6. @date:2026/1/14 11:48
  7. @desc:
  8. """
  9. import os.path
  10. import re
  11. from typing import Dict
  12. import uuid_utils.compat as uuid
  13. from django.core import validators
  14. from django.db import models, transaction
  15. from django.db.models import QuerySet
  16. from django.utils.translation import gettext_lazy as _
  17. from rest_framework import serializers
  18. from application.models import Application
  19. from common.db.search import get_dynamics_model, native_page_search, native_search
  20. from common.exception.app_exception import AppApiException
  21. from common.field.common import ObjectField
  22. from common.utils.common import get_file_content
  23. from knowledge.serializers.common import BatchSerializer
  24. from maxkb.conf import PROJECT_DIR
  25. from tools.models import Tool, ToolWorkflow
  26. from trigger.models import TriggerTypeChoices, Trigger, TriggerTaskTypeChoices, TriggerTask, TaskRecord
  27. class BatchActiveSerializer(serializers.Serializer):
  28. id_list = serializers.ListField(required=True, child=serializers.UUIDField(required=True), label=_('id list'))
  29. is_active = serializers.BooleanField(required=True, label=_("is_active"))
  30. def is_valid(self, *, model=None, raise_exception=False):
  31. super().is_valid(raise_exception=True)
  32. if model is not None:
  33. id_list = self.data.get('id_list')
  34. model_list = QuerySet(model).filter(id__in=id_list)
  35. if len(model_list) != len(id_list):
  36. model_id_list = [str(m.id) for m in model_list]
  37. error_id_list = list(filter(lambda row_id: not model_id_list.__contains__(row_id), id_list))
  38. raise AppApiException(500, _('The following id does not exist: %s') % ','.join(map(str, error_id_list)))
  39. class InputField(serializers.Serializer):
  40. source = serializers.CharField(required=True, label=_("source"), validators=[
  41. validators.RegexValidator(regex=re.compile("^custom|reference$"),
  42. message=_("The field only supports custom|reference"), code=500)
  43. ])
  44. value = ObjectField(required=True, label=_("Variable Value"), model_type_list=[str, list])
  45. class ApplicationTaskParameterSerializer(serializers.Serializer):
  46. question = InputField(required=True)
  47. api_input_field_list = serializers.JSONField(required=False)
  48. user_input_field_list = serializers.JSONField(required=False)
  49. image_list = InputField(required=False)
  50. document_list = InputField(required=False)
  51. audio_list = InputField(required=False)
  52. video_list = InputField(required=False)
  53. other_list = InputField(required=False)
  54. @staticmethod
  55. def _validate_input_dict(value, field_name):
  56. if not value:
  57. return value
  58. if not isinstance(value, dict):
  59. raise serializers.ValidationError(_("%s must be a dict") % field_name)
  60. for key, val in value.items():
  61. serializer = InputField(data=val)
  62. if not serializer.is_valid():
  63. raise serializers.ValidationError({f"{field_name}.{key}": serializer.errors})
  64. return value
  65. def validate_api_input_field_list(self, value):
  66. return self._validate_input_dict(value, 'api_input_field_list')
  67. def validate_user_input_field_list(self, value):
  68. return self._validate_input_dict(value, 'user_input_field_list')
  69. class ToolTaskParameterSerializer(serializers.Serializer):
  70. user_input_field_list = serializers.JSONField(required=False)
  71. @staticmethod
  72. def _validate_input_dict(value, field_name):
  73. if not value:
  74. return value
  75. if not isinstance(value, dict):
  76. raise serializers.ValidationError(_("%s must be a dict") % field_name)
  77. for key, val in value.items():
  78. serializer = InputField(data=val)
  79. if not serializer.is_valid():
  80. raise serializers.ValidationError({f"{field_name}.{key}": serializer.errors})
  81. return value
  82. def validate_user_input_field_list(self, value):
  83. return self._validate_input_dict(value, 'user_input_field_list')
  84. class TriggerValidationMixin:
  85. def validate(self, attrs):
  86. # trigger_setting 校验
  87. trigger_type = attrs.get('trigger_type')
  88. trigger_setting = attrs.get('trigger_setting')
  89. if trigger_type and trigger_setting:
  90. if trigger_type == TriggerTypeChoices.SCHEDULED:
  91. self._validate_scheduled_setting(trigger_setting)
  92. elif trigger_type == TriggerTypeChoices.EVENT:
  93. self._validate_event_setting(trigger_setting)
  94. else:
  95. raise AppApiException(500, _('Error trigger type'))
  96. return attrs
  97. @staticmethod
  98. def _validate_required_field(setting, field_name, trigger_type):
  99. if field_name not in setting:
  100. raise serializers.ValidationError({
  101. 'trigger_setting': _('%s type requires %s field') % (trigger_type, field_name)
  102. })
  103. @staticmethod
  104. def _validate_non_empty_array(value, field_name):
  105. if not isinstance(value, list):
  106. raise serializers.ValidationError({
  107. 'trigger_setting': _('%s must be an array') % field_name})
  108. if len(value) == 0:
  109. raise serializers.ValidationError({
  110. 'trigger_setting': _('%s must not be empty') % field_name})
  111. @staticmethod
  112. def _validate_number_range(values, field_name, min_val, max_val):
  113. for val in values:
  114. try:
  115. num = int(str(val))
  116. if num < min_val or num > max_val:
  117. raise ValueError
  118. except (ValueError, TypeError):
  119. raise serializers.ValidationError({
  120. 'trigger_setting': _('%s values must be between %s and %s') % (field_name, min_val, max_val)
  121. })
  122. def _validate_time_array(self, time_list):
  123. self._validate_non_empty_array(time_list, 'time')
  124. for time_str in time_list:
  125. self._validate_time_format(time_str)
  126. @staticmethod
  127. def _validate_time_format(time_str):
  128. import re
  129. pattern = r'^([01]\d|2[0-3]):([0-5]\d)$'
  130. if not re.match(pattern, str(time_str)):
  131. raise serializers.ValidationError({
  132. 'trigger_setting': _('Invalid time format: %s, must be HH:MM (e.g., 09:00)') % time_str
  133. })
  134. def _validate_scheduled_setting(self, setting):
  135. schedule_type = setting.get('schedule_type')
  136. valid_types = ['daily', 'weekly', 'monthly', 'interval', 'cron']
  137. if schedule_type not in valid_types:
  138. raise serializers.ValidationError(
  139. {'trigger_setting': _('schedule_type must be one of %s') % ', '.join(valid_types)
  140. })
  141. if schedule_type == 'daily':
  142. self._validate_daily(setting)
  143. elif schedule_type == 'weekly':
  144. self._validate_weekly(setting)
  145. elif schedule_type == 'monthly':
  146. self._validate_monthly(setting)
  147. elif schedule_type == 'interval':
  148. self._validate_interval(setting)
  149. elif schedule_type == 'cron':
  150. self._validate_cron(setting)
  151. def _validate_daily(self, setting):
  152. self._validate_required_field(setting, 'time', 'daily')
  153. self._validate_time_array(setting['time'])
  154. def _validate_weekly(self, setting):
  155. self._validate_required_field(setting, 'days', 'weekly')
  156. self._validate_required_field(setting, 'time', 'weekly')
  157. days = setting['days']
  158. self._validate_non_empty_array(days, 'days')
  159. self._validate_number_range(days, 'days', 1, 7)
  160. self._validate_time_array(setting['time'])
  161. def _validate_monthly(self, setting):
  162. self._validate_required_field(setting, 'days', 'monthly')
  163. self._validate_required_field(setting, 'time', 'monthly')
  164. days = setting['days']
  165. self._validate_non_empty_array(days, 'days')
  166. self._validate_number_range(days, 'days', 1, 31)
  167. self._validate_time_array(setting['time'])
  168. def _validate_interval(self, setting):
  169. self._validate_required_field(setting, 'interval_value', 'interval')
  170. self._validate_required_field(setting, 'interval_unit', 'interval')
  171. interval_value = setting['interval_value']
  172. interval_unit = setting['interval_unit']
  173. try:
  174. value_int = int(interval_value)
  175. if value_int < 1:
  176. raise ValueError
  177. except (ValueError, TypeError):
  178. raise serializers.ValidationError({
  179. 'trigger_setting': _('interval_value must be an integer greater than or equal to 1')
  180. })
  181. valid_units = ['minutes', 'hours']
  182. if interval_unit not in valid_units:
  183. raise serializers.ValidationError({
  184. 'trigger_setting': _('interval_unit must be one of %s') % ', '.join(valid_units)
  185. })
  186. @staticmethod
  187. def _validate_cron(setting):
  188. from apscheduler.triggers.cron import CronTrigger
  189. cron_expression: str = setting.get('cron_expression')
  190. if not cron_expression:
  191. raise serializers.ValidationError({
  192. 'trigger_setting': _('cron type requires cron_expression field')
  193. })
  194. try:
  195. CronTrigger.from_crontab(cron_expression.strip())
  196. except ValueError:
  197. raise serializers.ValidationError({
  198. 'trigger_setting': _('Invalid cron expression: %s') % cron_expression
  199. })
  200. @staticmethod
  201. def _validate_event_setting(setting):
  202. body = setting.get('body')
  203. if body is not None and not isinstance(body, list):
  204. raise serializers.ValidationError({
  205. 'trigger_setting': _('body must be an array')
  206. })
  207. class TriggerTaskCreateRequest(serializers.Serializer):
  208. source_type = serializers.ChoiceField(required=True, choices=TriggerTaskTypeChoices)
  209. source_id = serializers.CharField(required=True, label=_('source_id'))
  210. is_active = serializers.BooleanField(required=False, label=_('Is active'))
  211. meta = serializers.DictField(default=dict, required=False)
  212. parameter = serializers.DictField(default=dict, required=False)
  213. def validate(self, attrs):
  214. source_type = attrs.get('source_type')
  215. parameter = attrs.get('parameter')
  216. if source_type == TriggerTaskTypeChoices.APPLICATION:
  217. serializer = ApplicationTaskParameterSerializer(data=parameter)
  218. serializer.is_valid(raise_exception=True)
  219. attrs['parameter'] = serializer.validated_data
  220. if source_type == TriggerTaskTypeChoices.TOOL:
  221. serializer = ToolTaskParameterSerializer(data=parameter)
  222. serializer.is_valid(raise_exception=True)
  223. attrs['parameter'] = serializer.validated_data
  224. return attrs
  225. class TriggerTaskEditRequest(serializers.Serializer):
  226. source_type = serializers.ChoiceField(required=False, choices=TriggerTaskTypeChoices)
  227. source_id = serializers.CharField(required=False, label=_('source_id'))
  228. is_active = serializers.BooleanField(required=False, label=_('Is active'))
  229. meta = serializers.DictField(default=dict, required=False)
  230. parameter = serializers.DictField(default=dict, required=False)
  231. def validate(self, attrs):
  232. source_type = attrs.get('source_type')
  233. parameter = attrs.get('parameter')
  234. if source_type == TriggerTaskTypeChoices.APPLICATION:
  235. serializer = ApplicationTaskParameterSerializer(data=parameter)
  236. serializer.is_valid(raise_exception=True)
  237. attrs['parameter'] = serializer.validated_data
  238. if source_type == TriggerTaskTypeChoices.TOOL:
  239. serializer = ToolTaskParameterSerializer(data=parameter)
  240. serializer.is_valid(raise_exception=True)
  241. attrs['parameter'] = serializer.validated_data
  242. return attrs
  243. class TriggerEditRequest(TriggerValidationMixin, serializers.Serializer):
  244. name = serializers.CharField(required=False, label=_('trigger name'))
  245. desc = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_('trigger description'))
  246. trigger_type = serializers.ChoiceField(required=False, choices=TriggerTypeChoices)
  247. trigger_setting = serializers.DictField(required=False, label=_("trigger setting"))
  248. meta = serializers.DictField(default=dict, required=False)
  249. trigger_task = TriggerTaskEditRequest(many=True, required=False)
  250. class TriggerCreateRequest(TriggerValidationMixin, serializers.Serializer):
  251. id = serializers.UUIDField(required=True, label=_("Trigger ID"))
  252. name = serializers.CharField(required=True, label=_('trigger name'))
  253. desc = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_('trigger description'))
  254. trigger_type = serializers.ChoiceField(required=True, choices=TriggerTypeChoices)
  255. trigger_setting = serializers.DictField(required=True, label=_("trigger setting"))
  256. meta = serializers.DictField(default=dict, required=False)
  257. is_active = serializers.BooleanField(required=False, label=_('Is active'))
  258. trigger_task = TriggerTaskCreateRequest(many=True)
  259. class TriggerModelSerializer(serializers.ModelSerializer):
  260. class Meta:
  261. model = Trigger
  262. fields = "__all__"
  263. class TriggerTaskModelSerializer(serializers.ModelSerializer):
  264. class Meta:
  265. model = TriggerTask
  266. fields = "__all__"
  267. class ApplicationTriggerTaskSerializer(serializers.ModelSerializer):
  268. class Meta:
  269. model = Application
  270. fields = ['id', 'name', 'work_flow', 'icon', 'type']
  271. class ToolTriggerTaskSerializer(serializers.ModelSerializer):
  272. class Meta:
  273. model = Tool
  274. fields = ['id', 'name', 'input_field_list', 'icon']
  275. class TriggerResponse(serializers.ModelSerializer):
  276. class Meta:
  277. model = Trigger
  278. fields = "__all__"
  279. class TriggerSerializer(serializers.Serializer):
  280. workspace_id = serializers.CharField(required=True, label=_('workspace id'))
  281. user_id = serializers.UUIDField(required=True, label=_("User ID"))
  282. @transaction.atomic
  283. def insert(self, instance, with_valid=True):
  284. from trigger.handler.simple_tools import deploy
  285. if with_valid:
  286. self.is_valid(raise_exception=True)
  287. serializer = TriggerCreateRequest(data=instance)
  288. serializer.is_valid(raise_exception=True)
  289. valid_data = serializer.validated_data
  290. trigger_id = valid_data.get('id') if valid_data.get('id') else uuid.uuid7()
  291. trigger_model = Trigger(
  292. id=trigger_id,
  293. name=valid_data.get('name'),
  294. workspace_id=self.data.get('workspace_id'),
  295. desc=valid_data.get('desc') or '',
  296. trigger_type=valid_data.get('trigger_type'),
  297. trigger_setting=valid_data.get('trigger_setting'),
  298. meta=valid_data.get('meta', {}),
  299. is_active=valid_data.get('is_active') or False,
  300. user_id=self.data.get('user_id'),
  301. )
  302. trigger_model.save()
  303. trigger_tasks = valid_data.get('trigger_task')
  304. if trigger_tasks:
  305. is_active_map = self.batch_get_source_active_status(trigger_tasks)
  306. trigger_task_models = [
  307. TriggerTask(
  308. id=uuid.uuid7(),
  309. trigger_id=trigger_id,
  310. source_type=task_data.get('source_type'),
  311. source_id=task_data.get('source_id'),
  312. is_active=is_active_map.get((task_data.get('source_type'), task_data.get('source_id'))) or False,
  313. parameter=task_data.get('parameter', {}),
  314. meta=task_data.get('meta', {})
  315. )
  316. for task_data in trigger_tasks
  317. ]
  318. TriggerTask.objects.bulk_create(trigger_task_models)
  319. else:
  320. raise AppApiException(500, _('Trigger task can not be empty'))
  321. if trigger_model.is_active:
  322. deploy(TriggerModelSerializer(trigger_model).data, **{})
  323. return TriggerResponse(trigger_model).data
  324. @staticmethod
  325. def batch_get_source_active_status(trigger_tasks: list) -> Dict[tuple, bool]:
  326. """
  327. 批量查询所有 source 的 is_active 状态
  328. 返回: {(source_type, source_id): is_active}
  329. """
  330. config = {
  331. TriggerTaskTypeChoices.APPLICATION: (Application, 'is_publish'),
  332. TriggerTaskTypeChoices.TOOL: (Tool, 'is_active'),
  333. }
  334. source_ids_by_type = {}
  335. for task_data in trigger_tasks:
  336. source_type = task_data.get('source_type')
  337. source_id = task_data.get('source_id')
  338. if source_type not in config:
  339. raise AppApiException(500, _('Error source type'))
  340. if source_type not in source_ids_by_type:
  341. source_ids_by_type[source_type] = []
  342. source_ids_by_type[source_type].append(source_id)
  343. is_active_map = {}
  344. for source_type, source_ids in source_ids_by_type.items():
  345. source_model, field = config[source_type]
  346. source_query_set = QuerySet(source_model).filter(id__in=source_ids).values('id', field)
  347. for source in source_query_set:
  348. is_active_map[(source_type, str(source['id']))] = source[field]
  349. return is_active_map
  350. @staticmethod
  351. def is_active_source(source_type: str, source_id: str):
  352. config = {
  353. TriggerTaskTypeChoices.APPLICATION: (Application, 'is_publish'),
  354. TriggerTaskTypeChoices.TOOL: (Tool, 'is_active'),
  355. }
  356. if source_type not in config:
  357. raise AppApiException(500, _('Error source type'))
  358. source_model, field = config.get(TriggerTaskTypeChoices(source_type))
  359. source = QuerySet(source_model).filter(id=source_id).first()
  360. if not source:
  361. raise AppApiException(500, _('%s id does not exist') % source_type)
  362. return getattr(source, field)
  363. class Batch(serializers.Serializer):
  364. workspace_id = serializers.CharField(required=True, label=_('workspace id'))
  365. user_id = serializers.UUIDField(required=True, label=_("User ID"))
  366. def is_valid(self, *, raise_exception=False):
  367. super().is_valid(raise_exception=True)
  368. @transaction.atomic
  369. def batch_delete(self, instance: Dict, with_valid=True):
  370. from trigger.handler.simple_tools import deploy, undeploy
  371. if with_valid:
  372. BatchSerializer(data=instance).is_valid(model=Trigger, raise_exception=True)
  373. self.is_valid(raise_exception=True)
  374. workspace_id = self.data.get("workspace_id")
  375. trigger_id_list = instance.get("id_list")
  376. for trigger_id in trigger_id_list:
  377. trigger = QuerySet(Trigger).filter(id=trigger_id).first()
  378. undeploy(TriggerModelSerializer(trigger).data, **{})
  379. TaskRecord.objects.filter(trigger_id__in=trigger_id_list).delete()
  380. TriggerTask.objects.filter(trigger_id__in=trigger_id_list).delete()
  381. Trigger.objects.filter(workspace_id=workspace_id, id__in=trigger_id_list).delete()
  382. return True
  383. @transaction.atomic
  384. def batch_switch(self, instance: Dict, with_valid=True):
  385. from trigger.handler.simple_tools import deploy, undeploy
  386. if with_valid:
  387. BatchActiveSerializer(data=instance).is_valid(model=Trigger, raise_exception=True)
  388. self.is_valid(raise_exception=True)
  389. workspace_id = self.data.get("workspace_id")
  390. trigger_id_list = instance.get("id_list")
  391. is_active = instance.get("is_active")
  392. Trigger.objects.filter(workspace_id=workspace_id, id__in=trigger_id_list, is_active=not is_active).update(
  393. is_active=is_active)
  394. if is_active:
  395. for trigger_id in trigger_id_list:
  396. trigger = QuerySet(Trigger).filter(id=trigger_id).first()
  397. deploy(TriggerModelSerializer(trigger).data, **{})
  398. else:
  399. for trigger_id in trigger_id_list:
  400. trigger = QuerySet(Trigger).filter(id=trigger_id).first()
  401. undeploy(TriggerModelSerializer(trigger).data, **{})
  402. return True
  403. class TriggerOperateSerializer(serializers.Serializer):
  404. trigger_id = serializers.UUIDField(required=True, label=_('trigger id'))
  405. user_id = serializers.UUIDField(required=True, label=_("User ID"))
  406. workspace_id = serializers.CharField(required=True, label=_('workspace id'))
  407. def is_valid(self, *, raise_exception=False):
  408. super().is_valid(raise_exception=True)
  409. workspace_id = self.data.get('workspace_id')
  410. query_set = QuerySet(Trigger).filter(id=self.data.get('trigger_id'))
  411. if workspace_id:
  412. query_set = query_set.filter(workspace_id=workspace_id)
  413. if not query_set.exists():
  414. raise AppApiException(500, _('Trigger id does not exist'))
  415. @transaction.atomic
  416. def edit(self, instance: Dict, with_valid=True):
  417. from trigger.handler.simple_tools import deploy, undeploy
  418. if with_valid:
  419. self.is_valid()
  420. TriggerEditRequest(data=instance).is_valid(raise_exception=True)
  421. trigger_id = self.data.get('trigger_id')
  422. workspace_id = self.data.get('workspace_id')
  423. trigger = Trigger.objects.filter(workspace_id=workspace_id, id=trigger_id).first()
  424. if not trigger:
  425. raise serializers.ValidationError(_('Trigger not found'))
  426. trigger_direct_edit_field_list = ['name', 'desc', 'trigger_type', 'trigger_setting', 'meta', 'is_active']
  427. trigger_deploy_edit_field_list = ['trigger_type', 'trigger_setting', 'is_active']
  428. # is need to redeploy
  429. need_redeploy = any(field in instance for field in trigger_deploy_edit_field_list)
  430. for field in trigger_direct_edit_field_list:
  431. if field in instance:
  432. trigger.__setattr__(field, instance.get(field))
  433. trigger.save()
  434. # 处理trigger task
  435. trigger_tasks = instance.get('trigger_task')
  436. if trigger_tasks is not None:
  437. # 检查是否为空列表
  438. if not trigger_tasks:
  439. raise serializers.ValidationError(_('Trigger must have at least one task'))
  440. is_active_map = TriggerSerializer.batch_get_source_active_status(trigger_tasks)
  441. trigger_task_model_list = [TriggerTask(
  442. id=task_data.get('id') or uuid.uuid7(),
  443. trigger_id=trigger_id,
  444. source_type=task_data.get('source_type'),
  445. source_id=task_data.get('source_id'),
  446. is_active=is_active_map.get((task_data.get('source_type'), task_data.get('source_id'))) or False,
  447. parameter=task_data.get('parameter', []),
  448. meta=task_data.get('meta', {})
  449. ) for task_data in trigger_tasks]
  450. TriggerTask.objects.filter(trigger_id=trigger_id).delete()
  451. TriggerTask.objects.bulk_create(trigger_task_model_list)
  452. else:
  453. # 用户没提交 trigger_task 字段,确保数据库中有 task
  454. if not TriggerTask.objects.filter(trigger_id=trigger_id).exists():
  455. raise serializers.ValidationError(_('Trigger must have at least one task'))
  456. # 重新部署触发器任务
  457. if need_redeploy:
  458. if trigger.is_active and trigger.trigger_type == 'SCHEDULED':
  459. deploy(TriggerModelSerializer(trigger).data, **{})
  460. else:
  461. undeploy(TriggerModelSerializer(trigger).data, **{})
  462. return self.one(with_valid=False)
  463. def delete(self):
  464. from trigger.handler.simple_tools import deploy, undeploy
  465. self.is_valid(raise_exception=True)
  466. trigger_id = self.data.get('trigger_id')
  467. trigger = QuerySet(Trigger).filter(workspace_id=self.data.get('workspace_id'), id=trigger_id).first()
  468. if trigger:
  469. undeploy(TriggerModelSerializer(trigger).data, **{})
  470. TaskRecord.objects.filter(trigger_id=trigger_id).delete()
  471. TriggerTask.objects.filter(trigger_id=trigger_id).delete()
  472. Trigger.objects.filter(id=trigger_id).delete()
  473. return True
  474. def one(self, with_valid=True):
  475. if with_valid:
  476. self.is_valid()
  477. trigger_id = self.data.get('trigger_id')
  478. workspace_id = self.data.get('workspace_id')
  479. trigger = QuerySet(Trigger).filter(workspace_id=workspace_id, id=trigger_id).first()
  480. trigger_tasks = list(QuerySet(TriggerTask).filter(trigger_id=trigger_id))
  481. application_ids = []
  482. tool_ids = []
  483. for task in trigger_tasks:
  484. if task.source_type == TriggerTaskTypeChoices.APPLICATION:
  485. application_ids.append(task.source_id)
  486. elif task.source_type == TriggerTaskTypeChoices.TOOL:
  487. tool_ids.append(task.source_id)
  488. trigger_task_list = TriggerTaskModelSerializer(trigger_tasks, many=True).data
  489. application_task_list = []
  490. if application_ids:
  491. applications = Application.objects.filter(workspace_id=workspace_id, id__in=application_ids)
  492. application_task_list = ApplicationTriggerTaskSerializer(applications, many=True).data
  493. tool_task_list = []
  494. if tool_ids:
  495. tools = Tool.objects.filter(workspace_id=workspace_id, id__in=tool_ids)
  496. workflows = ToolWorkflow.objects.filter(
  497. tool_id__in=tools.filter(tool_type='WORKFLOW').values_list('id', flat=True),
  498. is_publish=True
  499. )
  500. workflow_dict = {wf.tool_id: wf.work_flow for wf in workflows}
  501. tool_task_list = []
  502. for tool in tools:
  503. tool_data = {
  504. 'id': str(tool.id),
  505. 'name': tool.name,
  506. 'input_field_list': tool.input_field_list,
  507. 'icon': tool.icon,
  508. 'tool_type': tool.tool_type
  509. }
  510. # 如果是工作流类型,添加 work_flow 字段
  511. if tool.tool_type == 'WORKFLOW':
  512. tool_data['work_flow'] = workflow_dict.get(tool.id)
  513. tool_task_list.append(tool_data)
  514. return {
  515. **TriggerModelSerializer(trigger).data,
  516. 'trigger_task': trigger_task_list,
  517. 'application_task_list': application_task_list,
  518. 'tool_task_list': tool_task_list,
  519. }
  520. class TriggerQuerySerializer(serializers.Serializer):
  521. name = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_('Trigger name'))
  522. type = serializers.CharField(required=False, allow_blank=True, allow_null=True, label=_('Trigger type'))
  523. is_active = serializers.BooleanField(required=False, allow_null=True, label=_('Is active'))
  524. task = serializers.CharField(required=False, allow_blank=True, allow_null=True, label=_('Trigger task'))
  525. create_user = serializers.CharField(required=False, allow_blank=True, allow_null=True, label=_('Create user'))
  526. workspace_id = serializers.CharField(required=True, label=_('workspace id'))
  527. def get_query_set(self):
  528. trigger_query_set = QuerySet(
  529. model=get_dynamics_model({
  530. 't.name': models.CharField(),
  531. 'trigger_type': models.CharField(),
  532. 't.workspace_id': models.CharField(),
  533. 't.is_active': models.BooleanField(),
  534. 't.user_id': models.CharField(),
  535. }))
  536. task_query_set = QuerySet(model=get_dynamics_model({
  537. 'trigger_task_str': models.CharField(),
  538. }))
  539. trigger_query_set = trigger_query_set.filter(**{'t.workspace_id': self.data.get("workspace_id")})
  540. if self.data.get("name"):
  541. trigger_query_set = trigger_query_set.filter(**{'t.name__icontains': self.data.get("name")})
  542. if self.data.get("type"):
  543. trigger_query_set = trigger_query_set.filter(trigger_type=self.data.get("type"))
  544. if self.data.get("is_active") is not None:
  545. trigger_query_set = trigger_query_set.filter(**{"t.is_active": self.data.get("is_active")})
  546. if self.data.get("task"):
  547. task_query_set = task_query_set.filter(trigger_task_str__icontains=self.data.get("task"))
  548. if self.data.get("create_user"):
  549. trigger_query_set = trigger_query_set.filter(**{"t.user_id": self.data.get("create_user")})
  550. return {"trigger_query_set": trigger_query_set, "task_query_set": task_query_set}
  551. def page(self, current_page: int, page_size: int, with_valid=True):
  552. if with_valid:
  553. self.is_valid(raise_exception=True)
  554. return native_page_search(current_page, page_size, self.get_query_set(), get_file_content(
  555. os.path.join(PROJECT_DIR, "apps", "trigger", "sql", "get_trigger_page_list.sql")
  556. ))
  557. def list(self, with_valid=True):
  558. if with_valid:
  559. self.is_valid(raise_exception=True)
  560. return native_search(self.get_query_set(), select_string=get_file_content(
  561. os.path.join(PROJECT_DIR, "apps", "trigger", "sql", "get_trigger_page_list.sql")))