Tyler Broyles
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note No publishing access yet

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.

      Your account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

      Your team account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

      Explore these features while you wait
      Complete general settings
      Bookmark and like published notes
      Write a few more notes
      Complete general settings
      Write a few more notes
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Make a copy
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Make a copy Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note No publishing access yet

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.

    Your account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

    Your team account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

    Explore these features while you wait
    Complete general settings
    Bookmark and like published notes
    Write a few more notes
    Complete general settings
    Write a few more notes
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    # 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 ![](https://i.imgur.com/BXmPneZ.png) 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... ![](https://i.imgur.com/cxtfYQY.png) 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 ``` ![](https://i.imgur.com/ddMGT7g.png) 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! ![](https://i.imgur.com/163iYQO.png) 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 ![](https://i.imgur.com/sk9WmB2.png) 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 ![](https://i.imgur.com/yzjniVd.png) ### 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 ![](https://i.imgur.com/KNtd1ez.png) 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 ![](https://i.imgur.com/ytGeKsG.png) ### 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).

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password
    or
    Sign in via Google Sign in via Facebook Sign in via X(Twitter) Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    By signing in, you agree to our terms of service.

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully