Skip to content

Task Tree Render

Yoom Lam edited this page Dec 29, 2019 · 23 revisions

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.

Usage is like structure_render

Use tree like structure_render on a Task:

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

> 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 │
                                            └──────────────────┘

ASCII and Compact output

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.

Attribute Dereferencing and Custom Column Headers

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

Attribute Transforms (using Lambdas)

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

Highlight Specific Task

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 │
                                        └──────────────────────┘

Be 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 │
                                            └──────────────────────┘

treeconfig

:heading_fill_char

> 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 │
                                            └──────────────────┘

:appeal_label_template

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 │
                                └────────────────┘

Column Headings Transforms

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         │
                                            └─────────────────────────────────────────────────────────────────────────────┘

Customize Drawing Characters

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] = false
TaskTreeRender.treeconfig[:top_chars] = "+--+"
TaskTreeRender.treeconfig[:bottom_chars] = "+--+"

See the TaskTreeRender.ansi and TaskTreeRender.ascii function for reference.

Customize treeconfig in .pryrc

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.

Clone this wiki locally