吸收数字员工管理端步骤调度预览
This commit is contained in:
@@ -33,6 +33,22 @@ public class DigitalEmployeeAdminWorkbenchRowDto {
|
||||
private String taskId;
|
||||
@JsonProperty("run_id")
|
||||
private String runId;
|
||||
@JsonProperty("step_id")
|
||||
private String stepId;
|
||||
@JsonProperty("dependency_status")
|
||||
private String dependencyStatus;
|
||||
@JsonProperty("lease_state")
|
||||
private String leaseState;
|
||||
@JsonProperty("lease_source")
|
||||
private String leaseSource;
|
||||
@JsonProperty("dispatch_status")
|
||||
private String dispatchStatus;
|
||||
@JsonProperty("dispatch_policy_key")
|
||||
private String dispatchPolicyKey;
|
||||
@JsonProperty("orchestration_id")
|
||||
private String orchestrationId;
|
||||
@JsonProperty("trigger_source")
|
||||
private String triggerSource;
|
||||
@JsonProperty("approval_id")
|
||||
private String approvalId;
|
||||
@JsonProperty("artifact_id")
|
||||
|
||||
@@ -74,6 +74,7 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
|
||||
"skill:mark_reviewed", "skill:dismiss",
|
||||
"policy_snapshot:pull_policy", "policy_snapshot:mark_reviewed", "policy_snapshot:dismiss",
|
||||
"scheduler:retry_scheduler_check", "scheduler:mark_reviewed", "scheduler:dismiss",
|
||||
"plan_step:mark_reviewed", "plan_step:dismiss", "plan_step:retry_policy_check",
|
||||
"plan:mark_reviewed", "plan:dismiss", "plan:request_revision_review", "plan:record_escalation",
|
||||
"revision:mark_reviewed", "revision:dismiss", "revision:request_revision_review",
|
||||
"audit:mark_reviewed", "audit:dismiss",
|
||||
@@ -995,6 +996,14 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
|
||||
.planId(businessRow.getPlanId())
|
||||
.taskId(businessRow.getTaskId())
|
||||
.runId(businessRow.getRunId())
|
||||
.stepId(firstString(businessView.get("step_id"), businessView.get("stepId"), payload.get("step_id"), payload.get("stepId"), "plan_step".equals(record.getEntityType()) ? record.getEntityId() : null))
|
||||
.dependencyStatus(firstString(businessView.get("dependency_status"), businessView.get("dependencyStatus"), payload.get("dependency_status"), payload.get("dependencyStatus")))
|
||||
.leaseState(firstString(businessView.get("lease_state"), businessView.get("leaseState"), payload.get("lease_state"), payload.get("leaseState")))
|
||||
.leaseSource(firstString(businessView.get("lease_source"), businessView.get("leaseSource"), payload.get("lease_source"), payload.get("leaseSource")))
|
||||
.dispatchStatus(firstString(businessView.get("dispatch_status"), businessView.get("dispatchStatus"), payload.get("dispatch_status"), payload.get("dispatchStatus")))
|
||||
.dispatchPolicyKey(firstString(businessView.get("dispatch_policy_key"), businessView.get("dispatchPolicyKey"), businessView.get("policy_key"), payload.get("dispatch_policy_key"), payload.get("dispatchPolicyKey"), payload.get("policy_key")))
|
||||
.orchestrationId(firstString(businessView.get("orchestration_id"), businessView.get("orchestrationId"), payload.get("orchestration_id"), payload.get("orchestrationId")))
|
||||
.triggerSource(firstString(businessView.get("trigger_source"), businessView.get("triggerSource"), payload.get("trigger_source"), payload.get("triggerSource")))
|
||||
.approvalId(firstString(businessView.get("approval_id"), payload.get("approval_id"), "approval".equals(record.getEntityType()) ? record.getEntityId() : null))
|
||||
.artifactId(firstString(businessView.get("artifact_id"), payload.get("artifact_id"), "artifact".equals(record.getEntityType()) ? record.getEntityId() : null))
|
||||
.cleanupRequestId(firstString(businessView.get("cleanup_request_id"), businessView.get("cleanupRequestId"), payload.get("cleanup_request_id"), payload.get("cleanupRequestId")))
|
||||
@@ -1050,6 +1059,7 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
|
||||
case "skill_audits" -> isSkillAuditRow(row);
|
||||
case "policy_center" -> isPolicyCenterRow(row);
|
||||
case "scheduler_operations" -> isSchedulerOperationsRow(row);
|
||||
case "plan_step_dispatch" -> isPlanStepDispatchRow(row);
|
||||
case "plan_governance" -> isPlanGovernanceRow(row);
|
||||
case "plan_revisions" -> isPlanRevisionRow(row);
|
||||
default -> true;
|
||||
@@ -1183,6 +1193,36 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
|
||||
|| StringUtils.isNotBlank(row.getJobId());
|
||||
}
|
||||
|
||||
private boolean isPlanStepDispatchRow(DigitalEmployeeAdminWorkbenchRowDto row) {
|
||||
String category = firstString(row.getCategory());
|
||||
String kind = firstString(row.getKind());
|
||||
String status = firstString(row.getStatus());
|
||||
String source = firstString(
|
||||
row.getLeaseSource(), row.getTriggerSource(),
|
||||
row.getBusinessView().get("source"), row.getPayload().get("source"),
|
||||
row.getBusinessView().get("lease_source"), row.getPayload().get("lease_source")
|
||||
);
|
||||
return StringUtils.equals(row.getEntityType(), "plan_step")
|
||||
|| StringUtils.equals(category, "plan_step_dispatch")
|
||||
|| StringUtils.equals(category, "plan_step")
|
||||
|| startsWithAny(kind, "plan_step_dispatch", "plan_step_lease", "dispatch_ready_steps")
|
||||
|| contains(kind, "plan_step_dispatch")
|
||||
|| contains(kind, "plan_step_lease")
|
||||
|| contains(kind, "dispatch_ready_steps")
|
||||
|| contains(kind, "lease_active")
|
||||
|| contains(kind, "remote_lease_rejected")
|
||||
|| contains(kind, "local_fallback")
|
||||
|| contains(status, "lease_active")
|
||||
|| contains(status, "remote_lease_rejected")
|
||||
|| contains(status, "local_fallback")
|
||||
|| contains(source, "remote_lease_rejected")
|
||||
|| contains(source, "local_fallback")
|
||||
|| StringUtils.isNotBlank(row.getStepId())
|
||||
|| StringUtils.isNotBlank(row.getLeaseState())
|
||||
|| StringUtils.isNotBlank(row.getDispatchStatus())
|
||||
|| StringUtils.isNotBlank(row.getDispatchPolicyKey());
|
||||
}
|
||||
|
||||
private boolean isPlanGovernanceRow(DigitalEmployeeAdminWorkbenchRowDto row) {
|
||||
String category = firstString(row.getCategory());
|
||||
String kind = firstString(row.getKind());
|
||||
@@ -1206,6 +1246,11 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
|
||||
private String adminCategory(DigitalEmployeeBusinessViewRowDto row, Map<String, Object> businessView, Map<String, Object> payload) {
|
||||
if (booleanValue(businessView.get("approval_conflict_active"))) return "approval_conflict";
|
||||
String kind = firstString(row.getKind(), businessView.get("kind"), payload.get("kind"));
|
||||
if (StringUtils.equals(row.getEntityType(), "plan_step")
|
||||
|| startsWithAny(kind, "plan_step_dispatch", "plan_step_lease")
|
||||
|| contains(kind, "dispatch_ready_steps")) {
|
||||
return "plan_step_dispatch";
|
||||
}
|
||||
if (contains(kind, "route_decision_blocked") || contains(kind, "route_decision_approval_required")
|
||||
|| contains(kind, "route_action_policy_blocked") || contains(kind, "route_action_missing_context")
|
||||
|| contains(kind, "route_action_not_mapped")) {
|
||||
|
||||
@@ -567,6 +567,94 @@ class DigitalEmployeeSyncApplicationServiceImplTest {
|
||||
assertThat(result.getRecords().get(1).getKind()).isEqualTo("scheduler_check_completed");
|
||||
}
|
||||
|
||||
@Test
|
||||
void queryAdminWorkbenchPlanStepDispatchReturnsStepLeaseAndDispatchRowsOnly() {
|
||||
when(repository.queryBusinessRecords(eq(101L), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(1L), eq(20L)))
|
||||
.thenReturn(page(List.of(
|
||||
record("outbox-step", "plan_step", "step-1", 101L, new Date(10_000),
|
||||
Map.of(
|
||||
"entity_type", "plan_step",
|
||||
"category", "plan_step",
|
||||
"title", "步骤一",
|
||||
"status", "ready",
|
||||
"summary", "依赖已满足",
|
||||
"plan_id", "plan-1",
|
||||
"task_id", "task-1",
|
||||
"step_id", "step-1",
|
||||
"dependency_status", "ready"
|
||||
),
|
||||
Map.of("id", "step-1", "status", "ready")),
|
||||
record("outbox-dispatch", "event", "event-step-1", 101L, new Date(11_000),
|
||||
Map.ofEntries(
|
||||
Map.entry("entity_type", "event"),
|
||||
Map.entry("kind", "plan_step_dispatch_started"),
|
||||
Map.entry("status", "running"),
|
||||
Map.entry("title", "步骤派发开始"),
|
||||
Map.entry("plan_id", "plan-1"),
|
||||
Map.entry("task_id", "task-1"),
|
||||
Map.entry("run_id", "run-1"),
|
||||
Map.entry("step_id", "step-1"),
|
||||
Map.entry("dispatch_status", "started"),
|
||||
Map.entry("orchestration_id", "orch-1"),
|
||||
Map.entry("trigger_source", "scheduler_sweep")
|
||||
),
|
||||
Map.of("id", "event-step-1", "kind", "plan_step_dispatch_started")),
|
||||
record("outbox-lease", "event", "event-step-2", 101L, new Date(12_000),
|
||||
Map.of(
|
||||
"entity_type", "event",
|
||||
"kind", "plan_step_lease_acquired",
|
||||
"status", "lease_active",
|
||||
"title", "步骤租约已获取",
|
||||
"plan_id", "plan-1",
|
||||
"step_id", "step-1",
|
||||
"lease_state", "active",
|
||||
"lease_source", "remote"
|
||||
),
|
||||
Map.of("id", "event-step-2", "kind", "plan_step_lease_acquired")),
|
||||
record("outbox-policy", "event", "event-step-3", 101L, new Date(13_000),
|
||||
Map.of(
|
||||
"entity_type", "event",
|
||||
"kind", "plan_step_dispatch_policy_pulled",
|
||||
"status", "synced",
|
||||
"title", "步骤派发策略已拉取",
|
||||
"step_id", "step-1",
|
||||
"dispatch_policy_key", "plan_step_dispatch"
|
||||
),
|
||||
Map.of("id", "event-step-3", "kind", "plan_step_dispatch_policy_pulled")),
|
||||
record("outbox-approval", "approval", "approval-1", 101L, new Date(14_000),
|
||||
Map.of("entity_type", "approval", "title", "普通审批", "status", "pending"),
|
||||
Map.of("id", "approval-1", "status", "pending")),
|
||||
record("outbox-notification", "notification", "notification-1", 101L, new Date(15_000),
|
||||
Map.of("entity_type", "notification", "title", "普通通知", "status", "unread"),
|
||||
Map.of("id", "notification-1", "status", "unread")),
|
||||
record("outbox-artifact", "artifact", "artifact-1", 101L, new Date(16_000),
|
||||
Map.of("entity_type", "artifact", "title", "普通产物", "status", "available"),
|
||||
Map.of("id", "artifact-1", "status", "available")),
|
||||
record("outbox-audit", "event", "event-audit-1", 101L, new Date(17_000),
|
||||
Map.of("entity_type", "event", "category", "tool_call_audit", "title", "审计", "status", "warn"),
|
||||
Map.of("id", "event-audit-1", "status", "warn"))
|
||||
)));
|
||||
|
||||
DigitalEmployeeAdminWorkbenchPageDto result = service.queryAdminWorkbench(
|
||||
"plan_step_dispatch", null, null, null, null, null, null, null, null, null, null, null, 1L, 20L
|
||||
);
|
||||
|
||||
assertThat(result.getTotal()).isEqualTo(4);
|
||||
assertThat(result.getRecords()).extracting(DigitalEmployeeAdminWorkbenchRowDto::getEntityId)
|
||||
.containsExactly("step-1", "event-step-1", "event-step-2", "event-step-3");
|
||||
assertThat(result.getRecords().get(0).getView()).isEqualTo("plan_step_dispatch");
|
||||
assertThat(result.getRecords().get(0).getStepId()).isEqualTo("step-1");
|
||||
assertThat(result.getRecords().get(0).getDependencyStatus()).isEqualTo("ready");
|
||||
assertThat(result.getRecords().get(1).getDispatchStatus()).isEqualTo("started");
|
||||
assertThat(result.getRecords().get(1).getOrchestrationId()).isEqualTo("orch-1");
|
||||
assertThat(result.getRecords().get(1).getTriggerSource()).isEqualTo("scheduler_sweep");
|
||||
assertThat(result.getRecords().get(2).getLeaseState()).isEqualTo("active");
|
||||
assertThat(result.getRecords().get(2).getLeaseSource()).isEqualTo("remote");
|
||||
assertThat(result.getRecords().get(3).getDispatchPolicyKey()).isEqualTo("plan_step_dispatch");
|
||||
assertThat(result.getRecords().get(3).getTitle()).isEqualTo("步骤派发策略已拉取");
|
||||
assertThat(result.getRecords().get(0).getSummary()).isEqualTo("依赖已满足");
|
||||
}
|
||||
|
||||
@Test
|
||||
void queryAdminWorkbenchPlanGovernanceReturnsGovernanceEscalationRowsOnly() {
|
||||
when(repository.queryBusinessRecords(eq(101L), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(1L), eq(20L)))
|
||||
@@ -1281,6 +1369,27 @@ class DigitalEmployeeSyncApplicationServiceImplTest {
|
||||
assertThat(activatePlan.getReasonCodes()).contains("high_risk_governance_action_disabled");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createAdminActionRecordsPlanStepLowRiskAndRejectsRemoteDispatch() {
|
||||
DigitalEmployeeAdminActionResultDto retryPolicyCheck = service.createAdminAction(
|
||||
adminActionRequest("plan_step", "step-1", "retry_policy_check")
|
||||
);
|
||||
DigitalEmployeeAdminActionResultDto dispatchReadyStep = service.createAdminAction(
|
||||
adminActionRequest("plan_step", "step-1", "dispatch_ready_step")
|
||||
);
|
||||
DigitalEmployeeAdminActionResultDto takeoverLease = service.createAdminAction(
|
||||
adminActionRequest("plan_step", "step-1", "takeover_lease")
|
||||
);
|
||||
|
||||
assertThat(retryPolicyCheck.getOk()).isTrue();
|
||||
assertThat(retryPolicyCheck.getStatus()).isEqualTo("pending");
|
||||
assertThat(retryPolicyCheck.getReasonCodes()).contains("action_recorded");
|
||||
assertThat(dispatchReadyStep.getOk()).isFalse();
|
||||
assertThat(dispatchReadyStep.getReasonCodes()).contains("unsupported_admin_action");
|
||||
assertThat(takeoverLease.getOk()).isFalse();
|
||||
assertThat(takeoverLease.getReasonCodes()).contains("unsupported_admin_action");
|
||||
}
|
||||
|
||||
@Test
|
||||
void queryPendingAdminActionsFiltersTenantClientDeviceAndMasksKey() {
|
||||
when(adminActionRepository.queryActions(eq(101L), eq("client-key-001"), eq("device-001"), eq("pending"), eq(1L), eq(20L)))
|
||||
|
||||
Reference in New Issue
Block a user