Timed Tasks - TISTATechnologies/caseflow GitHub Wiki
What are timed tasks?
Tasks (models which count Task
amongst their superclasses) indicate that an action needs to be performed by somebody or some automated process. A member of the quality review team may need to review a decision (QualityReviewTask
) or the automatic case distribution process may need to distribute a case to a judge (DistributionTask
).
In addition to these proactive task actions, sometimes an appeal just needs to wait for some time to pass, so we have extended the task model to account for these waiting periods.
Why do we need timed tasks?
The VLJ support staff may send correspondence to a Veteran's attorney requesting a response within 30 days. In order to record this waiting period and remove the case from that employees active task queue we create a task to indicate that the appeal is "on hold". Caseflow users place tasks on hold, specifying some number of days for the hold, and after that many days have elapsed the task that was placed on hold is returned to their active queue.
How do timed tasks work?
In order to acheive this behaviour the application uses three similarly-named entities: TimedHoldTask
, TimeableTask
, and TaskTimer
. TimedHoldTask
is the task in the task tree representing this waiting period. TimeableTask
is the mix-in that encapsulates the behaviour that allows for actions to be taken automatically on the TimedHoldTask
. And the TaskTimer
is the component of the system that keeps track of when the action should happen.
TimedHoldTask
The TimedHoldTask
task will always be a part of the task tree which might look like the following:
-----------------------------------------------------------------------------------------------
RootTask
---------+----------------------+----------------------+-------------------------+-------------
| | | |
+--------+---------+ +--------+---------+ +--------+--------+ +------------+------------+
| | | | | | | |
| TrackVeteranTask | | DistributionTask | | JudgeAssignTask | | JudgeDecisionReviewTask |
| | | | | | | |
+------------------+ +--------+---------+ +-----------------+ +------------+------------+
| |
+--------+---------+ +------------+------------+
| | | |
| InformalHearing- | | AttorneyTask |
| PresentationTask | | |
| | +------------+------------+
+------------------+ |
+------------+------------+
| |
| ExtensionColocatedTask |
| |
+------------+------------+
|
+------------+------------+
| |
| ExtensionColocatedTask |
| |
+------------+------------+
|
+------------+------------+
| |
| TimedHoldTask |
| |
+-------------------------+
TimeableTask
TimeableTask
will never be instantiated directly, rather it is a mix-in that requires the implementing class define two methods: when_timer_ends
describing the behaviour of the application when the timer ends, and timer_ends_at
indicating when the timer for an instance of the implementing class will end. Continuing to use TimedHoldTask
as our example, TimedHoldTask.when_timer_ends
updates the status of the task to completed
, and TimedHoldTask.timer_ends_at
simply adds the desired number of days for the task to be "on hold" to the timestamp of the task's creation.
TaskTimer
In order to trigger the behaviour defined by the when_timer_ends
method, the application creates a TaskTimer
object associated with a task instance that keeps track of the timer. We expect every instance of a class that mixes in TimeableTask
to have exactly 1 associated TaskTimer
. We can confirm this expectation with the following SQL query which should return no rows:
select task_id, count(*)
from task_timers
group by task_id
having count(*) > 1;
These task timers are automatically processed and completed by our TaskTimerJob
.
There are several timestamps associated with task timers that determine how they are processed.
created_at
: when the timer was created, automatically set by rails.updated_at
: the last time any attribute of the timer was updated, automatically set by rails.last_submitted_at
: when the timer should be completedsubmitted_at
: when the timer first became eligible to be completedattempted_at
: when the job last attempted to complete the timerprocessed_at
: when the job completed the timercancelled_at
: when the timer was abandoned and not completed
Currently there is nothing stopping a timer's last_submitted_at
from being in the past. This is often seen in EvidenceSubmissionWindowTasks as the start time of its 90 day window could be calculated to be more than 90 days ago. To handle timers that should have been completed outside of the 4 day window in TaskTimerJob
, we automatically reset last_submitted_at
so the timer can be picked up the next time the job is run.
TaskTimerJob
We have a TaskTimerJob
that runs every hour checking for TaskTimer
s that have reached their expiration and calls when_timer_ends
on the associated task, completing the timed task process.
TaskTimerJob
looks for all task timers that
- should have been completed before today (
last_sumbitted_at < Time.zone.now
) - have not been processed (
processed_at_ == nil
) - have not been cancelled (
cancelled_at_ == nil
) - have not had an attempt at processing within the last 3 hours (
attempted_at < 3.hours.ago
) - OR never been attempted (
attempted_at == nil
) AND should have been completed before 3 hours ago (last_submitted_at < 3.hours.ago
) - should have been completed within the last 4 days (
last_submitted_at > 4.days.ago
)
It then
- sets the timer's
attempted_at
to now - processes the timer as defined by the task's
when_timer_ends
method - closes the task
- creates any IHP tasks if needed
- clears out any errors
- sets the timer's
processed_at
to now
Why did we implement timed tasks this way?
At the time of this writing there are only two task classes that include TimeableTask
: TimedHoldTask
and EvidenceSubmissionWindowTask
. We anticipate more types of tasks needed the ability to have some action happen after a defined amount of time. For example, it may be desirable for the window for a VSO to submit an IHP to be closed after 90 days which could be accomplished by InformalHearingPresentationTask
including TimeableTask
.
TimeableTask
be cancelled?
Can a Yes. Tasks that implement TimeableTask
(only TimedHoldTask
and EvidenceSubmissionWindowTask
) are cancelled in the same manner as all other tasks, using the "Cancel Task" action. Cancellation of a task that implements TimeableTask
will cause associated TaskTimer
s to be closed as well.
While TimedHoldTask
s do not have task actions available directly on the task itself, the parent task (the one that is "on hold") has the option to cancel the associated TimedHoldTask
.