# APPEALS-23493: Reusability of Power of Attorney Details component ## Section 1: Goals of the research - Goal 1: Determine the difficulty of reuse of the existing PoA component from the case details page - Goal 2: Determine the workings of the component and it's relevant controllers, actions, and models. - Goal 3: Determine possible implemenations or issues that need to be answered for this work to occur ## Section 2: Details and Implementation ### Subsection 2.1: Details Link to the testing github branch: [Appeals-23493 Research](https://github.com/department-of-veterans-affairs/caseflow/tree/TYLERB/APPEALS-23493-Research) The existing component from the case details page is named PowerOfAttorneyDetail.jsx. The task is to see if the component can be placed into a new section of the app, specifically the decision review queue's Task Page or disposition instead of the Case Details page in queue. Here are some of the relevant files that we will be looking at and modifying: * The main component file: [PoADetails](https://github.com/department-of-veterans-affairs/caseflow/blob/master/client/app/queue/PowerOfAttorneyDetail.jsx) * The place where we might place the component. Either the [Disposition file](https://github.com/department-of-veterans-affairs/caseflow/blob/master/client/app/nonComp/components/Disposition.jsx) or the [TaskPage file](https://github.com/department-of-veterans-affairs/caseflow/blob/master/client/app/nonComp/pages/TaskPage.jsx) * The appeals controller [Appeals Controller file](https://github.com/department-of-veterans-affairs/caseflow/blob/master/app/controllers/appeals_controller.rb) * The noncomp index.js file [NonComp Index](https://github.com/department-of-veterans-affairs/caseflow/blob/master/client/app/nonComp/index.jsx) * The [Higher Level Review](https://github.com/department-of-veterans-affairs/caseflow/blob/master/app/models/higher_level_review.rb) and [Supplemental Claim](https://github.com/department-of-veterans-affairs/caseflow/blob/master/app/models/supplemental_claim.rb) model files * Possibly more including some erb files in noncomp/decision review The component works through api calls using the redux state via redux selectors. Therefore, in order to plug and play this component into our section of the app, we would need to either implement those or reuse them from the queue component. Some of the possible steps will be outlined below in the implementation section that could accomplish this. ### Subsection 2.2: Implementation #### Subsection 2.2.1: Adding the Component to Disposition First thing is first. Here's how the component is currently used in case details. [CaseDetailsView.jsx](https://github.com/department-of-veterans-affairs/caseflow/blob/master/client/app/queue/CaseDetailsView.jsx) ```jsx <PowerOfAttorneyDetail title={CASE_DETAILS_POA_SUBSTITUTE} appealId={appealId} additionalHeaderContent={ editPOAInformation && ( <span className="cf-push-right" {...editAppellantInformationLinkStyling} > <Link to={`/queue/appeals/${appealId}/edit_poa_information`}> {updatePOALink} </Link> </span> ) } /> ``` And here is what it looks like on the page ![](https://hackmd.io/_uploads/BJQQyKkP2.png) So we need to transplant this into our decision review queue page. The designs show it on the Disposition page so we will place it there. However, it should probably go in the TaskPage which would also include RecordRequests and BoardGrants. That's a question that needs to be answered before the actual implementation. So in Disposition.jsx let's throw our component in there. ```jsx return <div> <PowerOfAttorneyDetail title={COPY.CASE_DETAILS_POA_SUBSTITUTE} appealId={appealId} additionalHeaderContent={ editPOAInformation && ( <div> <h3>Something?</h3> <span className="cf-push-right" {...editAppellantInformationLinkStyling} > <Link to={`/queue/appeals/${appealId}/edit_poa_information`}> {updatePOALink} </Link> </span></div> ) } /> <div> <div className="cf-decisions"> <div className="usa-grid-full"> <div className="usa-width-one-half"> <h2>Decision</h2> <div>Review each issue and assign the appropriate dispositions.</div> </div> <div className="usa-width-one-half cf-txt-r"> {editIssuesLink} </div> </div> <div className="cf-decision-list"> ``` This component also needs some setup before it is used. It needs to be imported and it expects a few variables if we reuse the same component properties ```jsx // Import the component at the top of the page import PowerOfAttorneyDetail from '../../queue/PowerOfAttorneyDetail'; // Before the component is used some of these need to be established to pass them as props to PowerOfAttorneyDetail // These two are copied from case details and are styling and link text. They could easily be adjusted to what we want const editAppellantInformationLinkStyling = css({ fontSize: '2rem', fontWeight: 'normal', margin: '5px', }); const updatePOALink = appeal.hasPOA ? COPY.EDIT_APPELLANT_INFORMATION_LINK : COPY.UP_DATE_POA_LINK; // This is the real editPOAInformation from case details // const editPOAInformation = // props.userCanEditUnrecognizedPOA && // [APPELLANT_TYPES.OTHER_CLAIMANT, APPELLANT_TYPES.HEALTHCARE_PROVIDER_CLAIMANT].includes( // appeal.appellantType // ) && !appeal.hasPOA && props.featureToggles.edit_unrecognized_appellant_poa; // // Just set it to true for now to always show the button const editPOAInformation = true; // The appeal uuid that the PoA component uses to fetch the poa information from the backend const appealId = task.appeal.uuid; ``` Currently on the disposition page, the task.appeal information does not include uuid so we need to add it to the [decision_review_task_serializer.rb](https://github.com/department-of-veterans-affairs/caseflow/blob/master/app/models/serializers/work_queue/decision_review_task_serializer.rb) It would probably also mean updating the serializer test eventually as well but we will irnogre that for now. ```ruby attribute :appeal do |object| # If :issue_count is present then we're hitting this serializer from a Decision Review # queue table, and we do not need to gather request issues as they are not used there. skip_acquiring_request_issues = object[:issue_count] { id: decision_review(object).external_id, uuid: decision_review(object).uuid, isLegacyAppeal: false, issueCount: issue_count(object), activeRequestIssues: skip_acquiring_request_issues || request_issues(object).active.map(&:serialize) } end ``` Now everything should be setup on the disposition page for the PowerOfAttorneyDetail component to show up. However, it will error until the redux state is setup properly. #### Subsection 2.2.2: Setting up the component to work in the decision review/non comp section of the app There are probably 3 possible paths to getting this component to work in the decision review queue: * Implement it the same as how it works on the queue app in the Case Details Page. This means setting up all of the redux store, actions, and reducers from queue to make this work. * Only import the UnconnectedPowerOfAttorney component and directly use it based on properties returned from the intial page load * A hybrid approach where we use the intial page load data to setup the redux store. The first approach is what I went with for this example. So in order to start with this approach, the redux store will need to be in the proper state that it expects. In the NonComp [Index.jsx](https://github.com/department-of-veterans-affairs/caseflow/blob/master/client/app/nonComp/index.jsx) file ```jsx // This line sets up our initial state from the props based up from server erb files const initialState = mapDataToInitialState(this.props); ``` This line sets up our redux store to be in the form of a toplevel hash with keys from the properties that come in from the server {key1: value1, key2: value2} In the PoA Component Wrapper that retrieves the data from the backend we can follow it to see what information it needs but an easy way to do this would be to just copy the setup from the queue redux store ```jsx // Import the initial setup from the queue redux store // Also import the reducer for later import { initialState as initialQueueReduxState, workQueueReducer } from '../queue/reducers'; import workQueueUiReducer, { initialState as initialUIReduxState } from '../queue/uiReducer/uiReducer'; ``` Once these are imported we can use them to setup our state below ```jsx // Add the initial queue redux state to our state that it expects so it doesn't throw errors when it tries to reference those values. It will all be undefined data but that's probably fine. const initialState = mapDataToInitialState(this.props); initialState.queue = initialQueueReduxState; initialState.ui = initialUIReduxState; ``` To determine what fields we actually needed to be populated we could follow the logic in the const PowerOfAttorneyDetailWrapper component function in the PoADetails file. It uses a couple of selectors and actions to retreive the appeal data from the redux store and if it isn't there then it goes to api/backend ```jsx const wrappedComponent = ({ appealId, getAppealValue: getAppealValueRedux }) => { const { error, loading, powerOfAttorney, appellantType } = useSelector( powerOfAttorneyFromAppealSelector(appealId), shallowEqual ); // It also uses this selector that looks at the ui state so we need ui as well const poaAlert = useSelector((state) => state.ui.poaAlert); ``` Specifically it uses getAppealValue action and powerOfAttorneyFromAppealSelector selector. These are chained so first we have to follow the getAppealValue action. ```jsx! // In the poa component jsx file import { getAppealValue } from './QueueActions'; // In QueueActions.js file export const getAppealValue = (appealId, endpoint, name) => (dispatch) => { dispatch({ type: ACTIONS.STARTED_LOADING_APPEAL_VALUE, payload: { appealId, name } }); const urlString = `/appeals/${appealId}/${endpoint}`; ApiUtil.get(urlString).then((response) => { dispatch({ type: ACTIONS.RECEIVE_APPEAL_VALUE, payload: { appealId, name, response: response.body } }); }, (error) => { dispatch({ type: ACTIONS.ERROR_ON_RECEIVE_APPEAL_VALUE, payload: { appealId, name, error } }); }); }; ``` The most relevant parts of this action are: ```jsx // The url that it will use to fetch from the endpoint const urlString = `/appeals/${appealId}/${endpoint}`; // The action constants that are being dispatched ACTIONS.STARTED_LOADING_APPEAL_VALUE ACTIONS.RECEIVE_APPEAL_VALUE ACTIONS.ERROR_ON_RECEIVE_APPEAL_VALUE ``` We will worry about the url later. These constants are used as mappings to reducer functions that will be called asynchronously on the redux store with the retrieved/given payload. In the [Queue Reducer](https://github.com/department-of-veterans-affairs/caseflow/blob/master/client/app/queue/reducers.js) file ```jsx! // This maps our constant from the dispatch call to this function receiveAppealValue export const workQueueReducer = createReducer({ [ACTIONS.RECEIVE_APPEAL_VALUE]: receiveAppealValue, }); // The actual function that does the work const receiveAppealValue = (state, action) => { const existingState = state.loadingAppealDetail[action.payload.appealId] || {}; const existingDetails = state.appealDetails[action.payload.appealId] || {}; return update(state, { loadingAppealDetail: { $merge: { [action.payload.appealId]: { ...existingState, [action.payload.name]: { loading: false } } } }, appealDetails: { $merge: { [action.payload.appealId]: { ...existingDetails, [action.payload.name]: action.payload.response } } } }); }; ``` Here we can see the two keys being updated in the redux store: * loadingAppealDetail: * appealDetails: These are scoped to queue so it would be the equivilent of state.queue.loadingAppealDetail and state.queue.appealDetails That is a lot of searching to figure out what keys are needed. Hence, why it was easier earlier to just copy the entire state setup. So now that we know how this component wrapper works, we will need the queue reducer for sure if we want to use the wrapper in the same that the CaseDetails is using it. Back in our index.js file for Noncomp. This could also be done in the noncomp reducers file and exported. ```jsx! import { combineReducers } from 'redux'; // Have to combine the noncomp and queue reducers to get the functionality needed for PoA Details Component const rootReducer = combineReducers({ queue: workQueueReducer, nonComp: nonCompReducer }); // Also add it to our Redux store or nothing will happen <ReduxBase initialState={initialState} reducer={rootReducer}> ``` This is all the setup needed for the frontend to initial the api call. However, there is a side effect of using combineReducers in this way. Our noncomp state is now ```javascript { nonComp: {keys, values}, queue: {keys, values} } ``` When before it was the top level scope of the state. So this will unfortunately break basically all the connect statements in NonComp so we have to go fix all of those. [NonCompTabs](https://github.com/department-of-veterans-affairs/caseflow/blob/master/client/app/nonComp/components/NonCompTabs.jsx) ```jsx! const NonCompTabs = connect( (state) => ({ currentTab: state.nonComp.currentTab, baseTasksUrl: state.nonComp.baseTasksUrl, taskFilterDetails: state.nonComp.taskFilterDetails }) )(NonCompTabsUnconnected); ``` [TaskTableTab](https://github.com/department-of-veterans-affairs/caseflow/blob/master/client/app/nonComp/components/TaskTableTab.jsx) ```jsx! const TaskTableTab = connect( (state) => ({ featureToggles: state.nonComp.featureToggles }) )(TaskTableTabUnconnected); ``` [Disposition](https://github.com/department-of-veterans-affairs/caseflow/blob/master/client/app/nonComp/components/Disposition.jsx) ```jsx! export default connect( (state) => ({ appeal: state.nonComp.appeal, task: state.nonComp.task, decisionIssuesStatus: state.nonComp.decisionIssuesStatus }) )(NonCompDispositions); ``` [BoardGrant](https://github.com/department-of-veterans-affairs/caseflow/blob/master/client/app/nonComp/components/BoardGrant.jsx) ```jsx! const BoardGrant = connect( (state) => ({ appeal: state.nonComp.appeal, businessLine: state.nonComp.businessLine, task: state.nonComp.task, decisionIssuesStatus: state.nonComp.decisionIssuesStatus }) )(BoardGrantUnconnected); ``` [Record Request](https://github.com/department-of-veterans-affairs/caseflow/blob/master/client/app/nonComp/components/RecordRequest.jsx) ```jsx! const RecordRequest = connect( (state) => ({ appeal: state.nonComp.appeal, businessLine: state.nonComp.businessLine, task: state.nonComp.task, decisionIssuesStatus: state.nonComp.decisionIssuesStatus }) )(RecordRequestUnconnected); ``` There could be a couple more but hopefully that's all. The component should be setup now to work using the redux store The second type of implementation from the list of options would have looked more like this. In the disposition file we would import the unconnected component instead of the fully connected component. Of course it wouldn't work without fully supplying all of the data and the refresh button couldn't work either since it also relies on redux calls, but this is an option to consider. ```jsx! import { PowerOfAttorneyDetailUnconnected } from '../../queue/PowerOfAttorneyDetail'; // It would need all of these properties since they weren't being retrieved from the backend in the same way as the Queue implementation export const PowerOfAttorneyDetailUnconnected = ({ powerOfAttorney, appealId, poaAlert, appellantType }); ``` This second method would probably the next section quite a bit less messy at the cost of more backend work. However, it might result in a cleaner implementation overall. We could also implement a new wrapper for the decision review redux store instead of using the built in wrapper present in the component for the queue redux store. #### Subsection 2.2.3: Retrieving the information from the backend api This section is largely dependent on how the data is retrieved from the backend by the frontend implementation. I'll outline the existing method that queue currently uses and how to work with that first. ```jsx! // The url that it will use to fetch an appeal from the endpoint // This is in the QueueAction getAppealValue const urlString = `/appeals/${appealId}/${endpoint}`; // This is how it builds the endpoint. getAppealValueRedux(appealId, 'power_of_attorney', 'powerOfAttorney'); // So the resulting url will be /appeals/${appealID}/power_of_attorney ``` That url will be in the [routes.rb](https://github.com/department-of-veterans-affairs/caseflow/blob/master/config/routes.rb) file ```ruby! resources :appeals, param: :appeal_id, only: [:index, :show, :edit] do member do get :document_count get :veteran get :power_of_attorney patch :update_power_of_attorney get 'hearings', to: "appeals#most_recent_hearing" resources :issues, only: [:create, :update, :destroy], param: :vacols_sequence_id resources :special_issues, only: [:create, :index] resources :advance_on_docket_motions, only: [:create] get 'tasks', to: "tasks#for_appeal" patch 'update' post 'work_mode', to: "work_modes#create" patch 'cavc_remand', to: "cavc_remands#update" post 'cavc_remand', to: "cavc_remands#create" post 'appellant_substitution', to: "appellant_substitutions#create" patch 'nod_date_update', to: "nod_date_updates#update" end end # The actual part of the dsl we care about we care about get :power_of_attorney ``` This will hit the [appeals_controller](https://github.com/department-of-veterans-affairs/caseflow/blob/master/app/controllers/appeals_controller.rb) at the action power_of_attorney ```ruby! def power_of_attorney render json: power_of_attorney_data end ``` Which renders this json ```ruby! def power_of_attorney_data { representative_type: appeal.representative_type, representative_name: appeal.representative_name, representative_address: appeal.representative_address, representative_email_address: appeal.representative_email_address, representative_tz: appeal.representative_tz, poa_last_synced_at: appeal.poa_last_synced_at } end ``` Appeal is a cached attribute defined by this method ```ruby def appeal @appeal ||= Appeal.find_appeal_by_uuid_or_find_or_create_legacy_appeal_by_vacols_id(params[:appeal_id]) end ``` So this is already a problem. The decision review queue will be dealing with HigherLevelReviews and SupplmentalClaims instead of just Appeals. And we are using the appeals_controller for things that aren't always going to be appeals. There are ways to get around this by just checking against the other types of decision reviews first either here or in that find method but it's not an ideal solution. ```ruby! # We can hack the method to work for our types for now though @appeal ||= (HigherLevelReview.find_by(uuid: params[:appeal_id]) || SupplementalClaim.find_by(uuid: params[:appeal_id])) ``` So now we have our decision review saved as the appeal variable. Now the json method calls a bunch of methods on what it assumes is the appeal model. The HLR and SC model classes do not have most of these methods. In the [Appeal](https://github.com/department-of-veterans-affairs/caseflow/blob/master/app/models/appeal.rb) model ```ruby! delegate :power_of_attorney, to: :claimant delegate :representative_name, :representative_type, :representative_address, :representative_email_address, :poa_last_synced_at, :update_cached_attributes!, :save_with_updated_bgs_record!, to: :power_of_attorney, allow_nil: true ``` A lot of the call used for the json are delegated to the claimant association in the appeal model but that doesn't exist for our HLRs or SCs so we can add it for now In the [HigherLevelReview] model ```ruby! class HigherLevelReview < ClaimReview with_options if: :saving_review do validates :informal_conference, inclusion: { in: [true, false], message: "blank" } validates :same_office, inclusion: { in: [true, false], message: "blank" }, unless: lambda { FeatureToggle.enabled?(:updated_intake_forms, user: RequestStore.store[:current_user]) } end has_many :remand_supplemental_claims, as: :decision_review_remanded, class_name: "SupplementalClaim" delegate :power_of_attorney, to: :claimant delegate :representative_name, :representative_type, :representative_address, :representative_email_address, :poa_last_synced_at, :update_cached_attributes!, :save_with_updated_bgs_record!, to: :power_of_attorney, allow_nil: true # ... etc ``` There are two more methods in the json hash that are from the [AppealConcern](https://github.com/department-of-veterans-affairs/caseflow/blob/master/app/models/concerns/appeal_concern.rb) ActiveRecord Concern so we would need those too ```ruby! # Borrowed from AppealConcern def representative_tz timezone_identifier_for_address(representative_address) end # Borrowed from AppealConcern def timezone_identifier_for_address(addr) return if addr.blank? address_obj = addr.is_a?(Hash) ? Address.new(addr) : addr # Some appellant addresses have empty country values but valid city, state, and zip codes. # If the address has a zip code then we make the best guess that the address is within the US # (TimezoneService.address_to_timezone will raise an error if this guess is wrong and the zip # code is not a valid US zip code), otherwise we return nil without attempting to get # thetimezone identifier. if address_obj.country.blank? return if address_obj.zip.blank? new_address_hash = address_obj.as_json.symbolize_keys.merge(country: "USA") address_obj = Address.new(**new_address_hash) end # APO/FPO/DPO addresses do not have time zones so we don't attempt to fetch them. return if address_obj.military_or_diplomatic_address? begin TimezoneService.address_to_timezone(address_obj).identifier rescue TimezoneService::AmbiguousTimezoneError => error # TimezoneService raises an error for foreign countries that span multiple time zones since we # only look up time zones by country for foreign addresses. We do not act on these errors (they # are valid addresses, we just cannot determine the time zone) so we do not send the error to # Sentry, only to Datadog for trend tracking. DataDogService.increment_counter( metric_group: "appeal_timezone_service", metric_name: "ambiguous_timezone_error", app_name: RequestStore[:application], attrs: { country_code: error.country_code } ) nil rescue StandardError => error Raven.capture_exception(error) nil end end ``` Once those are in the redux retrieval should work and the component should be present on the Task page for a disposition. The styling will have to be adjusted since the title doesn't get rendered by the component itself. ![](https://hackmd.io/_uploads/HyTPgpJv3.png) This implementation reuses the current redux and component wrapper. If we made our own wrapper or we went with the unconnected component implementation. We could place most of the controller logic in the decision review queue controller instead of the appeal controller. We could also maybe pass it a different getAppealValue function which would let us do the same thing. I think that's probably preferable to going through the appeals controller for things that aren't appeals. Example of sending a different getAppealValue function ```jsx! // In dispositions <PowerOfAttorneyDetail title={COPY.CASE_DETAILS_POA_SUBSTITUTE} appealId={appealId} // getAppealValueRedux={this.getAppealValueRedux} getAppealValue={this.getAppealValueRedux} //... etc more properties // In PowerOfAttorneyDetail.jsx import { getAppealValue as importedGetAppealValue } from './QueueActions'; const PowerOfAttorneyDetailWrapper = (WrappedComponent) => { const wrappedComponent = ({ appealId, getAppealValue: getAppealValueRedux }) => { const getAppealValue = getAppealValueRedux || importedGetAppealValue; ``` Example of new wrapper ```jsx! // In our new wrapper file import { getDecisionReviewValue } from './QueueActions'; const PowerOfAttorneyDetailDecisionReviewWrapper = (WrappedComponent) => { // I hate this method of destructuring. It's so damn obfuscated unless you know the exact syntax of what is happening. const wrappedComponent = ({ appealId, getDecisionReviewValue: ourNewFunction }) => { //other setup etc... // Our new function or action which would could send a request that hits the DR controller instead of the appeals controller // example route: /decision_reviews/{appeal_id}/power_of_attorney ourNewFunction(appealId) } ``` #### Subsection 2.2.4: Setting up PoA Refresh button The button is created via the [PoaRefresh.jsx](https://github.com/department-of-veterans-affairs/caseflow/blob/master/client/app/queue/components/PoaRefresh.jsx) component It is added in the PoA component with these lines in the renderPoaLogic() function. ```jsx! // both variables are setup like this const isRecognizedAppellant = ![ APPELLANT_TYPES.OTHER_CLAIMANT, APPELLANT_TYPES.ATTORNEY_CLAIMANT, APPELLANT_TYPES.HEALTHCARE_PROVIDER_CLAIMANT ].includes(appellantType); const isRecognizedPoa = poa.representative_type !== 'Unrecognized representative'; if (isRecognizedAppellant && isRecognizedPoa) { return <PoaRefresh powerOfAttorney={poa} appealId={appealId} {...detailListStyling} />; } ``` Inside of the Poa Refresh component there seems to be a switch on a feature toggle to determine if the button should be shown or not ```jsx! const viewPoaRefresh = useSelector((state) => state.ui.featureToggles.poa_button_refresh); return <React.Fragment> {viewPoaRefresh && <div {...textStyling}> <em>{ COPY.CASE_DETAILS_POA_REFRESH_BUTTON_EXPLANATION }</em> <div {...gutterStyling}></div> <div {...boldText}{...syncStyling}> {poaSyncInfo.poaSyncDate && <em>{lastSyncedCopy}</em> } <PoaRefreshButton appealId={appealId} /> </div> </div> } </React.Fragment>; ``` This switch is based on a feature toggle in the ui state object. We are already passing up feature toggles in the index file in nonComp.js. It's easy enough add this feature toggle to the decision review [index.erb](https://github.com/department-of-veterans-affairs/caseflow/blob/master/app/views/decision_reviews/index.html.erb) and [show.html.erb](https://github.com/department-of-veterans-affairs/caseflow/blob/master/app/views/decision_reviews/show.html.erb) ```ruby featureToggles: { decisionReviewQueueSsnColumn: FeatureToggle.enabled?(:decision_review_queue_ssn_column, user: current_user), poa_button_refresh: FeatureToggle.enabled?(:poa_button_refresh, user: current_user), poa_auto_refresh: FeatureToggle.enabled?(:poa_auto_refresh, user: current_user) }, ``` Queue also has the poa_auto_refresh feature toggle. I am not sure what it does or if we want that as well. Now we can just do a quick hack in the nonComp index.js file to copy these nonComp feature toggles to the ui feature toggles state. It could probably be a dispatch call using an ui action but it's easier for a demonstration to just add it to the initial state and we are already population noncomp state like that anyhow. ```jsx! initialState.ui.featureToggles = this.props.serverNonComp.featureToggles; ``` After that the button should appear on the page in the component. Styling isn't perfect out of the box. ![](https://hackmd.io/_uploads/BJ8DN1lw3.png) The actual button click and rendering is handled by a different component inside of PoaRefresh named [PoaRefreshButton](https://github.com/department-of-veterans-affairs/caseflow/blob/master/client/app/queue/components/PoaRefreshButton.jsx) It uses the function below to update the PoA ```jsx! const updatePOA = () => { setButtonText(<SmallLoader message="Refresh POA" spinnerColor="#417505" />); ApiUtil.patch(`/appeals/${appealId}/update_power_of_attorney`).then((data) => { dispatch(setPoaRefreshAlert(data.body.alert_type, data.body.message, data.body.power_of_attorney)); setButtonText('Refresh POA'); }); }; ``` It hits the appeals controller again, however this button currently has no way to change the url like the other component since the ApiUtil patch is written directly into the component. The updatePOA function would need to be passed as a function if we wanted to change that somehow. Regardless of all that, it will currently hit the appeals controller on the action update_power_of_attorney ```ruby! def update_power_of_attorney clear_poa_not_found_cache if cooldown_period_remaining > 0 render json: { alert_type: "info", message: "Information is current at this time. Please try again in #{cooldown_period_remaining} minutes", power_of_attorney: power_of_attorney_data } else message, result, status = update_or_delete_power_of_attorney! render json: { alert_type: result, message: message, power_of_attorney: (status == "updated") ? power_of_attorney_data : {} } end rescue StandardError => error render_error(error) end ``` It will probably hit the else block first since it hasn't been updated for demo/local. Which means it will call the update_or_delete_power_of_attorney! method ```ruby! def update_or_delete_power_of_attorney! appeal.power_of_attorney&.try(:clear_bgs_power_of_attorney!) # clear memoization on legacy appeals poa = appeal.bgs_power_of_attorney if poa.blank? ["Successfully refreshed. No power of attorney information was found at this time.", "success", "blank"] elsif poa.bgs_record == :not_found poa.destroy! ["Successfully refreshed. No power of attorney information was found at this time.", "success", "deleted"] else poa.save_with_updated_bgs_record! ["POA Updated Successfully", "success", "updated"] end end ``` HLRs and SCs do not have this so we have to add it ```ruby! # HLR/SC does not have this method so we have to add poa = appeal.bgs_power_of_attorney ``` ```ruby def bgs_power_of_attorney claimant&.is_a?(BgsRelatedClaimant) ? power_of_attorney : nil end ``` After that the button click will work, but it will still error. However, the error is the normal BGS Postgres error for duplicate id meaning that it is probably working at this point. However the render_error method actually also breaks because the type method is missing from HLR/SC so add that too. ```ruby! def type "Original: Higher Level Review?" end ``` Now the button should behaving the same in both the queue Case Details page and the decision review Disposition page. **Note: I'm not sure how to make the actual Bgs call work, but I assume you would have to match the ids and participant ids to ones that you setup in the BGS fakes so it will properly update the information instead of forcing it into keys that will trigger a DB error.** ## Section 3: Results and Conclusions ### Subsection 3.1: Results Reusing the component is not as straightforward as we might have initially hoped. The implementation reusing component can be implemented in roughly 3 ways that are immediately apparent to me: * Completely reuse the full redux implementation from queue * Create a new wrapper to populate the PoA component from the noncomp redux store (Or it could still use the queue redux store. The main thing would be rerouting the api calls to the DR queue) * Import the Component without a wrapper and get the information on initial page load (We could also get that information through redux calls or api calls that we create from scratch.) No matter which method we go with, it will not be as quick as just placing the component on the page due all of the differences between the two frontend redux stores. The backend appeals controller also does not understand how to deal with decision reviews other than appeals (and rightfully so since it's the "APPEALS" controller). ### Subsection 3.2: Questions that need to be answered before or during implemenation Here is a list of questions that might be nice to have some answers to before someone picks this up eventually. 1. Which method of frontend implementation we want to go with? 2. Do we want to tack this onto the appeals controller or reroute everything to the DR controller? 3. Should this component only be on the Dispositions page or should it also be on the other Task pages like Record Request and Board Grant? 4. Should we break this ticket up into front and backend after all since there is more work than previously thought. 5. How to actually setup the fake BGS service, so that we can actually test the refresh button correctly.