mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-13 03:13:41 +08:00
fix(workflow): keep the editor canvas and status correct after publish
This commit is contained in:
parent
8cfc78e7b4
commit
4cf991851b
@ -57,13 +57,16 @@ public class WorkflowController {
|
||||
return R.ok(workflowService.listByWorkspace(workspaceId));
|
||||
}
|
||||
|
||||
@Operation(summary = "Get a workflow by id (includes inline draft).")
|
||||
@Operation(summary = "Get a workflow by id (includes inline draft + latest published graph).")
|
||||
@GetMapping("/{id}")
|
||||
@RequireWorkspaceRole("admin")
|
||||
public R<WorkflowEntity> get(@PathVariable long id,
|
||||
@RequestHeader("X-Workspace-Id") long workspaceId) {
|
||||
WorkflowEntity row = workflowService.get(id, workspaceId);
|
||||
if (row == null) return R.fail("workflow not found: " + id);
|
||||
// Surface the published revision's graph so the editor can render a
|
||||
// published workflow whose inline draft was cleared at publish time.
|
||||
workflowService.attachPublishedGraph(row);
|
||||
return R.ok(row);
|
||||
}
|
||||
|
||||
|
||||
@ -47,6 +47,23 @@ public class WorkflowEntity {
|
||||
@TableField(value = "latest_revision_id", updateStrategy = FieldStrategy.ALWAYS)
|
||||
private Long latestRevisionId;
|
||||
|
||||
/**
|
||||
* Latest published revision's graph JSON. Not a persisted column — it is
|
||||
* populated on the editor-facing read so a published workflow (whose inline
|
||||
* draft is cleared at publish time) still has a graph for the editor to
|
||||
* render instead of an empty canvas.
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private String publishedGraphJson;
|
||||
|
||||
/**
|
||||
* Human-facing version number of the latest published revision (1, 2, 3…).
|
||||
* Not a persisted column — populated on read so the UI shows "v3" instead
|
||||
* of leaking the latestRevisionId snowflake. Null when never published.
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private Integer latestRevisionNumber;
|
||||
|
||||
private Long createdBy;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
|
||||
@ -14,6 +14,9 @@ import vip.mate.workflow.repository.WorkflowRevisionMapper;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Workflow CRUD + draft / publish lifecycle. Drafts live inline on the
|
||||
@ -32,9 +35,34 @@ public class WorkflowService {
|
||||
private final WorkflowAclPort aclPort;
|
||||
|
||||
public List<WorkflowEntity> listByWorkspace(long workspaceId) {
|
||||
return workflowMapper.selectList(new LambdaQueryWrapper<WorkflowEntity>()
|
||||
List<WorkflowEntity> rows = workflowMapper.selectList(new LambdaQueryWrapper<WorkflowEntity>()
|
||||
.eq(WorkflowEntity::getWorkspaceId, workspaceId)
|
||||
.orderByDesc(WorkflowEntity::getUpdateTime));
|
||||
attachRevisionNumbers(rows);
|
||||
return rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch-populate {@link WorkflowEntity#getLatestRevisionNumber()} so the
|
||||
* workflow list shows a human version ("v3") instead of the latestRevisionId
|
||||
* snowflake. One query for the whole page; no-op when nothing is published.
|
||||
*/
|
||||
private void attachRevisionNumbers(List<WorkflowEntity> workflows) {
|
||||
List<Long> ids = workflows.stream()
|
||||
.map(WorkflowEntity::getLatestRevisionId)
|
||||
.filter(Objects::nonNull)
|
||||
.distinct()
|
||||
.toList();
|
||||
if (ids.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Map<Long, Integer> numberById = revisionMapper.selectBatchIds(ids).stream()
|
||||
.collect(Collectors.toMap(WorkflowRevisionEntity::getId, WorkflowRevisionEntity::getRevision));
|
||||
for (WorkflowEntity wf : workflows) {
|
||||
if (wf.getLatestRevisionId() != null) {
|
||||
wf.setLatestRevisionNumber(numberById.get(wf.getLatestRevisionId()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -51,6 +79,24 @@ public class WorkflowService {
|
||||
return row;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate {@link WorkflowEntity#getPublishedGraphJson()} from the latest
|
||||
* revision. The editor needs this because {@link #publish} clears the inline
|
||||
* draft on publish — without the published graph the canvas would render
|
||||
* empty for any published workflow. No-op when the workflow was never
|
||||
* published or the revision row is missing.
|
||||
*/
|
||||
public void attachPublishedGraph(WorkflowEntity workflow) {
|
||||
if (workflow == null || workflow.getLatestRevisionId() == null) {
|
||||
return;
|
||||
}
|
||||
WorkflowRevisionEntity revision = revisionMapper.selectById(workflow.getLatestRevisionId());
|
||||
if (revision != null) {
|
||||
workflow.setPublishedGraphJson(revision.getGraphJson());
|
||||
workflow.setLatestRevisionNumber(revision.getRevision());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as {@link #get(long, long)} but throws when the row is missing.
|
||||
* Used by mutation paths that can fail loudly instead of returning null.
|
||||
|
||||
@ -929,6 +929,12 @@ export interface WorkflowSummary {
|
||||
draftJson?: string
|
||||
draftUpdatedAt?: string
|
||||
latestRevisionId?: number
|
||||
/** Human version number of the latest published revision (1, 2, 3…) — shown
|
||||
* as "v3" instead of the latestRevisionId snowflake. Null when unpublished. */
|
||||
latestRevisionNumber?: number
|
||||
/** Latest published revision's graph JSON — populated by GET /workflows/{id}
|
||||
* so the editor can render a published workflow whose draft was cleared. */
|
||||
publishedGraphJson?: string
|
||||
createTime: string
|
||||
updateTime: string
|
||||
}
|
||||
|
||||
@ -31,7 +31,7 @@
|
||||
>
|
||||
<div class="list-row-name">
|
||||
{{ wf.name || t('workflows.unnamed') }}
|
||||
<span v-if="wf.latestRevisionId" class="badge published">{{ t('workflows.publishedBadge', { rev: wf.latestRevisionId }) }}</span>
|
||||
<span v-if="wf.latestRevisionId" class="badge published">{{ t('workflows.publishedBadge', { rev: wf.latestRevisionNumber ?? '?' }) }}</span>
|
||||
<span v-else class="badge draft">{{ t('workflows.draftBadge') }}</span>
|
||||
</div>
|
||||
<div class="list-row-desc">{{ wf.description || '-' }}</div>
|
||||
@ -46,6 +46,11 @@
|
||||
<input v-model="selected.name" class="editor-name" :placeholder="t('workflows.namePlaceholder')" />
|
||||
<input v-model="selected.description" class="editor-desc" :placeholder="t('workflows.descPlaceholder')" />
|
||||
<div class="editor-actions">
|
||||
<span class="badge editor-state" :class="selected.latestRevisionId ? 'published' : 'draft'">
|
||||
{{ selected.latestRevisionId
|
||||
? t('workflows.publishedBadge', { rev: selected.latestRevisionNumber ?? '?' })
|
||||
: t('workflows.draftBadge') }}
|
||||
</span>
|
||||
<button class="btn-ghost" :disabled="busy" @click="saveMeta">{{ t('workflows.actions.saveMeta') }}</button>
|
||||
<button class="btn-ghost" :disabled="busy" @click="saveDraft">{{ t('workflows.actions.saveDraft') }}</button>
|
||||
<button class="btn-ghost" :disabled="busy" @click="compile">{{ t('workflows.actions.compile') }}</button>
|
||||
@ -566,7 +571,10 @@ async function select(id: number) {
|
||||
try {
|
||||
const res = await workflowApi.get(id)
|
||||
selected.value = res.data as unknown as WorkflowSummary
|
||||
draftJson.value = selected.value?.draftJson ?? ''
|
||||
// Fall back to the latest published graph when the inline draft is empty —
|
||||
// publishing clears draftJson on the backend, so without this the canvas
|
||||
// and JSON editor would render empty for an already-published workflow.
|
||||
draftJson.value = selected.value?.draftJson || selected.value?.publishedGraphJson || ''
|
||||
compileErrors.value = []
|
||||
lastStatus.value = ''
|
||||
await reloadRuns()
|
||||
@ -766,8 +774,13 @@ async function onPublishSubmit(payload: { note: string }) {
|
||||
await workflowApi.saveDraft(selected.value.id, draftJson.value)
|
||||
await workflowApi.publish(selected.value.id, payload.note || undefined)
|
||||
publishDialogOpen.value = false
|
||||
setStatus(t('workflows.status.published'), 'ok')
|
||||
await reload()
|
||||
// Re-fetch the just-published workflow so the canvas rebinds to the
|
||||
// published graph — publish cleared draftJson, and select() now falls
|
||||
// back to publishedGraphJson. setStatus runs last because select()
|
||||
// resets lastStatus.
|
||||
await select(selected.value.id)
|
||||
setStatus(t('workflows.status.published'), 'ok')
|
||||
} catch (e) {
|
||||
handleCompileError(e)
|
||||
// Close the publish dialog on failure so the operator sees the
|
||||
@ -993,6 +1006,11 @@ watch(workspaceId, async () => {
|
||||
gap: 6px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
/* Persistent published/draft state — sits at the row's left, actions to its right. */
|
||||
.editor-state {
|
||||
align-self: center;
|
||||
margin-right: auto;
|
||||
}
|
||||
.editor-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user