Intake Structure Renderer - department-of-veterans-affairs/caseflow GitHub Wiki

The intake structure renderer is a Rails console add-on that generates and formats a quick overview of any intake-related model, inspired by the structure_render method for appeal task trees.

Usage

There are two ways to call the renderer, depending on your preference for convenience or distaste of monkey-patching.

> # the one-liner method
> puts IntakeRenderer.render(hlr)
>
> # monkey-patching makes multiple calls more convenient
> IntakeRenderer.patch_intake_classes
> puts hlr.render_intake
> puts hlr.veteran.render_intake

By default, PII is omitted from the rendered output, but it can be included explicitly:

> puts IntakeRenderer.render(hlr, show_pii: true)
> puts hlr.render_intake(show_pii: true)

Some sample output:

EndProductEstablishment 2393 (030HLRR, mod: 030, RFD)
├── Claim 600190553
├── RequestIssue 4765 (compensation, rating)
│   ├── descr: Service connection for PTSD is granted with an evaluat…
│   ├── Contention 348223
│   └── history:
│       ├── 2020-05-15 14:32:09 -0400: created
│       └── 2020-05-15 14:32:24 -0400: rating issue associated
├── history:
│   ├── 2020-05-15 14:32:09 -0400: created
│   ├── 2020-05-15 14:32:20 -0400: established
│   └── 2020-08-12 17:28:37 -0400: last synced: RFD
└── breadcrumbs:
    ├── HigherLevelReview 2425 (rcvd 2020-05-15, 93ab2535-0c78-46a9-8c8e-ae5b659e7973)
    └── Veteran 2622 (PID: 600320726)

Although the intake data model is much more heterogenous and irregular than a task tree, there is nevertheless a loose hierarchy of objects "owning" other objects, which this renderer makes use of. The breadcrumbs section at the end of the output provides broader context for the rendered object, and always leads back to a veteran.

Implemention

The code lives almost entirely in the IntakeRenderer class, which is a helper library; the Rails console loads it when starting up, but it's never called by the core app code.

Since simple recursion doesn't work for heterogenous types, each supported intake class is responsible for implementing a few methods (details, children, context) that determine how its instances will be rendered. This pattern is essentially polymorphism, where various types adhere to a common interface, and supply type-specific behavior on how to be a node in a tree.

However, rather than defining renderer-specific methods in the actual Caseflow Intake classes and polluting their method namespaces, all methods live in IntakeRenderer instead. Polymorphic method dispatch therefore requires a bit of cleverness: method names are prepended with the snake-cased class name. For example, RequestIssue's methods are simply named request_issue_details, request_issue_children, and request_issue_context.

Details

The *_details method returns a list of short tag-like descriptions for the object. The renderer uses these, along with the object's class and database ID, to format a one-line label for the object:

ClassName 123 (detail 1, detail 2, detail 3)

This label is returned by IntakeRenderer#label and is useful in many contexts, e.g. when non-hierarchically related to another intake object, or as a breadcrumb.

If the details method is not implemented, the resulting label will have its parenthetical portion omitted.

Children

The *_children method returns a list of child nodes for the object. Child nodes are represented either as strings (for a leaf child with a label) or a

{"label": [recursive, child, nodes]}

hash, according to the input type expected by the underlying TTY::Tree library. This recursion is commonly done by calling IntakeRenderer#structure on other Caseflow Intake objects.

If the method is not implemented, the object will be rendered as a leaf node.

Context

The *_context method returns a Caseflow Intake object that could be considered the parent or "owner" of the argument object. The breadcrumbs list is calculated by following the chain of parents, usually back to a Veteran.

Other than for Veteran, this method can be left un-implemented for types that are useful as a child node but don't have a single parent node, e.g. an intake User is responsible for many intakes. The renderer's tree hierarchy is an approximation, after all.

Further work

Outside of improving and fleshing out the existing renderer implementations, there are types that are not yet supported, including

  • DecisionDocument
  • BoardGrantEffectuation

as well as entire regions of the codebase that could benefit from structural clarity (RAMP reviews, perhaps?)

To add support for a new type, just add its class name to the RENDERABLE_CLASSNAMES list at the top, and implement whichever of the three aforementioned polymorphic methods are applicable.