-
Notifications
You must be signed in to change notification settings - Fork 18
Task Tree Render
An easier way to inspect task trees with fewer keystrokes.
Motivations:
- easier column-aligned printout for comparing attributes across tasks,
- attribute dereference chaining and flexible transforms,
- customizable column headings,
- and more visually appealing.
The code is located in app/models/concerns/task_tree_render.rb
,
which is included in app/models/concerns/prints_task_tree.rb
.
Use tree
like structure_render
on a Task
:
> task = Task.find(9)
> puts task.tree
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
Appeal 3 (evidence_submission) ──── │ ID │ STATUS │ ASSIGNED_TO_TYPE │ ASGN_BY │ ASGN_TO │ CREATED_AT │ UPDATED_AT │
InformalHearingPresentationTask │ 9 │ on_hold │ Organization │ │ Vso │ 2019-12-17 23:38:15 UTC │ 2019-12-17 23:38:15 UTC │
└── InformalHearingPresentationTask │ 11 │ assigned │ User │ MICHAEL_VSO │ MICHAEL_VSO │ 2019-12-17 23:38:15 UTC │ 2019-12-17 23:38:15 UTC │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
The appeal associated with the task is printed on the header line: appeal_id
followed with either docket_type
or docket_name
whichever exists first.
Customize the appeal label by changing treeconfig[:appeal_label_template]
.
Run tree
on an Appeal
:
> puts task.appeal.tree
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
Appeal 3 (evidence_submission) ──────────── │ ID │ STATUS │ ASSIGNED_TO_TYPE │ ASGN_BY │ ASGN_TO │ CREATED_AT │ UPDATED_AT │
└── RootTask │ 8 │ on_hold │ Organization │ │ Bva │ 2019-12-17 23:38:15 UTC │ 2019-12-17 23:38:15 UTC │
├── InformalHearingPresentationTask │ 9 │ on_hold │ Organization │ │ Vso │ 2019-12-17 23:38:15 UTC │ 2019-12-17 23:38:15 UTC │
│ └── InformalHearingPresentationTask │ 11 │ assigned │ User │ MICHAEL_VSO │ MICHAEL_VSO │ 2019-12-17 23:38:15 UTC │ 2019-12-17 23:38:15 UTC │
└── TrackVeteranTask │ 10 │ in_progress │ Organization │ CSS_ID19 │ Vso │ 2019-12-17 23:38:15 UTC │ 2019-12-17 23:38:15 UTC │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
Note that the values under the ASGN_BY
and ASGN_TO
columns are derived using predefined lambdas -- see the "Attribute Transforms" section below for more description.
Choose columns as usual:
> appeal = Appeal.find(3);
> puts appeal.tree(:id, :status)
┌──────────────────┐
Appeal 3 (evidence_submission) ──────────── │ ID │ STATUS │
└── RootTask │ 8 │ on_hold │
├── InformalHearingPresentationTask │ 9 │ on_hold │
│ └── InformalHearingPresentationTask │ 11 │ assigned │
└── TrackVeteranTask │ 10 │ in_progress │
└──────────────────┘
The default output is ANSI, which is more visually appealing. For compatibility with text editors and other tools, change output to use ASCII characters:
> TaskTreeRender.ascii;
> puts appeal.tree(:id, :status, :assigned_to_id)
+-----------------------------------+
Appeal 3 (evidence_submission) ------------ | ID | STATUS | ASSIGNED_TO_ID |
└── RootTask | 8 | on_hold | 4 |
├── InformalHearingPresentationTask | 9 | on_hold | 7 |
│ └── InformalHearingPresentationTask | 11 | assigned | 32 |
└── TrackVeteranTask | 10 | in_progress | 7 |
+-----------------------------------+
For more compact output without vertical lines and borders:
> TaskTreeRender.compact;
> puts appeal.tree
Appeal 3 (evidence_submission) ID STATUS ASSIGNED_TO_TYPE ASGN_BY ASGN_TO CREATED_AT UPDATED_AT
└── RootTask 8 on_hold Organization Bva 2019-12-17 23:38:15 UTC 2019-12-17 23:38:15 UTC
├── InformalHearingPresentationTask 9 on_hold Organization Vso 2019-12-17 23:38:15 UTC 2019-12-17 23:38:15 UTC
│ └── InformalHearingPresentationTask 11 assigned User MICHAEL_VSO MICHAEL_VSO 2019-12-17 23:38:15 UTC 2019-12-17 23:38:15 UTC
└── TrackVeteranTask 10 in_progress Organization CSS_ID19 Vso 2019-12-17 23:38:15 UTC 2019-12-17 23:38:15 UTC
Restore ANSI outlines by calling TaskTreeRender.ansi
.
Task attributes can be dereferenced by using an array representing a chain of methods to call.
For example, to show the type
of the assigned_to
object, use [:assigned_to, :type]
.
By default the column heading will be [:ASSIGNED_TO, :TYPE]
.
To manually set the column headings, use the col_labels
named parameter as follows:
> atts = [:id, :status, :assigned_to_type, :parent_id, [:assigned_to, :type], :created_at]
> col_labels = ["\#", "Status", "AssignToType", "P_ID", "ASGN_TO", "Created"]
> puts appeal.tree(*atts, col_labels: col_labels)
Appeal 3 (evidence_submission) # Status AssignToType P_ID ASGN_TO Created
└── RootTask 8 on_hold Organization Bva 2019-12-17 23:38:15 UTC
├── InformalHearingPresentationTask 9 on_hold Organization 8 Vso 2019-12-17 23:38:15 UTC
│ └── InformalHearingPresentationTask 11 assigned User 9 2019-12-17 23:38:15 UTC
└── TrackVeteranTask 10 in_progress Organization 8 Vso 2019-12-17 23:38:15 UTC
A more flexible alternative to attribute dereferencing is to use a Ruby lambda to derive values for a column.
In the example below, a new column with column heading ASGN_TO.TYPE
will be included
when the column is specified when calling tree
.
For each task in the tree, the specified lambda is called to populate the values under the column.
> TaskTreeRender.treeconfig[:value_funcs_hash]["ASGN_TO.TYPE"] = ->(task) {
TaskTreeRender.send_chain(task, [:assigned_to, :type])&.to_s || "" }
> puts appeal.tree(:id, :status, :assigned_to_type, "ASGN_TO.TYPE", :ASGN_BY, :ASGN_TO)
┌────────────────────────────────────────────────────────────────────────────────┐
Appeal 3 (evidence_submission) ──────────── │ ID │ STATUS │ ASSIGNED_TO_TYPE │ ASGN_TO.TYPE │ ASGN_BY │ ASGN_TO │
└── RootTask │ 8 │ on_hold │ Organization │ Bva │ │ Bva │
├── InformalHearingPresentationTask │ 9 │ on_hold │ Organization │ Vso │ │ Vso │
│ └── InformalHearingPresentationTask │ 11 │ assigned │ User │ │ MICHAEL_VSO │ MICHAEL_VSO │
└── TrackVeteranTask │ 10 │ in_progress │ Organization │ Vso │ CSS_ID19 │ Vso │
└────────────────────────────────────────────────────────────────────────────────┘
To see the set of attribute transforms, run TaskTreeRender.treeconfig[:value_funcs_hash]
.
To highlight a specific task with an asterisk *
, add the " "
attribute as follows:
> puts Task.find(8).tree(" ", :id, :status)
┌──────────────────────┐
Appeal 3 (evidence_submission) ──────── │ │ ID │ STATUS │
RootTask │ * │ 8 │ on_hold │
├── InformalHearingPresentationTask │ │ 9 │ on_hold │
│ └── InformalHearingPresentationTask │ │ 11 │ assigned │
└── TrackVeteranTask │ │ 10 │ in_progress │
└──────────────────────┘
By default, the calling task is highlighted -- in this case, the task with id 8.
To highlight a different task, set the named parameter highlight
with the task id like so:
> puts Appeal.find(3).tree(" ", :id, :status, highlight: 11)
┌──────────────────────┐
Appeal 3 (evidence_submission) ──────────── │ │ ID │ STATUS │
└── RootTask │ │ 8 │ on_hold │
├── InformalHearingPresentationTask │ │ 9 │ on_hold │
│ └── InformalHearingPresentationTask │ * │ 11 │ assigned │
└── TrackVeteranTask │ │ 10 │ in_progress │
└──────────────────────┘
> TaskTreeRender.treeconfig[:heading_fill_char] = "."
> puts appeal.tree(:id, :status)
┌──────────────────┐
Appeal 3 (evidence_submission) ............ │ ID │ STATUS │
└── RootTask │ 8 │ on_hold │
├── InformalHearingPresentationTask │ 9 │ on_hold │
│ └── InformalHearingPresentationTask │ 11 │ assigned │
└── TrackVeteranTask │ 10 │ in_progress │
└──────────────────┘
The appeal_label_template
is evaluated with self
being an Appeal
or LegacyAppeal
object.
Remember to use single quotes so that string interpolation is not immediately performed.
> TaskTreeRender.treeconfig[:appeal_label_template] = '#{self.class.name} #{id}, #{created_at}'
> puts Appeal.find(3).tree(:id, :status)
┌──────────────────┐
Appeal 3, 2019-12-17 23:38:15 UTC────────── │ ID │ STATUS │
└── RootTask │ 8 │ on_hold │
├── InformalHearingPresentationTask │ 9 │ on_hold │
│ └── InformalHearingPresentationTask │ 11 │ assigned │
└── TrackVeteranTask │ 10 │ in_progress │
└──────────────────┘
> puts LegacyAppeal.find(10).tree(:id, :status)
┌────────────────┐
LegacyAppeal 10, ────────────── │ ID │ STATUS │
└── RootTask │ 816 │ on_hold │
└── HearingTask │ 817 │ on_hold │
└── ScheduleHearingTask │ 818 │ assigned │
└────────────────┘
If named parameter col_labels
is not specified when calling tree
, a column heading transform is used to create column labels. To see predefined heading transforms, run TaskTreeRender.treeconfig[:heading_transform_funcs_hash]
.
For example, run the following to use the clipped_upcase_headings
transform:
> TaskTreeRender.treeconfig[:heading_transform] = :clipped_upcase_headings;
> puts Appeal.find(3).tree
┌─────────────────────────────────────────────────────────────────────────┐
Appeal 3 (evidence_submission) . . . . . . │ ID │ STATUS │ UPDATED_AT │ ASSIGNED_TO_ │ ASGN_TO │
└── RootTask │ 8 │ on_hold │ 2019-12-17 23:38:15 UTC │ Organization │ Bva │
├── InformalHearingPresentationTask │ 9 │ on_hold │ 2019-12-17 23:38:15 UTC │ Organization │ Vso │
│ └── InformalHearingPresentationTask │ 11 │ assigned │ 2019-12-17 23:38:15 UTC │ User │ MICHAEL_VSO │
└── TrackVeteranTask │ 10 │ in_progress │ 2019-12-17 23:38:15 UTC │ Organization │ Vso │
└─────────────────────────────────────────────────────────────────────────┘
Note the ASSIGNED_TO_
column heading has been clipped to the max length of the strings under that column.
Add your own heading transform like this:
> TaskTreeRender.treeconfig[:heading_transform_funcs_hash][:downcase_headings] = ->(key, _col_obj) { key.downcase };
> TaskTreeRender.treeconfig[:heading_transform] = :downcase_headings
> puts Appeal.find(3).tree
┌─────────────────────────────────────────────────────────────────────────────┐
Appeal 3 (evidence_submission) . . . . . . │ id │ status │ updated_at │ assigned_to_type │ asgn_to │
└── RootTask │ 8 │ on_hold │ 2019-12-17 23:38:15 UTC │ Organization │ Bva │
├── InformalHearingPresentationTask │ 9 │ on_hold │ 2019-12-17 23:38:15 UTC │ Organization │ Vso │
│ └── InformalHearingPresentationTask │ 11 │ assigned │ 2019-12-17 23:38:15 UTC │ User │ MICHAEL_VSO │
└── TrackVeteranTask │ 10 │ in_progress │ 2019-12-17 23:38:15 UTC │ Organization │ Vso │
└─────────────────────────────────────────────────────────────────────────────┘
To column separators, internal margins, and customize borders other treeconfig
keys can be modified.
- Change the column separator by setting
:col_sep
. - Adjust internal margins by setting
:cell_margin_char
to""
(empty string) or" "
(a longer string). - Exclude top and bottom borders by setting
:include_border
to false. - The
:top_chars
and:bottom_chars
settings are used for the top and bottom borders respectively.- The first and fourth characters are used for the corners.
- The second character is used to draw horizontal lines.
- The third character is used at the column separator locations.
- (See
TaskTreeRender.write_divider
for details).
TaskTreeRender.treeconfig[:col_sep] = "/"
TaskTreeRender.treeconfig[:cell_margin_char] = ""
TaskTreeRender.treeconfig[:include_border] = true
TaskTreeRender.treeconfig[:top_chars] = "+-++"
TaskTreeRender.treeconfig[:bottom_chars] = "+-++"
See the TaskTreeRender.ansi
and TaskTreeRender.ascii
function for reference.
You can customize settings contained in the TaskTreeRender.treeconfig
hash in your .pryrc
so that it is loaded each time the Rails console is run.
For example, if your .pryrc
file in the Caseflow top-level directory contains the following:
TaskTreeRender.treeconfig[:default_atts] = [:id, :status, :updated_at, :assigned_to_type, :ASGN_TO]
TaskTreeRender.treeconfig[:heading_fill_char] = ". "
puts "TaskTreeRender customized"
Then in a Rails console, running the following will use your customizations.
> puts Appeal.find(3).tree
┌─────────────────────────────────────────────────────────────────────────────┐
Appeal 3 (evidence_submission) . . . . . . │ ID │ STATUS │ UPDATED_AT │ ASSIGNED_TO_TYPE │ ASGN_TO │
└── RootTask │ 8 │ on_hold │ 2019-12-17 23:38:15 UTC │ Organization │ Bva │
├── InformalHearingPresentationTask │ 9 │ on_hold │ 2019-12-17 23:38:15 UTC │ Organization │ Vso │
│ └── InformalHearingPresentationTask │ 11 │ assigned │ 2019-12-17 23:38:15 UTC │ User │ MICHAEL_VSO │
└── TrackVeteranTask │ 10 │ in_progress │ 2019-12-17 23:38:15 UTC │ Organization │ Vso │
└─────────────────────────────────────────────────────────────────────────────┘
For byebug
, run load ".pryrc"
to load the customizations into the current byebug
environment.
- Home
- Acronyms and Glossary
- Caseflow products
- Caseflow Intake
- Caseflow Queue
- Appeals Consumer
- Caseflow Reader
- Caseflow eFolder
- Caseflow Hearings
- Caseflow Certification
- Caseflow APIs
- Appeal Status API
- Caseflow Dispatch
-
CSUM Roles
- System Admin
- VHA Team Management
- Active Record Queries Resource
- External Integrations
- Caseflow Demo
- Caseflow ProdTest
- Background
- Stuck Jobs
- VA Notify
- Caseflow-Team
- Frontend Best Practices
- Accessibility
- How-To
- Debugging Tips
- Adding a Feature Flag with FeatureToggle
- Editing AMA issues
- Editing a decision review
- Fixing task trees
- Investigating and diagnosing issues
- Data and Metric Request Workflow
- Exporting and Importing Appeals
- Explain page for Appeals
- Record associations and Foreign Keys
- Upgrading Ruby
- Stuck Appeals
- Testing Action Mailer Messages Locally
- Re-running Seed Files
- Rake Generator for Legacy Appeals
- Manually running Scheduled Jobs
- System Admin UI
- Caseflow Makefile
- Upgrading Postgresql from v11.7 to v14.8 Locally
- VACOLS VM Trigger Fix M1
- Using SlackService to Send a Job Alert
- Technical Talks