2019 12 04 Wisdom Wednesday Tasks and their Trees - department-of-veterans-affairs/caseflow GitHub Wiki

Part 0: Perspective, Limitations, & Biases

This talk is from my perspective! I am biased towards backend software, Rails, and team echo's work. Echo Team primarily works on Queue focused tasks and has very little interaction with other types of tasks, such as Hearing Tasks or Intake tasks. There is certainly interesting information to be gleaned from a front end/react view of tasks, and the perspectives of the other teams, both on Queue tasks and on the other task types!

While I'm largely following this doc for the presentation, I have a tendency to speak extemporaneously on things I find interesting without planning to, so feel free to take notes and then add them here afterwards! I think better when speaking out loud, so thank you all for being my rubber ducks today.

Part 1: Brief overview of 'Task Tree'

What do we mean by Task Tree?

Three options (currently):

  • Most often: An Appeal's Collection of Tasks - parent/child relationships
  • Sometimes: Rails Object Inheritance
  • Sometimes: An Appeal's Collection of Tasks - Created order (more properly, this is a appeal task graph)

Or sometimes a combination of these!

Is it a problem that we mean multiple things here? No!
While we need to make sure we're all on the same page for any given conversation, these different ways of looking at tasks in Caseflow reveal different insights into what is happening either on an appeal or in the code. If anything, I want more ways to visualize the Tasks in Caseflow, both those on a particular appeal and across the system generally.

An Appeal's Collection of Tasks - parent/child tasks

Every appeal has a task tree, sort of. It is created over time as the appeal accumulates tasks. Those tasks are related to each other in a tree structure via the parent_id on a task. A task only has one parent, but can have multiple children.

The task tree of an appeal is not a thing in and of itself. Rather, it is the collection of tasks and how they behave in relation to each other. We cannot simply 'create' an appeal with a task tree beyond the most trivial examples, but rather have to step through how an appeal would create tasks over its journey in Caseflow. Even if we manage to create good exemplar task trees for our development, testing, or demo needs (which I am incredibly in favor of doing!), they would inherently only be a subset of the reality of the possible trees in production.

Example - a completed hypothetical ES docket appeal with Colocated and QR tasks

This tree structure follows some structural norms, but adherence is reliant on all the engineers implementing those norms on each task's behavior. There is no 'tree' behavior configuration, only the sum of the behavior of the tasks.

  • Organization task with a user task child
    • Mostly true within Queue tasks
      • Exception: Judge & Attorney tasks - no org
      • Exception: QR - sometimes doesn't bother with a user task
    • Child tasks block Parent task
      • Within Queue this is now always true, to the best of my knowledge
    • All hearing related tasks live under HearingTask
      • Except Colocated & Mail subtypes!

How tasks relate to each other as this tree has changed over time! Previously valid task trees are no longer valid for new appeals, but could still exist for appeals that were extant before the change.

When we make changes to how the tasks relate to each other, we have to chose what to do with the existing appeals tasks.

Rails Object Inheritance

The whole inheritance chart

Less visual, but more manageable, grep queries!

  • Inherits Task: git grep "< \<Task\>" app/models/tasks
  • Inherits MailTask: git grep "< \<MailTask\>" app/models/tasks
  • Inherits any task type: git grep "< .*Task\>" app/models/tasks

From examining the Task Inheritance Tree we can discover connections and disconnections between the behavior of various tasks.

Task and LegacyTask are completely unrelated in inheritance. Even in who they inherit from! Task is from ActiveRecord, LegacyTask from ActiveModel.

Following the inheritance tree allows you to find behaviors not implemented directly by task class you're looking at. This is especially true in the case of validation behavior.

A validation pattern we follow: the validation invoked at the class and the conditional on that validation is defined differently in various children classes!

Walkthrough:
self.hide_from_queue_table_view Visual

An Appeal's Collection of Tasks - Created order (more properly, this is a task graph)

Newest lens! For any given appeal, shows what tasks were created before and after the other Documented in the Data-Driven Documentation of Task Trees Deep Dive.
Gives insight into real production task creation order, and can help reveal behavior.
More in Part 4! BLUF: team echo is super psyched about this.

Part 2: A diversion into Task Actions & Who Can Do What

Let's be frustrated together!

This section will hopefully become rapidly outdated as we improve the task actions architecture, but for now it would be nice to get us all on the same page, so let's dive in!

The Functions

actions_allowable?(user) - "Should this user have actions?" (ish)

Defined initially in Task with a few overrides.

  • Task: is the task open and Not an org task with a user child task assigned to this user
  • RootTask: Overrides Task's definition; always returns true
  • HearingAdminActionTask: Extends via super, also checks the user is a Hearings manager or Hearings Admin
  • ChangeHearingDispositionTask: Extends via super, also checks the user is a Hearings Admin

actions_available?(user) - A wrapper around actions_allowable that intended to disallow doing actions to on_hold tasks. (ish)

Defined initially in Task with a few overrides.

  • Task: false if on hold (unless timed hold) AND actions_allowable?
  • RootTask: Overrides Task's definition; always returns true
  • ColocatedTask: Overrides Task's definition; returns true if task is open
  • NoShowHearingTask: Overrides Task's definition; just calls actions_allowable?

available_actions_unwrapper(user) - a helper function for the Task serializer

A good lesson in smaller, focused PRs. The PR adding this (#7484) touched 36 files and wasn't related to the task serializer, so the reasoning is lost.

We call this method instead of available_actions in a dozen tests. I have done this. It is probably the wrong function, but an understandable mistake!

available_actions(user) - the meat of the logic!

defined 26 distinct times in the Inheritance model . Sometimes refers to the super function, sometimes does not.

actions returned often consider some combo of:

  • is the task is assigned to the user
  • is the task is assigned to the user's Organization
  • is the task is assigned to another user in the user's Organization
  • is the user is an Admin of the Organization of the assigned to user
  • is the parent task assigned to this user
  • and more

Echo is aware this is a pain to understand, and would love to refactor it!

So how do I see what actions a user has on a task?

Unfortunately, there is no easy answer. You need to interrogate the code for which, if any, of the checks it runs first (actions_available? or actions_allowable?), confirm that is true for your user, and then check available_actions returns.

There is not an easy way for a non-technical person to answer this question, currently, beyond User Impersonation.

Part 3: Debugging on Bat Team with Task Tree

Bat Team! Support needs your help! The user says the appeal automatically got cancelled. What's going on with it?

How I interpret the task trees

I largely use the Task Tree through structure_render(:id, :status) to determine the what the general step of the process the appeal is at.

We're at initial translation, on a timed hold

irb> appeal.structure_render(:id, :status)
Appeal 201 [id, status]
└── RootTask 596, on_hold
    └── DistributionTask 597, on_hold
         └── TranslationTask 598, on_hold
              └── TimedHoldTask 630, assigned

We're at Judge Assign, but in Colocated

irb> appeal.structure_render(:id, :status)
Appeal 1 [id, status]
└── RootTask 100, on_hold
    ├── DistributionTask 101, completed
    └── JudgeAssignTask 150, on_hold
        └── ExtensionColocatedTask 594, on_hold
            └── ExtensionColocatedTask 595, assigned

We're at the Evidence Window stage

irb> appeal.structure_render(:id, :status)
Appeal 201 [id, status]
└── RootTask 596, on_hold
    └── EvidenceWindowTask 597, assigned

We got to Dispatch, but the case was returned to the Judge for a correction

irb> appeal.structure_render(:id, :status)
Appeal 11 [id, status]
└── RootTask 31, on_hold
    ├── DistributionTask 32, completed
    ├── JudgeAssignTask 100, completed
    ├── JudgeDecisionReviewTask 501, completed
    │    └── AttorneyTask 502, completed
    └── BvaDispatchTask 600, on_hold
        └── BvaDispatchTask 601, on_hold
            └── JudgeDispatchReturnTask 700, assigned

How would I solve that problem though?

Did the Caseflow User close the last issue on appeal? I would ask for the closed_at value structure_render(:id, :status, :closed_at) and find the tasks that match/are immediately after the updated_at value on the last updated issue. Those need reopened!

Legacy Appeal Task Trees - A whole other thing

Are we looking at a Legacy Appeal? We only truly keep ColocatedTasks for LegacyAppeals. LegacyJudgeTasks and LegacvyAttorneyTasks are ephemeral and only created in memory to reflect the state in VACOLs. They do not persist once the appeal moves on.
This is why LegacyTask and Task do not share an inheritence tree! They are fundamentally distinct objects.

Why No Console Here?

Why aren't I showing you this on the dev rails console? Well, because task trees are only actually created by the appeal moving through the system, we don't actually have realistic task trees in dev. There's some effort underway to teach the Appeal factory to create more realistic task trees, but these are currently limited to early stages (post-intake, at distribution) and are still lacking in correct statuses and realistic creation times.

Bat Team FAQ

We've begun keeping a Bat Team FAQ for established processes for managing known bugs. But what you see here is a streamlined way to fix a known issue, rather than the actual debugging. I highly suggest riding along with folks on Bat Team (as their time allows!) to see how different folks debug issues.

Part 4: Interesting Info & Questions raised by the Data Driven Documentation

The Data-Driven Documentation of Task Trees

This is the first time we have the tree structures built from reality as opposed to from theory of behavior in the code! We can actually see what our trees look like, what weird edge cases are happening, and refer to these docs on bat team to clarify "how odd is this task tree I'm looking at here?"

I'm also excited to deep dive on any given Task to investigate and validate what good task trees look like.

For example, let's chat about AttorneyTask!

Last Notes

You Can Help!

On team echo we've been collecting Tasks Frustrations; we're no strangers to being confused or frustrated with these objects. Please feel free to add your frustrations to that issue; more insight is welcome! We're formulating solutions small and large.

Thanks

  • Thank you Hunter for helping me get the outline for this talk out of my head and into words
  • Thank you Kevin for helping in get the final draft done, and then helping me step away from it
  • Thank you to Team Echo for dealing with me fretting about the talk all this week
  • Thank you Tomas for structure_render
⚠️ **GitHub.com Fallback** ⚠️