Class ::xowf::Context

::xowf::Context[i] create ... \
           [ -all_roles (default "false") ] \
           [ -current_state (default "[self]::initial") ] \
           [ -default_definition (default "") ] \
           [ -in_role in_role ] \
           [ -object object ] \
           [ -wf_container wf_container ] \
           [ -workflow_definition workflow_definition ]

Defined in

Class Relations

  • class: ::xotcl::Class[i]
  • superclass: ::xotcl::Object[i]
::xotcl::Class create ::xowf::Context \
     -superclass ::xotcl::Object

Methods (to be applied on the object)

  • require (scripted)

     xowf::Context[i] require

    Testcases:
    create_workflow_with_instance, xowf
    #
    # Make sure, the context object for workflow '$obj exists. The
    # flag "-new" can be used to make sure, a new and fresh context is
    # available.
    #
    #:log "START-require"
    #
    set ctx $obj-wfctx
    if {$new && [nsf::is object $ctx]} {
      $ctx destroy
    }
    
    if {![nsf::is object $ctx]} {
      set wfContextClass [$obj wf_property workflow_context_class [self]]
    
      regsub -all \r\n [$obj wf_property workflow_definition] \n workflow_definition
      $wfContextClass create $ctx  -object $obj  -workflow_definition $workflow_definition  -destroy_on_cleanup
      $ctx initialize_context $obj
    }
    
    #:log "END-require ctx <$ctx>"
    return $ctx

Methods (to be applied on instances)

  • all_roles (setter)

  • as_graph (scripted)

    set dot ""
    set dot [::util::which dot]
    if {$dot eq ""} {
      return "<font color='red'>Program 'dot' is not available! No graph displayed.</font>"
    }
    set dotcode [:dotcode -current_state $current_state -visited $visited -dpi $dpi]
    
    set svg [util::inline_svg_from_dot -css [subst {
      svg g a:link {text-decoration: none;}
      div.inner svg {height:100%; overflow: visible; $style; margin: 0 auto;}
    }] $dotcode]
    return $svg
  • auto_form_constraints (scripted)

    if {${:wf_container} ne [self]} {
      return [${:wf_container} auto_form_constraints]
    } elseif {[info exists :auto_form_constraints]} {
      return ${:auto_form_constraints}
    }
    return ""
  • auto_form_template (scripted)

    if {${:wf_container} ne [self]} {
      return [${:wf_container} auto_form_template]
    } elseif {[info exists :auto_form_template]} {
      return ${:auto_form_template}
    }
    return ""
  • autoname (scripted)

    #
    # We want to distinguish between a set "autoname" and an
    # unspecified "autoname". Therefore, we do not want to use a
    # default in the WorkflowContainer
    #
    if {${:wf_container} ne [self]} {
      if {[${:wf_container} exists autoname]} {
        return [${:wf_container} autoname]
      }
    } elseif {[info exists :autoname]} {
      return ${:autoname}
    }
    return "f"
  • check (scripted)

    ns_log notice "--- check context"
    # Check minimal contents
    set o [:wf_definition_object initial]
    if {![nsf::is object $o] || ![$o istype State]} {
      return [list rc 1 errorMsg "No State 'initial' defined"]
    }
    # ease access to workflow constructs
    foreach s [:defined State]     {set state([$s name])  $s}
    foreach a [:defined Action]    {set action([$a name]) $a}
    foreach a [:defined Condition] {set condition([$a name]) $a}
    array set condition {else 1 true 1 default 1}
    # Check actions
    foreach a [:defined Action] {
      # Are some "next_states" undefined?
      foreach {cond value} [$a get_cond_values [$a next_state]] {
        if {$cond ne "" && ![info exists condition($cond)]} {
          return [list rc 1 errorMsg "Error in action [$a name]: no such condition '$cond' defined  (valid: [lsort [array names condition]])"]
        }
        if {$value ne "" && ![info exists state($value)]} {
          return [list rc 1 errorMsg "Error in action [$a name]: no such state '$value' defined  (valid: [lsort [array names state]])"]
        }
      }
    }
    foreach s [:defined State] {
      # Are some "actions" undefined?
      foreach {cond actions} [$s get_cond_values [$s actions]] {
        foreach a $actions {
          if {![info exists action($a)]} {
            return [list rc 1 errorMsg "Error in state [$s name]: no such action '$a' defined  (valid: [lsort [array names action]])"]
          }
        }
      }
      if {[$s form_loader] eq "" && [$s form] ne ""} {
        set :forms([$s form]) 1
      }
    }
    foreach p [:defined ::xowiki::formfield::FormField] {
      if {[$p exists parampage]} {set :parampages([$p set parampage]) 1}
    }
    
    #:msg "forms=[array names :forms], parampages=[array names :parampages] in-role [info exists :in_role] [array names :handled_roles]"
    
    if {![info exists :in_role]} {
      foreach role [array names :handled_roles] {
        set role_ctx [self]-$role
        if {[nsf::is object $role_ctx]} {
          set info [$role_ctx check]
          if {[dict get $info rc] == 1} {
            return $info
          }
          array set :forms [$role_ctx array get forms]
          array set :parampage [$role_ctx array get parampage]
        }
      }
      #:msg "forms=[array names :forms], parampages=[array names :parampages]"
      set page ${:object}
      $page references clear
      $page set __unresolved_object_type ::xowiki::Form
      foreach {type pages} [list wf_form [array names :forms] wf_parampage [array names :parampages]] {
        foreach p $pages {
          set form_info [:resolve_form_name -object $page $p]
          set l [::xowiki::Link new -volatile  -lang en  -page $page  -type $type  -name [dict get $form_info name]  -item_id [dict get $form_info form_id]]
          #
          # The "render" method of the link does the optional fetch of
          # the names, and maintains the variable references of the
          # page object (similar to render).
          #
          set link_text [$l render]
        }
      }
      set references [$page references get resolved]
      #:log "-- link_text=$link_text// $references"
    
      if {[llength $references] > 0} {
        #:msg "updating references refs=$references"
        $page references_update [lsort -unique $references]
        $page set __extra_references $references
        $page references clear
      }
      if {[llength [$page references get unresolved]] > 0} {
        #
        # TODO: We should provide a link to create the missing
        # forms. Maybe we change unresolved_references to a list...,
        # or maybe we write these into the DB.
        #
        :msg -html t "Missing forms: [join [$page references get unresolved] {, }]"
      }
    }
    return [list rc 0]
  • create_auto_form (scripted)

    #
    # Create a form on the fly. The created form can be influenced by
    # "auto_form_template" and "auto_form_constraints".
    #
    set vars [dict keys [$object set instance_attributes]]
    set template [:auto_form_template]
    if {$template ne ""} {
      :log "USE autoform template"
    } elseif {[llength $vars] == 0} {
      #set template "AUTO form, no instance variables defined,<br>@_text@"
      set template "@_text@"
    } else {
      set template "@[join $vars @,@]@<br>@_text@"
    }
    
    #:log "USE auto-form template=$template, vars=$vars  #        IA=[$object set instance_attributes],  #        V=[$object info vars] auto [:autoname]"
    
    set package_id [$object package_id]
    return [::xowiki::Form new  -package_id $package_id  -parent_id [::$package_id folder_id]  -name "Auto-Form"  -anon_instances [:autoname]  -form {}  -text [list $template text/html]  -form_constraints [:auto_form_constraints]  -destroy_on_cleanup ]
  • create_workflow_definition (scripted)

    #
    # Validation: since for shared workflow definitions, the workflow
    # container is named after the revision, we need only a validation
    # of "xowf::include" content (external source content). So the,
    # container-specific workflow_definition is revision specific, and
    # it will never change.
    #
    # set :__workflow_definition [list $workflow_definition]
    #
    if {[catch {${:wf_container} contains "
       Class create Action   -superclass  ::xowf::Action
       Class create State    -superclass  ::xowf::State
       Class create Condition -superclass ::xowf::Condition
       Class create Property -superclass  ::xowf::Property -set abstract 1
       [:default_definition]
       $workflow_definition"} errorMsg]} {
      ns_log error "Error in workflow definition ([${:object} name]): $errorMsg\n$::errorInfo\n ===== default_definition: [:default_definition] \n ===== workflow_definition: $workflow_definition"
      :msg -html t "Error in workflow definition of [${:object} name]: [ns_quotehtml $errorMsg]"
    }
    
    #
    # Store state of xowf-depends in the container for later
    # comparison.
    #
    if {[info exists ::__xowf_depends]} {
      ${:wf_container} set __xowf_depends [set ::__xowf_depends]
    }
    
    if {${:all_roles}} {
      #:msg want.to.create=[array names :handled_roles]
      foreach role [array names :handled_roles] {
        Context create ${:wf_container}-$role  -workflow_definition $workflow_definition  -in_role $role  -object ${:object}
      }
    }
  • current_state (setter)

  • debug (scripted)

    if {${:wf_container} ne [self]} {
      return [${:wf_container} debug]
    } elseif {[info exists :debug]} {
      return ${:debug}
    }
    return 0
  • default_definition (setter)

  • default_load_form_id (scripted)

    #:msg "resolving $form_name in state [:current_state] via default form loader"
    set form_id 0
    if {$form_name ne ""} {
      set resolved [:resolve_form_name -object ${:object} $form_name]
      set form_id [dict get $resolved form_id]
      if {$form_id == 0} {
        ns_log warning "could not resolve '$form_name' for ${:object}$resolved"
      }
      #:msg ".... object ${:object} ==> id = $form_id"
    }
    return $form_id
  • defined (scripted)

    set result [list]
    foreach c [${:wf_container} info children] {
      if {[$c istype $what]} {lappend result $c}
    }
    return $result
  • dotcode (scripted)

    set obj_id [namespace tail ${:object}]
    set dotcode [subst {digraph workflow_$obj_id \{
      dpi = $dpi;
      node \[shape=doublecircle, margin=0.001, fontsize=8, fixedsize=1, width=0.4, style=filled\]; start;
      node \[shape=ellipse, fontname="Courier", color=lightblue2, style=filled,
      fixedsize=0, fontsize=10, margin=0.06\];
      edge \[fontname="Courier", fontsize=9\];
    }]
    foreach s [:defined State] {
      if {[$s name] eq $current_state} {
        set color ",color=orange"
      } elseif {[$s name] in $visited} {
        set color ",color=yellow"
      } else {
        set color ""
      }
      append dotcode "  state_[$s name] \[label=\"[$s label]\"$color\];\n"
    }
    set initializeObj [:wf_definition_object initialize]
    if {[nsf::is object $initializeObj]} {
      append dotcode "start->state_initial \[label=\"[$initializeObj label]\"\];\n"
    } else {
      append dotcode "start->state_initial;\n"
    }
    
    set :condition_count 0
    foreach s [:defined State] {
      foreach a [$s get_actions -set true] {
        set actionObj [:wf_definition_object $a]
        append dotcode [:draw_transition $s $actionObj ""]
        set drawn($actionObj) 1
      }
      foreach role [$s set handled_roles] {
        set role_ctx [self]-$role
        #:msg exists?role=$role->[self]-$role->[nsf::is object ${role_ctx}]
        if {[nsf::is object ${role_ctx}::[$s name]]} {
          foreach a [${role_ctx}::[$s name] get_actions] {
            append dotcode [:draw_transition $s ${role_ctx}::$a "$role:"]
          }
        }
      }
    }
    #
    # State-safe actions might be called from every state. Draw the
    # arcs if not done yet.
    #
    foreach action [:defined Action] {
      if {[info exists drawn($action)]} {continue}
      if {[$action state_safe]} {
        foreach s [:defined State] {
          append dotcode [:draw_transition $s $action ""]
        }
      }
    }
    
    append dotcode "\}\n"
    return $dotcode
  • draw_arc (scripted)

    if {$next_state eq ""} {set next_state $from_state}
    set key transition($from_state,$next_state,$action)
    if {[info exists :$key]} {
      return ""
    }
    set :$key 1
    return "  state_$from_state -> state_$next_state \[label=\"$label\"$style\];\n"
  • draw_transition (scripted)

    #:msg "[self args]"
    
    if {[$action state_safe]} {
      set arc_style {,style="dashed",penwidth=1,color=gray}
    } else {
      set arc_style ""
    }
    set cond_values [$action get_cond_values [$action next_state]]
    set result ""
    if {[llength $cond_values]>2} {
      # we have conditional values
      set c cond_[$from name]_[incr :condition_count]
      append arc_style {,style="setlinewidth(1)",penwidth=1,color=gray}
      append result "  state_$c \[shape=diamond, fixedsize=1, width=0.2, height=0.2, fixedsize=1,style=solid,color=gray,label=\"\"\];\n"
      append result [:draw_arc [$from name] $c [$action name]-1 $role[$action label] ""]
      foreach {cond value} $cond_values {
        if {$cond ne ""} {set prefix "$cond"} {set prefix "else"}
        append result [:draw_arc $c $value [$action name] \[$prefix\] $arc_style]
      }
    } else {
      set prefix ""
      append result [:draw_arc [$from name] [lindex $cond_values 1] [$action name] $role$prefix[$action label] $arc_style]
    }
    return $result
  • flush_form_object (scripted)

    unset -nocomplain :form_obj
  • force_named_form (scripted)

    #
    # By using this method in the "initialize" action, one can bypass
    # the state specific forms and force a form to the certain name
    #
    set form_id [:default_load_form_id $form_name]
    if {$form_id == 0} {
      ns_log warning "use_named_form: could not locate form $form_name"
    } else {
      if {![nsf::is object ::${form_id}]} {
        ::xo::db::CrClass get_instance_from_db -item_id ${form_id}
      }
      set :form_id $form_id
    }
  • form (forward)

  • form_loader (forward)

  • form_object (scripted)

    set parent_id [$object parent_id]
    # After this method is activated, the form object of the form of
    # the current state is created and the instance variable form_id
    # is set.
    #
    # Load the actual form only once for this context.  We cache the
    # object name of the form in the context.
    #
    if {[info exists :form_obj]} {
      return ${:form_obj}
    }
    
    set package_id [$object package_id]
    #
    # We have to load the form, maybe via a form loader.  If the
    # form_loader is set nonempty and the method exists, then use the
    # form loader instead of the plain lookup. In case the form_loader
    # fails, it is supposed to return 0.
    #
    set loader [:form_loader]
    #:msg form_loader=$loader
    
    # TODO why no procsearch instead of "info methods"?
    if {$loader eq "" || [:info methods $loader] eq ""} {
      set form_id [:default_load_form_id [${:current_state} form]]
      if {$form_id == 0} {
        :log "=== NO default_load_form_id state ${:current_state} form <[${:current_state} form]>"
        #
        # When no form was found by the form loader ($form_id == 0) we
        # create automatically a form.
        #
        set form_object [:create_auto_form $object]
        :log "=== autoform $form_object"
      }
    } else {
      #:msg "using custom form loader $loader for [:form]"
      set form_object [:$loader [:form]]
    }
    
    #
    # At this place, the variable "form_id" might contain an id
    # (integer) or an object, provided by the custom file loader.
    #
    #:msg form_id=$form_id
    
    if {![info exists form_object]
        && [string is integer -strict $form_id]
        && $form_id > 0
      } {
      # just load the object conditionally
      if {![nsf::is object ::$form_id]} {
        ::xo::db::CrClass get_instance_from_db -item_id $form_id
      }
      set form_object ::$form_id
      #:msg form_object=$form_object
    }
    
    if {[$form_object istype "::xowiki::Form"]} {
      #
      # The item returned from the form loader was a form,
      # everything is fine.
      #
      #:msg form_object=$form_object-isForm
    
    } elseif {[$form_object istype "::xowiki::FormPage"]} {
      #
      # We got a FormPage. This FormPage might be a pseudo form (a
      # FormPage containing the property "form"). If not, add a "form"
      # property from the rendered content.
      #
      #:msg form_object=$form_object-pseudoForm-with-form=[$form_object property form]
      if {[$form_object property form] eq ""} {
        #
        # The FormPage contains no form, so try to provide one.  We
        # obtain the content by rendering the page_content. In some
        # cases it might be more efficient to obtain the content
        # from property "_text", but this might lead to unexpected
        # cases where the formpage uses _text for partial
        # information.
        #
        set text [$form_object render_content]
        $form_object set_property -new 1 form "<form>$text</form>"
        #:msg "_text=[$form_object property _text]"
      }
    } elseif {[$form_object info class] eq "::xowiki::Page"} {
    
      #
      # The $form_object is in reality an xowiki Page, make it look
      # like a form (with form buttons).
      #
      set form_object [::xowiki::Form new  -package_id $package_id  -parent_id [::$package_id folder_id]  -name "Auto-Form"  -anon_instances [:autoname]  -form "<form>[$form_object get_html_from_content [::$form_id text]]</form>"  -text ""  -form_constraints ""  -destroy_on_cleanup ]
    }
    
    set :form_obj $form_object
    return $form_object
  • get_actions (scripted)

    set actions [list]
    foreach action [${:current_state} get_actions] {
      lappend actions ${:wf_container}::$action
    }
    #:msg "for ${:current_state} actions '$actions"
    return $actions
  • get_current_state (scripted)

    namespace tail ${:current_state}
  • get_form_constraints (forward)

  • get_property (forward)

  • get_view_method (forward)

  • in_role (setter)

  • init (scripted)

    array set :handled_roles {}
    #
    # Register the context for the associated object. This has to be
    # before the creation of the workflow definition, since this might
    # refer to the context.
    #
    ${:object} wf_context [self]
    #
    # Create or share workflow definition
    #
    :require_workflow_definition ${:workflow_definition}
  • initialize_context (scripted)

    #:log "START-initialize_context <$obj>"
    #
    # Keep the object in an instance variable.
    #
    set :object $obj
    
    # set the state to a well defined starting point
    set state [$obj state]
    if {$state eq ""} {
      set state "initial"
      #:log "===== resetting state of $obj to $state"
    }
    :set_current_state $state
    
    if {![nsf::is object ${:current_state}]} {
      if {$state eq "initial"} {
        ns_log warning "no state object ${:current_state}"
      } else {
        #
        # The state was probably deleted from the workflow definition,
        # but the workflow instance does still need it. We complain an
        # reset the state to "initial", which should be always present.
        #
        :log "===== Workflow instance [$obj name] is in an undefined state '$state', reset to initial"
        $obj msg "Workflow instance [$obj name] is in an undefined state '$state', reset to initial"
        :set_current_state initial
      }
    }
    
    #
    # In most cases, the package_id is initialized here already
    #
    set package_id [$obj package_id]
    #:log "... OBJECT $obj HAS $package_id /[info commands ::$package_id/]"
    if {[info commands ::$package_id] eq ""}  {
      :log "... OBJECT $obj HAS $package_id, which is not initialized yet"
      xo::Package require $package_id
    }
    
    #
    # Set the embedded_context to the workflow context,
    # used e.g. by "behavior" of form-fields.
    #
    [::$package_id context] set embedded_context [self]
    
    set stateObj ${:current_state}
    catch {$stateObj eval [$stateObj eval_when_active]}
    
    if {[$obj istype ::xowiki::FormPage] && [$obj is_wf_instance]} {
      #
      # The workflow context may have the following variables:
      #   - "debug"
      #   - "policy"
      #   - "autoname"
      #   - "auto_form_constraints"
      #   - "auto_form_template"
      #   - "shared_definition"
      #
      if {[${:wf_container} exists debug] && [${:wf_container} set debug] > 0} {
        :show_debug_info $obj
      }
    
      if {[${:wf_container} exists policy]} {
        set policy [${:wf_container} set policy]
        if {![nsf::is object $policy]} {
          :msg "ignore non-existent policy '$policy'"
        } else {
          [$obj package_id] set policy $policy
        }
      }
    }
    #:log "END-initialize_context <$obj>\n\t  context vars: [lsort [:info vars]]\n\tcontainer vars: [lsort [${:wf_container} info vars]]"
  • object (setter)

  • object-specific (scripted)

    #:log "=== legacy call <$code>"
    :uplevel [list ${:object} eval $code]
  • property (forward)

  • require_workflow_definition (scripted)

    #
    # We define the classes Action, State and Property either
    # - per workflow instance (sub-object of the FormPage) or
    # - shared based on the revision_id of the workflow definition.
    #
    # Per-instance definitions have the advantage of allowing
    # e.g. per-object mixins for workflow context definitions, but
    # this can be costly for complex workflow definitions, e.g. when
    # multiple workflow instances are created for a single workflow
    # definition in a request.
    #
    #:log START-CREATES-sharedWorkflowDefinition=$::xowf::sharedWorkflowDefinition
    if {$::xowf::sharedWorkflowDefinition} {
      if {[${:object} is_wf]} {
        set source_obj ${:object}
      } else {
        set source_obj [${:object} page_template]
      }
    
      set revision_id [$source_obj revision_id]
      if {$revision_id == 0} {
        #
        # We have no "revision_id", but we have to have an
        # "item_id". Therefore, get then "item_id" from the
        # "revision_id" via SQL function
        # content_item.get_live_revision.
        #
        set revision_id [::acs::dc call content_item get_live_revision  -item_id [$source_obj item_id]]
        ns_log warning "xowf: tried to create a wf_container with revision_id 0 -> fixed to $revision_id"
      }
    
      set :wf_container ::xowf::$revision_id
    
      #
      # Validate workflow container: We cannot trust the shared
      # definition in case some xowf-include files were changed. When
      # we detect such situations, we delete the shared worklow
      # container, which will be recreated later.
      #
      if {[nsf::is object ${:wf_container}]} {
        set ok 1
        #set ok [expr {$workflow_definition eq [${:wf_container} set __workflow_definition]}]
        if {[${:wf_container} exists __xowf_depends]} {
          set depends [${:wf_container} set __xowf_depends]
          foreach {fn mtime} $depends {
            if {[ad_file mtime $fn] ne $mtime} {
              set ok 0
              break
            }
          }
        }
        if {!$ok} {
          ${:wf_container} destroy
          ns_log notice "xowf: invalidate container ${:wf_container}"
        }
      }
    
      if {![nsf::is object ${:wf_container}]} {
        #
        # We require an xotcl::Object, since the container needs the
        # method "contains"
        #
        #:log "=== create WorkflowContainer ${:wf_container}"
        WorkflowContainer create ${:wf_container}
        #:log "=== call create :create_workflow_definition"
        :create_workflow_definition $workflow_definition
        #:log "==== def\n$workflow_definition"
        #:log "==== wf_container children <[${:wf_container} info children]>"
      }
      #
      # This is for transitional code. For certain workflows in the
      # transition, we can define in the workflow whether or not is
      # shall be really shared by setting in the definition the
      # variable "shared_definition".
      #
      set use_shared_definition [${:wf_container} shared_definition]
    } else {
      set use_shared_definition 0
    }
    
    if {$use_shared_definition == 0} {
      set :wf_container [self]
      :create_workflow_definition $workflow_definition
      #:log [:serialize]
    } else {
      #
      # Evaluate once per request the object-specific code of the
      # workflow.
      #
      if {[${:object} is_wf_instance]} {
        set os_code [${:wf_container} object-specific]
        if {$os_code ne ""} {
          #:log "=== object-specific ${:object} eval <$os_code>"
          ${:object} eval $os_code
        }
      }
    }
    
    #:log [:serialize]
    #:log END-CREATES
  • resolve_form_name (scripted)

    set package_id [$object package_id]
    set parent_id [$object parent_id]
    set item_info [::$package_id item_ref -normalize_name false  -use_package_path 1  -use_site_wide_pages true  -default_lang [$object lang]  -parent_id $parent_id  $name]
    #ns_log notice "*** resolve_form_name <$name> in $parent_id [$parent_id name] => $item_info"
    set item_id [dict get $item_info item_id]
    set form_name [dict get $item_info prefix]:[dict get $item_info stripped_name]
    return [list form_id $item_id name $form_name]
  • set_current_state (scripted)

    set :current_state ${:wf_container}::$value
  • set_new_property (forward)

  • set_property (forward)

  • show_debug_info (scripted)

    set form        [${:current_state} form]
    set view_method [${:current_state} view_method]
    set form_loader [${:current_state} form_loader]
    if {$form eq ""} {set form NONE}
    if {$view_method eq ""} {set view_method NONE}
    if {$form_loader eq ""} {set form_loader NONE}
    
    $obj debug_msg "State: [${:current_state} name], Form: $form, View method: $view_method, Form loader: $form_loader, Context class: [:info class]"
    
    #set conds [list]
    #foreach c [:defined Condition] {
    #  lappend conds "[$c name] [$c]"
    #}
    #$obj debug_msg "Conditions: [join $conds {, }]"
    $obj debug_msg "Instance attributes: [list [$obj instance_attributes]]"
    foreach kind {State Action Condition} {
      $obj debug_msg "...${kind}s: [lsort [:defined $kind]]"
    }
  • wf-specific (scripted)

    if {[llength $args] > 0} {
      ns_log warning "wf-specific NOT SUPPORTED for non shared workflow "  "${:object} [${:object} name]: $args"
    }
  • wf_container (setter)

  • wf_definition_object (scripted)

    return ${:wf_container}::$name
  • workflow_definition (setter)