Skip to content

[OBPIH-6627] keep progress when reload on confirm page of full outbound import#4802

Merged
awalkowiak merged 9 commits intofeature/outbound-importfrom
bugfix/OBPIH-6627-keep-progress-when-reload-on-confirm-page-of-full-outbound-import
Aug 27, 2024
Merged

[OBPIH-6627] keep progress when reload on confirm page of full outbound import#4802
awalkowiak merged 9 commits intofeature/outbound-importfrom
bugfix/OBPIH-6627-keep-progress-when-reload-on-confirm-page-of-full-outbound-import

Conversation

@drodzewicz
Copy link
Collaborator

@drodzewicz drodzewicz commented Aug 23, 2024

✨ Description of Change

A concise summary of what is being changed. Please provide enough context for reviewers to be able to understand the change and why it is necessary. If the issue/ticket already provides enough information, you can put "See ticket" as the description.

Link to GitHub issue or Jira ticket: OBPIH-6627

Description:
A requested feature for the Full Outbound Import was to implement an ability for a user to refresh the page and not lose the filled-out form as we have in other workflows.

One big difference between full outbound import and other create stock movement workflows is that in the latter we save the data in the database and upon refreshing the page we full this created Pendning stock movement,but as for full outbound import, we are only saving it at the end so when we refresh the page we lose all the data.

To somehow save this filled-out data I decide to store it in the sessionStorage which is one of the browser storages that we can access.
I decided to use sessionStorage and localStorage because of the one main difference, sessionStorage expires once the tab is closed, unlike localStorage which has to be cleared manually. Since we are working with temporary data that we don't care to save for too long sessionStorage seemed like the approach to take.
To manage sessionStorage I implemented a custom useSessionStorage hook. kind of mimicking the useState

Another big change was a slight modification to the useWizzard hook.
Since we are dealing with page refreshes, we need to know what step we are refreshing, If user opens CONFIRM step and there is cached data we want to render that step with prefilled cached data, and if user opens DETAILS page and there is available cached data we want to assume that user wants to do a new import and we should not load cached data.

So I modified the useWizzard hook so we switch between steps based on the URL query parameter step instead of a value held in state


📷 Screenshots & Recordings (optional)

If this PR contains a UI change, consider adding one or more screenshots here or link to a screen recording to help reviewers visualize the change. Otherwise, you can remove this section.

- No screenshots -


📈 Test Plan

We require that all code changes come paired with a method of testing them. Please select which of the following testing approaches you've included with this change:

  • Frontend automation tests (unit)
  • Backend automation tests (unit, API, smoke)
  • End-to-end tests (Playwright)
  • Manual tests (please describe below)
  • Not Applicable

Description of test plan (if applicable):


✅ Quality Checks

Please confirm and check each of the following to ensure that your change conforms to the coding standards of the project:

  • The pull request title is prefixed with the issue/ticket number (Ex: [OBS-123] for Jira, [#0000] for GitHub, or [OBS-123, OBPIH-123] if there are multiple), or with [N/A] if not applicable
  • The pull request description has enough information for someone without context to be able to understand the change and why it is needed
  • The change has tests that prove the issue is fixed / the feature works as intended

@drodzewicz drodzewicz self-assigned this Aug 23, 2024
@github-actions github-actions bot added type: bug Addresses unintended behaviours of the app domain: frontend Changes or discussions relating to the frontend UI labels Aug 23, 2024
const setValue = (newValue) => sessionStorage.setItem(key, JSON.stringify(newValue));
const clearValue = () => sessionStorage.removeItem(key);

return [val, setValue, clearValue];
Copy link
Collaborator

Choose a reason for hiding this comment

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

wouldn't it be better to return it as object, so we would be able to get rid of the confusing:

const [cachedData,]

it would get even more weird if we wanted to use the first and the third property:

const [cachedData, null, clearValue]

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I modeled this useSessionStorage hook after useState where we destruct the value and setter from an array and accessing nth value like [,,clearFunction] = useSessionStorage() is rather common and is aligned with react conventions.

I don't feel like one solution is better than the other, it's what we prefer, both are correct.

Copy link
Collaborator

Choose a reason for hiding this comment

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

ok, for me it just looks weird when we have a comma without anything after that, but if you feel like it's better, then I'm fine, it was not a change request

if (_.isEmpty(cachedData) && !is(OutboundImportStep.DETAILS)) {
navigateToStep(OutboundImportStep.DETAILS);
}
}, []);
Copy link
Collaborator

Choose a reason for hiding this comment

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

let's move it to a separate small hook named e.g. useCachedData

Copy link
Collaborator

Choose a reason for hiding this comment

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

shouldn't the cachedData (or any property of that, since this is probably an object that needs to be compared properly) be the dependency of the useEffect? Is there a possibility that those would be loaded/recognized after the useEffect is run?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

values from localStorage and sessionStorage are not related to state and will not trigger the component rerender even if you put the in the dependency array.
Also these values are synchronous unlike useEffect which is asynchronous which means that these values will be available in the hook without a delay.

event.preventDefault();
submitMethod(getValues());
};

Copy link
Collaborator

Choose a reason for hiding this comment

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

I kinda do not get this method. Why do we want to skip validation? And which validation exactly? Is it the frontend validation of the file from previous step? Even if so, how do you distinguish here whether the submit happens from the refreshed page or from regular usage of the workflow?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The problem is that I don't cache the uploaded file packingList which is a required value in the validation schema,
So after loading all of the values from sessionStorage I am missing the packingList File which prevents me from submitting the form.

I figured that there are no more fields that we need to fill out on the CONFIRM step and all of the validation should have happened in the previous step so we can assume that the data that we have on confirm step is correct and as a workaround we can skip the validation.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Ok, I supposed this was the case, thanks for explaining

CONFIRM: 'CONFIRM',
};

export default OutboundImportStep;
Copy link
Collaborator

Choose a reason for hiding this comment

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

let's move it to consts directory, where we place all the enums.

* and that cached data is not relevant anymore
* */
if (!_.isEmpty(cachedData)) {
if (OutboundImportStep.CONFIRM === queryParams?.step) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I will probably get to this below, but why do we suddenly need to store the step in the query params, when we should have access to the current step from the useWizard hook?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

since we are dealing with page refreshes I needed know what page are we reloading, if we were to use just state for storing current step it would be cleared after the refresh and we wouldn't know if we need to load the cached data (when we are on CONFIRM step) or not load it when we are for example on DETAILS step

Copy link
Collaborator

Choose a reason for hiding this comment

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

thanks for explaining, I forgot to delete this comment after I figured out below what the case is.

}

return values;
}, [currentLocation]);
Copy link
Collaborator

Choose a reason for hiding this comment

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

shouldn't this include the cachedData and queryParams? (don't know if those are objects or not, so make sure this is compared properly).
Moreover - the dependency should be currentLocation?.id, to properly compare objects.

setTableData([...tableDataGrouped.itemsWithErrors, ...tableDataGrouped.itemsWithoutErrors]);
}
};

Copy link
Collaborator

Choose a reason for hiding this comment

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

thanks for moving that out, I was about to do such cleanups when I am back. I know I might've left a lot of 🍝 with a potential to cleanup, but I focused on the functionality at that time.

const loadCachedData = async () => {
if (!_.isEmpty(cachedData)) {
spinner.show();
setPackingListData(cachedData.packingList);
Copy link
Collaborator

Choose a reason for hiding this comment

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

one of the comments above - why isn't packing list set there, where the rest of the values?

Copy link
Collaborator

Choose a reason for hiding this comment

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

either set all the values here (probably more appropriate, but I am not sure if the react-hook-form would handle that well via setValue) or in the defaultValues, unless I am not aware of some edge cases why you separated those two.

Copy link
Collaborator

Choose a reason for hiding this comment

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

There is one edge case - the packingList that is displayed in the table differs from the one that we want to send to the API. I can see that you store only the one, that is sent to the API, you do not store the tableData.
The difference is that the table data contains fetched e.g. product, so we display the product name in the table, whereas the packingList that is sent to the API, contains only product id and if we were about to use it in the table, we would not display the product name.

};
}, { itemsWithErrors: [], itemsWithoutErrors: [] });

const validatePackingListItems = async (fulfilmentPayload) => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

the name of the method is misleading though - we validate the whole outbound at this point, not only the items.

*/
const currentStepKey = useMemo(() =>
parsedQueryParams.step || initialKey || steps[0]?.key,
[parsedQueryParams.step, steps, initialKey]);
Copy link
Collaborator

Choose a reason for hiding this comment

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

are we sure the changes of the query params are recognized "live" by React?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

we are sure

@awalkowiak awalkowiak merged commit ddf121e into feature/outbound-import Aug 27, 2024
@awalkowiak awalkowiak deleted the bugfix/OBPIH-6627-keep-progress-when-reload-on-confirm-page-of-full-outbound-import branch August 27, 2024 09:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

domain: frontend Changes or discussions relating to the frontend UI type: bug Addresses unintended behaviours of the app

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants