diff --git a/ui/apps/platform/package-lock.json b/ui/apps/platform/package-lock.json index 722c3070bf628..f2bd65f4f454b 100644 --- a/ui/apps/platform/package-lock.json +++ b/ui/apps/platform/package-lock.json @@ -23,7 +23,6 @@ "@patternfly/react-user-feedback": "^5.0.0", "axios": "^1.7.7", "computed-style-to-inline-style": "^3.0.0", - "connected-react-router": "^6.9.2", "core-js": "^3.39.0", "d3-axis": "^1.0.12", "d3-brush": "^3.0.0", @@ -38,7 +37,7 @@ "formik": "^2.2.9", "framer-motion": "^10.0.0", "graphql": "^16.8.1", - "history": "^4.9.0", + "history": "^5.3.0", "html2canvas": "1.0.0-rc.7", "initials": "^3.1.2", "js-base64": "^3.7.2", @@ -70,7 +69,7 @@ "react-popper": "0.9.0", "react-redux": "^7.2.6", "react-responsive": "^9.0.2", - "react-router-dom": "^5.3.4", + "react-router-dom": "^6.28.0", "react-router-hash-link": "^2.4.3", "react-select": "^2.0.0", "react-spinners": "^0.10.4", @@ -81,6 +80,7 @@ "react-vis": "^1.12.1", "redoc": "^2.0.0", "redux": "^4.1.2", + "redux-first-history": "^5.2.0", "redux-form": "^8.3.7", "redux-saga": "^0.16.0", "redux-thunk": "^2.4.1", @@ -4061,6 +4061,14 @@ "node": ">=10" } }, + "node_modules/@remix-run/router": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.21.0.tgz", + "integrity": "sha512-xfSkCAchbdG5PnbrKqFWwia4Bi61nH+wm8wLEqfHDyp7Y3dZzgqS2itV8i4gAq9pC2HsTpwyBC6Ds8VHZ96JlA==", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@rollup/plugin-babel": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", @@ -7578,26 +7586,6 @@ "node": ">=0.8" } }, - "node_modules/connected-react-router": { - "version": "6.9.2", - "resolved": "https://registry.npmjs.org/connected-react-router/-/connected-react-router-6.9.2.tgz", - "integrity": "sha1-+J+ofw6Xf8q/F0dftFUuFwzH5I4= sha512-bE8kNBiZv9Mivp7pYn9JvLH5ItTjLl45kk1/Vha0rmAK9I/ETb5JPJrAm0h2KCG9qLfv7vqU3Jo4UUDo0oJnQg==", - "dependencies": { - "lodash.isequalwith": "^4.4.0", - "prop-types": "^15.7.2" - }, - "optionalDependencies": { - "immutable": "^3.8.1 || ^4.0.0", - "seamless-immutable": "^7.1.3" - }, - "peerDependencies": { - "history": "^4.7.2", - "react": "^16.4.0 || ^17.0.0", - "react-redux": "^6.0.0 || ^7.1.0", - "react-router": "^4.3.1 || ^5.0.0", - "redux": "^3.6.0 || ^4.0.0" - } - }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -12231,16 +12219,12 @@ } }, "node_modules/history": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", - "integrity": "sha1-MzcaZeOoOyZ0NOKz87G0xYqtTPM= sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/history/-/history-5.3.0.tgz", + "integrity": "sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.1.2", - "loose-envify": "^1.2.0", - "resolve-pathname": "^3.0.0", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0", - "value-equal": "^1.0.1" + "@babel/runtime": "^7.7.6" } }, "node_modules/hoist-non-react-statics": { @@ -12641,7 +12625,8 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.0.0.tgz", "integrity": "sha1-uG943mre82CDle+yaakUYnl+LCM= sha512-zIE9hX70qew5qTUjSS7wi1iwj/l7+m54KWU247nhM3v806UdGj1yDndXj+IOYxxtW9zyLI+xqFNZjTuDaLUqFw==", - "optional": true + "optional": true, + "peer": true }, "node_modules/import-cwd": { "version": "3.0.0", @@ -13354,7 +13339,8 @@ "node_modules/isarray": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true }, "node_modules/isexe": { "version": "2.0.0", @@ -14911,11 +14897,6 @@ "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" }, - "node_modules/lodash.isequalwith": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.isequalwith/-/lodash.isequalwith-4.4.0.tgz", - "integrity": "sha1-Jmcm3dUo+FTyH06pigZWBuD7xrA=" - }, "node_modules/lodash.ismatch": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz", @@ -16513,15 +16494,6 @@ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha1-+8EUtgykKzDZ2vWFjkvWi77bZzU= sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, - "node_modules/path-to-regexp": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.9.0.tgz", - "integrity": "sha512-xIp7/apCFJuUHdDLWe8O1HIkb0kQrOMb/0u6FXQjemHn/ii5LrIzU6bdECnsiTF/GjZkMEKg1xdiZwNqDYlZ6g==", - "license": "MIT", - "dependencies": { - "isarray": "0.0.1" - } - }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -19040,39 +19012,33 @@ } }, "node_modules/react-router": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.3.4.tgz", - "integrity": "sha1-jKJS1w/MN4QeMUc8ehUc93eIe7U= sha512-Ys9K+ppnJah3QuaRiLxk+jDWOR1MekYQrlytiXxC1RyfbdsZkS5pvKAzCCr031xHixZwpnsYNT5xysdFHQaYsA==", - "dependencies": { - "@babel/runtime": "^7.12.13", - "history": "^4.9.0", - "hoist-non-react-statics": "^3.1.0", - "loose-envify": "^1.3.1", - "path-to-regexp": "^1.7.0", - "prop-types": "^15.6.2", - "react-is": "^16.6.0", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0" + "version": "6.28.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.28.0.tgz", + "integrity": "sha512-HrYdIFqdrnhDw0PqG/AKjAqEqM7AvxCz0DQ4h2W8k6nqmc5uRBYDag0SBxx9iYz5G8gnuNVLzUe13wl9eAsXXg==", + "dependencies": { + "@remix-run/router": "1.21.0" + }, + "engines": { + "node": ">=14.0.0" }, "peerDependencies": { - "react": ">=15" + "react": ">=16.8" } }, "node_modules/react-router-dom": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.3.4.tgz", - "integrity": "sha1-LtYv/YjK5tsTREX0oMCui5HS5eY= sha512-m4EqFMHv/Ih4kpcBCONHbkT68KoAeHN4p3lAGoNryfHi0dMy0kCzEZakiKRsvg5wHZ/JLrLW8o8KomWiz/qbYQ==", + "version": "6.28.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.28.0.tgz", + "integrity": "sha512-kQ7Unsl5YdyOltsPGl31zOjLrDv+m2VcIEcIHqYYD3Lp0UppLjrzcfJqDJwXxFw3TH/yvapbnUvPlAj7Kx5nbg==", "dependencies": { - "@babel/runtime": "^7.12.13", - "history": "^4.9.0", - "loose-envify": "^1.3.1", - "prop-types": "^15.6.2", - "react-router": "5.3.4", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0" + "@remix-run/router": "1.21.0", + "react-router": "6.28.0" + }, + "engines": { + "node": ">=14.0.0" }, "peerDependencies": { - "react": ">=15" + "react": ">=16.8", + "react-dom": ">=16.8" } }, "node_modules/react-router-hash-link": { @@ -19701,6 +19667,15 @@ "@babel/runtime": "^7.9.2" } }, + "node_modules/redux-first-history": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/redux-first-history/-/redux-first-history-5.2.0.tgz", + "integrity": "sha512-7kLqtSXGPZIgvEhl3B+3wRvzePvvZggpVqg+jpR2ZVqu2ESGj9DF6hMHpoEP7bGHqddljjKYjnRmtSetYEiG2Q==", + "peerDependencies": { + "history": "^4.7.2 || ^5.0", + "redux": "^3.6.0 || ^4.0.0 || ^5.0.0" + } + }, "node_modules/redux-form": { "version": "8.3.7", "resolved": "https://registry.npmjs.org/redux-form/-/redux-form-8.3.7.tgz", @@ -20017,11 +19992,6 @@ "node": ">=8" } }, - "node_modules/resolve-pathname": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", - "integrity": "sha1-mdAiJNPPJjaJvsuzk7xWAxMCXc0= sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==" - }, "node_modules/resolve-pkg-maps": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", @@ -20475,12 +20445,6 @@ "integrity": "sha1-VFFFB3xQFJHjOxXsQIwpQ3bpSuQ= sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==", "dev": true }, - "node_modules/seamless-immutable": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/seamless-immutable/-/seamless-immutable-7.1.4.tgz", - "integrity": "sha1-bpU23vCD3cTeoCB9ci4OgNDzcvg= sha512-XiUO1QP4ki4E2PHegiGAlu6r82o5A+6tRh7IkGGTVg/h+UoeX4nFBeCGPOhb4CYjvkqsfm/TUtvOMYC1xmV30A==", - "optional": true - }, "node_modules/select-hose": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", @@ -21876,11 +21840,6 @@ "resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz", "integrity": "sha1-2YDWa8crXVqcqG+3yf/bnImN3QM= sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==" }, - "node_modules/tiny-invariant": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.0.6.tgz", - "integrity": "sha1-s/mziDXjakHIQ6OwkHpaezdV3nM= sha512-FOyLWWVjG+aC0UqG76V53yAWdXfH8bO6FNmyZOuUrzDzK8DI3/JRY25UD7+g49JWM1LXwymsKERB+DzI0dTEQA==" - }, "node_modules/tiny-warning": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", @@ -22567,11 +22526,6 @@ "integrity": "sha1-S1YPZJ/E6RjdCrdc9JYei8iC2Co= sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true }, - "node_modules/value-equal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", - "integrity": "sha1-Hgt5THNMXAyt4XnEN9NW2TGjTWw= sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==" - }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/ui/apps/platform/package.json b/ui/apps/platform/package.json index bb395ba07edad..a0e615f69a77c 100644 --- a/ui/apps/platform/package.json +++ b/ui/apps/platform/package.json @@ -27,7 +27,6 @@ "@patternfly/react-user-feedback": "^5.0.0", "axios": "^1.7.7", "computed-style-to-inline-style": "^3.0.0", - "connected-react-router": "^6.9.2", "core-js": "^3.39.0", "d3-axis": "^1.0.12", "d3-brush": "^3.0.0", @@ -42,7 +41,7 @@ "formik": "^2.2.9", "framer-motion": "^10.0.0", "graphql": "^16.8.1", - "history": "^4.9.0", + "history": "^5.3.0", "html2canvas": "1.0.0-rc.7", "initials": "^3.1.2", "js-base64": "^3.7.2", @@ -74,7 +73,7 @@ "react-popper": "0.9.0", "react-redux": "^7.2.6", "react-responsive": "^9.0.2", - "react-router-dom": "^5.3.4", + "react-router-dom": "^6.28.0", "react-router-hash-link": "^2.4.3", "react-select": "^2.0.0", "react-spinners": "^0.10.4", @@ -85,6 +84,7 @@ "react-vis": "^1.12.1", "redoc": "^2.0.0", "redux": "^4.1.2", + "redux-first-history": "^5.2.0", "redux-form": "^8.3.7", "redux-saga": "^0.16.0", "redux-thunk": "^2.4.1", @@ -182,9 +182,6 @@ "@typescript-eslint/parser": "^8.17.0", "autoprefixer": "10.4.5", "babel-jest": "^29.7.0", - "connected-react-router": { - "react": "^18.0.0" - }, "eslint": "^9.16.0", "eslint-plugin-jest": "^28.9.0", "eslint-plugin-jest-dom": { diff --git a/ui/apps/platform/src/Components/PatternFly/LinkShim/LinkShim.tsx b/ui/apps/platform/src/Components/PatternFly/LinkShim/LinkShim.tsx index 67df37e7d48c3..b68596b5ec74f 100644 --- a/ui/apps/platform/src/Components/PatternFly/LinkShim/LinkShim.tsx +++ b/ui/apps/platform/src/Components/PatternFly/LinkShim/LinkShim.tsx @@ -18,6 +18,9 @@ function LinkShim({ href, ...rest }: AnchorHTMLAttributes): ReactElement { + if (!href) { + return <>; + } return ( {children} diff --git a/ui/apps/platform/src/Components/RelatedEntity.js b/ui/apps/platform/src/Components/RelatedEntity.js index ae542ba022b44..fa7630b01c53b 100644 --- a/ui/apps/platform/src/Components/RelatedEntity.js +++ b/ui/apps/platform/src/Components/RelatedEntity.js @@ -1,19 +1,20 @@ import React, { useContext } from 'react'; import PropTypes from 'prop-types'; -import { useHistory, useLocation, useRouteMatch } from 'react-router-dom'; +import { useLocation, useNavigate } from 'react-router-dom'; import Widget from 'Components/Widget'; import EntityIcon from 'Components/EntityIcon'; import { newWorkflowCases } from 'constants/useCaseTypes'; import workflowStateContext from 'Containers/workflowStateContext'; +import useWorkflowMatch from 'hooks/useWorkflowMatch'; import hexagonal from 'images/side-panel-icons/hexagonal.svg'; import URLService from 'utils/URLService'; // @TODO We should try to use this component for Compliance as well const RelatedEntity = ({ name, entityType, entityId, value, ...rest }) => { - const history = useHistory(); + const navigate = useNavigate(); const location = useLocation(); - const match = useRouteMatch(); + const match = useWorkflowMatch(); const workflowState = useContext(workflowStateContext); function onClick() { @@ -28,7 +29,7 @@ const RelatedEntity = ({ name, entityType, entityId, value, ...rest }) => { } else { url = URLService.getURL(match, location).push(entityType, entityId).url(); } - history.push(url); + navigate(url); } const content = ( diff --git a/ui/apps/platform/src/Components/RelatedEntityListCount.js b/ui/apps/platform/src/Components/RelatedEntityListCount.js index a0f0859b37fe5..270122d4833c2 100644 --- a/ui/apps/platform/src/Components/RelatedEntityListCount.js +++ b/ui/apps/platform/src/Components/RelatedEntityListCount.js @@ -1,17 +1,18 @@ import React, { useContext } from 'react'; -import { useHistory, useLocation, useRouteMatch } from 'react-router-dom'; +import { useLocation, useNavigate } from 'react-router-dom'; import PropTypes from 'prop-types'; import Widget from 'Components/Widget'; import { newWorkflowCases } from 'constants/useCaseTypes'; import workflowStateContext from 'Containers/workflowStateContext'; +import useWorkflowMatch from 'hooks/useWorkflowMatch'; import URLService from 'utils/URLService'; // @TODO We should try to use this component for Compliance as well const RelatedEntityListCount = ({ name, value, entityType, ...rest }) => { - const history = useHistory(); + const navigate = useNavigate(); const location = useLocation(); - const match = useRouteMatch(); + const match = useWorkflowMatch(); const workflowState = useContext(workflowStateContext); function onClick() { @@ -23,7 +24,7 @@ const RelatedEntityListCount = ({ name, value, entityType, ...rest }) => { } else { url = URLService.getURL(match, location).push(entityType).url(); } - history.push(url); + navigate(url); } const content =
{value}
; diff --git a/ui/apps/platform/src/Components/URLSearchInputWithAutocomplete.js b/ui/apps/platform/src/Components/URLSearchInputWithAutocomplete.js index 5127ec0493a1a..a43f46d297f66 100644 --- a/ui/apps/platform/src/Components/URLSearchInputWithAutocomplete.js +++ b/ui/apps/platform/src/Components/URLSearchInputWithAutocomplete.js @@ -1,5 +1,5 @@ import React, { useContext } from 'react'; -import { useHistory, useLocation } from 'react-router-dom'; +import { useLocation, useNavigate } from 'react-router-dom'; import PropTypes from 'prop-types'; import { components } from 'react-select'; import queryString from 'qs'; @@ -111,7 +111,7 @@ const URLSearchInputWithAutocomplete = ({ ...rest }) => { const location = useLocation(); - const history = useHistory(); + const navigate = useNavigate(); const searchParam = useContext(searchContext); const workflowState = useContext(workflowStateContext); @@ -227,10 +227,13 @@ const URLSearchInputWithAutocomplete = ({ function replaceLocationSearch(searchOptions) { const { pathname } = location; const search = transformSearchOptionsToQueryString(searchOptions); - history.replace({ - pathname, - search, - }); + navigate( + { + pathname, + search, + }, + { replace: true } + ); } function updateAutocompleteState(searchOptions) { diff --git a/ui/apps/platform/src/Containers/AccessControl/AccessControl.tsx b/ui/apps/platform/src/Containers/AccessControl/AccessControl.tsx index 1cdfb91c118b5..c9edcac5dc5f6 100644 --- a/ui/apps/platform/src/Containers/AccessControl/AccessControl.tsx +++ b/ui/apps/platform/src/Containers/AccessControl/AccessControl.tsx @@ -1,10 +1,7 @@ import React, { ReactElement } from 'react'; -import { Redirect, Route, Switch } from 'react-router-dom'; - -import { accessControlBasePath, accessControlPath } from 'routePaths'; - -import { getEntityPath } from './accessControlPaths'; +import { Navigate, Route, Routes } from 'react-router-dom'; +import { entityPathSegment } from './accessControlPaths'; import AccessControlRouteNotFound from './AccessControlRouteNotFound'; import AccessScopes from './AccessScopes/AccessScopes'; import AuthProviders from './AuthProviders/AuthProviders'; @@ -16,32 +13,23 @@ const paramId = ':entityId?'; function AccessControl(): ReactElement { return ( <> - + + } /> + } + /> + } /> + } + /> } + path={`${entityPathSegment.ACCESS_SCOPE}/${paramId}`} + element={} /> - - - - - - - - - - - - - - - - - - - - + } /> + ); } diff --git a/ui/apps/platform/src/Containers/AccessControl/AccessScopes/AccessScopes.tsx b/ui/apps/platform/src/Containers/AccessControl/AccessScopes/AccessScopes.tsx index 4239ba381c162..85abdea520846 100644 --- a/ui/apps/platform/src/Containers/AccessControl/AccessScopes/AccessScopes.tsx +++ b/ui/apps/platform/src/Containers/AccessControl/AccessScopes/AccessScopes.tsx @@ -1,6 +1,6 @@ /* eslint-disable no-nested-ternary */ import React, { ReactElement, useEffect, useState } from 'react'; -import { useHistory, useLocation, useParams } from 'react-router-dom'; +import { useLocation, useNavigate, useParams } from 'react-router-dom'; import { Alert, AlertActionCloseButton, @@ -40,7 +40,7 @@ const entityType = 'ACCESS_SCOPE'; function AccessScopes(): ReactElement { const { hasReadWriteAccess } = usePermissions(); const hasWriteAccessForPage = hasReadWriteAccess('Access'); - const history = useHistory(); + const navigate = useNavigate(); const { search } = useLocation(); const queryObject = getQueryObject(search); const { action } = queryObject; @@ -106,7 +106,7 @@ function AccessScopes(): ReactElement { }, []); function handleCreate() { - history.push(getEntityPath(entityType, undefined, { action: 'create' })); + navigate(getEntityPath(entityType, undefined, { action: 'create' })); } function handleDelete(idDelete: string) { @@ -117,12 +117,12 @@ function AccessScopes(): ReactElement { } function handleEdit() { - history.push(getEntityPath(entityType, entityId, { action: 'edit' })); + navigate(getEntityPath(entityType, entityId, { action: 'edit' })); } function handleCancel() { // Go back from action=create to list or go back from action=update to entity. - history.goBack(); + navigate(-1); } function handleSubmit(values: AccessScope): Promise { @@ -132,7 +132,7 @@ function AccessScopes(): ReactElement { setAccessScopes([...accessScopes, entityCreated]); // Go back from action=create to list. - history.goBack(); + navigate(-1); return null; // because the form has only catch and finally }) @@ -143,7 +143,7 @@ function AccessScopes(): ReactElement { ); // Replace path which had action=update with plain entity path. - history.replace(getEntityPath(entityType, entityId)); + navigate(getEntityPath(entityType, entityId), { replace: true }); return null; // because the form has only catch and finally }); diff --git a/ui/apps/platform/src/Containers/AccessControl/AuthProviders/AuthProviderForm.tsx b/ui/apps/platform/src/Containers/AccessControl/AuthProviders/AuthProviderForm.tsx index c8c67a46e25b4..a7a0a212410e2 100644 --- a/ui/apps/platform/src/Containers/AccessControl/AuthProviders/AuthProviderForm.tsx +++ b/ui/apps/platform/src/Containers/AccessControl/AuthProviders/AuthProviderForm.tsx @@ -1,6 +1,6 @@ /* eslint-disable react/no-array-index-key */ import React, { ReactElement } from 'react'; -import { useHistory, Link } from 'react-router-dom'; +import { Link, useNavigate } from 'react-router-dom'; import { useSelector, useDispatch } from 'react-redux'; import { createStructuredSelector } from 'reselect'; import { useFormik, FormikProvider, FieldArray } from 'formik'; @@ -33,8 +33,14 @@ import { InfoCircleIcon, PlusCircleIcon, TrashIcon } from '@patternfly/react-ico import SelectSingle from 'Components/SelectSingle'; // TODO import from where? import { selectors } from 'reducers'; import { actions as authActions } from 'reducers/auth'; +import { Role } from 'services/RolesService'; +import { + AuthProvider, + AuthProviderInfo, + getIsAuthProviderImmutable, + Group, +} from 'services/AuthService'; -import { AuthProvider, getIsAuthProviderImmutable } from 'services/AuthService'; import ConfigurationFormFields from './ConfigurationFormFields'; import RuleGroups, { RuleGroupErrors } from './RuleGroups'; import { @@ -57,7 +63,14 @@ export type AuthProviderFormProps = { onClickEdit: () => void; }; -const authProviderState = createStructuredSelector({ +type AuthProviderState = { + roles: Role[]; + groups: Group[]; + saveAuthProviderStatus: { status: string; message: string } | null; + availableProviderTypes: AuthProviderInfo[]; +}; + +const authProviderState = createStructuredSelector({ roles: selectors.getRoles, groups: selectors.getRuleGroups, saveAuthProviderStatus: selectors.getSaveAuthProviderStatus, @@ -93,7 +106,7 @@ function AuthProviderForm({ onClickCancel, onClickEdit, }: AuthProviderFormProps): ReactElement { - const history = useHistory(); + const navigate = useNavigate(); const { groups, roles, saveAuthProviderStatus, availableProviderTypes } = useSelector(authProviderState); const dispatch = useDispatch(); @@ -268,7 +281,7 @@ function AuthProviderForm({ dispatch(authActions.setSaveAuthProviderStatus(null)); // Go back from action=create to list. - history.goBack(); + navigate(-1); } const isSaving = saveAuthProviderStatus?.status === 'saving'; diff --git a/ui/apps/platform/src/Containers/AccessControl/AuthProviders/AuthProviders.tsx b/ui/apps/platform/src/Containers/AccessControl/AuthProviders/AuthProviders.tsx index f4175608d0c67..1507b8adac24d 100644 --- a/ui/apps/platform/src/Containers/AccessControl/AuthProviders/AuthProviders.tsx +++ b/ui/apps/platform/src/Containers/AccessControl/AuthProviders/AuthProviders.tsx @@ -3,7 +3,7 @@ import React, { ReactElement, useEffect, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { createStructuredSelector } from 'reselect'; import { selectors } from 'reducers'; -import { useHistory, useLocation, useParams, Link } from 'react-router-dom'; +import { useLocation, useNavigate, useParams, Link } from 'react-router-dom'; import ExternalLink from 'Components/PatternFly/IconText/ExternalLink'; import { Alert, @@ -31,7 +31,7 @@ import { actions as authActions, types as authActionTypes } from 'reducers/auth' import { actions as groupActions } from 'reducers/groups'; import { actions as inviteActions } from 'reducers/invite'; import { actions as roleActions, types as roleActionTypes } from 'reducers/roles'; -import { AuthProvider } from 'services/AuthService'; +import { AuthProvider, AuthProviderInfo, Group } from 'services/AuthService'; import usePermissions from 'hooks/usePermissions'; import { integrationsPath } from 'routePaths'; import { getVersionedDocs } from 'utils/versioning'; @@ -57,14 +57,21 @@ const authProviderNew = { config: {}, } as AuthProvider; // TODO what are the minimum properties for create request? -const authProviderState = createStructuredSelector({ +type AuthProviderState = { + authProviders: AuthProvider[]; + groups: Group[]; + isFetchingAuthProviders: boolean; + isFetchingRoles: boolean; + availableProviderTypes: AuthProviderInfo[]; +}; + +const authProviderState = createStructuredSelector({ authProviders: selectors.getAvailableAuthProviders, groups: selectors.getRuleGroups, isFetchingAuthProviders: (state) => selectors.getLoadingStatus(state, authActionTypes.FETCH_AUTH_PROVIDERS) as boolean, isFetchingRoles: (state) => selectors.getLoadingStatus(state, roleActionTypes.FETCH_ROLES) as boolean, - userRolePermissions: selectors.getUserRolePermissions, availableProviderTypes: selectors.getAvailableProviderTypes, }); @@ -75,7 +82,7 @@ function getNewAuthProviderObj(type) { function AuthProviders(): ReactElement { const { hasReadWriteAccess } = usePermissions(); const hasWriteAccessForPage = hasReadWriteAccess('Access'); - const history = useHistory(); + const navigate = useNavigate(); const { search } = useLocation(); const queryObject = getQueryObject(search); const { action, type } = queryObject; @@ -116,7 +123,7 @@ function AuthProviders(): ReactElement { function onClickCreate(event) { setIsCreateMenuOpen(false); - history.push( + navigate( getEntityPath(entityType, undefined, { ...queryObject, action: 'create', @@ -126,19 +133,20 @@ function AuthProviders(): ReactElement { } function onClickEdit() { - history.push(getEntityPath(entityType, entityId, { ...queryObject, action: 'edit' })); + navigate(getEntityPath(entityType, entityId, { ...queryObject, action: 'edit' })); } function onClickCancel() { dispatch(authActions.setSaveAuthProviderStatus(null)); // The entityId is undefined for create and defined for update. - history.push(getEntityPath(entityType, entityId, { ...queryObject, action: undefined })); + navigate(getEntityPath(entityType, entityId, { ...queryObject, action: undefined })); } function getProviderLabel(): string { - const provider = availableProviderTypes.find(({ value }) => value === type) ?? {}; - return (provider.label as string) ?? 'auth'; + const provider: Partial = + availableProviderTypes.find(({ value }) => value === type) ?? {}; + return provider.label ?? 'auth'; } const onToggle = (_isExpanded: boolean) => { diff --git a/ui/apps/platform/src/Containers/AccessControl/AuthProviders/AuthProvidersList.tsx b/ui/apps/platform/src/Containers/AccessControl/AuthProviders/AuthProvidersList.tsx index 2f79033ff58fe..3d1dd6229748f 100644 --- a/ui/apps/platform/src/Containers/AccessControl/AuthProviders/AuthProvidersList.tsx +++ b/ui/apps/platform/src/Containers/AccessControl/AuthProviders/AuthProvidersList.tsx @@ -7,7 +7,12 @@ import { ActionsColumn, Table, Tbody, Td, Thead, Th, Tr } from '@patternfly/reac import { selectors } from 'reducers'; import { actions as authActions } from 'reducers/auth'; -import { AuthProvider, AuthProviderInfo, getIsAuthProviderImmutable } from 'services/AuthService'; +import { + AuthProvider, + AuthProviderInfo, + AuthStatus, + getIsAuthProviderImmutable, +} from 'services/AuthService'; import { AccessControlEntityLink } from '../AccessControlLinks'; import { getOriginLabel } from '../traits'; @@ -29,7 +34,12 @@ export type AuthProvidersListProps = { authProviders: AuthProvider[]; }; -const authProviderState = createStructuredSelector({ +type AuthProviderState = { + currentUser: AuthStatus; + availableProviderTypes: AuthProviderInfo[]; +}; + +const authProviderState = createStructuredSelector({ currentUser: selectors.getCurrentUser, availableProviderTypes: selectors.getAvailableProviderTypes, }); diff --git a/ui/apps/platform/src/Containers/AccessControl/PermissionSets/PermissionSets.tsx b/ui/apps/platform/src/Containers/AccessControl/PermissionSets/PermissionSets.tsx index 8cc5e0f8ff1a2..fff713caa0f76 100644 --- a/ui/apps/platform/src/Containers/AccessControl/PermissionSets/PermissionSets.tsx +++ b/ui/apps/platform/src/Containers/AccessControl/PermissionSets/PermissionSets.tsx @@ -1,6 +1,6 @@ /* eslint-disable no-nested-ternary */ import React, { ReactElement, useEffect, useState } from 'react'; -import { useHistory, useLocation, useParams } from 'react-router-dom'; +import { useLocation, useNavigate, useParams } from 'react-router-dom'; import { Alert, AlertActionCloseButton, @@ -41,7 +41,7 @@ const entityType = 'PERMISSION_SET'; function PermissionSets(): ReactElement { const { hasReadWriteAccess } = usePermissions(); const hasWriteAccessForPage = hasReadWriteAccess('Access'); - const history = useHistory(); + const navigate = useNavigate(); const { search } = useLocation(); const queryObject = getQueryObject(search); const { action } = queryObject; @@ -134,7 +134,7 @@ function PermissionSets(): ReactElement { }, []); function handleCreate() { - history.push(getEntityPath(entityType, undefined, { action: 'create' })); + navigate(getEntityPath(entityType, undefined, { action: 'create' })); } function handleDelete(idDelete: string) { @@ -145,12 +145,12 @@ function PermissionSets(): ReactElement { } function handleEdit() { - history.push(getEntityPath(entityType, entityId, { action: 'edit' })); + navigate(getEntityPath(entityType, entityId, { action: 'edit' })); } function handleCancel() { // Go back from action=create to list or go back from action=update to entity. - history.goBack(); + navigate(-1); } function handleSubmit(values: PermissionSet): Promise { @@ -160,7 +160,7 @@ function PermissionSets(): ReactElement { setPermissionSets([...permissionSets, entityCreated]); // Go back from action=create to list. - history.goBack(); + navigate(-1); return null; // because the form has only catch and finally }) @@ -171,7 +171,7 @@ function PermissionSets(): ReactElement { ); // Replace path which had action=update with plain entity path. - history.replace(getEntityPath(entityType, entityId)); + navigate(getEntityPath(entityType, entityId), { replace: true }); return null; // because the form has only catch and finally }); diff --git a/ui/apps/platform/src/Containers/AccessControl/Roles/Roles.tsx b/ui/apps/platform/src/Containers/AccessControl/Roles/Roles.tsx index 83dcae4162ef5..061620a7640bc 100644 --- a/ui/apps/platform/src/Containers/AccessControl/Roles/Roles.tsx +++ b/ui/apps/platform/src/Containers/AccessControl/Roles/Roles.tsx @@ -1,6 +1,6 @@ /* eslint-disable no-nested-ternary */ import React, { ReactElement, useEffect, useState } from 'react'; -import { useHistory, useLocation, useParams } from 'react-router-dom'; +import { useLocation, useNavigate, useParams } from 'react-router-dom'; import { Alert, AlertActionCloseButton, @@ -46,7 +46,7 @@ const entityType = 'ROLE'; function Roles(): ReactElement { const { hasReadWriteAccess } = usePermissions(); const hasWriteAccessForPage = hasReadWriteAccess('Access'); - const history = useHistory(); + const navigate = useNavigate(); const { search } = useLocation(); const queryObject = getQueryObject(search); const { action, s } = queryObject; @@ -188,7 +188,7 @@ function Roles(): ReactElement { }, []); function handleCreate() { - history.push(getEntityPath(entityType, undefined, { action: 'create' })); + navigate(getEntityPath(entityType, undefined, { action: 'create' })); } function handleDelete(nameDelete: string) { @@ -199,12 +199,12 @@ function Roles(): ReactElement { } function handleEdit() { - history.push(getEntityPath(entityType, entityName, { action: 'edit' })); + navigate(getEntityPath(entityType, entityName, { action: 'edit' })); } function handleCancel() { // Go back from action=create to list or go back from action=update to entity. - history.goBack(); + navigate(-1); } function handleSubmit(values: Role): Promise { @@ -214,7 +214,7 @@ function Roles(): ReactElement { setRoles([...roles, values]); // Go back from action=create to list. - history.goBack(); + navigate(-1); return null; // because the form has only catch and finally }) @@ -223,7 +223,7 @@ function Roles(): ReactElement { setRoles(roles.map((entity) => (entity.name === values.name ? values : entity))); // Replace path which had action=update with plain entity path. - history.replace(getEntityPath(entityType, entityName)); + navigate(getEntityPath(entityType, entityName), { replace: true }); return null; // because the form has only catch and finally }); diff --git a/ui/apps/platform/src/Containers/AccessControl/accessControlPaths.ts b/ui/apps/platform/src/Containers/AccessControl/accessControlPaths.ts index 23f5078262e54..3bd078d9d44d4 100644 --- a/ui/apps/platform/src/Containers/AccessControl/accessControlPaths.ts +++ b/ui/apps/platform/src/Containers/AccessControl/accessControlPaths.ts @@ -1,4 +1,3 @@ -import { History } from 'react-router-dom'; import qs from 'qs'; import { accessControlBasePath } from 'routePaths'; @@ -15,12 +14,6 @@ export type AccessControlQueryObject = { type?: 'auth0' | 'odic' | 'saml' | 'userpki' | 'iap'; }; -export type AccessControlContainerProps = { - entityId: string; - history: History; - queryObject: AccessControlQueryObject; -}; - export const entityPathSegment: Record = { AUTH_PROVIDER: 'auth-providers', ROLE: 'roles', diff --git a/ui/apps/platform/src/Containers/AppPage.tsx b/ui/apps/platform/src/Containers/AppPage.tsx index 975a0a3c9e482..d99a998f41d7c 100644 --- a/ui/apps/platform/src/Containers/AppPage.tsx +++ b/ui/apps/platform/src/Containers/AppPage.tsx @@ -1,5 +1,5 @@ import React, { ReactElement } from 'react'; -import { Route, Switch } from 'react-router-dom'; +import { Route, Routes } from 'react-router-dom'; import { loginPath, @@ -19,23 +19,13 @@ function AppPage(): ReactElement { <> - - - - - - - - - - - - - - - - - + + } /> + } /> + } /> + } /> + } /> + ); } diff --git a/ui/apps/platform/src/Containers/AppPageTitle.tsx b/ui/apps/platform/src/Containers/AppPageTitle.tsx index 8385737b128af..6c9e9118550b7 100644 --- a/ui/apps/platform/src/Containers/AppPageTitle.tsx +++ b/ui/apps/platform/src/Containers/AppPageTitle.tsx @@ -1,5 +1,5 @@ import React, { ReactElement } from 'react'; -import { useLocation } from 'react-router-dom'; +import { Location, useLocation } from 'react-router-dom'; import capitalize from 'lodash/capitalize'; import { basePathToLabelMap } from 'routePaths'; @@ -9,10 +9,6 @@ import useCaseLabels from 'messages/useCase'; import PageTitle from 'Components/PageTitle'; -type Location = { - pathname: string; -}; - const getTitleFromWorkflowState = (workflowState): string => { const useCase = useCaseLabels[workflowState.getUseCase()]; const baseEntityType = resourceLabels[workflowState.getBaseEntityType()]; diff --git a/ui/apps/platform/src/Containers/Clusters/ClusterPage.tsx b/ui/apps/platform/src/Containers/Clusters/ClusterPage.tsx index 194f8d459fe71..eb33272f89a36 100644 --- a/ui/apps/platform/src/Containers/Clusters/ClusterPage.tsx +++ b/ui/apps/platform/src/Containers/Clusters/ClusterPage.tsx @@ -1,5 +1,5 @@ import React, { ReactElement, useEffect, useState } from 'react'; -import { useHistory } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import { Alert, Button, Flex, FlexItem } from '@patternfly/react-core'; import cloneDeep from 'lodash/cloneDeep'; import get from 'lodash/get'; @@ -59,7 +59,7 @@ export type ClusterPageProps = { }; function ClusterPage({ clusterId }: ClusterPageProps): ReactElement { - const history = useHistory(); + const navigate = useNavigate(); const { hasReadWriteAccess } = usePermissions(); const hasWriteAccessForCluster = hasReadWriteAccess('Cluster'); @@ -239,7 +239,7 @@ function ClusterPage({ clusterId }: ClusterPageProps): ReactElement { }); }); } else { - history.push(clustersBasePath); + navigate(clustersBasePath); } } diff --git a/ui/apps/platform/src/Containers/Clusters/ClustersPage.tsx b/ui/apps/platform/src/Containers/Clusters/ClustersPage.tsx index a9ac300264399..271d8e21d5b75 100644 --- a/ui/apps/platform/src/Containers/Clusters/ClustersPage.tsx +++ b/ui/apps/platform/src/Containers/Clusters/ClustersPage.tsx @@ -9,7 +9,7 @@ import ClustersTablePanel from './ClustersTablePanel'; import ClusterPage from './ClusterPage'; function ClustersPage(): ReactElement { - const { clusterId } = useParams(); // see routePaths for parameter + const { clusterId } = useParams() as { clusterId: string }; // see routePaths for parameter const searchQueryOptions = { variables: { diff --git a/ui/apps/platform/src/Containers/Clusters/ClustersTablePanel.tsx b/ui/apps/platform/src/Containers/Clusters/ClustersTablePanel.tsx index 80e75749a49b4..42284396ae2bf 100644 --- a/ui/apps/platform/src/Containers/Clusters/ClustersTablePanel.tsx +++ b/ui/apps/platform/src/Containers/Clusters/ClustersTablePanel.tsx @@ -1,5 +1,5 @@ import React, { ReactElement, useState, useReducer } from 'react'; -import { Link, useHistory } from 'react-router-dom'; +import { Link, useNavigate } from 'react-router-dom'; import useDeepCompareEffect from 'use-deep-compare-effect'; import { Alert, @@ -73,7 +73,7 @@ function ClustersTablePanel({ searchOptions, }: ClustersTablePanelProps): ReactElement { const { analyticsTrack } = useAnalytics(); - const history = useHistory(); + const navigate = useNavigate(); const { hasReadAccess, hasReadWriteAccess } = usePermissions(); const hasReadAccessForAdministration = hasReadAccess('Administration'); @@ -244,7 +244,7 @@ function ClustersTablePanel({ } function setSelectedClusterId(cluster: Cluster) { - history.push(`${clustersBasePath}/${cluster.id}`); + navigate(`${clustersBasePath}/${cluster.id}`); } function upgradeSingleCluster(id) { diff --git a/ui/apps/platform/src/Containers/Clusters/InitBundles/InitBundleForm.tsx b/ui/apps/platform/src/Containers/Clusters/InitBundles/InitBundleForm.tsx index d20993a6bbada..5a55e5d0673f0 100644 --- a/ui/apps/platform/src/Containers/Clusters/InitBundles/InitBundleForm.tsx +++ b/ui/apps/platform/src/Containers/Clusters/InitBundles/InitBundleForm.tsx @@ -1,5 +1,5 @@ import React, { ReactElement, useState } from 'react'; -import { useHistory } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import { ActionGroup, Alert, @@ -65,7 +65,7 @@ const validationSchema: yup.ObjectSchema = yup.object().sh function InitBundleForm(): ReactElement { const { analyticsTrack } = useAnalytics(); - const history = useHistory(); + const navigate = useNavigate(); const [errorMessage, setErrorMessage] = useState(''); const { errors, @@ -100,7 +100,7 @@ function InitBundleForm(): ReactElement { const { isOpen, onToggle } = useSelectToggle(); function goBack() { - history.goBack(); // to InputBundlesTable or NoClustersPage + navigate(-1); // to InputBundlesTable or NoClustersPage } // return setWhatever solves problem reported by typescript-eslint no-floating-promises diff --git a/ui/apps/platform/src/Containers/Clusters/InitBundles/InitBundlePage.tsx b/ui/apps/platform/src/Containers/Clusters/InitBundles/InitBundlePage.tsx index 4bb2bca3fdd66..3a1dc36b94b19 100644 --- a/ui/apps/platform/src/Containers/Clusters/InitBundles/InitBundlePage.tsx +++ b/ui/apps/platform/src/Containers/Clusters/InitBundles/InitBundlePage.tsx @@ -1,5 +1,5 @@ import React, { ReactElement, useState } from 'react'; -import { useHistory } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import { Alert, Bullseye, Button, PageSection, Spinner } from '@patternfly/react-core'; import useRestQuery from 'hooks/useRestQuery'; @@ -16,7 +16,7 @@ export type InitBundlePageProps = { }; function InitBundlePage({ hasWriteAccessForInitBundles, id }: InitBundlePageProps): ReactElement { - const history = useHistory(); + const navigate = useNavigate(); const [isRevoking, setIsRevoking] = useState(false); const { @@ -36,7 +36,7 @@ function InitBundlePage({ hasWriteAccessForInitBundles, id }: InitBundlePageProp function onCloseModal(wasRevoked: boolean) { setIsRevoking(false); if (wasRevoked) { - history.goBack(); // to table + navigate(-1); // to table } } diff --git a/ui/apps/platform/src/Containers/Collections/CollectionsFormPage.tsx b/ui/apps/platform/src/Containers/Collections/CollectionsFormPage.tsx index 01e5967a5f0ac..9472fa6274b2b 100644 --- a/ui/apps/platform/src/Containers/Collections/CollectionsFormPage.tsx +++ b/ui/apps/platform/src/Containers/Collections/CollectionsFormPage.tsx @@ -1,5 +1,5 @@ import React, { ReactElement, useRef, useState } from 'react'; -import { useHistory } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import { Alert, AlertActionCloseButton, @@ -65,7 +65,7 @@ function CollectionsFormPage({ hasWriteAccessForCollections, pageAction, }: CollectionsFormPageProps) { - const history = useHistory(); + const navigate = useNavigate(); const isXLargeScreen = useMediaQuery({ query: '(min-width: 1200px)' }); // --pf-v5-global--breakpoint--xl const collectionId = pageAction.type !== 'create' ? pageAction.collectionId : undefined; @@ -96,17 +96,11 @@ function CollectionsFormPage({ } = useSelectToggle(isXLargeScreen); function onEditCollection(id: string) { - history.push({ - pathname: `${collectionsBasePath}/${id}`, - search: 'action=edit', - }); + navigate(`${collectionsBasePath}/${id}?action=edit`); } function onCloneCollection(id: string) { - history.push({ - pathname: `${collectionsBasePath}/${id}`, - search: 'action=clone', - }); + navigate(`${collectionsBasePath}/${id}?action=clone`); } function onConfirmDeleteCollection() { @@ -115,7 +109,7 @@ function CollectionsFormPage({ } setIsDeleting(true); deleteCollection(deleteId) - .request.then(history.goBack) + .request.then(() => navigate(-1)) .catch((err) => { const message = getAxiosErrorMessage(err); addToast( @@ -210,7 +204,7 @@ function CollectionsFormPage({ onSubmit={(collection) => onSubmit(collection) .then(() => { - history.push({ pathname: `${collectionsBasePath}` }); + navigate(`${collectionsBasePath}`); if (pageAction.type === 'create') { analyticsTrack({ event: COLLECTION_CREATED, @@ -224,7 +218,7 @@ function CollectionsFormPage({ }) } onCancel={() => { - history.push({ pathname: `${collectionsBasePath}` }); + navigate(`${collectionsBasePath}`); }} configError={configError} setConfigError={setConfigError} diff --git a/ui/apps/platform/src/Containers/Collections/CollectionsTable.tsx b/ui/apps/platform/src/Containers/Collections/CollectionsTable.tsx index 423013e20df16..c901f0cf05682 100644 --- a/ui/apps/platform/src/Containers/Collections/CollectionsTable.tsx +++ b/ui/apps/platform/src/Containers/Collections/CollectionsTable.tsx @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import { Link, useHistory } from 'react-router-dom'; +import { Link, useNavigate } from 'react-router-dom'; import { Button, Pagination, @@ -46,7 +46,7 @@ function CollectionsTable({ onCollectionDelete, hasWriteAccess, }: CollectionsTableProps) { - const history = useHistory(); + const navigate = useNavigate(); const { page, perPage, setPage, setPerPage } = pagination; const [isDeleting, setIsDeleting] = useState(false); const [collectionToDelete, setCollectionToDelete] = useState(null); @@ -65,17 +65,11 @@ function CollectionsTable({ } function onEditCollection(id: string) { - history.push({ - pathname: `${collectionsBasePath}/${id}`, - search: 'action=edit', - }); + navigate(`${collectionsBasePath}/${id}?action=edit`); } function onCloneCollection(id: string) { - history.push({ - pathname: `${collectionsBasePath}/${id}`, - search: 'action=clone', - }); + navigate(`${collectionsBasePath}/${id}?action=clone`); } function onConfirmDeleteCollection(collection: Collection) { diff --git a/ui/apps/platform/src/Containers/Compliance/Entity/Deployment.js b/ui/apps/platform/src/Containers/Compliance/Entity/Deployment.js index 285d8bd837e0d..2d3eec7e7da7f 100644 --- a/ui/apps/platform/src/Containers/Compliance/Entity/Deployment.js +++ b/ui/apps/platform/src/Containers/Compliance/Entity/Deployment.js @@ -1,5 +1,5 @@ import React, { useContext, useState } from 'react'; -import { useRouteMatch, useLocation } from 'react-router-dom'; +import { useLocation } from 'react-router-dom'; import pluralize from 'pluralize'; import entityTypes from 'constants/entityTypes'; @@ -21,6 +21,7 @@ import Labels from 'Containers/Compliance/widgets/Labels'; import ComplianceByStandards from 'Containers/Compliance/widgets/ComplianceByStandards'; import isGQLLoading from 'utils/gqlLoading'; import searchContext from 'Containers/searchContext'; +import useWorkflowMatch from 'hooks/useWorkflowMatch'; import Header from './Header'; import ResourceTabs from './ResourceTabs'; @@ -46,7 +47,7 @@ const DeploymentPage = ({ }) => { const [isExporting, setIsExporting] = useState(false); const searchParam = useContext(searchContext); - const match = useRouteMatch(); + const match = useWorkflowMatch(); const location = useLocation(); return ( diff --git a/ui/apps/platform/src/Containers/Compliance/Entity/Page.js b/ui/apps/platform/src/Containers/Compliance/Entity/Page.js index 03e7be9230387..4697e37cfaa4f 100644 --- a/ui/apps/platform/src/Containers/Compliance/Entity/Page.js +++ b/ui/apps/platform/src/Containers/Compliance/Entity/Page.js @@ -1,8 +1,9 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { useLocation, useRouteMatch } from 'react-router-dom'; +import { useLocation } from 'react-router-dom'; import URLService from 'utils/URLService'; import entityTypes from 'constants/entityTypes'; +import useWorkflowMatch from 'hooks/useWorkflowMatch'; import NodePage from './Node'; import NamespacePage from './Namespace'; @@ -13,7 +14,7 @@ import StandardPage from './Standard'; const ComplianceEntityPage = () => { const location = useLocation(); - const match = useRouteMatch(); + const match = useWorkflowMatch(); const params = URLService.getParams(match, location); diff --git a/ui/apps/platform/src/Containers/Compliance/Entity/ResourceTabs.js b/ui/apps/platform/src/Containers/Compliance/Entity/ResourceTabs.js index fcba615b9c9f9..0858260e2b52c 100644 --- a/ui/apps/platform/src/Containers/Compliance/Entity/ResourceTabs.js +++ b/ui/apps/platform/src/Containers/Compliance/Entity/ResourceTabs.js @@ -1,6 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { Link, useLocation, useRouteMatch } from 'react-router-dom'; +import { Link, useLocation } from 'react-router-dom'; +import useWorkflowMatch from 'hooks/useWorkflowMatch'; import { resourceLabels } from 'messages/common'; import URLService from 'utils/URLService'; import pluralize from 'pluralize'; @@ -10,7 +11,7 @@ import queryService from 'utils/queryService'; import { getResourceCountFromAggregatedResults } from 'utils/complianceUtils'; const ResourceTabs = ({ entityType, entityId, resourceTabs, selectedType }) => { - const match = useRouteMatch(); + const match = useWorkflowMatch(); const location = useLocation(); function getLinkToListType(listEntityType) { diff --git a/ui/apps/platform/src/Containers/Compliance/List/List.js b/ui/apps/platform/src/Containers/Compliance/List/List.js index b2a6327a2299a..87d5917070867 100644 --- a/ui/apps/platform/src/Containers/Compliance/List/List.js +++ b/ui/apps/platform/src/Containers/Compliance/List/List.js @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { useHistory, useLocation, useRouteMatch } from 'react-router-dom'; +import { useLocation, useNavigate } from 'react-router-dom'; import lowerCase from 'lodash/lowerCase'; import pluralize from 'pluralize'; @@ -10,6 +10,7 @@ import SidePanelAdjacentArea from 'Components/SidePanelAdjacentArea'; import { searchCategories as searchCategoryTypes } from 'constants/entityTypes'; import searchContext from 'Containers/searchContext'; import { searchParams } from 'constants/searchParams'; +import useWorkflowMatch from 'hooks/useWorkflowMatch'; import ListTable from './Table'; // TODO: this exception will be unnecessary once Compliance pages are re-structured like Config Management /* eslint-disable-next-line import/no-cycle */ @@ -17,16 +18,16 @@ import SidePanel from './SidePanel'; import ComplianceSearchInput from '../ComplianceSearchInput'; const ComplianceList = ({ entityType, query, selectedRowId, noSearch }) => { - const history = useHistory(); + const navigate = useNavigate(); const location = useLocation(); - const match = useRouteMatch(); + const match = useWorkflowMatch(); function setSelectedRowId(row) { const { id } = row; const url = URLService.getURL(match, location) .set('entityListType1', entityType) .set('entityId1', id) .url(); - history.push(url); + navigate(url); } const placeholder = `Filter ${pluralize(lowerCase(entityType))}`; diff --git a/ui/apps/platform/src/Containers/Compliance/List/Page.js b/ui/apps/platform/src/Containers/Compliance/List/Page.js index 45b47c056aeb5..53818ddb232d3 100644 --- a/ui/apps/platform/src/Containers/Compliance/List/Page.js +++ b/ui/apps/platform/src/Containers/Compliance/List/Page.js @@ -1,6 +1,6 @@ import React, { useContext, useState } from 'react'; import PropTypes from 'prop-types'; -import { useLocation, useRouteMatch } from 'react-router-dom'; +import { useLocation } from 'react-router-dom'; import lowerCase from 'lodash/lowerCase'; import pluralize from 'pluralize'; @@ -8,13 +8,14 @@ import URLService from 'utils/URLService'; import BackdropExporting from 'Components/PatternFly/BackdropExporting'; import ComplianceList from 'Containers/Compliance/List/List'; import searchContext from 'Containers/searchContext'; +import useWorkflowMatch from 'hooks/useWorkflowMatch'; import ComplianceSearchInput from '../ComplianceSearchInput'; import Header from './Header'; const ComplianceListPage = () => { const [isExporting, setIsExporting] = useState(false); const location = useLocation(); - const match = useRouteMatch(); + const match = useWorkflowMatch(); const params = URLService.getParams(match, location); const searchParam = useContext(searchContext); const query = { ...params.query[searchParam] }; diff --git a/ui/apps/platform/src/Containers/Compliance/List/SidePanel.js b/ui/apps/platform/src/Containers/Compliance/List/SidePanel.js index 6c64b08e46ff8..367e3adb7ef49 100644 --- a/ui/apps/platform/src/Containers/Compliance/List/SidePanel.js +++ b/ui/apps/platform/src/Containers/Compliance/List/SidePanel.js @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { ExternalLinkAltIcon } from '@patternfly/react-icons'; -import { Link, useHistory, useLocation, useRouteMatch } from 'react-router-dom'; +import { Link, useLocation, useNavigate } from 'react-router-dom'; import Query from 'Components/CacheFirstQuery'; import CloseButton from 'Components/CloseButton'; @@ -10,6 +10,7 @@ import { resourceTypes, standardEntityTypes } from 'constants/entityTypes'; // TODO: this exception will be unnecessary once Compliance pages are re-structured like Config Management /* eslint-disable import/no-cycle */ import ControlPage from 'Containers/Compliance/Entity/Control'; +import useWorkflowMatch from 'hooks/useWorkflowMatch'; import URLService from 'utils/URLService'; import getEntityName from 'utils/getEntityName'; import { entityNameQueryMap } from 'utils/queryMap'; @@ -22,9 +23,9 @@ import DeploymentPage from '../Entity/Deployment'; const MAX_CONTROL_TITLE = 120; const ComplianceListSidePanel = ({ entityType, entityId }) => { - const history = useHistory(); + const navigate = useNavigate(); const location = useLocation(); - const match = useRouteMatch(); + const match = useWorkflowMatch(); function getEntityPage() { switch (entityType) { @@ -45,7 +46,7 @@ const ComplianceListSidePanel = ({ entityType, entityId }) => { function closeSidePanel() { const baseURL = URLService.getURL(match, location).clearSidePanelParams().url(); - history.push(baseURL); + navigate(baseURL); } const headerUrl = URLService.getURL(match, location).base(entityType, entityId).url(); diff --git a/ui/apps/platform/src/Containers/Compliance/Page.js b/ui/apps/platform/src/Containers/Compliance/Page.js index 669f5926da2de..1c2f1a0a16de0 100644 --- a/ui/apps/platform/src/Containers/Compliance/Page.js +++ b/ui/apps/platform/src/Containers/Compliance/Page.js @@ -1,39 +1,36 @@ import React from 'react'; -import { Route, Switch } from 'react-router-dom'; +import { Route, Routes } from 'react-router-dom'; import isEqual from 'lodash/isEqual'; import PageNotFound from 'Components/PageNotFound'; import searchContext from 'Containers/searchContext'; -import { mainPath } from 'routePaths'; import Dashboard from './Dashboard/ComplianceDashboardPage'; import Entity from './Entity/Page'; import List from './List/Page'; -const pageEntityListType = 'clusters|controls|deployments|namespaces|nodes'; -const pageEntityType = 'cluster|control|deployment|namespace|node|standard'; - -// mainPath instead of complianceBasePath because URLService requires param context = 'compliance' -const complianceDashboardPath = `${mainPath}/:context`; -const complianceListPath = `${mainPath}/:context/:pageEntityListType(${pageEntityListType})/:entityId1?/:entityType2?/:entityId2?`; -const complianceEntityPath = `${mainPath}/:context/:pageEntityType(${pageEntityType})/:pageEntityId?/:entityType1?/:entityId1?/:entityType2?/:entityId2?`; +const complianceListPath = `:entityId1?/:entityType2?/:entityId2?`; +const complianceEntityPath = `:pageEntityId?/:entityType1?/:entityId1?/:entityType2?/:entityId2?`; const Page = () => ( - - - - - - - - - - - - - - + + } /> + } /> + } /> + } /> + } /> + } /> + + } /> + } /> + } /> + } /> + } /> + } /> + + } /> + ); diff --git a/ui/apps/platform/src/Containers/Compliance/widgets/ComplianceByStandard.js b/ui/apps/platform/src/Containers/Compliance/widgets/ComplianceByStandard.js index e70464ff15a02..ccb1b3eda5b1c 100644 --- a/ui/apps/platform/src/Containers/Compliance/widgets/ComplianceByStandard.js +++ b/ui/apps/platform/src/Containers/Compliance/widgets/ComplianceByStandard.js @@ -1,7 +1,7 @@ import React, { useContext } from 'react'; import PropTypes from 'prop-types'; import capitalize from 'lodash/capitalize'; -import { Link, useLocation, useRouteMatch } from 'react-router-dom'; +import { Link, useLocation } from 'react-router-dom'; import URLService from 'utils/URLService'; import entityTypes, { standardBaseTypes } from 'constants/entityTypes'; @@ -16,6 +16,7 @@ import { MODERATE_MEDIUM_SEVERITY_COLOR, noViolationsColor, } from 'constants/severityColors'; +import useWorkflowMatch from 'hooks/useWorkflowMatch'; import { COMPLIANCE_STANDARDS } from 'queries/standard'; import queryService from 'utils/queryService'; import searchContext from 'Containers/searchContext'; @@ -140,7 +141,7 @@ const ComplianceByStandard = ({ className, }) => { const location = useLocation(); - const match = useRouteMatch(); + const match = useWorkflowMatch(); const searchParam = useContext(searchContext); const groupBy = [ entityTypes.STANDARD, diff --git a/ui/apps/platform/src/Containers/Compliance/widgets/ControlRelatedResourceList.js b/ui/apps/platform/src/Containers/Compliance/widgets/ControlRelatedResourceList.js index 3f6edd2e1fdad..f47d936fa8aa1 100644 --- a/ui/apps/platform/src/Containers/Compliance/widgets/ControlRelatedResourceList.js +++ b/ui/apps/platform/src/Containers/Compliance/widgets/ControlRelatedResourceList.js @@ -3,9 +3,10 @@ import PropTypes from 'prop-types'; import URLService from 'utils/URLService'; import entityTypes from 'constants/entityTypes'; import useCases from 'constants/useCaseTypes'; +import useWorkflowMatch from 'hooks/useWorkflowMatch'; import { AGGREGATED_RESULTS as QUERY } from 'queries/controls'; import queryService from 'utils/queryService'; -import { useLocation, useRouteMatch, Link } from 'react-router-dom'; +import { useLocation, Link } from 'react-router-dom'; import searchContext from 'Containers/searchContext'; import { entityNounOrdinaryCase } from '../entitiesForCompliance'; @@ -22,7 +23,7 @@ const ControlRelatedEntitiesList = ({ const linkContext = useCases.COMPLIANCE; const searchParam = useContext(searchContext); const location = useLocation(); - const match = useRouteMatch(); + const match = useWorkflowMatch(); function processData(data) { if (!data || !data.results) { diff --git a/ui/apps/platform/src/Containers/Compliance/widgets/EntityCompliance.js b/ui/apps/platform/src/Containers/Compliance/widgets/EntityCompliance.js index 807c99137a9f9..1c352c2dd7ef6 100644 --- a/ui/apps/platform/src/Containers/Compliance/widgets/EntityCompliance.js +++ b/ui/apps/platform/src/Containers/Compliance/widgets/EntityCompliance.js @@ -1,12 +1,13 @@ import React, { useContext } from 'react'; import PropTypes from 'prop-types'; -import { useRouteMatch, useLocation, useHistory } from 'react-router-dom'; +import { useLocation, useNavigate } from 'react-router-dom'; import Widget from 'Components/Widget'; import ArcSingle from 'Components/visuals/ArcSingle'; import Query from 'Components/CacheFirstQuery'; import Loader from 'Components/Loader'; import entityTypes, { standardBaseTypes } from 'constants/entityTypes'; +import useWorkflowMatch from 'hooks/useWorkflowMatch'; import URLService from 'utils/URLService'; import { AGGREGATED_RESULTS } from 'queries/controls'; import queryService from 'utils/queryService'; @@ -20,9 +21,9 @@ import VerticalBarChart from './VerticalBarChart'; const EntityCompliance = ({ entityType, entityName, clusterName }) => { const entityTypeLabel = entityNounSentenceCaseSingular[entityType]; const searchParam = useContext(searchContext); - const match = useRouteMatch(); + const match = useWorkflowMatch(); const location = useLocation(); - const history = useHistory(); + const navigate = useNavigate(); function getBarData(results) { return results @@ -56,7 +57,7 @@ const EntityCompliance = ({ entityType, entityName, clusterName }) => { }) .url(); - history.push(URL); + navigate(URL); } const whereClause = { [entityType]: entityName, [entityTypes.CLUSTER]: clusterName }; diff --git a/ui/apps/platform/src/Containers/Compliance/widgets/HorizontalBarChart.js b/ui/apps/platform/src/Containers/Compliance/widgets/HorizontalBarChart.js index 8026bb3128cfd..efdd5b44abe1e 100644 --- a/ui/apps/platform/src/Containers/Compliance/widgets/HorizontalBarChart.js +++ b/ui/apps/platform/src/Containers/Compliance/widgets/HorizontalBarChart.js @@ -7,7 +7,7 @@ import { HorizontalBarSeries, LabelSeries, } from 'react-vis'; -import { useHistory, Link } from 'react-router-dom'; +import { Link, useNavigate } from 'react-router-dom'; import PropTypes from 'prop-types'; import merge from 'deepmerge'; @@ -34,7 +34,7 @@ const HorizontalBarChart = ({ tickValues = [0, 25, 50, 75, 100], minimal = false, }) => { - const history = useHistory(); + const navigate = useNavigate(); const showLabel = (value) => value >= 10; @@ -58,7 +58,7 @@ const HorizontalBarChart = ({ const onValueClickHandler = (datum) => { if (datum.link) { - history.push(datum.link); + navigate(datum.link); } }; diff --git a/ui/apps/platform/src/Containers/Compliance/widgets/ResourceCount.js b/ui/apps/platform/src/Containers/Compliance/widgets/ResourceCount.js index 1bc47d959aaf1..4297b8b275a73 100644 --- a/ui/apps/platform/src/Containers/Compliance/widgets/ResourceCount.js +++ b/ui/apps/platform/src/Containers/Compliance/widgets/ResourceCount.js @@ -8,10 +8,11 @@ import Widget from 'Components/Widget'; import Query from 'Components/CacheFirstQuery'; import Loader from 'Components/Loader'; import CountWidget from 'Components/CountWidget'; +import useWorkflowMatch from 'hooks/useWorkflowMatch'; import { SEARCH_WITH_CONTROLS as QUERY } from 'queries/search'; import queryService from 'utils/queryService'; import { getResourceCountFromAggregatedResults } from 'utils/complianceUtils'; -import { useLocation, useRouteMatch } from 'react-router-dom'; +import { useLocation } from 'react-router-dom'; import useCases from 'constants/useCaseTypes'; import searchContext from 'Containers/searchContext'; @@ -19,7 +20,7 @@ import { entityNounSentenceCaseSingular } from '../entitiesForCompliance'; const ResourceCount = ({ entityType, relatedToResourceType, relatedToResource, count }) => { const searchParam = useContext(searchContext); - const match = useRouteMatch(); + const match = useWorkflowMatch(); const location = useLocation(); function getUrl() { diff --git a/ui/apps/platform/src/Containers/Compliance/widgets/StandardsAcrossEntity.js b/ui/apps/platform/src/Containers/Compliance/widgets/StandardsAcrossEntity.js index ce363aea037cb..5c1fe63443fca 100644 --- a/ui/apps/platform/src/Containers/Compliance/widgets/StandardsAcrossEntity.js +++ b/ui/apps/platform/src/Containers/Compliance/widgets/StandardsAcrossEntity.js @@ -1,6 +1,6 @@ import React, { useContext } from 'react'; import PropTypes from 'prop-types'; -import { useLocation, useRouteMatch } from 'react-router-dom'; +import { useLocation } from 'react-router-dom'; import { useQuery } from '@apollo/client'; import merge from 'lodash/merge'; @@ -10,6 +10,7 @@ import URLService from 'utils/URLService'; import Widget from 'Components/Widget'; import Loader from 'Components/Loader'; import NoResultsMessage from 'Components/NoResultsMessage'; +import useWorkflowMatch from 'hooks/useWorkflowMatch'; import { AGGREGATED_RESULTS_ACROSS_ENTITY } from 'queries/controls'; import searchContext from 'Containers/searchContext'; @@ -51,7 +52,7 @@ function setStandardsMapping(data, key, type) { const StandardsAcrossEntity = ({ entityType, bodyClassName, className }) => { const searchParam = useContext(searchContext); - const match = useRouteMatch(); + const match = useWorkflowMatch(); const location = useLocation(); const headerText = `Passing standards across ${entityNounOrdinaryCasePlural[entityType]}`; diff --git a/ui/apps/platform/src/Containers/Compliance/widgets/StandardsByEntity.js b/ui/apps/platform/src/Containers/Compliance/widgets/StandardsByEntity.js index 17cf96f215636..c13b3af490ede 100644 --- a/ui/apps/platform/src/Containers/Compliance/widgets/StandardsByEntity.js +++ b/ui/apps/platform/src/Containers/Compliance/widgets/StandardsByEntity.js @@ -1,6 +1,6 @@ import React, { useContext } from 'react'; import PropTypes from 'prop-types'; -import { useLocation, useRouteMatch } from 'react-router-dom'; +import { useLocation } from 'react-router-dom'; import { useQuery } from '@apollo/client'; import capitalize from 'lodash/capitalize'; import sortBy from 'lodash/sortBy'; @@ -11,6 +11,7 @@ import URLService from 'utils/URLService'; import Widget from 'Components/Widget'; import Loader from 'Components/Loader'; import NoResultsMessage from 'Components/NoResultsMessage'; +import useWorkflowMatch from 'hooks/useWorkflowMatch'; import { standardLabels } from 'messages/standards'; import { AGGREGATED_RESULTS_STANDARDS_BY_ENTITY } from 'queries/controls'; import searchContext from 'Containers/searchContext'; @@ -92,7 +93,7 @@ function getLabelLinks(match, location, data, entityType) { const StandardsByEntity = ({ entityType, bodyClassName, className }) => { const searchParam = useContext(searchContext); - const match = useRouteMatch(); + const match = useWorkflowMatch(); const location = useLocation(); const headerText = `Passing standards by ${entityNounOrdinaryCaseSingular[entityType]}`; diff --git a/ui/apps/platform/src/Containers/Compliance/widgets/VerticalClusterBar.js b/ui/apps/platform/src/Containers/Compliance/widgets/VerticalClusterBar.js index c4ccb6f076bff..787dd053f403b 100644 --- a/ui/apps/platform/src/Containers/Compliance/widgets/VerticalClusterBar.js +++ b/ui/apps/platform/src/Containers/Compliance/widgets/VerticalClusterBar.js @@ -8,7 +8,7 @@ import { VerticalBarSeries, } from 'react-vis'; import PropTypes from 'prop-types'; -import { useHistory, Link } from 'react-router-dom'; +import { Link, useNavigate } from 'react-router-dom'; import DiscreteColorLegend from 'react-vis/dist/legends/discrete-color-legend'; import merge from 'deepmerge'; @@ -26,7 +26,7 @@ function VerticalClusterBar({ tickFormat = (x) => `${x}%`, labelLinks = {}, }) { - const history = useHistory(); + const navigate = useNavigate(); const getLegendData = () => { return Object.keys(data) @@ -59,7 +59,7 @@ function VerticalClusterBar({ }, onValueClick: (datum) => { if (datum.link) { - history.push(datum.link); + navigate(datum.link); } }, }; diff --git a/ui/apps/platform/src/Containers/ComplianceEnhanced/ComplianceEnhancedPage.tsx b/ui/apps/platform/src/Containers/ComplianceEnhanced/ComplianceEnhancedPage.tsx deleted file mode 100644 index 1836783deb8d8..0000000000000 --- a/ui/apps/platform/src/Containers/ComplianceEnhanced/ComplianceEnhancedPage.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import React from 'react'; -import { Redirect, Route, Switch } from 'react-router-dom'; -import { PageSection } from '@patternfly/react-core'; - -import PageNotFound from 'Components/PageNotFound'; -import PageTitle from 'Components/PageTitle'; -import { - complianceEnhancedBasePath, - complianceEnhancedCoveragePath, - complianceEnhancedSchedulesPath, -} from 'routePaths'; - -import CoveragePage from './Coverage/CoveragePage'; -import ScanConfigsPage from './Schedules/ScanConfigsPage'; - -function ComplianceEnhancedPage() { - return ( - - } - /> - - - - - - - - - - - - - - ); -} - -export default ComplianceEnhancedPage; diff --git a/ui/apps/platform/src/Containers/ComplianceEnhanced/ComplianceNotFoundPage.tsx b/ui/apps/platform/src/Containers/ComplianceEnhanced/ComplianceNotFoundPage.tsx new file mode 100644 index 0000000000000..bc311a35934ca --- /dev/null +++ b/ui/apps/platform/src/Containers/ComplianceEnhanced/ComplianceNotFoundPage.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import { PageSection } from '@patternfly/react-core'; + +import PageNotFound from 'Components/PageNotFound'; +import PageTitle from 'Components/PageTitle'; + +function ComplianceNotFoundPage() { + return ( + + + + + ); +} + +export default ComplianceNotFoundPage; diff --git a/ui/apps/platform/src/Containers/ComplianceEnhanced/Coverage/CheckDetailsPage.tsx b/ui/apps/platform/src/Containers/ComplianceEnhanced/Coverage/CheckDetailsPage.tsx index 4630960e96c74..52325ecbaf2da 100644 --- a/ui/apps/platform/src/Containers/ComplianceEnhanced/Coverage/CheckDetailsPage.tsx +++ b/ui/apps/platform/src/Containers/ComplianceEnhanced/Coverage/CheckDetailsPage.tsx @@ -61,7 +61,7 @@ const searchFilterConfig: CompoundSearchFilterConfig = [ function CheckDetails() { const { scanConfigurationsQuery, selectedScanConfigName, setSelectedScanConfigName } = useContext(ScanConfigurationsContext); - const { checkName, profileName } = useParams(); + const { checkName, profileName } = useParams() as { checkName: string; profileName: string }; const { generatePathWithScanConfig } = useScanConfigRouter(); const [currentDatetime, setCurrentDatetime] = useState(new Date()); const pagination = useURLPagination(DEFAULT_COMPLIANCE_PAGE_SIZE); diff --git a/ui/apps/platform/src/Containers/ComplianceEnhanced/Coverage/ClusterDetailsPage.tsx b/ui/apps/platform/src/Containers/ComplianceEnhanced/Coverage/ClusterDetailsPage.tsx index d856d46c6d951..40792c9e74972 100644 --- a/ui/apps/platform/src/Containers/ComplianceEnhanced/Coverage/ClusterDetailsPage.tsx +++ b/ui/apps/platform/src/Containers/ComplianceEnhanced/Coverage/ClusterDetailsPage.tsx @@ -49,7 +49,7 @@ const searchFilterConfig = [profileCheckSearchFilterConfig]; function ClusterDetailsPage() { const { scanConfigurationsQuery, selectedScanConfigName, setSelectedScanConfigName } = useContext(ScanConfigurationsContext); - const { clusterId, profileName } = useParams(); + const { clusterId, profileName } = useParams() as { clusterId: string; profileName: string }; const { generatePathWithScanConfig, navigateWithScanConfigQuery } = useScanConfigRouter(); const pagination = useURLPagination(DEFAULT_COMPLIANCE_PAGE_SIZE); const { page, perPage, setPage } = pagination; diff --git a/ui/apps/platform/src/Containers/ComplianceEnhanced/Coverage/CoveragePage.tsx b/ui/apps/platform/src/Containers/ComplianceEnhanced/Coverage/CoveragePage.tsx index 4b47c2823d39b..f9493b4f4f78c 100644 --- a/ui/apps/platform/src/Containers/ComplianceEnhanced/Coverage/CoveragePage.tsx +++ b/ui/apps/platform/src/Containers/ComplianceEnhanced/Coverage/CoveragePage.tsx @@ -1,18 +1,12 @@ import React, { useContext } from 'react'; -import { Redirect, Route, Switch } from 'react-router-dom'; +import { Navigate, Route, Routes } from 'react-router-dom'; import { Alert, Bullseye, Spinner } from '@patternfly/react-core'; -import { complianceEnhancedCoveragePath } from 'routePaths'; import { getAxiosErrorMessage } from 'utils/responseErrorUtils'; -import { - coverageCheckDetailsPath, - coverageClusterDetailsPath, - coverageProfileChecksPath, - coverageProfileClustersPath, -} from './compliance.coverage.routes'; import CheckDetailsPage from './CheckDetailsPage'; import ClusterDetailsPage from './ClusterDetailsPage'; +import ComplianceNotFoundPage from '../ComplianceNotFoundPage'; import ComplianceProfilesProvider, { ComplianceProfilesContext, } from './ComplianceProfilesProvider'; @@ -46,26 +40,17 @@ function CoverageContent() { } return ( - - - - - - - - - - + + } /> + } /> + } /> - - - + path="profiles/:profileName/clusters/:clusterId" + element={} + /> + } /> + } /> + ); } @@ -81,9 +66,7 @@ function ProfilesRedirectHandler() { ); } - return ( - - ); + return ; } export default CoveragePage; diff --git a/ui/apps/platform/src/Containers/ComplianceEnhanced/Coverage/CoveragesPage.tsx b/ui/apps/platform/src/Containers/ComplianceEnhanced/Coverage/CoveragesPage.tsx index 7866744e0f9c6..cb4ef4f8cd0bf 100644 --- a/ui/apps/platform/src/Containers/ComplianceEnhanced/Coverage/CoveragesPage.tsx +++ b/ui/apps/platform/src/Containers/ComplianceEnhanced/Coverage/CoveragesPage.tsx @@ -1,5 +1,5 @@ import React, { useCallback, useContext, useState } from 'react'; -import { Route, Switch, useParams } from 'react-router-dom'; +import { Navigate, Route, Routes, useParams } from 'react-router-dom'; import { Bullseye, Divider, @@ -36,10 +36,7 @@ import { CHECK_STATUS_QUERY, CLUSTER_QUERY, } from './compliance.coverage.constants'; -import { - coverageProfileChecksPath, - coverageProfileClustersPath, -} from './compliance.coverage.routes'; +import { coverageProfileChecksPath } from './compliance.coverage.routes'; import { createScanConfigFilter } from './compliance.coverage.utils'; import { ComplianceProfilesContext } from './ComplianceProfilesProvider'; import CheckStatusDropdown from './components/CheckStatusDropdown'; @@ -62,7 +59,7 @@ function CoveragesPage() { false ); const { navigateWithScanConfigQuery } = useScanConfigRouter(); - const { profileName } = useParams(); + const { profileName } = useParams() as { profileName: string }; const { isLoading: isLoadingScanConfigProfiles, scanConfigProfilesResponse } = useContext(ComplianceProfilesContext); const { scanConfigurationsQuery, selectedScanConfigName, setSelectedScanConfigName } = @@ -221,14 +218,11 @@ function CoveragesPage() { - - - - - - - - + + } /> + } /> + } /> + )} diff --git a/ui/apps/platform/src/Containers/ComplianceEnhanced/Coverage/ProfileChecksPage.tsx b/ui/apps/platform/src/Containers/ComplianceEnhanced/Coverage/ProfileChecksPage.tsx index 9b3ff324c0af0..ef3ee091d10e5 100644 --- a/ui/apps/platform/src/Containers/ComplianceEnhanced/Coverage/ProfileChecksPage.tsx +++ b/ui/apps/platform/src/Containers/ComplianceEnhanced/Coverage/ProfileChecksPage.tsx @@ -16,7 +16,7 @@ import ProfileChecksTable from './ProfileChecksTable'; import { ScanConfigurationsContext } from './ScanConfigurationsProvider'; function ProfileChecksPage() { - const { profileName } = useParams(); + const { profileName } = useParams() as { profileName: string }; const { selectedScanConfigName } = useContext(ScanConfigurationsContext); const pagination = useURLPagination(DEFAULT_COMPLIANCE_PAGE_SIZE); diff --git a/ui/apps/platform/src/Containers/ComplianceEnhanced/Coverage/ProfileClustersPage.tsx b/ui/apps/platform/src/Containers/ComplianceEnhanced/Coverage/ProfileClustersPage.tsx index e9b4df4690eb0..f8a326cbccb0d 100644 --- a/ui/apps/platform/src/Containers/ComplianceEnhanced/Coverage/ProfileClustersPage.tsx +++ b/ui/apps/platform/src/Containers/ComplianceEnhanced/Coverage/ProfileClustersPage.tsx @@ -16,7 +16,7 @@ import ProfileClustersTable from './ProfileClustersTable'; import { ScanConfigurationsContext } from './ScanConfigurationsProvider'; function ProfileClustersPage() { - const { profileName } = useParams(); + const { profileName } = useParams() as { profileName: string }; const { selectedScanConfigName } = useContext(ScanConfigurationsContext); const [currentDatetime, setCurrentDatetime] = useState(new Date()); const pagination = useURLPagination(DEFAULT_COMPLIANCE_PAGE_SIZE); diff --git a/ui/apps/platform/src/Containers/ComplianceEnhanced/Coverage/hooks/useScanConfigRouter.ts b/ui/apps/platform/src/Containers/ComplianceEnhanced/Coverage/hooks/useScanConfigRouter.ts index 0000b293398d4..93933474902fa 100644 --- a/ui/apps/platform/src/Containers/ComplianceEnhanced/Coverage/hooks/useScanConfigRouter.ts +++ b/ui/apps/platform/src/Containers/ComplianceEnhanced/Coverage/hooks/useScanConfigRouter.ts @@ -1,5 +1,5 @@ import { useContext } from 'react'; -import { useHistory } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import { generatePathWithQuery } from 'utils/searchUtils'; import { SearchFilter } from 'types/search'; @@ -8,7 +8,7 @@ import { ScanConfigurationsContext } from '../ScanConfigurationsProvider'; const useScanConfigRouter = () => { const { selectedScanConfigName } = useContext(ScanConfigurationsContext); - const history = useHistory(); + const navigate = useNavigate(); function generatePathWithScanConfig( path, @@ -34,7 +34,7 @@ const useScanConfigRouter = () => { searchFilter = {} ) { const generatedPath = generatePathWithScanConfig(path, pathParams, searchFilter); - history.push(generatedPath); + navigate(generatedPath); } return { navigateWithScanConfigQuery, generatePathWithScanConfig }; diff --git a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/ScanConfigActionDropdown.tsx b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/ScanConfigActionDropdown.tsx index 866548ee25234..85bf0c824b49e 100644 --- a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/ScanConfigActionDropdown.tsx +++ b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/ScanConfigActionDropdown.tsx @@ -1,5 +1,5 @@ import React, { ReactElement, useState } from 'react'; -import { generatePath, useHistory } from 'react-router-dom'; +import { generatePath, useNavigate } from 'react-router-dom'; import { Dropdown, DropdownItem, @@ -36,7 +36,7 @@ function ScanConfigActionDropdown({ scanConfigResponse, isReportJobsEnabled, }: ScanConfigActionDropdownProps): ReactElement { - const history = useHistory(); + const navigate = useNavigate(); const [isOpen, setIsOpen] = useState(false); @@ -62,10 +62,7 @@ function ScanConfigActionDropdown({ // description={isScanning ? 'Edit is disabled while scan is running' : ''} isDisabled={isProcessing} onClick={() => { - history.push({ - pathname: scanConfigUrl, - search: 'action=edit', - }); + navigate(`${scanConfigUrl}?action=edit`); }} > Edit scan schedule diff --git a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/ScanConfigActionsColumn.tsx b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/ScanConfigActionsColumn.tsx index 0a1b227fb6c80..0b08c68322cd6 100644 --- a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/ScanConfigActionsColumn.tsx +++ b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/ScanConfigActionsColumn.tsx @@ -1,5 +1,5 @@ import React, { ReactElement } from 'react'; -import { generatePath, useHistory } from 'react-router-dom'; +import { generatePath, useNavigate } from 'react-router-dom'; import { ActionsColumn } from '@patternfly/react-table'; import { ComplianceScanConfigurationStatus } from 'services/ComplianceScanConfigurationService'; @@ -29,7 +29,7 @@ function ScanConfigActionsColumn({ isSnapshotStatusPending, isReportJobsEnabled, }: ScanConfigActionsColumnProps): ReactElement { - const history = useHistory(); + const navigate = useNavigate(); const { id, /* lastExecutedTime, */ scanConfig } = scanConfigResponse; const { notifiers } = scanConfig; @@ -45,10 +45,7 @@ function ScanConfigActionsColumn({ // isDisabled: isScanning, onClick: (event) => { event.preventDefault(); - history.push({ - pathname: scanConfigUrl, - search: 'action=edit', - }); + navigate(`${scanConfigUrl}?action=edit`); }, isDisabled: isSnapshotStatusPending, }, diff --git a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/ScanConfigDetailPage.tsx b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/ScanConfigDetailPage.tsx index e4f2638c4b703..9141654ecfa1a 100644 --- a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/ScanConfigDetailPage.tsx +++ b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/ScanConfigDetailPage.tsx @@ -18,7 +18,7 @@ function ScanConfigDetailPage({ hasWriteAccessForCompliance, isReportJobsEnabled, }: ScanConfigDetailPageProps): React.ReactElement { - const { scanConfigId } = useParams(); + const { scanConfigId } = useParams() as { scanConfigId: string }; const { pageAction } = usePageAction(); const scanConfigFetcher = useCallback(() => { diff --git a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/ScanConfigsPage.tsx b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/ScanConfigsPage.tsx index 4eeceb90feb3c..078d7be6cd5f8 100644 --- a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/ScanConfigsPage.tsx +++ b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/ScanConfigsPage.tsx @@ -1,14 +1,14 @@ import React from 'react'; -import { Redirect, Route, Switch } from 'react-router-dom'; +import { Navigate, Route, Routes } from 'react-router-dom'; import { Banner } from '@patternfly/react-core'; import usePageAction from 'hooks/usePageAction'; import usePermissions from 'hooks/usePermissions'; import { complianceEnhancedSchedulesPath } from 'routePaths'; import useFeatureFlags from 'hooks/useFeatureFlags'; -import { scanConfigDetailsPath } from './compliance.scanConfigs.routes'; import { PageActions } from './compliance.scanConfigs.utils'; import CreateScanConfigPage from './CreateScanConfigPage'; +import ComplianceNotFoundPage from '../ComplianceNotFoundPage'; import ScanConfigDetailPage from './ScanConfigDetailPage'; import ScanConfigsTablePage from './ScanConfigsTablePage'; @@ -34,38 +34,34 @@ function ScanConfigsPage() { newer )} - + { - if (pageAction === 'create' && hasWriteAccessForCompliance) { - return ; - } - if (!pageAction) { - return ( - - ); - } - return ; - }} - /> - { - return ( - + ) : !pageAction ? ( + - ); - }} + ) : ( + + ) + } + /> + + } /> - + } /> + ); } diff --git a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/ScanConfigsTablePage.tsx b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/ScanConfigsTablePage.tsx index de31598547116..8a3ef725a5d41 100644 --- a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/ScanConfigsTablePage.tsx +++ b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/ScanConfigsTablePage.tsx @@ -1,6 +1,5 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ import React, { useState, useCallback } from 'react'; -import { generatePath, Link, useHistory } from 'react-router-dom'; +import { generatePath, Link } from 'react-router-dom'; import pluralize from 'pluralize'; import { @@ -31,7 +30,6 @@ import { complianceEnhancedSchedulesPath } from 'routePaths'; import DeleteModal from 'Components/PatternFly/DeleteModal'; import EmptyStateTemplate from 'Components/EmptyStateTemplate'; import PageTitle from 'Components/PageTitle'; -import TabNavSubHeader from 'Components/TabNav/TabNavSubHeader'; import useAlert from 'hooks/useAlert'; import useRestQuery from 'hooks/useRestQuery'; import useURLPagination from 'hooks/useURLPagination'; diff --git a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/ViewScanConfigDetail.tsx b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/ViewScanConfigDetail.tsx index baf25ecc72b79..14a1382657df7 100644 --- a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/ViewScanConfigDetail.tsx +++ b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/ViewScanConfigDetail.tsx @@ -54,7 +54,7 @@ function ViewScanConfigDetail({ isLoading, error = null, }: ViewScanConfigDetailProps): React.ReactElement { - const { scanConfigId } = useParams(); + const { scanConfigId } = useParams() as { scanConfigId: string }; const { analyticsTrack } = useAnalytics(); const [activeScanConfigTab, setActiveScanConfigTab] = useURLStringUnion( diff --git a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ScanConfigWizardForm.tsx b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ScanConfigWizardForm.tsx index e62d71384f34e..43e988ea3f6cd 100644 --- a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ScanConfigWizardForm.tsx +++ b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ScanConfigWizardForm.tsx @@ -1,5 +1,5 @@ import React, { ReactElement, useCallback, useRef, useState } from 'react'; -import { useHistory } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import { Wizard, WizardStep } from '@patternfly/react-core/deprecated'; import { FormikProvider } from 'formik'; import { complianceEnhancedSchedulesPath } from 'routePaths'; @@ -41,7 +41,7 @@ type ScanConfigWizardFormProps = { function ScanConfigWizardForm({ initialFormValues }: ScanConfigWizardFormProps): ReactElement { const { analyticsTrack } = useAnalytics(); - const history = useHistory(); + const navigate = useNavigate(); const formik = useFormikScanConfig(initialFormValues); const [isCreating, setIsCreating] = useState(false); const [createScanConfigError, setCreateScanConfigError] = useState(''); @@ -73,7 +73,7 @@ function ScanConfigWizardForm({ initialFormValues }: ScanConfigWizardFormProps): errorMessage: '', }, }); - history.push(complianceEnhancedSchedulesPath); + navigate(complianceEnhancedSchedulesPath); } catch (error) { analyticsTrack({ event: COMPLIANCE_SCHEDULES_WIZARD_SAVE_CLICKED, @@ -117,7 +117,7 @@ function ScanConfigWizardForm({ initialFormValues }: ScanConfigWizardFormProps): } function onClose(): void { - history.push(complianceEnhancedSchedulesPath); + navigate(complianceEnhancedSchedulesPath); } function setAllFieldsTouched(formikGroupKey: string): void { diff --git a/ui/apps/platform/src/Containers/ConfigManagement/Dashboard/Header/AppMenu.js b/ui/apps/platform/src/Containers/ConfigManagement/Dashboard/Header/AppMenu.js index 7d1f0e53abfc8..73db54481cb5d 100644 --- a/ui/apps/platform/src/Containers/ConfigManagement/Dashboard/Header/AppMenu.js +++ b/ui/apps/platform/src/Containers/ConfigManagement/Dashboard/Header/AppMenu.js @@ -1,9 +1,10 @@ import React from 'react'; import entityTypes from 'constants/entityTypes'; +import useWorkflowMatch from 'hooks/useWorkflowMatch'; import pluralize from 'pluralize'; import entityLabels from 'messages/entity'; import URLService from 'utils/URLService'; -import { useLocation, useRouteMatch } from 'react-router-dom'; +import { useLocation } from 'react-router-dom'; import DashboardMenu from 'Components/DashboardMenu'; @@ -28,7 +29,7 @@ const AppMenu = () => { entityTypes.SECRET, ]; - const match = useRouteMatch(); + const match = useWorkflowMatch(); const location = useLocation(); const urlBuilder = URLService.getURL(match, location); const options = createOptions(urlBuilder, types); diff --git a/ui/apps/platform/src/Containers/ConfigManagement/Dashboard/Header/CISControlsTile.js b/ui/apps/platform/src/Containers/ConfigManagement/Dashboard/Header/CISControlsTile.js index daab0adddf413..79619dbdf00d8 100644 --- a/ui/apps/platform/src/Containers/ConfigManagement/Dashboard/Header/CISControlsTile.js +++ b/ui/apps/platform/src/Containers/ConfigManagement/Dashboard/Header/CISControlsTile.js @@ -1,7 +1,8 @@ import React from 'react'; import URLService from 'utils/URLService'; -import { useLocation, useRouteMatch } from 'react-router-dom'; +import { useLocation } from 'react-router-dom'; import entityTypes from 'constants/entityTypes'; +import useWorkflowMatch from 'hooks/useWorkflowMatch'; import { gql, useQuery } from '@apollo/client'; import logError from 'utils/logError'; @@ -19,7 +20,7 @@ const CISControlsTile = () => { logError(error); } - const match = useRouteMatch(); + const match = useWorkflowMatch(); const location = useLocation(); const controlsURL = URLService.getURL(match, location).base(entityTypes.CONTROL).url(); diff --git a/ui/apps/platform/src/Containers/ConfigManagement/Dashboard/Header/PoliciesTile.js b/ui/apps/platform/src/Containers/ConfigManagement/Dashboard/Header/PoliciesTile.js index 741f927a42a12..bc5cc7392ab25 100644 --- a/ui/apps/platform/src/Containers/ConfigManagement/Dashboard/Header/PoliciesTile.js +++ b/ui/apps/platform/src/Containers/ConfigManagement/Dashboard/Header/PoliciesTile.js @@ -1,9 +1,10 @@ import React from 'react'; import { gql } from '@apollo/client'; -import { useLocation, useRouteMatch } from 'react-router-dom'; +import { useLocation } from 'react-router-dom'; import entityTypes from 'constants/entityTypes'; import Query from 'Components/ThrowingQuery'; import EntityTileLink from 'Components/EntityTileLink'; +import useWorkflowMatch from 'hooks/useWorkflowMatch'; import queryService from 'utils/queryService'; import URLService from 'utils/URLService'; @@ -14,7 +15,7 @@ const policiesQuery = gql` `; const PoliciesTile = () => { - const match = useRouteMatch(); + const match = useWorkflowMatch(); const location = useLocation(); const policiesURL = URLService.getURL(match, location).base(entityTypes.POLICY).url(); diff --git a/ui/apps/platform/src/Containers/ConfigManagement/Dashboard/Header/RBACMenu.js b/ui/apps/platform/src/Containers/ConfigManagement/Dashboard/Header/RBACMenu.js index 116fc12b89a6b..5efaa38a2acbe 100644 --- a/ui/apps/platform/src/Containers/ConfigManagement/Dashboard/Header/RBACMenu.js +++ b/ui/apps/platform/src/Containers/ConfigManagement/Dashboard/Header/RBACMenu.js @@ -3,9 +3,10 @@ import entityTypes from 'constants/entityTypes'; import pluralize from 'pluralize'; import entityLabels from 'messages/entity'; import URLService from 'utils/URLService'; -import { useLocation, useRouteMatch } from 'react-router-dom'; +import { useLocation } from 'react-router-dom'; import DashboardMenu from 'Components/DashboardMenu'; +import useWorkflowMatch from 'hooks/useWorkflowMatch'; const getLabel = (entityType) => pluralize(entityLabels[entityType]); @@ -21,7 +22,7 @@ const createOptions = (urlBuilder, types) => { const RBACMenu = () => { const types = [entityTypes.SUBJECT, entityTypes.SERVICE_ACCOUNT, entityTypes.ROLE]; - const match = useRouteMatch(); + const match = useWorkflowMatch(); const location = useLocation(); const urlBuilder = URLService.getURL(match, location); const options = createOptions(urlBuilder, types); diff --git a/ui/apps/platform/src/Containers/ConfigManagement/Dashboard/widgets/ComplianceByControls.js b/ui/apps/platform/src/Containers/ConfigManagement/Dashboard/widgets/ComplianceByControls.js index cc555a5e5341a..1b9bce6f96a38 100644 --- a/ui/apps/platform/src/Containers/ConfigManagement/Dashboard/widgets/ComplianceByControls.js +++ b/ui/apps/platform/src/Containers/ConfigManagement/Dashboard/widgets/ComplianceByControls.js @@ -2,10 +2,11 @@ import React, { useState, useContext } from 'react'; import { Alert } from '@patternfly/react-core'; import PropTypes from 'prop-types'; import { gql } from '@apollo/client'; -import { Link, useLocation, useRouteMatch } from 'react-router-dom'; +import { Link, useLocation } from 'react-router-dom'; import queryService from 'utils/queryService'; import entityTypes, { standardEntityTypes, standardBaseTypes } from 'constants/entityTypes'; import { COMPLIANCE_FAIL_COLOR, COMPLIANCE_PASS_COLOR } from 'constants/severityColors'; +import useWorkflowMatch from 'hooks/useWorkflowMatch'; import { standardLabels } from 'messages/standards'; import URLService from 'utils/URLService'; import { getAxiosErrorMessage } from 'utils/responseErrorUtils'; @@ -275,7 +276,7 @@ const ComplianceByControls = ({ className, standardOptions }) => { const [selectedStandard, selectStandard] = useState(options[0]); const location = useLocation(); - const match = useRouteMatch(); + const match = useWorkflowMatch(); function onChange(datum) { const standard = options.find((option) => option.value === datum); diff --git a/ui/apps/platform/src/Containers/ConfigManagement/Dashboard/widgets/Lollipop.js b/ui/apps/platform/src/Containers/ConfigManagement/Dashboard/widgets/Lollipop.js index ab8176a1ee5c7..dda737fe4fe1c 100644 --- a/ui/apps/platform/src/Containers/ConfigManagement/Dashboard/widgets/Lollipop.js +++ b/ui/apps/platform/src/Containers/ConfigManagement/Dashboard/widgets/Lollipop.js @@ -10,12 +10,12 @@ import { GradientDefs, } from 'react-vis'; import max from 'lodash/max'; -import { useHistory } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import PropTypes from 'prop-types'; import BarGradient from 'Components/visuals/BarGradient'; const Lollipop = ({ data }) => { - const history = useHistory(); + const navigate = useNavigate(); function getGridLineValues() { const interval = data.length < 5 ? 1 : 5; @@ -49,7 +49,7 @@ const Lollipop = ({ data }) => { function onValueClickHandler(datum) { if (datum.link) { - history.push(datum.link); + navigate(datum.link); } } diff --git a/ui/apps/platform/src/Containers/ConfigManagement/Dashboard/widgets/PolicyViolationsBySeverity.js b/ui/apps/platform/src/Containers/ConfigManagement/Dashboard/widgets/PolicyViolationsBySeverity.js index 31cfc4459ee40..16e15c81307a2 100644 --- a/ui/apps/platform/src/Containers/ConfigManagement/Dashboard/widgets/PolicyViolationsBySeverity.js +++ b/ui/apps/platform/src/Containers/ConfigManagement/Dashboard/widgets/PolicyViolationsBySeverity.js @@ -5,7 +5,8 @@ import Sunburst from 'Components/visuals/Sunburst'; import Query from 'Components/ThrowingQuery'; import Loader from 'Components/Loader'; import networkStatuses from 'constants/networkStatuses'; -import { Link, useRouteMatch, useLocation } from 'react-router-dom'; +import useWorkflowMatch from 'hooks/useWorkflowMatch'; +import { Link, useLocation } from 'react-router-dom'; import { gql } from '@apollo/client'; import max from 'lodash/max'; import { severityValues, severities } from 'constants/severities'; @@ -59,7 +60,7 @@ function getCategorySeverity(category, violationsByCategory) { } const PolicyViolationsBySeverity = () => { - const match = useRouteMatch(); + const match = useWorkflowMatch(); const location = useLocation(); const searchParam = useContext(searchContext); const processData = (data) => { diff --git a/ui/apps/platform/src/Containers/ConfigManagement/Dashboard/widgets/SecretsMostUsedAcrossDeployments.js b/ui/apps/platform/src/Containers/ConfigManagement/Dashboard/widgets/SecretsMostUsedAcrossDeployments.js index db2684fa9a65d..5405734fad7c6 100644 --- a/ui/apps/platform/src/Containers/ConfigManagement/Dashboard/widgets/SecretsMostUsedAcrossDeployments.js +++ b/ui/apps/platform/src/Containers/ConfigManagement/Dashboard/widgets/SecretsMostUsedAcrossDeployments.js @@ -1,5 +1,5 @@ import React from 'react'; -import { Link, useRouteMatch, useLocation } from 'react-router-dom'; +import { Link, useLocation } from 'react-router-dom'; import { gql } from '@apollo/client'; import pluralize from 'pluralize'; import dateFns from 'date-fns'; @@ -9,6 +9,7 @@ import URLService from 'utils/URLService'; import entityTypes from 'constants/entityTypes'; import Query from 'Components/ThrowingQuery'; import Widget from 'Components/Widget'; +import useWorkflowMatch from 'hooks/useWorkflowMatch'; const QUERY = gql` query secrets { @@ -66,7 +67,7 @@ const getCertificateStatus = (files) => { }; const SecretsMostUsedAcrossDeployments = () => { - const match = useRouteMatch(); + const match = useWorkflowMatch(); const location = useLocation(); function processData(data) { diff --git a/ui/apps/platform/src/Containers/ConfigManagement/Dashboard/widgets/UsersWithMostClusterAdminRoles.js b/ui/apps/platform/src/Containers/ConfigManagement/Dashboard/widgets/UsersWithMostClusterAdminRoles.js index da83640b0d384..be6dfa037e786 100644 --- a/ui/apps/platform/src/Containers/ConfigManagement/Dashboard/widgets/UsersWithMostClusterAdminRoles.js +++ b/ui/apps/platform/src/Containers/ConfigManagement/Dashboard/widgets/UsersWithMostClusterAdminRoles.js @@ -1,12 +1,13 @@ import React from 'react'; import { gql } from '@apollo/client'; import Loader from 'Components/Loader'; -import { Link, useLocation, useRouteMatch } from 'react-router-dom'; +import { Link, useLocation } from 'react-router-dom'; import URLService from 'utils/URLService'; import entityTypes from 'constants/entityTypes'; import networkStatuses from 'constants/networkStatuses'; import Query from 'Components/ThrowingQuery'; import Widget from 'Components/Widget'; +import useWorkflowMatch from 'hooks/useWorkflowMatch'; import Lollipop from './Lollipop'; @@ -24,7 +25,7 @@ const QUERY = gql` `; const UsersWithMostClusterAdminRoles = () => { - const match = useRouteMatch(); + const match = useWorkflowMatch(); const location = useLocation(); function processData(data) { diff --git a/ui/apps/platform/src/Containers/ConfigManagement/Entity/Control.js b/ui/apps/platform/src/Containers/ConfigManagement/Entity/Control.js index 39233e8e7886e..e5949db4eae08 100644 --- a/ui/apps/platform/src/Containers/ConfigManagement/Entity/Control.js +++ b/ui/apps/platform/src/Containers/ConfigManagement/Entity/Control.js @@ -1,5 +1,5 @@ import React, { useContext } from 'react'; -import { useLocation, useRouteMatch } from 'react-router-dom'; +import { useLocation } from 'react-router-dom'; import { gql } from '@apollo/client'; import entityTypes from 'constants/entityTypes'; import useCases from 'constants/useCaseTypes'; @@ -10,6 +10,7 @@ import PageNotFound from 'Components/PageNotFound'; import CollapsibleSection from 'Components/CollapsibleSection'; import ControlDetails from 'Components/ControlDetails'; import RelatedEntityListCount from 'Components/RelatedEntityListCount'; +import useWorkflowMatch from 'hooks/useWorkflowMatch'; import isGQLLoading from 'utils/gqlLoading'; import Widget from 'Components/Widget'; import searchContext from 'Containers/searchContext'; @@ -45,7 +46,7 @@ const QUERY = gql` const Control = ({ id, entityListType, query, entityContext }) => { const location = useLocation(); - const match = useRouteMatch(); + const match = useWorkflowMatch(); const searchParam = useContext(searchContext); const variables = { diff --git a/ui/apps/platform/src/Containers/ConfigManagement/Entity/EntityTabs.js b/ui/apps/platform/src/Containers/ConfigManagement/Entity/EntityTabs.js index 7c4395b55083c..8291e4d3f61ba 100644 --- a/ui/apps/platform/src/Containers/ConfigManagement/Entity/EntityTabs.js +++ b/ui/apps/platform/src/Containers/ConfigManagement/Entity/EntityTabs.js @@ -1,10 +1,11 @@ import React from 'react'; import PropTypes from 'prop-types'; import entityTypes from 'constants/entityTypes'; +import useWorkflowMatch from 'hooks/useWorkflowMatch'; import entityLabels from 'messages/entity'; import pluralize from 'pluralize'; import URLService from 'utils/URLService'; -import { useRouteMatch, useLocation } from 'react-router-dom'; +import { useLocation } from 'react-router-dom'; import GroupedTabs from 'Components/GroupedTabs'; import entityTabsMap from '../entityTabRelationships'; @@ -39,7 +40,7 @@ const ENTITY_TO_TAB = { }; const EntityTabs = ({ entityType, entityListType, pageEntityId }) => { - const match = useRouteMatch(); + const match = useWorkflowMatch(); const location = useLocation(); function getTab(relationship) { diff --git a/ui/apps/platform/src/Containers/ConfigManagement/Entity/Page.js b/ui/apps/platform/src/Containers/ConfigManagement/Entity/Page.js index d29859ffaf0b5..01718743f5e78 100644 --- a/ui/apps/platform/src/Containers/ConfigManagement/Entity/Page.js +++ b/ui/apps/platform/src/Containers/ConfigManagement/Entity/Page.js @@ -1,5 +1,5 @@ import React, { useCallback, useEffect, useRef, useState } from 'react'; -import { useHistory, useLocation, useRouteMatch } from 'react-router-dom'; +import { useLocation, useNavigate } from 'react-router-dom'; import SidePanelAnimatedArea from 'Components/animations/SidePanelAnimatedArea'; import BackdropExporting from 'Components/PatternFly/BackdropExporting'; @@ -11,6 +11,7 @@ import configMgmtPaginationContext, { import searchContext from 'Containers/searchContext'; import workflowStateContext from 'Containers/workflowStateContext'; import useClickOutside from 'hooks/useClickOutside'; +import useWorkflowMatch from 'hooks/useWorkflowMatch'; import parseURL from 'utils/URLParser'; import URLService from 'utils/URLService'; import { WorkflowState } from 'utils/WorkflowState'; @@ -23,8 +24,8 @@ const EntityPage = () => { const sidePanelRef = useRef(null); const [isExporting, setIsExporting] = useState(false); const location = useLocation(); - const history = useHistory(); - const match = useRouteMatch(); + const navigate = useNavigate(); + const match = useWorkflowMatch(); const workflowState = parseURL(location); const { useCase, search, sort, paging } = workflowState; const pageState = new WorkflowState( @@ -53,8 +54,8 @@ const EntityPage = () => { useEffect(() => setFadeIn(false), [pageEntityId]); const closeSidePanel = useCallback(() => { - history.push(URLService.getURL(match, location).clearSidePanelParams().url()); - }, [history, match, location]); + navigate(URLService.getURL(match, location).clearSidePanelParams().url()); + }, [navigate, match, location]); useClickOutside(sidePanelRef, closeSidePanel, !!entityId1); diff --git a/ui/apps/platform/src/Containers/ConfigManagement/Entity/widgets/DeploymentsWithFailedPolicies.js b/ui/apps/platform/src/Containers/ConfigManagement/Entity/widgets/DeploymentsWithFailedPolicies.js index 45db829cd4018..9298b419c0921 100644 --- a/ui/apps/platform/src/Containers/ConfigManagement/Entity/widgets/DeploymentsWithFailedPolicies.js +++ b/ui/apps/platform/src/Containers/ConfigManagement/Entity/widgets/DeploymentsWithFailedPolicies.js @@ -12,10 +12,11 @@ import NoResultsMessage from 'Components/NoResultsMessage'; import Query from 'Components/ThrowingQuery'; import Loader from 'Components/Loader'; import { entityViolationsColumns } from 'constants/listColumns'; +import useWorkflowMatch from 'hooks/useWorkflowMatch'; import PolicySeverityIconText from 'Components/PatternFly/IconText/PolicySeverityIconText'; import Table, { defaultHeaderClassName, defaultColumnClassName } from 'Components/Table'; -import { useHistory, useLocation, useRouteMatch } from 'react-router-dom'; +import { useLocation, useNavigate } from 'react-router-dom'; import TableWidget from './TableWidget'; const getDeploymentsGroupedByPolicies = (data) => { @@ -38,14 +39,14 @@ const getDeploymentsGroupedByPolicies = (data) => { const Deployments = ({ original: policy, entityContext }) => { const { deployments } = policy; const columns = entityViolationsColumns[entityTypes.DEPLOYMENT](entityContext); - const history = useHistory(); + const navigate = useNavigate(); const location = useLocation(); - const match = useRouteMatch(); + const match = useWorkflowMatch(); function onRowClick(row) { const id = resolvePath(row, 'id'); const url = URLService.getURL(match, location).push(entityTypes.DEPLOYMENT, id).url(); - history.push(url); + navigate(url); } return ( { @@ -25,9 +26,9 @@ const TableWidget = ({ header, entityType, ...rest }) => { ...widgetProps } = { ...rest }; - const history = useHistory(); + const navigate = useNavigate(); const location = useLocation(); - const match = useRouteMatch(); + const match = useWorkflowMatch(); const headerComponents = ( @@ -35,7 +36,7 @@ const TableWidget = ({ header, entityType, ...rest }) => { function onRowClick(row) { const id = resolvePath(row, idAttribute); const url = URLService.getURL(match, location).push(entityType, id).url(); - history.push(url); + navigate(url); } return ( data.results; const Clusters = ({ className, selectedRowId, onRowClick, query, data }) => { const location = useLocation(); - const match = useRouteMatch(); + const match = useWorkflowMatch(); const autoFocusSearchInput = !selectedRowId; const tableColumns = buildTableColumns(match, location); const { [SEARCH_OPTIONS.POLICY_STATUS.CATEGORY]: policyStatus, ...restQuery } = query || {}; diff --git a/ui/apps/platform/src/Containers/ConfigManagement/List/Deployments.js b/ui/apps/platform/src/Containers/ConfigManagement/List/Deployments.js index 56edb9b976c14..e4a30441d29cc 100644 --- a/ui/apps/platform/src/Containers/ConfigManagement/List/Deployments.js +++ b/ui/apps/platform/src/Containers/ConfigManagement/List/Deployments.js @@ -1,5 +1,5 @@ import React, { useContext } from 'react'; -import { useLocation, useRouteMatch } from 'react-router-dom'; +import { useLocation } from 'react-router-dom'; import pluralize from 'pluralize'; import PolicyStatusIconText from 'Components/PatternFly/IconText/PolicyStatusIconText'; @@ -14,6 +14,7 @@ import entityTypes from 'constants/entityTypes'; import { deploymentSortFields } from 'constants/sortFields'; import { entityListPropTypes, entityListDefaultprops } from 'constants/entityPageProps'; import { CLIENT_SIDE_SEARCH_OPTIONS as SEARCH_OPTIONS } from 'constants/searchOptions'; +import useWorkflowMatch from 'hooks/useWorkflowMatch'; import { DEPLOYMENTS_QUERY } from 'queries/deployment'; import queryService from 'utils/queryService'; import URLService from 'utils/URLService'; @@ -192,7 +193,7 @@ const Deployments = ({ entityContext, }) => { const location = useLocation(); - const match = useRouteMatch(); + const match = useWorkflowMatch(); const searchParam = useContext(searchContext); const autoFocusSearchInput = !selectedRowId; diff --git a/ui/apps/platform/src/Containers/ConfigManagement/List/Images.js b/ui/apps/platform/src/Containers/ConfigManagement/List/Images.js index 79b0ae0cdadd1..44a258569ffc4 100644 --- a/ui/apps/platform/src/Containers/ConfigManagement/List/Images.js +++ b/ui/apps/platform/src/Containers/ConfigManagement/List/Images.js @@ -1,5 +1,5 @@ import React from 'react'; -import { useLocation, useRouteMatch } from 'react-router-dom'; +import { useLocation } from 'react-router-dom'; import pluralize from 'pluralize'; import { format } from 'date-fns'; @@ -13,6 +13,7 @@ import dateTimeFormat from 'constants/dateTimeFormat'; import { entityListPropTypes, entityListDefaultprops } from 'constants/entityPageProps'; import entityTypes from 'constants/entityTypes'; import { imageSortFields } from 'constants/sortFields'; +import useWorkflowMatch from 'hooks/useWorkflowMatch'; import { IMAGES_QUERY } from 'queries/image'; import queryService from 'utils/queryService'; import URLService from 'utils/URLService'; @@ -107,7 +108,7 @@ const Images = ({ entityContext, }) => { const location = useLocation(); - const match = useRouteMatch(); + const match = useWorkflowMatch(); const autoFocusSearchInput = !selectedRowId; const queryText = queryService.objectToWhereClause(query); const variables = queryText ? { query: queryText } : null; diff --git a/ui/apps/platform/src/Containers/ConfigManagement/List/List.js b/ui/apps/platform/src/Containers/ConfigManagement/List/List.js index 9fe1d05dec187..af1833332194c 100644 --- a/ui/apps/platform/src/Containers/ConfigManagement/List/List.js +++ b/ui/apps/platform/src/Containers/ConfigManagement/List/List.js @@ -1,6 +1,6 @@ import React, { useState, useContext } from 'react'; import PropTypes from 'prop-types'; -import { useRouteMatch, useLocation, useHistory } from 'react-router-dom'; // Updated imports +import { useLocation, useNavigate } from 'react-router-dom'; // Updated imports import pluralize from 'pluralize'; import resolvePath from 'object-resolve-path'; @@ -16,6 +16,7 @@ import workflowStateContext from 'Containers/workflowStateContext'; import { searchCategories as searchCategoryTypes } from 'constants/entityTypes'; import useCases from 'constants/useCaseTypes'; import { LIST_PAGE_SIZE } from 'constants/workflowPages.constants'; +import useWorkflowMatch from 'hooks/useWorkflowMatch'; import entityLabels from 'messages/entity'; import { SEARCH_OPTIONS_QUERY } from 'queries/search'; import isGQLLoading from 'utils/gqlLoading'; @@ -41,9 +42,9 @@ const List = ({ autoFocusSearchInput, noDataText, }) => { - const match = useRouteMatch(); + const match = useWorkflowMatch(); const location = useLocation(); - const history = useHistory(); + const navigate = useNavigate(); const workflowState = useContext(workflowStateContext); const configMgmtPagination = useContext(configMgmtPaginationContext); const page = workflowState.paging[configMgmtPagination.pageParam]; @@ -55,7 +56,7 @@ const List = ({ function onRowClickHandler(row) { const id = resolvePath(row, idAttribute); const url = URLService.getURL(match, location).push(id).url(); - history.push(url); + navigate(url); } const categories = [searchCategoryTypes[entityType]]; @@ -94,7 +95,7 @@ const List = ({ } function setPage(newPage) { - history.push(workflowState.setPage(newPage).toUrl()); + navigate(workflowState.setPage(newPage).toUrl()); } function onSortedChange(newSort, column) { @@ -109,7 +110,7 @@ const List = ({ }); const url = workflowState.setSort(workflowSort).toUrl(); - history.push(url); + navigate(url); } function getHeaderComponents(totalRows) { diff --git a/ui/apps/platform/src/Containers/ConfigManagement/List/ListFrontendPaginated.js b/ui/apps/platform/src/Containers/ConfigManagement/List/ListFrontendPaginated.js index 38ad63b344261..3f7c22553e5c2 100644 --- a/ui/apps/platform/src/Containers/ConfigManagement/List/ListFrontendPaginated.js +++ b/ui/apps/platform/src/Containers/ConfigManagement/List/ListFrontendPaginated.js @@ -1,6 +1,6 @@ import React, { useState } from 'react'; import PropTypes from 'prop-types'; -import { useRouteMatch, useLocation, useHistory } from 'react-router-dom'; +import { useLocation, useNavigate } from 'react-router-dom'; import pluralize from 'pluralize'; import resolvePath from 'object-resolve-path'; @@ -13,6 +13,7 @@ import TablePagination from 'Components/TablePagination'; import URLSearchInput from 'Components/URLSearchInput'; import { searchCategories as searchCategoryTypes } from 'constants/entityTypes'; import useCases from 'constants/useCaseTypes'; +import useWorkflowMatch from 'hooks/useWorkflowMatch'; import entityLabels from 'messages/entity'; import { SEARCH_OPTIONS_QUERY } from 'queries/search'; import isGQLLoading from 'utils/gqlLoading'; @@ -34,15 +35,15 @@ const ListFrontendPaginated = ({ autoFocusSearchInput, noDataText, }) => { - const match = useRouteMatch(); + const match = useWorkflowMatch(); const location = useLocation(); - const history = useHistory(); + const navigate = useNavigate(); const [page, setPage] = useState(0); function onRowClickHandler(row) { const id = resolvePath(row, idAttribute); const url = URLService.getURL(match, location).push(id).url(); - history.push(url); + navigate(url); } const categories = [searchCategoryTypes[entityType]]; diff --git a/ui/apps/platform/src/Containers/ConfigManagement/List/Namespaces.js b/ui/apps/platform/src/Containers/ConfigManagement/List/Namespaces.js index be0ec854c74c5..ffb38b31f75da 100644 --- a/ui/apps/platform/src/Containers/ConfigManagement/List/Namespaces.js +++ b/ui/apps/platform/src/Containers/ConfigManagement/List/Namespaces.js @@ -1,5 +1,5 @@ import React, { useContext } from 'react'; -import { useLocation, useRouteMatch } from 'react-router-dom'; +import { useLocation } from 'react-router-dom'; import pluralize from 'pluralize'; import PolicyStatusIconText from 'Components/PatternFly/IconText/PolicyStatusIconText'; @@ -14,12 +14,12 @@ import { entityListPropTypes, entityListDefaultprops } from 'constants/entityPag import entityTypes from 'constants/entityTypes'; import { CLIENT_SIDE_SEARCH_OPTIONS as SEARCH_OPTIONS } from 'constants/searchOptions'; import { namespaceSortFields } from 'constants/sortFields'; +import useWorkflowMatch from 'hooks/useWorkflowMatch'; import { NAMESPACES_NO_POLICIES_QUERY } from 'queries/namespace'; import queryService from 'utils/queryService'; import URLService from 'utils/URLService'; import { getConfigMgmtPathForEntitiesAndId } from '../entities'; import List from './List'; - import filterByPolicyStatus from './utilities/filterByPolicyStatus'; export const defaultNamespaceSort = [ @@ -209,7 +209,7 @@ const Namespaces = ({ entityContext, }) => { const location = useLocation(); - const match = useRouteMatch(); + const match = useWorkflowMatch(); const searchParam = useContext(searchContext); const autoFocusSearchInput = !selectedRowId; diff --git a/ui/apps/platform/src/Containers/ConfigManagement/List/Nodes.js b/ui/apps/platform/src/Containers/ConfigManagement/List/Nodes.js index 557e5eeca8abd..1459b223670e3 100644 --- a/ui/apps/platform/src/Containers/ConfigManagement/List/Nodes.js +++ b/ui/apps/platform/src/Containers/ConfigManagement/List/Nodes.js @@ -1,5 +1,5 @@ import React from 'react'; -import { useLocation, useRouteMatch } from 'react-router-dom'; +import { useLocation } from 'react-router-dom'; import { gql } from '@apollo/client'; import { format } from 'date-fns'; import pluralize from 'pluralize'; @@ -14,6 +14,7 @@ import dateTimeFormat from 'constants/dateTimeFormat'; import entityTypes from 'constants/entityTypes'; import { entityListPropTypes, entityListDefaultprops } from 'constants/entityPageProps'; import { nodeSortFields } from 'constants/sortFields'; +import useWorkflowMatch from 'hooks/useWorkflowMatch'; import queryService from 'utils/queryService'; import URLService from 'utils/URLService'; import { getConfigMgmtPathForEntitiesAndId } from '../entities'; @@ -167,7 +168,7 @@ const Nodes = ({ totalResults, entityContext, }) => { - const match = useRouteMatch(); + const match = useWorkflowMatch(); const location = useLocation(); const autoFocusSearchInput = !selectedRowId; const tableColumns = buildTableColumns(match, location, entityContext); diff --git a/ui/apps/platform/src/Containers/ConfigManagement/List/Page.js b/ui/apps/platform/src/Containers/ConfigManagement/List/Page.js index 3b034d6f74b15..f7f7c4c3ccbeb 100644 --- a/ui/apps/platform/src/Containers/ConfigManagement/List/Page.js +++ b/ui/apps/platform/src/Containers/ConfigManagement/List/Page.js @@ -1,5 +1,5 @@ import React, { useCallback, useContext, useRef, useState } from 'react'; -import { useHistory, useLocation, useRouteMatch } from 'react-router-dom'; +import { useLocation, useNavigate } from 'react-router-dom'; import pluralize from 'pluralize'; import upperFirst from 'lodash/upperFirst'; import startCase from 'lodash/startCase'; @@ -18,6 +18,7 @@ import searchContext from 'Containers/searchContext'; import { searchParams } from 'constants/searchParams'; import workflowStateContext from 'Containers/workflowStateContext'; import useClickOutside from 'hooks/useClickOutside'; +import useWorkflowMatch from 'hooks/useWorkflowMatch'; import entityLabels from 'messages/entity'; import parseURL from 'utils/URLParser'; import URLService from 'utils/URLService'; @@ -29,8 +30,8 @@ import SidePanel from '../SidePanel/SidePanel'; const ListPage = () => { const sidePanelRef = useRef(null); const location = useLocation(); - const history = useHistory(); - const match = useRouteMatch(); + const navigate = useNavigate(); + const match = useWorkflowMatch(); const [isExporting, setIsExporting] = useState(false); const workflowState = parseURL(location); @@ -49,14 +50,14 @@ const ListPage = () => { const searchParam = useContext(searchContext); const closeSidePanel = useCallback(() => { - history.push(URLService.getURL(match, location).clearSidePanelParams().url()); - }, [history, match, location]); + navigate(URLService.getURL(match, location).clearSidePanelParams().url()); + }, [navigate, match, location]); useClickOutside(sidePanelRef, closeSidePanel, !!entityId1); function onRowClick(entityId) { const urlBuilder = URLService.getURL(match, location).push(entityId); - history.push(urlBuilder.url()); + navigate(urlBuilder.url()); } const header = upperFirst(pluralize(entityLabels[pageEntityListType])); diff --git a/ui/apps/platform/src/Containers/ConfigManagement/List/Roles.js b/ui/apps/platform/src/Containers/ConfigManagement/List/Roles.js index e96e5c7003435..c7b65e447964c 100644 --- a/ui/apps/platform/src/Containers/ConfigManagement/List/Roles.js +++ b/ui/apps/platform/src/Containers/ConfigManagement/List/Roles.js @@ -1,5 +1,5 @@ import React from 'react'; -import { useLocation, useRouteMatch } from 'react-router-dom'; +import { useLocation } from 'react-router-dom'; import pluralize from 'pluralize'; import { format } from 'date-fns'; @@ -13,6 +13,7 @@ import dateTimeFormat from 'constants/dateTimeFormat'; import { entityListPropTypes, entityListDefaultprops } from 'constants/entityPageProps'; import entityTypes from 'constants/entityTypes'; import { roleSortFields } from 'constants/sortFields'; +import useWorkflowMatch from 'hooks/useWorkflowMatch'; import { K8S_ROLES_QUERY } from 'queries/role'; import queryService from 'utils/queryService'; import URLService from 'utils/URLService'; @@ -228,7 +229,7 @@ const Roles = ({ entityContext, }) => { const location = useLocation(); - const match = useRouteMatch(); + const match = useWorkflowMatch(); const autoFocusSearchInput = !selectedRowId; const tableColumns = buildTableColumns(match, location, entityContext); const queryText = queryService.objectToWhereClause(query); diff --git a/ui/apps/platform/src/Containers/ConfigManagement/List/Secrets.js b/ui/apps/platform/src/Containers/ConfigManagement/List/Secrets.js index 4dacac1f21f9e..c7fb36ef3e9b1 100644 --- a/ui/apps/platform/src/Containers/ConfigManagement/List/Secrets.js +++ b/ui/apps/platform/src/Containers/ConfigManagement/List/Secrets.js @@ -1,5 +1,5 @@ import React from 'react'; -import { useLocation, useRouteMatch } from 'react-router-dom'; +import { useLocation } from 'react-router-dom'; import uniq from 'lodash/uniq'; import { format } from 'date-fns'; import pluralize from 'pluralize'; @@ -13,6 +13,7 @@ import TableCellLink from 'Components/TableCellLink'; import dateTimeFormat from 'constants/dateTimeFormat'; import entityTypes from 'constants/entityTypes'; import { entityListPropTypes, entityListDefaultprops } from 'constants/entityPageProps'; +import useWorkflowMatch from 'hooks/useWorkflowMatch'; import { SECRETS_QUERY } from 'queries/secret'; import { secretSortFields } from 'constants/sortFields'; import queryService from 'utils/queryService'; @@ -159,7 +160,7 @@ const Secrets = ({ entityContext, }) => { const location = useLocation(); - const match = useRouteMatch(); + const match = useWorkflowMatch(); const autoFocusSearchInput = !selectedRowId; const tableColumns = buildTableColumns(match, location, entityContext); const queryText = queryService.objectToWhereClause(query); diff --git a/ui/apps/platform/src/Containers/ConfigManagement/List/ServiceAccounts.js b/ui/apps/platform/src/Containers/ConfigManagement/List/ServiceAccounts.js index b5cdd8466a16c..b4141831df2da 100644 --- a/ui/apps/platform/src/Containers/ConfigManagement/List/ServiceAccounts.js +++ b/ui/apps/platform/src/Containers/ConfigManagement/List/ServiceAccounts.js @@ -1,5 +1,5 @@ import React from 'react'; -import { useLocation, useRouteMatch } from 'react-router-dom'; +import { useLocation } from 'react-router-dom'; import pluralize from 'pluralize'; import { @@ -11,6 +11,7 @@ import TableCellLink from 'Components/TableCellLink'; import { entityListPropTypes, entityListDefaultprops } from 'constants/entityPageProps'; import entityTypes from 'constants/entityTypes'; import { serviceAccountSortFields } from 'constants/sortFields'; +import useWorkflowMatch from 'hooks/useWorkflowMatch'; import { SERVICE_ACCOUNTS_QUERY } from 'queries/serviceAccount'; import { sortValueByLength } from 'sorters/sorters'; import queryService from 'utils/queryService'; @@ -177,7 +178,7 @@ const ServiceAccounts = ({ entityContext, }) => { const location = useLocation(); - const match = useRouteMatch(); + const match = useWorkflowMatch(); const autoFocusSearchInput = !selectedRowId; const tableColumns = buildTableColumns(match, location, entityContext); const queryText = queryService.objectToWhereClause(query); diff --git a/ui/apps/platform/src/Containers/ConfigManagement/List/Subjects.js b/ui/apps/platform/src/Containers/ConfigManagement/List/Subjects.js index e3f2c7fba2abc..5ad3c51211497 100644 --- a/ui/apps/platform/src/Containers/ConfigManagement/List/Subjects.js +++ b/ui/apps/platform/src/Containers/ConfigManagement/List/Subjects.js @@ -1,5 +1,5 @@ import React from 'react'; -import { useLocation, useRouteMatch } from 'react-router-dom'; +import { useLocation } from 'react-router-dom'; import pluralize from 'pluralize'; import { @@ -11,6 +11,7 @@ import TableCellLink from 'Components/TableCellLink'; import { entityListPropTypes, entityListDefaultprops } from 'constants/entityPageProps'; import entityTypes from 'constants/entityTypes'; import { subjectSortFields } from 'constants/sortFields'; +import useWorkflowMatch from 'hooks/useWorkflowMatch'; import { SUBJECTS_QUERY } from 'queries/subject'; import queryService from 'utils/queryService'; import URLService from 'utils/URLService'; @@ -106,7 +107,7 @@ const createTableRows = (data) => data?.results || []; const Subjects = ({ selectedRowId, onRowClick, query, className, data, totalResults }) => { const location = useLocation(); - const match = useRouteMatch(); + const match = useWorkflowMatch(); const autoFocusSearchInput = !selectedRowId; const tableColumns = buildTableColumns(match, location); const queryText = queryService.objectToWhereClause(query); diff --git a/ui/apps/platform/src/Containers/ConfigManagement/Page.js b/ui/apps/platform/src/Containers/ConfigManagement/Page.js index 0c74c8705cce8..227cf825c6944 100644 --- a/ui/apps/platform/src/Containers/ConfigManagement/Page.js +++ b/ui/apps/platform/src/Containers/ConfigManagement/Page.js @@ -1,31 +1,47 @@ import React from 'react'; -import { Route, Switch } from 'react-router-dom'; -import { workflowPaths } from 'routePaths'; +import { Route, Routes } from 'react-router-dom'; import isEqual from 'lodash/isEqual'; import PageNotFound from 'Components/PageNotFound'; import searchContext from 'Containers/searchContext'; import { searchParams } from 'constants/searchParams'; import useCases from 'constants/useCaseTypes'; + import DashboardPage from './Dashboard/Page'; import ListPage from './List/Page'; import EntityPage from './Entity/Page'; +const listPath = `:entityId1?/:entityType2?/:entityId2?`; +const entityPath = `:pageEntityId?/:entityType1?/:entityId1?/:entityType2?/:entityId2?`; + const Page = () => ( - - - - - - - - - - - - - - + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + ); diff --git a/ui/apps/platform/src/Containers/ConfigManagement/SidePanel/BackButton.js b/ui/apps/platform/src/Containers/ConfigManagement/SidePanel/BackButton.js index 441578d3ea62f..b9ae816380c58 100644 --- a/ui/apps/platform/src/Containers/ConfigManagement/SidePanel/BackButton.js +++ b/ui/apps/platform/src/Containers/ConfigManagement/SidePanel/BackButton.js @@ -1,14 +1,15 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { Link, useLocation, useRouteMatch } from 'react-router-dom'; +import { Link, useLocation } from 'react-router-dom'; import URLService from 'utils/URLService'; +import useWorkflowMatch from 'hooks/useWorkflowMatch'; import { ArrowLeft } from 'react-feather'; import EntityIcon from 'Components/EntityIcon'; const BackButton = ({ entityType1, entityListType2, entityId2 }) => { const location = useLocation(); - const match = useRouteMatch(); + const match = useWorkflowMatch(); if (entityListType2 || entityId2) { const link = URLService.getURL(match, location).pop().url(); diff --git a/ui/apps/platform/src/Containers/ConfigManagement/SidePanel/BreadCrumbs.js b/ui/apps/platform/src/Containers/ConfigManagement/SidePanel/BreadCrumbs.js index 075fd30782ecc..efba610a992e4 100644 --- a/ui/apps/platform/src/Containers/ConfigManagement/SidePanel/BreadCrumbs.js +++ b/ui/apps/platform/src/Containers/ConfigManagement/SidePanel/BreadCrumbs.js @@ -3,9 +3,11 @@ import PropTypes from 'prop-types'; import pluralize from 'pluralize'; import upperFirst from 'lodash/upperFirst'; import { ChevronRight } from 'react-feather'; -import { Link, useLocation, useRouteMatch } from 'react-router-dom'; +import { Link, useLocation } from 'react-router-dom'; import useEntityName from 'hooks/useEntityName'; +import useWorkflowMatch from 'hooks/useWorkflowMatch'; + import entityLabels from 'messages/entity'; import URLService from 'utils/URLService'; @@ -85,7 +87,7 @@ const getMaxWidthClass = (length) => { const BreadCrumbLinks = (props) => { const location = useLocation(); - const match = useRouteMatch(); + const match = useWorkflowMatch(); // disable because unused history might be specified for rest spread idiom. const { className, ...params } = props; diff --git a/ui/apps/platform/src/Containers/ConfigManagement/SidePanel/SidePanel.js b/ui/apps/platform/src/Containers/ConfigManagement/SidePanel/SidePanel.js index 49a2d87145e14..a3010c2b7beab 100644 --- a/ui/apps/platform/src/Containers/ConfigManagement/SidePanel/SidePanel.js +++ b/ui/apps/platform/src/Containers/ConfigManagement/SidePanel/SidePanel.js @@ -1,6 +1,6 @@ import React, { useContext } from 'react'; import PropTypes from 'prop-types'; -import { useLocation, useHistory, useRouteMatch, Link } from 'react-router-dom'; +import { useLocation, useNavigate, Link } from 'react-router-dom'; import { ExternalLinkAltIcon } from '@patternfly/react-icons'; import CloseButton from 'Components/CloseButton'; @@ -8,6 +8,7 @@ import { PanelNew, PanelBody, PanelHead, PanelHeadEnd } from 'Components/Panel'; import searchContext from 'Containers/searchContext'; import Entity from 'Containers/ConfigManagement/Entity'; import workflowStateContext from 'Containers/workflowStateContext'; +import useWorkflowMatch from 'hooks/useWorkflowMatch'; import parseURL from 'utils/URLParser'; import URLService from 'utils/URLService'; import BreadCrumbs from './BreadCrumbs'; @@ -23,9 +24,9 @@ const SidePanel = ({ entityId2, query, }) => { - const match = useRouteMatch(); + const match = useWorkflowMatch(); const location = useLocation(); - const history = useHistory(); + const navigate = useNavigate(); const workflowState = parseURL(location); const searchParam = useContext(searchContext); const isList = !entityId1 || (entityListType2 && !entityId2); @@ -56,7 +57,7 @@ const SidePanel = ({ } function onClose() { - history.push(URLService.getURL(match, location).clearSidePanelParams().url()); + navigate(URLService.getURL(match, location).clearSidePanelParams().url()); } const entityId = getCurrentEntityId(); diff --git a/ui/apps/platform/src/Containers/Dashboard/Widgets/AgingImagesChart.tsx b/ui/apps/platform/src/Containers/Dashboard/Widgets/AgingImagesChart.tsx index 4d1eada82db06..daba615d811be 100644 --- a/ui/apps/platform/src/Containers/Dashboard/Widgets/AgingImagesChart.tsx +++ b/ui/apps/platform/src/Containers/Dashboard/Widgets/AgingImagesChart.tsx @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import { useHistory } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import { Chart, ChartAxis, @@ -115,7 +115,7 @@ function makeChartData( } function AgingImagesChart({ searchFilter, timeRanges, timeRangeCounts }: AgingImagesChartProps) { - const history = useHistory(); + const navigate = useNavigate(); const [widgetContainer, setWidgetContainer] = useState(null); const widgetContainerResizeEntry = useResizeObserver(widgetContainer); const chartData = makeChartData(searchFilter, timeRanges, timeRangeCounts); @@ -163,7 +163,7 @@ function AgingImagesChart({ searchFilter, timeRanges, timeRangeCounts }: AgingIm data={barData} labels={({ datum }) => `${Math.round(parseInt(datum.y, 10))}`} style={{ data: { fill } }} - events={[navigateOnClickEvent(history, () => labelLink)]} + events={[navigateOnClickEvent(navigate, () => labelLink)]} /> ); })} diff --git a/ui/apps/platform/src/Containers/Dashboard/Widgets/ComplianceLevelsByStandardChart.tsx b/ui/apps/platform/src/Containers/Dashboard/Widgets/ComplianceLevelsByStandardChart.tsx index 8884905431fd7..41fbb99bab78a 100644 --- a/ui/apps/platform/src/Containers/Dashboard/Widgets/ComplianceLevelsByStandardChart.tsx +++ b/ui/apps/platform/src/Containers/Dashboard/Widgets/ComplianceLevelsByStandardChart.tsx @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import { useHistory } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import { Chart, ChartAxis, @@ -36,7 +36,7 @@ type ComplianceLevelsByStandardChartProps = { function ComplianceLevelsByStandardChart({ complianceLevelsByStandard, }: ComplianceLevelsByStandardChartProps) { - const history = useHistory(); + const navigate = useNavigate(); const [widgetContainer, setWidgetContainer] = useState(null); const widgetContainerResizeEntry = useResizeObserver(widgetContainer); @@ -80,7 +80,7 @@ function ComplianceLevelsByStandardChart({ barWidth={defaultChartBarWidth} data={[{ x: name, y: passing, link }]} labels={({ datum }) => `${Math.round(parseInt(datum.y, 10))}%`} - events={[navigateOnClickEvent(history, () => link)]} + events={[navigateOnClickEvent(navigate, () => link)]} /> ))} diff --git a/ui/apps/platform/src/Containers/Dashboard/Widgets/ViolationsByPolicyCategoryChart.tsx b/ui/apps/platform/src/Containers/Dashboard/Widgets/ViolationsByPolicyCategoryChart.tsx index acc9d7555031a..3d442d724e766 100644 --- a/ui/apps/platform/src/Containers/Dashboard/Widgets/ViolationsByPolicyCategoryChart.tsx +++ b/ui/apps/platform/src/Containers/Dashboard/Widgets/ViolationsByPolicyCategoryChart.tsx @@ -1,5 +1,5 @@ import React, { useState, useCallback } from 'react'; -import { useHistory } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import { Chart, ChartAxis, @@ -166,7 +166,7 @@ function ViolationsByPolicyCategoryChart({ setHiddenSeverities, searchFilter, }: ViolationsByPolicyCategoryChartProps) { - const history = useHistory(); + const navigate = useNavigate(); const [widgetContainer, setWidgetContainer] = useState(null); const widgetContainerResizeEntry = useResizeObserver(widgetContainer); @@ -203,7 +203,7 @@ function ViolationsByPolicyCategoryChart({ events={[ // TS2339: Property 'xName' does not exist on type '{}'. // eslint-disable-next-line @typescript-eslint/no-explicit-any - navigateOnClickEvent(history, (targetProps: any) => { + navigateOnClickEvent(navigate, (targetProps: any) => { const category = targetProps?.datum?.xName; return linkForViolationsCategory( category, diff --git a/ui/apps/platform/src/Containers/Integrations/IntegrationForm/IntegrationForm.tsx b/ui/apps/platform/src/Containers/Integrations/IntegrationForm/IntegrationForm.tsx index e8ca15c31b473..41bf25ffdfb3d 100644 --- a/ui/apps/platform/src/Containers/Integrations/IntegrationForm/IntegrationForm.tsx +++ b/ui/apps/platform/src/Containers/Integrations/IntegrationForm/IntegrationForm.tsx @@ -1,5 +1,5 @@ -import React, { FunctionComponent, ReactElement } from 'react'; -import { useHistory } from 'react-router-dom'; +import React, { FunctionComponent, ReactElement, useEffect } from 'react'; +import { useNavigate } from 'react-router-dom'; import { isUserResource } from 'Containers/AccessControl/traits'; import useCentralCapabilities from 'hooks/useCentralCapabilities'; @@ -124,15 +124,17 @@ function IntegrationForm({ initialValues, isEditable, }: IntegrationFormProps): ReactElement { - const history = useHistory(); + const navigate = useNavigate(); const { isCentralCapabilityAvailable } = useCentralCapabilities(); const canUseCloudBackupIntegrations = isCentralCapabilityAvailable( 'centralCanUseCloudBackupIntegrations' ); - if (!canUseCloudBackupIntegrations && source === 'backups') { - history.replace(integrationsPath); - } + useEffect(() => { + if (!canUseCloudBackupIntegrations && source === 'backups') { + navigate(integrationsPath, { replace: true }); + } + }, [canUseCloudBackupIntegrations, source, navigate]); const Form: FunctionComponent> = ComponentFormMap?.[source]?.[type]; diff --git a/ui/apps/platform/src/Containers/Integrations/IntegrationsListPage/IntegrationsListPage.tsx b/ui/apps/platform/src/Containers/Integrations/IntegrationsListPage/IntegrationsListPage.tsx index 48427e9cc0f1d..569e774b00a16 100644 --- a/ui/apps/platform/src/Containers/Integrations/IntegrationsListPage/IntegrationsListPage.tsx +++ b/ui/apps/platform/src/Containers/Integrations/IntegrationsListPage/IntegrationsListPage.tsx @@ -1,4 +1,4 @@ -import React, { ReactElement, useState } from 'react'; +import React, { ReactElement, useEffect, useState } from 'react'; import { PageSection, PageSectionVariants, @@ -8,7 +8,7 @@ import { Divider, Flex, } from '@patternfly/react-core'; -import { useParams, useHistory } from 'react-router-dom'; +import { useParams, useNavigate } from 'react-router-dom'; import { connect } from 'react-redux'; import BreadcrumbItemLink from 'Components/BreadcrumbItemLink'; @@ -31,6 +31,8 @@ import { getIsMachineAccessConfig, getIsSignatureIntegration, getIsScannerV4, + IntegrationSource, + IntegrationType, } from '../utils/integrationUtils'; import { @@ -46,19 +48,21 @@ function IntegrationsListPage({ deleteMachineAccessConfigs, deleteCloudSources, }): ReactElement { - const { source, type } = useParams(); + const { source, type } = useParams() as { source: IntegrationSource; type: IntegrationType }; const integrations = useIntegrations({ source, type }); const [deletingIntegrationIds, setDeletingIntegrationIds] = useState([]); - const history = useHistory(); + const navigate = useNavigate(); const { isCentralCapabilityAvailable } = useCentralCapabilities(); const canUseCloudBackupIntegrations = isCentralCapabilityAvailable( 'centralCanUseCloudBackupIntegrations' ); - if (!canUseCloudBackupIntegrations && source === 'backups') { - history.replace(integrationsPath); - } + useEffect(() => { + if (!canUseCloudBackupIntegrations && source === 'backups') { + navigate(integrationsPath, { replace: true }); + } + }, [canUseCloudBackupIntegrations, source, navigate]); const typeLabel = getIntegrationLabel(source, type); const isAPIToken = getIsAPIToken(source, type); diff --git a/ui/apps/platform/src/Containers/Integrations/IntegrationsListPage/IntegrationsTable.tsx b/ui/apps/platform/src/Containers/Integrations/IntegrationsListPage/IntegrationsTable.tsx index 561a9f6fbb113..e4b3810474f94 100644 --- a/ui/apps/platform/src/Containers/Integrations/IntegrationsListPage/IntegrationsTable.tsx +++ b/ui/apps/platform/src/Containers/Integrations/IntegrationsListPage/IntegrationsTable.tsx @@ -21,7 +21,13 @@ import TableCellValue from 'Components/TableCellValue/TableCellValue'; import { isUserResource } from 'Containers/AccessControl/traits'; import useIntegrationPermissions from '../hooks/useIntegrationPermissions'; import usePageState from '../hooks/usePageState'; -import { Integration, getIsAPIToken, getIsClusterInitBundle } from '../utils/integrationUtils'; +import { + Integration, + IntegrationSource, + IntegrationType, + getIsAPIToken, + getIsClusterInitBundle, +} from '../utils/integrationUtils'; import tableColumnDescriptor from '../utils/tableColumnDescriptor'; import DownloadCAConfigBundle from './DownloadCAConfigBundle'; @@ -54,7 +60,7 @@ function IntegrationsTable({ isReadOnly, }: IntegrationsTableProps): React.ReactElement { const permissions = useIntegrationPermissions(); - const { source, type } = useParams(); + const { source, type } = useParams() as { source: IntegrationSource; type: IntegrationType }; const { getPathToCreate, getPathToEdit, getPathToViewDetails } = usePageState(); const { selected, diff --git a/ui/apps/platform/src/Containers/Integrations/IntegrationsPage.tsx b/ui/apps/platform/src/Containers/Integrations/IntegrationsPage.tsx index 95cea9e67977f..10841fb06b24c 100644 --- a/ui/apps/platform/src/Containers/Integrations/IntegrationsPage.tsx +++ b/ui/apps/platform/src/Containers/Integrations/IntegrationsPage.tsx @@ -1,14 +1,7 @@ import React, { ReactElement } from 'react'; -import { Redirect, Route, Switch } from 'react-router-dom'; +import { Navigate, Route, Routes } from 'react-router-dom'; -import { - clustersInitBundlesPath, - integrationsPath, - integrationsListPath, - integrationCreatePath, - integrationEditPath, - integrationDetailsPath, -} from 'routePaths'; +import { clustersInitBundlesPath } from 'routePaths'; import IntegrationsNotFoundPage from './IntegrationsNotFoundPage'; import IntegrationTilesPage from './IntegrationTiles/IntegrationTilesPage'; @@ -20,33 +13,22 @@ import IntegrationDetailsPage from './IntegrationDetailsPage'; const Page = (): ReactElement => { // Redirect from list or view page to cluster init bundles list. return ( - - - - + + } /> } + path="authProviders/clusterInitBundle" + element={} /> - - - - - - - - - - - - - - - - + } + /> + } /> + } /> + } /> + } /> + } /> + ); }; diff --git a/ui/apps/platform/src/Containers/Integrations/hooks/useIntegrationActions.ts b/ui/apps/platform/src/Containers/Integrations/hooks/useIntegrationActions.ts index 535205bdb99fa..2dda9801ac533 100644 --- a/ui/apps/platform/src/Containers/Integrations/hooks/useIntegrationActions.ts +++ b/ui/apps/platform/src/Containers/Integrations/hooks/useIntegrationActions.ts @@ -1,4 +1,4 @@ -import { useHistory } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import { integrationsPath } from 'routePaths'; import { @@ -31,7 +31,7 @@ export type UseIntegrationActionsResult = { }; function useIntegrationActions(): UseIntegrationActionsResult { - const history = useHistory(); + const navigate = useNavigate(); const { isEditing, params: { source, type }, @@ -48,18 +48,18 @@ function useIntegrationActions(): UseIntegrationActionsResult { typeof updatePassword === 'boolean' ? await saveIntegration(source, data, { updatePassword }) : await saveIntegrationV2(source, data); - history.push(integrationsListPath); + navigate(integrationsListPath); } else if (type === 'apitoken') { responseData = await generateAPIToken(data); } else if (type === 'clusterInitBundle') { responseData = await generateClusterInitBundle(data); } else if (type === 'machineAccess') { responseData = await createMachineAccessConfig(data); - history.goBack(); + navigate(-1); } else { responseData = await createIntegration(source, data); // we only want to redirect when creating a new (non-apitoken and non-clusterinitbundle) integration - history.goBack(); + navigate(-1); } fetchIntegrations(); @@ -86,7 +86,7 @@ function useIntegrationActions(): UseIntegrationActionsResult { } function onCancel() { - history.push(integrationsListPath); + navigate(integrationsListPath); } return { onSave, onTest, onCancel }; diff --git a/ui/apps/platform/src/Containers/Integrations/hooks/useIntegrationPermissions.test.js b/ui/apps/platform/src/Containers/Integrations/hooks/useIntegrationPermissions.test.js index 8462fbb232002..59513aee8c6b8 100644 --- a/ui/apps/platform/src/Containers/Integrations/hooks/useIntegrationPermissions.test.js +++ b/ui/apps/platform/src/Containers/Integrations/hooks/useIntegrationPermissions.test.js @@ -2,13 +2,11 @@ import React from 'react'; import { renderHook } from '@testing-library/react'; import { Provider } from 'react-redux'; -import { createBrowserHistory as createHistory } from 'history'; +import { HistoryRouter as Router } from 'redux-first-history/rr6'; import configureStore from 'store/configureStore'; import useIntegrationPermissions from './useIntegrationPermissions'; -const history = createHistory(); - const initialStoreWrite = { app: { roles: { @@ -45,10 +43,14 @@ const initialStoreNone = { describe('useIntegrationPermissions', () => { it('should return write permissions', () => { - const store = configureStore(initialStoreWrite, history); + const { store, history } = configureStore(initialStoreWrite); const { result } = renderHook(() => useIntegrationPermissions(), { - wrapper: ({ children }) => {children}, + wrapper: ({ children }) => ( + + {children} + + ), }); expect(result.current.authProviders.write).toEqual(true); @@ -62,10 +64,14 @@ describe('useIntegrationPermissions', () => { }); it('should return read permissions', () => { - const store = configureStore(initialStoreRead, history); + const { store, history } = configureStore(initialStoreRead); const { result } = renderHook(() => useIntegrationPermissions(), { - wrapper: ({ children }) => {children}, + wrapper: ({ children }) => ( + + {children} + + ), }); expect(result.current.authProviders.write).toEqual(false); @@ -79,10 +85,14 @@ describe('useIntegrationPermissions', () => { }); it('should return no permissions', () => { - const store = configureStore(initialStoreNone, history); + const { store, history } = configureStore(initialStoreNone); const { result } = renderHook(() => useIntegrationPermissions(), { - wrapper: ({ children }) => {children}, + wrapper: ({ children }) => ( + + {children} + + ), }); expect(result.current.authProviders.write).toEqual(false); diff --git a/ui/apps/platform/src/Containers/Integrations/hooks/useIntegrationPermissions.ts b/ui/apps/platform/src/Containers/Integrations/hooks/useIntegrationPermissions.ts index ff56d5ba0285e..ef28f206213df 100644 --- a/ui/apps/platform/src/Containers/Integrations/hooks/useIntegrationPermissions.ts +++ b/ui/apps/platform/src/Containers/Integrations/hooks/useIntegrationPermissions.ts @@ -3,6 +3,7 @@ import { createStructuredSelector } from 'reselect'; import { selectors } from 'reducers'; import { getHasReadPermission, getHasReadWritePermission } from 'reducers/roles'; +import { PermissionsMap } from 'services/RolesService'; import { IntegrationSource } from '../utils/integrationUtils'; type UseIntegrationPermissionsResponse = Record< @@ -10,7 +11,11 @@ type UseIntegrationPermissionsResponse = Record< { read: boolean; write: boolean } >; -const authProviderState = createStructuredSelector({ +type AuthProviderState = { + userRolePermissions: { resourceToAccess: PermissionsMap }; +}; + +const authProviderState = createStructuredSelector({ userRolePermissions: selectors.getUserRolePermissions, }); diff --git a/ui/apps/platform/src/Containers/Integrations/hooks/useIntegrations.ts b/ui/apps/platform/src/Containers/Integrations/hooks/useIntegrations.ts index 6ad9de80e9f1c..a6e2e43042cf8 100644 --- a/ui/apps/platform/src/Containers/Integrations/hooks/useIntegrations.ts +++ b/ui/apps/platform/src/Containers/Integrations/hooks/useIntegrations.ts @@ -5,7 +5,10 @@ import { selectors } from 'reducers'; import { Integration, IntegrationSource, IntegrationType } from '../utils/integrationUtils'; -const selectIntegrations = createStructuredSelector({ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type SelectIntegrationsState = Record; + +const selectIntegrations = createStructuredSelector({ apiTokens: selectors.getAPITokens, machineAccessConfigs: selectors.getMachineAccessConfigs, notifiers: selectors.getNotifiers, diff --git a/ui/apps/platform/src/Containers/Integrations/hooks/usePageState.ts b/ui/apps/platform/src/Containers/Integrations/hooks/usePageState.ts index eef52a51db4d6..17a84798ae5f5 100644 --- a/ui/apps/platform/src/Containers/Integrations/hooks/usePageState.ts +++ b/ui/apps/platform/src/Containers/Integrations/hooks/usePageState.ts @@ -1,4 +1,4 @@ -import { useLocation, useRouteMatch } from 'react-router-dom'; +import { useLocation, useMatch } from 'react-router-dom'; import { integrationCreatePath, integrationDetailsPath, @@ -16,8 +16,6 @@ type Params = { type Location = { pathname: string }; -type Match = { isExact: boolean; params: Params }; - export type PageStates = 'LIST' | 'CREATE' | 'EDIT' | 'VIEW_DETAILS'; type UsePageStateResult = { @@ -38,10 +36,10 @@ type UsePageStateResult = { function usePageState(): UsePageStateResult { const location: Location = useLocation(); - const matchList: Match = useRouteMatch(integrationsListPath); - const matchCreate: Match = useRouteMatch(integrationCreatePath); - const matchEdit: Match = useRouteMatch(integrationEditPath); - const matchViewDetails: Match = useRouteMatch(integrationDetailsPath); + const matchList = useMatch(integrationsListPath); + const matchCreate = useMatch(integrationCreatePath); + const matchEdit = useMatch(integrationEditPath); + const matchViewDetails = useMatch(integrationDetailsPath); function getPathToCreate(source: IntegrationSource, type: IntegrationType): string { return `${integrationsPath}/${source}/${type}/create`; @@ -59,10 +57,10 @@ function usePageState(): UsePageStateResult { return `${integrationsPath}/${source}/${type}/view/${id}`; } - if (matchList?.isExact) { + if (matchList) { return { pageState: 'LIST', - params: matchList.params, + params: matchList.params as Params, isList: true, isCreating: false, isEditing: false, @@ -72,10 +70,10 @@ function usePageState(): UsePageStateResult { getPathToViewDetails, }; } - if (matchCreate?.isExact) { + if (matchCreate) { return { pageState: 'CREATE', - params: matchCreate.params, + params: matchCreate.params as Params, isList: false, isCreating: true, isEditing: false, @@ -85,10 +83,10 @@ function usePageState(): UsePageStateResult { getPathToViewDetails, }; } - if (matchEdit?.isExact) { + if (matchEdit) { return { pageState: 'EDIT', - params: matchEdit.params, + params: matchEdit.params as Params, isList: false, isCreating: false, isEditing: true, @@ -98,10 +96,10 @@ function usePageState(): UsePageStateResult { getPathToViewDetails, }; } - if (matchViewDetails?.isExact) { + if (matchViewDetails) { return { pageState: 'VIEW_DETAILS', - params: matchViewDetails.params, + params: matchViewDetails.params as Params, isList: false, isCreating: false, isEditing: false, diff --git a/ui/apps/platform/src/Containers/MainPage/AuthenticatedRoutes.tsx b/ui/apps/platform/src/Containers/MainPage/AuthenticatedRoutes.tsx index b770b63303aed..54cee8bb2fb68 100644 --- a/ui/apps/platform/src/Containers/MainPage/AuthenticatedRoutes.tsx +++ b/ui/apps/platform/src/Containers/MainPage/AuthenticatedRoutes.tsx @@ -1,6 +1,6 @@ import React, { ReactElement } from 'react'; import { useSelector } from 'react-redux'; -import { Redirect, useLocation } from 'react-router-dom'; +import { Navigate, useLocation } from 'react-router-dom'; import { createStructuredSelector } from 'reselect'; import LoadingSection from 'Components/PatternFly/LoadingSection'; @@ -29,11 +29,12 @@ function AuthenticatedRoutes(): ReactElement { case AUTH_STATUS.AUTH_PROVIDERS_LOADING_ERROR: case AUTH_STATUS.LOGIN_AUTH_PROVIDERS_LOADING_ERROR: return ( - ); diff --git a/ui/apps/platform/src/Containers/MainPage/Body.tsx b/ui/apps/platform/src/Containers/MainPage/Body.tsx index d38c875559d30..347249751cf33 100644 --- a/ui/apps/platform/src/Containers/MainPage/Body.tsx +++ b/ui/apps/platform/src/Containers/MainPage/Body.tsx @@ -1,12 +1,12 @@ import React, { ElementType, ReactElement, useEffect } from 'react'; import { useSelector } from 'react-redux'; -import { Redirect, Route, Switch, useLocation } from 'react-router-dom'; +import { Navigate, Route, Routes, useLocation, useParams } from 'react-router-dom'; import { PageSection } from '@patternfly/react-core'; // Import path variables in alphabetical order to minimize merge conflicts when multiple people add routes. import { RouteKey, - accessControlPath, + accessControlBasePath, administrationEventsPathWithParam, apidocsPath, apidocsPathV2, @@ -18,7 +18,7 @@ import { collectionsPath, complianceEnhancedCoveragePath, complianceEnhancedSchedulesPath, - compliancePath, + complianceBasePath, configManagementPath, dashboardPath, exceptionConfigurationPath, @@ -34,14 +34,13 @@ import { systemConfigPath, systemHealthPath, userBasePath, - violationsPath, + violationsBasePath, vulnManagementPath, vulnerabilitiesWorkloadCvesPath, vulnerabilityReportsPath, exceptionManagementPath, vulnerabilitiesNodeCvesPath, vulnerabilitiesPlatformCvesPath, - deprecatedPoliciesBasePath, policiesBasePath, vulnerabilitiesUserWorkloadsPath, vulnerabilitiesPlatformPath, @@ -89,7 +88,7 @@ type RouteComponent = { const routeComponentMap: Record = { 'access-control': { component: asyncComponent(() => import('Containers/AccessControl/AccessControl')), - path: accessControlPath, + path: accessControlBasePath, }, 'administration-events': { component: asyncComponent( @@ -139,16 +138,21 @@ const routeComponentMap: Record = { component: asyncComponent(() => import('Containers/Collections/CollectionsPage')), path: collectionsPath, }, - // Compliance enhanced must precede compliance classic. - 'compliance-enhanced': { + 'compliance-coverage': { component: asyncComponent( - () => import('Containers/ComplianceEnhanced/ComplianceEnhancedPage') + () => import('Containers/ComplianceEnhanced/Coverage/CoveragePage') ), - path: [complianceEnhancedCoveragePath, complianceEnhancedSchedulesPath], + path: complianceEnhancedCoveragePath, + }, + 'compliance-schedules': { + component: asyncComponent( + () => import('Containers/ComplianceEnhanced/Schedules/ScanConfigsPage') + ), + path: complianceEnhancedSchedulesPath, }, compliance: { component: asyncComponent(() => import('Containers/Compliance/Page')), - path: compliancePath, + path: complianceBasePath, }, configmanagement: { component: asyncComponent(() => import('Containers/ConfigManagement/Page')), @@ -204,7 +208,7 @@ const routeComponentMap: Record = { }, violations: { component: asyncComponent(() => import('Containers/Violations/ViolationsPage')), - path: violationsPath, + path: violationsBasePath, }, 'vulnerabilities/exception-management': { component: asyncComponent( @@ -265,12 +269,33 @@ type BodyProps = { isFeatureFlagEnabled: IsFeatureFlagEnabled; }; +function WorkloadCvesRedirect() { + const location = useLocation(); + + const newPath = location.pathname.replace( + vulnerabilitiesWorkloadCvesPath, + vulnerabilitiesAllImagesPath + ); + + return ; +} + +function DeprecatedPoliciesRedirect() { + const { policyId, command } = useParams(); + + const newPath = `${policiesBasePath}${policyId ? `/${policyId}` : ''}${ + command ? `/${command}` : '' + }`; + + return ; +} + function Body({ hasReadAccess, isFeatureFlagEnabled }: BodyProps): ReactElement { const location = useLocation(); const { analyticsPageVisit } = useAnalytics(); useEffect(() => { analyticsPageVisit('Page Viewed', '', { path: location.pathname }); - }, [location, analyticsPageVisit]); + }, [location.pathname, analyticsPageVisit]); const { hasReadWriteAccess } = usePermissions(); const hasWriteAccessForInviting = hasReadWriteAccess('Access'); const showInviteModal = useSelector(selectors.inviteSelector); @@ -280,48 +305,31 @@ function Body({ hasReadAccess, isFeatureFlagEnabled }: BodyProps): ReactElement return (
- - } /> - } /> + + } /> + } /> {/* Make sure the following Redirect element works after react-router-dom upgrade */} - } - /> - ( - - )} - /> + } /> {isFeatureFlagEnabled('ROX_PLATFORM_CVE_SPLIT') && ( { - const newPath = location.pathname.replace( - vulnerabilitiesWorkloadCvesPath, - vulnerabilitiesAllImagesPath - ); - return ; - }} + // a search and replace of the subpath we are redirecting, which is accomplished + // by using the WorkloadCvesRedirect component. + element={} /> )} {Object.keys(routeComponentMap) .filter((routeKey) => isRouteEnabled(routePredicates, routeKey as RouteKey)) .map((routeKey) => { - const { component, path } = routeComponentMap[routeKey]; - return ; + const { component: Component, path } = routeComponentMap[routeKey]; + return ( + } /> + ); })} - - - - + } /> + {hasWriteAccessForInviting && showInviteModal && }
diff --git a/ui/apps/platform/src/Containers/MainPage/Header/ClusterStatusButton.tsx b/ui/apps/platform/src/Containers/MainPage/Header/ClusterStatusButton.tsx index fac96c26e12a4..0d26c6a57eb84 100644 --- a/ui/apps/platform/src/Containers/MainPage/Header/ClusterStatusButton.tsx +++ b/ui/apps/platform/src/Containers/MainPage/Header/ClusterStatusButton.tsx @@ -1,6 +1,6 @@ import React, { CSSProperties, ReactElement } from 'react'; import { Activity } from 'react-feather'; -import { useHistory } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import { Tooltip } from '@patternfly/react-core'; import { clustersBasePath } from 'routePaths'; @@ -23,7 +23,7 @@ const ClusterStatusButton = ({ degraded = 0, unhealthy = 0, }: ClusterStatusButtonProps): ReactElement => { - const history = useHistory(); + const navigate = useNavigate(); const hasDegradedClusters = degraded > 0; const hasUnhealthyClusters = unhealthy > 0; const hasProblems = hasDegradedClusters || hasUnhealthyClusters; @@ -71,13 +71,10 @@ const ClusterStatusButton = ({ } const onClick = () => { - history.push({ - pathname: clustersBasePath, - search: '', - // TODO after ClustersPage sets search filter according to search query string in URL: - // If any clusters have problems, then Clusters list has search filter. - // search: hasUnhealthyClusters || hasDegradedClusters ? '?s[Cluster Health][0]=UNHEALTHY&s[Cluster Health][1]=DEGRADED' : '', - }); + navigate(`${clustersBasePath}`); + // TODO after ClustersPage sets search filter according to search query string in URL: + // If any clusters have problems, then Clusters list has search filter. + // search: hasUnhealthyClusters || hasDegradedClusters ? '?s[Cluster Health][0]=UNHEALTHY&s[Cluster Health][1]=DEGRADED' : '', }; // On masthead, black text on white background like a dropdown menu. diff --git a/ui/apps/platform/src/Containers/MainPage/MainPage.tsx b/ui/apps/platform/src/Containers/MainPage/MainPage.tsx index 8096f4816cb1f..5d1e5fa0ad9d2 100644 --- a/ui/apps/platform/src/Containers/MainPage/MainPage.tsx +++ b/ui/apps/platform/src/Containers/MainPage/MainPage.tsx @@ -1,6 +1,6 @@ import React, { ReactElement, useEffect, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; -import { useHistory } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import { Page, Button } from '@patternfly/react-core'; import { OutlinedCommentsIcon } from '@patternfly/react-icons'; @@ -21,7 +21,7 @@ import Body from './Body'; import AcsFeedbackModal from './AcsFeedbackModal'; function MainPage(): ReactElement { - const history = useHistory(); + const navigate = useNavigate(); const dispatch = useDispatch(); const { isFeatureFlagEnabled, isLoadingFeatureFlags } = useFeatureFlags(); @@ -42,7 +42,7 @@ function MainPage(): ReactElement { if (clusters?.length === 0) { // If no clusters, and user can admin Clusters, redirect to clusters section. // Only applicable in Cloud Services. - history.push(clustersBasePath); + navigate(clustersBasePath); } }) .catch(() => {}) @@ -50,7 +50,8 @@ function MainPage(): ReactElement { setIsLoadingClustersCount(false); }); } - }, [hasWriteAccessForCluster, history]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [hasWriteAccessForCluster]); // Prerequisites from initial requests for conditional rendering that affects all authenticated routes: // feature flags: for NavigationSidebar and Body diff --git a/ui/apps/platform/src/Containers/MainPage/Navigation/HorizontalSubnav.tsx b/ui/apps/platform/src/Containers/MainPage/Navigation/HorizontalSubnav.tsx index fbabf5ddae06c..10f812dcaf8e7 100644 --- a/ui/apps/platform/src/Containers/MainPage/Navigation/HorizontalSubnav.tsx +++ b/ui/apps/platform/src/Containers/MainPage/Navigation/HorizontalSubnav.tsx @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import { matchPath, useHistory, useLocation } from 'react-router-dom'; +import { matchPath, useLocation, useNavigate } from 'react-router-dom'; import { Nav, Dropdown, @@ -48,24 +48,33 @@ function getSubnavDescriptionGroups( type: 'link', content: 'User Workloads', path: violationsUserWorkloadsViewPath, - isActive: (location) => - location.search.includes(`filteredWorkflowView=Applications view`), + isActive: (location) => { + const search: string = location.search || ''; + const encodedValue = encodeURIComponent('Applications view'); + return search.includes(`filteredWorkflowView=${encodedValue}`); + }, routeKey: 'violations', }, { type: 'link', content: 'Platform', path: violationsPlatformViewPath, - isActive: (location) => - location.search.includes(`filteredWorkflowView=Platform view`), + isActive: (location) => { + const search: string = location.search || ''; + const encodedValue = encodeURIComponent('Platform view'); + return search.includes(`filteredWorkflowView=${encodedValue}`); + }, routeKey: 'violations', }, { type: 'link', content: 'All Violations', path: violationsFullViewPath, - isActive: (location) => - location.search.includes(`filteredWorkflowView=Full view`), + isActive: (location) => { + const search: string = location.search || ''; + const encodedValue = encodeURIComponent('Full view'); + return search.includes(`filteredWorkflowView=${encodedValue}`); + }, routeKey: 'violations', }, ] @@ -146,7 +155,7 @@ function matchBasePath({ descriptionPath: string; }): boolean { const basePath = descriptionPath.split('?')[0] ?? ''; - return Boolean(matchPath(pathname, basePath)); + return Boolean(matchPath({ path: `${basePath}/*` }, pathname)); } /* @@ -179,7 +188,7 @@ export type HorizontalSubnavProps = { }; function HorizontalSubnav({ hasReadAccess, isFeatureFlagEnabled }: HorizontalSubnavProps) { - const history = useHistory(); + const navigate = useNavigate(); const location = useLocation(); const routePredicates = { hasReadAccess, isFeatureFlagEnabled }; @@ -203,7 +212,9 @@ function HorizontalSubnav({ hasReadAccess, isFeatureFlagEnabled }: HorizontalSub _event: React.MouseEvent | undefined, value: string | number | undefined ) => { - history.push(value); + if (value !== undefined) { + navigate(value.toString()); + } setOpenDropdownKey(null); }; diff --git a/ui/apps/platform/src/Containers/MainPage/Navigation/NavigationItem.tsx b/ui/apps/platform/src/Containers/MainPage/Navigation/NavigationItem.tsx index 9c92f2881be82..e4ab451a63972 100644 --- a/ui/apps/platform/src/Containers/MainPage/Navigation/NavigationItem.tsx +++ b/ui/apps/platform/src/Containers/MainPage/Navigation/NavigationItem.tsx @@ -11,7 +11,7 @@ export type NavigationItemProps = { function NavigationItem({ isActive, path, content }: NavigationItemProps): ReactElement { return ( - + (isActive ? 'pf-m-current' : '')} end> {content} diff --git a/ui/apps/platform/src/Containers/MainPage/Navigation/NavigationSidebar.tsx b/ui/apps/platform/src/Containers/MainPage/Navigation/NavigationSidebar.tsx index 4a99d1f2adb65..2720908bf06e9 100644 --- a/ui/apps/platform/src/Containers/MainPage/Navigation/NavigationSidebar.tsx +++ b/ui/apps/platform/src/Containers/MainPage/Navigation/NavigationSidebar.tsx @@ -1,5 +1,5 @@ import React, { ReactElement } from 'react'; -import { matchPath, useLocation } from 'react-router-dom'; +import { Location, matchPath, useLocation } from 'react-router-dom'; import { Nav, NavExpandable, @@ -69,20 +69,23 @@ function getNavDescriptions(isFeatureFlagEnabled: IsFeatureFlagEnabled): NavDesc content: 'Results', path: vulnerabilitiesUserWorkloadsPath, routeKey: 'vulnerabilities/user-workloads', - isActive: (location) => - Boolean( - matchPath(location.pathname, [ - vulnerabilitiesWorkloadCvesPath, - vulnerabilitiesNodeCvesPath, - vulnerabilitiesUserWorkloadsPath, - vulnerabilitiesPlatformPath, - vulnerabilitiesAllImagesPath, - vulnerabilitiesInactiveImagesPath, - vulnerabilitiesImagesWithoutCvesPath, - vulnerabilitiesViewPath, - vulnerabilitiesPlatformCvesPath, - ]) - ), + isActive: (location: Location) => { + const pathsToMatch = [ + vulnerabilitiesWorkloadCvesPath, + vulnerabilitiesNodeCvesPath, + vulnerabilitiesUserWorkloadsPath, + vulnerabilitiesPlatformPath, + vulnerabilitiesAllImagesPath, + vulnerabilitiesInactiveImagesPath, + vulnerabilitiesImagesWithoutCvesPath, + vulnerabilitiesViewPath, + vulnerabilitiesPlatformCvesPath, + ]; + + return pathsToMatch.some((path) => + matchPath({ path: `${path}/*` }, location.pathname) + ); + }, }, { type: 'link', @@ -106,7 +109,7 @@ function getNavDescriptions(isFeatureFlagEnabled: IsFeatureFlagEnabled): NavDesc path: vulnManagementPath, routeKey: 'vulnerability-management', isActive: (location) => - Boolean(matchPath(location.pathname, { vulnManagementPath, exact: true })), + Boolean(matchPath({ path: vulnManagementPath }, location.pathname)), }, ] : [ @@ -154,7 +157,7 @@ function getNavDescriptions(isFeatureFlagEnabled: IsFeatureFlagEnabled): NavDesc path: vulnManagementPath, routeKey: 'vulnerability-management', isActive: (location) => - Boolean(matchPath(location.pathname, { vulnManagementPath, exact: true })), + Boolean(matchPath({ path: vulnManagementPath }, location.pathname)), }, ]; @@ -199,13 +202,13 @@ function getNavDescriptions(isFeatureFlagEnabled: IsFeatureFlagEnabled): NavDesc type: 'link', content: Coverage, path: complianceEnhancedCoveragePath, - routeKey: 'compliance-enhanced', + routeKey: 'compliance-coverage', }, { type: 'link', content: Schedules, path: complianceEnhancedSchedulesPath, - routeKey: 'compliance-enhanced', + routeKey: 'compliance-schedules', }, { type: 'separator', @@ -217,11 +220,17 @@ function getNavDescriptions(isFeatureFlagEnabled: IsFeatureFlagEnabled): NavDesc path: complianceBasePath, routeKey: 'compliance', isActive: (location) => - Boolean(matchPath(location.pathname, complianceBasePath)) && - !matchPath(location.pathname, [ - complianceEnhancedCoveragePath, - complianceEnhancedSchedulesPath, - ]), + Boolean( + matchPath({ path: `${complianceBasePath}/*` }, location.pathname) + ) && + !matchPath( + { path: `${complianceEnhancedCoveragePath}/*` }, + location.pathname + ) && + !matchPath( + { path: `${complianceEnhancedSchedulesPath}/*` }, + location.pathname + ), }, ], }, @@ -338,7 +347,12 @@ function NavigationSidebar({ const hasChildMatchPath = children.some( (childDescription) => childDescription.type === 'link' && - (Boolean(matchPath(location.pathname, childDescription.path)) || + (Boolean( + matchPath( + { path: `${childDescription.path}/*` }, + location.pathname + ) + ) || isActiveLink(location, childDescription)) ); return ( @@ -358,10 +372,10 @@ function NavigationSidebar({ return ( { - const queryString = clearSimulationQuery(history.location.search); - history.push(`${networkBasePath}${queryString}`); - }, [history]); + const queryString = clearSimulationQuery(location.search); + navigate(`${networkBasePath}${queryString}`); + }, [navigate, location.search]); type OnNavigateArgs = { nodeID: string; @@ -125,7 +126,7 @@ const TopologyComponent = ({ setDefaultDeploymentTab(deploymentTabs.DETAILS); const { data, id } = newSelectedEntity; const [newNodeType, newNodeId] = getUrlParamsForNode(data.type, id); - const queryString = clearSimulationQuery(history.location.search); + const queryString = clearSimulationQuery(location.search); // if found, and it's not the logical grouping of all external sources, then trigger URL update if (newNodeId !== 'EXTERNAL') { let newURL = `${networkBasePath}/${newNodeType}/${encodeURIComponent(newNodeId)}`; @@ -133,10 +134,10 @@ const TopologyComponent = ({ newURL = `${newURL}/externalIP/${externalIP}`; } newURL = `${newURL}${queryString}`; - history.push(newURL); + navigate(newURL); } else { // otherwise, return to the graph-only state - history.push(`${networkBasePath}${queryString}`); + navigate(`${networkBasePath}${queryString}`); } } } @@ -212,7 +213,7 @@ const TopologyComponent = ({ if (selectedNode) { panNodeIntoView(selectedNode); } else if ( - history.location.pathname !== networkBasePath && + location.pathname !== networkBasePath && !selectedNode && model.nodes.length > 0 ) { @@ -222,7 +223,7 @@ const TopologyComponent = ({ // is available – we want to prevent closing the sidebar until data has been fetched closeSidebar(); } - }, [controller, model, selectedNode, history, closeSidebar, panNodeIntoView]); + }, [controller, location.pathname, model, selectedNode, closeSidebar, panNodeIntoView]); const selectedIds = selectedNode ? [selectedNode.id] : []; diff --git a/ui/apps/platform/src/Containers/NetworkGraph/simulation/SimulateNetworkPolicyButton.tsx b/ui/apps/platform/src/Containers/NetworkGraph/simulation/SimulateNetworkPolicyButton.tsx index 23406291d5837..389cc22ccf141 100644 --- a/ui/apps/platform/src/Containers/NetworkGraph/simulation/SimulateNetworkPolicyButton.tsx +++ b/ui/apps/platform/src/Containers/NetworkGraph/simulation/SimulateNetworkPolicyButton.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { Button } from '@patternfly/react-core'; -import { useHistory } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import { networkBasePath } from 'routePaths'; import useAnalytics, { CLUSTER_LEVEL_SIMULATOR_OPENED } from 'hooks/useAnalytics'; @@ -16,7 +16,7 @@ type SimulateNetworkPolicyButtonProps = { function SimulateNetworkPolicyButton({ simulation, isDisabled }: SimulateNetworkPolicyButtonProps) { const { analyticsTrack } = useAnalytics(); - const history = useHistory(); + const navigate = useNavigate(); const { searchFilter } = useURLSearch(); const [, setSimulationQueryValue] = useURLParameter('simulation', undefined); @@ -28,7 +28,7 @@ function SimulateNetworkPolicyButton({ simulation, isDisabled }: SimulateNetwork properties, }); - history.push(`${networkBasePath}${history.location.search as string}`); + navigate(`${networkBasePath}${location.search}`); setSimulationQueryValue('networkPolicy'); } diff --git a/ui/apps/platform/src/Containers/Policies/Detail/PolicyDetail.tsx b/ui/apps/platform/src/Containers/Policies/Detail/PolicyDetail.tsx index 8d2cd8814f12e..5227c4167b309 100644 --- a/ui/apps/platform/src/Containers/Policies/Detail/PolicyDetail.tsx +++ b/ui/apps/platform/src/Containers/Policies/Detail/PolicyDetail.tsx @@ -1,5 +1,5 @@ import React, { ReactElement, useState } from 'react'; -import { useHistory } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import { Alert, AlertActionCloseButton, @@ -51,7 +51,7 @@ function PolicyDetail({ hasWriteAccessForPolicy, policy, }: PolicyDetailProps): ReactElement { - const history = useHistory(); + const navigate = useNavigate(); const [isRequesting, setIsRequesting] = useState(false); const [requestError, setRequestError] = useState(null); @@ -72,17 +72,11 @@ function PolicyDetail({ } function onEditPolicy() { - history.push({ - pathname: `${policiesBasePath}/${id}`, - search: 'action=edit', - }); + navigate(`${policiesBasePath}/${id}?action=edit`); } function onClonePolicy() { - history.push({ - pathname: `${policiesBasePath}/${id}`, - search: 'action=clone', - }); + navigate(`${policiesBasePath}/${id}?action=clone`); } function onExportPolicy() { @@ -151,7 +145,7 @@ function PolicyDetail({ deletePolicy(id) .then(() => { // Route change causes policy table page to request policies. - history.goBack(); + navigate(-1); }) .catch((error) => { setRequestError( diff --git a/ui/apps/platform/src/Containers/Policies/Table/PoliciesTable.tsx b/ui/apps/platform/src/Containers/Policies/Table/PoliciesTable.tsx index f9a90e3759c7a..4b77aed50db06 100644 --- a/ui/apps/platform/src/Containers/Policies/Table/PoliciesTable.tsx +++ b/ui/apps/platform/src/Containers/Policies/Table/PoliciesTable.tsx @@ -1,5 +1,5 @@ import React, { useState, useEffect } from 'react'; -import { Link, useHistory } from 'react-router-dom'; +import { Link, useNavigate } from 'react-router-dom'; import { Button, Flex, @@ -97,7 +97,7 @@ function PoliciesTable({ searchOptions, }: PoliciesTableProps): React.ReactElement { const expandedRowSet = useSet(); - const history = useHistory(); + const navigate = useNavigate(); const [labelAndNotifierIdsForTypes, setLabelAndNotifierIdsForTypes] = useState< LabelAndNotifierIdsForType[] >([]); @@ -145,17 +145,11 @@ function PoliciesTable({ } function onEditPolicy(id: string) { - history.push({ - pathname: `${policiesBasePath}/${id}`, - search: 'action=edit', - }); + navigate(`${policiesBasePath}/${id}?action=edit`); } function onClonePolicy(id: string) { - history.push({ - pathname: `${policiesBasePath}/${id}`, - search: 'action=clone', - }); + navigate(`${policiesBasePath}/${id}?action=clone`); } const selectedIds = getSelectedIds(); diff --git a/ui/apps/platform/src/Containers/Policies/Table/PoliciesTablePage.tsx b/ui/apps/platform/src/Containers/Policies/Table/PoliciesTablePage.tsx index 6a02a42bc3861..d9ad304b78a53 100644 --- a/ui/apps/platform/src/Containers/Policies/Table/PoliciesTablePage.tsx +++ b/ui/apps/platform/src/Containers/Policies/Table/PoliciesTablePage.tsx @@ -1,5 +1,5 @@ import React, { useState, useEffect } from 'react'; -import { useHistory } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import { PageSection, Bullseye, @@ -56,7 +56,7 @@ function PoliciesTablePage({ handleChangeSearchFilter, searchFilter, }: PoliciesTablePageProps): React.ReactElement { - const history = useHistory(); + const navigate = useNavigate(); const { getSortParams, sortOption } = useURLSort({ defaultSortOption, sortFields }); const [notifiers, setNotifiers] = useState([]); @@ -72,7 +72,7 @@ function PoliciesTablePage({ const query = searchFilter ? getRequestQueryStringForSearchFilter(searchFilter) : ''; function onClickCreatePolicy() { - history.push(`${policiesBasePath}/?action=create`); + navigate(`${policiesBasePath}/?action=create`); } function onClickImportPolicy() { diff --git a/ui/apps/platform/src/Containers/Policies/Wizard/PolicyWizard.tsx b/ui/apps/platform/src/Containers/Policies/Wizard/PolicyWizard.tsx index 5a26f0ad7e5e8..ccb1a8ac2c92e 100644 --- a/ui/apps/platform/src/Containers/Policies/Wizard/PolicyWizard.tsx +++ b/ui/apps/platform/src/Containers/Policies/Wizard/PolicyWizard.tsx @@ -1,5 +1,5 @@ import React, { ReactElement, useEffect, useState } from 'react'; -import { useHistory } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import { FormikProvider, useFormik } from 'formik'; import { Alert, @@ -48,7 +48,7 @@ type PolicyWizardProps = { }; function PolicyWizard({ pageAction, policy }: PolicyWizardProps): ReactElement { - const history = useHistory(); + const navigate = useNavigate(); const [stepId, setStepId] = useState(POLICY_DEFINITION_DETAILS_ID); const [isValidOnServer, setIsValidOnServer] = useState(false); const [policyErrorMessage, setPolicyErrorMessage] = useState(''); @@ -66,7 +66,7 @@ function PolicyWizard({ pageAction, policy }: PolicyWizardProps): ReactElement { pageAction === 'edit' ? savePolicy(serverPolicy) : createPolicy(serverPolicy); request .then(() => { - history.goBack(); + navigate(-1); }) .catch((error) => { setPolicyErrorMessage(getAxiosErrorMessage(error)); @@ -92,7 +92,7 @@ function PolicyWizard({ pageAction, policy }: PolicyWizardProps): ReactElement { } = formik; function closeWizard(): void { - history.goBack(); + navigate(-1); } function scrollToTop() { diff --git a/ui/apps/platform/src/Containers/PolicyManagement/PolicyManagementPage.tsx b/ui/apps/platform/src/Containers/PolicyManagement/PolicyManagementPage.tsx index 433f8d8f44ec7..b0b365de1e442 100644 --- a/ui/apps/platform/src/Containers/PolicyManagement/PolicyManagementPage.tsx +++ b/ui/apps/platform/src/Containers/PolicyManagement/PolicyManagementPage.tsx @@ -1,30 +1,16 @@ import React from 'react'; -import { Redirect, Route, Switch } from 'react-router-dom'; +import { Navigate, Route, Routes } from 'react-router-dom'; -import { - policiesBasePath, - policiesPath, - policyManagementBasePath, - policyCategoriesPath, -} from 'routePaths'; import PoliciesPage from 'Containers/Policies/PoliciesPage'; import PolicyCategoriesPage from 'Containers/PolicyCategories/PolicyCategoriesPage'; function PolicyManagementPage() { return ( - - } - /> - - - - - - - + + } /> + } /> + } /> + ); } diff --git a/ui/apps/platform/src/Containers/Risk/CreatePolicyFromSearch.js b/ui/apps/platform/src/Containers/Risk/CreatePolicyFromSearch.js index 05df475963a77..47da125b0823a 100644 --- a/ui/apps/platform/src/Containers/Risk/CreatePolicyFromSearch.js +++ b/ui/apps/platform/src/Containers/Risk/CreatePolicyFromSearch.js @@ -1,6 +1,6 @@ import React, { useContext, useEffect } from 'react'; import PropTypes from 'prop-types'; -import { useHistory } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import { connect } from 'react-redux'; import { Button } from '@patternfly/react-core'; @@ -21,7 +21,7 @@ function CreatePolicyFromSearch({ clearFormMessages, }) { const workflowState = useContext(workflowStateContext); - const history = useHistory(); + const navigate = useNavigate(); // this utility filters out incomplete search pairs const currentSearch = workflowState.getCurrentSearchState(); @@ -36,10 +36,7 @@ function CreatePolicyFromSearch({ generatePolicyFromSearch(queryString) .then((response) => { - history.push({ - pathname: policiesBasePath, - search: '?action=generate', - }); + navigate(`${policiesBasePath}?action=generate`); const newPolicy = { ...response?.policy, diff --git a/ui/apps/platform/src/Containers/Risk/RiskPage.js b/ui/apps/platform/src/Containers/Risk/RiskPage.js index cae32267e60ad..5184f60390640 100644 --- a/ui/apps/platform/src/Containers/Risk/RiskPage.js +++ b/ui/apps/platform/src/Containers/Risk/RiskPage.js @@ -1,5 +1,5 @@ import React, { useState, useCallback } from 'react'; -import { useHistory, useLocation, useParams } from 'react-router-dom'; +import { useLocation, useNavigate, useParams } from 'react-router-dom'; import { useQuery } from '@apollo/client'; import { PageBody } from 'Components/Panel'; @@ -13,7 +13,7 @@ import RiskSidePanel from './RiskSidePanel'; import RiskTablePanel from './RiskTablePanel'; const RiskPage = () => { - const history = useHistory(); + const navigate = useNavigate(); const location = useLocation(); const params = useParams(); const { deploymentId } = params; @@ -32,9 +32,9 @@ const RiskPage = () => { const newUrl = newWorkflowState.toUrl(); - history.push(newUrl); + navigate(newUrl); }, - [workflowState, history] + [workflowState, navigate] ); const searchQueryOptions = { diff --git a/ui/apps/platform/src/Containers/Risk/RiskTablePanel.js b/ui/apps/platform/src/Containers/Risk/RiskTablePanel.js index 079e0281ef7c4..25bf1077fe127 100644 --- a/ui/apps/platform/src/Containers/Risk/RiskTablePanel.js +++ b/ui/apps/platform/src/Containers/Risk/RiskTablePanel.js @@ -1,6 +1,6 @@ import React, { useContext, useState, useCallback } from 'react'; import PropTypes from 'prop-types'; -import { useHistory } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import useDeepCompareEffect from 'use-deep-compare-effect'; import { Bullseye } from '@patternfly/react-core'; import { ExclamationTriangleIcon } from '@patternfly/react-icons'; @@ -33,7 +33,7 @@ function RiskTablePanel({ setIsViewFiltered, searchOptions, }) { - const history = useHistory(); + const navigate = useNavigate(); const workflowState = useContext(workflowStateContext); const pageSearch = workflowState.search[searchParams.page]; const sortOption = workflowState.sort[sortParams.page] || DEFAULT_RISK_SORT; @@ -44,7 +44,7 @@ function RiskTablePanel({ const [deploymentCount, setDeploymentsCount] = useState(0); function setPage(newPage) { - history.push(workflowState.setPage(newPage).toUrl()); + navigate(workflowState.setPage(newPage).toUrl()); } const setSortOption = useCallback( (newSortOption) => { @@ -52,9 +52,9 @@ function RiskTablePanel({ const newUrl = workflowState.setSort(convertedSortOption).setPage(0).toUrl(); - history.push(newUrl); + navigate(newUrl); }, - [history, workflowState] + [navigate, workflowState] ); /* diff --git a/ui/apps/platform/src/Containers/Search/SearchPage.tsx b/ui/apps/platform/src/Containers/Search/SearchPage.tsx index ef4683f6c58f1..157a55f53e9aa 100644 --- a/ui/apps/platform/src/Containers/Search/SearchPage.tsx +++ b/ui/apps/platform/src/Containers/Search/SearchPage.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useState, ReactElement } from 'react'; -import { useHistory, useLocation } from 'react-router-dom'; +import { useLocation, useNavigate } from 'react-router-dom'; import { Alert, Bullseye, @@ -34,7 +34,7 @@ import { import './SearchPage.css'; function SearchPage(): ReactElement { - const history = useHistory(); + const navigate = useNavigate(); const { search } = useLocation(); /* @@ -114,9 +114,9 @@ function SearchPage(): ReactElement { // If the current search filter is empty, then replace, else push. if (stringifiedSearchFilter.length === 0) { - history.replace(searchPathWithQueryString); + navigate(searchPathWithQueryString, { replace: true }); } else { - history.push(searchPathWithQueryString); + navigate(searchPathWithQueryString); } } diff --git a/ui/apps/platform/src/Containers/User/UserPage.js b/ui/apps/platform/src/Containers/User/UserPage.js index 01c23a605b87f..6aa6259a22a8e 100644 --- a/ui/apps/platform/src/Containers/User/UserPage.js +++ b/ui/apps/platform/src/Containers/User/UserPage.js @@ -1,5 +1,5 @@ import React from 'react'; -import { NavLink, Route, Switch } from 'react-router-dom'; +import { NavLink, Route, Routes, useMatch, useParams } from 'react-router-dom'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { createStructuredSelector, createSelector } from 'reselect'; @@ -44,6 +44,25 @@ function UserPage({ resourceToAccessByRole, userData }) { const authProviderName = usedAuthProvider?.type === 'basic' ? 'Basic' : (usedAuthProvider?.name ?? ''); + const isUserPathActive = useMatch(userBasePath); + const isRolePathActive = useMatch(userRolePath); + + const UserRoleRoute = () => { + const { roleName } = useParams(); + const role = roles.find((_role) => _role.name === roleName); + + if (role) { + return ; + } + + return ( + + + {`Role name: ${roleName}`} + + ); + }; + return ( <> @@ -75,23 +94,15 @@ function UserPage({ resourceToAccessByRole, userData }) {
- + + } /> { - const role = roles.find((_role) => _role.name === roleName); - - if (role) { - return ( - - ); - } - - return ( - - - {`Role name: ${roleName}`} - - ); - }} + index + element={ + + } /> - - - - +
diff --git a/ui/apps/platform/src/Containers/Violations/Details/ViolationDetailsPage.tsx b/ui/apps/platform/src/Containers/Violations/Details/ViolationDetailsPage.tsx index 587c6d4b48409..2e820122471f0 100644 --- a/ui/apps/platform/src/Containers/Violations/Details/ViolationDetailsPage.tsx +++ b/ui/apps/platform/src/Containers/Violations/Details/ViolationDetailsPage.tsx @@ -43,7 +43,7 @@ function ViolationDetailsPage(): ReactElement { const [alert, setAlert] = useState(null); const [isFetchingSelectedAlert, setIsFetchingSelectedAlert] = useState(false); - const { alertId } = useParams(); + const { alertId } = useParams() as { alertId: string }; const { filteredWorkflowView } = useFilteredWorkflowViewURLState('Full view'); diff --git a/ui/apps/platform/src/Containers/Violations/ViolationsPage.tsx b/ui/apps/platform/src/Containers/Violations/ViolationsPage.tsx index 7cefdbfc611e2..b180fd4287029 100644 --- a/ui/apps/platform/src/Containers/Violations/ViolationsPage.tsx +++ b/ui/apps/platform/src/Containers/Violations/ViolationsPage.tsx @@ -1,24 +1,17 @@ import React, { ReactElement } from 'react'; -import { Route, Switch } from 'react-router-dom'; +import { Route, Routes } from 'react-router-dom'; -import { violationsBasePath, violationsPath } from 'routePaths'; import ViolationsTablePage from './ViolationsTablePage'; import ViolationDetailsPage from './Details/ViolationDetailsPage'; import ViolationNotFoundPage from './ViolationNotFoundPage'; function ViolationsPage(): ReactElement { return ( - - - - - - - - - - - + + } /> + } /> + } /> + ); } diff --git a/ui/apps/platform/src/Containers/VulnMgmt/Dashboard/VulnMgmtDashboardPage.js b/ui/apps/platform/src/Containers/VulnMgmt/Dashboard/VulnMgmtDashboardPage.js index 28899e5a1d9c3..627a214efc29d 100644 --- a/ui/apps/platform/src/Containers/VulnMgmt/Dashboard/VulnMgmtDashboardPage.js +++ b/ui/apps/platform/src/Containers/VulnMgmt/Dashboard/VulnMgmtDashboardPage.js @@ -1,5 +1,5 @@ import React, { useContext } from 'react'; -import { useHistory } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import usePermissions from 'hooks/usePermissions'; import entityTypes from 'constants/entityTypes'; @@ -29,7 +29,7 @@ const entityMenuTypes = [ ]; const VulnDashboardPage = () => { - const history = useHistory(); + const navigate = useNavigate(); const { hasReadAccess } = usePermissions(); const hasReadAccessForIntegration = hasReadAccess('Integration'); const workflowState = useContext(workflowStateContext); @@ -62,7 +62,7 @@ const VulnDashboardPage = () => { targetUrl = workflowState.setSearch(allSearch).toUrl(); } - history.push(targetUrl); + navigate(targetUrl); } const cveFilter = searchState.Fixable ? 'Fixable' : 'All'; diff --git a/ui/apps/platform/src/Containers/VulnMgmt/Dashboard/WorkflowDashboardLayout.js b/ui/apps/platform/src/Containers/VulnMgmt/Dashboard/WorkflowDashboardLayout.js index 25f3bdd13d883..992e15367bff2 100644 --- a/ui/apps/platform/src/Containers/VulnMgmt/Dashboard/WorkflowDashboardLayout.js +++ b/ui/apps/platform/src/Containers/VulnMgmt/Dashboard/WorkflowDashboardLayout.js @@ -1,7 +1,8 @@ import React from 'react'; import URLService from 'utils/URLService'; import useCaseTypes from 'constants/useCaseTypes'; -import { useLocation, useRouteMatch } from 'react-router-dom'; +import useWorkflowMatch from 'hooks/useWorkflowMatch'; +import { useLocation } from 'react-router-dom'; import parseURL from 'utils/URLParser'; import workflowStateContext from 'Containers/workflowStateContext'; @@ -13,7 +14,7 @@ const DashboardMap = { const WorkflowDashboardLayout = () => { const location = useLocation(); - const match = useRouteMatch(); + const match = useWorkflowMatch(); const params = URLService.getParams(match, location); const workflowState = parseURL(location); diff --git a/ui/apps/platform/src/Containers/VulnMgmt/Entity/TableWidget.js b/ui/apps/platform/src/Containers/VulnMgmt/Entity/TableWidget.js index 512e957abda17..53ca3e254ded4 100644 --- a/ui/apps/platform/src/Containers/VulnMgmt/Entity/TableWidget.js +++ b/ui/apps/platform/src/Containers/VulnMgmt/Entity/TableWidget.js @@ -1,6 +1,6 @@ import React, { useState, useContext } from 'react'; import PropTypes from 'prop-types'; -import { useHistory } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import resolvePath from 'object-resolve-path'; import workflowStateContext from 'Containers/workflowStateContext'; @@ -19,7 +19,7 @@ const TableWidget = ({ ...rest }) => { const workflowState = useContext(workflowStateContext); - const history = useHistory(); + const navigate = useNavigate(); const [localPage, setLocalPage] = useState(0); const { columns, @@ -57,7 +57,7 @@ const TableWidget = ({ function onRowClick(row) { const id = resolvePath(row, idAttribute); const url = workflowState.pushRelatedEntity(entityType, id).toUrl(); - history.push(url); + navigate(url); } return ( diff --git a/ui/apps/platform/src/Containers/VulnMgmt/List/Cves/VulnMgmtListCves.js b/ui/apps/platform/src/Containers/VulnMgmt/List/Cves/VulnMgmtListCves.js index cd80b64086763..f81c461e2e3d0 100644 --- a/ui/apps/platform/src/Containers/VulnMgmt/List/Cves/VulnMgmtListCves.js +++ b/ui/apps/platform/src/Containers/VulnMgmt/List/Cves/VulnMgmtListCves.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import { gql } from '@apollo/client'; import * as Icon from 'react-feather'; import { connect } from 'react-redux'; -import { useHistory } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import { defaultHeaderClassName, @@ -291,7 +291,7 @@ const VulnMgmtCves = ({ refreshTrigger, setRefreshTrigger, }) => { - const history = useHistory(); + const navigate = useNavigate(); const { analyticsTrack } = useAnalytics(); const isRouteEnabled = useIsRouteEnabled(); const { hasReadWriteAccess } = usePermissions(); @@ -486,7 +486,7 @@ const VulnMgmtCves = ({ const newWorkflowState = workflowState.setSearch(targetSearchState); const newUrl = newWorkflowState.toUrl(); - history.push(newUrl); + navigate(newUrl); }; function closeDialog(idsToStaySelected = []) { diff --git a/ui/apps/platform/src/Containers/VulnMgmt/List/EntityList.js b/ui/apps/platform/src/Containers/VulnMgmt/List/EntityList.js index b8d2a9c276879..42b45fd9c4658 100644 --- a/ui/apps/platform/src/Containers/VulnMgmt/List/EntityList.js +++ b/ui/apps/platform/src/Containers/VulnMgmt/List/EntityList.js @@ -1,6 +1,6 @@ import React, { useContext, useRef, useLayoutEffect } from 'react'; import PropTypes from 'prop-types'; -import { useHistory } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import resolvePath from 'object-resolve-path'; import workflowStateContext from 'Containers/workflowStateContext'; import { PanelNew, PanelBody, PanelHead, PanelHeadEnd, PanelTitle } from 'Components/Panel'; @@ -42,7 +42,7 @@ const EntityList = ({ }) => { const tableRef = useRef(null); const workflowState = useContext(workflowStateContext); - const history = useHistory(); + const navigate = useNavigate(); function toggleTableRow(id) { const newSelection = toggleRow(id, selection); @@ -60,7 +60,7 @@ const EntityList = ({ const id = resolvePath(row, idAttribute); const url = workflowState.pushListItem(id).toUrl(); - history.push(url); + navigate(url); } function setPage(newPage) { @@ -68,7 +68,7 @@ const EntityList = ({ setSelection([]); } - history.push(workflowState.setPage(newPage).toUrl()); + navigate(workflowState.setPage(newPage).toUrl()); } // render section diff --git a/ui/apps/platform/src/Containers/VulnMgmt/List/WorkflowListPage.js b/ui/apps/platform/src/Containers/VulnMgmt/List/WorkflowListPage.js index 40a437ab2075e..bc40fd5f86a1b 100644 --- a/ui/apps/platform/src/Containers/VulnMgmt/List/WorkflowListPage.js +++ b/ui/apps/platform/src/Containers/VulnMgmt/List/WorkflowListPage.js @@ -3,8 +3,8 @@ import PropTypes from 'prop-types'; import { Bullseye } from '@patternfly/react-core'; import { ExclamationCircleIcon } from '@patternfly/react-icons'; import { useQuery } from '@apollo/client'; -import { useHistory } from 'react-router-dom'; import Raven from 'raven-js'; +import { useNavigate } from 'react-router-dom'; import EmptyStateTemplate from 'Components/EmptyStateTemplate'; import PageNotFound from 'Components/PageNotFound'; @@ -43,7 +43,7 @@ const WorkflowListPage = ({ setSelection, renderRowActionButtons, }) => { - const history = useHistory(); + const navigate = useNavigate(); const workflowState = useContext(workflowStateContext); const [sortFields, setSortFields] = useState({}); const { isFeatureFlagEnabled } = useFeatureFlags(); @@ -120,7 +120,7 @@ const WorkflowListPage = ({ }); const url = workflowState.setSort(workflowSort).setPage(0).toUrl(); - history.push(url); + navigate(url); } return ( diff --git a/ui/apps/platform/src/Containers/VulnMgmt/WorkflowLayout.js b/ui/apps/platform/src/Containers/VulnMgmt/WorkflowLayout.js index 52523a8c2a710..c724a939ab7d4 100644 --- a/ui/apps/platform/src/Containers/VulnMgmt/WorkflowLayout.js +++ b/ui/apps/platform/src/Containers/VulnMgmt/WorkflowLayout.js @@ -1,28 +1,42 @@ import React from 'react'; -import { Route, Switch } from 'react-router-dom'; -import { workflowPaths } from 'routePaths'; +import { Route, Routes } from 'react-router-dom'; import isEqual from 'lodash/isEqual'; + import PageNotFound from 'Components/PageNotFound'; import DashboardPage from './Dashboard/WorkflowDashboardLayout'; import ListPage from './List/WorkflowListPageLayout'; import EntityPage from './Entity/WorkflowEntityPageLayout'; +const vulnerabilitiesListPath = `:entityId1?/:entityType2?/:entityId2?`; +const vulnerabilitiesEntityPath = `:pageEntityId?/:entityType1?/:entityId1?/:entityType2?/:entityId2?`; + const Page = () => ( - - - - - - - - - - - - - - + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + ); export default React.memo(Page, isEqual); diff --git a/ui/apps/platform/src/Containers/VulnMgmt/WorkflowSidePanel.js b/ui/apps/platform/src/Containers/VulnMgmt/WorkflowSidePanel.js index 7e1fbd127ac82..f9f7c8b69a344 100644 --- a/ui/apps/platform/src/Containers/VulnMgmt/WorkflowSidePanel.js +++ b/ui/apps/platform/src/Containers/VulnMgmt/WorkflowSidePanel.js @@ -1,6 +1,6 @@ import React from 'react'; import { ExternalLinkAltIcon } from '@patternfly/react-icons'; -import { Link, useHistory, useLocation } from 'react-router-dom'; +import { Link, useNavigate, useLocation } from 'react-router-dom'; import CloseButton from 'Components/CloseButton'; import { PanelNew, PanelBody, PanelHead, PanelHeadEnd } from 'Components/Panel'; @@ -10,7 +10,7 @@ import parseURL from 'utils/URLParser'; import EntityBreadCrumbs from './EntityBreadCrumbs'; const WorkflowSidePanel = ({ children }) => { - const history = useHistory(); + const navigate = useNavigate(); const location = useLocation(); const workflowState = parseURL(location); const pageStack = workflowState.getPageStack(); @@ -18,7 +18,7 @@ const WorkflowSidePanel = ({ children }) => { function onClose() { const url = workflowState.removeSidePanelParams().toUrl(); - history.push(url); + navigate(url); } const url = workflowState.getSkimmedStack().toUrl(); diff --git a/ui/apps/platform/src/Containers/VulnMgmt/widgets/LabeledBarGraph.js b/ui/apps/platform/src/Containers/VulnMgmt/widgets/LabeledBarGraph.js index ab81149c5642b..cdf7aa7260750 100644 --- a/ui/apps/platform/src/Containers/VulnMgmt/widgets/LabeledBarGraph.js +++ b/ui/apps/platform/src/Containers/VulnMgmt/widgets/LabeledBarGraph.js @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import max from 'lodash/max'; -import { useHistory } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import { FlexibleXYPlot, @@ -38,7 +38,7 @@ function getLabelData(data) { } const LabeledBarGraph = ({ data, title }) => { - const history = useHistory(); + const navigate = useNavigate(); const { onValueMouseOver, onValueMouseOut } = useGraphHoverHint(); const upperBoundX = max([...data.map((datum) => datum.x)]); @@ -47,7 +47,7 @@ const LabeledBarGraph = ({ data, title }) => { function onValueClickHandler(datum) { if (datum.url) { - history.push(datum.url); + navigate(datum.url); } } diff --git a/ui/apps/platform/src/Containers/VulnMgmt/widgets/Scatterplot.js b/ui/apps/platform/src/Containers/VulnMgmt/widgets/Scatterplot.js index cf39abfdc114b..74d8acc2bac9b 100644 --- a/ui/apps/platform/src/Containers/VulnMgmt/widgets/Scatterplot.js +++ b/ui/apps/platform/src/Containers/VulnMgmt/widgets/Scatterplot.js @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { useHistory } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import { FlexibleXYPlot, XAxis, @@ -31,7 +31,7 @@ const Scatterplot = ({ legendData, }) => { const { onValueMouseOver, onValueMouseOut } = useGraphHoverHint(); - const history = useHistory(); + const navigate = useNavigate(); const lowX = lowerX !== null ? lowerX : getLowValue(data, 'x', xMultiple); const highX = upperX !== null ? upperX : getHighValue(data, 'x', xMultiple, shouldPadX); @@ -43,7 +43,7 @@ const Scatterplot = ({ function onValueClickHandler(datum) { if (datum.url) { - history.push(datum.url); + navigate(datum.url); } } diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/ExceptionManagement/ExceptionManagementPage.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/ExceptionManagement/ExceptionManagementPage.tsx index fecee12a160af..6ccd6327bff9a 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/ExceptionManagement/ExceptionManagementPage.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/ExceptionManagement/ExceptionManagementPage.tsx @@ -1,30 +1,15 @@ import React from 'react'; -import { PageSection } from '@patternfly/react-core'; -import { Route, Switch } from 'react-router-dom'; +import { Route, Routes } from 'react-router-dom'; -import { exceptionManagementPath } from 'routePaths'; - -import PageNotFound from 'Components/PageNotFound'; -import PageTitle from 'Components/PageTitle'; import ExceptionRequestsPage from './ExceptionRequestsPage'; import ExceptionRequestDetailsPage from './ExceptionRequestDetailsPage'; function ExceptionManagementPage() { return ( - - - - - - - - - - - - - - + + } /> + } /> + ); } diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/ExceptionManagement/ExceptionRequestDetailsPage.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/ExceptionManagement/ExceptionRequestDetailsPage.tsx index 5dff310bf3f6e..46302e76c0341 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/ExceptionManagement/ExceptionRequestDetailsPage.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/ExceptionManagement/ExceptionRequestDetailsPage.tsx @@ -79,7 +79,7 @@ export function getCVEsForUpdatedRequest(exception: VulnerabilityException): str const tabContentId = 'ExceptionRequestDetails'; function ExceptionRequestDetailsPage() { - const { requestId } = useParams(); + const { requestId } = useParams() as { requestId: string }; const { hasReadWriteAccess } = usePermissions(); const { currentUser } = useAuthStatus(); const hasWriteAccessForApproving = hasReadWriteAccess('VulnerabilityManagementApprovals'); @@ -146,7 +146,7 @@ function ExceptionRequestDetailsPage() { return ( ); } diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/ExceptionManagement/ExceptionRequestsPage.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/ExceptionManagement/ExceptionRequestsPage.tsx index 5a0c6a8ef3118..0e7b876fbe264 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/ExceptionManagement/ExceptionRequestsPage.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/ExceptionManagement/ExceptionRequestsPage.tsx @@ -8,7 +8,7 @@ import { Tabs, Title, } from '@patternfly/react-core'; -import { Route, Switch, Redirect, useLocation, useHistory } from 'react-router-dom'; +import { Route, Routes, Navigate, useLocation, useNavigate } from 'react-router-dom'; import { exceptionManagementPath } from 'routePaths'; @@ -38,7 +38,7 @@ const tabKeyURLMap: Record = { function ExceptionRequestsPage() { const location = useLocation(); - const history = useHistory(); + const navigate = useNavigate(); let activeTabKey: TabKey = 'PENDING_REQUESTS'; @@ -57,7 +57,7 @@ function ExceptionRequestsPage() { const queryParams = location.search; // If you're manipulating the query parameters before navigating, consider improving this URL construction const url = `${path}${queryParams}`; - history.push(url); + navigate(url); }; /* eslint-disable accessibility/Tab-empty-contentId */ @@ -101,21 +101,13 @@ function ExceptionRequestsPage() { title={Denied requests} /> - - - - - - - - - - - - - - } /> - + + } /> + } /> + } /> + } /> + } /> + ); diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/NodeCves/NodeCvesPage.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/NodeCves/NodeCvesPage.tsx index 37309c418e98e..c12d88e31c580 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/NodeCves/NodeCvesPage.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/NodeCves/NodeCvesPage.tsx @@ -1,19 +1,15 @@ import React from 'react'; -import { Route, Switch } from 'react-router-dom'; +import { Route, Routes } from 'react-router-dom'; import { PageSection } from '@patternfly/react-core'; import PageNotFound from 'Components/PageNotFound'; import PageTitle from 'Components/PageTitle'; import ScannerV4IntegrationBanner from 'Components/ScannerV4IntegrationBanner'; -import { vulnerabilitiesNodeCvesPath } from 'routePaths'; import usePermissions from 'hooks/usePermissions'; import NodeCvesOverviewPage from './Overview/NodeCvesOverviewPage'; import NodeCvePage from './NodeCve/NodeCvePage'; import NodePage from './Node/NodePage'; -const vulnerabilitiesNodeCveSinglePath = `${vulnerabilitiesNodeCvesPath}/cves/:cveId`; -const vulnerabilitiesNodeSinglePath = `${vulnerabilitiesNodeCvesPath}/nodes/:nodeId`; - function NodeCvesPage() { const { hasReadAccess } = usePermissions(); const hasReadAccessForIntegration = hasReadAccess('Integration'); @@ -21,23 +17,20 @@ function NodeCvesPage() { return ( <> {hasReadAccessForIntegration && } - - - - - - - - - - - - - - - - - + + } /> + } /> + } /> + + + + + } + /> + ); } diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/PlatformCves/PlatformCvesPage.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/PlatformCves/PlatformCvesPage.tsx index 3258b8e72a282..fcbc78f819b5b 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/PlatformCves/PlatformCvesPage.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/PlatformCves/PlatformCvesPage.tsx @@ -1,20 +1,16 @@ import React from 'react'; -import { Route, Switch } from 'react-router-dom'; +import { Route, Routes } from 'react-router-dom'; import { PageSection } from '@patternfly/react-core'; import PageNotFound from 'Components/PageNotFound'; import PageTitle from 'Components/PageTitle'; import ScannerV4IntegrationBanner from 'Components/ScannerV4IntegrationBanner'; -import { vulnerabilitiesPlatformCvesPath } from 'routePaths'; import usePermissions from 'hooks/usePermissions'; import PlatformCvesOverviewPage from './Overview/PlatformCvesOverviewPage'; import PlatformCvePage from './PlatformCve/PlatformCvePage'; import ClusterPage from './Cluster/ClusterPage'; -const vulnerabilitiesPlatformCveSinglePath = `${vulnerabilitiesPlatformCvesPath}/cves/:cveId`; -const vulnerabilitiesPlatformClusterSinglePath = `${vulnerabilitiesPlatformCvesPath}/clusters/:clusterId`; - function PlatformCvesPage() { const { hasReadAccess } = usePermissions(); const hasReadAccessForIntegration = hasReadAccess('Integration'); @@ -22,23 +18,20 @@ function PlatformCvesPage() { return ( <> {hasReadAccessForIntegration && } - - - - - - - - - - - - - - - - - + + } /> + } /> + } /> + + + + + } + /> + ); } diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/ModifyVulnReport/CloneVulnReportPage.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/ModifyVulnReport/CloneVulnReportPage.tsx index a2e5bc92880ba..dbe662b7bcf75 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/ModifyVulnReport/CloneVulnReportPage.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/ModifyVulnReport/CloneVulnReportPage.tsx @@ -1,5 +1,5 @@ import React, { useEffect } from 'react'; -import { useHistory, useParams } from 'react-router-dom'; +import { useNavigate, useParams } from 'react-router-dom'; import { PageSection, Title, @@ -36,8 +36,8 @@ const wizardStepNames = [ ]; function CloneVulnReportPage() { - const history = useHistory(); - const { reportId } = useParams(); + const navigate = useNavigate(); + const { reportId } = useParams() as { reportId: string }; const { reportConfiguration, isLoading, error } = useFetchReport(reportId); const formik = useReportFormValues(); @@ -48,7 +48,7 @@ function CloneVulnReportPage() { } = useCreateReport({ onCompleted: () => { formik.resetForm(); - history.push(vulnerabilityReportsPath); + navigate(vulnerabilityReportsPath); }, }); @@ -87,7 +87,7 @@ function CloneVulnReportPage() { } function onClose() { - history.push(vulnerabilityReportsPath); + navigate(vulnerabilityReportsPath); } const wizardSteps = [ diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/ModifyVulnReport/CreateVulnReportPage.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/ModifyVulnReport/CreateVulnReportPage.tsx index 29bc395e2c2fc..43ee74550effd 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/ModifyVulnReport/CreateVulnReportPage.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/ModifyVulnReport/CreateVulnReportPage.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { useHistory } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import { PageSection, Title, @@ -31,13 +31,13 @@ const wizardStepNames = [ ]; function CreateVulnReportPage() { - const history = useHistory(); + const navigate = useNavigate(); const formik = useReportFormValues(); const { isLoading, error, createReport } = useCreateReport({ onCompleted: () => { formik.resetForm(); - history.push(vulnerabilityReportsPath); + navigate(vulnerabilityReportsPath); }, }); @@ -46,7 +46,7 @@ function CreateVulnReportPage() { } function onClose() { - history.push(vulnerabilityReportsPath); + navigate(vulnerabilityReportsPath); } // @TODO: This is reused in the Edit and Clone components so we can try to refactor this soon diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/ModifyVulnReport/EditVulnReportPage.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/ModifyVulnReport/EditVulnReportPage.tsx index 396a4f5080575..d01753878f714 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/ModifyVulnReport/EditVulnReportPage.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/ModifyVulnReport/EditVulnReportPage.tsx @@ -1,5 +1,5 @@ import React, { useEffect } from 'react'; -import { useHistory, useParams } from 'react-router-dom'; +import { useNavigate, useParams } from 'react-router-dom'; import { PageSection, Title, @@ -36,15 +36,15 @@ const wizardStepNames = [ ]; function EditVulnReportPage() { - const history = useHistory(); - const { reportId } = useParams(); + const navigate = useNavigate(); + const { reportId } = useParams() as { reportId: string }; const { reportConfiguration, isLoading, error } = useFetchReport(reportId); const formik = useReportFormValues(); const { isSaving, saveError, saveReport } = useSaveReport({ onCompleted: () => { formik.resetForm(); - history.push(vulnerabilityReportsPath); + navigate(vulnerabilityReportsPath); }, }); @@ -80,7 +80,7 @@ function EditVulnReportPage() { } function onClose() { - history.push(vulnerabilityReportsPath); + navigate(vulnerabilityReportsPath); } const wizardSteps = [ diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/ViewVulnReport/ViewVulnReportPage.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/ViewVulnReport/ViewVulnReportPage.tsx index e3b12ea81d24a..875b4ec2cdf08 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/ViewVulnReport/ViewVulnReportPage.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/ViewVulnReport/ViewVulnReportPage.tsx @@ -1,5 +1,5 @@ import React, { ReactElement, useState } from 'react'; -import { useHistory, useParams, generatePath } from 'react-router-dom'; +import { useNavigate, useParams, generatePath } from 'react-router-dom'; import { Alert, AlertActionCloseButton, @@ -66,8 +66,8 @@ const allReportJobsTabId = 'VulnReportsConfigReportJobs'; const headingLevel = 'h2'; function ViewVulnReportPage() { - const history = useHistory(); - const { reportId } = useParams(); + const navigate = useNavigate(); + const { reportId } = useParams() as { reportId: string }; const [isActionsDropdownOpen, setIsActionsDropdownOpen] = useState(false); const [selectedTab, setSelectedTab] = useState('CONFIGURATION_DETAILS'); @@ -90,7 +90,7 @@ function ViewVulnReportPage() { deleteResults, } = useDeleteModal({ onCompleted: () => { - history.push(vulnerabilityReportsPath); + navigate(vulnerabilityReportsPath); }, }); @@ -138,7 +138,7 @@ function ViewVulnReportPage() { const vulnReportPageURL = generatePath(vulnerabilityReportPath, { reportId: reportConfiguration.id, - }) as string; + }); const reportFormValues = getReportFormValuesFromConfiguration(reportConfiguration); @@ -204,7 +204,7 @@ function ViewVulnReportPage() { key="Edit report" component="button" onClick={() => { - history.push(`${vulnReportPageURL}?action=edit`); + navigate(`${vulnReportPageURL}?action=edit`); }} isDisabled={isReportStatusPending || isRunning} > @@ -240,7 +240,7 @@ function ViewVulnReportPage() { key="Clone report" component="button" onClick={() => { - history.push(`${vulnReportPageURL}?action=clone`); + navigate(`${vulnReportPageURL}?action=clone`); }} > Clone report diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/VulnReportingPage.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/VulnReportingPage.tsx index ffab0225b863f..fd92b4830992a 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/VulnReportingPage.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/VulnReportingPage.tsx @@ -1,5 +1,6 @@ +/* eslint-disable no-nested-ternary */ import React from 'react'; -import { Route, Switch, Redirect } from 'react-router-dom'; +import { Navigate, Route, Routes } from 'react-router-dom'; import usePageAction from 'hooks/usePageAction'; import usePermissions from 'hooks/usePermissions'; @@ -11,8 +12,6 @@ import EditVulnReportPage from './ModifyVulnReport/EditVulnReportPage'; import CloneVulnReportPage from './ModifyVulnReport/CloneVulnReportPage'; import ViewVulnReportPage from './ViewVulnReport/ViewVulnReportPage'; -import { vulnerabilityReportPath } from './pathsForVulnerabilityReporting'; - import './VulnReportingPage.css'; type PageActions = 'create' | 'edit' | 'clone'; @@ -27,34 +26,32 @@ function VulnReportingPage() { hasReadAccess('Integration'); // for notifiers return ( - + { - if (pageAction === 'create' && hasWriteAccessForReport) { - return ; - } - if (pageAction === undefined) { - return ; - } - return ; - }} + index + element={ + pageAction === 'create' && hasWriteAccessForReport ? ( + + ) : !pageAction ? ( + + ) : ( + + ) + } /> { - if (pageAction === 'edit' && hasWriteAccessForReport) { - return ; - } - if (pageAction === 'clone' && hasWriteAccessForReport) { - return ; - } - return ; - }} + path=":reportId" + element={ + pageAction === 'create' && hasWriteAccessForReport ? ( + + ) : pageAction === 'clone' && hasWriteAccessForReport ? ( + + ) : ( + + ) + } /> - + ); } diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/VulnReports/VulnReportsPage.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/VulnReports/VulnReportsPage.tsx index 7737f62caabb2..66f944aafc06f 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/VulnReports/VulnReportsPage.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/VulnReports/VulnReportsPage.tsx @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import { Link, generatePath, useHistory } from 'react-router-dom'; +import { Link, generatePath, useNavigate } from 'react-router-dom'; import isEmpty from 'lodash/isEmpty'; import { Alert, @@ -76,7 +76,7 @@ const sortOptions = { const emptyReportArray = []; function VulnReportsPage() { - const history = useHistory(); + const navigate = useNavigate(); const { currentUser } = useAuthStatus(); const { hasReadWriteAccess, hasReadAccess } = usePermissions(); @@ -414,7 +414,7 @@ function VulnReportsPage() { { reportId: report.id, } - ) as string; + ); const snapshot = reportSnapshots[report.id]; const isReportStatusPending = snapshot?.reportStatus.runState === 'PREPARING' || @@ -424,7 +424,7 @@ function VulnReportsPage() { title: 'Edit report', onClick: (event) => { event.preventDefault(); - history.push(`${vulnReportURL}?action=edit`); + navigate(`${vulnReportURL}?action=edit`); }, isDisabled: isReportStatusPending, }, @@ -457,7 +457,7 @@ function VulnReportsPage() { title: 'Clone report', onClick: (event) => { event.preventDefault(); - history.push(`${vulnReportURL}?action=clone`); + navigate(`${vulnReportURL}?action=clone`); }, }, { diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Image/ImagePage.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Image/ImagePage.tsx index 2bebf467c63b8..17d353d1231a2 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Image/ImagePage.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Image/ImagePage.tsx @@ -79,8 +79,8 @@ function OptionalSbomButtonTooltip({ } function ImagePage() { - const { imageId } = useParams(); const { getAbsoluteUrl, pageTitle } = useWorkloadCveViewContext(); + const { imageId } = useParams() as { imageId: string }; const { data, error } = useQuery< { image: { diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/WorkloadCvesPage.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/WorkloadCvesPage.tsx index 4dfb43386c10b..094669c3d567d 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/WorkloadCvesPage.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/WorkloadCvesPage.tsx @@ -1,5 +1,5 @@ import React, { useMemo } from 'react'; -import { Route, Switch } from 'react-router-dom'; +import { Route, Routes } from 'react-router-dom'; import { PageSection } from '@patternfly/react-core'; import PageNotFound from 'Components/PageNotFound'; @@ -118,31 +118,24 @@ function WorkloadCvesPage({ view }: WorkloadCvePageProps) { return ( {hasReadAccessForIntegration && } - + {hasReadAccessForNamespaces && ( - - - + } /> )} - - - - - - - - - - - - - - - - - - - + } /> + } /> + } /> + } /> + + + + + } + /> + ); } diff --git a/ui/apps/platform/src/hooks/useURLPagination.test.tsx b/ui/apps/platform/src/hooks/useURLPagination.test.tsx index 6e331c37fd2bb..1c95eafe2b991 100644 --- a/ui/apps/platform/src/hooks/useURLPagination.test.tsx +++ b/ui/apps/platform/src/hooks/useURLPagination.test.tsx @@ -1,34 +1,12 @@ -import React, { ReactNode } from 'react'; -import { MemoryRouter, Route, RouteComponentProps } from 'react-router-dom'; +import React from 'react'; +import { createMemoryHistory } from 'history'; +import { HistoryRouter as Router } from 'redux-first-history/rr6'; +import { MemoryRouter, useLocation } from 'react-router-dom'; import { renderHook, act } from '@testing-library/react'; import { URLSearchParams } from 'url'; import useURLPagination from './useURLPagination'; -type WrapperProps = { - children: ReactNode; - onRouteRender: (renderResult: RouteComponentProps) => void; - initialEntries: string[]; -}; - -function Wrapper({ children, onRouteRender, initialEntries = [] }: WrapperProps) { - return ( - - - {children} - - ); -} - -const createWrapper = (props) => { - return function CreatedWrapper({ children }) { - return {children}; - }; -}; - beforeAll(() => { jest.useFakeTimers(); }); @@ -44,15 +22,17 @@ test('should update pagination parameters in the URL', async () => { let params; let testLocation; - const { result } = renderHook(() => useURLPagination(10), { - wrapper: createWrapper({ - children: [], - onRouteRender: ({ location }) => { - testLocation = location; - }, - initialEntries: [''], - }), - }); + const { result } = renderHook( + () => { + testLocation = useLocation(); + return useURLPagination(10); + }, + { + wrapper: ({ children }) => ( + {children} + ), + } + ); // Check new and existing values before setter function is called params = new URLSearchParams(testLocation.search); @@ -87,23 +67,23 @@ test('should update pagination parameters in the URL', async () => { test('should not add history states when setting values with a "replace" action', async () => { let params; - let historyLength; - let testLocation; - const { result } = renderHook(() => useURLPagination(10), { - wrapper: createWrapper({ - children: [], - onRouteRender: ({ location, history }) => { - testLocation = location; - historyLength = history.length; - }, - initialEntries: [''], - }), + const history = createMemoryHistory({ + initialEntries: [''], }); + const { result } = renderHook( + () => { + return useURLPagination(10); + }, + { + wrapper: ({ children }) => {children}, + } + ); + // Check the length of the initial history stack - params = new URLSearchParams(testLocation.search); - expect(historyLength).toBe(1); + params = new URLSearchParams(history.location.search); + expect(history.index).toBe(0); expect(params.get('page')).toBe(null); expect(params.get('perPage')).toBe(null); @@ -114,8 +94,8 @@ test('should not add history states when setting values with a "replace" action' }); // Check the length of the history stack after updating the page parameter - params = new URLSearchParams(testLocation.search); - expect(historyLength).toBe(1); + params = new URLSearchParams(history.location.search); + expect(history.index).toBe(0); expect(params.get('page')).toBe('2'); expect(params.get('perPage')).toBe(null); @@ -126,31 +106,31 @@ test('should not add history states when setting values with a "replace" action' }); // Check the length of the history stack after updating the perPage parameter - params = new URLSearchParams(testLocation.search); - expect(historyLength).toBe(1); + params = new URLSearchParams(history.location.search); + expect(history.index).toBe(0); expect(params.get('page')).toBe('1'); expect(params.get('perPage')).toBe('20'); }); test('should only add a single history state when setting perPage without an action parameter', async () => { let params; - let historyLength; - let testLocation; - const { result } = renderHook(() => useURLPagination(10), { - wrapper: createWrapper({ - children: [], - onRouteRender: ({ location, history }) => { - testLocation = location; - historyLength = history.length; - }, - initialEntries: [''], - }), + const history = createMemoryHistory({ + initialEntries: [''], }); + const { result } = renderHook( + () => { + return useURLPagination(10); + }, + { + wrapper: ({ children }) => {children}, + } + ); + // Check the length of the initial history stack - params = new URLSearchParams(testLocation.search); - expect(historyLength).toBe(1); + params = new URLSearchParams(history.location.search); + expect(history.index).toBe(0); expect(params.get('page')).toBe(null); expect(params.get('perPage')).toBe(null); @@ -161,8 +141,8 @@ test('should only add a single history state when setting perPage without an act }); // Check the length of the history stack after updating the page parameter - params = new URLSearchParams(testLocation.search); - expect(historyLength).toBe(2); + params = new URLSearchParams(history.location.search); + expect(history.index).toBe(1); expect(params.get('page')).toBe('2'); expect(params.get('perPage')).toBe(null); @@ -173,8 +153,8 @@ test('should only add a single history state when setting perPage without an act }); // Check the length of the history stack after updating the perPage parameter - params = new URLSearchParams(testLocation.search); - expect(historyLength).toBe(3); + params = new URLSearchParams(history.location.search); + expect(history.index).toBe(2); expect(params.get('page')).toBe('1'); expect(params.get('perPage')).toBe('20'); }); diff --git a/ui/apps/platform/src/hooks/useURLParameter.test.tsx b/ui/apps/platform/src/hooks/useURLParameter.test.tsx index 03dc0e4d2c298..6e1f086756d08 100644 --- a/ui/apps/platform/src/hooks/useURLParameter.test.tsx +++ b/ui/apps/platform/src/hooks/useURLParameter.test.tsx @@ -1,36 +1,12 @@ -import React, { ReactNode } from 'react'; -import { MemoryRouter, Route, RouteComponentProps } from 'react-router-dom'; +import React from 'react'; +import { createMemoryHistory } from 'history'; +import { MemoryRouter, useLocation, useNavigate } from 'react-router-dom'; +import { HistoryRouter as Router } from 'redux-first-history/rr6'; import { renderHook, act } from '@testing-library/react'; import { URLSearchParams } from 'url'; import useURLParameter from './useURLParameter'; -type WrapperProps = { - children: ReactNode; - onRouteRender: (renderResult: RouteComponentProps) => void; - initialEntries: string[]; -}; - -// This Wrapper component allows the `useURLParameter` hook to simulate the browser's -// URL bar in JSDom via the MemoryRouter -function Wrapper({ children, onRouteRender, initialEntries = [] }: WrapperProps) { - return ( - - - {children} - - ); -} - -const createWrapper = (props) => { - return function CreatedWrapper({ children }) { - return {children}; - }; -}; - beforeAll(() => { jest.useFakeTimers(); }); @@ -46,15 +22,17 @@ test('should read/write scoped string value in URL parameter without changing ex let params; let testLocation; - const { result } = renderHook(() => useURLParameter('testKey', undefined), { - wrapper: createWrapper({ - children: [], - onRouteRender: ({ location }) => { - testLocation = location; - }, - initialEntries: ['?oldKey=test'], - }), - }); + const { result } = renderHook( + () => { + testLocation = useLocation(); + return useURLParameter('testKey', undefined); + }, + { + wrapper: ({ children }) => ( + {children} + ), + } + ); // Check new and existing values before setter function is called params = new URLSearchParams(testLocation.search); @@ -91,15 +69,14 @@ test('should allow multiple sequential parameter updates without data loss', asy let testLocation; const { result } = renderHook( - () => [useURLParameter('key1', 'oldValue1'), useURLParameter('key2', undefined)], + () => { + testLocation = useLocation(); + return [useURLParameter('key1', 'oldValue1'), useURLParameter('key2', undefined)]; + }, { - wrapper: createWrapper({ - children: [], - onRouteRender: ({ location }) => { - testLocation = location; - }, - initialEntries: ['?key1=oldValue1'], - }), + wrapper: ({ children }) => ( + {children} + ), } ); @@ -135,15 +112,18 @@ test('should read/write scoped complex object in URL parameter without changing }; const emptyState: StateObject = { clusters: [] }; - const { result } = renderHook(() => useURLParameter('testKey', emptyState), { - wrapper: createWrapper({ - children: [], - onRouteRender: ({ location }) => { - testLocation = location; - }, - initialEntries: ['?oldKey=test'], - }), - }); + + const { result } = renderHook( + () => { + testLocation = useLocation(); + return useURLParameter('testKey', emptyState); + }, + { + wrapper: ({ children }) => ( + {children} + ), + } + ); function isStateObject(obj: unknown): obj is StateObject { return typeof obj === 'object' && obj !== null && 'clusters' in obj; @@ -201,21 +181,27 @@ test('should read/write scoped complex object in URL parameter without changing expect(Array.from(params.entries())).toHaveLength(1); }); -test('should implement push and replace state for history', async () => { - let testHistory; +test('should implement push and replace state for navigate', async () => { + let testNavigate; let testLocation; - const { result } = renderHook(() => useURLParameter('testKey', undefined), { - wrapper: createWrapper({ - children: [], - onRouteRender: ({ history, location }) => { - testHistory = history; - testLocation = location; - }, - initialIndex: 1, - initialEntries: ['/main/dashboard', '/main/clusters?oldKey=test'], - }), - }); + const { result } = renderHook( + () => { + testLocation = useLocation(); + testNavigate = useNavigate(); + return useURLParameter('testKey', undefined); + }, + { + wrapper: ({ children }) => ( + + {children} + + ), + } + ); // Test the the default behavior is to push URL parameter changes to the history stack actAndRunTicks(() => { @@ -225,7 +211,7 @@ test('should implement push and replace state for history', async () => { expect(testLocation.pathname).toBe('/main/clusters'); expect(testLocation.search).toBe('?oldKey=test&testKey=testValue'); actAndRunTicks(() => { - testHistory.goBack(); + testNavigate(-1); }); expect(testLocation.pathname).toBe('/main/clusters'); expect(testLocation.search).toBe('?oldKey=test'); @@ -238,7 +224,7 @@ test('should implement push and replace state for history', async () => { expect(testLocation.pathname).toBe('/main/clusters'); expect(testLocation.search).toBe('?oldKey=test&testKey=newTestValue'); actAndRunTicks(() => { - testHistory.goBack(); + testNavigate(-1); }); expect(testLocation.pathname).toBe('/main/dashboard'); expect(testLocation.search).toBe(''); @@ -247,28 +233,26 @@ test('should implement push and replace state for history', async () => { test('should batch URL parameter updates', async () => { let params; let testLocation; - let testHistory; + + const history = createMemoryHistory({ + initialEntries: [''], + }); const { result } = renderHook( - () => ({ - hook1: useURLParameter('testKey1', undefined), - hook2: useURLParameter('testKey2', undefined), - }), + () => { + testLocation = useLocation(); + return { + hook1: useURLParameter('testKey1', undefined), + hook2: useURLParameter('testKey2', undefined), + }; + }, { - wrapper: createWrapper({ - children: [], - onRouteRender: ({ history, location }) => { - testHistory = history; - testLocation = location; - }, - initialIndex: 1, - initialEntries: [''], - }), + wrapper: ({ children }) => {children}, } ); params = new URLSearchParams(testLocation.search); - expect(testHistory.length).toBe(1); + expect(history.index).toBe(0); expect(params.get('testKey1')).toBeNull(); expect(params.get('testKey2')).toBeNull(); expect(result.current.hook1[0]).toBeUndefined(); @@ -281,7 +265,7 @@ test('should batch URL parameter updates', async () => { }); params = new URLSearchParams(testLocation.search); - expect(testHistory.length).toBe(2); + expect(history.index).toBe(1); expect(params.get('testKey1')).toBe('testValue'); expect(params.get('testKey2')).toBeNull(); expect(result.current.hook1[0]).toBe('testValue'); @@ -297,7 +281,7 @@ test('should batch URL parameter updates', async () => { }); params = new URLSearchParams(testLocation.search); - expect(testHistory.length).toBe(3); + expect(history.index).toBe(2); expect(params.get('testKey1')).toBe('newValue1'); expect(params.get('testKey2')).toBe('newValue3'); expect(result.current.hook1[0]).toBe('newValue1'); @@ -310,7 +294,7 @@ test('should batch URL parameter updates', async () => { }); params = new URLSearchParams(testLocation.search); - expect(testHistory.length).toBe(3); + expect(history.index).toBe(2); expect(params.get('testKey1')).toBe('newTestValue'); expect(params.get('testKey2')).toBe('newValue3'); expect(result.current.hook1[0]).toBe('newTestValue'); @@ -325,7 +309,7 @@ test('should batch URL parameter updates', async () => { }); params = new URLSearchParams(testLocation.search); - expect(testHistory.length).toBe(4); + expect(history.index).toBe(3); expect(params.get('testKey1')).toBe('newValue4'); expect(params.get('testKey2')).toBe('newValue5'); expect(result.current.hook1[0]).toBe('newValue4'); diff --git a/ui/apps/platform/src/hooks/useURLParameter.ts b/ui/apps/platform/src/hooks/useURLParameter.ts index 3d6796f604f6b..443c9b81753ed 100644 --- a/ui/apps/platform/src/hooks/useURLParameter.ts +++ b/ui/apps/platform/src/hooks/useURLParameter.ts @@ -1,13 +1,9 @@ import { createContext, useCallback, useContext, useRef } from 'react'; -import { useLocation, useHistory } from 'react-router-dom'; +import { NavigateFunction, useLocation, useNavigate } from 'react-router-dom'; import isEqual from 'lodash/isEqual'; import { getQueryObject, getQueryString } from 'utils/queryStringUtils'; -// TODO replace with a more accurate type when we upgrade React Router and 'history' -// eslint-disable-next-line @typescript-eslint/no-explicit-any -type History = any; - export type QueryValue = undefined | string | string[] | qs.ParsedQs | qs.ParsedQs[]; // Note that when we upgrade React Router and 'history' we can probably import a more accurate version of this type @@ -27,12 +23,17 @@ export type UrlParameterUpdate = { * @param updates Url parameter updates that need to be applied to the URL * @param history The history object to use to apply the updates */ -export function applyUpdatesToUrl(updates: UrlParameterUpdate[], history: History) { +export function applyUpdatesToUrl( + updates: UrlParameterUpdate[], + search: string, + pathname: string, + navigate: NavigateFunction +) { const action = updates.some(({ historyAction }) => historyAction === 'push') ? 'push' : 'replace'; - const previousQuery = getQueryObject(history.location.search) || {}; + const previousQuery = getQueryObject(search) || {}; const newQuery = { ...previousQuery }; updates.forEach(({ keyPrefix, newValue }) => { @@ -46,7 +47,11 @@ export function applyUpdatesToUrl(updates: UrlParameterUpdate[], history: Histor // Do not change history states if setter is called with current value if (!isEqual(previousQuery, newQuery)) { - history[action]({ search: getQueryString(newQuery) }); + if (action === 'push') { + navigate(`${pathname}${getQueryString(newQuery)}`); + } else if (action === 'replace') { + navigate(`${pathname}${getQueryString(newQuery)}`, { replace: true }); + } } } @@ -61,19 +66,24 @@ function makeMicrotaskSchedulingContext() { let updates: UrlParameterUpdate[] = []; let isUpdateScheduled = false; - function scheduleAndFlushUpdates(history: History) { + function scheduleAndFlushUpdates(search: string, pathname: string, navigate: NavigateFunction) { queueMicrotask(() => { - applyUpdatesToUrl(updates, history); + applyUpdatesToUrl(updates, search, pathname, navigate); updates = []; isUpdateScheduled = false; }); } return { - addUrlParameterUpdate: (update: UrlParameterUpdate, history: History) => { + addUrlParameterUpdate: ( + update: UrlParameterUpdate, + search: string, + pathname: string, + navigate: NavigateFunction + ) => { updates = [...updates, update]; if (!isUpdateScheduled) { - scheduleAndFlushUpdates(history); + scheduleAndFlushUpdates(search, pathname, navigate); } isUpdateScheduled = true; }, @@ -104,8 +114,8 @@ export type UseURLParameterResult = [ */ function useURLParameter(keyPrefix: string, defaultValue: QueryValue): UseURLParameterResult { const { addUrlParameterUpdate } = useContext(UrlParameterUpdateContext); - const history = useHistory(); const location = useLocation(); + const navigate = useNavigate(); // We use an internal Ref here so that calling code that depends on the // value returned by this hook can detect updates. e.g. When used in the // dependency array of a `useEffect`. @@ -115,9 +125,14 @@ function useURLParameter(keyPrefix: string, defaultValue: QueryValue): UseURLPar const setValue = useCallback( (newValue: QueryValue, historyAction: HistoryAction = 'push') => { - addUrlParameterUpdate({ historyAction, keyPrefix, newValue }, history); + addUrlParameterUpdate( + { historyAction, keyPrefix, newValue }, + location.search, + location.pathname, + navigate + ); }, - [addUrlParameterUpdate, keyPrefix, history] + [addUrlParameterUpdate, keyPrefix, location.search, location.pathname, navigate] ); const nextValue = getQueryObject(location.search)[keyPrefix] || defaultValue; diff --git a/ui/apps/platform/src/hooks/useURLSort.test.js b/ui/apps/platform/src/hooks/useURLSort.test.js index 4964673279c9a..5e11c9c8efb53 100644 --- a/ui/apps/platform/src/hooks/useURLSort.test.js +++ b/ui/apps/platform/src/hooks/useURLSort.test.js @@ -1,15 +1,12 @@ import React from 'react'; import { renderHook, act } from '@testing-library/react'; -import { Router } from 'react-router-dom'; -import { createMemoryHistory } from 'history'; +import { MemoryRouter } from 'react-router-dom'; import cloneDeep from 'lodash/cloneDeep'; import useURLSort from './useURLSort'; const wrapper = ({ children }) => { - const history = createMemoryHistory(); - - return {children}; + return {children}; }; beforeAll(() => { diff --git a/ui/apps/platform/src/hooks/useURLStringUnion.test.tsx b/ui/apps/platform/src/hooks/useURLStringUnion.test.tsx index 2b2c9328e760e..f345d5d1edf0a 100644 --- a/ui/apps/platform/src/hooks/useURLStringUnion.test.tsx +++ b/ui/apps/platform/src/hooks/useURLStringUnion.test.tsx @@ -1,36 +1,10 @@ -import React, { ReactNode } from 'react'; -import { MemoryRouter, Route, RouteComponentProps } from 'react-router-dom'; +import React from 'react'; +import { MemoryRouter, useLocation } from 'react-router-dom'; import { renderHook, act } from '@testing-library/react'; import { URLSearchParams } from 'url'; import useURLStringUnion from './useURLStringUnion'; -type WrapperProps = { - children: ReactNode; - onRouteRender: (renderResult: RouteComponentProps) => void; - initialEntries: string[]; -}; - -// This Wrapper component allows the hook to simulate the browser's -// URL bar in JSDom via the MemoryRouter -function Wrapper({ children, onRouteRender, initialEntries = [] }: WrapperProps) { - return ( - - - {children} - - ); -} - -const createWrapper = (props) => { - return function CreatedWrapper({ children }) { - return {children}; - }; -}; - beforeAll(() => { jest.useFakeTimers(); }); @@ -48,15 +22,18 @@ test('should read/write only the specified set of strings to the URL parameter', const possibleUrlValues = ['Alpha', 'Beta', 'Delta'] as const; - const { result } = renderHook(() => useURLStringUnion('urlKey', possibleUrlValues), { - wrapper: createWrapper({ - children: [], - onRouteRender: ({ location }) => { - testLocation = location; - }, - initialEntries: [''], - }), - }); + const { result } = renderHook( + () => { + testLocation = useLocation(); + return useURLStringUnion('urlKey', possibleUrlValues); + }, + { + wrapper: ({ children }) => ( + {children} + ), + } + ); + actAndRunTicks(() => {}); // Check that default value is applied correctly @@ -112,17 +89,17 @@ test('should default to the current URL parameter value on initialization, if it const possibleUrlValues = ['Alpha', 'Beta', 'Delta'] as const; const { result: initialValidResult } = renderHook( - () => useURLStringUnion('urlKey', possibleUrlValues), + () => { + testLocation = useLocation(); + return useURLStringUnion('urlKey', possibleUrlValues); + }, { - wrapper: createWrapper({ - children: [], - onRouteRender: ({ location }) => { - testLocation = location; - }, - initialEntries: ['?urlKey=Beta'], - }), + wrapper: ({ children }) => ( + {children} + ), } ); + actAndRunTicks(() => {}); // Check that default value is not applied if the URL param already contains a valid value @@ -135,18 +112,19 @@ test('should use the default value when an invalid value is entered directly int let testLocation; const possibleUrlValues = ['Alpha', 'Beta', 'Delta'] as const; + const { result: initialInvalidResult } = renderHook( - () => useURLStringUnion('urlKey', possibleUrlValues), + () => { + testLocation = useLocation(); + return useURLStringUnion('urlKey', possibleUrlValues); + }, { - wrapper: createWrapper({ - children: [], - onRouteRender: ({ location }) => { - testLocation = location; - }, - initialEntries: ['?urlKey=Bogus'], - }), + wrapper: ({ children }) => ( + {children} + ), } ); + actAndRunTicks(() => {}); // Check that default value is applied correctly when the URL param is invalid diff --git a/ui/apps/platform/src/hooks/useWorkflowMatch.js b/ui/apps/platform/src/hooks/useWorkflowMatch.js new file mode 100644 index 0000000000000..aeb91d0539841 --- /dev/null +++ b/ui/apps/platform/src/hooks/useWorkflowMatch.js @@ -0,0 +1,29 @@ +import { useLocation, matchPath } from 'react-router-dom'; + +import { workflowPaths, validPageEntityListTypes, validPageEntityTypes } from 'routePaths'; + +function useWorkflowMatch() { + const location = useLocation(); + + const entityMatch = matchPath({ path: workflowPaths.ENTITY }, location.pathname); + + const listMatch = matchPath({ path: workflowPaths.LIST }, location.pathname); + + const dashboardMatch = matchPath({ path: workflowPaths.DASHBOARD }, location.pathname); + + if (entityMatch && validPageEntityTypes.includes(entityMatch.params.pageEntityType)) { + return entityMatch; + } + + if (listMatch && validPageEntityListTypes.includes(listMatch.params.pageEntityListType)) { + return listMatch; + } + + if (dashboardMatch) { + return dashboardMatch; + } + + return null; +} + +export default useWorkflowMatch; diff --git a/ui/apps/platform/src/index.tsx b/ui/apps/platform/src/index.tsx index 6e8a97251e73a..1f84660e7a8f5 100644 --- a/ui/apps/platform/src/index.tsx +++ b/ui/apps/platform/src/index.tsx @@ -6,9 +6,8 @@ import React from 'react'; import { createRoot } from 'react-dom/client'; import { Provider } from 'react-redux'; -import { AnyAction, Store } from 'redux'; -import { ConnectedRouter } from 'connected-react-router'; -import { createBrowserHistory as createHistory } from 'history'; +import { HistoryRouter as Router } from 'redux-first-history/rr6'; +import { AnyAction } from 'redux'; import { ApolloProvider } from '@apollo/client'; import 'css.imports'; @@ -51,8 +50,7 @@ installRaven(); const rootNode = document.getElementById('root'); /* @ts-expect-error `createRoot` expects a non-null argument */ const root = createRoot(rootNode); -const history = createHistory(); -const store = configureStore(undefined, history) as Store; +const { store, history } = configureStore(); const apolloClient = configureApollo(); const dispatch = (action) => @@ -67,17 +65,11 @@ dispatch(fetchCentralCapabilitiesThunk()); root.render( - {/* - (dv 2024-05-01) - ConnectedRouter does not explicitly declare `children` as a prop, which is expected by React types >=18 - so we need to use `@ts-expect-error` to suppress the type error - */} - {/* @ts-expect-error `connected-react-router does not support React 18 */} - + - + ); diff --git a/ui/apps/platform/src/reducers/index.js b/ui/apps/platform/src/reducers/index.js index c259dd9ab03f2..08f75dc388cbb 100644 --- a/ui/apps/platform/src/reducers/index.js +++ b/ui/apps/platform/src/reducers/index.js @@ -1,6 +1,5 @@ import { combineReducers } from 'redux'; import { reducer as formReducer } from 'redux-form'; -import { connectRouter } from 'connected-react-router'; import bindSelectors from 'utils/bindSelectors'; import apiTokens, { selectors as apiTokenSelectors } from './apitokens'; @@ -22,7 +21,6 @@ import serverResponseStatus, { } from './serverResponseStatus'; import metadata, { selectors as metadataSelectors } from './metadata'; import loading, { selectors as loadingSelectors } from './loading'; -import { selectors as routeSelectors } from './routes'; import groups, { selectors as groupsSelectors } from './groups'; import publicConfig, { selectors as publicConfigSelectors } from './publicConfig'; import telemetryConfig, { selectors as telemetryConfigSelectors } from './telemetryConfig'; @@ -56,9 +54,9 @@ const appReducer = combineReducers({ cloudSources, }); -const createRootReducer = (history) => { +const createRootReducer = (routerReducer) => { return combineReducers({ - router: connectRouter(history), + router: routerReducer, form: formReducer, app: appReducer, }); @@ -68,7 +66,6 @@ export default createRootReducer; // Selectors -const getRoute = (state) => state.router; const getApp = (state) => state.app; const getAPITokens = (state) => getApp(state).apiTokens; const getAuth = (state) => getApp(state).auth; @@ -103,7 +100,6 @@ const boundSelectors = { ...bindSelectors(getFeatureFlags, featureFlagSelectors), ...bindSelectors(getPolicies, policySelectors), ...bindSelectors(getRoles, roleSelectors), - ...bindSelectors(getRoute, routeSelectors), ...bindSelectors(getSearchAutocomplete, searchAutoCompleteSelectors), ...bindSelectors(getServerResponseStatus, serverResponseStatusSelectors), ...bindSelectors(getLoadingStatus, loadingSelectors), diff --git a/ui/apps/platform/src/reducers/routes.js b/ui/apps/platform/src/reducers/routes.js deleted file mode 100644 index 9c7ffeea4bb7e..0000000000000 --- a/ui/apps/platform/src/reducers/routes.js +++ /dev/null @@ -1,25 +0,0 @@ -import { LOCATION_CHANGE, push, replace, go, goBack, goForward } from 'connected-react-router'; - -// Action Types - -export const types = { - LOCATION_CHANGE, -}; - -// Actions - -export const actions = { - push, - replace, - go, - goBack, - goForward, -}; - -// Selectors - -const getLocation = (state) => state.location; - -export const selectors = { - getLocation, -}; diff --git a/ui/apps/platform/src/routePaths.ts b/ui/apps/platform/src/routePaths.ts index 4606517025cb5..9fe110a7d133a 100644 --- a/ui/apps/platform/src/routePaths.ts +++ b/ui/apps/platform/src/routePaths.ts @@ -33,7 +33,6 @@ export const clustersSecureClusterPath = `${clustersBasePath}/secure-a-cluster`; export const collectionsBasePath = `${mainPath}/collections`; export const collectionsPath = `${mainPath}/collections/:collectionId?`; export const complianceBasePath = `${mainPath}/compliance`; -export const compliancePath = `${mainPath}/:context(compliance)`; export const complianceEnhancedBasePath = `${mainPath}/compliance`; export const complianceEnhancedCoveragePath = `${complianceEnhancedBasePath}/coverage`; export const complianceEnhancedSchedulesPath = `${complianceEnhancedBasePath}/schedules`; @@ -153,7 +152,8 @@ export type RouteKey = | 'clusters' | 'collections' | 'compliance' - | 'compliance-enhanced' + | 'compliance-coverage' + | 'compliance-schedules' | 'configmanagement' | 'dashboard' | 'exception-configuration' @@ -234,7 +234,10 @@ const routeRequirementsMap: Record = { // 'ServiceAccount', // for Cluster and Deployment ]), }, - 'compliance-enhanced': { + 'compliance-coverage': { + resourceAccessRequirements: everyResource(['Compliance']), + }, + 'compliance-schedules': { resourceAccessRequirements: everyResource(['Compliance']), }, configmanagement: { @@ -469,11 +472,11 @@ export const basePathToLabelMap: Record = { [userBasePath]: 'User Profile', }; -const entityListTypeMatcher = `(${Object.values(urlEntityListTypes).join('|')})`; -const entityTypeMatcher = `(${Object.values(urlEntityTypes).join('|')})`; +export const validPageEntityListTypes = Object.values(urlEntityListTypes); +export const validPageEntityTypes = Object.values(urlEntityTypes); export const workflowPaths = { DASHBOARD: `${mainPath}/:context`, - LIST: `${mainPath}/:context/:pageEntityListType${entityListTypeMatcher}/:entityId1?/:entityType2?/:entityId2?`, - ENTITY: `${mainPath}/:context/:pageEntityType${entityTypeMatcher}/:pageEntityId?/:entityType1?/:entityId1?/:entityType2?/:entityId2?`, + LIST: `${mainPath}/:context/:pageEntityListType/:entityId1?/:entityType2?/:entityId2?`, + ENTITY: `${mainPath}/:context/:pageEntityType/:pageEntityId?/:entityType1?/:entityId1?/:entityType2?/:entityId2?`, }; diff --git a/ui/apps/platform/src/sagas/authSagas.js b/ui/apps/platform/src/sagas/authSagas.js index 847c795b7fa9a..1d25ccd3d3830 100644 --- a/ui/apps/platform/src/sagas/authSagas.js +++ b/ui/apps/platform/src/sagas/authSagas.js @@ -1,8 +1,8 @@ import { all, take, call, fork, put, takeLatest, takeEvery, select } from 'redux-saga/effects'; import { delay } from 'redux-saga'; -import { push } from 'connected-react-router'; import queryString from 'qs'; import Raven from 'raven-js'; +import { LOCATION_CHANGE, push } from 'redux-first-history'; import { Base64 } from 'js-base64'; import { loginPath, testLoginResultsPath, authResponsePrefix } from 'routePaths'; @@ -14,7 +14,6 @@ import { fetchUserRolePermissions } from 'services/RolesService'; import { selectors } from 'reducers'; import { actions, types, AUTH_STATUS } from 'reducers/auth'; import { actions as groupActions } from 'reducers/groups'; -import { types as locationActionTypes } from 'reducers/routes'; import { actions as notificationActions } from 'reducers/notifications'; import { actions as rolesActions } from 'reducers/roles'; @@ -393,7 +392,7 @@ export default function* auth() { yield fork(fetchAvailableProviderTypes); // take the first location change, i.e. the location where user landed first time - const action = yield take(locationActionTypes.LOCATION_CHANGE); + const action = yield take(LOCATION_CHANGE); const { payload: { location }, } = action; diff --git a/ui/apps/platform/src/sagas/authSagas.test.js b/ui/apps/platform/src/sagas/authSagas.test.js index 5748ce4d18842..8490bb8f5c5a0 100644 --- a/ui/apps/platform/src/sagas/authSagas.test.js +++ b/ui/apps/platform/src/sagas/authSagas.test.js @@ -1,5 +1,5 @@ import { select, call } from 'redux-saga/effects'; -import { push } from 'connected-react-router'; +import { push } from 'redux-first-history'; import { expectSaga } from 'redux-saga-test-plan'; import { dynamic, throwError } from 'redux-saga-test-plan/providers'; diff --git a/ui/apps/platform/src/sagas/sagaTestUtils.js b/ui/apps/platform/src/sagas/sagaTestUtils.js index 3f87b7ed29568..99cd0e075baa4 100644 --- a/ui/apps/platform/src/sagas/sagaTestUtils.js +++ b/ui/apps/platform/src/sagas/sagaTestUtils.js @@ -1,5 +1,3 @@ -import { types as locationActionTypes } from '../reducers/routes'; - /** * Redux Saga helper function that creates an action for changing the location state * @@ -11,7 +9,7 @@ import { types as locationActionTypes } from '../reducers/routes'; */ export default function createLocationChange(pathname, from, hash) { return { - type: locationActionTypes.LOCATION_CHANGE, + type: '@@router/LOCATION_CHANGE', payload: { location: { pathname, hash, state: { from } } }, }; } diff --git a/ui/apps/platform/src/store/configureStore.js b/ui/apps/platform/src/store/configureStore.js index 6333c217702ca..748455a536439 100644 --- a/ui/apps/platform/src/store/configureStore.js +++ b/ui/apps/platform/src/store/configureStore.js @@ -1,5 +1,6 @@ import { createStore, applyMiddleware, compose } from 'redux'; -import { routerMiddleware } from 'connected-react-router'; +import { createReduxHistoryContext } from 'redux-first-history'; +import { createBrowserHistory } from 'history'; import createSagaMiddleware from 'redux-saga'; import createRavenMiddleware from 'raven-for-redux'; import Raven from 'raven-js'; @@ -11,6 +12,10 @@ import { actions as authActions } from 'reducers/auth'; import * as AuthService from 'services/AuthService'; import registerServerErrorHandler from 'services/serverErrorHandler'; +const { createReduxHistory, routerMiddleware, routerReducer } = createReduxHistoryContext({ + history: createBrowserHistory(), +}); + const sagaMiddleware = createSagaMiddleware({ onError: (error) => Raven.captureException(error), }); @@ -18,25 +23,20 @@ const sagaMiddleware = createSagaMiddleware({ // Omit Redux state to reduce size of payload in /api/logimbue request. const ravenMiddleware = createRavenMiddleware(Raven, { stateTransformer: () => null }); -export default function configureStore(initialState = {}, history) { - const middlewares = [sagaMiddleware, routerMiddleware(history), ravenMiddleware, thunk]; +export default function configureStore(initialState = {}) { + const middlewares = [sagaMiddleware, routerMiddleware, ravenMiddleware, thunk]; const enhancers = [applyMiddleware(...middlewares)]; - // If Redux DevTools Extension is installed use it, otherwise use Redux compose const composeEnhancers = process.env.NODE_ENV !== 'production' && typeof window === 'object' && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ - ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({ - // TODO Try to remove when `react-router-redux` is out of beta, LOCATION_CHANGE should not be fired more than once after hot reloading - // Prevent recomputing reducers for `replaceReducer` - shouldHotReload: false, - }) + ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({ shouldHotReload: false }) : compose; - const rootReducer = createRootReducer(history); + + const rootReducer = createRootReducer(routerReducer); const store = createStore(rootReducer, initialState, composeEnhancers(...enhancers)); - // add auth interceptors before any HTTP request to APIs (i.e. before running sagas) AuthService.addAuthInterceptors((error) => store.dispatch(authActions.handleAuthHttpError(error)) ); @@ -45,6 +45,10 @@ export default function configureStore(initialState = {}, history) { () => store.dispatch({ type: 'serverStatus/RESPONSE_SUCCESS' }), () => store.dispatch({ type: 'serverStatus/RESPONSE_FAILURE', now: Date.now() }) ); + sagaMiddleware.run(rootSaga); - return store; + + const history = createReduxHistory(store); + + return { store, history }; } diff --git a/ui/apps/platform/src/test-utils/ComponentProviders.tsx b/ui/apps/platform/src/test-utils/ComponentProviders.tsx index 6979b4c0ed5b2..883d4fd53360e 100644 --- a/ui/apps/platform/src/test-utils/ComponentProviders.tsx +++ b/ui/apps/platform/src/test-utils/ComponentProviders.tsx @@ -1,9 +1,8 @@ import React from 'react'; import { Store, createStore } from 'redux'; import { Provider } from 'react-redux'; -import { Router } from 'react-router-dom'; +import { BrowserRouter } from 'react-router-dom'; import { ApolloProvider } from '@apollo/client'; -import { createBrowserHistory } from 'history'; import configureApolloClient from 'configureApolloClient'; @@ -19,13 +18,11 @@ export default function ComponentTestProviders({ children, reduxStore = defaultStore, }: ComponentTestProviderProps) { - const history = createBrowserHistory(); - return ( - + {children} - + ); } diff --git a/ui/apps/platform/src/test-utils/renderWithRouter.js b/ui/apps/platform/src/test-utils/renderWithRouter.js index 89761c2db2105..cf0fd00163c8b 100644 --- a/ui/apps/platform/src/test-utils/renderWithRouter.js +++ b/ui/apps/platform/src/test-utils/renderWithRouter.js @@ -1,18 +1,10 @@ import React from 'react'; -import { Router } from 'react-router-dom'; -import { createMemoryHistory } from 'history'; +import { MemoryRouter } from 'react-router-dom'; import { render } from '@testing-library/react'; -function renderWithRouter( - ui, - { route = '/', history = createMemoryHistory({ initialEntries: [route] }) } = {} -) { +function renderWithRouter(ui, { route = '/' } = {}) { return { - ...render({ui}), - // adding `history` to the returned utilities to allow us - // to reference it in our tests (just try to avoid using - // this to test implementation details). - history, + ...render({ui}), }; } diff --git a/ui/apps/platform/src/utils/URLGenerator.js b/ui/apps/platform/src/utils/URLGenerator.js index 95038f2251051..ec207597ce19b 100644 --- a/ui/apps/platform/src/utils/URLGenerator.js +++ b/ui/apps/platform/src/utils/URLGenerator.js @@ -153,7 +153,14 @@ function generateURL(workflowState) { encodeValuesOnly: true, }) : ''; - const newPath = generatePath(path, params) + queryString; + + const encodedParams = Object.fromEntries( + Object.entries(params).map(([key, value]) => [ + key, + value !== undefined ? encodeURIComponent(value) : value, + ]) + ); + const newPath = generatePath(path, encodedParams) + queryString; return newPath; } diff --git a/ui/apps/platform/src/utils/URLParseGenerate.test.js b/ui/apps/platform/src/utils/URLParseGenerate.test.js index 6e4ce185d9af0..594a8f639a374 100644 --- a/ui/apps/platform/src/utils/URLParseGenerate.test.js +++ b/ui/apps/platform/src/utils/URLParseGenerate.test.js @@ -1,4 +1,3 @@ -import { createLocation } from 'history'; import qs from 'qs'; import entityTypes from 'constants/entityTypes'; @@ -13,10 +12,10 @@ import { WorkflowState } from './WorkflowState'; function getLocation(search, pathname) { return { - location: createLocation({ - search: qs.stringify(search), + location: { pathname, - }), + search: `?${qs.stringify(search)}`, + }, }; } @@ -123,10 +122,10 @@ describe('ParseURL', () => { const context = 'clusters'; const pathname = `/main/${context}/0123456789abcdef`; - const location = createLocation({ + const location = { pathname, search: '', - }); + }; const workflowState = parseURL(location); @@ -143,10 +142,10 @@ describe('ParseURL', () => { const context = 'clusters'; const pathname = `/main/${context}`; - const location = createLocation({ + const location = { pathname, search: 's[CLUSTER_HEALTH]=HEALTHY', - }); + }; const workflowState = parseURL(location); diff --git a/ui/apps/platform/src/utils/URLParser.ts b/ui/apps/platform/src/utils/URLParser.ts index d1e678c62e48a..56715f3a1983d 100644 --- a/ui/apps/platform/src/utils/URLParser.ts +++ b/ui/apps/platform/src/utils/URLParser.ts @@ -1,6 +1,5 @@ -import { matchPath } from 'react-router-dom'; +import { Location, matchPath } from 'react-router-dom'; import qs, { ParsedQs } from 'qs'; -import { Location, LocationState } from 'history'; import useCases from 'constants/useCaseTypes'; import { searchParams, sortParams, pagingParams } from 'constants/searchParams'; @@ -16,6 +15,8 @@ import { policiesPath, userRolePath, accessControlPath, + validPageEntityListTypes, + validPageEntityTypes, } from '../routePaths'; type ParamsWithContext = { @@ -35,10 +36,7 @@ const nonWorkflowUseCasePathEntries = Object.entries({ function getNonWorkflowParams(pathname): ParamsWithContext { for (let i = 0; i < nonWorkflowUseCasePathEntries.length; i += 1) { const [useCaseKey, path] = nonWorkflowUseCasePathEntries[i]; - const matchedPath = matchPath(pathname, { - path, - exact: true, - }); + const matchedPath = matchPath({ path, end: true }, pathname); if (matchedPath?.params) { const { params } = matchedPath; @@ -55,24 +53,23 @@ function getNonWorkflowParams(pathname): ParamsWithContext { function getParams(pathname): ParamsWithContext { // The type casts assert that workflow paths include a :context param. - const matchedEntityPath = matchPath(pathname, { - path: workflowPaths.ENTITY, - }); - if (matchedEntityPath?.params) { + const matchedEntityPath = matchPath({ path: workflowPaths.ENTITY }, pathname); + if ( + matchedEntityPath && + validPageEntityTypes.includes(matchedEntityPath?.params?.pageEntityType ?? '') + ) { return matchedEntityPath.params as ParamsWithContext; } - const matchedListPath = matchPath(pathname, { - path: workflowPaths.LIST, - }); - if (matchedListPath?.params) { + const matchedListPath = matchPath({ path: workflowPaths.LIST }, pathname); + if ( + matchedListPath && + validPageEntityListTypes.includes(matchedListPath?.params?.pageEntityListType ?? '') + ) { return matchedListPath.params as ParamsWithContext; } - const matchedDashboardPath = matchPath(pathname, { - path: workflowPaths.DASHBOARD, - exact: true, - }); + const matchedDashboardPath = matchPath({ path: workflowPaths.DASHBOARD, end: true }, pathname); if (matchedDashboardPath?.params) { return matchedDashboardPath.params as ParamsWithContext; } @@ -152,7 +149,7 @@ function formatSort(sort?: ParsedQs | ParsedQs[]): Record[] | n // Convert URL to workflow state and search objects // note: this will read strictly from 'location' as 'match' is relative to the closest Route component -function parseURL(location: Location): WorkflowState { +function parseURL(location: Location): WorkflowState { if (!location) { // TODO: be more specific, it could be an exception instead of a dummy object return new WorkflowState(); diff --git a/ui/apps/platform/src/utils/chartUtils.ts b/ui/apps/platform/src/utils/chartUtils.ts index 6ac16d8e9a628..bcc3291b52c6f 100644 --- a/ui/apps/platform/src/utils/chartUtils.ts +++ b/ui/apps/platform/src/utils/chartUtils.ts @@ -1,4 +1,4 @@ -import { History } from 'react-router-dom'; +import { NavigateFunction } from 'react-router-dom'; import { ChartBarProps, ChartLabelProps, @@ -58,7 +58,7 @@ type ChartEventHandler = ValueOf; * A helper function to generate a chart onClick event that initiates navigation to another page. */ export function navigateOnClickEvent( - history: History, + navigate: NavigateFunction, /** A function that generates the link to navigate to when the entity is clicked */ linkWith: (props: ChartLabelProps) => string, /** An array of Victory onClick event handlers that will be called before navigation is initiated */ @@ -67,7 +67,7 @@ export function navigateOnClickEvent( const navigateEventHandler = { mutation: (props) => { const link = linkWith(props); - history.push(link); + navigate(link); return null; }, }; diff --git a/ui/apps/platform/src/utils/sagaEffects.js b/ui/apps/platform/src/utils/sagaEffects.js index accfd737f3240..3621609fd69f9 100644 --- a/ui/apps/platform/src/utils/sagaEffects.js +++ b/ui/apps/platform/src/utils/sagaEffects.js @@ -1,5 +1,5 @@ +import { LOCATION_CHANGE } from 'redux-first-history'; import { take, fork } from 'redux-saga/effects'; -import { LOCATION_CHANGE } from 'connected-react-router'; import { matchPath } from 'react-router-dom'; /** @@ -22,13 +22,14 @@ export const takeEveryLocation = (route, saga, ...args) => fork(function* worker() { while (true) { const action = yield take(LOCATION_CHANGE); + if (action.payload?.location?.pathname) { const { payload: { location }, } = action; - const match = matchPath(location.pathname, route); + const match = matchPath(route, location.pathname); + if (match) { - // @ts-expect-error TODO: understand what we're doing wrong here... yield fork(saga, ...args.concat({ match, location })); } } @@ -51,13 +52,14 @@ export const takeEveryNewlyMatchedLocation = (route, saga, ...args) => let prevLocationMatched = false; while (true) { const action = yield take(LOCATION_CHANGE); + if (action.payload?.location?.pathname) { const { payload: { location }, } = action; - const match = matchPath(location.pathname, route); + const match = matchPath(`${route}/*`, location.pathname); + if (match && !prevLocationMatched) { - // @ts-expect-error TODO: understand what we're doing wrong here... yield fork(saga, ...args.concat({ match, location })); } prevLocationMatched = !!match;