-
Notifications
You must be signed in to change notification settings - Fork 18
feat: 增加节点循环执行机制 --story=130003551 #585
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
base: feat/template_loop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
|
|
||
| (function () { | ||
| $.atoms.loop = [ | ||
| { | ||
| tag_code: "loop", | ||
| type: "input", | ||
| attrs: { | ||
| name: gettext("循环变量"), | ||
| hookable: true, | ||
| validation: [] | ||
| } | ||
| }, | ||
| ] | ||
| })(); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| """ | ||
| TencentBlueKing is pleased to support the open source community by making | ||
| 蓝鲸流程引擎服务 (BlueKing Flow Engine Service) available. | ||
| Copyright (C) 2024 THL A29 Limited, | ||
| a Tencent company. All rights reserved. | ||
| Licensed under the MIT License (the "License"); | ||
| you may not use this file except in compliance with the License. | ||
| You may obtain a copy of the License at http://opensource.org/licenses/MIT | ||
| Unless required by applicable law or agreed to in writing, | ||
| software distributed under the License is distributed on | ||
| an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, | ||
| either express or implied. See the License for the | ||
| specific language governing permissions and limitations under the License. | ||
| 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 django.conf import settings | ||
| from django.utils.translation import ugettext_lazy as _ | ||
| from pipeline.core.data.var import LazyVariable | ||
| from pipeline.core.flow.io import StringItemSchema | ||
|
|
||
| from bkflow.pipeline_plugins.variables.base import SelfExplainVariable | ||
|
|
||
|
|
||
| class Loop(LazyVariable, SelfExplainVariable): | ||
| code = "loop" | ||
| name = _("循环变量") | ||
| type = "general" | ||
| tag = "loop.loop" | ||
| form = "{}variables/{}.js".format(settings.STATIC_URL, code) | ||
| schema = StringItemSchema(description=_("循环变量")) | ||
|
|
||
| def get_value(self): | ||
| # 循环节点因引用 | ||
| if hasattr(self, "inner_loop") and self.inner_loop != -1: | ||
| return self.value.split(",")[self.inner_loop - 1] | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚨 索引越界风险:当 inner_loop 超过数组长度时会抛出 IndexError。建议添加边界检查: values = self.value.split(",")
if self.inner_loop > len(values):
raise ValueError(f"inner_loop {self.inner_loop} exceeds values length {len(values)}")
return values[self.inner_loop - 1] |
||
| # 普通节点引用 | ||
| return self.value | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -42,6 +42,15 @@ def validate_web_pipeline_tree(web_pipeline_tree): | |
| # constants key pattern validate | ||
| key_validation_errors = [] | ||
| context_values = [] | ||
|
|
||
| for name, act in web_pipeline_tree["activities"].items(): | ||
| loop_config = act.get("loop_config", {}) | ||
| if not loop_config.get("enable", False): | ||
| continue | ||
| loop_params = loop_config.get("loop_params", []) | ||
| if loop_config["loop_times"] != min([len(param.split(",")) for key, param in loop_params.items()]): | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚨 空列表异常:当 loop_params 为空字典时,min() 会抛出 ValueError。建议先检查是否为空: if not loop_params:
raise exceptions.ParserWebTreeException("loop_params is empty")
lengths = [len(param.split(",")) for key, param in loop_params.items()]
if loop_config["loop_times"] != min(lengths):
raise exceptions.ParserWebTreeException("loop times not matched") |
||
| raise exceptions.ParserWebTreeException("loop times not matched") | ||
|
|
||
| classification = classify_constants(web_pipeline_tree["constants"], is_subprocess=False) | ||
| for key, const in web_pipeline_tree["constants"].items(): | ||
| key_value = const.get("key") | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -398,14 +398,16 @@ def retry(self, operator: str, *args, **kwargs) -> OperationResult: | |
| api_result = bamboo_engine_api.get_data(runtime=self.runtime, node_id=self.node_id) | ||
| if not api_result.result: | ||
| return api_result | ||
| loop_retry = kwargs.get("loop", False) | ||
| return bamboo_engine_api.retry_node( | ||
| runtime=self.runtime, node_id=self.node_id, data=kwargs.get("inputs") or None | ||
| runtime=self.runtime, node_id=self.node_id, data=kwargs.get("inputs") or None, loop_retry=loop_retry | ||
| ) | ||
|
|
||
| @record_operation(RecordType.task_node.name, TaskOperationType.skip.name, TaskOperationSource.app.name) | ||
| @uniform_task_operation_result | ||
| def skip(self, operator: str, *args, **kwargs) -> OperationResult: | ||
| return bamboo_engine_api.skip_node(runtime=self.runtime, node_id=self.node_id) | ||
| loop_skip = kwargs.get("loop", False) | ||
| return bamboo_engine_api.skip_node(runtime=self.runtime, node_id=self.node_id, loop_skip=loop_skip) | ||
|
|
||
| @record_operation(RecordType.task_node.name, TaskOperationType.callback.name, TaskOperationSource.api.name) | ||
| @uniform_task_operation_result | ||
|
|
@@ -461,8 +463,9 @@ def get_node_detail( | |
| detail = detail[self.node_id] | ||
| # 默认只请求最后一次循环结果 | ||
| format_bamboo_engine_status(detail) | ||
| node_info = self.runtime.get_node(self.node_id) | ||
| if loop is None or int(loop) >= detail["loop"]: | ||
| loop = detail["loop"] | ||
| loop = detail["loop"] if not node_info.loop_strategy else -1 | ||
| hist_result = bamboo_engine_api.get_node_histories(runtime=runtime, node_id=self.node_id, loop=loop) | ||
| if not hist_result: | ||
| logger.exception("bamboo_engine_api.get_node_histories fail") | ||
|
|
@@ -482,8 +485,21 @@ def get_node_detail( | |
| detail["version"] = hist_result.data[-1]["version"] | ||
|
|
||
| for hist in detail["histories"]: | ||
| # 重试记录必然是因为失败才重试 | ||
| hist.setdefault("state", bamboo_engine_states.FAILED) | ||
| raw_inputs = hist["inputs"].get("subprocess") | ||
| if raw_inputs: | ||
| inputs = raw_inputs["constants"] | ||
| inputs = {key[2:-1]: value.get("value") for key, value in inputs.items()} | ||
| hist["inputs"] = inputs | ||
|
|
||
| # 重试记录必然是因为失败才重试,设置了循环策略的节点只有成功才能接着循环 | ||
| if node_info.loop_strategy: | ||
| if hist["skip"] or hist["outputs"].get("_result"): | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if hist["skip"] or (hist["outputs"].get("_result") is True): |
||
| state = bamboo_engine_states.FINISHED | ||
| else: | ||
| state = bamboo_engine_states.FAILED | ||
| else: | ||
| state = bamboo_engine_states.FAILED | ||
| hist.setdefault("state", state) | ||
| hist["history_id"] = hist["id"] | ||
| format_bamboo_engine_status(hist) | ||
| # 节点未执行 | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.