Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MB-13125 - handle state for weightTickets #8873

Merged
merged 19 commits into from
Jul 18, 2022

Conversation

ronaktruss
Copy link
Contributor

@ronaktruss ronaktruss commented Jul 8, 2022

Jira ticket for this change

Summary

Adds weightTicket info into the redux state. This change is still dependent on the integration to ensure that appropriate API calls are being made.

Setup to Run Your Code

💻 You will need to use two separate terminals to test this locally.
Terminal 1

Start the UI locally.

make client_run
Terminal 2

Start the Go server locally.

make server_run

Additional steps

  1. Access the customer app
  2. Login as [email protected]
  3. When navigating from the about page to the weight ticket page you can verify that the redux state is updated with the fake data that is provided by the createWeightTicket function.
  4. To test a hard refresh scenario you have to mock an ADD_ENTITIES action that has some weightTicket info.
Example action:
{
  type: 'ADD_ENTITIES',
  entities: {
    mtoShipments: {
      '2ed2998e-ae36-46cd-af83-c3ecee55fe3e': {
        createdAt: '2022-07-01T01:10:51.224Z',
        eTag: 'MjAyMi0wNy0xMVQxODoyMDoxOC43MjQ1NzRa',
        id: '2ed2998e-ae36-46cd-af83-c3ecee55fe3e',
        moveTaskOrderID: '26b960d8-a96d-4450-a441-673ccd7cc3c7',
        ppmShipment: {
          actualDestinationPostalCode: '30813',
          actualMoveDate: '2022-07-31',
          actualPickupPostalCode: '90210',
          advanceAmountReceived: null,
          advanceAmountRequested: 598700,
          approvedAt: '2022-04-15T12:30:00.000Z',
          createdAt: '2022-07-01T01:10:51.231Z',
          destinationPostalCode: '30813',
          eTag: 'MjAyMi0wNy0xMVQxODoyMDoxOC43NTIwMDNa',
          estimatedIncentive: 1000000,
          estimatedWeight: 4000,
          expectedDepartureDate: '2020-03-15',
          hasProGear: true,
          hasReceivedAdvance: false,
          hasRequestedAdvance: true,
          id: 'b9ae4c25-1376-4b9b-8781-106b5ae7ecab',
          netWeight: null,
          pickupPostalCode: '90210',
          proGearWeight: 1987,
          reviewedAt: null,
          secondaryDestinationPostalCode: '30814',
          secondaryPickupPostalCode: '90211',
          shipmentId: '2ed2998e-ae36-46cd-af83-c3ecee55fe3e',
          sitEstimatedCost: null,
          sitEstimatedDepartureDate: null,
          sitEstimatedEntryDate: null,
          sitEstimatedWeight: null,
          sitExpected: false,
          spouseProGearWeight: 498,
          status: 'WAITING_ON_CUSTOMER',
          submittedAt: null,
          updatedAt: '2022-07-11T18:20:18.752Z',
          weightTickets: [
            {
              id: "1111",
              emptyDocumentId: "2222",
            },
          ],
        },
        shipmentType: 'PPM',
        status: 'APPROVED',
        updatedAt: '2022-07-11T18:20:18.724Z'
      }
    }
  }
}

Verification Steps for Author

These are to be checked by the author.

  • Tested in the Experimental environment (for changes to containers, app startup, or connection to data stores)
  • Request review from a member of a different team.
  • Have the Jira acceptance criteria been met for this change?

Verification Steps for Reviewers

These are to be checked by a reviewer.

Frontend

  • User facing changes have been reviewed by design.
  • There are no aXe warnings for UI.
  • This works in Supported Browsers and their phone views (Chrome, Firefox, IE, Edge).
  • There are no new console errors in the browser devtools
  • There are no new console errors in the test output
  • If this PR adds a new component to Storybook, it ensures the component is fully responsive, OR if it is intentionally not, a wrapping div using the officeApp class or custom min-width styling is used to hide any states the would not be visible to the user.

Screenshots

@robot-mymove
Copy link

robot-mymove commented Jul 8, 2022

Messages
📖 🔗 MB-13125

Generated by 🚫 dangerJS against fceb84e

Comment on lines 38 to 42
if (mtoShipment?.ppmShipment?.weightTickets) {
mtoShipment?.ppmShipment?.weightTickets.push(resp);
} else {
mtoShipment.ppmShipment.weightTickets = [resp];
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic may not be necessary if weightTickets defaults to returning an empty array

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thought: We return an empty array for other things like orders and MTOShipments, so I feel like we should probably remain consistent with that for weight tickets.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll leave this here for now, since we can't ensure that within this PR. But we can likely remove this during the integration story.

const mtoShipment = useSelector((state) => selectMTOShipmentById(state, mtoShipmentId));
// TODO add selector for selecting weight ticket from Redux store when data changes are solidified
const currentWeightTicket = useSelector((state) => selectWeightTicketById(state, mtoShipmentId, weightTicketId));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it may be worth having a selector for getting all weightTickets and then another selector for getting the current weight ticket

Comment on lines 148 to 157
it('displays an error if the createWeightTicket request fails', async () => {
createWeightTicket.mockRejectedValueOnce('an error occurred');

render(<WeightTickets />, { wrapper: MockProviders });

await waitFor(() => {
expect(screen.getByText('Failed to create trip record')).toBeInTheDocument();
});
});

Copy link
Contributor Author

@ronaktruss ronaktruss Jul 11, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this test matches what the user would see. If we were to change the mockRejectedValueOnce to mockRejectedValue you would see that the test would fail. On the UI we wouldn't show the error message because the page would still be in the state where we show the loading screen.

We can likely update the UI to conditionally show the error msg or the loading in this circumstance.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

praise: nice catch!

@ronaktruss ronaktruss changed the title action is not a plain object error MB-13125 - handle state for weightTickets Jul 11, 2022
@ronaktruss ronaktruss marked this pull request as ready for review July 11, 2022 22:10
@@ -120,7 +124,7 @@ const WeightTickets = () => {
trailerMeetsCriteria,
};

patchWeightTicket(mtoShipment.id, weightTicket.id, payload, weightTicket.eTag)
patchWeightTicket(mtoShipment.id, currentWeightTicket.id, payload, currentWeightTicket.eTag)
.then(() => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add in what the updating of the shipment weight ticket would look like?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added. There are quite a few ways to handle this, happy to discuss alternatives.

To test locally relax the UI restrictions(remove yup validation/disabled condition) and ensure that the create and patch call return promises with the same ID.

@duncan-truss
Copy link
Contributor

What is the plan for when the user adds a file and that request completes will you add that to the document uploads array in Redux? How about for the scenario where an upload is deleted?

I think my suggestion would still be to have weight tickets broken out into their own entities in Redux and be fetched separately in an endpoint that returns all weight tickets for a move/shipment instead of having that nested in the shipment response. That way not every fetch or update of a shipment will be required to re-fetch all of the weight tickets/pro-gear/expenses uploads every time. And like we talked about the customer and office apps share a service object and adding on those fetched associations will mean you have a lot of office endpoints that now need to return closeout docs when it's not needed for most functionality.

@ronaktruss
Copy link
Contributor Author

ronaktruss commented Jul 13, 2022

What is the plan for when the user adds a file and that request completes will you add that to the document uploads array in Redux? How about for the scenario where an upload is deleted?

Yeah that's likely the plan so we don't have to make a separate API call on each upload to fetch the latest info. Let me add examples of this.

I think my suggestion would still be to have weight tickets broken out into their own entities in Redux and be fetched separately in an endpoint that returns all weight tickets for a move/shipment instead of having that nested in the shipment response. That way not every fetch or update of a shipment will be required to re-fetch all of the weight tickets/pro-gear/expenses uploads every time. And like we talked about the customer and office apps share a service object and adding on those fetched associations will mean you have a lot of office endpoints that now need to return closeout docs when it's not needed for most functionality.

I think that's a reasonable approach though I have some concerns about the tradeoff between time and performance. Will schedule ~20 min to chat if that works for you.

@ronaktruss
Copy link
Contributor Author

ronaktruss commented Jul 13, 2022

I think my suggestion would still be to have weight tickets broken out into their own entities in Redux and be fetched separately in an endpoint that returns all weight tickets for a move/shipment instead of having that nested in the shipment response. That way not every fetch or update of a shipment will be required to re-fetch all of the weight tickets/pro-gear/expenses uploads every time. And like we talked about the customer and office apps share a service object and adding on those fetched associations will mean you have a lot of office endpoints that now need to return closeout docs when it's not needed for most functionality.

@duncan-truss an I just had a chat about the above. There are two options that were discussed:

  1. Have weight tickets/pro gear/expenses be their own entities
  2. Have these nested within existing entities

Summary

I'm leaning towards going with 2 since imo its likely less time consuming and it provides us a way to get there and iterate as we come across pain points. This approach means that we likely need to make some modifications/work arounds when we get to the office portion.

Additional Context (examples of what each approach may look like, implementation details could change):

  1. Have weight tickets/pro gear/expenses be their own entities

    • Having useEffects on the associated pages to ensure that the correct information is available in state on each page
    • This may means adding 4 endpoints (1 for each of the three entities and 1 for all of them together)
    • Add additional entities to Redux state
    • We will still need to have local redux logic to update the specific index of the entity (ie. Updating the correct weight ticket if we have multiple)
    • We need to have redux logic manipulating the upload information to avoid having to make an additional API call
    • We have a shared handler that can be used by both the office app
  2. Have these nested within existing entities (this is the approach used in this PR)

    • Have more significantly more logic to manipulate redux state (most of it will be abstracted as selectors)
    • Makes components more reliant on state
    • We need to have redux logic manipulating the upload information to avoid having to make an additional API call
    • We will need to have to manage the office app separately (we may need to move the logic of the mto_shipment_fetcher that eagerly loads information into the service or handler so that its not shared by the office)

@felipe-lee
Copy link
Contributor

I think my suggestion would still be to have weight tickets broken out into their own entities in Redux and be fetched separately in an endpoint that returns all weight tickets for a move/shipment instead of having that nested in the shipment response. That way not every fetch or update of a shipment will be required to re-fetch all of the weight tickets/pro-gear/expenses uploads every time. And like we talked about the customer and office apps share a service object and adding on those fetched associations will mean you have a lot of office endpoints that now need to return closeout docs when it's not needed for most functionality.

@duncan-truss an I just had a chat about the above. There are two options that were discussed:

1. Have weight tickets/pro gear/expenses be their own entities

2. Have these nested within existing entities

Summary

I'm leaning towards going with 2 since imo its likely less time consuming and it provides us a way to get there and iterate as we come across pain points. This approach means that we likely need to make some modifications/work arounds when we get to the office portion.

Additional Context (examples of what each approach may look like, implementation details could change):

1. Have weight tickets/pro gear/expenses be their own entities
   
   * Having useEffects on the associated pages to ensure that the correct information is available in state on each page
   * This may means adding 4 endpoints (1 for each of the three entities and 1 for all of them together)
   * Add additional entities to Redux state
   * We will still need to have local redux logic to update the specific index of the entity (ie. Updating the correct weight ticket if we have multiple)
   * We need to have redux logic manipulating the upload information to avoid having to make an additional API call
   * We have a shared handler that can be used by both the office app

2. Have these nested within existing entities (this is the approach used in this PR)
   
   * Have more significantly more logic to manipulate redux state (most of it will be abstracted as selectors)
   * Makes components more reliant on state
   * We need to have redux logic manipulating the upload information to avoid having to make an additional API call
   * We will need to have to manage the office app separately (we may need to move the logic of the mto_shipment_fetcher that eagerly loads information into the service or handler so that its not shared by the office)

I think for the sake of time, I'm leaning toward option 2 as well. I wonder if we do need to add workarounds for the office or if the extra calls will add much since they will only really affect PPMShipments. Like maybe we should check to see how much that actually affects things.

If we chose not to go with option 2, I think another option would be to add the GET method to the /mto-shipments/{mtoShipmentId} endpoint so we can get specific shipments. It's faster to put up a single API endpoint, it's less costly to fetch a single shipment than all shipments, we already have a redux action to update a single shipment, and it would still contain the info we need. The main downside to this is that weight tickets would sometimes be in redux and sometimes not (since the /mto_shipments call wouldn't return the weight tickets). So maybe not a good option 🤷🏼

@@ -96,9 +108,27 @@ const WeightTickets = () => {
};

const handleUploadDelete = (uploadId, fieldName, values, setFieldTouched, setFieldValue) => {
const remainingUploads = values[`${fieldName}`]?.filter((upload) => upload.id !== uploadId);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason that something like values[`${fieldName}`] is used over values[fieldName]?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's usually to avoid eslint detect-object-injection errors because fieldName, if provided by the user or other malicious source, might not be a string eslint-community/eslint-plugin-security#21

@ronaktruss ronaktruss requested a review from a team July 14, 2022 17:20
history.push(generatePath(customerRoutes.SHIPMENT_PPM_REVIEW_PATH, { moveId, mtoShipmentId }));
dispatch(updateMTOShipment(mtoShipment));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if this is reachable if it comes after history.push(), it might need moved before the history call.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I tested this, it seemed to work as expected. I'll double check the behavior during the integration.

Copy link

@scarequotes scarequotes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thumbs up.

@mergify mergify bot merged commit 5c1530f into master Jul 18, 2022
@mergify mergify bot deleted the rp-MB-13125-handle-state-weight-tickets branch July 18, 2022 17:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging this pull request may close these issues.

6 participants