feat: expose workflow semantic documentation subjects
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@govoplan/workflow-webui",
|
||||
"version": "0.1.18",
|
||||
"version": "0.1.19",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { Trash2 } from "lucide-react";
|
||||
import { BookOpen, Trash2 } from "lucide-react";
|
||||
import {
|
||||
ActionToolbar,
|
||||
Button,
|
||||
DocumentationHelpLink,
|
||||
DismissibleAlert,
|
||||
FormField,
|
||||
ReferenceMultiSelect,
|
||||
@@ -21,6 +22,8 @@ export default function WorkflowInspector({
|
||||
edge,
|
||||
nodeLibrary,
|
||||
readOnly,
|
||||
semanticDefinitionId,
|
||||
semanticStepAvailable,
|
||||
onChange,
|
||||
onDelete,
|
||||
onEdgeChange,
|
||||
@@ -30,6 +33,8 @@ export default function WorkflowInspector({
|
||||
edge: WorkflowGraphEdge | null;
|
||||
nodeLibrary: WorkflowNodeType[];
|
||||
readOnly: boolean;
|
||||
semanticDefinitionId: string | null;
|
||||
semanticStepAvailable: boolean;
|
||||
onChange: (node: WorkflowGraphNode) => void;
|
||||
onDelete: (nodeId: string) => void;
|
||||
onEdgeChange: (edge: WorkflowGraphEdge) => void;
|
||||
@@ -207,16 +212,35 @@ export default function WorkflowInspector({
|
||||
<strong>Inspector</strong>
|
||||
<small>{definition?.label ?? node.type}</small>
|
||||
</span>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="workflow-inspector-delete"
|
||||
onClick={() => onDelete(node.id)}
|
||||
disabled={readOnly}
|
||||
aria-label="Delete node"
|
||||
title="Delete node"
|
||||
>
|
||||
<Trash2 size={16} />
|
||||
</Button>
|
||||
<span className="workflow-inspector-actions">
|
||||
{semanticDefinitionId && semanticStepAvailable ? <>
|
||||
<DocumentationHelpLink
|
||||
reference={{
|
||||
contextId: semanticHelpContext(semanticDefinitionId, node.id),
|
||||
documentationType: "user"
|
||||
}}
|
||||
label="Open step meaning"
|
||||
/>
|
||||
<a
|
||||
className="btn btn-secondary"
|
||||
href={semanticAuthoringHref(semanticDefinitionId, node.id)}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<BookOpen size={15} aria-hidden="true" /> Document meaning
|
||||
</a>
|
||||
</> : null}
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="workflow-inspector-delete"
|
||||
onClick={() => onDelete(node.id)}
|
||||
disabled={readOnly}
|
||||
aria-label="Delete node"
|
||||
title="Delete node"
|
||||
>
|
||||
<Trash2 size={16} />
|
||||
</Button>
|
||||
</span>
|
||||
</ActionToolbar>
|
||||
<div className="workflow-inspector-fields">
|
||||
{error ? (
|
||||
@@ -332,3 +356,23 @@ function stringList(value: unknown): string[] {
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function semanticHelpContext(definitionId: string, nodeId: string): string {
|
||||
return [
|
||||
"semantic",
|
||||
"workflow",
|
||||
"workflow_definition",
|
||||
definitionId,
|
||||
`workflow-node-${nodeId}`
|
||||
].join(".");
|
||||
}
|
||||
|
||||
function semanticAuthoringHref(definitionId: string, nodeId: string): string {
|
||||
const params = new URLSearchParams({
|
||||
module: "workflow",
|
||||
subjectKind: "workflow_definition",
|
||||
subjectId: definitionId,
|
||||
routeAnchor: `workflow-node-${nodeId}`
|
||||
});
|
||||
return `/docs/semantic?${params}`;
|
||||
}
|
||||
|
||||
@@ -104,6 +104,7 @@ const iconByName: Record<string, LucideIcon> = {
|
||||
};
|
||||
|
||||
export default function WorkflowNode({
|
||||
id,
|
||||
data,
|
||||
selected,
|
||||
isConnectable
|
||||
@@ -114,6 +115,7 @@ export default function WorkflowNode({
|
||||
const outputPorts = data.definition.output_ports;
|
||||
return (
|
||||
<div
|
||||
id={`workflow-node-${id}`}
|
||||
className={[
|
||||
"workflow-node",
|
||||
`workflow-node-${data.definition.category}`,
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
} from "react";
|
||||
import {
|
||||
Archive,
|
||||
BookOpen,
|
||||
CheckCircle2,
|
||||
CopyPlus,
|
||||
Download,
|
||||
@@ -708,6 +709,23 @@ export default function WorkflowPage({
|
||||
aria-label="Workflow description"
|
||||
/>
|
||||
</span>}
|
||||
helpAction={draft.id ? <>
|
||||
<DocumentationHelpLink
|
||||
reference={{
|
||||
contextId: semanticHelpContext(draft.id),
|
||||
documentationType: "user"
|
||||
}}
|
||||
label="Open workflow meaning"
|
||||
/>
|
||||
<a
|
||||
className="btn btn-secondary"
|
||||
href={semanticAuthoringHref(draft.id)}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<BookOpen size={16} aria-hidden="true" /> Document meaning
|
||||
</a>
|
||||
</> : undefined}
|
||||
primaryActions={<span className="workflow-command-bar">
|
||||
{draft.id ? (
|
||||
<select
|
||||
@@ -927,6 +945,15 @@ export default function WorkflowPage({
|
||||
edge={selectedEdge}
|
||||
nodeLibrary={nodeLibrary}
|
||||
readOnly={graphReadOnly}
|
||||
semanticDefinitionId={draft.id || null}
|
||||
semanticStepAvailable={Boolean(
|
||||
draft.id
|
||||
&& !historicalRevision
|
||||
&& selectedNode
|
||||
&& savedDraft?.graph.nodes.some(
|
||||
(node) => node.id === selectedNode.id
|
||||
)
|
||||
)}
|
||||
onChange={(node) => {
|
||||
if (!draft || graphReadOnly) return;
|
||||
updateGraph(updateWorkflowGraphNode(draft.graph, node));
|
||||
@@ -1053,6 +1080,32 @@ export default function WorkflowPage({
|
||||
);
|
||||
}
|
||||
|
||||
function semanticHelpContext(
|
||||
definitionId: string,
|
||||
routeAnchor?: string
|
||||
): string {
|
||||
return [
|
||||
"semantic",
|
||||
"workflow",
|
||||
"workflow_definition",
|
||||
definitionId,
|
||||
routeAnchor
|
||||
].filter(Boolean).join(".");
|
||||
}
|
||||
|
||||
function semanticAuthoringHref(
|
||||
definitionId: string,
|
||||
routeAnchor?: string
|
||||
): string {
|
||||
const params = new URLSearchParams({
|
||||
module: "workflow",
|
||||
subjectKind: "workflow_definition",
|
||||
subjectId: definitionId
|
||||
});
|
||||
if (routeAnchor) params.set("routeAnchor", routeAnchor);
|
||||
return `/docs/semantic?${params}`;
|
||||
}
|
||||
|
||||
function WorkflowStandardComparisonDialog({
|
||||
open,
|
||||
settings,
|
||||
|
||||
@@ -451,6 +451,18 @@
|
||||
color: var(--danger-text);
|
||||
}
|
||||
|
||||
.workflow-inspector-actions {
|
||||
display: inline-flex;
|
||||
min-width: 0;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.workflow-inspector-actions .btn {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.workflow-diagnostics {
|
||||
display: flex;
|
||||
max-height: 38%;
|
||||
|
||||
Reference in New Issue
Block a user