# APPEALS-10565 Research
We require a way to capture and track the requests for users who are requesting membership to VHA organizations. At the moment it is unclear what the best way to go about this would be as there do not seem to be any existing notification systems in Caseflow that are specific to user management (i.e. not tied to some other Caseflow business process i.e. appeals, hearings etc.). For this reason, although some sort of task system on the surface would seem to make sense - all existing tasks relate to one of the aforementioned business processes. Therefore attempting to utilize a task (at least as we currently do) would seem to present some hurdles to in terms of creating one that pertains only to user management.
The ask of this research task is to investigate and document the feasibility of potential solutions for this new user management system that we'll need to implement.
## Feasibility of Using Tasks
This section aims to address the feasibility of using the existing Tasks table/class to capture and track the existance of notifications
Initial thoughts: Tasks, as the are currently used, are typically associated with appeals in almost all cases that I am aware of. In this ask, there would be no need for any type of appeal information to be generated or tracked, and therefore tasks might not be the best method to accomplish this.
### General Task Overview
Below is a list of all the task associations and attributes
```ruby
#Task Association Names
[
:versions,
:parent,
:children,
:assigned_to,
:assigned_by,
:cancelled_by,
:appeal,
:ama_appeal,
:legacy_appeal,
:supplemental_claim,
:higher_level_review,
:attorney_case_reviews,
:task_timers,
:cached_appeal
]
#Task Attributes
{
"id"=>nil,
"appeal_id"=>nil,
"appeal_type"=>nil,
"assigned_at"=>nil,
"assigned_by_id"=>nil,
"assigned_to_id"=>nil,
"assigned_to_type"=>nil,
"cancellation_reason"=>nil,
"cancelled_by_id"=>nil,
"closed_at"=>nil,
"created_at"=>nil,
"instructions"=>[],
"parent_id"=>nil,
"placed_on_hold_at"=>nil,
"started_at"=>nil,
"status"=>"assigned",
"type"=>nil,
"updated_at"=>nil
}
```
From the list we can see that there are a lot of attibutes and associations that would be irrelevant for what we are trying to accomplish.
The two associations we are most interested in are assigned_to and assigned_by as that can be used to capture the user that made the request and the organization the user is requesting to join.
The attributes that we are most interested in (excluding keys and ids) are status, started_at, updated_at, and closed_at. These will probably be enough to identify the state of the request from initial request all the way through rejecting or accepting the request. The instructions field could also be potentially useful for conveying information between the two parties.
### Reusing an existing task
The EngineeringTask is an example of a task created for a purpose that external to the normal appeals workflow. However, it is still tied to an appeal like most other tasks.
QualityReviewTask is similar to the EngineeringTask for establishing precedent for tasks that aren't directly a part of the normal caseflow workflow.
A TimeableTask might be reusable to schedule, process, and cleanup the requests to join an organization
MailTask children keep track of email addresses and could potentially be reused in some way.
#### Synopsis:
There don't seem to be any existing tasks that would currently fit the ask and I don't recommend modifying or repurposing an existing existing task to accomplish this.
### Creating a new task
Creating a new task to capture the effort is easy enough
```ruby
# Simple inheritance with no specific functionality yet
class JoinOrganizationTask < Task
end
```
We can then use this new task in our form on the help page to create a request to an organization.
We need to update the TasksController to include our new task type. Specifically adding the line below to this hash TASK_CLASSES_LOOKUP
```ruby
JoinOrganizationTask: JoinOrganizationTask,
```
Now we should be able to create this new task through the UI with a post request with this payload
**tbd update this so that it works with actual values**
```javascript
// Create a post request to the url /tasks
// Use the below payload to create the task
{
type: JoinOrganizationTask,
assigned_to: VhaCamo.singleton!,
assigned_by: User
}
```
But before that let's go into the console and try to create one of our new task types
```ruby
new_task = JoinOrganizationTask.create!
```
Whoops

Well, unfortunately due to this line in the Tasks.rb model you are always expected to have an Appeal
```ruby
validates :assigned_to, :appeal, :type, :status, presence: true
```
So we have to hack our subclass a bit to dodge this validation
```ruby
# Simple inheritance with no specific functionality yet
class JoinOrganizationTask < Task
# Removes the validators from the tasks superclass
clear_validators!
# Reapplys the validators to the subclass excluding appeal
validates :assigned_to, :assigned_by, :type, :status, presence: true
end
```
Let's try that again
```ruby
new_task = JoinOrganizationTask.create!(assigned_to: VhaCamo.singleton, assigned_by: User.first)
```
And it fails! Again...

In the Tasks.rb file in verify_org_task_unique it is once again expecting an appeal. This is already requiring quite a few hacks to avoid the appeal association. So lets override the method in our subclass
```ruby
def verify_org_task_unique
# We could still use this to check if the org has other tasks of this type
# The check would only allow one of these tasks with the assigned user
# To this assigned org with the status of assigned or open
```

There's a database constraint as well. That's yet one more pretty large change that has to be made to use tasks for this purpose.
```ruby!
# rails generate migration RemoveNulls
# Edit the script to look like this
class RemoveNulls < ActiveRecord::Migration[5.2]
def change
change_column_null :tasks, :appeal_id, true
change_column_null :tasks, :appeal_type, true
end
end
#inside the Schema.rb should look like this
create_table "tasks", force: :cascade do |t|
t.integer "appeal_id"
t.string "appeal_type"
end
```
Finally, it will allow object creation!

TBD Test it somehow. Possibly remove csrf tokens or actually make the form on the page
### Help page form for testing it out
We need some way to test out the controller and disabling the csrf token check is gross, so we might as well build out a simple form for vha help page to test the task since it's part of the ask.
On the pages page at the url /help add the new vha help page link
Add a new object to the pages array in HelpRootView.jsx file to create a new link on the page
```jsx
{
name: 'VHA Help',
url: '/vha/help'
}
```
Add a new route for the url above in the help/index.js file
```jsx!
// Also import this at the top
import VhaHelp from './components/VhaHelp';
<PageRoute exact
path="/vha/help"
title="Vha Help"
component={VhaHelp} />
```
Finally go create the VhaHelp.jsx file and export the VhaHelp Component
```jsx!
import React from 'react';
class VhaHelp extends React.Component {
render() {
return <div className="cf-help-content">
<h1> Vha Help page</h1>
</div>;
}
}
export default VhaHelp;
```
Now that the javascript side is generated for now. We have to update the controller methods
In application_controller.rb update the help_url method which is used for the routing
```ruby!
def help_url
{
"certification" => certification_help_path,
"dispatch-arc" => dispatch_help_path,
"reader" => reader_help_path,
"hearings" => hearing_prep_help_path,
"intake" => intake_help_path,
"vha" => vha_help_path
}[application] || help_path
end
```
In routes.rb add a vha help route
```ruby!
# Add a route with this url to point to the help controller which will go up to application controller and use the method updated above to create the appropiate url to controller action mapping
get 'vha/help' => 'help#vha'
```
Lastly go create a vha.html.erb file in the same location as the other help files and copy their content to the new file
```htmlmixed!
<% content_for :full_page_content do %>
<%= react_component("Help", props: {
userDisplayName: current_user ? current_user.display_name : "",
dropdownUrls: dropdown_urls,
feedbackUrl: feedback_url,
buildDate: build_date
}) %>
<% end %>
```
That should take care of all the routing and if we load the page we should see our new help page at the following url: /vha/help

If we add a simple form to the page we should be able to create an instance of our new task using the tasks controller by sending a post request to the /tasks url.
Updating the VhaHelp.jsx to include a form and a submit function with the correct post data.
```javascript!
import React from 'react';
import ApiUtil from 'app/util/ApiUtil';
class VhaHelp extends React.Component {
constructor(props) {
super(props);
this.state = {
results: '',
};
this.handleSubmit = this.handleSubmit.bind(this);
}
async handleSubmit(event) {
event.preventDefault();
const payload = {
data: {
tasks:
[
{
type: 'JoinOrganizationTask',
assigned_to_id: 2000000222,
assigned_to_type: 'BusinessLine'
}
]
}
};
try {
const results = await ApiUtil.post('/tasks', payload);
this.setState({ results: JSON.stringify(results.body.tasks.data[0], undefined, 2) });
} catch (error) {
console.log(error);
// throw error;
}
}
render() {
return (
<div className="cf-help-content">
<h1> Vha Help page</h1>
<form onSubmit={this.handleSubmit}>
<h2>My Form?</h2>
<pre><code>{this.state.results}</code></pre>
<input type="submit" value="Submit" />
</form>
</div>
);
}
}
export default VhaHelp;
```
That is simple enough, but unfortunately it doesn't work without more hacks to the task controller and tasks model.
Back in the tasks controller, since we don't have an external id or an appeal we have to change the create_params method
```ruby
if task[:external_id]
appeal = Appeal.find_appeal_by_uuid_or_find_or_create_legacy_appeal_by_vacols_id(task[:external_id])
end
```
But that's not enough, the Task.rb file method create_by_params also has to be adjusted for the lack of appeal and parent task.
```ruby
def create_from_params(params, user)
# This line has to be adjusted because .find will error if it is nil and since there is no parent it will always fail
parent_task = Task.find_by(id: params[:parent_id])
# likewise this has to be skipped
if parent_task
fail Caseflow::Error::ChildTaskAssignedToSameUser if parent_of_same_type_has_same_assignee(parent_task, params)
end
# These steps also have to be skipped because there is no parent task
if parent_task
verify_user_can_create!(user, parent_task)
params = modify_params_for_create(params)
child = create_child_task(parent_task, user, params)
parent_task.update!(status: params[:status]) if params[:status]
else
# This is the new else logic to handle this new task and potentially any others without an appeal
params = modify_params_for_create(params)
# New create_task_without_appeal method
child = create_task_without_appeal(user, params)
end
child
end
# New method to handle creating this task without an appeal
def create_task_without_appeal(current_user, params)
Task.create!(
type: name,
assigned_by_id: current_user.id,
assigned_to: params[:assigned_to] || child_task_assignee(parent, params),
instructions: params[:instructions]
)
end
```
But it still doesn't work after all of this because the tasks controller json_tasks(modified_tasks) line uses the tasks_serializer. Guess what doesn't work without an appeal? So the tasks serializer has to be updated with safe operators around every attribute that uses an appeal.
A few examples, but all of the attributes with appeal have to be updated since this task isn't tied to an appeal.
```ruby
attribute :veteran_file_number do |object|
object.appeal&.veteran_file_number
end
attribute :closest_regional_office do |object|
object.appeal&.closest_regional_office && RegionalOffice.find!(object.appeal.closest_regional_office)
end
attribute :external_appeal_id do |object|
object.appeal&.external_id
end
```
Finally after all that when we submit the form it will create an instance of our task and return the created task data to the page

### Overall Opinion for using a task
There were no existing tasks that handled notifications that were not directly tied to an appeal. The notifications that existed were also almost entirely based around email notification which we could certainly reuse if that is our primary method of notification.
The new JoinOrganizationTask did work as a method to store and track the User request to join the vha organization.
**Pros:**
* Can reuse existing controller logic and routing
* No additional database tables need to be created
* The ask closely resembles a normal task workflow since it is assigned and closed when the task is finished
**Cons:**
* Hacking Tasks.rb to make this work without an appeal or parent is a bit scary since Tasks are used for everything relating to an appeal in caseflow
* It requires a lot of hacking to make this work
* If we needed additional attributes we would have to add them to the tasks table which all other tasks use.
* There are a lot of additional attributes and associations that we don't currently need for the ask that are present because of other tasks
My recommendation based on the comparisons of pros and cons would be to avoid using Tasks as a method of tracking the user requests to join an organziation.
## Creating a customizable data record
One method we could pursue to track this information is to simply create a new database table and active record model. I'm going to start from the model created in APPEALS-11024
```ruby
# frozen_string_literal: true
class OrganizationMembershipRequest < CaseflowRecord
attribute :created_at, :datetime, default: Time.zone.now
validates :created_by, :organization, presence: true, on: :create
belongs_to :created_by, class_name: "User"
belongs_to :decision_by, class_name: "User"
belongs_to :organization, class_name: "Organization"
alias_attribute :requested_by, :created_by
def approved?
permitted_admittance || false
end
def denied?
permitted_admittance == false
end
def pending?
permitted_admittance.nil?
end
def apply_decision(admin_user, decision)
# We may want a column for tracking whenever a decision is applied
update!(
decision_by: admin_user,
permitted_admittance: decision
)
end
end
```
Testing out the new data type after migration and creation of the database table

Since it works we need a way to create this from the UI. We need to add a controller to handle the actions from the UI. We can start from the controller file in APPEALS-11024. We need to add a create method so we can send a post request to the controller in order to create the instance from the vha help page form.
```ruby
# frozen_string_literal: true
class OrganizationMembershipRequestsController < ApplicationController
before_action :verify_org_admin, only: [:update_membership_request]
def update_membership_request
organization_membership_request
render json: { org_request: organization_membership_request }
end
# New create method
def create
new_org_request = OrganizationMembershipRequest.create_from_params(create_params, current_user)
# Might want to create a serializer
render json: new_org_request
end
private
def verify_org_admin
Organization.find_by(url: params[:org_url]).user_is_admin?(current_user)
end
# Helper method for creating a new object
def create_params
new_org_task = params
new_org_task = new_org_task.require(:organization_membership_request).permit(:organization_id)
new_org_task
end
end
```
We also have to add a route in routes.rb to create this new object through the controller with a post request
```ruby
resources :organization_membership_requests, only: [:create] do
end
```
Lastly update the OrganizationMembershipRequests.rb model file to contain the method to create our new instance object from params and a user
```ruby
# Class methods
class << self
def create_from_params(params, user)
# Needs some error handling in true implementation
organization = Organization.find_by(id: params[:organization_id])
create!(
created_by: user,
organization: organization
)
end
end
```
Now if we add a new submit function to the VhaHelp.jsx file to replace the old one for the tasks post form submission we can test out this new class as well. Create a new handle submit function there.
```javascript
async handleSubmit(event) {
event.preventDefault();
const payload = {
data: {
organization_membership_request:
{
organization_id: 2000000222
}
}
};
try {
const results = await ApiUtil.post('/organization_membership_requests', payload);
this.setState({ results: JSON.stringify(results.body, undefined, 2) });
} catch (error) {
console.log(error);
// throw error;
}
}
```
The submit works! Below is the returned json for the newly created OrganizationMembershipRequests object

### Overall Opinion for creating a brand new datatype
The new OrganizationMembershipRequests model works as a method to store and track the User's request to join the vha organization.
**Pros:**
* Flexibility - since this is a new database table and model we can expand it to do whatever it is we need to do
* Low coupling - we don't have to worry about causing other failures by altering existing behavior
**Cons:**
* Have to create a new database table (Using a task would also require a migration however)
* Have to create new routing, controller logic, and model logic. There's nothing to reuse since it is all brand new.
* Currently untested behavior since it is a brand new workflow and model
## Other possibilities placeholder
There are potentially more implementations that have not been discovered yet.
## Overall recommendation
My overall recommendation is going to be creating a customizable data record. I can't see a situation in which it is ever going to be worth it to modify existing task behavior to such an extent that is required to make tasks work for the desired behavior. Tasks are heavily heavily coupled to appeals and parent tasks (e.g. Root task which has an appeal).