Best Practices for Workflow

There are as many techniques and styles to putting together workflow as there are people building workflows. However, there are specific techniques that make code easier to read and maintain. These are best practices that Kinetic employs in our workflows and we want to share these with all workflow developers.

Ruby Best Practices

One of the powerful components of the workflow engine is the ability to create dynamic parameters and connectors. However, with power often comes complexity. To help reduce the impact of complexity, we recommend you follow the following best practices:

Format Code

Take the time to properly format and indent your code. This makes the code easier to read and to understand, particularly for a third party coming in after the fact to do troubleshooting or maintenance.

For example, instead of this:

codeSubmissions = JSON.parse(@results['Get Allocation Code Mapping']['Submissions List JSON'])
type = entryType
codeSubmissions.each do |sub|
if sub['values']['Workday Time Entry Code']==timeEntry['Time_Type']
type = sub['values']['Allocation Code Description'].downcase
end
end

Do this:

codeSubmissions = JSON.parse(@results['Get Allocation Code Mapping']['Submissions List JSON'])
type = entryType
codeSubmissions.each do |sub|
  if sub['values']['Workday Time Entry Code']==timeEntry['Time_Type']
    type = sub['values']['Allocation Code Description'].downcase
  end
end

Comment Code

Not everyone thinks alike or would use the same method to get to the desired result. Commenting your code allows others (who may have used a different technique) to follow your code and your thought process. This makes it easier for troubleshooting and maintenance.

For example, instead of:

codeSubmissions = JSON.parse(@results['Get Allocation Code Mapping']['Submissions List JSON'])
type = entryType
codeSubmissions.each do |sub|
  if sub['values']['Workday Time Entry Code']==timeEntry['Time_Type']
    type = sub['values']['Allocation Code Description'].downcase
  end
end

Do this:

# parse the previously retrieved allocation mappings
codeSubmissions = JSON.parse(@results['Get Allocation Code Mapping']['Submissions List JSON'])
# set type to default
type = entryType

# loop through allocation mappings
codeSubmissions.each do |sub|
  # if the allocation time entry code is the same as the time type in the 
  # current entry use that allocation description as the type
  if sub['values']['Workday Time Entry Code']==timeEntry['Time_Type']
    type = sub['values']['Allocation Code Description'].downcase
  end
end

Parse Data Once

If you need to parse data, for example XML or JSON, it is best to do this parse once in the beginning. Parse once into a variable and then use that variable throughout the rest of the code. This has a minimal functional difference in most cases, but can make the code easier to follow and read.

For example, instead of

#setting form values
values = {
  "Submission Date Time" => Time.now.iso8601,
  "AT Request ID" => JSON.parse( @results['Workday Time Entry Loop Head']['Value'] )['request_id'],
  "Hours" => JSON.parse( @results['Workday Time Entry Loop Head']['Value'] )['time_off_hours'],
  "Time Off Type" => JSON.parse( @results['Workday Time Entry Loop Head']['Value'] )['time_off_type'],
  "Time Off Data" => @results['Workday Time Entry Loop Head'],
  "Validation Output" =>  @results['Add Time Off']['error message'],
  "Successfully Processed Date Time" => completedDate
}

do something like:

#parsing loop input
info = JSON.parse( @results['Workday Time Entry Loop Head']['Value'] )

#setting form values
values = {
  "Submission Date Time" => Time.now.iso8601,
  "AT Request ID" => info['request_id'],
  "Hours" => info['time_off_hours'],
  "Time Off Type" => info['time_off_type'],
  "Time Off Data" => info.to_json,
  "Validation Output" =>  @results['Add Time Off']['error message'],
  "Successfully Processed Date Time" => completedDate
}

Avoid Complex Connectors

If your connector expressions become complex (more than a couple of connected statements), it is considered best practice to do that logic in an echo node before the appropriate connection. This allows the logic to be properly commented, spaced, and indented. This method also allows the results of that logic to be visible in the run without having to look into the logs, thus making it easier to troubleshoot.

Example:

Complex connector:

@results['Validate Time Entry Block']['Handler Error Message'].to_s.empty? && JSON.parse( @results['Validate Time Entry Block']['Results JSON'] )['get_import_processes_response']['response_data']['import_process']['import_process_data']['percent_complete'] == "1" && JSON.parse( @results['Validate Time Entry Block']['Results JSON'] )['get_import_processes_response']['response_data']['import_process']['import_process_data']['has_messages'].to_i > 0

New echo node named "Time Entry Complete with Messages":

<%=
# set variables to visibly simply condition statement
handler_error = !@results['Validate Time Entry Block']['Handler Error Message'].to_s.empty?
import_process_data = {}
if !handler_error
    import_process_data = JSON.parse( @results['Validate Time Entry Block']['Results JSON'] )['get_import_processes_response']['response_data']['import_process']['import_process_data']
end

# condition
!handler_error && import_process_data['percent_complete'] == "1" && import_process_data['has_messages'].to_i > 0
%>

Revised connector criteria:

@results['Time Entry Complete with Message']['Output'] == "true"

Naming Conventions

It is recommended that handlers / routines be named with "object" then "action". For example, submission_create vs create_submission. This causes all of the Submission handlers, for example, to be together when the list is sorted by name and makes it easier to find the tree / handler / routine you may be looking for. Such a naming convention also allows for simpler access for scripting/programmatic updates.

If building your own full solution, name the routines built specifically for use with that solution accordingly. You will see Kinetic's solution workflow with a prefix of kinetic_solution:<solutionName> so that you can identify anything tied specifically to that solution.