Workflow Best Practices

Overview

There are as many techniques and styles for putting together workflow as there are people building those 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 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

People don't always think alike, so not everyone would use the same method to get the desired result. Adding comments to your code allows others who may have used a different technique to follow your code and your thought process. This makes troubleshooting and maintenance easier.

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's 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 this:

#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 this:

#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.

For example, here is a 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

Here is a 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
%>

Finally, here is the revised connector criteria:

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

Naming Conventions

It is recommended that handlers and routines be named using an "_" format, for example, "submission_create" (as opposed to"create_submission"). This format groups all handlers of a specific type (in this case, submission handlers) when sorting the list by name, making it easier to find what you're looking for. This 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. Kinetic's solution workflows all use a prefix of "kinetic_solution:" so that you can identify anything tied specifically to that solution.