# flow-work-logic.psm1
<#
 The logic to run the workflow engine

History:
  EC2619 - logic from previous versions & restructured
#>

<##.mod-doc
flow-work-logic.psm1
================

_PSEC version 0.0.9_

#### Workflow bootstrap ####

This script module contains the routines that handle the the Workflow implementation

It does this by extending the Form object to add the additional fields it requires for managing
the Workflow.

#### Workflow Architecture ####

A workflow consists of one or more steps.

These steps are controlled from a Workflow Control Panel that is rendered by the @@GUI.  The @@Workflow i  s created with the @@GuiConfig.

The Workflow Control Panel is used to prepare each step, execute each step and  inspect the results of each execution.

Each step is expected to produce a @@Brief (simple json format) that is used to represent a summary of the step execution.

The execution of each step can result in a GOOD status or FAIL status as recorded in the @@Brief.
Failure to produce the @@Brief is treated as a failure.

Any standard output is stored in a @@RunLog which can be easily viewed from the control panel.

Dependencies can be specified which ensures a subsequent step only runs when the dependant step successfully executes (status is GOOD).

In addition, steps can be specified that only show if the previous step fails and are used to take corrective action (such as updating a database).

#>

using namespace System.Collections;
#using namespace System.Management.Automation;
using module .\gui-classes.psm1
using module .\gui-utils.psm1
using module .\flow-classes.psm1
using module .\flow-task-logic.psm1

set-strictmode -version 2.0

<#.md
.desc
desc tbd
.details
details tbd
#>
class FlowExec : Flow { FlowExec([string]$wflName,[TaskFlowStruc]$tfs) : base($wflName,$tfs) { } #---- methods ----
[void]addTask([Task]$task) { $this.tasks.add($task); $task.updatePriorTask($this); <# if ($task.whenFail -ne $null) { if ($this.minorPriorTask -eq $null) { $task.priorTask = $this.priorTask; } else { $task.priorTask = $this.minorPriorTask; } $this.minorStepSeq += 1; $task.stepLabel = "Step $($this.majorStepSeq)$(".abcdefghijklmnopq".substring($this.minorStepSeq,1))"; $this.minorPriorTask = $task; } else { $this.majorStepSeq += 1; $this.minorStepSeq = 0; $this.minorPriorTask = $null; $task.priorTask = $this.priorTask; $this.priorTask = $task; $task.stepLabel = "Step $($this.majorStepSeq) "; } #> }
[void] showWelcome([Elem]$elem,[Object]$ctx) { logBoth("show Welcome $($elem)"); #$locnRTF = "$($elem.form.baseLocn)\doc-$($elem.form.flow.wflName).rtf" #hlogBoth("show Welcome $($elem.form.baseLocn) $locnRTF"); <# $elem.form.findElem('tab-panel').switchPanel("##-welcome"); $elem.form.cfg.activeTask = "##-welcome"; #EC0204 $box = $elem.form.findElem('##-welcome-box'); $box.peer.text = "no text found to load for $($locnRTF -replace '.rtf','.md')"; $box.peer.ScrollBars = 'Vertical' $box.peer.ZoomFactor = 0.80; $parPeer = $box.peer.parent; hlog("sizes $($box.peer.size) $($box.peer.height) $($box.peer.width)") hlog("par.sizes $($parPeer.size) $($parPeer.height) $($parPeer.width)") $w = $parPeer.width; $h = $parPeer.height; $box.peer.Size = "$($w - 15),$($h - 20)"; $box.peer.RightMargin = $w - 110 $textRTF = (convertAndLoadRtfFile $locnRTF $null $null); if ($textRtf -ne $null) { $box.peer.RTF = $textRTF } #> }
[void] showHelp([Elem]$elem,[Object]$ctx) { ##hlogBoth("showHelp $elem"); $form = $elem.form; [dialog]$dlg = [Dialog]::makeCommonDialog($form,330,800,'que',"GUI Workflow Help",$false) #$dlg.setMar(5,0,0,0); $pan = ([Panel]::createPanel($dlg,'wrap')).setMar(10,-8,5,0); # Move scroll bar to where it should be $pan.peer.backColor = rgb 240 233 241; [TextBox]$box = ([TextBox]::createTextBox($pan,"1000,670",$null,$true)).setAbs(1,0); $box.peer.BorderStyle = "none"; $txthelp = $box.peer; # Note. The RightMargin contortion was discovered thru trial and error. # without it the lines were wrapped incorrectly (shorter). $txtHelp.ReadOnly = $true; $txtHelp.Size = "1000,790"; $txtHelp.RightMargin = 890 $txtHelp.text = "help text not supplied" #$txtHelp.add_textChanged({ #hlog("Text changed $this `r`n$($this.RTF)`r`n"); #}) $txtHelp.BackColor = rgb 240 233 241 $txtHelp.ZoomFactor = 0.80; $execLocn = getMandEnvVbl("PSEC_V4_EXEC_DIR"); $mdFile = "$($execLocn)/res/psec-help-wfl-gui.md" $rtfFile = "$($execLocn)/res/psec-help-wfl-gui.rtf" if (test-path $mdFile) { $fileMD = $(Get-Item $mdFile) $fileRTF = $null; if (test-path $rtfFile) { $fileRTF = $(Get-Item $rtfFile) } if (($fileRTF -eq $null) -or ($fileRTF.LastWriteTime -lt $fileMD.LastWriteTime) <#-or $true #>) { #hlogBoth("refresh RTF file $mdFile"); [void](& ./scr/dgen-rtf.ps1 "$($execlocn)/res/psec-help-wfl-gui") hlogBoth("refresh completed of $mdFile"); } } #$rtfFile = "$($elem.form.psecBase)/gui/psec-gui-workflow-fix.rtf" hlogBoth("load help from $rtffile") if (test-path $rtfFile) { $rtfText = Get-Content -path $rtfFile -encoding ascii $txtHelp.RTF = $rtftext } $box.peer.ScrollBars = 'Vertical' $dlg.layout(); $dlg.peer.show(); } } # ------------------------ main entry point ------------------ <#.md .desc Build Workflow form - main entry point V4 .details We have check -cfg.json exists, not its contents. #>
function buildWorkflowForm([string]$wflCfg,[string]$dynCfg) { [string]$nowPic = (Get-Date -Format "ddd yyyy-MMM-dd HH:mm:ss") hlogBoth("Starting workflow ts=$nowPic cfg=$wflCfg dyn=$dynCfg") $flowDir = $wflCfg | Split-Path -Parent $flowName = ($wflCfg | Split-Path -Leaf) -replace "-cfg.json","" hlogBoth("Starting workflow ts=$nowPic cfg=$wflCfg n=$flowName dir=$flowDir") $tfs = [TaskFlowStruc]::new(); [FlowExec]$flow = [FlowExec]::new($flowName,$tfs); $flow.cfgPath = $wflCfg; $flow.dynPath = $dynCfg; $flow.flowPath = $flowDir; hlogBoth("Flow $flow") $cfgHT = loadCfgFile($wflCfg); hlogBoth("Title=$($cfgHT.title) tasks=$($cfgHT.tasks.length))"); $dynHT = loadCfgFile($dynCfg); $flow.dynHT = $dynHT; $tasks = (& populateFlowTaskList $flow $cfgHT); if ($null -eq $tasks) {return $null;} hlogBoth("have $($tasks.count) task objects flowPath=$($flow.flowPath) libPath=$($flow.libPath)"); forEach($task in $tasks) { $flow.addTask($task); hlogBoth("TASK:$task $($task.title)"); } if ($host.Name -match 'consolehost') { $host.ui.RawUI.WindowTitle = $cfgHT.title + " (PSEC Console)" } $null = (& buildFlowForm $flow $dynHT $cfgHT.title) return $flow; } <#.md .parm $cfgHT The The configuration file converted to HashTable .desc This method creates the Task objects and populates them from the configuration file. .details Using the configuration file this method iterates through the Task list and builds the necessary objects for each task. #>
function populateFlowTaskList([FlowExec]$flow,$cfgHT) { try { [string]$libBase = $ENV:PSEC_V4_LIB_DIR if (exists $cfgHT libSetup) { if ($libBase -eq "") {throw "use of library requires PSEC_V4_LIB_DIR be defined in envionment variable"} $libImport = "$libBase/$($cfgHT.libSetup)" $flow.libSetup = (& importSetupCode $libImport); $flow.libPath = $libBase; } $flowSetupPath = "$($flow.flowPath)/$($flow.wflName)-setup.psm1" if (test-path $flowSetupPath) { $flowSetup = (& importSetupCode $flowSetupPath); [void]($flowSetup.psecFlowSetup($flow)); $flow.flowSetup = $flowSetup; } $ix = 0; $bHadErr = $false; [System.Collections.ArrayList]$taskList = @(); forEach ($t in $cfgHT.Tasks) { $ix += 1; $taskName = ""; $taskExec = ""; $taskType = ""; $taskDesc = ""; $taskTitle = ""; $bLib = $false; if ((exists $t inactive) -and ($t.inactive)) { #hlogNow("Skip $taskName $bLib"); continue; } if (exists $t lib) { if ($libBase -eq "") {throw "use of library requires PSEC_V4_LIB_DIR be defined in envionment variable(2)"} $tLib = (& verifyTaskFolderLibCopy $t) $taskName = $t.lib; $taskExec = $t.lib; $bLib = $true; if (exists $tLib type) { $taskType = $tLib.type; } if (exists $tLib title) { $taskTitle = $tLib.title; } if (exists $t alias) { # allows us to reuse same lib script in same workflow $taskName = $t.alias; } if (exists $t title) { # allow this to override lib $taskTitle = $t.title; } if (exists $tLib desc) { $taskDesc = $tLib.desc; } if (exists $t desc) { # allow this to override lib $taskDesc = $t.desc; } } elseif (exists $t name) { $taskName = $t.name; $taskExec = $t.name; if (exists $t type) { $taskType = $t.type; } if (exists $t title) { $taskTitle = $t.title; } if (exists $t desc) { $taskDesc = $t.desc; } } else { hlogNBoth("missing name or lib ix=$ix") $bHadErr = $true; } if ($taskType -eq "") { hlogNow("$taskName ix=$ix task type not defined") $bHadErr = $true; } else { if ($taskTitle -eq "") { $taskTitle = "$taskName - no title defined" } if ($taskDesc -eq "") { $taskDesc = "$taskName - no desc defined" } #$taskType = "cfg-err"; $task = [TaskExec]::new($taskName, $taskType, $taskTitle, $taskDesc); $task.tfs = $flow.tfs; $task.taskCfg = $t $task.execLocn = "$($flow.flowPath)/tasks/$taskName" $task.execdef = "$($task.execlocn)/$($taskName)-def.psm1" $task.helpLocn = "$($task.execlocn)/$($taskName)-help.rtf" if ($bLib) { $task.libLocn = "$($libBase)/$($taskExec)" $task.execdef = "$($libBase)/$($taskExec)/$($taskExec)-lib.psm1" if ($taskExec -eq $taskName) { $task.helpLocn = "$($libBase)/$($taskExec)/$($taskExec)-help.rtf" } } $task.statlocn = "$($task.execlocn)\brief.json.txt" $task.loglocn = "$($task.execlocn)\runlog.txt" $task.optlocn = "$($task.execlocn)\options.json.txt" importTaskDefnScript $flow $task $task.flow = $flow; hlog("configure lib=$bLib $taskName $taskType $taskTitle $taskDesc"); if (exists $t dependsOn) { $task.dependsOn = @() forEach($prior in $t.dependsOn.split("/")) { [Task]$hit = $null; forEach($prevTask in $taskList) { if ($prevTask.name -eq $prior) { $hit = $prevTask; break; } } if ($hit -eq $null) { throw "Lost $prior dependent task"; } $task.dependsOn += $hit; #if ($hit.depTasks -eq $null) { # $hit.depTasks = @(); #} #$hit.depTasks += $task } } if (exists $t whenFail) { [Task]$hit = $null; $task.whenFail = @() forEach($prior in $t.whenFail.split("/")) { forEach($prevTask in $taskList) { if ($prevTask.name -eq $prior) { $hit = $prevTask; break; } } if ($hit -eq $null) { throw "Lost $prior whenFail task"; } $task.whenFail += $hit; } } [void]$taskList.add($task); } } if ($bHadErr) { [void](& ErrorPanel "Quit due to error listed in DLOG"); return $null; } return ,$taskList.toArray(); } catch { hlogBoth("CATCH $_") hlogBoth("$($_.ScriptStackTrace)"); [void](& ErrorPanel "ERROR Catch $_"); return $null; } }
function importTaskDefnScript([FlowExec]$flow,[Task]$task) { # --- hook in definitions $import = "$($task.execdef)" if (test-path $import) { importTaskDefn $flow $task $import $false #if ($task.execOvrDef -ne $null) { #EC1B13 # importDefn $flow $tht $task $task.execOvrDef $true #} } else { <# previous if ($task.type -eq [TaskType]::Manual) { $task.statFlag("file $($task.execdef) not located",$true); ##[void]$task.find('View Params').disableLink(); } else { $task.statFlag("file $($task.execdef) not located",$true); ##[void]$task.find('View Params').disableLink(); } #> $task.type = [TaskType]::CfgErr; $task.desc = "$import definition does not exist" hlogBoth("CfgErr: $($task.desc)") } } <#.md .desc General import module handler .details .note14Powershell seemed more reliable when we used diferent names for the function and methods. #>
function importTaskDefn([Flow]$flow,[Task]$task,[string]$import,[boolean]$bOvrDef) { $task.statGood("$($task.execdef) found"); hLog("importing $import $($task.name) flow=$flow $bOvrDef") import-module $import -force -disableNameChecking -verbose [PSCustomObject]$custobj = $null; if ($bOvrDef) { $custobj = [PSCustomObject](& defineFlowTaskOver $flow $task) # get exported methods and variables $task.overObj = $custobj; } else { $custobj = [PSCustomObject](& defineFlowTask $flow $task) # get exported methods and variables $task.custObj = $custobj; } ##hlog("custObj $($custObj.getType())"); $custobj.PsObject.methods | ForEach { $name = $_.name $type = $_.getType() if("$type" -eq "psscriptmethod") { $val = $_.value ##hlog("cust.obj meth $name/$type/$val"); $task.methods[$name] = $true } } if (!$bOvrDef) { $custobj.PsObject.properties | ForEach { $name = $_.name $type = $_.getType() $val = $_.value ##hlog("cust.obj vbl $name $type $val"); $task.vbls[$name] = "$val"; # string only } } else { ##see $task } } <# Since the setup code contains classes we do this early in the load process so that the configuration can instantiate the class. The lib setup code s/b loaded before the flow setup code becuase of the class hierarchy. #>
function importSetupCode([string]$import) { hlogBoth("importSetupCode $import"); if (test-path $import) { import-module $import -force -disableNameChecking [PSCustomObject]$custobj = & defineSetupFunction # get exported methods and variables if (exists $custobj.PsObject.methods psecFlowSetup) { return $custObj; } else { throw "expected psecFlowSetup method not found in $import" } } else { throw "import code $import not found" } throw "import code $import cannot happen" } <#.md .desc Verify library reference for task folder .details This mechanism is used to allow templates of common routines to be used. During the start of a workflow the latest copy of the lib-.. script and *.md file while maintaining the original execLocn. Thus the underlying routines do not have to deal with scoping library copies. .notes #>
function verifyTaskFolderLibCopy([hashtable]$t) { [string]$libBase = $ENV:PSEC_V4_LIB_DIR if ($libBase -eq "") {throw "use of library requires PSEC_V4_LIB_DIR be defined in envionment variable"} $libDir = "$($libBase)/$($t.lib)" if (!(test-path $libDir)) {throw "lib $libDir reference $($t.lib) not defined in $libBase"} $libCfg = "$($libDir)/$($t.lib)-libcfg.json" if (!(test-path $libCfg)) {throw "lib cfg $libCfg not defined in $libDir"} $cfgHT = loadCfgFile($libCfg); if (notexists $cfgHT type) {throw "lib reference $libCfg must contain type spec"} <##if ($libCfg -eq $null) {throw "use of library requires psec_lib be defined in envionment variable"} hilite -yellow "libCfg $libFile $($libCfg)" if (notexists $libCfg $t.lib) {throw "lib reference $($t.lib) not defined in $libFile"} if (!(test-path "$($libBase)/$($t.lib)")) {throw "lib reference $($t.lib) has no folder in $libBase"} [hashtable]$lht = $libCfg[$t.lib] if (notexists $lht 'type') {throw "lib reference $($t.lib) must have type definition"} if (!(test-path "$($libBase)/$($t.lib)/lib-$($t.lib).psm1")) {throw "lib reference $($t.lib) has no script lib-$($t.lib).psm1"} $targLocn = "$baseLocn/tasks/$($t.lib)" if (!(test-path $targLocn)) {[void](mkdir $targLocn)} return $t #> return $cfgHT; }
function buildFlowForm([FlowExec] $flow,[hashtable]$dynHT, [string]$title) { $callBack = { param([string]$exitStr) #$tempCfg = $cfgfile -replace "config","tempcfg" #hlog("callback Form is closing $($cfg.activeTask) $cfgfile $tempcfg") hlogBoth("callback Form is closing cseq=$exitStr") <# $newHT = $cfg.clone(); $nIX = -1 forEach($t in $cfg.config.tasks) { $nIX += 1 if (exists $t lib) { ($newHT.config.tasks[$nIX]).remove('name') ($newHT.config.tasks[$nIX]).remove('title') } } stowConfig $cfgLocn $newHT; $appJob.lastExitCode = 0; #> } $form = (createWflForm $dynHT $callBack) ##$form.bDebLayout = $true; #make conditional on console being present $flow.wflForm = $form; $form.ctx = $flow; $form.peer.text = $title; #$form.baseLocn = $baseLocn; #$form.psecBase = $CVT.psecBase; $form.regColor('actionBG',240,240,255); $form.regColor('taskBG',220,220,255); $form.regColor('white',255,255,255); $form.regColor('red',255,0,0); $form.regColor('green',0,255,0); $form.regColor('blue',0,0,255); $form.regColor('black',0,0,0); $form.regColor('flag',255,0,0); $form.regColor('good',0,160,0); $form.regColor('warn',255,128,0); $panTop = ([Panel]::createPanel($form,'top')).setAbs(0,0).backColor('actionBG').setHorz().setMar(2,2).setGap(1,1); $panLog = ([Panel]::createLogger($form,'log',200)).setAbs(0,200).backColor('actionBG').setMar(2,2).setGap(1,1); $form.log("created form $($dynHT.winsize.title)"); [hashtable]$widgets = @{}; #$imgDir = "$($CVT.curApp.appDir)\imgs" if ("$($env:PSEC_V4_EXEC_DIR)" -eq "") { [void](& ErrorPanel "Environment variable PSEC_V4_EXEC_DIR not defined") return; } $imgDir = "$($env:PSEC_V4_EXEC_DIR)/res/imgs" $widgets.imgOK = & loadScaledImage $imgDir "ok-grn.png" 50 $widgets.imgQ = & loadScaledImage $imgDir "q-org.png" 35 $widgets.imgX = & loadScaledImage $imgDir "x-red.png" 40 $widgets.imgWOK = & loadScaledImage $imgDir "ok-org.png" 50 $widgets.imgWX = & loadScaledImage $imgDir "x-org.png" 40 $widgets.imgErr = & loadScaledImage $imgDir "trouble-red.png" 40 $null = createWorkFlowPanel $flow $panTop $widgets $tab = $flow.wflForm.form.findElem("tab-panel"); if ($tab.curPanel.name -eq "##-welcome") { $null = (& populateWorkWelcome $flow) $form.bLayDirty = $true; $null = $form.layout(); } #[void](& resizeExit $form $null); <# $form.layoutExit = { param([Form]$form) $tab = $form.findElem("tab-panel"); if ($tab.curPanel.name -eq "##-welcome") { $welPan = $form.findElem("##-welcome-box"); $form.showWelcome($welPan,$null); } } #> hlog("showForm about to be issued"); $form.ShowForm() } <#.md .desc Create Workflow specialized form .parm $closeCallBack Form close custom processing handlerexecLocn = .details #>
function createWflForm([hashtable]$dynCfg,[scriptBlock] $closeCallBack) {# $form -ne $null for extended Form class $peer = New-Object Windows.Forms.Form $wflForm = [WflForm]::new($peer,'main', $dynCfg.winsize.formW, $dynCfg.winsize.formH); [void]([Form]::createForm($null,$dynCfg,$closeCallBack,$wflForm)) $icon = getGuiIcon("bng"); $peer.icon = $icon; #$peer.onLoad = $function:openExit; <#$resizeExit = { param($form,$ctx) $logger = $form.findElemOpt("LOGGER"); #$taskHdr = $form.findElemOpt("task-hdr"); $p = $form.peer.size $wid = $p.width; $hgt = $p.height; $task = $($form.activeTask); $hdr = $task.hdrDiv; $body = $task.bodyDiv; hlog("resizeExit logger=$($logger.peer.size.height) hdr=$($hdr.minH) body=$($body.minH)/$($body.reqH) $hgt $task"); $minH = $hdr.minH + $logger.peer.size.height + 30 $remH = $hgt - $minH $scroll = "" if ($body.reqH -gt $remH) { $scroll = "Scroll $remH" $body.setMaxHgt($remH); } else { $body.setMaxHgt(0); } $form.calcMinHeight(); $form.bLayDirty = $true; hlog("resizeExit done $scroll min=$minH remH=$remH hgt=$hgt maxH=$($body.maxH)"); }#> $wflForm.resizeExit = $function:resizeExit; $wflForm.openExit = $function:openExit; return $wflForm; } <# EC2701.This exit is required because the RTF formatting does not take until the window shows. It means when there is no valid active Tab and we show the default help then we need to do this. Will see if we need to do this for other active tabs. #>
function openExit([Form]$form,[object]$ctx) { hlogBoth("formOpen exit $form ctx=$ctx"); $tab = $form.findElem("tab-panel"); if ($tab.curPanel.name -eq "##-welcome") { $null = (& populateWorkWelcome $ctx); } }
function resizeExit([Form]$form,[object]$ctx) { $logger = $form.findElemOpt("LOGGER"); #$taskHdr = $form.findElemOpt("task-hdr"); $p = $form.peer.size $wid = $p.width; $hgt = $p.height; $task = $($form.activeTask); if ($task -eq $null) { #& see $form ##throw "lost active task $($form.activeTask) - fix cfg" # EC0204 - If at thos point the workflow is saved the activeTask pointer gets nullified. hlog("Unable to locate task definition $($form.cfg.activeTask) or it is null"); $form.logFlag("Unable to locate task definition $($form.cfg.activeTask) or it is null. Manually config workflow config file"); return; } $hdr = $task.hdrDiv; $body = $task.bodyDiv; hlog("resizeExit logger=$($logger.peer.size.height) hdr=$($hdr.minH) body=$($body.minH)/$($body.reqH) $hgt $task"); $minH = $hdr.minH + $logger.peer.size.height + 30 $remH = $hgt - $minH $scroll = "" if ($body.reqH -gt $remH) { $scroll = "Scroll $remH" $body.setMaxHgt($remH); } else { $body.setMaxHgt(0); } $form.bNoJam = $true; $form.calcMinHeight(); $form.bLayDirty = $true; hlog("resizeExit done $scroll min=$minH remH=$remH hgt=$hgt maxH=$($body.maxH)"); }
function showWorkHelp([ActionSet]$actSet,[Elem]$elem,[object]$ctx) { [FlowExec]$flow = $actSet.flow; hlogBoth("show help action set $($flow.wflName)"); $flow.showHelp($elem,$ctx); }
function showWorkWelcome([ActionSet]$actSet,[Elem]$elem,[object]$ctx) { hlogBoth("show welcome action set $($actSet.flow)"); [FlowExec]$flow = $actSet.flow; $flow.wflForm.findElem('tab-panel').switchPanel("##-welcome"); $null = (& populateWorkWelcome $flow) }
function populateWorkWelcome([FlowExec]$flow) { hlogBoth("populateWorkWelcome $flow"); $form = $flow.wflForm; $locnRTF = "$($flow.flowPath)\$($flow.wflName)-doc.rtf" hlog("show Welcome $locnRTF"); #$form.findElem('tab-panel').switchPanel("##-welcome"); #$form.cfg.activeTask = "##-welcome"; #EC0204 $box = $form.findElem('##-welcome-box'); $box.peer.text = "no text found to load for $($locnRTF -replace '.rtf','.md')"; $box.peer.ScrollBars = 'Vertical' $box.peer.ZoomFactor = 0.80; $parPeer = $box.peer.parent; hlog("sizes $($box.peer.size) $($box.peer.height) $($box.peer.width)") hlog("par.sizes sz=$($parPeer.size) h=$($parPeer.height) w=$($parPeer.width)") $w = $parPeer.width; $h = $parPeer.height; $box.peer.Size = "$($w - 15),$($h - 20)"; $box.peer.RightMargin = $w - 110 $textRTF = (convertAndLoadRtfFile $locnRTF "" ""); if ($textRtf -ne $null) { $box.peer.RTF = $textRTF } } # ------------------------ Standard Constructs ------------------ <#.md .desc Create the right-side Workflow tabbed panel .details #>
function createWorkFlowPanel([FlowExec]$flow,[Panel]$par,[hashtable]$widgets) { #$cfg = $par.form.cfg; $par.form.regFont('taskHead',$par.form.getBoldFont(14)); $par.form.regFont('bodyLabel',$par.form.getBoldFont(12)); $par.form.regFont('hdrLink',$par.form.getFont(12)); $par.form.regFont('menuLabel',$par.form.getFont(11)); $par.form.regIcon('iconOK',$widgets.imgOK); $par.form.regIcon('iconWOK',$widgets.imgWOK); $par.form.regIcon('iconQ', $widgets.imgQ); $par.form.regIcon('iconErr', $widgets.imgErr); $par.form.regIcon('iconX', $widgets.imgX); $par.form.regIcon('iconWX', $widgets.imgWX); hlog("color is $($par.form.colors.actionBG)") $panLft = ([Panel]::createPanel($par,'lft')).setAbs(310,0).backColor("actionBG").setMar(2,2).setGap(1,1); $panHdr = ([Div]::createDiv($panLft,'left-top')).setHorz().setAbs(0,1).backColor("actionBG").setGap(20,1); $panLab = ([Label]::createLabel($panHdr,'Workflow Steps')); $panWelcome = ([Link]::createLink($panHdr,'Welcome')).dock("right").backColor("actionBG"); $welcomeAction = [ActionSet]::new($flow,$null,$function:showWorkWelcome); $panWelcome.action = $welcomeAction.actionMethod; $panHelp = ([Link]::createLink($panHdr,"Help$([char]0xa0)$([char]0xa0)$([char]0xa0)$([char]0xa0)")).dock("right").backColor("actionBG"); $helpAction = [ActionSet]::new($flow,$null,$function:showWorkHelp); $panHelp.action = $helpAction.actionMethod; $par.form.regElem($panLft,'menu-panel'); $panRgt = ([TabPanel]::createTabPanel($par,'rgt')).setAbs(0,0).backColor("actionBG").setMar(2,2).setGap(1,1); $par.form.regElem($panRgt,'tab-panel'); $panRgt.switchExit = { param([TabPanel]$swElem,[Panel]$curPanel) $name = $curPanel.name hlog("switch exit called $name $curPanel"); $flow.dynHT.activeTask = $name [void](stowConfig $flow.dynPath $flow.dynHT); if ($name -ne "##-welcome") { $curPanel.form.cfg.activeTask = $name; $task = $curPanel.tied hlog("switch to $name TabPanel task=$task"); $curPanel.form.activeTask = $task; #EC8C20 if ($task -ne $null) { $task.menuDiv.backColor("white") } $task.refreshRgtPanel(); $winSize = "$($curPanel.form.peer.size.width),$($curPanel.form.peer.size.height)"; if ($winSize -ne $task.origWinSize) { hlog("resize needed for $winSize $($task.origWinSize)"); $task.origWinSize = $winSize; [void](& resizeExit $curPanel.form $null); } } #if ($curPanel.tied -ne $null) {[void](& prepareLivePanel $curPanel)} } #create welcome panel $welPan = ([Panel]::createPanel($panRgt,"##-welcome")).setMar(10,10).backColor("actionBG"); $box = ([TextBox]::createTextBox($welPan,"400,400",$null,$true)).setAbs(1,0).setFill(); $par.form.regElem($box,'##-welcome-box') $welPan.peer.backColor = rgb 240 233 241; $box.peer.backColor = rgb 240 233 241; $box.peer.BorderStyle = "none"; $panRgt.addTabPanel($welPan) forEach($task in $flow.tasks) { hlogBoth("lft menu task $($task.toString())") $div = (createMenuDiv $flow $task $panLft $widgets).setAbs(0,1).backColor('taskBG'); $task.menuDiv = $div; $div.tied = $task; if ($task.whenFail -ne $null) { $div.bHidden = $true; } #if (exists $task.vbls desc) { <# $task.find("desc-area").bHidden = $false; $desc = $task.find("desc-lines"); $text = $task.vbls.desc -replace "`r",""; $text = $text -replace "`n"," "; $text = $text -replace "
","`r`n"; $text = $text -replace "<.br.>","
"; $desc.peer.text = $text; if (exists $task.vbls descHint) { $hint = $task.vbls.descHint -replace "`r",""; $hint = $hint -replace "`n"," "; $hint = $hint -replace "
","`r`n"; $hint = $hint -replace "<.br.>","
"; $desc.form.toolTip.setToolTip($desc.peer,$hint); } #> #} } forEach($task in $flow.tasks) { $task.form = $form; $task.createRgtPanel($flow,$panLft,$panRgt); if (exists $task.methods primeContextTask) { $htPrev = @{}; if (test-path $task.statLocn) { $json = Get-Content -path $task.statlocn -encoding ascii [hashtable]$htPrev = jsonAsHashTable $json } $task.custObj.primeContextTask($task,$htPrev); } } ##return $flow; ##=================== early exit <# # [RunProps]$p = [RunProps]::new(); $p.name = 'TestFlow'; $p.year = 2018; $p.mon = 10;# > [CustFlowBase]$flowCust = [CustFlowBase](importWflSetup $appJob $wflName $baseLocn $cfg $libBase); if ($flowCust -ne $null) {hlog("flowCust $flowCust")} [Flow]$flow = [Flow]::new($wflName,$flowCust); $par.form.flow = $flow; #> # $cycleTask = $null;createHdrLink <# forEach($tht in $cfg.config.tasks) { if (!(exists $tht title)) {$tht.title = $null;} #if (!(exists $tht desc)) {$tht.desc = $null;} #hlog('task n={0} t={1} desc={2} title={3}' -f $tht.name,$tht.type,$tht.desc,$tht.title); [Task]$task = formatTask $baseLocn $flow.tasks $tht $libBase $libCfg; $task.form = $par.form; $task.flow = $flow; $task.cfb = $flowCust; $par.form.regTask($task); $task.tabPan = $panRgt; $flow.addTask($task); if (exists $tht whenCycle) { ## never fully implemented if ($cycleTask -eq $null) {throw "expected previous task type cycle $task"} $cycleTask.depTasks += $task; $task.whenCycle = $tht.whenCycle; if ($task.dependsOn -eq $null) {$task.dependsOn = @();} $task.dependsOn += $cycleTask; $task.arrIX = $task.form.cycle["ix_$($task.whenCycle)"].split(','); $task.cycleTask = $cycleTask; } # --- build status panel if (exists $tht lib) {$tht.name = $tht.lib} $pan = ([Panel]::createPanel($panRgt,$tht.name)).setMar(10,10).backColor("actionBG"); $pan.tied = $task; $hdr = ([Div]::createDiv($pan,"hdr")).setHorz().setAbs(0,1); $task.hdrDiv = $hdr; $lab = ([Label]::createLabel($hdr,"task-head-title")).setFont('taskHead').hook($task).setText("Task: $($task.desc)" -f $task.getCurCycle()) $lab.form.toolTip.SetToolTip($lab.peer,"$($task.name) $($task.type)"); #show mapping to definition $ancs = ([Div]::createDiv($hdr,"anchors")).setHorz().setAbs(1,0).setGap(50,0).dock('right'); [ViewTaskBase]$vt = createViewTask $flow $task $panLft $panRgt $ancs; if ($task.type -eq "manual") { #[void](createHdrLink $ancs 'Reset TODO Steps' $task $task.markManualTaskNotDone).disableLink().hook($task); $vt.createWflHdrLink('Reset TODO Steps',$vt.markManualTaskNotDone).disableLink().hook($task); } elseif ($task.type -eq "cycle") { $cycleTask = $task; $task.depTasks = @(); } elseif ($task.type -eq "ctrlpage") { } else { $vt.createWflHdrLink('View Log',$vt.viewRunTaskLog).disableLink().hook($task); $taskLink = $vt.createWflHdrLink('Run Task',$vt.RunTask).hook($task); } if ($task.type -ne "manual") { $vt.createWflHdrLink('View Params',$vt.viewTaskParams).hook($task); } $vt.createWflHdrLink('View Task',$vt.viewTaskSetup).hook($task); $vt.createWflHdrLink('View Help',$vt.viewTaskHelp).hook($task); $body = ([Div]::createDiv($pan,"body")); $task.bodyDiv = $body; $body.bUseVScr = $true; $task.origWinSize = "$($body.form.peer.size.width),$($body.form.peer.size.height)"; # for task switches $DescArea = ([Div]::createDiv($body,"desc-area")).setHorz().hook($task) $DescArea.bHidden = $true; #reserve position [void]([Label]::createLabel($DescArea,"Description:","145,40")).setFont('bodyLabel').setAbs(1,1) [void]([Label]::createLabel($DescArea,"desc-lines")).setAbs(300,0).hook($task).maxWidth(700); $StatLine = ([Div]::createDiv($body,"stat-line")).setHorz().setAbs(0,1); [void]([Label]::createLabel($StatLine,"Status:","145,40")).setFont('bodyLabel').setAbs(1,1) $task.statElem = ([Label]::createLabel($StatLine,"to-be-loaded","450,30")).setFont('bodyLabel') [void]([Label]::createLabel($StatLine,"stat-why","450,30")).hook($task).setText('') [void]([Label]::createLabel($StatLine,"stat-time")).hook($task).setText(''); $OptArea = ([Div]::createDiv($body,"opt-area")).setHorz().hook($task) $OptArea.bHidden = $true; #reserve position [void]([Label]::createLabel($OptArea,"Options:","145,40")).setFont('bodyLabel').setAbs(1,1) [void]([Div]::createDiv($OptArea,"opt-lines")).hook($task); importTaskDefn $flow $tht $task if ($task.type -eq 'ctrlpage') { if ($task.execBut -eq $null) {throw "expect execBut to be populated"} $execBut = $task.execBut; $execDiv = ([Div]::createDiv($body,"exec-button")).setHorz(); [void]([Label]::createLabel($execDiv,"Execute:","145,40")).setFont('bodyLabel').setAbs(1,1) $execBut.but = ([Button]::createButton($execDiv,$execBut.butText,"200,40",$null)).clickHandler([ElemMate]::new("click",$execBut.clickScript,$task)); $execBut.setServerStatus($execBut.servStat); } if (($task.type -ne 'manual') -and ($task.type -ne 'ctrlpage')) { $BrfArea = ([Div]::createDiv($body,"stat-line")).setHorz(); [void]([Label]::createLabel($BrfArea,"Brief:","145,40")).setFont('bodyLabel').setAbs(1,1) $GridArea = ([Div]::createDiv($BrfArea,"grid-area")).setAbs(800,400); $GridArea.peer.AutoScroll = $false; $GridArea.peer.HorizontalScroll.Enabled = $false; $GridArea.peer.HorizontalScroll.Visible = $false; $GridArea.peer.HorizontalScroll.Maximum = 0; $GridArea.peer.AutoScroll = $true; [void]([Grid]::createGrid($GridArea,$null,$null,$null,$task)).setName("brf-grid").hook($task).hidePeer($true); $task.find("brf-grid").peer.size = "880,100"; } else { $bUpdateBrf = $false; if ($task.type -eq 'manual') { $StepDiv = ([Div]::createDiv($body,"step-line")).setHorz(); [void]([Label]::createLabel($StepDiv,"Steps:","145,40")).setFont('bodyLabel').setAbs(1,1) $StepArea = ([Div]::createDiv($StepDiv,"step-area")).setAbs(800,400); $ix = -1 $steps = $task.vbls.manSteps.split("/"); $task.manSteps = New-Object ManStep[] $steps.count [void]$task.getBriefFile() if ($task.brief -eq $null) {$task.brief = @{sCondCode='FAIL';sReason='Step(s) not performed'}} forEach($step in $steps) { $ix += 1 $manStep = [ManStep]::new() $manStep.task = $task; $manStep.name = $step; $manStep.line = $ix + 1; $StepLine = ([Div]::createDiv($StepArea,"step-line")).setAbs(1,1).setHorz(); $click = { param($evt) $elem = $evt.tag; $manStep = $elem.tied; hlog("checkbox clicked $evt $($evt.getType()) $($manStep.name) checked=($evt.checked)"); if (!$manStep.bIgnore) { [void]$manStep.task.getBriefFile(); $manStep.task.brief["step-$($manStep.line)"] = $evt.checked; $manStep.task.stowBriefFile($manStep.task.brief); $manStep.task.markManualTaskStatus($true); } } $chkLine = ([CheckBox]::createCheckBox($StepLine,"50,30",$null,"")) $manStep.chkBox = $chkLine; $chkLine.tied = $manStep; $labName = "step-$($manStep.line)" if (exists $task.brief $labName) { $chkLine.peer.checked = $task.brief[$labName]; } else { $task.brief[$labName] = $false; $bUpdateBrf = $true; } [void]([Label]::createLabel($StepLine,"$($manStep.line). $($manStep.name)","600,40")).setFont('bodyLabel').setAbs(1,1) $task.manSteps[$ix] = $manStep; $chkLine.peer.add_CheckedChanged($click); } } if ($bUpdateBrf) {$task.stowBriefFile($task.brief)}; } if (exists $task.methods primeTask) {[void]$task.custobj.primeTask($task,$cfg)}; if ($task.type -eq "cycle") { if (!(exists $task.methods onSwitchTask)) {throw "task type cycle requires onSwitchTask method $($task.name)"} if (!(exists $task.methods primeCycles)) {throw "task type cycle requires primeCycles method $($task.name)"} [void]$task.custobj.primeCycles($task,$tht); $task.form.cycleController = $task; if ($task.dependsOn -ne $null) { $task.briefTS = $task.dependsOn[0].briefTS + 1; #never run so fake briefTS } } $panRgt.addTabPanel($pan); } hlog("Created $flow"); forEach($task in $flow.tasks) { hlogBoth("rgt menu task $task") $div = (createMenuDiv $task $panLft $widgets).setAbs(0,1).backColor('taskBG'); $task.menuDiv = $div; $div.tied = $task; if ($task.whenFail -ne $null) { $div.bHidden = $true; } if (exists $task.vbls desc) { $task.find("desc-area").bHidden = $false; $desc = $task.find("desc-lines"); $text = $task.vbls.desc -replace "`r",""; $text = $text -replace "`n"," "; $text = $text -replace "
","`r`n"; $text = $text -replace "<.br.>","
"; $desc.peer.text = $text; if (exists $task.vbls descHint) { $hint = $task.vbls.descHint -replace "`r",""; $hint = $hint -replace "`n"," "; $hint = $hint -replace "
","`r`n"; $hint = $hint -replace "<.br.>","
"; $desc.form.toolTip.setToolTip($desc.peer,$hint); } } if (exists $task.methods options) { $opts = $task.fetchOptions(); if ($opts.count -gt 0) { $task.find("opt-area").bHidden = $false; forEach($opt in $task.methods.options) { $row = buildOptionLine $task $task.find("opt-lines") } } } if (testPath($task.statlocn)) { $task.populateBriefGrid($true); } if (testPath($task.loglocn)) { [void]$task.find("View Log").enableLink(); } if ($task.type -eq "manual") { ##$task.markManualTaskStatus($false); } } ##$flow.tasks[0].updateDependencyTree($true); #> $flow.tasks[0].updateDependencyTree($true); $activePanel = "##-welcome"; $tabPan = $par.form.findElem('tab-panel'); if ((exists $flow.dynHT activeTask) -and ($flow.dynHT.activeTask -ne "?") -and ($tabPan.panels[$flow.dynHT.activeTask] -ne $null)) { $activePanel = $flow.dynHT.activeTask; } $tabPan.switchPanel($activePanel); return $flow; } <#.md .desc Create Menu Div of Workflow form .parm $widgets ICON image references .details #>
function createMenuDiv([FlowExec]$flow,[Task]$task,[Panel]$par,[hashtable]$widgets) { hlog("createdMenuDiv $($task.toString()) $($task.title)"); [Area]$menu = $par; if ($task.type -eq 'cycle') { $menu = [Area]([Div]::createDiv($par,"cycle-menu")).setMar(0,0); } $div = ([Div]::createDiv($menu,$task.name)).setHorz().hook($task) <# if ($task.type -eq 'cycle') { [void]([Div]::createDiv($menu,"cycle-area")).setAbs(1,1).hook($task); if (!(exists $task.methods formatCycleLinkArea)) {throw "task type cycle requires formatCycleLinkArea method $($task.name)"} try { #hlog("calling $($task.custobj.formatCycleLinkArea.getType())"); [void]$task.custobj.formatCycleLinkArea($task); } catch { $e = $_ hLogErr("$($e.exception.message)") hLogErr("$($e.ScriptStackTrace)") throw "that is it" } } #> if ($task.type -eq [TaskType]::CfgErr) { $null = ([Icon]::createIcon($div, $widgets.imgErr)).setName("task-icon").hook($task); } else { $null = ([Icon]::createIcon($div, $widgets.imgQ)).setName("task-icon").hook($task); } $lab = ([Label]::createLabel($div,"$($task.stepLabel)")).setFont('menuLabel'); ## $lab.form.toolTip.SetToolTip($lab.peer,"$($task.name) $($task.type)`r`nRight Click for popup menu"); #show mapping to definition ## [void](createActionMenu $task $lab) $menuDivAction = [ActionSet]::new($flow,$task,$function:showMenuDivAction); $lnk = ([Link]::createLink($div,"menu-link-name")).setFont('menuLabel').backColor('taskBG').hook($task).setLinkText($task.name); $lnk.action = $menuDivAction.actionMethod; if ($task.title -ne "") { [void]$lnk.setLinkText($task.title) } $lnk.tied = $task; <# $lnk.onClick = { param([Elem]$elem) [Task]$task = $elem.tied hlog("clicked $elem $($task.name) twin=$($elem.tied)"); try { $elem.tied.tabPan.switchPanel($task.name); } catch { $e = $_ hLogErr("$($e.exception.message)") hLogErr("$($e.ScriptStackTrace)") } } #> return $div; }
function showMenuDivAction([ActionSet]$actSet,[Elem]$elem,[object]$ctx) { hlogBoth("show menu $($actSet.flow.wflName) $($actSet.task.toString())"); $actSet.task.tabPan.switchPanel($actSet.task.name); } <#.md .desc Create menu of actions for Task .details #>
function createActionMenu([Task]$task,[Label]$lab) { $cms = New-Object System.Windows.Forms.ContextMenuStrip [Flow]$flow = $lab.form.flow; [void](createActionItem $cms $flow $task "Configure $($flow.wflName) Workflow" $function:actionCfgWorkflow); if ($flow.cfb -ne $null) { [void](createActionItem $cms $flow $task "Edit $($flow.wflName) Workflow setup" $function:actionEditFlowSetup); } [void](createActionItem $cms $flow $task "Edit $($task.name) definition" $function:actionEditTaskDef); $lab.peer.ContextMenuStrip = $cms } <#.md .desc Create single action item for Action Menu .details #>
function createActionItem([System.Windows.Forms.ContextMenuStrip]$cms,[Flow]$flow,[Task]$task,[string]$action,[ScriptBlock]$func) { $mi = new-object System.Windows.Forms.ToolStripMenuItem::new($action); $mi.tag = [ActionSet]::new($flow,$task,$func); $mi.add_Click({ [ActionSet]$set = $this.tag; hlog("action $($this.text) clicked"); & $set.func $set.flow $set.task }); [void]$cms.Items.Add($mi); }
X
PSEC - Powershell Enhanced Capability
1.2.1
  src: flow-work-logic.psm1

Copyright © 2018-2021, 2022, Rexcel System Inc.