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

CUMULUS-3862: Upgrade React Router to v6 #1148

Open
wants to merge 47 commits into
base: develop
Choose a base branch
from
Open

Conversation

jjmccoy
Copy link
Member

@jjmccoy jjmccoy commented Sep 25, 2024

Summary: React Router from v5 to v6

Addresses CUMULUS-3862: Upgrade React Router from v5 to v6

Changes

PR Checklist

  • Update CHANGELOG
  • Unit tests
  • Adhoc testing
  • Integration tests

@jjmccoy jjmccoy marked this pull request as ready for review September 27, 2024 17:21
@jjmccoy jjmccoy added the Needs Review Looking for a reviewer label Jan 6, 2025
@npauzenga npauzenga added In Review and removed Needs Review Looking for a reviewer labels Feb 13, 2025
Copy link
Contributor

@npauzenga npauzenga left a comment

Choose a reason for hiding this comment

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

@jjmccoy leaving the first part of this review. I've looked at 74/100 files and I want to get something up as it's taking a while to go through everything. Still going through the remaining 26 files and I'll post another review for those.

Also I see there are Cypress tests failing. Are those flukes or likely to require more changes?

@@ -52,14 +52,19 @@
"react/react-in-jsx-scope": 2,
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn",
"eslint-comments/no-unused-disable": "warn",
"eslint-comments/no-unused-disable": "off",
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this just because we don't heed the warnings?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah. Lol

- **CUMULUS-3862**
- Updated React Router v5.2.0 to v6.26.2
- Updated routes and hooks for app navigation
- Established new router with withRouter.js HOC
Copy link
Contributor

Choose a reason for hiding this comment

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

What's HOC?

Copy link
Member Author

Choose a reason for hiding this comment

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

HOC = Higher Order Component. In React, they allow you to reuse component logic. With the upgrade to v6 the dependency 'withRouter.js' and eventually 'history.js' (when Redux ticket is complete) no longer. We need to apply the hooks 'useDispatch', 'useNavigate', etc. that React Router and Redux use now.

CHANGELOG.md Outdated
- Updated routes and hooks for app navigation
- Established new router with withRouter.js HOC
- Established withUrlHelper.js HOC to later replace URL params utility
- Updated unit tests to accommodate component changes
Copy link
Contributor

Choose a reason for hiding this comment

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

Probably don't need to call out test updates explicitly. The CL (in my opinion anyway) should just be used to communicate functional changes to users.

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh okay. I thought since they were dramatically changed to include the React Router hooks but yeah I can remove that.

Copy link
Member Author

Choose a reason for hiding this comment

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

Removed. Changes committed.

@@ -13,5 +13,5 @@ import App from './js/App';
axe.default(React, ReactDOM, 1000);
});
} */

ReactDOM.createRoot(document.getElementById('site-canvas')).render(<App />);
const root = createRoot(document.getElementById('app'));
Copy link
Contributor

Choose a reason for hiding this comment

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

Did we actually change the id from site-canvas to app? Will delete this comment if it becomes clear later.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes. Changed from 'site-canvas' to 'app' to be consistent with React documentation when referencing.

@@ -160,7 +160,7 @@
"deepmerge": "^4.2.2",
"final-form": "^4.20.4",
"global": "^4.3.1",
"history": "^4.10.1",
"history": "^4.7.2",
Copy link
Contributor

Choose a reason for hiding this comment

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

What's the reasoning for downgrading?

Copy link
Member Author

Choose a reason for hiding this comment

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

When I ran '--legacy-peer-deps' for 'connected-react-router' to work with the React and React Router upgrade it downgraded it. The 'history' prop in the Redux store and dependency will be removed in the next ticket when the React upgrade is complete because it will no longer be supported.

import PropTypes from 'prop-types';
import LoadingEllipsis from '../LoadingEllipsis/loading-ellipsis';

const CreateNewReportButton = ({ createReportInflight, getPersistentQueryParams }) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Was this extracted from another component? Or is this new functionality?

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah I see this in list.js. It looks like this is a refined version of what was rendered there previously. The tags have been updated, are there functional/visual changes here?

Copy link
Member Author

Choose a reason for hiding this comment

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

No visual changes just making sure the hooks work properly. Links and buttons for routing work differently because we have to call the React Router and Redux hooks. It's a cleaner way as well to handle React components.

withConflicts = [],
onlyInCumulus = [],
onlyInOrca = []
} = granules;
Copy link
Contributor

Choose a reason for hiding this comment

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

Wouldn't granules always be an empty object here?

</div>
const rulesOps = {
list: rules.list,
enabled: rules.enabled || {},
Copy link
Contributor

Choose a reason for hiding this comment

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

Is an object the correct type to assign if rules.enabled is false? Shouldn't it be false? Same question for the next two...

}
}

Copy link
Contributor

Choose a reason for hiding this comment

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

Another big block that looks like a move. Is there anything changed in here?

Copy link
Member Author

Choose a reason for hiding this comment

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

I had to refactor from class to functional component to implement React Router. No functionality changes just changed to standard. They have to be in a particular call order for React. The useEffect has to go first and all...then useMemo...

search: locationQueryParams.search[path] || getPersistentQueryParams(routeLocation),
})}
to={location}
onClick={(e) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this fix an issue? Was there a conflict here where e.g. clicking the link was doing something unexpected that needed to be prevented?

const [leftScrollButtonVisibility, setLeftScrollButtonVisibility] = useState({ display: 'none', opacity: 0 });
const [rightScrollButtonVisibility, setRightScrollButtonVisibility] = useState({ display: 'none', opacity: 0 });
let sortByState;

Copy link
Contributor

Choose a reason for hiding this comment

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

It seems like there are a lot of changes here unrelated to the React Router update. Can you give a quick overview of the objective here?

Copy link
Member Author

Choose a reason for hiding this comment

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

It's still there. I just put state together and moved the let with the other variables. Some of our code is disorganized and I needed to clean it up to understand where everything is. Sorry for any confusion.

Screenshot 2025-02-18 at 4 30 52 PM

@@ -1,7 +1,6 @@
import React, { useState, useEffect, lazy, Suspense, useCallback, useRef } from 'react';
import React, { useState, useEffect, useMemo, lazy, Suspense, useCallback, useRef } from 'react';
Copy link
Contributor

Choose a reason for hiding this comment

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

Similar question on this file. There seem to be a lot of changes here related to memoization on tables. Was this change related to the Router update (or a knock-on effect of that update)? What drove these changes?

Copy link
Member Author

Choose a reason for hiding this comment

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

Because we have links to routes in our tables I had to be able to use the hooks for React Router then I had to memorize the hooks so the queries wouldn't endlessly loop.

import { Helmet } from 'react-helmet';
import Modal from 'react-bootstrap/Modal';
import PropTypes from 'prop-types';
// import get from 'lodash/get';
Copy link
Contributor

Choose a reason for hiding this comment

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

Reminder to remove commented code

Copy link
Member Author

Choose a reason for hiding this comment

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

Darn! Thought I got them all. So many files. Lol

Copy link
Member Author

Choose a reason for hiding this comment

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

Removed. Changes committed.

let state = {};
try {
state = JSON.parse(decodeURIComponent(stateParam));
console.log('Parsed state:', state);
Copy link
Contributor

Choose a reason for hiding this comment

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

Does the dash have debugging set up? Can we have log stuff like this only if e.g. the user has debugging set to "1"?

Copy link
Member Author

Choose a reason for hiding this comment

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

Not that I know of. I don't see much on the dashboard but we should have it though. I wanted to make sure since this is authentication. I will set a variable for that level.

state = JSON.parse(decodeURIComponent(stateParam));
console.log('Parsed state:', state);
} catch (error) {
console.error('Error parsing state:', error);
Copy link
Contributor

Choose a reason for hiding this comment

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

What would cause an error parsing the state? An invalid param? If that does happen, should the user be presented with an error?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, if the state parameter contains an invalid URL or param. I would think so but that would be another mechanism outside of console debugging.

urlHelper: PropTypes.shape({
location: PropTypes.object,
navigate: PropTypes.func,
// historyPushWithQueryParams: PropTypes.func,
Copy link
Contributor

Choose a reason for hiding this comment

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

Reminder to remove comments

Copy link
Member Author

Choose a reason for hiding this comment

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

Doh! Good catch.

Copy link
Member Author

Choose a reason for hiding this comment

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

Removed. Change committed.

@@ -302,6 +302,7 @@ export const providerLink = (provider) => {
export const bool = (value) => (value ? 'Yes' : 'No');

export const displayCase = (string) => {
if (!string) return nullValue;
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this fix a bug?

Copy link
Member Author

Choose a reason for hiding this comment

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

I would say "yes" and making sure if something comes up undefined or empty as a check for the link. I saw it throughout this file so I made sure it was here too.

@@ -73,9 +60,10 @@ export const tableColumns = [
{
Header: 'MMT',
accessor: 'MMTLink',
Cell: ({ cell: { value } }) => (value ? <a href={value} target="_blank">MMT</a> : null), // eslint-disable-line react/prop-types
Cell: ({ cell: { value } }) => (value ? <a href={value} target="_blank" rel="noopener noreferrer">MMT</a> : null), // eslint-disable-line react/prop-types
Copy link
Contributor

Choose a reason for hiding this comment

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

What does this do? Curious about the behavior changes here.

Copy link
Member Author

Choose a reason for hiding this comment

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

rel="noopener noreferrer" is a security best practice for target="_blank" and just noticed it when I was updating the links for the table.

const collectionId = row.collectionId || row.original?.collectionId;
const granuleId = row.granuleId || row.original?.granuleId;

if (collectionId && granuleId) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this fix issues where collectionId isn't provided?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes

@@ -106,12 +117,16 @@ export const granuleTableColumns = [
{
Header: 'Name',
accessor: 'granuleId',
isLink: true,
Copy link
Contributor

Choose a reason for hiding this comment

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

General question: Was the table link handling changed to support the router update?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes

const pathname = `/reconciliation-reports/report/${encodeURIComponent(value)}`;
const search = getPersistentQueryParams();

if (type === 'Internal' || type === 'Granule Inventory') {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice. Always prefer if to switches when we just have a couple cases. Nice refactor.

return nonEsFields;
};

export const bulkActions = (rules) => [{
Copy link
Contributor

Choose a reason for hiding this comment

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

Why are these removed?

Copy link
Member Author

@jjmccoy jjmccoy Feb 18, 2025

Choose a reason for hiding this comment

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

I couldn't use the React Router hooks in this utility because it wasn't a React component so I moved that code to the Rules overview.js component (similar in another component that was already set up this way previously) and refactored them. Sorry for the alarm.

Screenshot 2025-02-18 at 5 08 11 PM

@@ -0,0 +1,87 @@
/* eslint-disable import/no-cycle */
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 think we need this, do we? There shouldn't be cyclic dependencies if this is a module we're creating and we've only imported third-party modules?

Copy link
Member Author

Choose a reason for hiding this comment

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

I will test again and see.

Copy link
Member Author

Choose a reason for hiding this comment

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

Removed. Tested. Looks good. Change committed.

@@ -60,26 +60,53 @@ describe('Dashboard Home Page', () => {
});

it('displays a compatible Cumulus API Version number', () => {
const apiVersionNumber = 'a.b.c';
const apiVersionNumber = 'a.b.c-d.e';
Copy link
Contributor

Choose a reason for hiding this comment

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

Did we change the way the version number is displayed?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, it was changed in a previous ticket.

});
});

it('displays metrics overview with default 24-hour time range', () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm surprised to find new assertions like this as part of the React router update. Is this related?

Copy link
Member Author

Choose a reason for hiding this comment

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

No, that's not in result of my ticket. There was a previous ticket regarding the time range change for the Metrics Overview. I ran into the same issue with the API version. A previous change to the component that wasn't updated in the test. So...the responsibility is mine now.

@@ -57,20 +57,42 @@ const locationQueryParams = {
search: {}
};

const urlHelper = {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we need to mock the urlHelper? What would be the problem if we imported the actual module?

Copy link
Member Author

Choose a reason for hiding this comment

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

I thought it was the better way to integrate it with what was here since the Collection List component uses urlHelper as a prop.

@@ -16,25 +16,62 @@ import { GranuleOverview } from '../../../app/src/js/components/Granules/granule

const logs = { items: [''] };

const match = { params: { granuleId: 'my-granule-id' } };
// const match = { params: { granuleId: 'my-granule-id' } }; // removed because useParams hook now
Copy link
Contributor

Choose a reason for hiding this comment

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

Reminder to remove comments

Copy link
Member Author

Choose a reason for hiding this comment

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

Removed. Changes committed.

workflowOptions={[]}
/>
</MemoryRouter>
<MemoryRouter initialEntries={['/granules/my-granule-id']}>
Copy link
Contributor

Choose a reason for hiding this comment

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

This looks like something we could store as a variable and use both here and in the previous assertion?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants