Workflow independence audit, store tests, InstallPackage decomposition
Fix RenderWorkflow() handler (was a stub that never loaded data), remove dead chat UI from workflow.html, align stage mode naming with Go constants, add 17 SQLite store tests, decompose 400-line InstallPackage into 7 phases, add E2E workflow-without-chat test. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -25,6 +25,7 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"armature/config"
|
||||
"armature/models"
|
||||
"armature/store"
|
||||
)
|
||||
|
||||
@@ -631,18 +632,20 @@ func (e *Engine) RenderLogin() gin.HandlerFunc {
|
||||
|
||||
// WorkflowPageData is passed to the workflow.html template via PageData.Data.
|
||||
type WorkflowPageData struct {
|
||||
ChannelID string
|
||||
ChannelTitle string
|
||||
ChannelDescription string
|
||||
SessionID string
|
||||
SessionName string
|
||||
StageMode string // custom | form_only | form_chat | review
|
||||
StageName string
|
||||
FormTemplateJSON string // typed form template JSON (empty if custom)
|
||||
TotalStages int
|
||||
CurrentStage int
|
||||
SurfacePkgID string
|
||||
BrandingJSON string
|
||||
EntryToken string
|
||||
WorkflowTitle string
|
||||
WorkflowDescription string
|
||||
SessionID string
|
||||
SessionName string
|
||||
StageMode string // form | review | delegated | automated
|
||||
StageName string
|
||||
FormTemplateJSON string // typed form template JSON (empty if delegated)
|
||||
TotalStages int
|
||||
CurrentStage int
|
||||
SurfacePkgID string
|
||||
BrandingJSON string
|
||||
InstanceID string // workflow instance ID (used for API calls)
|
||||
Status string // pending | active | completed | cancelled | stale
|
||||
}
|
||||
|
||||
// WorkflowLandingPageData is passed to workflow-landing.html.
|
||||
@@ -660,49 +663,91 @@ type WorkflowLandingPageData struct {
|
||||
PersonaName string
|
||||
PersonaIcon string
|
||||
StageCount int
|
||||
FirstStageMode string // custom | form_only | form_chat
|
||||
FirstStageMode string // form | review | delegated | automated
|
||||
ResumeURL string // non-empty if visitor has an active session
|
||||
}
|
||||
|
||||
// RenderWorkflow renders the workflow entry page for anonymous session visitors.
|
||||
// RenderWorkflow renders the workflow execution page for a running instance.
|
||||
// Route: /w/:id — :id is either an instance ID or a public entry token.
|
||||
// The middleware (AuthOrSession) has already created/resumed the session.
|
||||
func (e *Engine) RenderWorkflow() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
// :id is the public entry token (from /w/:id)
|
||||
channelID := c.Param("id")
|
||||
ctx := c.Request.Context()
|
||||
entryToken := c.Param("id")
|
||||
sessionID := c.GetString("session_id")
|
||||
|
||||
var title, description string
|
||||
if title == "" {
|
||||
title = "Workflow"
|
||||
}
|
||||
|
||||
// Load session display name
|
||||
sessionName := "Visitor"
|
||||
|
||||
// Load workflow stage info for form rendering
|
||||
var stageMode, stageName, formTplJSON, surfacePkgID, brandingJSON string
|
||||
var totalStages, currentStage int
|
||||
stageMode = "custom" // default
|
||||
|
||||
instanceName, _, _ := e.loadBranding()
|
||||
|
||||
// Try to load instance by entry token, then by ID
|
||||
var inst *models.WorkflowInstance
|
||||
if e.stores.Workflows != nil {
|
||||
inst, _ = e.stores.Workflows.GetInstanceByToken(ctx, entryToken)
|
||||
if inst == nil {
|
||||
inst, _ = e.stores.Workflows.GetInstance(ctx, entryToken)
|
||||
}
|
||||
}
|
||||
|
||||
if inst == nil {
|
||||
c.String(http.StatusNotFound, "Workflow instance not found")
|
||||
return
|
||||
}
|
||||
|
||||
// Load workflow definition for title, description, branding
|
||||
title, description := "Workflow", ""
|
||||
var brandingJSON string
|
||||
wf, _ := e.stores.Workflows.GetByID(ctx, inst.WorkflowID)
|
||||
if wf != nil {
|
||||
title = wf.Name
|
||||
description = wf.Description
|
||||
if len(wf.Branding) > 0 {
|
||||
brandingJSON = string(wf.Branding)
|
||||
}
|
||||
}
|
||||
|
||||
// Load stages to resolve current stage info
|
||||
var stageMode, stageName, formTplJSON, surfacePkgID string
|
||||
var totalStages, currentStage int
|
||||
stageMode = "form" // default
|
||||
|
||||
stages, _ := e.stores.Workflows.ListStages(ctx, inst.WorkflowID)
|
||||
totalStages = len(stages)
|
||||
for i, s := range stages {
|
||||
if s.ID == inst.CurrentStage || s.Name == inst.CurrentStage {
|
||||
currentStage = i
|
||||
stageName = s.Name
|
||||
stageMode = s.StageMode
|
||||
if stageMode == "" {
|
||||
stageMode = "form"
|
||||
}
|
||||
if len(s.FormTemplate) > 0 && string(s.FormTemplate) != "null" {
|
||||
formTplJSON = string(s.FormTemplate)
|
||||
}
|
||||
if s.SurfacePkgID != nil {
|
||||
surfacePkgID = *s.SurfacePkgID
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
e.Render(c, "workflow.html", PageData{
|
||||
Surface: "workflow",
|
||||
InstanceName: instanceName,
|
||||
Data: WorkflowPageData{
|
||||
ChannelID: channelID,
|
||||
ChannelTitle: title,
|
||||
ChannelDescription: description,
|
||||
SessionID: sessionID,
|
||||
SessionName: sessionName,
|
||||
StageMode: stageMode,
|
||||
StageName: stageName,
|
||||
FormTemplateJSON: formTplJSON,
|
||||
TotalStages: totalStages,
|
||||
CurrentStage: currentStage,
|
||||
SurfacePkgID: surfacePkgID,
|
||||
BrandingJSON: brandingJSON,
|
||||
EntryToken: entryToken,
|
||||
WorkflowTitle: title,
|
||||
WorkflowDescription: description,
|
||||
SessionID: sessionID,
|
||||
SessionName: sessionName,
|
||||
StageMode: stageMode,
|
||||
StageName: stageName,
|
||||
FormTemplateJSON: formTplJSON,
|
||||
TotalStages: totalStages,
|
||||
CurrentStage: currentStage,
|
||||
SurfacePkgID: surfacePkgID,
|
||||
BrandingJSON: brandingJSON,
|
||||
InstanceID: inst.ID,
|
||||
Status: inst.Status,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user