diff --git a/frontend/desktop/src/components/common/TemplateCanvas/ToolPanel/index.vue b/frontend/desktop/src/components/common/TemplateCanvas/ToolPanel/index.vue
index b4299a7edc..1efc7007c1 100644
--- a/frontend/desktop/src/components/common/TemplateCanvas/ToolPanel/index.vue
+++ b/frontend/desktop/src/components/common/TemplateCanvas/ToolPanel/index.vue
@@ -27,6 +27,7 @@
{{ zoomRatio + '%' }}
- {{ '--' }}
+ {{ $t('执行历史') }}
@@ -747,6 +747,8 @@
}
.empty-text {
padding: 5px;
+ color: #ccc;
+ cursor: not-allowed;
}
}
diff --git a/frontend/desktop/src/pages/task/TaskExecute/ExecuteInfo.vue b/frontend/desktop/src/pages/task/TaskExecute/ExecuteInfo.vue
index 3181e16b05..26c2f3387f 100644
--- a/frontend/desktop/src/pages/task/TaskExecute/ExecuteInfo.vue
+++ b/frontend/desktop/src/pages/task/TaskExecute/ExecuteInfo.vue
@@ -30,7 +30,8 @@
+ @click="$emit('click', $event)">
@@ -134,7 +142,10 @@
node.expanded = true
return
}
- if (!node.expanded) {
+ if (node.expanded) {
+ const activeId = node.parentId ? node.id + '-' + node.parentId : node.id
+ node.expanded = activeId !== this.activeId
+ } else {
node.expanded = true
}
this.$emit('click', node)
@@ -201,6 +212,11 @@
background: #f0f1f5;
border-color: #c4c6cc;
}
+ &.empty-condition {
+ color: #63656e;
+ background: #f5f7fa;
+ border-color: #dcdee5;
+ }
}
.common-icon-converge-node,
@@ -258,6 +274,7 @@
}
&.expanded {
.common-icon-next-triangle-shape {
+ top: -3px;
transform: rotate(90deg);
transition: transform .2s;
}
@@ -266,9 +283,19 @@
.node-name {
flex: 1;
+ display: flex;
+ align-items: center;
overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
+ .name {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+ .empty-branch {
+ flex-shrink: 0;
+ font-size: 12px;
+ color: #979ba5;
+ }
}
}
.node-active {
diff --git a/frontend/desktop/src/pages/task/TaskExecute/TaskOperation.vue b/frontend/desktop/src/pages/task/TaskExecute/TaskOperation.vue
index d1cf4ff3d0..183bc8bd3a 100644
--- a/frontend/desktop/src/pages/task/TaskExecute/TaskOperation.vue
+++ b/frontend/desktop/src/pages/task/TaskExecute/TaskOperation.vue
@@ -68,7 +68,7 @@
:width="sidebarWidth"
:quick-close="true"
:before-close="onBeforeClose"
- @hidden="onHiddenSideslider">
+ @hidden="onHiddenSideSlider">
-
+
@@ -98,7 +98,8 @@
@packUp="packUp">
{
+ return item.type !== 'ServiceActivity' || item.component?.code === 'subprocess_plugin'
+ })
}
},
mounted () {
@@ -507,12 +515,14 @@
this.onOperationClick('execute')
})
}
+ window.addEventListener('resize', this.onWindowResize, false)
},
beforeDestroy () {
if (source) {
source.cancel('cancelled')
}
this.cancelTaskStatusTimer()
+ window.removeEventListener('resize', this.onWindowResize, false)
},
methods: {
...mapActions('task/', [
@@ -857,15 +867,12 @@
}
const res = await this.instanceNodeSkip(data)
if (res.result) {
- this.isNodeInfoPanelShow = false
- this.nodeInfoType = ''
this.$bkMessage({
message: i18n.t('跳过成功'),
theme: 'success'
})
- setTimeout(() => {
- this.setTaskStatusTimer()
- }, 1000)
+ // 更新节点执行信息
+ this.updateNodeExecuteInfo()
}
} catch (e) {
console.log(e)
@@ -909,11 +916,8 @@
message: i18n.t('强制终止执行成功'),
theme: 'success'
})
- this.isNodeInfoPanelShow = false
- this.nodeInfoType = ''
- setTimeout(() => {
- this.setTaskStatusTimer()
- }, 1000)
+ // 更新节点执行信息
+ this.updateNodeExecuteInfo()
}
} catch (e) {
console.log(e)
@@ -962,11 +966,8 @@
message: i18n.t('继续成功'),
theme: 'success'
})
- this.isNodeInfoPanelShow = false
- this.nodeInfoType = ''
- setTimeout(() => {
- this.setTaskStatusTimer()
- }, 1000)
+ // 更新节点执行信息
+ this.updateNodeExecuteInfo()
}
} catch (e) {
console.log(e)
@@ -1103,6 +1104,8 @@
this.openNodeInfoPanel('modifyParams', isSubProcessNode ? i18n.t('重试子流程') : i18n.t('重试节点'))
this.retryNodeId = id
}
+ // 记录是否由【节点详情侧栏】打开的【重试侧栏】
+ this.isSourceDetailSideBar = !!info
} catch (error) {
console.warn(error)
}
@@ -1263,11 +1266,22 @@
data.node_id = node_id
}
await this.onRetryTask(data)
- this.isNodeInfoPanelShow = false
- this.retryNodeId = undefined
- // 重新轮询任务状态
- this.setTaskStatusTimer()
this.updateNodeActived(this.nodeDetailConfig.id, false)
+ // 重新打开详情面板
+ if (this.isSourceDetailSideBar) {
+ setTimeout(() => {
+ // 重新轮询任务状态
+ Promise.resolve(this.loadTaskStatus()).then(() => {
+ this.onNodeClick(data.node_id)
+ this.retryNodeId = undefined
+ this.isSourceDetailSideBar = false
+ })
+ }, 1000)
+ } else {
+ this.retryNodeId = undefined
+ // 更新节点执行信息
+ this.updateNodeExecuteInfo()
+ }
} catch (error) {
console.warn(error)
} finally {
@@ -1351,9 +1365,9 @@
return
}
+ this.approval.pending = true
this.$refs.approvalForm.validate().then(async () => {
try {
- this.approval.pending = true
const { id, is_passed, message } = this.approval
const params = {
is_passed,
@@ -1362,8 +1376,8 @@
task_id: this.subProcessTaskId || this.instance_id,
node_id: id
}
- // 如果存在子流程任务节点时则不需要传subprocess_id
- if (!this.subProcessTaskId) {
+ // 如果存在子流程任务节点时则需要传subprocess_id
+ if (this.subProcessTaskId) {
let { subprocess_stack: stack } = this.nodeDetailConfig
if (stack) {
stack = JSON.parse(stack)
@@ -1375,11 +1389,19 @@
this.approval.is_passed = true
this.approval.message = ''
this.approval.dialogShow = false
+ this.$bkMessage({
+ message: i18n.t('节点审批成功'),
+ theme: 'success'
+ })
+ // 更新节点执行信息
+ this.updateNodeExecuteInfo()
} catch (e) {
console.error(e)
} finally {
this.approval.pending = false
}
+ }, () => {
+ this.approval.pending = false
})
},
onApprovalCancel () {
@@ -1388,21 +1410,23 @@
this.approval.message = ''
this.approval.dialogShow = false
},
- onPauseClick (id, taskId, independent) {
- this.taskPause(id, taskId, independent)
- this.isNodeInfoPanelShow = false
- this.nodeInfoType = ''
- setTimeout(() => {
- this.setTaskStatusTimer()
- }, 1000)
+ async onPauseClick (id, taskId) {
+ try {
+ await this.taskPause(true, id, taskId)
+ // 更新节点执行信息
+ this.updateNodeExecuteInfo()
+ } catch (error) {
+ console.warn(error)
+ }
},
- onContinueClick (id, taskId, independent) {
- this.taskResume(id, taskId, independent)
- this.isNodeInfoPanelShow = false
- this.nodeInfoType = ''
- setTimeout(() => {
- this.setTaskStatusTimer()
- }, 1000)
+ async onContinueClick (id, taskId) {
+ try {
+ await this.taskResume(true, id, taskId)
+ // 更新节点执行信息
+ this.updateNodeExecuteInfo()
+ } catch (error) {
+ console.warn(error)
+ }
},
onCloseConfigPanel () {
this.isShowConditionEdit = false
@@ -1605,7 +1629,11 @@
// 分支,条件并行
if (['ExclusiveGateway', 'ConditionalParallelGateway'].includes(nodeConfig.type)) {
treeItem.gatewayId = parentOrdered ? gatewayId : id
- this.getGatewayConvergeNodes(id, id, this.convergeInfo)
+ this.getGatewayConvergeNodes({
+ id,
+ parentId: id,
+ convergeInfo: this.convergeInfo
+ })
const loopList = [] // 需要打回的node的incoming
targetNodes.forEach(item => {
const curNode = taskAndGwNodeMap[item]
@@ -1626,7 +1654,8 @@
expanded: false,
target: flows[key].target,
gatewayId: id,
- children: []
+ children: [],
+ taskId
}
})
// 添加条件分支默认节点
@@ -1644,12 +1673,17 @@
expanded: false,
target: flows[flow_id].target,
gatewayId: id,
- children: []
+ children: [],
+ taskId
})
}
} else if (nodeConfig.type === 'ParallelGateway') {
treeItem.gatewayId = gatewayId || id
- this.getGatewayConvergeNodes(id, id, this.convergeInfo)
+ this.getGatewayConvergeNodes({
+ id,
+ parentId: id,
+ convergeInfo: this.convergeInfo
+ })
// 添加并行默认条件
conditions = nodeConfig.outgoing.map((key, index) => {
const branchName = this.$t('并行') + (index + 1)
@@ -1663,7 +1697,8 @@
conditionType: 'parallel',
target: flows[key].target,
gatewayId: id,
- children: []
+ children: [],
+ taskId
}
})
if (this.nodeIds[flowId]) {
@@ -1778,7 +1813,8 @@
parentId,
independentId,
gatewayId: id,
- lastId: item.id
+ lastId: item.id,
+ taskId
},
ordered,
item.children
@@ -1840,81 +1876,150 @@
}, {})
Object.assign(this.nodeTargetMaps, targetMap)
},
- getGatewayConvergeNodes (id, parentId, convergeInfo = {}, index, isDeep) {
+ /**
+ * id: 当前查找的id
+ * parentId: 最外层的网关id
+ * convergeInfo: { 汇聚详情
+ * id: '', 网关节点
+ * checkedNodes: [], 已经查找过的节点
+ * convergeNode: '', 最终汇聚的节点
+ * branchCount: 1 总共有多少条分支
+ * }
+ * index: 当前节点属于哪条分支下的
+ * isDeep: 是否递归
+ */
+ getGatewayConvergeNodes (data) {
+ const {
+ id,
+ parentId,
+ convergeInfo = {},
+ index,
+ isDeep,
+ isLastBranch
+ } = data
if (!id) return
if (!convergeInfo[parentId]) {
convergeInfo[parentId] = {
id: parentId,
checkedNodes: [],
convergeNode: '',
- branchCount: 1
+ branchCount: 1 // 默认是一条分支
}
}
const targetNodes = this.nodeTargetMaps[id] || []
+ // 多条输出分支
if (targetNodes.length > 1) {
+ // 非递归时,将节点分支添加到总分支,删除旧分支。
if (!isDeep) {
convergeInfo[parentId].branchCount += targetNodes.length - 1
}
targetNodes.forEach((targetId, branchIndex) => {
let newIndex = branchIndex
+ // 当前节点属于存在分支下时,向下查找时采用新的分支数
if (index !== 0) {
const branches = Object.keys(convergeInfo[parentId]).filter(item => /^branch[0-9]*$/.test(item))
newIndex = branches.length
}
+ // 非递归时使用传入的分支数
newIndex = isDeep ? index : newIndex
- this.getGatewayConvergeNodes(targetId, parentId, convergeInfo, newIndex, isDeep)
+ this.getGatewayConvergeNodes({
+ id: targetId,
+ parentId,
+ convergeInfo,
+ index: newIndex,
+ isDeep,
+ isLastBranch: branchIndex === targetNodes.length - 1
+ })
})
} else {
+ // 单条输出分支
const { checkedNodes, branchCount = 0 } = convergeInfo[parentId]
const countArr = [...Array(branchCount).keys()]
const { end_event } = this.pipelineData
+ // 已查找过的节点、结束节点、汇聚节点
if ([...checkedNodes, end_event.id].includes(id) || this.nodeSourceMaps[id].length > 1) {
+ // 记录分支下的汇聚节点
const branchConvergeNode = convergeInfo[parentId][`branch${index}`]
if (!branchConvergeNode) {
convergeInfo[parentId][`branch${index}`] = [id]
} else if (!branchConvergeNode.includes(id)) {
branchConvergeNode.push(id)
}
+ // 记录查找过的节点
if (!checkedNodes.includes(id)) {
checkedNodes.push(id)
}
+ // 所有分支下的汇聚节点
const convergeNodes = countArr.map(item => {
const data = convergeInfo[parentId][`branch${item}`] || []
return [...new Set(data)]
}).flat()
- if (this.findMost(convergeNodes) === branchCount) {
- convergeInfo[parentId].convergeNode = id
- } else if (index === branchCount - 1) {
- countArr.forEach(item => {
- if (!convergeInfo[parentId].convergeNode) {
- const data = convergeInfo[parentId][`branch${item}`] || []
+ // 如果重复出现汇聚节点的最大次数等于分支数则表示已经找到最终的汇聚节点了
+ const countMap = this.getCountMap(convergeNodes)
+ const matchNode = Object.keys(countMap).filter(key => countMap[key] === branchCount)
+ if (matchNode[0]) {
+ convergeInfo[parentId].convergeNode = matchNode[0]
+ } else if (index === branchCount - 1 && (isDeep ? isLastBranch : true)) { // 最后一条分支
+ // 没找到汇聚节点则继续向下递归
+ if (!convergeInfo[parentId].convergeNode) {
+ const executedNodes = []
+ countArr.forEach(idx => {
+ // 根据各条分支最后的汇聚节点继续查找
+ const data = convergeInfo[parentId][`branch${idx}`] || []
const [lastId] = data.slice(-1)
+ // 合并有相同汇聚节点的分支
+ const existed = executedNodes.find(item => item.id === lastId)
+ if (existed && lastId !== end_event.id) {
+ const samePreBranch = convergeInfo[parentId][`branch${existed.index}`]
+ convergeInfo[parentId][`branch${idx}`] = [...samePreBranch]
+ const [preLastId] = samePreBranch.slice(-1)
+ this.getGatewayConvergeNodes({
+ id: preLastId,
+ parentId,
+ convergeInfo,
+ index: idx,
+ isDeep: true,
+ isLastBranch: idx === countArr.length - 1
+ })
+ return
+ }
+ executedNodes.push({ id: lastId, index: idx })
const targetIds = this.nodeTargetMaps[lastId] || [lastId]
+ // 向下递归
targetIds.forEach(targetId => {
- this.getGatewayConvergeNodes(targetId, parentId, convergeInfo, item, true)
+ this.getGatewayConvergeNodes({
+ id: targetId,
+ parentId,
+ convergeInfo,
+ index: idx,
+ isDeep: true,
+ isLastBranch: idx === countArr.length - 1
+ })
})
- }
- })
+ })
+ }
}
} else {
checkedNodes.push(id)
const targetId = targetNodes[0]
- this.getGatewayConvergeNodes(targetId, parentId, convergeInfo, index, isDeep)
+ this.getGatewayConvergeNodes({
+ id: targetId,
+ parentId,
+ convergeInfo,
+ index,
+ isDeep,
+ isLastBranch
+ })
}
}
},
- findMost (arr) {
- if (!arr.length) return
- if (arr.length === 1) return 1
- let maxNum = 0
- arr.reduce((acc, cur) => {
+ getCountMap (arr) {
+ if (!arr.length) return {}
+ const countMap = arr.reduce((acc, cur) => {
acc[cur] ? acc[cur] += 1 : acc[cur] = 1
- if (acc[cur] > maxNum) {
- maxNum = acc[cur]
- }
return acc
}, {})
- return maxNum
+ return countMap
},
judgeNodeBack (id, backId, checked) {
if (checked.includes(id)) return id === backId
@@ -1959,7 +2064,6 @@
openNodeInfoPanel (type, name, isCondition = false) {
this.sideSliderTitle = name
this.isNodeInfoPanelShow = true
- this.sidebarWidth = 960
this.nodeInfoType = type
this.isCondition = isCondition
},
@@ -2078,6 +2182,10 @@
this.convergeInfo = {}
this.nodeIds = {}
this.nodeData = this.getOrderedTree(this.completePipelineData)
+ // 如果选中节点为子流程节点则侧栏宽度默认为最大值
+ if (this.hasSubprocessNode) {
+ this.sidebarWidth = window.innerWidth - 400
+ }
this.openNodeInfoPanel('executeInfo', i18n.t('节点详情'))
},
onOpenConditionEdit (data, isCondition = true) {
@@ -2340,9 +2448,21 @@
try {
this.pending.retry = true
await this.onRetryTask(data)
- this.isNodeInfoPanelShow = false
- this.setTaskStatusTimer()
- this.updateNodeActived(this.nodeDetailConfig.id, false)
+ // 重新打开详情面板
+ if (this.isSourceDetailSideBar) {
+ setTimeout(() => {
+ // 重新轮询任务状态
+ Promise.resolve(this.loadTaskStatus()).then(() => {
+ this.onNodeClick(data.node_id)
+ this.retryNodeId = undefined
+ this.isSourceDetailSideBar = false
+ })
+ }, 1000)
+ } else {
+ this.isNodeInfoPanelShow = false
+ this.setTaskStatusTimer()
+ this.updateNodeActived(this.nodeDetailConfig.id, false)
+ }
} catch (error) {
console.warn(error)
} finally {
@@ -2353,6 +2473,11 @@
this.isNodeInfoPanelShow = false
this.retryNodeId = undefined
this.updateNodeActived(id, false)
+ // 重新打开详情面板
+ if (this.isSourceDetailSideBar) {
+ this.onNodeClick(id)
+ this.isSourceDetailSideBar = false
+ }
},
onModifyTimeSuccess (id) {
this.isNodeInfoPanelShow = false
@@ -2410,6 +2535,11 @@
},
packUp () {
this.isNodeInfoPanelShow = false
+ // 重新打开详情面板
+ if (this.isSourceDetailSideBar) {
+ this.onNodeClick(this.retryNodeId)
+ this.isSourceDetailSideBar = false
+ }
this.retryNodeId = undefined
},
onshutDown () {
@@ -2436,10 +2566,11 @@
}
}
},
- onHiddenSideslider () {
+ onHiddenSideSlider () {
this.subProcessTaskId = null
this.nodeInfoType = ''
this.retryNodeName = ''
+ this.sidebarWidth = 960
this.updateNodeActived(this.nodeDetailConfig.node_id, false)
},
// 判断RUNNING的节点是否有暂停节点,若有,则将当前任务状态标记为暂停状态
@@ -2492,6 +2623,49 @@
}
document.removeEventListener('mousemove', this.handleMouseMove)
document.removeEventListener('mouseup', this.handleMouseUp)
+ },
+ onWindowResize () {
+ const maxWidth = window.innerWidth - 400
+ let width = this.sidebarWidth
+ width = width > maxWidth ? maxWidth : width
+ width = width < 960 ? 960 : width
+ this.sidebarWidth = width
+ },
+ updateNodeExecuteInfo () {
+ if (this.isNodeInfoPanelShow && this.nodeInfoType === 'executeInfo') {
+ this.updateNodeActived(this.nodeDetailConfig.id, true)
+ const execInfoInstance = this.$refs.executeInfo
+ execInfoInstance.loading = true
+ execInfoInstance.subprocessLoading = !!execInfoInstance.subProcessPipeline
+ setTimeout(async () => {
+ try {
+ const { root_node, component_code, taskId } = this.nodeDetailConfig
+ // 重新拉取父流程状态
+ await this.loadTaskStatus()
+ // 更新节点详情
+ await execInfoInstance.loadNodeInfo()
+ // 拉取独立子流程状态
+ if (taskId && component_code !== 'subprocess_plugin') {
+ const nodes = root_node.split('-')
+ execInfoInstance.subprocessTasks[taskId] = {
+ root_node: nodes.slice(0, -1).join('-'),
+ node_id: nodes.slice(-1)[0]
+ }
+ // 获取独立子流程任务状态
+ execInfoInstance.loadSubprocessStatus()
+ }
+ } catch (error) {
+ execInfoInstance.loading = false
+ execInfoInstance.subprocessLoading = false
+ }
+ }, 1000)
+ } else {
+ this.isNodeInfoPanelShow = false
+ this.nodeInfoType = ''
+ setTimeout(() => {
+ this.setTaskStatusTimer()
+ }, 1000)
+ }
}
}
}
@@ -2559,8 +2733,11 @@
}
}
}
-/deep/.bk-sideslider-content {
- height: calc(100% - 60px);
+/deep/.bk-sideslider {
+ min-width: 1360px;
+ .bk-sideslider-content {
+ height: calc(100% - 60px);
+ }
}
.header {
display: flex;
diff --git a/frontend/desktop/src/pages/template/TemplateEdit/BatchUpdateDialog.vue b/frontend/desktop/src/pages/template/TemplateEdit/BatchUpdateDialog.vue
index 59377b9caf..5050d432f9 100644
--- a/frontend/desktop/src/pages/template/TemplateEdit/BatchUpdateDialog.vue
+++ b/frontend/desktop/src/pages/template/TemplateEdit/BatchUpdateDialog.vue
@@ -148,6 +148,7 @@
import atomFilter from '@/utils/atomFilter.js'
import tools from '@/utils/tools.js'
import i18n from '@/config/i18n/index.js'
+ import formSchema from '@/utils/formSchema.js'
import InputParams from './NodeConfig/InputParams.vue'
import OutputParams from './NodeConfig/OutputParams.vue'
import NoData from '@/components/common/base/NoData.vue'
@@ -571,7 +572,7 @@
const curVar = this.$store.state.template.constants[key] // 当前版本key相同的变量
const { source_type, source_info } = varItem
if (['component_inputs', 'component_outputs'].includes(source_type)) {
- this.subflowForms.forEach(subflow => {
+ this.subflowForms.forEach((subflow, index) => {
if (source_info[subflow.id]) { // 该节点最新版本输入输出参数有勾选
source_info[subflow.id].slice(0).forEach(nodeFormItem => {
// 注释 1.a 场景
@@ -623,6 +624,24 @@
})
}
}
+
+ const { form, inputsConfig } = subflow.latestForm
+ const formValue = form[key]
+ const inputRef = this.$refs.inputParams[index]
+ let hook = false
+ // 获取输入参数的勾选状态
+ if (inputRef && inputRef.hooked) {
+ hook = inputRef.hooked[key] || false
+ }
+ if (varItem.is_meta && formValue && hook) {
+ const schema = formSchema.getSchema(formValue.key, inputsConfig)
+ varItem['form_schema'] = schema
+ varItem.meta = formValue.meta
+ // 如果之前选中的下拉项被删除了,则删除对应的值
+ const curVal = varItem.value
+ const isMatch = curVal ? schema.attrs.items.find(item => item.value === curVal) : true
+ varItem.value = isMatch ? curVal : ''
+ }
})
if (Object.keys(source_info).length > 0) {
constants[key] = varItem
diff --git a/frontend/desktop/src/pages/template/TemplateEdit/NodeConfig/JsonschemaInputParams.vue b/frontend/desktop/src/pages/template/TemplateEdit/NodeConfig/JsonschemaInputParams.vue
index 0b6653ac12..1a003df87d 100644
--- a/frontend/desktop/src/pages/template/TemplateEdit/NodeConfig/JsonschemaInputParams.vue
+++ b/frontend/desktop/src/pages/template/TemplateEdit/NodeConfig/JsonschemaInputParams.vue
@@ -1,9 +1,11 @@
@@ -22,6 +24,7 @@
BkuiForm
},
props: {
+ isViewMode: Boolean,
inputs: {
type: Object,
default: () => ({})
@@ -40,29 +43,40 @@
value (val) {
this.inputFormData = tools.deepClone(val)
}
+ },
+ methods: {
+ validate () {
+ return this.$refs.jsonschemaFormRef.validateForm()
+ }
}
}