From ca648e6f98d1e11f06e2f65e0711133d5edc4afe Mon Sep 17 00:00:00 2001 From: Bryan Florkiewicz Date: Thu, 9 May 2024 23:54:46 -0400 Subject: [PATCH 001/151] Add ip address whitelist UI to manage satellites page for itless --- src/components/Satellite/IPWhitelistTable.tsx | 261 ++++++++++++++++++ src/layouts/SatelliteToken.tsx | 15 +- 2 files changed, 275 insertions(+), 1 deletion(-) create mode 100644 src/components/Satellite/IPWhitelistTable.tsx diff --git a/src/components/Satellite/IPWhitelistTable.tsx b/src/components/Satellite/IPWhitelistTable.tsx new file mode 100644 index 000000000..8261638b1 --- /dev/null +++ b/src/components/Satellite/IPWhitelistTable.tsx @@ -0,0 +1,261 @@ +import React, { useEffect, useState } from 'react'; +import { debounce } from 'lodash'; +import { + ActionGroup, + Bullseye, + Button, + EmptyState, + EmptyStateBody, + EmptyStateHeader, + EmptyStateVariant, + Form, + FormGroup, + FormHelperText, + HelperText, + HelperTextItem, + Modal, + ModalVariant, + Text, + TextContent, + TextInput, + ValidatedOptions, +} from '@patternfly/react-core'; +import { InnerScrollContainer, OuterScrollContainer, Table, TableText, TableVariant, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table'; +import { ExclamationCircleIcon } from '@patternfly/react-icons'; +import axios from 'axios'; +import SkeletonTable from '@redhat-cloud-services/frontend-components/SkeletonTable'; + +type IPBlock = { + ip_block: string; + org_id: string; + created_at: string; +}; + +const IPWhitelistTable: React.FC = () => { + const [allAddresses, setAllAddresses] = useState([]); + const [loaded, setLoaded] = useState(false); + const [actionPending, setActionPending] = useState(false); + const [inputAddresses, setInputAddresses] = useState(''); + const [inputAddressesValidated, setInputAddressesesValidated] = useState(false); + const [removeAddresses, setRemoveAddresses] = useState(''); + const [isIPModalOpen, setIsIPModalOpen] = useState(false); + const [isIPRemoveModalOpen, setIsIPRemoveModalOpen] = useState(false); + + const getIPAddresses = () => { + return axios.get('/api/v1/allowlist'); + }; + + const removeIPAddresses = (ipBlock: string) => { + return axios.delete(`/api/v1/allowlist?block=${ipBlock}`); + }; + + const addIPAddresses = (ipBlock: string) => { + return axios.post('/api/v1/allowlist', { ip_block: ipBlock }); + }; + + useEffect(() => { + if (!loaded && !actionPending) { + // If we fail to load the IP blocks, show the empty state by default after 10 seconds + setTimeout(() => setLoaded(true), 10000); + getIPAddresses() + .then((res) => { + setAllAddresses(res.data); + setLoaded(true); + }) + .catch((err) => console.error(err)); + } + }, [loaded, actionPending]); + + const onChangedAddresses = (value: string) => { + setInputAddresses(value); + setInputAddressesesValidated(validateIPAddress(value)); + }; + + const onSubmitAddresses = () => { + setActionPending(true); + addIPAddresses(inputAddresses) + .then(() => { + setInputAddresses(''); + setIsIPModalOpen(false); + setLoaded(false); + return getIPAddresses(); + }) + .then((res) => { + setAllAddresses(res.data); + setLoaded(true); + }) + .catch((err) => console.error(err)) + .finally(() => setActionPending(false)); + }; + + const onRemoveAddresses = () => { + setActionPending(true); + removeIPAddresses(removeAddresses) + .then(() => { + setRemoveAddresses(''); + setIsIPRemoveModalOpen(false); + setLoaded(false); + return getIPAddresses(); + }) + .then((res) => { + setAllAddresses(res.data); + setLoaded(true); + }) + .catch((err) => console.error(err)) + .finally(() => setActionPending(false)); + }; + + const onChangedAddressesDebounced = debounce(onChangedAddresses, 500); + + const validateIPAddress = (address: string) => { + return /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\/([1-9]|[12][0-9]|3[0-2]))?$/.test( + address + ); + }; + + const validationError = inputAddresses.length > 0 && !inputAddressesValidated; + + const addIPModal = ( + { + setInputAddresses(''); + setIsIPModalOpen(false); + }} + title={'Add IP Addresses to Allow List'} + variant={ModalVariant.medium} + > +
) => event.preventDefault()}> + + + Before connecting to your satellite servers, Red Hat needs to add your IP address or range of IP addresses to an allow-list. + + onChangedAddressesDebounced(value)} + > + {validationError && ( + + + } variant={ValidatedOptions.error}> + Enter a valid IP address or CIDR notation IP range + + + + )} + + + + +
+
+ ); + + const removeIPModal = ( + { + setRemoveAddresses(''); + setIsIPRemoveModalOpen(false); + }} + title={'Remove IP Addresses from Allow List'} + variant={ModalVariant.medium} + > +
) => event.preventDefault()}> + + + The following IP addresses will be removed from the allow list + + + + + + +
+
+ ); + + const columnNames = { + ip_block: 'IP Block', + org_id: 'Org ID', + created_at: 'Created At', + remove: '', + }; + + const skeletonTable = ; + + const emptyTable = ( + + + + + + + Before connecting to your satellite servers, Red Hat needs to add your IP address or range of IP addresses to an allow-list. + + + + + + ); + + const ipTable = ( + + + + + + + + + + + + + {allAddresses.length <= 0 && emptyTable} + {allAddresses.map((ipBlock) => ( + + + + + + + ))} + +
{columnNames.ip_block}{columnNames.org_id}{columnNames.created_at}{columnNames.remove}
{ipBlock.ip_block}{ipBlock.org_id}{ipBlock.created_at} + + + +
+
+
+ ); + + return ( + <> + {addIPModal} + {removeIPModal} + <> + {loaded ? ipTable : skeletonTable} +
+ +
+ + + ); +}; + +export default IPWhitelistTable; diff --git a/src/layouts/SatelliteToken.tsx b/src/layouts/SatelliteToken.tsx index b7e770abe..e128848ea 100644 --- a/src/layouts/SatelliteToken.tsx +++ b/src/layouts/SatelliteToken.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useContext, useEffect, useState } from 'react'; import axios from 'axios'; import { Header } from '../components/Header/Header'; import { Button } from '@patternfly/react-core/dist/dynamic/components/Button'; @@ -8,11 +8,14 @@ import { List, ListComponent, ListItem, OrderType } from '@patternfly/react-core import { Masthead } from '@patternfly/react-core/dist/dynamic/components/Masthead'; import { Page, PageSection } from '@patternfly/react-core/dist/dynamic/components/Page'; import SatelliteTable from '../components/Satellite/SatelliteTable'; +import IPWhitelistTable from '../components/Satellite/IPWhitelistTable'; import { getEnv } from '../utils/common'; +import ChromeAuthContext from '../auth/ChromeAuthContext'; const SatelliteToken: React.FC = () => { const [token, setToken] = useState(''); const [error, setError] = useState(null); + const { user } = useContext(ChromeAuthContext); const generateToken = () => { axios @@ -87,6 +90,16 @@ const SatelliteToken: React.FC = () => { + {user.identity.user?.is_org_admin ? ( + + + IP Address Allow List + + + + + + ) : null} ); From d36070860b9884d87d4293219586f4acd223cb42 Mon Sep 17 00:00:00 2001 From: Martin Marosi Date: Mon, 27 May 2024 14:07:52 +0200 Subject: [PATCH 002/151] Identify all outgoing requests with extra frontend header. --- src/utils/iqeEnablement.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/utils/iqeEnablement.ts b/src/utils/iqeEnablement.ts index 90c8ce146..327c10d15 100644 --- a/src/utils/iqeEnablement.ts +++ b/src/utils/iqeEnablement.ts @@ -11,6 +11,8 @@ import type { AuthContextProps } from 'react-oidc-context'; let xhrResults: XMLHttpRequest[] = []; let fetchResults: Record = {}; +// this extra header helps with API metrics +const FE_ORIGIN_HEADER_NAME = 'x-rh-frontend-origin'; const DENIED_CROSS_CHECK = 'Access denied from RBAC on cross-access check'; const AUTH_ALLOWED_ORIGINS = [ location.origin, @@ -104,6 +106,8 @@ export function init(store: Store, authRef: React.MutableRefObject Date: Thu, 30 May 2024 12:03:38 +0200 Subject: [PATCH 003/151] Make year in footers update automatically --- src/components/Footer/Footer.tsx | 4 +++- src/components/Stratosphere/Footer.tsx | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/Footer/Footer.tsx b/src/components/Footer/Footer.tsx index d800a2cb2..cebef1a99 100644 --- a/src/components/Footer/Footer.tsx +++ b/src/components/Footer/Footer.tsx @@ -9,6 +9,8 @@ import RedHatIcon from '@patternfly/react-icons/dist/dynamic/icons/redhat-icon'; import './Footer.scss'; +const currentYear = new Date().getFullYear(); + export type FooterProps = { setCookieElement: Dispatch>; cookieElement: Element | null; @@ -39,7 +41,7 @@ const Footer = ({ setCookieElement, cookieElement }: FooterProps) => { - ©2023 Red Hat, Inc. + ©{currentYear} Red Hat, Inc. diff --git a/src/components/Stratosphere/Footer.tsx b/src/components/Stratosphere/Footer.tsx index a2acc0a4f..a7083ae5f 100644 --- a/src/components/Stratosphere/Footer.tsx +++ b/src/components/Stratosphere/Footer.tsx @@ -6,6 +6,8 @@ import React, { VoidFunctionComponent } from 'react'; import './footer.scss'; +const currentYear = new Date().getFullYear(); + const FooterLink: VoidFunctionComponent<{ href: string; label: React.ReactNode }> = ({ href, label }) => ( @@ -24,7 +26,7 @@ const Footer = () => ( - Copyright c 2023 Red Hat, Inc. + Copyright c {currentYear} Red Hat, Inc. From b38656f323e10fcff9533e6100d1ca0324c25909 Mon Sep 17 00:00:00 2001 From: Martin Marosi Date: Thu, 30 May 2024 12:44:59 +0200 Subject: [PATCH 004/151] Inject UI header only in hcc scope. --- src/utils/iqeEnablement.ts | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/utils/iqeEnablement.ts b/src/utils/iqeEnablement.ts index 8076597fb..e17f5acd1 100644 --- a/src/utils/iqeEnablement.ts +++ b/src/utils/iqeEnablement.ts @@ -26,6 +26,20 @@ const isExcluded = (target: string) => { return AUTH_EXCLUDED_URLS.some((regex) => regex.test(target)); }; +const shouldInjectUIHeader = (path: URL | Request | string = '') => { + if (path instanceof URL) { + // the type URL has a different match function than the cases above + return location.origin === path.origin && !isExcluded(path.href); + } else if (path instanceof Request) { + const isOriginAllowed = location.origin === path.url; + return isOriginAllowed && !isExcluded(path.url); + } else if (typeof path === 'string') { + return location.origin === path || !path.startsWith('http'); + } + + return true; +}; + const verifyTarget = (originMatch: string, urlMatch: string) => { const isOriginAllowed = AUTH_ALLOWED_ORIGINS.some((origin) => { if (typeof origin === 'string') { @@ -106,7 +120,8 @@ export function init(chromeStore: ReturnType, authRef: React // Send Auth header, it will be changed to Authorization later down the line this.setRequestHeader('Auth', `Bearer ${authRef.current.user?.access_token}`); } - + } + if (shouldInjectUIHeader((this as XMLHttpRequest & { _url: string })._url)) { this.setRequestHeader(FE_ORIGIN_HEADER_NAME, 'hcc'); } // eslint-disable-line func-names @@ -140,7 +155,7 @@ export function init(chromeStore: ReturnType, authRef: React request.headers.append('Authorization', `Bearer ${authRef.current.user?.access_token}`); } - if (!request.headers.has(FE_ORIGIN_HEADER_NAME)) { + if (shouldInjectUIHeader(request) && !request.headers.has(FE_ORIGIN_HEADER_NAME)) { request.headers.append(FE_ORIGIN_HEADER_NAME, 'hcc'); } From 5f22f17bb144763efeec2f7049db249ca1cf9e94 Mon Sep 17 00:00:00 2001 From: Martin Marosi Date: Fri, 31 May 2024 14:56:30 +0200 Subject: [PATCH 005/151] Enable scoped UI id header to same origin. --- src/utils/iqeEnablement.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/utils/iqeEnablement.ts b/src/utils/iqeEnablement.ts index e17f5acd1..0368b4dae 100644 --- a/src/utils/iqeEnablement.ts +++ b/src/utils/iqeEnablement.ts @@ -31,13 +31,13 @@ const shouldInjectUIHeader = (path: URL | Request | string = '') => { // the type URL has a different match function than the cases above return location.origin === path.origin && !isExcluded(path.href); } else if (path instanceof Request) { - const isOriginAllowed = location.origin === path.url; + const isOriginAllowed = path.url.startsWith(location.origin); return isOriginAllowed && !isExcluded(path.url); } else if (typeof path === 'string') { - return location.origin === path || !path.startsWith('http'); + return path.startsWith(location.origin) || path.startsWith('/api'); } - return true; + return false; }; const verifyTarget = (originMatch: string, urlMatch: string) => { From a46d337766a984d35ce14b2dc83fbf4b5b1e2385 Mon Sep 17 00:00:00 2001 From: Martin Marosi Date: Mon, 3 Jun 2024 11:52:50 +0200 Subject: [PATCH 006/151] Add token refresh test --- cypress/e2e/release-gate/refresh-token.cy.ts | 23 ++++++++++++++++++++ src/auth/ChromeAuthContext.ts | 2 ++ src/auth/OIDCConnector/OIDCSecured.tsx | 2 ++ src/chrome/create-chrome.test.ts | 3 +++ src/chrome/create-chrome.ts | 2 ++ 5 files changed, 32 insertions(+) create mode 100644 cypress/e2e/release-gate/refresh-token.cy.ts diff --git a/cypress/e2e/release-gate/refresh-token.cy.ts b/cypress/e2e/release-gate/refresh-token.cy.ts new file mode 100644 index 000000000..44c7882da --- /dev/null +++ b/cypress/e2e/release-gate/refresh-token.cy.ts @@ -0,0 +1,23 @@ +// Landing page has changed +describe('Auth', () => { + it('should force refresh token', () => { + cy.login(); + cy.intercept('POST', 'https://sso.stage.redhat.com/auth/realms/redhat-external/protocol/openid-connect/token').as('tokenRefresh'); + cy.visit('/'); + // initial token request + cy.wait('@tokenRefresh'); + + // wait for chrome to init + cy.contains('Services').should('be.visible'); + // intercept it after initial load + // force token refresh + cy.wait(1000); + cy.window().then((win) => { + win.insights.chrome.$internal.forceAuthRefresh(); + }); + + cy.wait('@tokenRefresh').then((interception) => { + expect(interception.response?.statusCode).to.eq(200); + }); + }); +}); diff --git a/src/auth/ChromeAuthContext.ts b/src/auth/ChromeAuthContext.ts index b97d46a70..14c8b150a 100644 --- a/src/auth/ChromeAuthContext.ts +++ b/src/auth/ChromeAuthContext.ts @@ -20,6 +20,7 @@ export type ChromeAuthContextValue = { getOfflineToken: () => Promise>; doOffline: () => Promise; reAuthWithScopes: (...scopes: string[]) => Promise; + forceRefresh: () => Promise; }; const blankUser: ChromeUser = { @@ -49,6 +50,7 @@ const ChromeAuthContext = createContext({ tokenExpires: 0, user: blankUser, reAuthWithScopes: () => Promise.resolve(), + forceRefresh: () => Promise.resolve(), }); export default ChromeAuthContext; diff --git a/src/auth/OIDCConnector/OIDCSecured.tsx b/src/auth/OIDCConnector/OIDCSecured.tsx index e3f73aa31..6bee06fa3 100644 --- a/src/auth/OIDCConnector/OIDCSecured.tsx +++ b/src/auth/OIDCConnector/OIDCSecured.tsx @@ -114,6 +114,7 @@ export function OIDCSecured({ encodeURIComponent(redirectUri.toString().split('#')[0]) ); }, + forceRefresh: () => Promise.resolve(), doOffline: () => login(authRef.current, ['offline_access'], prepareOfflineRedirect()), getUser: () => Promise.resolve(mapOIDCUserToChromeUser(authRef.current.user ?? {}, {})), token: authRef.current.user?.access_token ?? '', @@ -162,6 +163,7 @@ export function OIDCSecured({ user: chromeUser, token: user.access_token, tokenExpires: user.expires_at!, + forceRefresh: authRef.current.signinSilent, })); sentry(chromeUser); } diff --git a/src/chrome/create-chrome.test.ts b/src/chrome/create-chrome.test.ts index 64dabbba8..82b7fee65 100644 --- a/src/chrome/create-chrome.test.ts +++ b/src/chrome/create-chrome.test.ts @@ -87,6 +87,9 @@ describe('create chrome', () => { token: 'string', tokenExpires: 0, user: mockUser, + forceRefresh() { + return Promise.resolve(); + }, }; const chromeContextOptionsMock = { diff --git a/src/chrome/create-chrome.ts b/src/chrome/create-chrome.ts index ccc6ea96a..00cddad92 100644 --- a/src/chrome/create-chrome.ts +++ b/src/chrome/create-chrome.ts @@ -195,6 +195,8 @@ export const createChromeContext = ({ }, $internal: { store, + // Not supposed to be used by tenants + forceAuthRefresh: chromeAuth.forceRefresh, }, enablePackagesDebug: () => warnDuplicatePkg(), requestPdf, From c725cfa855a1c55dd1851b7e824df4b9f388256c Mon Sep 17 00:00:00 2001 From: Ryan Long Date: Wed, 5 Jun 2024 08:53:51 -0400 Subject: [PATCH 007/151] Update VirtualAssistant.tsx --- src/components/Routes/VirtualAssistant.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Routes/VirtualAssistant.tsx b/src/components/Routes/VirtualAssistant.tsx index ca2d14d70..5dc41c53e 100644 --- a/src/components/Routes/VirtualAssistant.tsx +++ b/src/components/Routes/VirtualAssistant.tsx @@ -4,7 +4,7 @@ import { ScalprumComponent } from '@scalprum/react-core'; import './virtual-assistant.scss'; -const viableRoutes = ['/', '/insights/*', '/settings/*', '/subscriptions/overview/*', '/subscriptions/inventory/*', '/subscriptions/usage/*']; +const viableRoutes = ['/', '/insights/*', '/settings/*', '/subscriptions/overview/*', '/subscriptions/inventory/*', '/subscriptions/usage/*', '/openshift/insights/*']; const VirtualAssistant = () => { return ( From 99ef0aa0f670e68d9102f9a2efe7a4820aecfbf6 Mon Sep 17 00:00:00 2001 From: Ryan Long Date: Wed, 5 Jun 2024 08:58:49 -0400 Subject: [PATCH 008/151] lint --- src/components/Routes/VirtualAssistant.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/components/Routes/VirtualAssistant.tsx b/src/components/Routes/VirtualAssistant.tsx index 5dc41c53e..beca6ddb7 100644 --- a/src/components/Routes/VirtualAssistant.tsx +++ b/src/components/Routes/VirtualAssistant.tsx @@ -4,7 +4,15 @@ import { ScalprumComponent } from '@scalprum/react-core'; import './virtual-assistant.scss'; -const viableRoutes = ['/', '/insights/*', '/settings/*', '/subscriptions/overview/*', '/subscriptions/inventory/*', '/subscriptions/usage/*', '/openshift/insights/*']; +const viableRoutes = [ + '/', + '/insights/*', + '/settings/*', + '/subscriptions/overview/*', + '/subscriptions/inventory/*', + '/subscriptions/usage/*', + '/openshift/insights/*', +]; const VirtualAssistant = () => { return ( From 97922be439bd8b4c390e9223d0217acf67e823a8 Mon Sep 17 00:00:00 2001 From: ewinchel Date: Mon, 10 Jun 2024 10:57:56 -0400 Subject: [PATCH 009/151] Fix drawer background color --- src/layouts/DefaultLayout.scss | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/layouts/DefaultLayout.scss b/src/layouts/DefaultLayout.scss index a91fa2437..f4826d7ce 100644 --- a/src/layouts/DefaultLayout.scss +++ b/src/layouts/DefaultLayout.scss @@ -31,3 +31,10 @@ display: inherit; flex-direction: inherit; } + +.pf-v5-c-page__drawer { + .pf-v5-c-drawer__content { + background-color: transparent; + } +} + From 19729f0e078732e3354dc55a2564581356aba615 Mon Sep 17 00:00:00 2001 From: InsaneZein Date: Tue, 11 Jun 2024 02:40:50 -0500 Subject: [PATCH 010/151] add RHEL, Ansible, and OpenShift to services dropdown (#2855) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add RHEL, Ansible, and OpenShift to services dropdown * styling updates * remove unused imports; increase height * fix dom nesting warning; fix imports * change import to use /dynamic * fix cypress component test * fix navigation cypress test * remove filters from platform urls * test for view all text --------- Co-authored-by: epwinchell <1287144+epwinchell@users.noreply.github.com> Co-authored-by: ewinchel Co-authored-by: Martin Maroši --- .../AllServicesDropdown.cy.tsx | 4 +- cypress/e2e/release-gate/navigation.cy.ts | 2 +- .../AllServicesDropdown.scss | 6 +-- .../AllServicesDropdown/AllServicesMenu.tsx | 11 ---- .../AllServicesDropdown/AllServicesTabs.scss | 3 ++ .../AllServicesDropdown/AllServicesTabs.tsx | 50 +++++++++++++------ .../PlatformServicesLinks.tsx | 19 +++++++ 7 files changed, 62 insertions(+), 33 deletions(-) create mode 100644 src/components/AllServicesDropdown/AllServicesTabs.scss create mode 100644 src/components/AllServicesDropdown/PlatformServicesLinks.tsx diff --git a/cypress/component/AllServicesDropdown/AllServicesDropdown.cy.tsx b/cypress/component/AllServicesDropdown/AllServicesDropdown.cy.tsx index f68fb6004..e33fec960 100644 --- a/cypress/component/AllServicesDropdown/AllServicesDropdown.cy.tsx +++ b/cypress/component/AllServicesDropdown/AllServicesDropdown.cy.tsx @@ -23,11 +23,11 @@ describe('', () => { it('should close all services dropdown in link matches current pathname', () => { function checkMenuClosed() { cy.get('.pf-v5-c-menu-toggle__text').click(); - cy.contains('All services').should('exist'); + cy.contains('View all').should('exist'); cy.contains('Favorites').click(); cy.contains('Test section').click(); cy.contains('Test link').click(); - cy.contains('All services').should('not.exist'); + cy.contains('View all').should('not.exist'); } cy.intercept('http://localhost:8080/api/chrome-service/v1/static/stable/stage/services/services-generated.json', [ { diff --git a/cypress/e2e/release-gate/navigation.cy.ts b/cypress/e2e/release-gate/navigation.cy.ts index 7abcabbd1..b67bd442d 100644 --- a/cypress/e2e/release-gate/navigation.cy.ts +++ b/cypress/e2e/release-gate/navigation.cy.ts @@ -17,7 +17,7 @@ describe('Navigation', () => { cy.contains('.pf-v5-c-tabs__link', 'Favorites'); // click on all services - cy.get('.chr-l-stack__item-browse-all-services a').click(); + cy.get('[data-ouia-component-id="View all link"]').first().click(); // get users link cy.get('p:contains("Users")').click(); diff --git a/src/components/AllServicesDropdown/AllServicesDropdown.scss b/src/components/AllServicesDropdown/AllServicesDropdown.scss index 3c1ee7fa2..84561dc0e 100644 --- a/src/components/AllServicesDropdown/AllServicesDropdown.scss +++ b/src/components/AllServicesDropdown/AllServicesDropdown.scss @@ -63,14 +63,14 @@ --pf-v5-c-sidebar__panel--md--FlexBasis: 20rem; --pf-v5-c-sidebar__panel--BackgroundColor: var(--pf-v5-global--BackgroundColor--200); &__main { - height: 630px; + height: 770px; } &__content { - height: 630px; + height: 770px; overflow: auto; } &__panel { - height: 630px; + height: 770px; box-shadow: inset -4px 0 4px -4px rgba(0, 0, 0, 0.16); .pf-v5-c-tabs { --pf-v5-c-tabs__item--m-current__link--after--BorderColor: transparent; diff --git a/src/components/AllServicesDropdown/AllServicesMenu.tsx b/src/components/AllServicesDropdown/AllServicesMenu.tsx index a9b0e9edf..1f7d3026e 100644 --- a/src/components/AllServicesDropdown/AllServicesMenu.tsx +++ b/src/components/AllServicesDropdown/AllServicesMenu.tsx @@ -6,10 +6,8 @@ import { Stack, StackItem } from '@patternfly/react-core/dist/dynamic/layouts/St import { Panel, PanelMain } from '@patternfly/react-core/dist/dynamic/components/Panel'; import { Sidebar, SidebarContent, SidebarPanel } from '@patternfly/react-core/dist/dynamic/components/Sidebar'; import { TabContent } from '@patternfly/react-core/dist/dynamic/components/Tabs'; -import { Text, TextContent, TextVariants } from '@patternfly/react-core/dist/dynamic/components/Text'; import { Title } from '@patternfly/react-core/dist/dynamic/components/Title'; -import ChromeLink from '../ChromeLink'; import TimesIcon from '@patternfly/react-icons/dist/dynamic/icons/times-icon'; import type { AllServicesSection } from '../AllServices/allServicesLinks'; import FavoriteServicesGallery from '../FavoriteServices/ServicesGallery'; @@ -81,15 +79,6 @@ const AllServicesMenu = ({ setIsOpen, isOpen, menuRef, linkSections, favoritedSe - - - - - - - - - - { - handleTabClick?.(e, FAVORITE_TAB_ID); - }} - eventKey={FAVORITE_TAB_ID} - title={ - - Favorites - - - - - } - /> + + Platform + + + <> + + Solutions{' '} + + View all + + + + { + handleTabClick?.(e, FAVORITE_TAB_ID); + }} + eventKey={FAVORITE_TAB_ID} + title={ + + Favorites + + + + + } + /> {/* The tabs children type is busted and does not accept array. Hence the fragment wrapper */} {linkSections.map((section, index) => ( onTabClick(section, index)} + className="pf-v5-u-pl-sm" /> ))} diff --git a/src/components/AllServicesDropdown/PlatformServicesLinks.tsx b/src/components/AllServicesDropdown/PlatformServicesLinks.tsx new file mode 100644 index 000000000..bfb019116 --- /dev/null +++ b/src/components/AllServicesDropdown/PlatformServicesLinks.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import ChromeLink from '../ChromeLink'; + +const PlatformServiceslinks = () => { + return ( + <> + + Red Hat Ansible Platform + + + Red Hat Enterprise Linux + + + Red Hat OpenShift + + + ); +}; +export default PlatformServiceslinks; From 1731eef65351b3302cac0dbc76b7ae85bb6a5d33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Maro=C5=A1i?= Date: Wed, 12 Jun 2024 12:04:55 +0200 Subject: [PATCH 011/151] Migrate notifications reducer to Jotai (#2857) Co-authored-by: Filip Hlavac <50696716+fhlavac@users.noreply.github.com> --- .../NotificationDrawer.cy.tsx | 127 +++++++++++++ src/components/Header/Tools.tsx | 14 +- .../DrawerPanelContent.test.tsx | 176 ------------------ .../DrawerPanelContent.tsx | 43 +++-- .../NotificationsDrawer/NotificationItem.tsx | 9 +- src/components/RootApp/ScalprumRoot.tsx | 10 +- src/hooks/useChromeServiceEvents.ts | 9 +- src/layouts/DefaultLayout.tsx | 8 +- src/redux/action-types.ts | 9 - src/redux/actions.ts | 31 +-- src/redux/chromeReducers.ts | 81 +------- src/redux/index.ts | 31 --- src/redux/store.d.ts | 27 --- src/state/atoms/notificationDrawerAtom.ts | 37 ++++ 14 files changed, 213 insertions(+), 399 deletions(-) create mode 100644 cypress/component/NotificationDrawer/NotificationDrawer.cy.tsx delete mode 100644 src/components/NotificationsDrawer/DrawerPanelContent.test.tsx create mode 100644 src/state/atoms/notificationDrawerAtom.ts diff --git a/cypress/component/NotificationDrawer/NotificationDrawer.cy.tsx b/cypress/component/NotificationDrawer/NotificationDrawer.cy.tsx new file mode 100644 index 000000000..268c0d9b1 --- /dev/null +++ b/cypress/component/NotificationDrawer/NotificationDrawer.cy.tsx @@ -0,0 +1,127 @@ +import React, { useEffect } from 'react'; +import { BrowserRouter } from 'react-router-dom'; +import DrawerPanel from '../../../src/components/NotificationsDrawer/DrawerPanelContent'; +import { Page } from '@patternfly/react-core'; +import { NotificationData, notificationDrawerDataAtom, notificationDrawerExpandedAtom } from '../../../src/state/atoms/notificationDrawerAtom'; +import { useAtom, useSetAtom } from 'jotai'; + +const notificationDrawerData: NotificationData[] = [ + { + id: '1', + title: 'Notification 1', + read: false, + created: new Date().toString(), + description: 'This is a test notification', + source: 'openshift', + }, + { + id: '2', + title: 'Notification 2', + read: false, + created: new Date().toString(), + description: 'This is a test notification', + source: 'console', + }, + { + id: '3', + title: 'Notification 3', + read: false, + created: new Date().toString(), + description: 'This is a test notification', + source: 'console', + }, +]; + +const DrawerLayout = ({ markAll = false }: { markAll?: boolean }) => { + const [isNotificationDrawerExpanded, setIsNotificationDrawerExpanded] = useAtom(notificationDrawerExpandedAtom); + const setNotifications = useSetAtom(notificationDrawerDataAtom); + useEffect(() => { + return () => { + setNotifications([]); + setIsNotificationDrawerExpanded(false); + }; + }, []); + return ( + + + + }> + + ); +}; + +describe('Notification Drawer', () => { + beforeEach(() => { + cy.viewport(1200, 800); + }); + it('should toggle drawer', () => { + cy.mount(); + cy.get('#drawer-toggle').click(); + cy.contains('No notifications found').should('be.visible'); + cy.get('#drawer-toggle').click(); + cy.contains('No notifications found').should('not.exist'); + }); + + it('should populate notifications', () => { + cy.mount(); + cy.get('#populate-notifications').click(); + cy.get('#drawer-toggle').click(); + notificationDrawerData.forEach((notification) => { + cy.contains(notification.title).should('be.visible'); + }); + }); + + it('should mark single notification as read', () => { + cy.mount(); + cy.get('#populate-notifications').click(); + cy.get('#drawer-toggle').click(); + cy.get('.pf-m-read').should('have.length', 0); + cy.contains('Notification 1').get('input[type="checkbox"]').first().click(); + cy.get('.pf-m-read').should('have.length', 1); + }); + + it('should mark one notification as unread', () => { + cy.mount(); + cy.get('#populate-notifications').click(); + cy.get('#drawer-toggle').click(); + cy.get('.pf-m-read').should('have.length', 3); + cy.contains('Notification 1').get('input[type="checkbox"]').first().click(); + cy.get('.pf-m-read').should('have.length', 2); + }); + + it('should mark all notifications as read', () => { + cy.mount(); + cy.get('#populate-notifications').click(); + cy.get('#drawer-toggle').click(); + cy.get('.pf-m-read').should('have.length', 0); + cy.get('#notifications-actions-toggle').click(); + cy.contains('Mark visible as read').click(); + cy.get('.pf-m-read').should('have.length', 3); + }); + + it('should mark all notifications as not read', () => { + cy.mount(); + cy.get('#populate-notifications').click(); + cy.get('#drawer-toggle').click(); + cy.get('.pf-m-read').should('have.length', 3); + cy.get('#notifications-actions-toggle').click(); + cy.contains('Mark visible as unread').click(); + cy.get('.pf-m-read').should('have.length', 0); + }); + + it('should select console filter', () => { + cy.mount(); + cy.get('#populate-notifications').click(); + cy.get('#drawer-toggle').click(); + cy.get('.pf-v5-c-notification-drawer__list-item').should('have.length', 3); + cy.get('#notifications-filter-toggle').click(); + cy.contains('Console').click(); + cy.get('.pf-v5-c-notification-drawer__list-item').should('have.length', 2); + cy.contains('Reset filter').click(); + cy.get('.pf-v5-c-notification-drawer__list-item').should('have.length', 3); + }); +}); diff --git a/src/components/Header/Tools.tsx b/src/components/Header/Tools.tsx index fd0e52ff1..298d5b667 100644 --- a/src/components/Header/Tools.tsx +++ b/src/components/Header/Tools.tsx @@ -16,20 +16,19 @@ import UserToggle from './UserToggle'; import ToolbarToggle, { ToolbarToggleDropdownItem } from './ToolbarToggle'; import SettingsToggle, { SettingsToggleDropdownGroup } from './SettingsToggle'; import HeaderAlert from './HeaderAlert'; -import { useDispatch, useSelector } from 'react-redux'; import cookie from 'js-cookie'; import { ITLess, getRouterBasename, getSection, isBeta } from '../../utils/common'; import { useIntl } from 'react-intl'; import { useFlag } from '@unleash/proxy-client-react'; import messages from '../../locales/Messages'; import { createSupportCase } from '../../utils/createCase'; -import { ReduxState } from '../../redux/store'; import BellIcon from '@patternfly/react-icons/dist/dynamic/icons/bell-icon'; -import { toggleNotificationsDrawer } from '../../redux/actions'; import useWindowWidth from '../../hooks/useWindowWidth'; import ChromeAuthContext from '../../auth/ChromeAuthContext'; import { isPreviewAtom } from '../../state/atoms/releaseAtom'; import chromeStore from '../../state/chromeStore'; +import { useAtom, useAtomValue } from 'jotai'; +import { notificationDrawerExpandedAtom, unreadNotificationsAtom } from '../../state/atoms/notificationDrawerAtom'; const isITLessEnv = ITLess(); @@ -107,9 +106,8 @@ const Tools = () => { const enableIntegrations = useFlag('platform.sources.integrations'); const { xs } = useWindowWidth(); const { user, token } = useContext(ChromeAuthContext); - const unreadNotifications = useSelector(({ chrome: { notifications } }: ReduxState) => notifications.data.some((item) => !item.read)); - const isDrawerExpanded = useSelector(({ chrome: { notifications } }: ReduxState) => notifications?.isExpanded); - const dispatch = useDispatch(); + const unreadNotifications = useAtomValue(unreadNotificationsAtom); + const [isNotificationDrawerExpanded, toggleNotifications] = useAtom(notificationDrawerExpandedAtom); const intl = useIntl(); const location = useLocation(); const settingsPath = isITLessEnv ? `/settings/my-user-access` : enableIntegrations ? `/settings/integrations` : '/settings/sources'; @@ -313,9 +311,9 @@ const Tools = () => { dispatch(toggleNotificationsDrawer())} + onClick={() => toggleNotifications((prev) => !prev)} aria-label="Notifications" - isExpanded={isDrawerExpanded} + isExpanded={isNotificationDrawerExpanded} > diff --git a/src/components/NotificationsDrawer/DrawerPanelContent.test.tsx b/src/components/NotificationsDrawer/DrawerPanelContent.test.tsx deleted file mode 100644 index 298e38db5..000000000 --- a/src/components/NotificationsDrawer/DrawerPanelContent.test.tsx +++ /dev/null @@ -1,176 +0,0 @@ -import React from 'react'; -import { act, fireEvent, render, waitFor } from '@testing-library/react'; -import { Provider } from 'react-redux'; -import configureMockStore, { MockStore } from 'redux-mock-store'; -import DrawerPanel from './DrawerPanelContent'; -import { BrowserRouter } from 'react-router-dom'; -import { markAllNotificationsAsRead, markAllNotificationsAsUnread, markNotificationAsRead, markNotificationAsUnread } from '../../redux/actions'; -import { readTestData, testData } from './notificationDrawerUtils'; - -const mockStore = configureMockStore(); - -const stateWithNotifications = { - chrome: { - notifications: { - data: testData, - isExpanded: true, - count: 3, - }, - }, -}; - -const stateWithoutNotifications = { - chrome: { - notifications: { - data: [], - isExpanded: true, - count: 0, - }, - }, -}; - -const stateWithReadNotifications = { - chrome: { - notifications: { - data: readTestData, - isExpanded: true, - count: 2, - }, - }, -}; - -const renderComponent = (store: MockStore) => { - return render( - - - - - - - - ); -}; - -describe('Drawer panel functionality', () => { - test('Renders the drawer panel empty successfully. ', () => { - const store = mockStore(stateWithoutNotifications); - - const renderedResult = renderComponent(store); - expect(renderedResult.getByText('Notifications')).toBeInTheDocument(); - }); - - test('Renders notification drawer with notifications successfully', () => { - const store = mockStore(stateWithNotifications); - - const renderedResult = renderComponent(store); - expect(renderedResult.getByText('Test Notification 1')).toBeInTheDocument(); - }); - - test('Marking notification as read successfully', async () => { - const store = mockStore(stateWithNotifications); - - const renderedResult = renderComponent(store); - - const checkbox = renderedResult.getAllByRole('checkbox'); - - await act(async () => { - fireEvent.click(checkbox[0]); - }); - - const actions = store.getActions(); - - await waitFor(() => { - expect(actions).toContainEqual(markNotificationAsRead('1')); - }); - }); - - test('Mark notification as unread successfully', async () => { - const store = mockStore(stateWithReadNotifications); - - const renderedResult = renderComponent(store); - - const checkbox = renderedResult.getAllByRole('checkbox'); - - await act(async () => { - fireEvent.click(checkbox[0]); - }); - - const actions = store.getActions(); - - await waitFor(() => { - expect(actions).toContainEqual(markNotificationAsUnread('1')); - }); - }); - - test('Mark all notifications as read successfully', async () => { - const store = mockStore(stateWithNotifications); - - const renderedResult = renderComponent(store); - - const actionMenuButton = renderedResult.getByRole('button', { name: /Notifications actions dropdown/i }); - - await act(async () => { - fireEvent.click(actionMenuButton); - }); - - const actionDropdownItems = await renderedResult.getAllByRole('menuitem'); - - await act(async () => { - fireEvent.click(actionDropdownItems[1]); - }); - - const actions = store.getActions(); - - await waitFor(() => { - expect(actions).toContainEqual(markAllNotificationsAsRead()); - }); - }); - - test('Mark all notifications as unread successfully', async () => { - const store = mockStore(stateWithReadNotifications); - - const renderedResult = renderComponent(store); - - const actionMenuButton = renderedResult.getByRole('button', { name: /Notifications actions dropdown/i }); - - await act(async () => { - fireEvent.click(actionMenuButton); - }); - - const actionDropdownItems = await renderedResult.getAllByRole('menuitem'); - - act(() => { - fireEvent.click(actionDropdownItems[2]); - }); - - const actions = store.getActions(); - - await waitFor(() => { - expect(actions).toContainEqual(markAllNotificationsAsUnread()); - }); - }); - - test('Select filter successfully', async () => { - const store = mockStore(stateWithNotifications); - - const renderedResult = renderComponent(store); - - const filterMenuButton = renderedResult.getByRole('button', { name: /Notifications filter/i }); - - await act(async () => { - fireEvent.click(filterMenuButton); - }); - - const filterMenuItems = await renderedResult.getAllByRole('menuitem'); - - await act(async () => { - fireEvent.click(filterMenuItems[2]); - }); - - const filteredNotification = await renderedResult.getAllByRole('listitem'); - - await waitFor(() => { - expect(filteredNotification.length === 1); - }); - }); -}); diff --git a/src/components/NotificationsDrawer/DrawerPanelContent.tsx b/src/components/NotificationsDrawer/DrawerPanelContent.tsx index 2d16e900a..21043a5d2 100644 --- a/src/components/NotificationsDrawer/DrawerPanelContent.tsx +++ b/src/components/NotificationsDrawer/DrawerPanelContent.tsx @@ -1,4 +1,5 @@ -import React, { useContext, useEffect, useState } from 'react'; +import React, { useContext, useEffect, useMemo, useState } from 'react'; +import { useAtom, useAtomValue, useSetAtom } from 'jotai'; import { PopoverPosition } from '@patternfly/react-core/dist/dynamic/components/Popover'; import { Badge } from '@patternfly/react-core/dist/dynamic/components/Badge'; import { Flex, FlexItem } from '@patternfly/react-core/dist/dynamic/layouts/Flex'; @@ -14,18 +15,22 @@ import { } from '@patternfly/react-core/dist/dynamic/components/NotificationDrawer'; import { Text } from '@patternfly/react-core/dist/dynamic/components/Text'; import { Title } from '@patternfly/react-core/dist/dynamic/components/Title'; -import { useDispatch, useSelector } from 'react-redux'; import FilterIcon from '@patternfly/react-icons/dist/dynamic/icons/filter-icon'; import BellSlashIcon from '@patternfly/react-icons/dist/dynamic/icons/bell-slash-icon'; import EllipsisVIcon from '@patternfly/react-icons/dist/dynamic/icons/ellipsis-v-icon'; import orderBy from 'lodash/orderBy'; import { Link, useNavigate } from 'react-router-dom'; -import { NotificationData, ReduxState } from '../../redux/store'; import NotificationItem from './NotificationItem'; -import { markAllNotificationsAsRead, markAllNotificationsAsUnread, toggleNotificationsDrawer } from '../../redux/actions'; import { filterConfig } from './notificationDrawerUtils'; import ChromeAuthContext from '../../auth/ChromeAuthContext'; import InternalChromeContext from '../../utils/internalChromeContext'; +import { + NotificationData, + notificationDrawerDataAtom, + notificationDrawerExpandedAtom, + notificationDrawerFilterAtom, + updateNotificationsStatusAtom, +} from '../../state/atoms/notificationDrawerAtom'; export type DrawerPanelProps = { innerRef: React.Ref; @@ -65,11 +70,11 @@ const EmptyNotifications = ({ isOrgAdmin, onLinkClick }: { onLinkClick: () => vo const DrawerPanelBase = ({ innerRef }: DrawerPanelProps) => { const [isDropdownOpen, setIsDropdownOpen] = useState(false); const [isFilterDropdownOpen, setIsFilterDropdownOpen] = useState(false); - const [activeFilters, setActiveFilters] = useState([]); - const [filteredNotifications, setFilteredNotifications] = useState([]); + const [activeFilters, setActiveFilters] = useAtom(notificationDrawerFilterAtom); + const toggleDrawer = useSetAtom(notificationDrawerExpandedAtom); const navigate = useNavigate(); - const dispatch = useDispatch(); - const notifications = useSelector(({ chrome: { notifications } }: ReduxState) => notifications?.data || []); + const notifications = useAtomValue(notificationDrawerDataAtom); + const updateNotificationsStatus = useSetAtom(updateNotificationsStatusAtom); const auth = useContext(ChromeAuthContext); const isOrgAdmin = auth?.user?.identity?.user?.is_org_admin; const { getUserPermissions } = useContext(InternalChromeContext); @@ -95,27 +100,27 @@ const DrawerPanelBase = ({ innerRef }: DrawerPanelProps) => { }; }, []); - useEffect(() => { - const modifiedNotifications = (activeFilters || []).reduce( - (acc: NotificationData[], chosenFilter: string) => [...acc, ...notifications.filter(({ source }) => source === chosenFilter)], - [] - ); - - setFilteredNotifications(modifiedNotifications); - }, [activeFilters]); + const filteredNotifications = useMemo( + () => + (activeFilters || []).reduce( + (acc: NotificationData[], chosenFilter: string) => [...acc, ...notifications.filter(({ source }) => source === chosenFilter)], + [] + ), + [activeFilters] + ); const onNotificationsDrawerClose = () => { setActiveFilters([]); - dispatch(toggleNotificationsDrawer()); + toggleDrawer(false); }; const onMarkAllAsRead = () => { - dispatch(markAllNotificationsAsRead()); + updateNotificationsStatus(true); setIsDropdownOpen(false); }; const onMarkAllAsUnread = () => { - dispatch(markAllNotificationsAsUnread()); + updateNotificationsStatus(false); setIsDropdownOpen(false); }; diff --git a/src/components/NotificationsDrawer/NotificationItem.tsx b/src/components/NotificationsDrawer/NotificationItem.tsx index 3ad2edb4c..a845526e5 100644 --- a/src/components/NotificationsDrawer/NotificationItem.tsx +++ b/src/components/NotificationsDrawer/NotificationItem.tsx @@ -1,4 +1,5 @@ import React, { useState } from 'react'; +import { useSetAtom } from 'jotai'; import { NotificationDrawerList, NotificationDrawerListItem, @@ -12,9 +13,7 @@ import { MenuToggle, MenuToggleElement } from '@patternfly/react-core/dist/dynam import { Dropdown, DropdownItem, DropdownList } from '@patternfly/react-core/dist/dynamic/components/Dropdown'; import EllipsisVIcon from '@patternfly/react-icons/dist/dynamic/icons/ellipsis-v-icon'; import DateFormat from '@redhat-cloud-services/frontend-components/DateFormat'; -import { useDispatch } from 'react-redux'; -import { NotificationData } from '../../redux/store'; -import { markNotificationAsRead, markNotificationAsUnread } from '../../redux/actions'; +import { NotificationData, updateNotificationReadAtom } from '../../state/atoms/notificationDrawerAtom'; interface NotificationItemProps { notification: NotificationData; @@ -22,10 +21,10 @@ interface NotificationItemProps { } const NotificationItem: React.FC = ({ notification, onNavigateTo }) => { const [isDropdownOpen, setIsDropdownOpen] = useState(false); - const dispatch = useDispatch(); + const updateNotificationRead = useSetAtom(updateNotificationReadAtom); const onCheckboxToggle = () => { - dispatch(!notification.read ? markNotificationAsRead(notification.id) : markNotificationAsUnread(notification.id)); + updateNotificationRead(notification.id, !notification.read); setIsDropdownOpen(false); }; diff --git a/src/components/RootApp/ScalprumRoot.tsx b/src/components/RootApp/ScalprumRoot.tsx index 3ded2a6e1..e2bfd2562 100644 --- a/src/components/RootApp/ScalprumRoot.tsx +++ b/src/components/RootApp/ScalprumRoot.tsx @@ -1,7 +1,7 @@ import React, { Suspense, lazy, memo, useCallback, useContext, useEffect, useMemo, useRef } from 'react'; import axios from 'axios'; import { ScalprumProvider, ScalprumProviderProps } from '@scalprum/react-core'; -import { shallowEqual, useDispatch, useSelector, useStore } from 'react-redux'; +import { shallowEqual, useSelector, useStore } from 'react-redux'; import { Route, Routes } from 'react-router-dom'; import { HelpTopic, HelpTopicContext } from '@patternfly/quickstarts'; import isEqual from 'lodash/isEqual'; @@ -30,11 +30,11 @@ import chromeApiWrapper from './chromeApiWrapper'; import { ITLess, isBeta } from '../../utils/common'; import InternalChromeContext from '../../utils/internalChromeContext'; import useChromeServiceEvents from '../../hooks/useChromeServiceEvents'; -import { populateNotifications } from '../../redux/actions'; import useTrackPendoUsage from '../../hooks/useTrackPendoUsage'; import ChromeAuthContext from '../../auth/ChromeAuthContext'; import { onRegisterModuleWriteAtom } from '../../state/atoms/chromeModuleAtom'; import useTabName from '../../hooks/useTabName'; +import { NotificationData, notificationDrawerDataAtom } from '../../state/atoms/notificationDrawerAtom'; const ProductSelection = lazy(() => import('../Stratosphere/ProductSelection')); @@ -52,11 +52,11 @@ export type ScalprumRootProps = FooterProps & { const ScalprumRoot = memo( ({ config, helpTopicsAPI, quickstartsAPI, cookieElement, setCookieElement, ...props }: ScalprumRootProps) => { const { setFilteredHelpTopics } = useContext(HelpTopicContext); - const dispatch = useDispatch(); const internalFilteredTopics = useRef([]); const { analytics } = useContext(SegmentContext); const chromeAuth = useContext(ChromeAuthContext); const registerModule = useSetAtom(onRegisterModuleWriteAtom); + const populateNotifications = useSetAtom(notificationDrawerDataAtom); const store = useStore(); const mutableChromeApi = useRef(); @@ -70,8 +70,8 @@ const ScalprumRoot = memo( async function getNotifications() { try { - const notifications = await axios.get('/api/notifications/v1/notifications/drawer'); - dispatch(populateNotifications(notifications.data?.data || [])); + const { data } = await axios.get<{ data: NotificationData[] }>('/api/notifications/v1/notifications/drawer'); + populateNotifications(data?.data || []); } catch (error) { console.error('Unable to get Notifications ', error); } diff --git a/src/hooks/useChromeServiceEvents.ts b/src/hooks/useChromeServiceEvents.ts index 88e0fd6a8..8686edee8 100644 --- a/src/hooks/useChromeServiceEvents.ts +++ b/src/hooks/useChromeServiceEvents.ts @@ -1,10 +1,9 @@ import { useContext, useEffect, useMemo, useRef } from 'react'; -import { useDispatch } from 'react-redux'; import { useFlag } from '@unleash/proxy-client-react'; -import { UPDATE_NOTIFICATIONS } from '../redux/action-types'; -import { NotificationsPayload } from '../redux/store'; import { setCookie } from '../auth/setCookie'; import ChromeAuthContext from '../auth/ChromeAuthContext'; +import { useSetAtom } from 'jotai'; +import { NotificationData, NotificationsPayload, addNotificationAtom } from '../state/atoms/notificationDrawerAtom'; const NOTIFICATION_DRAWER = 'com.redhat.console.notifications.drawer'; const SAMPLE_EVENT = 'sample.type'; @@ -28,14 +27,14 @@ function isGenericEvent(event: unknown): event is GenericEvent { const useChromeServiceEvents = () => { const connection = useRef(); - const dispatch = useDispatch(); + const addNotification = useSetAtom(addNotificationAtom); const isNotificationsEnabled = useFlag('platform.chrome.notifications-drawer'); const { token, tokenExpires } = useContext(ChromeAuthContext); const handlerMap: { [key in EventTypes]: (payload: GenericEvent) => void } = useMemo( () => ({ [NOTIFICATION_DRAWER]: (data: GenericEvent) => { - dispatch({ type: UPDATE_NOTIFICATIONS, payload: data }); + addNotification(data.data as unknown as NotificationData); }, [SAMPLE_EVENT]: (data: GenericEvent) => console.log('Received sample payload', data), }), diff --git a/src/layouts/DefaultLayout.tsx b/src/layouts/DefaultLayout.tsx index cec77bcf2..4f3dfce01 100644 --- a/src/layouts/DefaultLayout.tsx +++ b/src/layouts/DefaultLayout.tsx @@ -1,6 +1,6 @@ import React, { memo, useContext, useEffect, useRef, useState } from 'react'; +import { useAtomValue } from 'jotai'; import classnames from 'classnames'; -import { useSelector } from 'react-redux'; import GlobalFilter from '../components/GlobalFilter/GlobalFilter'; import { useScalprum } from '@scalprum/react-core'; import { Masthead } from '@patternfly/react-core/dist/dynamic/components/Masthead'; @@ -22,13 +22,13 @@ import DrawerPanel from '../components/NotificationsDrawer/DrawerPanelContent'; import '../components/Navigation/Navigation.scss'; import './DefaultLayout.scss'; -import { ReduxState } from '../redux/store'; import useNavigation from '../utils/useNavigation'; import { NavigationProps } from '../components/Navigation'; import { getUrl } from '../hooks/useBundle'; import { useFlag } from '@unleash/proxy-client-react'; import ChromeAuthContext from '../auth/ChromeAuthContext'; import VirtualAssistant from '../components/Routes/VirtualAssistant'; +import { notificationDrawerExpandedAtom } from '../state/atoms/notificationDrawerAtom'; type ShieldedRootProps = { hideNav?: boolean; @@ -50,7 +50,7 @@ type DefaultLayoutProps = { const DefaultLayout: React.FC = ({ hasBanner, selectedAccountNumber, hideNav, isNavOpen, setIsNavOpen, Sidebar, Footer }) => { const intl = useIntl(); const { loaded, schema, noNav } = useNavigation(); - const isDrawerExpanded = useSelector(({ chrome: { notifications } }: ReduxState) => notifications?.isExpanded); + const isNotificationsDrawerExpanded = useAtomValue(notificationDrawerExpandedAtom); const drawerPanelRef = useRef(); const focusDrawer = () => { const tabbableElement = drawerPanelRef.current?.querySelector('a, button') as HTMLAnchorElement | HTMLButtonElement; @@ -78,7 +78,7 @@ const DefaultLayout: React.FC = ({ hasBanner, selectedAccoun {...(isNotificationsEnabled && { onNotificationDrawerExpand: focusDrawer, notificationDrawer: , - isNotificationDrawerExpanded: isDrawerExpanded, + isNotificationDrawerExpanded: isNotificationsDrawerExpanded, })} sidebar={ (noNav || hideNav) && Sidebar diff --git a/src/redux/action-types.ts b/src/redux/action-types.ts index 440e37789..82ac8d342 100644 --- a/src/redux/action-types.ts +++ b/src/redux/action-types.ts @@ -31,12 +31,3 @@ export const POPULATE_QUICKSTARTS_CATALOG = '@@chrome/populate-quickstarts-catal export const ADD_QUICKSTARTS_TO_APP = '@@chrome/add-quickstart'; export const DISABLE_QUICKSTARTS = '@@chrome/disable-quickstarts'; export const CLEAR_QUICKSTARTS = '@@chrome/clear-quickstarts'; - -export const TOGGLE_NOTIFICATIONS_DRAWER = '@@chrome/toggle-notifications-drawer'; -export const POPULATE_NOTIFICATIONS = '@@chrome/populate-notifications'; - -export const MARK_NOTIFICATION_AS_READ = '@@chrome/mark-notification-as-read'; -export const MARK_NOTIFICATION_AS_UNREAD = '@@chrome/mark-notification-as-unread'; -export const MARK_ALL_NOTIFICATION_AS_READ = '@@chrome/mark-all-notification-as-read'; -export const MARK_ALL_NOTIFICATION_AS_UNREAD = '@@chrome/mark-all-notification-as-unread'; -export const UPDATE_NOTIFICATIONS = '@@chrome/update-notifications'; diff --git a/src/redux/actions.ts b/src/redux/actions.ts index d3091a723..2bd73daf1 100644 --- a/src/redux/actions.ts +++ b/src/redux/actions.ts @@ -3,7 +3,7 @@ import { getAllSIDs, getAllTags, getAllWorkloads } from '../components/GlobalFil import type { TagFilterOptions, TagPagination } from '../components/GlobalFilter/tagsApi'; import type { ChromeUser } from '@redhat-cloud-services/types'; import type { FlagTagsFilter, NavDOMEvent, NavItem, Navigation } from '../@types/types'; -import type { AccessRequest, NotificationData, NotificationsPayload } from './store'; +import type { AccessRequest } from './store'; import type { QuickStart } from '@patternfly/quickstarts'; export function userLogIn(user: ChromeUser | boolean) { @@ -167,32 +167,3 @@ export const markActiveProduct = (product?: string) => ({ type: actionTypes.MARK_ACTIVE_PRODUCT, payload: product, }); - -export const toggleNotificationsDrawer = () => ({ - type: actionTypes.TOGGLE_NOTIFICATIONS_DRAWER, -}); - -export const populateNotifications = (data: NotificationData[]) => ({ type: actionTypes.POPULATE_NOTIFICATIONS, payload: { data } }); - -export const markNotificationAsRead = (id: string) => ({ - type: actionTypes.MARK_NOTIFICATION_AS_READ, - payload: id, -}); - -export const markNotificationAsUnread = (id: string) => ({ - type: actionTypes.MARK_NOTIFICATION_AS_UNREAD, - payload: id, -}); - -export const markAllNotificationsAsRead = () => ({ - type: actionTypes.MARK_ALL_NOTIFICATION_AS_READ, -}); - -export const markAllNotificationsAsUnread = () => ({ - type: actionTypes.MARK_ALL_NOTIFICATION_AS_UNREAD, -}); - -export const updateNotifications = (payload: NotificationsPayload) => ({ - type: actionTypes.UPDATE_NOTIFICATIONS, - payload, -}); diff --git a/src/redux/chromeReducers.ts b/src/redux/chromeReducers.ts index 595fc7735..0b854bc6c 100644 --- a/src/redux/chromeReducers.ts +++ b/src/redux/chromeReducers.ts @@ -3,7 +3,7 @@ import { ChromeUser } from '@redhat-cloud-services/types'; import { REQUESTS_COUNT, REQUESTS_DATA } from '../utils/consts'; import { NavItem, Navigation } from '../@types/types'; import { ITLess, highlightItems, levelArray } from '../utils/common'; -import { AccessRequest, ChromeState, NotificationData, NotificationsPayload } from './store'; +import { AccessRequest, ChromeState } from './store'; export function appNavClick(state: ChromeState, { payload }: { payload: { id: string } }): ChromeState { return { @@ -242,82 +242,3 @@ export function markActiveProduct(state: ChromeState, { payload }: { payload?: s activeProduct: payload, }; } - -export function toggleNotificationsReducer(state: ChromeState) { - return { - ...state, - notifications: { - ...state.notifications, - data: state.notifications?.data || [], - isExpanded: !state.notifications?.isExpanded, - }, - }; -} - -export function populateNotificationsReducer(state: ChromeState, { payload: { data } }: { payload: { data: NotificationData[] } }) { - return { - ...state, - notifications: { - ...state.notifications, - data, - }, - }; -} - -export function markNotificationAsRead(state: ChromeState, { payload }: { payload: string }): ChromeState { - return { - ...state, - notifications: { - isExpanded: state.notifications?.isExpanded || false, - count: state.notifications?.data?.length || 0, - data: (state.notifications?.data || []).map((notification: NotificationData) => - notification.id === payload ? { ...notification, read: true } : notification - ), - }, - }; -} - -export function markNotificationAsUnread(state: ChromeState, { payload }: { payload: string }): ChromeState { - return { - ...state, - notifications: { - isExpanded: state.notifications?.isExpanded || false, - count: state.notifications?.data?.length || 0, - data: (state.notifications?.data || []).map((notification: NotificationData) => - notification.id === payload ? { ...notification, read: false } : notification - ), - }, - }; -} - -export function markAllNotificationsAsRead(state: ChromeState): ChromeState { - return { - ...state, - notifications: { - isExpanded: state.notifications?.isExpanded || false, - count: state.notifications?.count || 0, - data: (state.notifications?.data || []).map((notification) => ({ ...notification, read: true })), - }, - }; -} - -export function markAllNotificationsAsUnread(state: ChromeState): ChromeState { - return { - ...state, - notifications: { - isExpanded: state.notifications?.isExpanded || false, - count: state.notifications?.data?.length || 0, - data: (state.notifications?.data || []).map((notification) => ({ ...notification, read: false })), - }, - }; -} - -export function updateNotificationsReducer(state: ChromeState, { payload }: { payload: NotificationsPayload }) { - return { - ...state, - notifications: { - ...state.notifications, - data: [...state.notifications.data, payload.data], - }, - }; -} diff --git a/src/redux/index.ts b/src/redux/index.ts index 2a8478aef..62e688267 100644 --- a/src/redux/index.ts +++ b/src/redux/index.ts @@ -12,20 +12,13 @@ import { loginReducer, markAccessRequestRequestReducer, markActiveProduct, - markAllNotificationsAsRead, - markAllNotificationsAsUnread, - markNotificationAsRead, - markNotificationAsUnread, onPageAction, onPageObjectId, - populateNotificationsReducer, populateQuickstartsReducer, setPendoFeedbackFlag, toggleDebuggerButton, toggleDebuggerModal, toggleFeedbackModal, - toggleNotificationsReducer, - updateNotificationsReducer, } from './chromeReducers'; import { globalFilterDefaultState, @@ -57,21 +50,14 @@ import { LOAD_LEFT_NAVIGATION_SEGMENT, LOAD_NAVIGATION_LANDING_PAGE, MARK_ACTIVE_PRODUCT, - MARK_ALL_NOTIFICATION_AS_READ, - MARK_ALL_NOTIFICATION_AS_UNREAD, - MARK_NOTIFICATION_AS_READ, - MARK_NOTIFICATION_AS_UNREAD, MARK_REQUEST_NOTIFICATION_SEEN, - POPULATE_NOTIFICATIONS, POPULATE_QUICKSTARTS_CATALOG, SET_PENDO_FEEDBACK_FLAG, TOGGLE_DEBUGGER_BUTTON, TOGGLE_DEBUGGER_MODAL, TOGGLE_FEEDBACK_MODAL, - TOGGLE_NOTIFICATIONS_DRAWER, UPDATE_ACCESS_REQUESTS_NOTIFICATIONS, UPDATE_DOCUMENT_TITLE_REDUCER, - UPDATE_NOTIFICATIONS, USER_LOGIN, } from './action-types'; import { ChromeState, GlobalFilterState, ReduxState } from './store'; @@ -96,13 +82,6 @@ const reducers = { [UPDATE_DOCUMENT_TITLE_REDUCER]: documentTitleReducer, [MARK_ACTIVE_PRODUCT]: markActiveProduct, [CLEAR_QUICKSTARTS]: clearQuickstartsReducer, - [TOGGLE_NOTIFICATIONS_DRAWER]: toggleNotificationsReducer, - [POPULATE_NOTIFICATIONS]: populateNotificationsReducer, - [MARK_NOTIFICATION_AS_READ]: markNotificationAsRead, - [MARK_NOTIFICATION_AS_UNREAD]: markNotificationAsUnread, - [MARK_ALL_NOTIFICATION_AS_READ]: markAllNotificationsAsRead, - [MARK_ALL_NOTIFICATION_AS_UNREAD]: markAllNotificationsAsUnread, - [UPDATE_NOTIFICATIONS]: updateNotificationsReducer, }; const globalFilter = { @@ -129,11 +108,6 @@ export const chromeInitialState: ReduxState = { quickstarts: { quickstarts: {}, }, - notifications: { - data: [], - isExpanded: false, - count: 0, - }, }, globalFilter: globalFilterDefaultState, }; @@ -154,11 +128,6 @@ export default function (): { quickstarts: { quickstarts: {}, }, - notifications: { - data: [], - isExpanded: false, - count: 0, - }, }, action ) => applyReducerHash(reducers)(state, action), diff --git a/src/redux/store.d.ts b/src/redux/store.d.ts index 6540e901a..c35a0cd2c 100644 --- a/src/redux/store.d.ts +++ b/src/redux/store.d.ts @@ -9,32 +9,6 @@ export type InternalNavigation = { export type AccessRequest = { request_id: string; created: string; seen: boolean }; -export type NotificationData = { - id: string; - title: string; - description: string; - read: boolean; - source: string; - created: string; -}; - -export type Notifications = { - isExpanded: boolean; - data: NotificationData[]; - count: number; -}; - -export type NotificationsPayload = { - data: NotificationData; - source: string; - // cloud events sub protocol metadata - datacontenttype: string; - specversion: string; - // a type field used to identify message purpose - type: string; - time: string; -}; - export type ChromeState = { activeApp?: string; activeProduct?: string; @@ -59,7 +33,6 @@ export type ChromeState = { }; }; documentTitle?: string; - notifications: Notifications; }; export type GlobalFilterWorkloads = { diff --git a/src/state/atoms/notificationDrawerAtom.ts b/src/state/atoms/notificationDrawerAtom.ts new file mode 100644 index 000000000..f83b5f6f4 --- /dev/null +++ b/src/state/atoms/notificationDrawerAtom.ts @@ -0,0 +1,37 @@ +import { atom } from 'jotai'; + +export type NotificationData = { + id: string; + title: string; + description: string; + read: boolean; + source: string; + created: string; +}; + +export type NotificationsPayload = { + data: NotificationData; + source: string; + // cloud events sub protocol metadata + datacontenttype: string; + specversion: string; + // a type field used to identify message purpose + type: string; + time: string; +}; + +export const notificationDrawerExpandedAtom = atom(false); +export const notificationDrawerDataAtom = atom([]); +export const notificationDrawerCountAtom = atom(0); +export const notificationDrawerFilterAtom = atom([]); +export const updateNotificationsStatusAtom = atom(null, (_get, set, read: boolean = false) => { + set(notificationDrawerDataAtom, (prev) => prev.map((notification) => ({ ...notification, read }))); +}); +export const updateNotificationReadAtom = atom(null, (_get, set, id: string, read: boolean) => { + set(notificationDrawerDataAtom, (prev) => prev.map((notification) => (notification.id === id ? { ...notification, read } : notification))); +}); +export const unreadNotificationsAtom = atom((get) => get(notificationDrawerDataAtom).filter((notification) => !notification.read).length); +export const addNotificationAtom = atom(null, (_get, set, ...notifications: NotificationData[]) => { + set(notificationDrawerDataAtom, (prev) => [...notifications, ...prev]); + set(notificationDrawerCountAtom, (prev) => prev + notifications.length); +}); From 3af37c6e5ce5699eab58fbcdfa744c6db8ae9e7e Mon Sep 17 00:00:00 2001 From: Adam Jetmar <67118990+JetyAdam@users.noreply.github.com> Date: Fri, 14 Jun 2024 12:32:19 +0200 Subject: [PATCH 012/151] Jotai migration isFeedbackModalOpen (#2860) * Jotai migration isFeedbackModalOpen * Update Jotai migration isFeedbackModalOpen * Use different atom method * Add tests for Jotai migration isFeedbackModalOpen * Remove toggleFeedbackModal function from reducers * Remove dead code --------- Co-authored-by: Adam Jetmar --- .../ChromeContext/feedbackModal.cy.tsx | 141 ++++++++++++++++++ src/chrome/create-chrome.ts | 8 +- src/components/Feedback/FeedbackModal.tsx | 22 +-- src/components/Feedback/usePendoFeedback.ts | 14 +- src/redux/action-types.ts | 2 - src/redux/actions.ts | 10 -- src/redux/chromeReducers.ts | 28 ---- src/redux/index.ts | 6 - src/redux/store.d.ts | 2 - src/state/atoms/feedbackModalAtom.ts | 4 + src/state/chromeStore.ts | 2 + 11 files changed, 171 insertions(+), 68 deletions(-) create mode 100644 cypress/component/ChromeContext/feedbackModal.cy.tsx create mode 100644 src/state/atoms/feedbackModalAtom.ts diff --git a/cypress/component/ChromeContext/feedbackModal.cy.tsx b/cypress/component/ChromeContext/feedbackModal.cy.tsx new file mode 100644 index 000000000..1b7a1a2ab --- /dev/null +++ b/cypress/component/ChromeContext/feedbackModal.cy.tsx @@ -0,0 +1,141 @@ +import React, { Context, createContext, useContext, useState } from 'react'; +import { createChromeContext } from '../../../src/chrome/create-chrome'; +import { ChromeAPI } from '@redhat-cloud-services/types'; +import { AnalyticsBrowser } from '@segment/analytics-next'; +import { ChromeAuthContextValue } from '../../../src/auth/ChromeAuthContext'; +import { getSharedScope, initSharedScope } from '@scalprum/core'; +import { Store } from 'redux'; +import Feedback from '../../../src/components/Feedback'; +import { IntlProvider } from 'react-intl'; +import InternalChromeContext from '../../../src/utils/internalChromeContext'; +import { Provider as JotaiProvider } from 'jotai'; +import chromeStore from '../../../src/state/chromeStore'; + +describe('Feedback Modal', () => { + let chromeContext: Context; + let contextValue: ChromeAPI; + const NestedComponen = () => { + return ( + + 'stage' } as any}> + + + + ); + }; + const InnerComponent = () => { + const { usePendoFeedback } = useContext(chromeContext); + usePendoFeedback(); + return null; + }; + + const Wrapper = () => { + const [removeComponent, setRemoveComponent] = useState(false); + return ( + + 'stage' } as any}> + + {!removeComponent ? : null} + + + + ); + }; + + const CustomButton = () => { + const { toggleFeedbackModal } = useContext(chromeContext); + + return ( + + ); + }; + + const CustomComponent = () => { + return ( + + + 'stage' } as any}> + + + + + + ); + }; + + beforeEach(() => { + initSharedScope(); + const scope = getSharedScope(); + scope['@chrome/visibilityFunctions'] = { + '*': { + loaded: 1, + get: () => {}, + }, + }; + contextValue = createChromeContext({ + analytics: {} as AnalyticsBrowser, + chromeAuth: { + getUser: () => Promise.resolve({}), + } as ChromeAuthContextValue, + helpTopics: {} as ChromeAPI['helpTopics'], + quickstartsAPI: {} as ChromeAPI['quickStarts'], + registerModule: () => {}, + setPageMetadata: () => {}, + store: { + dispatch: () => {}, + } as unknown as Store, + useGlobalFilter: () => {}, + }); + chromeContext = createContext(contextValue); + }); + it('should test opening and closing feedback modal', () => { + const Modal = () => { + return ( + + + + ); + }; + cy.mount(); + cy.contains('Tell us about your experience').should('not.exist'); + cy.contains('Feedback').click(); + cy.contains('Tell us about your experience').should('exist'); + cy.get('[aria-label="Close"]').click(); + cy.contains('Tell us about your experience').should('not.exist'); + }); + + it('should test pendoFeedback', () => { + const Context = () => { + return ( + + + + ); + }; + cy.mount(); + cy.contains('Tell us about your experience').should('not.exist'); + cy.contains('Feedback').click(); + cy.contains('Tell us about your experience').should('not.exist'); + cy.contains('Remove component from dom').click(); + cy.contains('Feedback').click(); + cy.contains('Tell us about your experience').should('exist'); + }); + + it('should use custom feedback button to open feedback modal', () => { + const CustomModal = () => { + return ( + + + + ); + }; + cy.mount(); + cy.contains('Tell us about your experience').should('not.exist'); + cy.contains('Custom feedback button').click(); + cy.contains('Tell us about your experience').should('exist'); + cy.get('[aria-label="Close"]').click(); + cy.contains('Tell us about your experience').should('not.exist'); + }); +}); diff --git a/src/chrome/create-chrome.ts b/src/chrome/create-chrome.ts index 00cddad92..137624c04 100644 --- a/src/chrome/create-chrome.ts +++ b/src/chrome/create-chrome.ts @@ -14,7 +14,6 @@ import { removeGlobalFilter, toggleDebuggerButton, toggleDebuggerModal, - toggleFeedbackModal, toggleGlobalFilter, } from '../redux/actions'; import { ITLess, getEnv, getEnvDetails, isBeta, isProd, updateDocumentTitle } from '../utils/common'; @@ -22,7 +21,6 @@ import { createSupportCase } from '../utils/createCase'; import debugFunctions from '../utils/debugFunctions'; import { flatTags } from '../components/GlobalFilter/globalFilterApi'; import { PUBLIC_EVENTS } from '../utils/consts'; -import { usePendoFeedback } from '../components/Feedback'; import { middlewareListener } from '../redux/redux-config'; import { clearAnsibleTrialFlag, isAnsibleTrialFlagActive, setAnsibleTrialFlag } from '../utils/isAnsibleTrialFlagActive'; import chromeHistory from '../utils/chromeHistory'; @@ -37,6 +35,8 @@ import qe from '../utils/iqeEnablement'; import { RegisterModulePayload } from '../state/atoms/chromeModuleAtom'; import requestPdf from '../pdf/requestPdf'; import chromeStore from '../state/chromeStore'; +import { isFeedbackModalOpenAtom } from '../state/atoms/feedbackModalAtom'; +import { usePendoFeedback } from '../components/Feedback'; export type CreateChromeContextConfig = { useGlobalFilter: (callback: (selectedTags?: FlagTagsFilter) => any) => ReturnType; @@ -170,7 +170,9 @@ export const createChromeContext = ({ segment: { setPageMetadata, }, - toggleFeedbackModal: (isOpen: boolean) => dispatch(toggleFeedbackModal(isOpen)), + toggleFeedbackModal: (isOpen: boolean) => { + chromeStore.set(isFeedbackModalOpenAtom, isOpen); + }, enableDebugging: () => dispatch(toggleDebuggerButton(true)), toggleDebuggerModal: (isOpen: boolean) => dispatch(toggleDebuggerModal(isOpen)), // FIXME: Update types once merged diff --git a/src/components/Feedback/FeedbackModal.tsx b/src/components/Feedback/FeedbackModal.tsx index 937514b6d..e87b043d5 100644 --- a/src/components/Feedback/FeedbackModal.tsx +++ b/src/components/Feedback/FeedbackModal.tsx @@ -11,13 +11,12 @@ import ExternalLinkAltIcon from '@patternfly/react-icons/dist/dynamic/icons/exte import OutlinedCommentsIcon from '@patternfly/react-icons/dist/dynamic/icons/outlined-comments-icon'; import { DeepRequired } from 'utility-types'; import { ChromeUser } from '@redhat-cloud-services/types'; -import { useDispatch, useSelector } from 'react-redux'; import { useIntl } from 'react-intl'; +import { useAtom, useAtomValue } from 'jotai'; +import { isFeedbackModalOpenAtom, usePendoFeedbackAtom } from '../../state/atoms/feedbackModalAtom'; import feedbackIllo from '../../../static/images/feedback_illo.svg'; import FeedbackForm from './FeedbackForm'; -import { toggleFeedbackModal } from '../../redux/actions'; -import { ReduxState } from '../../redux/store'; import FeedbackSuccess from './FeedbackSuccess'; import messages from '../../locales/Messages'; import FeedbackError from './FeedbackError'; @@ -42,9 +41,8 @@ export type FeedbackPages = const FeedbackModal = memo(() => { const intl = useIntl(); - const usePendoFeedback = useSelector(({ chrome: { usePendoFeedback } }) => usePendoFeedback); - const isOpen = useSelector(({ chrome: { isFeedbackModalOpen } }) => isFeedbackModalOpen); - const dispatch = useDispatch(); + const [isModalOpen, setIsModalOpen] = useAtom(isFeedbackModalOpenAtom); + const usePendoFeedback = useAtomValue(usePendoFeedbackAtom); const [modalPage, setModalPage] = useState('feedbackHome'); const { getEnvironment } = useContext(InternalChromeContext); const chromeAuth = useContext(ChromeAuthContext); @@ -52,9 +50,9 @@ const FeedbackModal = memo(() => { const user = chromeAuth.user as DeepRequired; const env = getEnvironment(); const isAvailable = env === 'prod' || env === 'stage'; - const setIsModalOpen = (isOpen: boolean) => dispatch(toggleFeedbackModal(isOpen)); const handleCloseModal = () => { - setIsModalOpen(false), setModalPage('feedbackHome'); + setIsModalOpen(false); + setModalPage('feedbackHome'); }; const ModalDescription = ({ modalPage }: { modalPage: FeedbackPages }) => { @@ -208,7 +206,13 @@ const FeedbackModal = memo(() => { {intl.formatMessage(messages.feedback)} - + diff --git a/src/components/Feedback/usePendoFeedback.ts b/src/components/Feedback/usePendoFeedback.ts index cd007bbad..c803ff981 100644 --- a/src/components/Feedback/usePendoFeedback.ts +++ b/src/components/Feedback/usePendoFeedback.ts @@ -1,22 +1,20 @@ import { useEffect } from 'react'; -import { spinUpStore } from '../../redux/redux-config'; -import { setPendoFeedbackFlag } from '../../redux/actions'; +import { useSetAtom } from 'jotai'; +import { usePendoFeedbackAtom } from '../../state/atoms/feedbackModalAtom'; const usePendoFeedback = () => { /** * We have to use the "spinUpStore" instead of just calling useDispatch * Otherwise we will end up using the "dispatch" instance from the application not chrome! */ - const { - store: { dispatch }, - } = spinUpStore(); + const setPendoFeedback = useSetAtom(usePendoFeedbackAtom); useEffect(() => { - dispatch(setPendoFeedbackFlag(true)); + setPendoFeedback(true); return () => { - dispatch(setPendoFeedbackFlag(false)); + setPendoFeedback(false); }; - }, []); + }, [setPendoFeedback]); }; export default usePendoFeedback; diff --git a/src/redux/action-types.ts b/src/redux/action-types.ts index 82ac8d342..0690ff205 100644 --- a/src/redux/action-types.ts +++ b/src/redux/action-types.ts @@ -16,8 +16,6 @@ export const GLOBAL_FILTER_REMOVE = '@@chrome/global-filter-remove'; export const LOAD_NAVIGATION_LANDING_PAGE = '@@chrome/load-navigation-landing-page'; export const LOAD_LEFT_NAVIGATION_SEGMENT = '@@chrome/load-navigation-segment'; -export const SET_PENDO_FEEDBACK_FLAG = '@@chrome/set-pendo-feedback-flag'; -export const TOGGLE_FEEDBACK_MODAL = '@@chrome/toggle-feedback-modal'; export const TOGGLE_DEBUGGER_MODAL = '@@chrome/toggle-debugger-modal'; export const TOGGLE_DEBUGGER_BUTTON = '@@chrome/toggle-debugger-button'; export const UPDATE_ACCESS_REQUESTS_NOTIFICATIONS = '@@chrome/update-access-requests-notifications'; diff --git a/src/redux/actions.ts b/src/redux/actions.ts index 2bd73daf1..bd33f2b31 100644 --- a/src/redux/actions.ts +++ b/src/redux/actions.ts @@ -101,16 +101,6 @@ export const onToggle = () => ({ type: 'NAVIGATION_TOGGLE', }); -export const setPendoFeedbackFlag = (payload: boolean) => ({ - type: actionTypes.SET_PENDO_FEEDBACK_FLAG, - payload, -}); - -export const toggleFeedbackModal = (payload: boolean) => ({ - type: actionTypes.TOGGLE_FEEDBACK_MODAL, - payload, -}); - export const toggleDebuggerModal = (payload: boolean) => ({ type: actionTypes.TOGGLE_DEBUGGER_MODAL, payload, diff --git a/src/redux/chromeReducers.ts b/src/redux/chromeReducers.ts index 0b854bc6c..d7424b796 100644 --- a/src/redux/chromeReducers.ts +++ b/src/redux/chromeReducers.ts @@ -80,34 +80,6 @@ export function loadNavigationSegmentReducer( return state; } -export function setPendoFeedbackFlag( - state: ChromeState, - { - payload, - }: { - payload: boolean; - } -): ChromeState { - return { - ...state, - usePendoFeedback: payload, - }; -} - -export function toggleFeedbackModal( - state: ChromeState, - { - payload, - }: { - payload: boolean; - } -): ChromeState { - return { - ...state, - isFeedbackModalOpen: payload, - }; -} - export function toggleDebuggerModal( state: ChromeState, { diff --git a/src/redux/index.ts b/src/redux/index.ts index 62e688267..5d27dfd2d 100644 --- a/src/redux/index.ts +++ b/src/redux/index.ts @@ -15,10 +15,8 @@ import { onPageAction, onPageObjectId, populateQuickstartsReducer, - setPendoFeedbackFlag, toggleDebuggerButton, toggleDebuggerModal, - toggleFeedbackModal, } from './chromeReducers'; import { globalFilterDefaultState, @@ -52,10 +50,8 @@ import { MARK_ACTIVE_PRODUCT, MARK_REQUEST_NOTIFICATION_SEEN, POPULATE_QUICKSTARTS_CATALOG, - SET_PENDO_FEEDBACK_FLAG, TOGGLE_DEBUGGER_BUTTON, TOGGLE_DEBUGGER_MODAL, - TOGGLE_FEEDBACK_MODAL, UPDATE_ACCESS_REQUESTS_NOTIFICATIONS, UPDATE_DOCUMENT_TITLE_REDUCER, USER_LOGIN, @@ -70,8 +66,6 @@ const reducers = { [CHROME_PAGE_OBJECT]: onPageObjectId, [LOAD_NAVIGATION_LANDING_PAGE]: loadNavigationLandingPageReducer, [LOAD_LEFT_NAVIGATION_SEGMENT]: loadNavigationSegmentReducer, - [SET_PENDO_FEEDBACK_FLAG]: setPendoFeedbackFlag, - [TOGGLE_FEEDBACK_MODAL]: toggleFeedbackModal, [TOGGLE_DEBUGGER_MODAL]: toggleDebuggerModal, [TOGGLE_DEBUGGER_BUTTON]: toggleDebuggerButton, [UPDATE_ACCESS_REQUESTS_NOTIFICATIONS]: accessRequestsNotificationsReducer, diff --git a/src/redux/store.d.ts b/src/redux/store.d.ts index c35a0cd2c..0b1fa639c 100644 --- a/src/redux/store.d.ts +++ b/src/redux/store.d.ts @@ -16,8 +16,6 @@ export type ChromeState = { pageAction?: string; pageObjectId?: string; navigation: InternalNavigation; - usePendoFeedback?: boolean; - isFeedbackModalOpen?: boolean; isDebuggerModalOpen?: boolean; isDebuggerEnabled?: boolean; accessRequests: { diff --git a/src/state/atoms/feedbackModalAtom.ts b/src/state/atoms/feedbackModalAtom.ts new file mode 100644 index 000000000..cdc155484 --- /dev/null +++ b/src/state/atoms/feedbackModalAtom.ts @@ -0,0 +1,4 @@ +import { atom } from 'jotai'; + +export const isFeedbackModalOpenAtom = atom(false); +export const usePendoFeedbackAtom = atom(false); diff --git a/src/state/chromeStore.ts b/src/state/chromeStore.ts index 0397ca3a3..58de266dc 100644 --- a/src/state/chromeStore.ts +++ b/src/state/chromeStore.ts @@ -4,6 +4,7 @@ import { contextSwitcherOpenAtom } from './atoms/contextSwitcher'; import { isPreviewAtom } from './atoms/releaseAtom'; import { isBeta } from '../utils/common'; import { gatewayErrorAtom } from './atoms/gatewayErrorAtom'; +import { isFeedbackModalOpenAtom } from './atoms/feedbackModalAtom'; const chromeStore = createStore(); @@ -12,6 +13,7 @@ chromeStore.set(contextSwitcherOpenAtom, false); chromeStore.set(activeModuleAtom, undefined); chromeStore.set(isPreviewAtom, isBeta()); chromeStore.set(gatewayErrorAtom, undefined); +chromeStore.set(isFeedbackModalOpenAtom, false); // globally handle subscription to activeModuleAtom chromeStore.sub(activeModuleAtom, () => { From 93b4b48d30ab07a02288f40cd84b3ddb5115e749 Mon Sep 17 00:00:00 2001 From: Martin Marosi Date: Fri, 14 Jun 2024 13:09:22 +0200 Subject: [PATCH 013/151] Repair broken OIDC state. --- cypress/e2e/auth/OIDC/OIDCState.cy.ts | 37 ++++++++++++++++++++ src/auth/OIDCConnector/OIDCProvider.tsx | 23 ++++++------ src/auth/OIDCConnector/OIDCStateReloader.tsx | 36 +++++++++++++++++++ 3 files changed, 86 insertions(+), 10 deletions(-) create mode 100644 cypress/e2e/auth/OIDC/OIDCState.cy.ts create mode 100644 src/auth/OIDCConnector/OIDCStateReloader.tsx diff --git a/cypress/e2e/auth/OIDC/OIDCState.cy.ts b/cypress/e2e/auth/OIDC/OIDCState.cy.ts new file mode 100644 index 000000000..680a4df00 --- /dev/null +++ b/cypress/e2e/auth/OIDC/OIDCState.cy.ts @@ -0,0 +1,37 @@ +describe('OIDC State', () => { + const BROKEN_URL_HASH = + '#state=ebc8e454f3794afcab512efb234d686c&session_state=fe052e48-c1f7-4941-abd4-33374a407951&code=f87aeee6-228d-405c-88d8-146b1e0eb9b1.fe052e48-c1f7-4941-aaa4-33334a407951.5efe402b-7f07-4878-a419-6797ce7aeb3b'; + it('Should detect broken state in URL and refresh browser', () => { + cy.login(); + + // should pass normally + cy.visit('/'); + + cy.contains('Insights QA').should('exist'); + + // Introduce broken state in URL + + Cypress.on('uncaught:exception', () => { + // Error is expected to be thrown + return false; + }); + const pathname = `/foo/bar?baz=quaz${BROKEN_URL_HASH}`; + cy.visit(pathname); + + cy.url().should('contain', BROKEN_URL_HASH); + cy.wait(1000); + cy.url().should('not.contain', BROKEN_URL_HASH); + cy.window().then((win) => { + Cypress.on('uncaught:exception', () => { + // Enable cypress exceptions again + return true; + }); + // The reloader should preserve pathname and query params + const url = new URL(win.location.href); + expect(url.hash).to.be.empty; + expect(url.pathname).to.eq('/beta/foo/bar'); + expect(url.search).to.eq('?baz=quaz'); + cy.contains('Insights QA').should('exist'); + }); + }); +}); diff --git a/src/auth/OIDCConnector/OIDCProvider.tsx b/src/auth/OIDCConnector/OIDCProvider.tsx index d82c10376..8b8e63d5f 100644 --- a/src/auth/OIDCConnector/OIDCProvider.tsx +++ b/src/auth/OIDCConnector/OIDCProvider.tsx @@ -6,6 +6,7 @@ import platformUrl from '../platformUrl'; import { OIDCSecured } from './OIDCSecured'; import AppPlaceholder from '../../components/AppPlaceholder'; import { postbackUrlSetup } from '../offline'; +import OIDCStateReloader from './OIDCStateReloader'; const betaPartial = isBeta() ? '/beta' : ''; @@ -73,16 +74,18 @@ const OIDCProvider: React.FC = ({ children }) => { } return ( - - - {children} - - + + + + {children} + + + ); }; diff --git a/src/auth/OIDCConnector/OIDCStateReloader.tsx b/src/auth/OIDCConnector/OIDCStateReloader.tsx new file mode 100644 index 000000000..988415721 --- /dev/null +++ b/src/auth/OIDCConnector/OIDCStateReloader.tsx @@ -0,0 +1,36 @@ +import React, { PropsWithChildren } from 'react'; + +const INVALID_AUTH_STATE_ERROR = 'No matching state found in storage'; + +export default class OIDCStateReloader extends React.Component { + state: { hasError: boolean } = { + hasError: false, + }; + + static getDerivedStateFromError() { + return { hasError: true }; + } + handleInvalidAuthState(): void { + const repairedUrl = new URL(window.location.href); + // remove invalid SSO state and force re authentication + repairedUrl.hash = ''; + // remove possibly broken local storage state from client + localStorage.clear(); + // hard page reload + window.location.href = repairedUrl.toString(); + } + + componentDidCatch(error: Error): void { + // handle invalid auth state error + if (typeof error.message === 'string' && error.message === INVALID_AUTH_STATE_ERROR) { + this.handleInvalidAuthState(); + } + } + + render() { + if (this.state.hasError) { + return null; + } + return this.props.children; + } +} From 2234d5b6cd45f6fd9b03467b8535813da6ace03d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 16 Jun 2024 09:53:50 +0000 Subject: [PATCH 014/151] Bump braces from 3.0.2 to 3.0.3 Bumps [braces](https://github.com/micromatch/braces) from 3.0.2 to 3.0.3. - [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md) - [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3) --- updated-dependencies: - dependency-name: braces dependency-type: indirect ... Signed-off-by: dependabot[bot] --- package-lock.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index fab6b5645..ffe4671de 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12050,11 +12050,11 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -15758,9 +15758,9 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dependencies": { "to-regex-range": "^5.0.1" }, From 6891edbfd3cf1ba616b988e4f22462b0af3c2a04 Mon Sep 17 00:00:00 2001 From: Martin Marosi Date: Tue, 18 Jun 2024 10:10:22 +0200 Subject: [PATCH 015/151] Hide OpenShift VA behind a feature flag. --- src/components/RootApp/ScalprumRoot.test.js | 1 + src/components/Routes/VirtualAssistant.tsx | 20 +++++++++++--------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/components/RootApp/ScalprumRoot.test.js b/src/components/RootApp/ScalprumRoot.test.js index 4f3aaa72c..f1adb993b 100644 --- a/src/components/RootApp/ScalprumRoot.test.js +++ b/src/components/RootApp/ScalprumRoot.test.js @@ -38,6 +38,7 @@ jest.mock('@unleash/proxy-client-react', () => { ...unleash, useFlag: () => false, useFlagsStatus: () => ({ flagsReady: true }), + useFlags: () => [], }; }); diff --git a/src/components/Routes/VirtualAssistant.tsx b/src/components/Routes/VirtualAssistant.tsx index beca6ddb7..caa020579 100644 --- a/src/components/Routes/VirtualAssistant.tsx +++ b/src/components/Routes/VirtualAssistant.tsx @@ -1,20 +1,22 @@ import React, { Fragment } from 'react'; import { Route, Routes } from 'react-router-dom'; import { ScalprumComponent } from '@scalprum/react-core'; +import { useFlags } from '@unleash/proxy-client-react'; import './virtual-assistant.scss'; -const viableRoutes = [ - '/', - '/insights/*', - '/settings/*', - '/subscriptions/overview/*', - '/subscriptions/inventory/*', - '/subscriptions/usage/*', - '/openshift/insights/*', -]; +const flaggedRoutes: { [flagName: string]: string } = { 'platform.va.openshift.insights': '/openshift/insights/*' }; const VirtualAssistant = () => { + const viableRoutes = ['/', '/insights/*', '/settings/*', '/subscriptions/overview/*', '/subscriptions/inventory/*', '/subscriptions/usage/*']; + + const allFlags = useFlags(); + allFlags.forEach((flag) => { + if (flaggedRoutes[flag.name] && flag.enabled) { + viableRoutes.push(flaggedRoutes[flag.name]); + } + }); + return ( {viableRoutes.map((route) => ( From 93f09bde07c4ab9b4d5c5ff8290797d5d022c375 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 18 Jun 2024 19:01:58 +0000 Subject: [PATCH 016/151] Bump ws from 7.5.9 to 7.5.10 Bumps [ws](https://github.com/websockets/ws) from 7.5.9 to 7.5.10. - [Release notes](https://github.com/websockets/ws/releases) - [Commits](https://github.com/websockets/ws/compare/7.5.9...7.5.10) --- updated-dependencies: - dependency-name: ws dependency-type: indirect ... Signed-off-by: dependabot[bot] --- package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index ffe4671de..914ff72b9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29411,9 +29411,9 @@ } }, "node_modules/webpack-bundle-analyzer/node_modules/ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "dev": true, "engines": { "node": ">=8.3.0" @@ -29943,9 +29943,9 @@ } }, "node_modules/ws": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", - "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "dev": true, "engines": { "node": ">=10.0.0" From fb69ba678d27344364caf9a2f1fc7f36ae74e1a8 Mon Sep 17 00:00:00 2001 From: Cody Mitchell Date: Thu, 13 Jun 2024 20:15:07 -0400 Subject: [PATCH 017/151] enable marking as read in the notification drawer --- .../NotificationDrawer.cy.tsx | 37 ++++++++-- .../DrawerPanelContent.tsx | 74 ++++++++++++++++--- .../NotificationsDrawer/NotificationItem.tsx | 26 +++++-- src/state/atoms/notificationDrawerAtom.ts | 8 ++ 4 files changed, 120 insertions(+), 25 deletions(-) diff --git a/cypress/component/NotificationDrawer/NotificationDrawer.cy.tsx b/cypress/component/NotificationDrawer/NotificationDrawer.cy.tsx index 268c0d9b1..c6164bea3 100644 --- a/cypress/component/NotificationDrawer/NotificationDrawer.cy.tsx +++ b/cypress/component/NotificationDrawer/NotificationDrawer.cy.tsx @@ -75,41 +75,62 @@ describe('Notification Drawer', () => { }); }); - it('should mark single notification as read', () => { + it('should mark a single notification as read', () => { + cy.intercept('PUT', 'http://localhost:8080/api/notifications/v1/notifications/drawer/read', { + statusCode: 200, + }); cy.mount(); cy.get('#populate-notifications').click(); cy.get('#drawer-toggle').click(); cy.get('.pf-m-read').should('have.length', 0); - cy.contains('Notification 1').get('input[type="checkbox"]').first().click(); + cy.get('[aria-label="Notification actions dropdown"]').first().click(); + cy.get('[role="menuitem"]').contains('Mark as read').first().click(); cy.get('.pf-m-read').should('have.length', 1); }); - it('should mark one notification as unread', () => { + it('should mark a single notification as unread', () => { + cy.intercept('PUT', 'http://localhost:8080/api/notifications/v1/notifications/drawer/read', { + statusCode: 200, + }); cy.mount(); cy.get('#populate-notifications').click(); cy.get('#drawer-toggle').click(); cy.get('.pf-m-read').should('have.length', 3); - cy.contains('Notification 1').get('input[type="checkbox"]').first().click(); - cy.get('.pf-m-read').should('have.length', 2); + cy.get('[aria-label="Notification actions dropdown"]').first().click(); + cy.get('[role="menuitem"]').contains('Mark as unread').first().click(); }); it('should mark all notifications as read', () => { + cy.intercept('PUT', 'http://localhost:8080/api/notifications/v1/notifications/drawer/read', { + statusCode: 200, + }); cy.mount(); cy.get('#populate-notifications').click(); cy.get('#drawer-toggle').click(); cy.get('.pf-m-read').should('have.length', 0); + // select all notifications + cy.get('[aria-label="notifications-bulk-select"]').click(); + cy.get('[data-ouia-component-id="notifications-bulk-select-select-all"]').click(); + // mark selected as read cy.get('#notifications-actions-toggle').click(); - cy.contains('Mark visible as read').click(); + cy.contains('Mark selected as read').click(); cy.get('.pf-m-read').should('have.length', 3); }); - it('should mark all notifications as not read', () => { + it('should mark all notifications as unread', () => { + cy.intercept('PUT', 'http://localhost:8080/api/notifications/v1/notifications/drawer/read', { + statusCode: 200, + }); cy.mount(); cy.get('#populate-notifications').click(); cy.get('#drawer-toggle').click(); cy.get('.pf-m-read').should('have.length', 3); + // select all notifications + cy.get('[aria-label="notifications-bulk-select"]').click(); + cy.get('[data-ouia-component-id="notifications-bulk-select-select-all"]').click(); + // mark selected as unread cy.get('#notifications-actions-toggle').click(); - cy.contains('Mark visible as unread').click(); + cy.contains('Mark selected as unread').click(); cy.get('.pf-m-read').should('have.length', 0); }); diff --git a/src/components/NotificationsDrawer/DrawerPanelContent.tsx b/src/components/NotificationsDrawer/DrawerPanelContent.tsx index 21043a5d2..2d2bcf42f 100644 --- a/src/components/NotificationsDrawer/DrawerPanelContent.tsx +++ b/src/components/NotificationsDrawer/DrawerPanelContent.tsx @@ -29,8 +29,13 @@ import { notificationDrawerDataAtom, notificationDrawerExpandedAtom, notificationDrawerFilterAtom, - updateNotificationsStatusAtom, + notificationDrawerSelectedAtom, + updateNotificationReadAtom, + updateNotificationSelectedAtom, + updateNotificationsSelectedAtom, } from '../../state/atoms/notificationDrawerAtom'; +import BulkSelect from '@redhat-cloud-services/frontend-components/BulkSelect'; +import axios from 'axios'; export type DrawerPanelProps = { innerRef: React.Ref; @@ -74,11 +79,14 @@ const DrawerPanelBase = ({ innerRef }: DrawerPanelProps) => { const toggleDrawer = useSetAtom(notificationDrawerExpandedAtom); const navigate = useNavigate(); const notifications = useAtomValue(notificationDrawerDataAtom); - const updateNotificationsStatus = useSetAtom(updateNotificationsStatusAtom); + const selectedNotifications = useAtomValue(notificationDrawerSelectedAtom); + const updateSelectedNotification = useSetAtom(updateNotificationSelectedAtom); const auth = useContext(ChromeAuthContext); const isOrgAdmin = auth?.user?.identity?.user?.is_org_admin; const { getUserPermissions } = useContext(InternalChromeContext); const [hasNotificationsPermissions, setHasNotificationsPermissions] = useState(false); + const updateNotificationRead = useSetAtom(updateNotificationReadAtom); + const updateAllNotificationsSelected = useSetAtom(updateNotificationsSelectedAtom); useEffect(() => { let mounted = true; @@ -114,14 +122,29 @@ const DrawerPanelBase = ({ innerRef }: DrawerPanelProps) => { toggleDrawer(false); }; - const onMarkAllAsRead = () => { - updateNotificationsStatus(true); - setIsDropdownOpen(false); + const onUpdateSelectedStatus = (read: boolean) => { + axios + .put('/api/notifications/v1/notifications/drawer/read', { + notification_ids: selectedNotifications.map((notification) => notification.id), + read_status: read, + }) + .then(() => { + selectedNotifications.forEach((notification) => updateNotificationRead(notification.id, read)); + setIsDropdownOpen(false); + updateAllNotificationsSelected(false); + }) + .catch((e) => { + console.error('failed to update notification read status', e); + }); }; - const onMarkAllAsUnread = () => { - updateNotificationsStatus(false); - setIsDropdownOpen(false); + const selectAllNotifications = (selected: boolean) => { + updateAllNotificationsSelected(selected); + }; + + const selectVisibleNotifications = () => { + const visibleNotifications = activeFilters.length > 0 ? filteredNotifications : notifications; + visibleNotifications.forEach((notification) => updateSelectedNotification(notification.id, true)); }; const onFilterSelect = (chosenFilter: string) => { @@ -137,11 +160,23 @@ const DrawerPanelBase = ({ innerRef }: DrawerPanelProps) => { const dropdownItems = [ , - - Mark visible as read + { + onUpdateSelectedStatus(true); + }} + isDisabled={notifications.length === 0} + > + Mark selected as read , - - Mark visible as unread + { + onUpdateSelectedStatus(false); + }} + isDisabled={notifications.length === 0} + > + Mark selected as unread , , , @@ -225,6 +260,21 @@ const DrawerPanelBase = ({ innerRef }: DrawerPanelProps) => { > {filterDropdownItems()} + selectAllNotifications(false) }, + { + title: `Select visible (${activeFilters.length > 0 ? filteredNotifications.length : notifications.length})`, + key: 'select-visible', + onClick: selectVisibleNotifications, + }, + { title: `Select all (${notifications.length})`, key: 'select-all', onClick: () => selectAllNotifications(true) }, + ]} + count={notifications.filter(({ selected }) => selected).length} + checked={notifications.length > 0 && notifications.every(({ selected }) => selected)} + ouiaId="notifications-bulk-select" + /> ) => ( = ({ notification, onNavigateTo }) => { const [isDropdownOpen, setIsDropdownOpen] = useState(false); + const updateNotificationSelected = useSetAtom(updateNotificationSelectedAtom); const updateNotificationRead = useSetAtom(updateNotificationReadAtom); const onCheckboxToggle = () => { - updateNotificationRead(notification.id, !notification.read); - setIsDropdownOpen(false); + updateNotificationSelected(notification.id, !notification.selected); + }; + + const onMarkAsRead = () => { + axios + .put('/api/notifications/v1/notifications/drawer/read', { + notification_ids: [notification.id], + read_status: !notification.read, + }) + .then(() => { + updateNotificationRead(notification.id, !notification.read); + setIsDropdownOpen(false); + }) + .catch((e) => { + console.error('failed to update notification read status', e); + }); }; const notificationDropdownItems = [ - {`Mark as ${!notification.read ? 'read' : 'unread'}`}, + {`Mark as ${!notification.read ? 'read' : 'unread'}`}, onNavigateTo('settings/notifications/configure-events')}> Manage this event , @@ -39,7 +55,7 @@ const NotificationItem: React.FC = ({ notification, onNav - + ) => ( [...notifications, ...prev]); set(notificationDrawerCountAtom, (prev) => prev + notifications.length); }); +export const notificationDrawerSelectedAtom = atom((get) => get(notificationDrawerDataAtom).filter((notification) => notification.selected)); +export const updateNotificationSelectedAtom = atom(null, (_get, set, id: string, selected: boolean) => { + set(notificationDrawerDataAtom, (prev) => prev.map((notification) => (notification.id === id ? { ...notification, selected } : notification))); +}); +export const updateNotificationsSelectedAtom = atom(null, (_get, set, selected: boolean) => { + set(notificationDrawerDataAtom, (prev) => prev.map((notification) => ({ ...notification, selected }))); +}); From 3e3b812f12db9d63d582a9fed1076dfc4688381d Mon Sep 17 00:00:00 2001 From: Martin Marosi Date: Tue, 14 May 2024 14:26:44 +0200 Subject: [PATCH 018/151] Init internal preview flag from chrome service. --- src/bootstrap.tsx | 37 ++++++++++++++++++++--- src/hooks/useAsyncLoader.ts | 43 +++++++++++++++++++++++++++ src/state/atoms/releaseAtom.ts | 12 ++++++-- src/state/atoms/utils.ts | 3 +- src/state/chromeStore.ts | 2 ++ src/utils/VisibilitySingleton.test.ts | 19 ++++++------ src/utils/VisibilitySingleton.ts | 17 +++++++++-- src/utils/initUserConfig.ts | 39 ++++++++++++++++++++++++ 8 files changed, 152 insertions(+), 20 deletions(-) create mode 100644 src/hooks/useAsyncLoader.ts create mode 100644 src/utils/initUserConfig.ts diff --git a/src/bootstrap.tsx b/src/bootstrap.tsx index 85fe5ab00..17393c2c5 100644 --- a/src/bootstrap.tsx +++ b/src/bootstrap.tsx @@ -1,8 +1,8 @@ -import React, { useEffect, useMemo, useState } from 'react'; +import React, { Suspense, useContext, useEffect, useMemo, useState } from 'react'; import { createRoot } from 'react-dom/client'; import { Provider, useSelector } from 'react-redux'; import { IntlProvider, ReactIntlErrorCode } from 'react-intl'; -import { Provider as JotaiProvider } from 'jotai'; +import { Provider as JotaiProvider, useSetAtom } from 'jotai'; import { spinUpStore } from './redux/redux-config'; import RootApp from './components/RootApp'; @@ -14,6 +14,11 @@ import messages from './locales/data.json'; import ErrorBoundary from './components/ErrorComponents/ErrorBoundary'; import chromeStore from './state/chromeStore'; import { GenerateId } from '@patternfly/react-core/dist/dynamic/helpers/GenerateId/GenerateId'; +import { isPreviewAtom } from './state/atoms/releaseAtom'; +import AppPlaceholder from './components/AppPlaceholder'; +import useAsyncLoader from './hooks/useAsyncLoader'; +import { ChromeUserConfig, initChromeUserConfig } from './utils/initUserConfig'; +import ChromeAuthContext from './auth/ChromeAuthContext'; const isITLessEnv = ITLess(); const language: keyof typeof messages = 'en'; @@ -34,7 +39,14 @@ const useInitializeAnalytics = () => { }, []); }; -const App = () => { +const App = ({ initApp }: { initApp: (...args: Parameters) => ChromeUserConfig | undefined }) => { + const { getUser, token } = useContext(ChromeAuthContext); + // triggers suspense based async call to block rendering until the async call is resolved + // TODO: Most of async init should be moved to this method + initApp({ + getUser, + token, + }); const documentTitle = useSelector(({ chrome }: ReduxState) => chrome?.documentTitle); const [cookieElement, setCookieElement] = useState(null); @@ -48,6 +60,23 @@ const App = () => { return ; }; +const ConfigLoader = () => { + const initPreview = useSetAtom(isPreviewAtom); + function initSuccess(userConfig: ChromeUserConfig) { + initPreview(userConfig.data.uiPreview); + } + function initFail() { + initPreview(false); + } + const { loader } = useAsyncLoader(initChromeUserConfig, initSuccess, initFail); + const [cookieElement, setCookieElement] = useState(null); + return ( + }> + + + ); +}; + const entry = document.getElementById('chrome-entry'); if (entry) { const reactRoot = createRoot(entry); @@ -69,7 +98,7 @@ if (entry) { > - + diff --git a/src/hooks/useAsyncLoader.ts b/src/hooks/useAsyncLoader.ts new file mode 100644 index 000000000..b2d210a0e --- /dev/null +++ b/src/hooks/useAsyncLoader.ts @@ -0,0 +1,43 @@ +import { useRef } from 'react'; + +function useAsyncLoader, T extends Array>( + asyncMethod: (...args: T) => Promise, + afterResolve?: (result: R) => void, + afterReject?: (error: any) => void +) { + const storage = useRef<{ resolved: boolean; rejected: boolean; promise?: Promise; result?: R }>({ + resolved: false, + rejected: false, + promise: undefined, + result: undefined, + }); + + return { + loader: (...args: Parameters) => { + if (storage.current.rejected) return; + + if (storage.current.resolved) return storage.current.result; + + if (storage.current.promise) throw storage.current.promise; + + storage.current.promise = asyncMethod(...args) + .then((res) => { + storage.current.promise = undefined; + storage.current.resolved = true; + storage.current.result = res; + afterResolve?.(res); + return res; + }) + .catch((error) => { + storage.current.promise = undefined; + storage.current.rejected = true; + afterReject?.(error); + return error; + }); + + throw storage.current.promise; + }, + }; +} + +export default useAsyncLoader; diff --git a/src/state/atoms/releaseAtom.ts b/src/state/atoms/releaseAtom.ts index f54332027..c19a48c19 100644 --- a/src/state/atoms/releaseAtom.ts +++ b/src/state/atoms/releaseAtom.ts @@ -1,5 +1,11 @@ +import axios from 'axios'; +import { updateVisibilityFunctionsBeta, visibilityFunctionsExist } from '../../utils/VisibilitySingleton'; import { atomWithToggle } from './utils'; -import { isBeta } from '../../utils/common'; - -export const isPreviewAtom = atomWithToggle(isBeta()); +export const isPreviewAtom = atomWithToggle(undefined, (isPreview) => { + // Required to change the `isBeta` function return value in the visibility functions + if (visibilityFunctionsExist()) { + updateVisibilityFunctionsBeta(isPreview); + axios.post('/api/chrome-service/v1/user/update-ui-preview', { uiPreview: isPreview }); + } +}); diff --git a/src/state/atoms/utils.ts b/src/state/atoms/utils.ts index 0bbb74aba..44d912d4f 100644 --- a/src/state/atoms/utils.ts +++ b/src/state/atoms/utils.ts @@ -1,10 +1,11 @@ import { WritableAtom, atom } from 'jotai'; // recipe from https://jotai.org/docs/recipes/atom-with-toggle -export function atomWithToggle(initialValue?: boolean): WritableAtom { +export function atomWithToggle(initialValue?: boolean, onToggle?: (value: boolean) => void): WritableAtom { const anAtom = atom(initialValue, (get, set, nextValue?: boolean) => { const update = nextValue ?? !get(anAtom); set(anAtom, update); + onToggle?.(update); }); return anAtom as WritableAtom; diff --git a/src/state/chromeStore.ts b/src/state/chromeStore.ts index 58de266dc..e727b8f8c 100644 --- a/src/state/chromeStore.ts +++ b/src/state/chromeStore.ts @@ -14,6 +14,8 @@ chromeStore.set(activeModuleAtom, undefined); chromeStore.set(isPreviewAtom, isBeta()); chromeStore.set(gatewayErrorAtom, undefined); chromeStore.set(isFeedbackModalOpenAtom, false); +// is set in bootstrap +chromeStore.set(isPreviewAtom, false); // globally handle subscription to activeModuleAtom chromeStore.sub(activeModuleAtom, () => { diff --git a/src/utils/VisibilitySingleton.test.ts b/src/utils/VisibilitySingleton.test.ts index d8593674d..cf90ad6b5 100644 --- a/src/utils/VisibilitySingleton.test.ts +++ b/src/utils/VisibilitySingleton.test.ts @@ -32,11 +32,12 @@ describe('VisibilitySingleton', () => { const getUserPermissions = jest.fn(); let visibilityFunctions: VisibilityFunctions; - beforeAll(() => { + beforeEach(() => { initializeVisibilityFunctions({ getUser, getToken, getUserPermissions, + isPreview: false, }); visibilityFunctions = getVisibilityFunctions(); }); @@ -162,16 +163,14 @@ describe('VisibilitySingleton', () => { }); test('isBeta', async () => { - const { location } = window; - // @ts-ignore - delete window.location; - // @ts-ignore - window.location = { - pathname: '/beta/insights/foo', - }; - + initializeVisibilityFunctions({ + getUser, + getToken, + getUserPermissions, + isPreview: true, + }); + visibilityFunctions = getVisibilityFunctions(); expect(visibilityFunctions.isBeta()).toBe(true); - window.location = location; }); test('isProd - false', async () => { diff --git a/src/utils/VisibilitySingleton.ts b/src/utils/VisibilitySingleton.ts index 95363c562..5362b084c 100644 --- a/src/utils/VisibilitySingleton.ts +++ b/src/utils/VisibilitySingleton.ts @@ -1,5 +1,5 @@ import { ChromeAPI } from '@redhat-cloud-services/types'; -import { isBeta, isProd } from './common'; +import { isProd } from './common'; import cookie from 'js-cookie'; import axios, { AxiosRequestConfig } from 'axios'; import isEmpty from 'lodash/isEmpty'; @@ -25,10 +25,12 @@ const initialize = ({ getUserPermissions, getUser, getToken, + isPreview, }: { getUser: ChromeAPI['auth']['getUser']; getToken: ChromeAPI['auth']['getToken']; getUserPermissions: ChromeAPI['getUserPermissions']; + isPreview: boolean; }) => { /** * Check if is permitted to see navigation link @@ -81,7 +83,11 @@ const initialize = ({ Object.entries(entitlements || {}).reduce((acc, [key, { is_entitled }]) => ({ ...acc, [key]: is_entitled }), {}); }, isProd: () => isProd(), - isBeta: () => isBeta(), + /** + * @deprecated Should use feature flags instead + * @returns {boolean} + */ + isBeta: () => isPreview, isHidden: () => true, // FIXME: Why always true? withEmail: async (...toHave: string[]) => { const data = await getUser(); @@ -149,4 +155,11 @@ export const getVisibilityFunctions = () => { return visibilityFunctions['*'].get(); }; +export const visibilityFunctionsExist = () => !!getSharedScope()['@chrome/visibilityFunctions']; + +export const updateVisibilityFunctionsBeta = (isPreview: boolean) => { + const visibilityFunctions = getVisibilityFunctions(); + visibilityFunctions.isBeta = () => isPreview; +}; + export const initializeVisibilityFunctions = initialize; diff --git a/src/utils/initUserConfig.ts b/src/utils/initUserConfig.ts new file mode 100644 index 000000000..e9db70611 --- /dev/null +++ b/src/utils/initUserConfig.ts @@ -0,0 +1,39 @@ +import axios from 'axios'; +import { isBeta } from './common'; +import { initializeVisibilityFunctions } from './VisibilitySingleton'; +import createGetUserPermissions from '../auth/createGetUserPermissions'; +import { ChromeUser } from '@redhat-cloud-services/types'; + +export type ChromeUserConfig = { + data: { + uiPreview: boolean; + }; +}; + +export const initChromeUserConfig = async ({ getUser, token }: { getUser: () => Promise; token: string }) => { + const LOCAL_PREVIEW = localStorage.getItem('chrome:local-preview') === 'true'; + let config: ChromeUserConfig; + if (!LOCAL_PREVIEW) { + config = { + data: { + uiPreview: isBeta(), + }, + }; + } else { + const { data } = await axios.get('/api/chrome-service/v1/user', { + params: { + 'skip-identity-cache': 'true', + }, + }); + config = data; + } + + initializeVisibilityFunctions({ + getUser, + getToken: () => Promise.resolve(token), + getUserPermissions: createGetUserPermissions(getUser, () => Promise.resolve(token)), + isPreview: config.data.uiPreview, + }); + + return config; +}; From 38f24b878133e666dcececaed8045f9a2f415c0d Mon Sep 17 00:00:00 2001 From: Martin Marosi Date: Tue, 14 May 2024 14:33:12 +0200 Subject: [PATCH 019/151] Update unleash preview context on preview toggle. --- src/components/FeatureFlags/FeatureFlagsProvider.tsx | 7 ++++--- src/state/atoms/releaseAtom.ts | 5 +++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/components/FeatureFlags/FeatureFlagsProvider.tsx b/src/components/FeatureFlags/FeatureFlagsProvider.tsx index f51a3f2a6..383710144 100644 --- a/src/components/FeatureFlags/FeatureFlagsProvider.tsx +++ b/src/components/FeatureFlags/FeatureFlagsProvider.tsx @@ -3,8 +3,9 @@ import { FlagProvider, IFlagProvider, UnleashClient } from '@unleash/proxy-clien import { DeepRequired } from 'utility-types'; import { captureException } from '@sentry/react'; import * as Sentry from '@sentry/react'; +import { useAtomValue } from 'jotai'; import ChromeAuthContext, { ChromeAuthContextValue } from '../../auth/ChromeAuthContext'; -import { isBeta } from '../../utils/common'; +import { isPreviewAtom } from '../../state/atoms/releaseAtom'; const config: IFlagProvider['config'] = { url: `${document.location.origin}/api/featureflags/v0`, @@ -63,16 +64,16 @@ export const getFeatureFlagsError = () => localStorage.getItem(UNLEASH_ERROR_KEY const FeatureFlagsProvider: React.FC = ({ children }) => { const { user } = useContext(ChromeAuthContext) as DeepRequired; + const isPreview = useAtomValue(isPreviewAtom); unleashClient = useMemo( () => new UnleashClient({ ...config, context: { - // TODO: instead of the isBeta, use the internal chrome state // the unleash context is not generic, look for issue/PR in the unleash repo or create one // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - 'platform.chrome.ui.preview': isBeta(), + 'platform.chrome.ui.preview': isPreview, userId: user?.identity.internal?.account_id, orgId: user?.identity.internal?.org_id, ...(user diff --git a/src/state/atoms/releaseAtom.ts b/src/state/atoms/releaseAtom.ts index c19a48c19..957e62339 100644 --- a/src/state/atoms/releaseAtom.ts +++ b/src/state/atoms/releaseAtom.ts @@ -1,6 +1,7 @@ import axios from 'axios'; import { updateVisibilityFunctionsBeta, visibilityFunctionsExist } from '../../utils/VisibilitySingleton'; import { atomWithToggle } from './utils'; +import { unleashClient } from '../../components/FeatureFlags/FeatureFlagsProvider'; export const isPreviewAtom = atomWithToggle(undefined, (isPreview) => { // Required to change the `isBeta` function return value in the visibility functions @@ -8,4 +9,8 @@ export const isPreviewAtom = atomWithToggle(undefined, (isPreview) => { updateVisibilityFunctionsBeta(isPreview); axios.post('/api/chrome-service/v1/user/update-ui-preview', { uiPreview: isPreview }); } + // Required to change the `platform.chrome.ui.preview` context in the feature flags, TS is bugged + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + unleashClient?.updateContext({ 'platform.chrome.ui.preview': isPreview }); }); From 7b06ef850188c6eed44f251cac1db63f1f7d9bce Mon Sep 17 00:00:00 2001 From: Martin Marosi Date: Tue, 14 May 2024 14:33:48 +0200 Subject: [PATCH 020/151] Use internal preview state where ever possible. --- src/analytics/SegmentProvider.tsx | 24 ++++--- src/analytics/analytics.test.ts | 6 +- src/analytics/index.ts | 15 ++-- src/auth/OIDCConnector/OIDCProvider.tsx | 4 +- src/auth/OIDCConnector/OIDCSecured.tsx | 7 -- src/auth/OIDCConnector/utils.ts | 1 + src/chrome/create-chrome.test.ts | 2 + src/chrome/create-chrome.ts | 6 +- .../AllServicesDropdown/AllServicesTabs.tsx | 6 +- src/components/AppFilter/useAppFilter.ts | 19 +++-- src/components/ChromeRoute/ChromeRoute.tsx | 4 +- src/components/Feedback/FeedbackModal.tsx | 10 ++- src/components/Header/HeaderAlert.tsx | 3 + .../Header/HeaderTests/Tools.test.js | 28 +++++++- src/components/Header/PreviewAlert.tsx | 61 ++++++++++++++++ src/components/Header/SettingsToggle.tsx | 6 +- src/components/Header/ToolbarToggle.tsx | 6 +- src/components/Header/Tools.tsx | 70 ++++++++----------- src/components/Navigation/ChromeNavItem.tsx | 6 +- src/components/Navigation/index.tsx | 8 ++- src/components/RootApp/ScalprumRoot.test.js | 12 +++- src/components/RootApp/ScalprumRoot.tsx | 13 ++-- src/hooks/useAllLinks.ts | 9 ++- src/hooks/useFavoritedServices.ts | 5 +- src/index.ts | 5 ++ src/layouts/DefaultLayout.test.js | 8 +++ src/state/atoms/scalprumConfigAtom.ts | 9 ++- src/utils/cache.ts | 2 + src/utils/common.ts | 13 +++- src/utils/createCase.ts | 20 +++--- src/utils/fetchNavigationFiles.ts | 16 +++-- src/utils/useNavigation.ts | 12 +++- src/utils/usePreviewFlag.ts | 12 ---- 33 files changed, 289 insertions(+), 139 deletions(-) create mode 100644 src/components/Header/PreviewAlert.tsx delete mode 100644 src/utils/usePreviewFlag.ts diff --git a/src/analytics/SegmentProvider.tsx b/src/analytics/SegmentProvider.tsx index 244083b1e..93e7b0d45 100644 --- a/src/analytics/SegmentProvider.tsx +++ b/src/analytics/SegmentProvider.tsx @@ -1,7 +1,7 @@ import React, { useContext, useEffect, useRef } from 'react'; import { AnalyticsBrowser } from '@segment/analytics-next'; import Cookie from 'js-cookie'; -import { ITLess, isBeta, isProd } from '../utils/common'; +import { ITLess, isProd } from '../utils/common'; import { ChromeUser } from '@redhat-cloud-services/types'; import { useLocation } from 'react-router-dom'; import axios from 'axios'; @@ -11,6 +11,7 @@ import { getUrl } from '../hooks/useBundle'; import ChromeAuthContext from '../auth/ChromeAuthContext'; import { useAtomValue } from 'jotai'; import { activeModuleAtom, activeModuleDefinitionReadAtom } from '../state/atoms/activeModuleAtom'; +import { isPreviewAtom } from '../state/atoms/releaseAtom'; type SegmentEnvs = 'dev' | 'prod'; type SegmentModules = 'acs' | 'openshift' | 'hacCore'; @@ -31,7 +32,7 @@ function getAdobeVisitorId() { return -1; } -const getPageEventOptions = () => { +const getPageEventOptions = (isPreview: boolean) => { const path = window.location.pathname.replace(/^\/(beta\/|preview\/|beta$|preview$)/, '/'); const search = new URLSearchParams(window.location.search); @@ -46,7 +47,7 @@ const getPageEventOptions = () => { { path, url: `${window.location.origin}${path}${window.location.search}`, - isBeta: isBeta(), + isBeta: isPreview, module: window._segment?.activeModule, // Marketing campaing tracking ...trackingContext, @@ -77,7 +78,7 @@ const getAPIKey = (env: SegmentEnvs = 'dev', module: SegmentModules, moduleAPIKe KEY_FALLBACK[env]; let observer: MutationObserver | undefined; -const registerAnalyticsObserver = () => { +const registerAnalyticsObserver = (isPreview: boolean) => { // never override the observer if (observer) { return; @@ -96,7 +97,7 @@ const registerAnalyticsObserver = () => { oldHref = newLocation; window?.sendCustomEvent?.('pageBottom'); setTimeout(() => { - window.segment?.page(...getPageEventOptions()); + window.segment?.page(...getPageEventOptions(isPreview)); }); } }); @@ -113,7 +114,7 @@ const emailDomain = (email = '') => (/@/g.test(email) ? email.split('@')[1].toLo const getPagePathSegment = (pathname: string, n: number) => pathname.split('/')[n] || ''; -const getIdentityTraits = (user: ChromeUser, pathname: string, activeModule = '') => { +const getIdentityTraits = (user: ChromeUser, pathname: string, activeModule = '', isPreview: boolean) => { const entitlements = Object.entries(user.entitlements).reduce( (acc, [key, entitlement]) => ({ ...acc, @@ -132,7 +133,7 @@ const getIdentityTraits = (user: ChromeUser, pathname: string, activeModule = '' isOrgAdmin: user.identity.user?.is_org_admin, currentBundle: getUrl('bundle'), currentApp: activeModule, - isBeta: isBeta(), + isBeta: isPreview, ...(user.identity.user ? { name: `${user.identity.user.first_name} ${user.identity.user.last_name}`, @@ -158,6 +159,7 @@ const SegmentProvider: React.FC = ({ children }) => { const analytics = useRef(); const analyticsLoaded = useRef(false); const { user } = useContext(ChromeAuthContext); + const isPreview = useAtomValue(isPreviewAtom); const activeModule = useAtomValue(activeModuleAtom); const activeModuleDefinition = useAtomValue(activeModuleDefinitionReadAtom); @@ -198,7 +200,7 @@ const SegmentProvider: React.FC = ({ children }) => { activeModule, }; const newKey = getAPIKey(DEV_ENV ? 'dev' : 'prod', activeModule as SegmentModules, moduleAPIKey); - const identityTraits = getIdentityTraits(user, pathname, activeModule); + const identityTraits = getIdentityTraits(user, pathname, activeModule, isPreview); const identityOptions = { context: { groupId: user.identity.internal?.org_id, @@ -226,7 +228,7 @@ const SegmentProvider: React.FC = ({ children }) => { }, }); analytics.current.group(user.identity.internal?.org_id, groupTraits); - analytics.current.page(...getPageEventOptions()); + analytics.current.page(...getPageEventOptions(isPreview)); initialized.current = true; // eslint-disable-next-line @typescript-eslint/ban-ts-comment //@ts-ignore TS does not allow accessing the instance settings but its necessary for us to not create instances if we don't have to @@ -259,12 +261,12 @@ const SegmentProvider: React.FC = ({ children }) => { }; useEffect(() => { - registerAnalyticsObserver(); + registerAnalyticsObserver(isPreview); return () => { observer?.disconnect(); observer = undefined; }; - }, []); + }, [isPreview]); useEffect(() => { handleModuleUpdate(); diff --git a/src/analytics/analytics.test.ts b/src/analytics/analytics.test.ts index 8e886e64d..4b6f4e092 100644 --- a/src/analytics/analytics.test.ts +++ b/src/analytics/analytics.test.ts @@ -33,7 +33,7 @@ function buildUser(token: any): DeepRequired { describe('User + Analytics', () => { describe('buildUser + getPendoConf internal', () => { test('should build a valid internal Pendo config', () => { - const conf = getPendoConf(buildUser(token)); + const conf = getPendoConf(buildUser(token), false); expect(conf).toMatchObject({ account: { id: '540155', @@ -47,7 +47,7 @@ describe('User + Analytics', () => { }); test('should build a valid external Pendo config', () => { - const conf = getPendoConf(buildUser(externalToken)); + const conf = getPendoConf(buildUser(externalToken), false); expect(conf).toMatchObject({ account: { id: '540155', @@ -61,7 +61,7 @@ describe('User + Analytics', () => { }); test('should build a valid IBM pendo config', () => { - const conf = getPendoConf(buildUser(ibmToken)); + const conf = getPendoConf(buildUser(ibmToken), false); expect(conf).toMatchObject({ account: { id: '540155', diff --git a/src/analytics/index.ts b/src/analytics/index.ts index 3c7f599c3..cbd78a516 100644 --- a/src/analytics/index.ts +++ b/src/analytics/index.ts @@ -1,4 +1,4 @@ -import { isBeta, isProd } from '../utils/common'; +import { isProd } from '../utils/common'; import { ChromeUser } from '@redhat-cloud-services/types'; import { DeepRequired } from 'utility-types'; @@ -14,23 +14,22 @@ function isInternalFlag(email: string, isInternal = false) { return ''; } -function getUrl(type?: string) { +function getUrl(type?: string, isPreview = false) { if (['/beta', '/preview', '/'].includes(window.location.pathname)) { return 'landing'; } const sections = window.location.pathname.split('/').slice(1); - const isBetaEnv = isBeta(); if (type) { - if (isBetaEnv) { + if (isPreview) { return type === 'bundle' ? sections[1] : sections[2]; } return type === 'bundle' ? sections[0] : sections[1]; } - isBetaEnv && sections.shift(); - return [isBetaEnv, ...sections]; + isPreview && sections.shift(); + return [isPreview, ...sections]; } function getAdobeVisitorId() { @@ -42,7 +41,7 @@ function getAdobeVisitorId() { return -1; } -export function getPendoConf(data: DeepRequired) { +export function getPendoConf(data: DeepRequired, isPreview: boolean) { const userID = `${data.identity.internal.account_id}${isInternalFlag(data.identity.user.email, data.identity.user.is_internal)}`; const entitlements: Record = {}; @@ -53,7 +52,7 @@ export function getPendoConf(data: DeepRequired) { entitlements[`entitlements_${key}_trial`] = value.is_trial; }); - const [isBeta, currentBundle, currentApp, ...rest] = getUrl(); + const [isBeta, currentBundle, currentApp, ...rest] = getUrl(undefined, isPreview); return { visitor: { diff --git a/src/auth/OIDCConnector/OIDCProvider.tsx b/src/auth/OIDCConnector/OIDCProvider.tsx index 8b8e63d5f..bdac4000c 100644 --- a/src/auth/OIDCConnector/OIDCProvider.tsx +++ b/src/auth/OIDCConnector/OIDCProvider.tsx @@ -8,7 +8,9 @@ import AppPlaceholder from '../../components/AppPlaceholder'; import { postbackUrlSetup } from '../offline'; import OIDCStateReloader from './OIDCStateReloader'; -const betaPartial = isBeta() ? '/beta' : ''; +const LOCAL_PREVIEW = localStorage.getItem('chrome:local-preview') === 'true'; +// TODO: remove this once the local preview is enabled by default +const betaPartial = LOCAL_PREVIEW ? '' : isBeta() ? '/beta' : ''; const OIDCProvider: React.FC = ({ children }) => { const [cookieElement, setCookieElement] = useState(null); diff --git a/src/auth/OIDCConnector/OIDCSecured.tsx b/src/auth/OIDCConnector/OIDCSecured.tsx index 6bee06fa3..ef89cfba8 100644 --- a/src/auth/OIDCConnector/OIDCSecured.tsx +++ b/src/auth/OIDCConnector/OIDCSecured.tsx @@ -8,13 +8,11 @@ import { generateRoutesList } from '../../utils/common'; import getInitialScope from '../getInitialScope'; import { init } from '../../utils/iqeEnablement'; import entitlementsApi from '../entitlementsApi'; -import { initializeVisibilityFunctions } from '../../utils/VisibilitySingleton'; import sentry from '../../utils/sentry'; import AppPlaceholder from '../../components/AppPlaceholder'; import { FooterProps } from '../../components/Footer/Footer'; import logger from '../logger'; import { login, logout } from './utils'; -import createGetUserPermissions from '../createGetUserPermissions'; import initializeAccessRequestCookies from '../initializeAccessRequestCookies'; import { getOfflineToken, prepareOfflineRedirect } from '../offline'; import { OFFLINE_REDIRECT_STORAGE_KEY } from '../../utils/consts'; @@ -151,11 +149,6 @@ export function OIDCSecured({ const entitlements = await fetchEntitlements(user); const chromeUser = mapOIDCUserToChromeUser(user, entitlements); const getUser = () => Promise.resolve(chromeUser); - initializeVisibilityFunctions({ - getUser, - getToken: () => Promise.resolve(user.access_token), - getUserPermissions: createGetUserPermissions(getUser, () => Promise.resolve(user.access_token)), - }); setState((prev) => ({ ...prev, ready: true, diff --git a/src/auth/OIDCConnector/utils.ts b/src/auth/OIDCConnector/utils.ts index 2adb905e8..8a319fc12 100644 --- a/src/auth/OIDCConnector/utils.ts +++ b/src/auth/OIDCConnector/utils.ts @@ -44,6 +44,7 @@ export async function logout(auth: AuthContextProps, bounce?: boolean) { key.startsWith(GLOBAL_FILTER_KEY) ); deleteLocalStorageItems([...keys, OFFLINE_REDIRECT_STORAGE_KEY, LOGIN_SCOPES_STORAGE_KEY]); + // FIXME: Remove this one local preview is enabled by default const pathname = isBeta() ? getRouterBasename() : ''; if (bounce) { const eightSeconds = new Date(new Date().getTime() + 8 * 1000); diff --git a/src/chrome/create-chrome.test.ts b/src/chrome/create-chrome.test.ts index 82b7fee65..0f07ad858 100644 --- a/src/chrome/create-chrome.test.ts +++ b/src/chrome/create-chrome.test.ts @@ -104,6 +104,7 @@ describe('create chrome', () => { enableTopics: jest.fn(), setActiveTopic: jest.fn(), }, + isPreview: false, quickstartsAPI: { Catalog: QuickStartCatalog, set() { @@ -126,6 +127,7 @@ describe('create chrome', () => { getUser: () => Promise.resolve(mockUser), getToken: () => Promise.resolve('mocked-token'), getUserPermissions: () => Promise.resolve([]), + isPreview: false, }; initializeVisibilityFunctions(mockAuthMethods); }); diff --git a/src/chrome/create-chrome.ts b/src/chrome/create-chrome.ts index 137624c04..441281adf 100644 --- a/src/chrome/create-chrome.ts +++ b/src/chrome/create-chrome.ts @@ -16,7 +16,7 @@ import { toggleDebuggerModal, toggleGlobalFilter, } from '../redux/actions'; -import { ITLess, getEnv, getEnvDetails, isBeta, isProd, updateDocumentTitle } from '../utils/common'; +import { ITLess, getEnv, getEnvDetails, isProd, updateDocumentTitle } from '../utils/common'; import { createSupportCase } from '../utils/createCase'; import debugFunctions from '../utils/debugFunctions'; import { flatTags } from '../components/GlobalFilter/globalFilterApi'; @@ -47,6 +47,7 @@ export type CreateChromeContextConfig = { helpTopics: ChromeAPI['helpTopics']; chromeAuth: ChromeAuthContextValue; registerModule: (payload: RegisterModulePayload) => void; + isPreview: boolean; }; export const createChromeContext = ({ @@ -58,6 +59,7 @@ export const createChromeContext = ({ helpTopics, registerModule, chromeAuth, + isPreview, }: CreateChromeContextConfig): ChromeAPI => { const fetchPermissions = createFetchPermissionsWatcher(chromeAuth.getUser); const visibilityFunctions = getVisibilityFunctions(); @@ -154,7 +156,7 @@ export const createChromeContext = ({ } dispatch(toggleGlobalFilter(isHidden)); }, - isBeta, + isBeta: () => isPreview, isChrome2: true, enable: debugFunctions, isDemo: () => Boolean(Cookies.get('cs_demo')), diff --git a/src/components/AllServicesDropdown/AllServicesTabs.tsx b/src/components/AllServicesDropdown/AllServicesTabs.tsx index 66fbf1a0b..e26bb3cfc 100644 --- a/src/components/AllServicesDropdown/AllServicesTabs.tsx +++ b/src/components/AllServicesDropdown/AllServicesTabs.tsx @@ -1,4 +1,5 @@ import React, { useEffect, useRef } from 'react'; +import { useAtomValue } from 'jotai'; import { Icon } from '@patternfly/react-core/dist/dynamic/components/Icon'; import { Tab, TabProps, TabTitleText, Tabs, TabsProps } from '@patternfly/react-core/dist/dynamic/components/Tabs'; @@ -6,12 +7,12 @@ import StarIcon from '@patternfly/react-icons/dist/dynamic/icons/star-icon'; import { FAVORITE_TAB_ID, TAB_CONTENT_ID } from './common'; import type { AllServicesSection as AllServicesSectionType } from '../AllServices/allServicesLinks'; -import { isBeta } from '../../utils/common'; import { Divider } from '@patternfly/react-core/dist/dynamic/components/Divider'; import { Text, TextVariants } from '@patternfly/react-core/dist/dynamic/components/Text'; import ChromeLink from '../ChromeLink'; import './AllServicesTabs.scss'; import PlatformServiceslinks from './PlatformServicesLinks'; +import { isPreviewAtom } from '../../state/atoms/releaseAtom'; export type AllServicesTabsProps = { activeTabKey: string | number; @@ -27,6 +28,7 @@ export type AllServicesTabsProps = { type TabWrapper = Omit; const TabWrapper = (props: TabWrapper) => { + const isPreview = useAtomValue(isPreviewAtom); const tabRef = useRef(null); const hoverTimer = useRef(undefined); const stopHoverEffect = () => { @@ -40,7 +42,7 @@ const TabWrapper = (props: TabWrapper) => { const timeout = setTimeout(() => { // should be available only in preview // use refs to supply the required tab events - isBeta() && tabRef.current?.click(); + isPreview && tabRef.current?.click(); }, 300); hoverTimer.current = timeout; }; diff --git a/src/components/AppFilter/useAppFilter.ts b/src/components/AppFilter/useAppFilter.ts index 75defc33e..ae894ed76 100644 --- a/src/components/AppFilter/useAppFilter.ts +++ b/src/components/AppFilter/useAppFilter.ts @@ -3,10 +3,13 @@ import { useEffect, useState } from 'react'; import { useSelector } from 'react-redux'; import { BundleNavigation, ChromeModule, NavItem } from '../../@types/types'; import { ReduxState } from '../../redux/store'; -import { getChromeStaticPathname, isBeta, isProd } from '../../utils/common'; +import { getChromeStaticPathname } from '../../utils/common'; import { evaluateVisibility } from '../../utils/isNavItemVisible'; import { useAtomValue } from 'jotai'; import { chromeModulesAtom } from '../../state/atoms/chromeModuleAtom'; +import { isPreviewAtom } from '../../state/atoms/releaseAtom'; + +const LOCAL_PREVIEW = localStorage.getItem('chrome:local-preview') === 'true'; export type AppFilterBucket = { id: string; @@ -14,8 +17,6 @@ export type AppFilterBucket = { links: NavItem[]; }; -const previewBundles = ['']; - export const requiredBundles = [ 'application-services', 'openshift', @@ -26,8 +27,7 @@ export const requiredBundles = [ 'iam', 'quay', 'subscriptions', - ...(!isProd() ? previewBundles : isBeta() ? previewBundles : []), -].filter(Boolean); +]; export const itLessBundles = ['openshift', 'insights', 'settings', 'iam']; @@ -92,7 +92,7 @@ type AppFilterState = { }; const useAppFilter = () => { - const isBetaEnv = isBeta(); + const isPreview = useAtomValue(isPreviewAtom); const [state, setState] = useState({ isLoaded: false, isLoading: false, @@ -193,7 +193,12 @@ const useAppFilter = () => { axios .get(`${getChromeStaticPathname('navigation')}/${fragment}-navigation.json?ts=${Date.now()}`) // fallback static CSC for EE env - .catch(() => axios.get(`${isBetaEnv ? '/beta' : ''}/config/chrome/${fragment}-navigation.json?ts=${Date.now()}`)) + .catch(() => { + // FIXME: Remove this once local preview is enabled by default + // No /beta will be needed in the future + const previewFragment = LOCAL_PREVIEW ? '' : isPreview ? '/beta' : ''; + return axios.get(`${previewFragment}/config/chrome/${fragment}-navigation.json?ts=${Date.now()}`); + }) .then(handleBundleData) .then(() => Object.values(existingSchemas).map((data) => handleBundleData({ data } as { data: BundleNavigation }))) .catch((err) => { diff --git a/src/components/ChromeRoute/ChromeRoute.tsx b/src/components/ChromeRoute/ChromeRoute.tsx index 8117eec43..8c4903a4d 100644 --- a/src/components/ChromeRoute/ChromeRoute.tsx +++ b/src/components/ChromeRoute/ChromeRoute.tsx @@ -14,6 +14,7 @@ import ChromeAuthContext from '../../auth/ChromeAuthContext'; import { useAtomValue, useSetAtom } from 'jotai'; import { activeModuleAtom } from '../../state/atoms/activeModuleAtom'; import { gatewayErrorAtom } from '../../state/atoms/gatewayErrorAtom'; +import { isPreviewAtom } from '../../state/atoms/releaseAtom'; export type ChromeRouteProps = { scope: string; @@ -27,6 +28,7 @@ export type ChromeRouteProps = { // eslint-disable-next-line react/display-name const ChromeRoute = memo( ({ scope, module, scopeClass, path, props }: ChromeRouteProps) => { + const isPreview = useAtomValue(isPreviewAtom); const dispatch = useDispatch(); const { setActiveHelpTopicByName } = useContext(HelpTopicContext); const { user } = useContext(ChromeAuthContext); @@ -45,7 +47,7 @@ const ChromeRoute = memo( */ if (window.pendo) { try { - window.pendo.updateOptions(getPendoConf(user as DeepRequired)); + window.pendo.updateOptions(getPendoConf(user as DeepRequired, isPreview)); } catch (error) { console.error('Unable to update pendo options'); console.error(error); diff --git a/src/components/Feedback/FeedbackModal.tsx b/src/components/Feedback/FeedbackModal.tsx index e87b043d5..4a683d77b 100644 --- a/src/components/Feedback/FeedbackModal.tsx +++ b/src/components/Feedback/FeedbackModal.tsx @@ -1,4 +1,5 @@ import React, { memo, useContext, useState } from 'react'; +import { useAtomValue } from 'jotai'; import { Button } from '@patternfly/react-core/dist/dynamic/components/Button'; import { Card, CardBody, CardTitle } from '@patternfly/react-core/dist/dynamic/components/Card'; import { FlexItem } from '@patternfly/react-core/dist/dynamic/layouts/Flex'; @@ -26,6 +27,7 @@ import { createSupportCase } from '../../utils/createCase'; import './Feedback.scss'; import ChromeAuthContext from '../../auth/ChromeAuthContext'; import { useSegment } from '../../analytics/useSegment'; +import { isPreviewAtom } from '../../state/atoms/releaseAtom'; const FEEDBACK_OPEN_EVENT = 'chrome.feedback.open'; @@ -54,6 +56,7 @@ const FeedbackModal = memo(() => { setIsModalOpen(false); setModalPage('feedbackHome'); }; + const isPreview = useAtomValue(isPreviewAtom); const ModalDescription = ({ modalPage }: { modalPage: FeedbackPages }) => { switch (modalPage) { @@ -73,7 +76,12 @@ const FeedbackModal = memo(() => { {intl.formatMessage(messages.reportABug)} {intl.formatMessage(messages.describeBugUrgentCases)} - createSupportCase(user.identity, chromeAuth.token)}> + createSupportCase(user.identity, chromeAuth.token, isPreview)} + > {intl.formatMessage(messages.openSupportCase)} diff --git a/src/components/Header/HeaderAlert.tsx b/src/components/Header/HeaderAlert.tsx index 5b01de6fd..a26e14fbe 100644 --- a/src/components/Header/HeaderAlert.tsx +++ b/src/components/Header/HeaderAlert.tsx @@ -45,6 +45,9 @@ const HeaderAlert = ({ const onClose = () => { onDismiss && onDismiss(); setAlertVisible(false); + if (timer) { + clearTimeout(timer); + } }; return ( diff --git a/src/components/Header/HeaderTests/Tools.test.js b/src/components/Header/HeaderTests/Tools.test.js index 414dc2734..051207a1c 100644 --- a/src/components/Header/HeaderTests/Tools.test.js +++ b/src/components/Header/HeaderTests/Tools.test.js @@ -7,6 +7,13 @@ import { MemoryRouter } from 'react-router-dom'; jest.mock('../UserToggle', () => () => ''); jest.mock('../ToolbarToggle', () => () => ''); +jest.mock('../../../state/atoms/releaseAtom', () => { + const util = jest.requireActual('../../../state/atoms/utils'); + return { + __esModule: true, + isPreviewAtom: util.atomWithToggle(false), + }; +}); jest.mock('@unleash/proxy-client-react', () => { const proxyClient = jest.requireActual('@unleash/proxy-client-react'); @@ -20,6 +27,13 @@ jest.mock('@unleash/proxy-client-react', () => { }); describe('Tools', () => { + let assignMock = jest.fn(); + + delete window.location; + window.location = { assign: assignMock, href: '', pathname: '' }; + afterEach(() => { + assignMock.mockClear(); + }); it('should render correctly', async () => { const mockClick = jest.fn(); let container; @@ -36,8 +50,16 @@ describe('Tools', () => { }); it('should switch release correctly', () => { - expect(switchRelease(true, '/beta/settings/rbac')).toEqual(`/settings/rbac`); - expect(switchRelease(true, '/preview/settings/rbac')).toEqual(`/settings/rbac`); - expect(switchRelease(false, '/settings/rbac')).toEqual(`/beta/settings/rbac`); + const cases = [ + ['/beta/settings/rbac', '/settings/rbac'], + ['/preview/settings/rbac', '/settings/rbac'], + ['/settings/rbac', '/settings/rbac'], + ]; + + cases.forEach(([input, expected]) => { + window.location.href = ''; + switchRelease(true, input); + expect(window.location.href).toEqual(expected); + }); }); }); diff --git a/src/components/Header/PreviewAlert.tsx b/src/components/Header/PreviewAlert.tsx new file mode 100644 index 000000000..3e86d91cb --- /dev/null +++ b/src/components/Header/PreviewAlert.tsx @@ -0,0 +1,61 @@ +import React, { useState } from 'react'; +import cookie from 'js-cookie'; +import HeaderAlert from './HeaderAlert'; +import { useAtom } from 'jotai'; +import { isPreviewAtom } from '../../state/atoms/releaseAtom'; +import { isBeta } from '../../utils/common'; +import { AlertActionLink, AlertVariant } from '@patternfly/react-core/dist/dynamic/components/Alert'; +import { useLocation } from 'react-router-dom'; +import { useFlag } from '@unleash/proxy-client-react'; + +const LOCAL_PREVIEW = localStorage.getItem('chrome:local-preview') === 'true'; + +const PreviewAlert = ({ switchRelease }: { switchRelease: (isBeta: boolean, pathname: string, previewEnabled: boolean) => void }) => { + const [isPreview, togglePreview] = useAtom(isPreviewAtom); + const [prevPreviewValue, setPrevPreviewValue] = useState(isPreview); + const location = useLocation(); + const previewEnabled = useFlag('platform.chrome.preview'); + + // FIXME: Remove the cookie check once the local preview is enabled by default + const shouldRenderAlert = LOCAL_PREVIEW ? isPreview !== prevPreviewValue : cookie.get('cs_toggledRelease') === 'true'; + const isPreviewEnabled = LOCAL_PREVIEW ? isPreview : isBeta(); + + function handlePreviewToggle() { + if (!LOCAL_PREVIEW) { + switchRelease(isPreviewEnabled, location.pathname, previewEnabled); + } + togglePreview(); + } + + return shouldRenderAlert ? ( + + + Learn more + + { + handlePreviewToggle(); + }} + >{`${isPreviewEnabled ? 'Disable' : 'Enable'} preview`} + + } + onDismiss={() => { + cookie.set('cs_toggledRelease', 'false'); + setPrevPreviewValue(isPreview); + }} + /> + ) : null; +}; + +export default PreviewAlert; diff --git a/src/components/Header/SettingsToggle.tsx b/src/components/Header/SettingsToggle.tsx index fbb08000d..337559361 100644 --- a/src/components/Header/SettingsToggle.tsx +++ b/src/components/Header/SettingsToggle.tsx @@ -1,11 +1,12 @@ import React, { useState } from 'react'; +import { useAtomValue } from 'jotai'; import { Dropdown, DropdownGroup, DropdownItem, DropdownList } from '@patternfly/react-core/dist/dynamic/components/Dropdown'; import { Divider } from '@patternfly/react-core/dist/dynamic/components/Divider'; import { MenuToggle } from '@patternfly/react-core/dist/dynamic/components/MenuToggle'; import { PopoverPosition } from '@patternfly/react-core/dist/dynamic/components/Popover'; import ChromeLink from '../ChromeLink/ChromeLink'; -import { isBeta } from '../../utils/common'; +import { isPreviewAtom } from '../../state/atoms/releaseAtom'; export type SettingsToggleDropdownGroup = { title: string; @@ -35,6 +36,7 @@ export type SettingsToggleProps = { const SettingsToggle = (props: SettingsToggleProps) => { const [isOpen, setIsOpen] = useState(false); + const isPreview = useAtomValue(isPreviewAtom); const dropdownItems = props.dropdownItems.map(({ title, items }, groupIndex) => ( @@ -45,7 +47,7 @@ const SettingsToggle = (props: SettingsToggleProps) => { ouiaId={title} isDisabled={isDisabled} component={({ className: itemClassName }) => ( - + {title} )} diff --git a/src/components/Header/ToolbarToggle.tsx b/src/components/Header/ToolbarToggle.tsx index bcf4fcc1d..d13670dad 100644 --- a/src/components/Header/ToolbarToggle.tsx +++ b/src/components/Header/ToolbarToggle.tsx @@ -1,10 +1,11 @@ import React, { useState } from 'react'; +import { useAtomValue } from 'jotai'; import { Dropdown, DropdownItem, DropdownList } from '@patternfly/react-core/dist/dynamic/components/Dropdown'; import { MenuToggle } from '@patternfly/react-core/dist/dynamic/components/MenuToggle'; import { PopoverPosition } from '@patternfly/react-core/dist/dynamic/components/Popover'; import ChromeLink from '../ChromeLink/ChromeLink'; -import { isBeta } from '../../utils/common'; +import { isPreviewAtom } from '../../state/atoms/releaseAtom'; export type ToolbarToggleDropdownItem = { url?: string; @@ -31,6 +32,7 @@ export type ToolbarToggleProps = { const ToolbarToggle = (props: ToolbarToggleProps) => { const [isOpen, setIsOpen] = useState(false); + const isPreview = useAtomValue(isPreviewAtom); const onSelect = () => { setIsOpen((prev) => !prev); @@ -64,7 +66,7 @@ const ToolbarToggle = (props: ToolbarToggleProps) => { component={ appId && url ? ({ className: itemClassName }) => ( - + {title} ) diff --git a/src/components/Header/Tools.tsx b/src/components/Header/Tools.tsx index 298d5b667..a567b33e8 100644 --- a/src/components/Header/Tools.tsx +++ b/src/components/Header/Tools.tsx @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/ban-ts-comment */ import React, { memo, useContext, useEffect, useState } from 'react'; import { useLocation } from 'react-router-dom'; -import { AlertActionLink, AlertVariant } from '@patternfly/react-core/dist/dynamic/components/Alert'; +import { useAtom, useAtomValue } from 'jotai'; import { Button } from '@patternfly/react-core/dist/dynamic/components/Button'; import { Divider } from '@patternfly/react-core/dist/dynamic/components/Divider'; import { DropdownItem } from '@patternfly/react-core/dist/dynamic/components/Dropdown'; @@ -15,9 +15,8 @@ import RedhatIcon from '@patternfly/react-icons/dist/dynamic/icons/redhat-icon'; import UserToggle from './UserToggle'; import ToolbarToggle, { ToolbarToggleDropdownItem } from './ToolbarToggle'; import SettingsToggle, { SettingsToggleDropdownGroup } from './SettingsToggle'; -import HeaderAlert from './HeaderAlert'; import cookie from 'js-cookie'; -import { ITLess, getRouterBasename, getSection, isBeta } from '../../utils/common'; +import { ITLess, getRouterBasename, getSection } from '../../utils/common'; import { useIntl } from 'react-intl'; import { useFlag } from '@unleash/proxy-client-react'; import messages from '../../locales/Messages'; @@ -26,22 +25,27 @@ import BellIcon from '@patternfly/react-icons/dist/dynamic/icons/bell-icon'; import useWindowWidth from '../../hooks/useWindowWidth'; import ChromeAuthContext from '../../auth/ChromeAuthContext'; import { isPreviewAtom } from '../../state/atoms/releaseAtom'; -import chromeStore from '../../state/chromeStore'; -import { useAtom, useAtomValue } from 'jotai'; import { notificationDrawerExpandedAtom, unreadNotificationsAtom } from '../../state/atoms/notificationDrawerAtom'; +import PreviewAlert from './PreviewAlert'; + +const LOCAL_PREVIEW = localStorage.getItem('chrome:local-preview') === 'true'; const isITLessEnv = ITLess(); +/** + * @deprecated Switch release will be replaces by the internal chrome state variable + */ export const switchRelease = (isBeta: boolean, pathname: string, previewEnabled: boolean) => { cookie.set('cs_toggledRelease', 'true'); const previewFragment = getRouterBasename(pathname); - chromeStore.set(isPreviewAtom, !isBeta); + let href = ''; if (isBeta) { - return pathname.replace(previewFragment.includes('beta') ? /\/beta/ : /\/preview/, ''); + href = pathname.replace(previewFragment.includes('beta') ? /\/beta/ : /\/preview/, ''); } else { - return previewEnabled ? `/preview${pathname}` : `/beta${pathname}`; + href = previewEnabled ? `/preview${pathname}` : `/beta${pathname}`; } + window.location.href = href; }; const InternalButton = () => ( @@ -103,6 +107,7 @@ const Tools = () => { isRhosakEntitled: false, isDemoAcc: false, }); + const [isPreview, setIsPreview] = useAtom(isPreviewAtom); const enableIntegrations = useFlag('platform.sources.integrations'); const { xs } = useWindowWidth(); const { user, token } = useContext(ChromeAuthContext); @@ -112,7 +117,7 @@ const Tools = () => { const location = useLocation(); const settingsPath = isITLessEnv ? `/settings/my-user-access` : enableIntegrations ? `/settings/integrations` : '/settings/sources'; const identityAndAccessManagmentPath = '/iam/user-access/users'; - const betaSwitcherTitle = `${isBeta() ? intl.formatMessage(messages.stopUsing) : intl.formatMessage(messages.use)} ${intl.formatMessage( + const betaSwitcherTitle = `${isPreview ? intl.formatMessage(messages.stopUsing) : intl.formatMessage(messages.use)} ${intl.formatMessage( messages.betaRelease )}`; @@ -203,7 +208,7 @@ const Tools = () => { }, { title: intl.formatMessage(messages.openSupportCase), - onClick: () => createSupportCase(user.identity, token), + onClick: () => createSupportCase(user.identity, token, isPreview), isDisabled: window.location.href.includes('/application-services') && !isRhosakEntitled, isHidden: isITLessEnv, }, @@ -239,7 +244,12 @@ const Tools = () => { }, { title: betaSwitcherTitle, - onClick: () => (window.location.href = switchRelease(isBeta(), location.pathname, previewEnabled)), + onClick: () => { + if (!LOCAL_PREVIEW) { + switchRelease(isPreview, location.pathname, previewEnabled); + } + setIsPreview(); + }, }, { title: 'separator' }, ...aboutMenuDropdownItems, @@ -268,8 +278,13 @@ const Tools = () => { label="Preview on" labelOff="Preview off" aria-label="Preview switcher" - isChecked={isBeta()} - onChange={() => (window.location.href = switchRelease(isBeta(), location.pathname, previewEnabled))} + isChecked={isPreview} + onChange={() => { + if (!LOCAL_PREVIEW) { + switchRelease(isPreview, location.pathname, previewEnabled); + } + setIsPreview(); + }} isReversed className="chr-c-beta-switcher" /> @@ -377,34 +392,7 @@ const Tools = () => { /> - {cookie.get('cs_toggledRelease') === 'true' ? ( - - - Learn more - - { - window.location.href = switchRelease(isBeta(), location.pathname, previewEnabled); - }} - > - {`${isBeta() ? 'Disable' : 'Enable'} preview`} - - - } - onDismiss={() => cookie.set('cs_toggledRelease', 'false')} - /> - ) : null} + ); }; diff --git a/src/components/Navigation/ChromeNavItem.tsx b/src/components/Navigation/ChromeNavItem.tsx index 7ee8d9f6f..4a351b176 100644 --- a/src/components/Navigation/ChromeNavItem.tsx +++ b/src/components/Navigation/ChromeNavItem.tsx @@ -9,13 +9,14 @@ import StarIcon from '@patternfly/react-icons/dist/dynamic/icons/star-icon'; import { titleCase } from 'title-case'; import classNames from 'classnames'; import get from 'lodash/get'; +import { useAtomValue } from 'jotai'; -import { isBeta } from '../../utils/common'; import ChromeLink, { LinkWrapperProps } from '../ChromeLink/ChromeLink'; import { useDispatch, useSelector } from 'react-redux'; import { markActiveProduct } from '../../redux/actions'; import { ChromeNavItemProps } from '../../@types/types'; import useFavoritePagesWrapper from '../../hooks/useFavoritePagesWrapper'; +import { isPreviewAtom } from '../../state/atoms/releaseAtom'; const ChromeNavItem = ({ appId, @@ -30,6 +31,7 @@ const ChromeNavItem = ({ product, notifier = '', }: ChromeNavItemProps) => { + const isPreview = useAtomValue(isPreviewAtom); const hasNotifier = useSelector((state) => get(state, notifier)); const dispatch = useDispatch(); const { favoritePages } = useFavoritePagesWrapper(); @@ -62,7 +64,7 @@ const ChromeNavItem = ({ )} - {isBetaEnv && !isBeta() && !isExternal && ( + {isBetaEnv && !isPreview && !isExternal && ( This service is a Preview.}> diff --git a/src/components/Navigation/index.tsx b/src/components/Navigation/index.tsx index 71605853b..c84e0ddf9 100644 --- a/src/components/Navigation/index.tsx +++ b/src/components/Navigation/index.tsx @@ -1,22 +1,24 @@ import React, { Fragment, useRef, useState } from 'react'; import { Nav, NavList } from '@patternfly/react-core/dist/dynamic/components/Nav'; import { PageContextConsumer } from '@patternfly/react-core/dist/dynamic/components/Page'; +import { useAtomValue } from 'jotai'; import NavContext from './navContext'; import componentMapper from './componentMapper'; import ChromeNavItemFactory from './ChromeNavItemFactory'; import BetaInfoModal from '../../components/BetaInfoModal'; -import { isBeta } from '../../utils/common'; import NavLoader from './Loader'; import ChromeNavItem from './ChromeNavItem'; import type { Navigation as NavigationSchema } from '../../@types/types'; import { useFlag } from '@unleash/proxy-client-react'; import { getUrl } from '../../hooks/useBundle'; +import { isPreviewAtom } from '../../state/atoms/releaseAtom'; export type NavigationProps = { loaded: boolean; schema: NavigationSchema }; const Navigation: React.FC = ({ loaded, schema }) => { + const isPreview = useAtomValue(isPreviewAtom); const [showBetaModal, setShowBetaModal] = useState(false); const deferedOnClickArgs = useRef<[React.MouseEvent | undefined, string | undefined, string | undefined]>([ undefined, @@ -27,7 +29,7 @@ const Navigation: React.FC = ({ loaded, schema }) => { const breadcrumbsDisabled = !useFlag('platform.chrome.bredcrumbs.enabled'); const onLinkClick = (origEvent: React.MouseEvent, href: string) => { - if (!showBetaModal && !isBeta()) { + if (!showBetaModal && !isPreview) { origEvent.preventDefault(); deferedOnClickArgs.current = [origEvent, href, origEvent?.currentTarget?.text]; setShowBetaModal(true); @@ -68,7 +70,7 @@ const Navigation: React.FC = ({ loaded, schema }) => { { - if (!isBeta()) { + if (!isPreview) { const [origEvent, href] = deferedOnClickArgs.current; const isMetaKey = event.ctrlKey || event.metaKey || origEvent?.ctrlKey || origEvent?.metaKey; const url = `${document.baseURI}beta${href}`; diff --git a/src/components/RootApp/ScalprumRoot.test.js b/src/components/RootApp/ScalprumRoot.test.js index f1adb993b..378cf7151 100644 --- a/src/components/RootApp/ScalprumRoot.test.js +++ b/src/components/RootApp/ScalprumRoot.test.js @@ -42,6 +42,14 @@ jest.mock('@unleash/proxy-client-react', () => { }; }); +jest.mock('../../state/atoms/releaseAtom', () => { + const util = jest.requireActual('../../state/atoms/utils'); + return { + __esModule: true, + isPreviewAtom: util.atomWithToggle(false), + }; +}); + window.ResizeObserver = window.ResizeObserver || jest.fn().mockImplementation(() => ({ @@ -238,7 +246,7 @@ describe('ScalprumRoot', () => { }, }); - const { container } = render( + const { container } = await render( @@ -281,7 +289,7 @@ describe('ScalprumRoot', () => { }, }); - const { container } = render( + const { container } = await render( diff --git a/src/components/RootApp/ScalprumRoot.tsx b/src/components/RootApp/ScalprumRoot.tsx index e2bfd2562..1f3a2cebf 100644 --- a/src/components/RootApp/ScalprumRoot.tsx +++ b/src/components/RootApp/ScalprumRoot.tsx @@ -8,7 +8,7 @@ import isEqual from 'lodash/isEqual'; import { AppsConfig } from '@scalprum/core'; import { ChromeAPI, EnableTopicsArgs } from '@redhat-cloud-services/types'; import { ChromeProvider } from '@redhat-cloud-services/chrome'; -import { useSetAtom } from 'jotai'; +import { useAtomValue, useSetAtom } from 'jotai'; import chromeHistory from '../../utils/chromeHistory'; import DefaultLayout from '../../layouts/DefaultLayout'; @@ -27,7 +27,7 @@ import Footer, { FooterProps } from '../Footer/Footer'; import updateSharedScope from '../../chrome/update-shared-scope'; import useBundleVisitDetection from '../../hooks/useBundleVisitDetection'; import chromeApiWrapper from './chromeApiWrapper'; -import { ITLess, isBeta } from '../../utils/common'; +import { ITLess } from '../../utils/common'; import InternalChromeContext from '../../utils/internalChromeContext'; import useChromeServiceEvents from '../../hooks/useChromeServiceEvents'; import useTrackPendoUsage from '../../hooks/useTrackPendoUsage'; @@ -35,6 +35,7 @@ import ChromeAuthContext from '../../auth/ChromeAuthContext'; import { onRegisterModuleWriteAtom } from '../../state/atoms/chromeModuleAtom'; import useTabName from '../../hooks/useTabName'; import { NotificationData, notificationDrawerDataAtom } from '../../state/atoms/notificationDrawerAtom'; +import { isPreviewAtom } from '../../state/atoms/releaseAtom'; const ProductSelection = lazy(() => import('../Stratosphere/ProductSelection')); @@ -57,6 +58,7 @@ const ScalprumRoot = memo( const chromeAuth = useContext(ChromeAuthContext); const registerModule = useSetAtom(onRegisterModuleWriteAtom); const populateNotifications = useSetAtom(notificationDrawerDataAtom); + const isPreview = useAtomValue(isPreviewAtom); const store = useStore(); const mutableChromeApi = useRef(); @@ -152,9 +154,10 @@ const ScalprumRoot = memo( setPageMetadata, chromeAuth, registerModule, + isPreview, }); // reset chrome object after token (user) updates/changes - }, [chromeAuth.token]); + }, [chromeAuth.token, isPreview]); const scalprumProviderProps: ScalprumProviderProps<{ chrome: ChromeAPI }> = useMemo(() => { if (!mutableChromeApi.current) { @@ -183,7 +186,7 @@ const ScalprumRoot = memo( const newManifest = { ...manifest, // Compatibility required for bot pure SDK plugins, HCC plugins and sdk v1/v2 plugins until all are on the same system. - baseURL: manifest.name.includes('hac-') && !manifest.baseURL ? `${isBeta() ? '/beta' : ''}/api/plugins/${manifest.name}/` : '/', + baseURL: manifest.name.includes('hac-') && !manifest.baseURL ? `${isPreview ? '/beta' : ''}/api/plugins/${manifest.name}/` : '/', loadScripts: manifest.loadScripts?.map((script) => `${manifest.baseURL}${script}`.replace(/\/\//, '/')) ?? [ `${manifest.baseURL ?? ''}plugin-entry.js`, ], @@ -194,7 +197,7 @@ const ScalprumRoot = memo( }, }, }; - }, [chromeAuth.token]); + }, [chromeAuth.token, isPreview]); if (!mutableChromeApi.current) { return null; diff --git a/src/hooks/useAllLinks.ts b/src/hooks/useAllLinks.ts index a383dae0c..a10dcefea 100644 --- a/src/hooks/useAllLinks.ts +++ b/src/hooks/useAllLinks.ts @@ -1,8 +1,10 @@ import { useEffect, useState } from 'react'; +import { useAtomValue } from 'jotai'; import { BundleNav, BundleNavigation, NavItem } from '../@types/types'; import fetchNavigationFiles from '../utils/fetchNavigationFiles'; import { evaluateVisibility } from '../utils/isNavItemVisible'; import { isExpandableNav } from '../utils/common'; +import { isPreviewAtom } from '../state/atoms/releaseAtom'; const getFirstChildRoute = (routes: NavItem[] = []): NavItem | undefined => { const firstLeaf = routes.find((item) => !item.expandable && item.href); @@ -81,8 +83,8 @@ const getNavLinks = (navItems: NavItem[]): NavItem[] => { return links; }; -const fetchNavigation = async () => { - const bundlesNavigation = await fetchNavigationFiles().then((data) => data.map(handleBundleResponse)); +const fetchNavigation = async (isPreview: boolean) => { + const bundlesNavigation = await fetchNavigationFiles(isPreview).then((data) => data.map(handleBundleResponse)); const parsedBundles = await Promise.all( bundlesNavigation.map(async (bundleNav) => ({ ...bundleNav, @@ -94,9 +96,10 @@ const fetchNavigation = async () => { }; const useAllLinks = () => { + const isPreview = useAtomValue(isPreviewAtom); const [allLinks, setAllLinks] = useState([]); useEffect(() => { - fetchNavigation().then(setAllLinks); + fetchNavigation(isPreview).then(setAllLinks); }, []); return allLinks; }; diff --git a/src/hooks/useFavoritedServices.ts b/src/hooks/useFavoritedServices.ts index b5f2683ff..7af6d7260 100644 --- a/src/hooks/useFavoritedServices.ts +++ b/src/hooks/useFavoritedServices.ts @@ -1,14 +1,17 @@ import { ServiceTileProps } from '../components/FavoriteServices/ServiceTile'; import useAllServices from './useAllServices'; import { useEffect, useMemo, useState } from 'react'; +import { useAtomValue } from 'jotai'; import fetchNavigationFiles, { extractNavItemGroups } from '../utils/fetchNavigationFiles'; import { NavItem, Navigation } from '../@types/types'; import { findNavLeafPath } from '../utils/common'; import useFavoritePagesWrapper from './useFavoritePagesWrapper'; import { isAllServicesLink } from '../components/AllServices/allServicesLinks'; import useAllLinks from './useAllLinks'; +import { isPreviewAtom } from '../state/atoms/releaseAtom'; const useFavoritedServices = () => { + const isPreview = useAtomValue(isPreviewAtom); const { favoritePages } = useFavoritePagesWrapper(); const { availableSections } = useAllServices(); const allLinks = useAllLinks(); @@ -30,7 +33,7 @@ const useFavoritedServices = () => { }, [availableSections]); useEffect(() => { - fetchNavigationFiles() + fetchNavigationFiles(isPreview) .then((data) => setBundles(data as Navigation[])) .catch((error) => { console.error('Unable to fetch favorite services', error); diff --git a/src/index.ts b/src/index.ts index e1c6ff838..918dcc792 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,7 +12,12 @@ Object.keys(localStorage).map((key) => { // we can't use build to set base to /beta or /preview as they both share the same build // base tag has to be adjusted once at start up function adjustBase() { + const LOCAL_PREVIEW = localStorage.getItem('chrome:local-preview') === 'true'; const baseTag = document.getElementsByTagName('base')?.[0]; + if (LOCAL_PREVIEW) { + baseTag.href = '/'; + return; + } const previewFragment = window.location.pathname.split('/')?.[1]; if (isBeta() && baseTag && previewFragment) { baseTag.href = `/${previewFragment}/`; diff --git a/src/layouts/DefaultLayout.test.js b/src/layouts/DefaultLayout.test.js index 754b253f9..c9c1fadbf 100644 --- a/src/layouts/DefaultLayout.test.js +++ b/src/layouts/DefaultLayout.test.js @@ -5,6 +5,14 @@ import { render } from '@testing-library/react'; import configureStore from 'redux-mock-store'; import { Provider } from 'react-redux'; +jest.mock('../state/atoms/releaseAtom', () => { + const util = jest.requireActual('../state/atoms/utils'); + return { + __esModule: true, + isPreviewAtom: util.atomWithToggle(false), + }; +}); + describe('DefaultLayout', () => { let initialState; let mockStore; diff --git a/src/state/atoms/scalprumConfigAtom.ts b/src/state/atoms/scalprumConfigAtom.ts index a15e434d2..de42acc22 100644 --- a/src/state/atoms/scalprumConfigAtom.ts +++ b/src/state/atoms/scalprumConfigAtom.ts @@ -22,20 +22,23 @@ export const writeInitialScalprumConfigAtom = atom( [key: string]: ChromeModule; } ) => { - const isBetaEnv = isBeta(); + const LOCAL_PREVIEW = localStorage.getItem('chrome:local-preview') === 'true'; + // TODO: Remove this once the local preview is enabled by default + // Assets will be loaded always from root '/' in local preview mode + const previewFragment = LOCAL_PREVIEW ? '' : isBeta() ? '/beta' : ''; const scalprumConfig = Object.entries(schema).reduce( (acc, [name, config]) => ({ ...acc, [name]: { name, module: `${name}#./RootApp`, - manifestLocation: `${window.location.origin}${isBetaEnv ? '/beta' : ''}${config.manifestLocation}?ts=${Date.now()}`, + manifestLocation: `${window.location.origin}${previewFragment}${config.manifestLocation}?ts=${Date.now()}`, }, }), { chrome: { name: 'chrome', - manifestLocation: `${window.location.origin}${isBetaEnv ? '/beta' : ''}/apps/chrome/js/fed-mods.json?ts=${Date.now()}`, + manifestLocation: `${window.location.origin}${previewFragment}/apps/chrome/js/fed-mods.json?ts=${Date.now()}`, }, } ); diff --git a/src/utils/cache.ts b/src/utils/cache.ts index cdce6f2ff..8ee4549b4 100644 --- a/src/utils/cache.ts +++ b/src/utils/cache.ts @@ -18,6 +18,8 @@ let store: LocalForage; * This issue may occur when the user switches between envs without logging out and in. */ const envSwap = () => { + // TODO: Remove this once the local preview is enabled by default + // Only non-beta env will exist in the future const currentEnv = isBeta() ? 'beta' : 'non-beta'; const prevEnv = localStorage.getItem('chrome:prevEnv'); if (prevEnv && currentEnv !== prevEnv) { diff --git a/src/utils/common.ts b/src/utils/common.ts index 38d91a03f..8535c037e 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -6,6 +6,8 @@ import axios from 'axios'; import { Required } from 'utility-types'; import useBundle, { getUrl } from '../hooks/useBundle'; +const LOCAL_PREVIEW = localStorage.getItem('chrome:local-preview') === 'true'; + export const DEFAULT_SSO_ROUTES = { prod: { url: ['access.redhat.com', 'prod.foo.redhat.com', 'cloud.redhat.com', 'console.redhat.com', 'us.console.redhat.com'], @@ -200,11 +202,17 @@ export function isProd() { return location.host === 'cloud.redhat.com' || location.host === 'console.redhat.com' || location.host.includes('prod.foo.redhat.com'); } +/** + * @deprecated preview flag is now determined via chrome internal state variable + */ export function isBeta(pathname?: string) { const previewFragment = (pathname ?? window.location.pathname).split('/')[1]; return ['beta', 'preview'].includes(previewFragment); } +/** + * @deprecated router basename will always be `/` + */ export function getRouterBasename(pathname?: string) { const previewFragment = (pathname ?? window.location.pathname).split('/')[1]; return isBeta(pathname) ? `/${previewFragment}` : '/'; @@ -355,7 +363,10 @@ export const chromeServiceStaticPathname = { }; export function getChromeStaticPathname(type: 'modules' | 'navigation' | 'services' | 'search') { - const stableEnv = isBeta() ? 'beta' : 'stable'; + // TODO: Remove once local preview is enabled by default + // Only non-beta env will exist in the future + // Feature flags should be used to enable/disable features + const stableEnv = LOCAL_PREVIEW ? 'stable' : isBeta() ? 'beta' : 'stable'; const prodEnv = isProd() ? 'prod' : ITLess() ? 'itless' : 'stage'; return `${CHROME_SERVICE_BASE}${chromeServiceStaticPathname[stableEnv][prodEnv]}/${type}`; } diff --git a/src/utils/createCase.ts b/src/utils/createCase.ts index 5a64a460e..9006ddad0 100644 --- a/src/utils/createCase.ts +++ b/src/utils/createCase.ts @@ -3,13 +3,15 @@ import logger from '../auth/logger'; import URI from 'urijs'; const log = logger('createCase.js'); -import { getEnvDetails, isBeta, isProd } from './common'; +import { getEnvDetails, isProd } from './common'; import { HYDRA_ENDPOINT } from './consts'; import { ChromeUser } from '@redhat-cloud-services/types'; import { getUrl } from '../hooks/useBundle'; import chromeStore from '../state/chromeStore'; import { activeModuleAtom } from '../state/atoms/activeModuleAtom'; +const LOCAL_PREVIEW = localStorage.getItem('chrome:local-preview') === 'true'; + // Lit of products that are bundles const BUNDLE_PRODUCTS = [ { id: 'openshift', name: 'Red Hat OpenShift Cluster Manager' }, @@ -43,8 +45,9 @@ function registerProduct() { return product?.name; } -async function getAppInfo(activeModule: string) { - let path = `${window.location.origin}${isBeta() ? '/beta/' : '/'}apps/${activeModule}/app.info.json`; +async function getAppInfo(activeModule: string, isPreview: boolean) { + const previewFragment = LOCAL_PREVIEW ? '' : isPreview ? '/beta' : ''; + let path = `${window.location.origin}${previewFragment}apps/${activeModule}/app.info.json`; try { return activeModule && (await (await fetch(path)).json()); } catch (error) { @@ -53,7 +56,7 @@ async function getAppInfo(activeModule: string) { * Transformation co camel case is requried by webpack remote moduled name requirements. * If we don't find the app info with camel case app id we try using kebab-case */ - path = `${window.location.origin}${isBeta() ? '/beta/' : '/'}apps/${activeModule.replace(/[A-Z]/g, '-$&').toLowerCase()}/app.info.json`; + path = `${window.location.origin}${previewFragment}apps/${activeModule.replace(/[A-Z]/g, '-$&').toLowerCase()}/app.info.json`; try { return activeModule && (await (await fetch(path)).json()); } catch (error) { @@ -62,21 +65,22 @@ async function getAppInfo(activeModule: string) { } } -async function getProductData() { +async function getProductData(isPreview: boolean) { const activeModule = chromeStore.get(activeModuleAtom); - const appData = await getAppInfo(activeModule ?? ''); + const appData = await getAppInfo(activeModule ?? '', isPreview); return appData; } export async function createSupportCase( userInfo: ChromeUser['identity'], token: string, + isPreview: boolean, fields?: { caseFields: Record; } ) { const currentProduct = registerProduct() || 'Other'; - const productData = await getProductData(); + const productData = await getProductData(isPreview); // a temporary fallback to getUrl() until all apps are redeployed, which will fix getProductData() - remove after some time const { src_hash, app_name } = { src_hash: productData?.src_hash, app_name: productData?.app_name ?? getUrl('app') }; const portalUrl = `${getEnvDetails()?.portal}`; @@ -98,7 +102,7 @@ export async function createSupportCase( }, sessionDetails: { createdBy: `${userInfo.user?.username}`, - environment: `Production${isBeta() ? ' Beta' : ''}, ${ + environment: `Production${isPreview ? ' Beta' : ''}, ${ src_hash ? `Current app: ${app_name}, Current app hash: ${src_hash}, Current URL: ${window.location.href}` : `Unknown app, filed on ${window.location.href}` diff --git a/src/utils/fetchNavigationFiles.ts b/src/utils/fetchNavigationFiles.ts index 30067e936..58274add5 100644 --- a/src/utils/fetchNavigationFiles.ts +++ b/src/utils/fetchNavigationFiles.ts @@ -2,14 +2,14 @@ import axios from 'axios'; import { BundleNavigation, NavItem, Navigation } from '../@types/types'; import { Required } from 'utility-types'; import { itLessBundles, requiredBundles } from '../components/AppFilter/useAppFilter'; -import { ITLess, getChromeStaticPathname, isBeta } from './common'; +import { ITLess, getChromeStaticPathname } from './common'; + +const LOCAL_PREVIEW = localStorage.getItem('chrome:local-preview') === 'true'; export function isBundleNavigation(item: unknown): item is BundleNavigation { return typeof item !== 'undefined'; } -const bundles = ITLess() ? itLessBundles : requiredBundles; - export function isNavItems(navigation: Navigation | NavItem[]): navigation is Navigation { return Array.isArray((navigation as Navigation).navItems); } @@ -39,7 +39,8 @@ const filesCache: { existingRequest: undefined, }; -const fetchNavigationFiles = async () => { +const fetchNavigationFiles = async (isPreview: boolean) => { + const bundles = ITLess() ? itLessBundles : requiredBundles; if (filesCache.ready && filesCache.expires > Date.now()) { return filesCache.data; } @@ -53,7 +54,12 @@ const fetchNavigationFiles = async () => { bundles.map((fragment) => axios .get(`${getChromeStaticPathname('navigation')}/${fragment}-navigation.json?ts=${Date.now()}`) - .catch(() => axios.get(`${isBeta() ? '/beta' : ''}/config/chrome/${fragment}-navigation.json?ts=${Date.now()}`)) + .catch(() => { + // FIXME: Remove this once local preview is enabled by default + // No /beta will be needed in the future + const previewFragment = LOCAL_PREVIEW ? '' : isPreview ? '/beta' : ''; + return axios.get(`${previewFragment}/config/chrome/${fragment}-navigation.json?ts=${Date.now()}`); + }) .then((response) => response.data) .catch((err) => { console.error('Unable to load bundle navigation', err, fragment); diff --git a/src/utils/useNavigation.ts b/src/utils/useNavigation.ts index 0c312e300..d11ad4e82 100644 --- a/src/utils/useNavigation.ts +++ b/src/utils/useNavigation.ts @@ -1,16 +1,17 @@ import axios from 'axios'; -import { useSetAtom } from 'jotai'; +import { useAtomValue, useSetAtom } from 'jotai'; import { useContext, useEffect, useRef, useState } from 'react'; import { batch, useDispatch, useSelector } from 'react-redux'; import { loadLeftNavSegment } from '../redux/actions'; import { useLocation, useNavigate } from 'react-router-dom'; -import { BLOCK_CLEAR_GATEWAY_ERROR, getChromeStaticPathname, isBeta } from './common'; +import { BLOCK_CLEAR_GATEWAY_ERROR, getChromeStaticPathname } from './common'; import { evaluateVisibility } from './isNavItemVisible'; import { QuickStartContext } from '@patternfly/quickstarts'; import { useFlagsStatus } from '@unleash/proxy-client-react'; import { BundleNavigation, NavItem, Navigation } from '../@types/types'; import { ReduxState } from '../redux/store'; import { clearGatewayErrorAtom } from '../state/atoms/gatewayErrorAtom'; +import { isPreviewAtom } from '../state/atoms/releaseAtom'; function cleanNavItemsHref(navItem: NavItem) { const result = { ...navItem }; @@ -55,6 +56,7 @@ const useNavigation = () => { const currentNamespace = pathname.split('/')[1]; const schema = useSelector(({ chrome: { navigation } }: ReduxState) => navigation[currentNamespace] as Navigation); const [noNav, setNoNav] = useState(false); + const isPreview = useAtomValue(isPreviewAtom); /** * We need a side effect to get the value into the mutation observer closure @@ -111,7 +113,11 @@ const useNavigation = () => { axios .get(`${getChromeStaticPathname('navigation')}/${currentNamespace}-navigation.json`) // fallback static CSC for EE env - .catch(() => axios.get(`${isBeta() ? '/beta' : ''}/config/chrome/${currentNamespace}-navigation.json?ts=${Date.now()}`)) + .catch(() => { + const LOCAL_PREVIEW = localStorage.getItem('chrome:local-preview') === 'true'; + const previewFragment = LOCAL_PREVIEW ? '' : isPreview ? '/beta' : ''; + return axios.get(`${previewFragment}/config/chrome/${currentNamespace}-navigation.json?ts=${Date.now()}`); + }) .then(async (response) => { if (observer && typeof observer.disconnect === 'function') { observer.disconnect(); diff --git a/src/utils/usePreviewFlag.ts b/src/utils/usePreviewFlag.ts deleted file mode 100644 index e28a3a958..000000000 --- a/src/utils/usePreviewFlag.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { useFlag } from '@unleash/proxy-client-react'; -import { isBeta, isProd } from './common'; - -export const usePreviewFlag = (flag: string) => { - const notificationsOverhaul = useFlag(flag); - - if (isProd() && !isBeta()) { - return false; - } - - return notificationsOverhaul; -}; From 2913f39035c87658513607b36b4be80facf3c3bd Mon Sep 17 00:00:00 2001 From: Martin Marosi Date: Tue, 14 May 2024 14:43:27 +0200 Subject: [PATCH 021/151] Force reload Scalprum route component on preview change this will ensure that any access to isBeta chrome API in runtime is triggered. The routes children should be re-initialized. --- src/components/ChromeRoute/ChromeRoute.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ChromeRoute/ChromeRoute.tsx b/src/components/ChromeRoute/ChromeRoute.tsx index 8c4903a4d..f8e7cf7b4 100644 --- a/src/components/ChromeRoute/ChromeRoute.tsx +++ b/src/components/ChromeRoute/ChromeRoute.tsx @@ -75,7 +75,7 @@ const ChromeRoute = memo(
} fallback={LoadingFallback} // LoadingFallback={() => LoadingFallback} From 632bd2df28e2886ae3a2de752b9608b3e10ed694 Mon Sep 17 00:00:00 2001 From: Martin Marosi Date: Wed, 15 May 2024 09:19:51 +0200 Subject: [PATCH 022/151] Fix unleash circular dependency issues. --- .../FeatureFlags/FeatureFlagsProvider.tsx | 59 ++++++++----------- src/components/FeatureFlags/unleashClient.ts | 27 +++++++++ src/state/atoms/releaseAtom.ts | 12 ++-- src/utils/VisibilitySingleton.ts | 4 +- 4 files changed, 61 insertions(+), 41 deletions(-) create mode 100644 src/components/FeatureFlags/unleashClient.ts diff --git a/src/components/FeatureFlags/FeatureFlagsProvider.tsx b/src/components/FeatureFlags/FeatureFlagsProvider.tsx index 383710144..f1913f21d 100644 --- a/src/components/FeatureFlags/FeatureFlagsProvider.tsx +++ b/src/components/FeatureFlags/FeatureFlagsProvider.tsx @@ -6,6 +6,7 @@ import * as Sentry from '@sentry/react'; import { useAtomValue } from 'jotai'; import ChromeAuthContext, { ChromeAuthContextValue } from '../../auth/ChromeAuthContext'; import { isPreviewAtom } from '../../state/atoms/releaseAtom'; +import { UNLEASH_ERROR_KEY, getUnleashClient, setUnleashClient } from './unleashClient'; const config: IFlagProvider['config'] = { url: `${document.location.origin}/api/featureflags/v0`, @@ -52,43 +53,33 @@ const config: IFlagProvider['config'] = { }, }; -export const UNLEASH_ERROR_KEY = 'chrome:feature-flags:error'; - -/** - * Clear error localstorage flag before initialization - */ -localStorage.setItem(UNLEASH_ERROR_KEY, 'false'); - -export let unleashClient: UnleashClient; -export const getFeatureFlagsError = () => localStorage.getItem(UNLEASH_ERROR_KEY) === 'true'; - const FeatureFlagsProvider: React.FC = ({ children }) => { const { user } = useContext(ChromeAuthContext) as DeepRequired; const isPreview = useAtomValue(isPreviewAtom); - unleashClient = useMemo( - () => - new UnleashClient({ - ...config, - context: { - // the unleash context is not generic, look for issue/PR in the unleash repo or create one - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - 'platform.chrome.ui.preview': isPreview, - userId: user?.identity.internal?.account_id, - orgId: user?.identity.internal?.org_id, - ...(user - ? { - properties: { - account_number: user?.identity.account_number, - email: user?.identity.user.email, - }, - } - : {}), - }, - }), - [] - ); - return {children}; + useMemo(() => { + const client = new UnleashClient({ + ...config, + context: { + // the unleash context is not generic, look for issue/PR in the unleash repo or create one + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + 'platform.chrome.ui.preview': isPreview, + userId: user?.identity.internal?.account_id, + orgId: user?.identity.internal?.org_id, + ...(user + ? { + properties: { + account_number: user?.identity.account_number, + email: user?.identity.user.email, + }, + } + : {}), + }, + }); + setUnleashClient(client); + return client; + }, []); + return {children}; }; export default FeatureFlagsProvider; diff --git a/src/components/FeatureFlags/unleashClient.ts b/src/components/FeatureFlags/unleashClient.ts new file mode 100644 index 000000000..e8b347599 --- /dev/null +++ b/src/components/FeatureFlags/unleashClient.ts @@ -0,0 +1,27 @@ +import { UnleashClient } from '@unleash/proxy-client-react'; + +let unleashClient: UnleashClient; + +export const UNLEASH_ERROR_KEY = 'chrome:feature-flags:error'; + +/** + * Clear error localstorage flag before initialization + */ +localStorage.setItem(UNLEASH_ERROR_KEY, 'false'); + +export const getFeatureFlagsError = () => localStorage.getItem(UNLEASH_ERROR_KEY) === 'true'; + +export function getUnleashClient() { + if (!unleashClient) { + throw new Error('UnleashClient not initialized!'); + } + return unleashClient; +} + +export function setUnleashClient(client: UnleashClient) { + unleashClient = client; +} + +export function unleashClientExists() { + return !!unleashClient; +} diff --git a/src/state/atoms/releaseAtom.ts b/src/state/atoms/releaseAtom.ts index 957e62339..31faf80dc 100644 --- a/src/state/atoms/releaseAtom.ts +++ b/src/state/atoms/releaseAtom.ts @@ -1,7 +1,7 @@ import axios from 'axios'; import { updateVisibilityFunctionsBeta, visibilityFunctionsExist } from '../../utils/VisibilitySingleton'; import { atomWithToggle } from './utils'; -import { unleashClient } from '../../components/FeatureFlags/FeatureFlagsProvider'; +import { getUnleashClient, unleashClientExists } from '../../components/FeatureFlags/unleashClient'; export const isPreviewAtom = atomWithToggle(undefined, (isPreview) => { // Required to change the `isBeta` function return value in the visibility functions @@ -9,8 +9,10 @@ export const isPreviewAtom = atomWithToggle(undefined, (isPreview) => { updateVisibilityFunctionsBeta(isPreview); axios.post('/api/chrome-service/v1/user/update-ui-preview', { uiPreview: isPreview }); } - // Required to change the `platform.chrome.ui.preview` context in the feature flags, TS is bugged - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - unleashClient?.updateContext({ 'platform.chrome.ui.preview': isPreview }); + if (unleashClientExists()) { + // Required to change the `platform.chrome.ui.preview` context in the feature flags, TS is bugged + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + getUnleashClient().updateContext({ 'platform.chrome.ui.preview': isPreview }); + } }); diff --git a/src/utils/VisibilitySingleton.ts b/src/utils/VisibilitySingleton.ts index 5362b084c..acfb1a308 100644 --- a/src/utils/VisibilitySingleton.ts +++ b/src/utils/VisibilitySingleton.ts @@ -4,8 +4,8 @@ import cookie from 'js-cookie'; import axios, { AxiosRequestConfig } from 'axios'; import isEmpty from 'lodash/isEmpty'; import get from 'lodash/get'; -import { getFeatureFlagsError, unleashClient } from '../components/FeatureFlags/FeatureFlagsProvider'; import { getSharedScope, initSharedScope } from '@scalprum/core'; +import { getFeatureFlagsError, getUnleashClient } from '../components/FeatureFlags/unleashClient'; const matcherMapper = { isEmpty, @@ -132,7 +132,7 @@ const initialize = ({ } }, featureFlag: (flagName: string, expectedValue: boolean) => - getFeatureFlagsError() !== true && unleashClient?.isEnabled(flagName) === expectedValue, + getFeatureFlagsError() !== true && getUnleashClient()?.isEnabled(flagName) === expectedValue, }; // in order to properly distribute the module, it has be added to the webpack share scope to avoid reference issues if these functions are called from chrome shared modules From 34dfe7ac6fc05244d6db8de523fe2ac2f3cfc17c Mon Sep 17 00:00:00 2001 From: Martin Marosi Date: Mon, 20 May 2024 11:49:20 +0200 Subject: [PATCH 023/151] Fix missing context in cypress component tests. --- .../AllServicesPage/AllServicesPage.cy.tsx | 41 +++++++++++-------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/cypress/component/AllServicesPage/AllServicesPage.cy.tsx b/cypress/component/AllServicesPage/AllServicesPage.cy.tsx index 3f27c4a56..3fc8141e6 100644 --- a/cypress/component/AllServicesPage/AllServicesPage.cy.tsx +++ b/cypress/component/AllServicesPage/AllServicesPage.cy.tsx @@ -8,6 +8,8 @@ import { ScalprumProvider } from '@scalprum/react-core'; import { getVisibilityFunctions, initializeVisibilityFunctions } from '../../../src/utils/VisibilitySingleton'; import userFixture from '../../fixtures/testUser.json'; import { ChromeUser } from '@redhat-cloud-services/types'; +import { FeatureFlagsProvider } from '../../../src/components/FeatureFlags'; +import ChromeAuthContext from '../../../src/auth/ChromeAuthContext'; describe('', () => { beforeEach(() => { @@ -31,6 +33,7 @@ describe('', () => { it('should filter by service category title', () => { initializeVisibilityFunctions({ + isPreview: false, getToken: () => Promise.resolve(''), getUser: () => Promise.resolve(userFixture as unknown as ChromeUser), getUserPermissions: () => Promise.resolve([]), @@ -48,22 +51,28 @@ describe('', () => { }, })); cy.mount( - - - - - - - - - + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + + + + + + + + + + + + + ); cy.get('.pf-v5-c-text-input-group__text-input').type('consoleset'); From d4ed4b7475edc0dbab5745967efd1b16e06f70e4 Mon Sep 17 00:00:00 2001 From: Martin Marosi Date: Wed, 22 May 2024 08:59:25 +0200 Subject: [PATCH 024/151] Adress PR feedback. --- cypress.config.ts | 2 +- cypress/e2e/auth/OIDC/OIDCState.cy.ts | 3 ++- src/components/AppFilter/useAppFilter.ts | 12 +---------- src/components/Feedback/FeedbackModal.tsx | 3 +-- src/components/Header/Tools.tsx | 23 +++++++++----------- src/state/atoms/releaseAtom.ts | 26 +++++++++++++---------- src/utils/initUserConfig.ts | 1 + 7 files changed, 31 insertions(+), 39 deletions(-) diff --git a/cypress.config.ts b/cypress.config.ts index dce3ed31d..f90523287 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -34,7 +34,7 @@ export default defineConfig({ }, e2e: { blockHosts: ['consent.trustarc.com'], - baseUrl: 'https://stage.foo.redhat.com:1337/beta', + baseUrl: 'https://stage.foo.redhat.com:1337/', env: { E2E_USER: process.env.E2E_USER, E2E_PASSWORD: process.env.E2E_PASSWORD, diff --git a/cypress/e2e/auth/OIDC/OIDCState.cy.ts b/cypress/e2e/auth/OIDC/OIDCState.cy.ts index 680a4df00..e37192d2b 100644 --- a/cypress/e2e/auth/OIDC/OIDCState.cy.ts +++ b/cypress/e2e/auth/OIDC/OIDCState.cy.ts @@ -26,10 +26,11 @@ describe('OIDC State', () => { // Enable cypress exceptions again return true; }); + cy.wait(1000); // The reloader should preserve pathname and query params const url = new URL(win.location.href); expect(url.hash).to.be.empty; - expect(url.pathname).to.eq('/beta/foo/bar'); + expect(url.pathname).to.eq('/foo/bar'); expect(url.search).to.eq('?baz=quaz'); cy.contains('Insights QA').should('exist'); }); diff --git a/src/components/AppFilter/useAppFilter.ts b/src/components/AppFilter/useAppFilter.ts index ae894ed76..8bf9846ad 100644 --- a/src/components/AppFilter/useAppFilter.ts +++ b/src/components/AppFilter/useAppFilter.ts @@ -17,17 +17,7 @@ export type AppFilterBucket = { links: NavItem[]; }; -export const requiredBundles = [ - 'application-services', - 'openshift', - 'insights', - 'edge', - 'ansible', - 'settings', - 'iam', - 'quay', - 'subscriptions', -]; +export const requiredBundles = ['application-services', 'openshift', 'insights', 'edge', 'ansible', 'settings', 'iam', 'quay', 'subscriptions']; export const itLessBundles = ['openshift', 'insights', 'settings', 'iam']; diff --git a/src/components/Feedback/FeedbackModal.tsx b/src/components/Feedback/FeedbackModal.tsx index 4a683d77b..943c9276a 100644 --- a/src/components/Feedback/FeedbackModal.tsx +++ b/src/components/Feedback/FeedbackModal.tsx @@ -1,5 +1,5 @@ import React, { memo, useContext, useState } from 'react'; -import { useAtomValue } from 'jotai'; +import { useAtom, useAtomValue } from 'jotai'; import { Button } from '@patternfly/react-core/dist/dynamic/components/Button'; import { Card, CardBody, CardTitle } from '@patternfly/react-core/dist/dynamic/components/Card'; import { FlexItem } from '@patternfly/react-core/dist/dynamic/layouts/Flex'; @@ -13,7 +13,6 @@ import OutlinedCommentsIcon from '@patternfly/react-icons/dist/dynamic/icons/out import { DeepRequired } from 'utility-types'; import { ChromeUser } from '@redhat-cloud-services/types'; import { useIntl } from 'react-intl'; -import { useAtom, useAtomValue } from 'jotai'; import { isFeedbackModalOpenAtom, usePendoFeedbackAtom } from '../../state/atoms/feedbackModalAtom'; import feedbackIllo from '../../../static/images/feedback_illo.svg'; diff --git a/src/components/Header/Tools.tsx b/src/components/Header/Tools.tsx index a567b33e8..029f45bc8 100644 --- a/src/components/Header/Tools.tsx +++ b/src/components/Header/Tools.tsx @@ -33,7 +33,7 @@ const LOCAL_PREVIEW = localStorage.getItem('chrome:local-preview') === 'true'; const isITLessEnv = ITLess(); /** - * @deprecated Switch release will be replaces by the internal chrome state variable + * @deprecated Switch release will be replaced by the internal chrome state variable */ export const switchRelease = (isBeta: boolean, pathname: string, previewEnabled: boolean) => { cookie.set('cs_toggledRelease', 'true'); @@ -185,6 +185,13 @@ const Tools = () => { : []), ]; + const handleToggle = () => { + if (!LOCAL_PREVIEW) { + switchRelease(isPreview, location.pathname, previewEnabled); + } + setIsPreview(); + }; + useEffect(() => { if (user) { setState({ @@ -244,12 +251,7 @@ const Tools = () => { }, { title: betaSwitcherTitle, - onClick: () => { - if (!LOCAL_PREVIEW) { - switchRelease(isPreview, location.pathname, previewEnabled); - } - setIsPreview(); - }, + onClick: handleToggle, }, { title: 'separator' }, ...aboutMenuDropdownItems, @@ -279,12 +281,7 @@ const Tools = () => { labelOff="Preview off" aria-label="Preview switcher" isChecked={isPreview} - onChange={() => { - if (!LOCAL_PREVIEW) { - switchRelease(isPreview, location.pathname, previewEnabled); - } - setIsPreview(); - }} + onChange={handleToggle} isReversed className="chr-c-beta-switcher" /> diff --git a/src/state/atoms/releaseAtom.ts b/src/state/atoms/releaseAtom.ts index 31faf80dc..dc8fae6ac 100644 --- a/src/state/atoms/releaseAtom.ts +++ b/src/state/atoms/releaseAtom.ts @@ -3,16 +3,20 @@ import { updateVisibilityFunctionsBeta, visibilityFunctionsExist } from '../../u import { atomWithToggle } from './utils'; import { getUnleashClient, unleashClientExists } from '../../components/FeatureFlags/unleashClient'; -export const isPreviewAtom = atomWithToggle(undefined, (isPreview) => { - // Required to change the `isBeta` function return value in the visibility functions - if (visibilityFunctionsExist()) { - updateVisibilityFunctionsBeta(isPreview); - axios.post('/api/chrome-service/v1/user/update-ui-preview', { uiPreview: isPreview }); - } - if (unleashClientExists()) { - // Required to change the `platform.chrome.ui.preview` context in the feature flags, TS is bugged - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - getUnleashClient().updateContext({ 'platform.chrome.ui.preview': isPreview }); +export const isPreviewAtom = atomWithToggle(undefined, async (isPreview) => { + try { + // Required to change the `isBeta` function return value in the visibility functions + if (visibilityFunctionsExist()) { + updateVisibilityFunctionsBeta(isPreview); + await axios.post('/api/chrome-service/v1/user/update-ui-preview', { uiPreview: isPreview }); + } + if (unleashClientExists()) { + // Required to change the `platform.chrome.ui.preview` context in the feature flags, TS is bugged + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + getUnleashClient().updateContext({ 'platform.chrome.ui.preview': isPreview }); + } + } catch (error) { + console.error('Failed to update the visibility functions or feature flags context', error); } }); diff --git a/src/utils/initUserConfig.ts b/src/utils/initUserConfig.ts index e9db70611..3a2c81ae7 100644 --- a/src/utils/initUserConfig.ts +++ b/src/utils/initUserConfig.ts @@ -13,6 +13,7 @@ export type ChromeUserConfig = { export const initChromeUserConfig = async ({ getUser, token }: { getUser: () => Promise; token: string }) => { const LOCAL_PREVIEW = localStorage.getItem('chrome:local-preview') === 'true'; let config: ChromeUserConfig; + // FIXME: remove this once fully switched to internal preview if (!LOCAL_PREVIEW) { config = { data: { From c917bfe5b1c204120e7dc6831b9234e7899ed5f4 Mon Sep 17 00:00:00 2001 From: Georgii Karataev Date: Wed, 19 Jun 2024 15:11:36 +0200 Subject: [PATCH 025/151] docs: Add documentation on local search development --- README.md | 4 ++++ docs/localSearchDevelopment.md | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 docs/localSearchDevelopment.md diff --git a/README.md b/README.md index a793e204b..f1d18ef75 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,10 @@ devServer: { ``` 3. Run insights-chrome with `npm run dev` or `npm run dev:beta`. +## Local search development + +See [local search development documentation](./docs/localSearchDevelopment.md). + ## LocalStorage Debugging There are some localStorage values for you to enable debuging information or enable some values that are in experimental state. If you want to enable them call `const iqe = insights.chrome.enable.iqe()` for instance to enable such service. This function will return callback to disable such feature so calling `iqe()` will remove such item from localStorage. diff --git a/docs/localSearchDevelopment.md b/docs/localSearchDevelopment.md new file mode 100644 index 000000000..0cbfa6828 --- /dev/null +++ b/docs/localSearchDevelopment.md @@ -0,0 +1,33 @@ +# Local search development + +You can develop and debug search results (homepage, the "Search for services" field) by running Insights Chrome together with chrome-service-backend. + +## Prerequisites + +1. Have a go language setup. You can follow the [gmv guide](https://github.com/moovweb/gvm#installing). +2. Have a podman installed. [Getting started guide](https://podman.io/get-started) +3. Have the [chrome-service-backend](https://github.com/RedHatInsights/chrome-service-backend) checkout locally. +4. Make sure you terminal supports the [Makefile](https://makefiletutorial.com/) utility. + +## Setting up the development environment + +chrome-service-backend is the bridge between kafka and the browser client. It exposes the search-index.json endpoint required for Chrome search to function. + +### Run chrome-service-backend first + +1. Follow the chrome-service-backend steps for local setup (`make dev-static` or `make dev-static-node` should be enough just to serve the static assets including search index). +2. You can request http://localhost:8000/api/chrome-service/v1/static/stable/stage/search/search-index.json (assuming you have left the default port settings) to test the connection and make sure that the chrome service is serving static assets. + +### Generate the local search index + +1. Follow the chrome-service-backend instructions to generate the search index as a JSON file (running `make generate-search-index` should be enough). + +### Start Insights Chrome frontend + +1. Once your chrome service backend is running, start the chrome dev server with the chrome service config using this command: `NAV_CONFIG=8000 yarn dev`. + +When all the steps are complete, you should be able to see the search results (https://stage.foo.redhat.com:1337, "Search for services") provided from the locally generated search index. Any subsequent update to search index must be followed with `make generate-search-index` to regenerate the search index file. + +### Debug tooling + +You can enable additional logging of the search results when typing any prompt by editing [levenshtein-search.ts](../src/utils/levenshtein-search.ts) and setting `debugFlag` to true. \ No newline at end of file From 2dc20701b87994bf77590052b22f182008d277b1 Mon Sep 17 00:00:00 2001 From: Janet Cobb Date: Thu, 20 Jun 2024 12:04:17 -0400 Subject: [PATCH 026/151] Update patternfly quickstarts --- package-lock.json | 8 ++++---- package.json | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 914ff72b9..57d313542 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,7 @@ "@openshift/dynamic-plugin-sdk": "^5.0.1", "@orama/orama": "^2.0.3", "@patternfly/patternfly": "^5.3.0", - "@patternfly/quickstarts": "^5.3.0", + "@patternfly/quickstarts": "^5.4.0-prerelease.1", "@patternfly/react-charts": "^7.3.0", "@patternfly/react-core": "^5.3.0", "@patternfly/react-icons": "^5.3.0", @@ -4125,9 +4125,9 @@ "integrity": "sha512-93uWA15bOJDgu8NF2iReWbbNtWdtM+v7iaDpK33mJChgej+whiFpGLtQPI2jFk1aVW3rDpbt4qm4OaNinpzSsg==" }, "node_modules/@patternfly/quickstarts": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/@patternfly/quickstarts/-/quickstarts-5.3.0.tgz", - "integrity": "sha512-2+nKrLag8z8p9d9caQvlSMqcMGkfd8uRl54SGykpjkdp7UDT6VER/nsb4gAZkJA7udrY+yJ8EockNFY6eCiGbA==", + "version": "5.4.0-prerelease.1", + "resolved": "https://registry.npmjs.org/@patternfly/quickstarts/-/quickstarts-5.4.0-prerelease.1.tgz", + "integrity": "sha512-Sl9LdZh2mbk1q2NaEG6Tmopl9KbUoKKl9jgQU9zwBaI+u5x7ZiVbD2Q0uAyliraKQNZPUs86TA0roAeYh3iFeg==", "dependencies": { "@patternfly/react-catalog-view-extension": "^5.0.0", "dompurify": "^2.2.6", diff --git a/package.json b/package.json index 078f32067..7cd55272a 100644 --- a/package.json +++ b/package.json @@ -132,16 +132,16 @@ "@data-driven-forms/react-form-renderer": "^3.22.1", "@formatjs/cli": "4.8.4", "@openshift/dynamic-plugin-sdk": "^5.0.1", + "@orama/orama": "^2.0.3", "@patternfly/patternfly": "^5.3.0", - "@patternfly/quickstarts": "^5.3.0", + "@patternfly/quickstarts": "^5.4.0-prerelease.1", "@patternfly/react-charts": "^7.3.0", "@patternfly/react-core": "^5.3.0", "@patternfly/react-icons": "^5.3.0", "@patternfly/react-tokens": "^5.3.0", - "@orama/orama": "^2.0.3", - "@redhat-cloud-services/frontend-components": "^4.2.2", "@redhat-cloud-services/chrome": "^1.0.9", "@redhat-cloud-services/entitlements-client": "1.2.0", + "@redhat-cloud-services/frontend-components": "^4.2.2", "@redhat-cloud-services/frontend-components-notifications": "^4.1.0", "@redhat-cloud-services/frontend-components-pdf-generator": "4.0.4", "@redhat-cloud-services/frontend-components-utilities": "^4.0.2", From 2099bfd0c7b08fbf1bfbc1ceb9a5bc2b8f89059f Mon Sep 17 00:00:00 2001 From: Janet Cobb Date: Thu, 20 Jun 2024 15:08:06 -0400 Subject: [PATCH 027/151] Fix protocol separator Based on [0], LOCAL_APPS expects the format `service:port~proto`, not `service:port:proto`. [0]: https://github.com/RedHatInsights/frontend-components/blob/fc61adcd088fe25997c36b8ef799f37dd7691739/packages/config-utils/src/proxy.ts#L70-L72 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a793e204b..9aa1f0c9b 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ You can spin chrome locally together with other applications. Use `LOCAL_APPS` t For illustration, to deploy Advisor together with Insights Chrome, you would require to 1. Run Advisor on any available port with `npm run start -- --port=8004` or `npm run start:beta -- --port=8004`, -2. Run Chrome and list the Advisor's port: `LOCAL_APPS=advisor:8004:http npm run dev` or `LOCAL_APPS=advisor:8004:http npm run dev:beta`. +2. Run Chrome and list the Advisor's port: `LOCAL_APPS=advisor:8004~http npm run dev` or `LOCAL_APPS=advisor:8004~http npm run dev:beta`. #### Example 2 (using devServer route) From 59240487e3df99d288f9130bd41bdf352a00e13d Mon Sep 17 00:00:00 2001 From: Cody Mitchell Date: Mon, 24 Jun 2024 10:55:05 -0400 Subject: [PATCH 028/151] handle drawer read in default test --- cypress/component/DefaultLayout.cy.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cypress/component/DefaultLayout.cy.js b/cypress/component/DefaultLayout.cy.js index dcd0572a9..9a2800716 100644 --- a/cypress/component/DefaultLayout.cy.js +++ b/cypress/component/DefaultLayout.cy.js @@ -91,6 +91,9 @@ describe('', () => { }); reduxRegistry.register(chromeReducer()); store = reduxRegistry.getStore(); + cy.intercept('PUT', 'http://localhost:8080/api/notifications/v1/notifications/drawer/read', { + statusCode: 200, + }); cy.intercept('GET', '/api/featureflags/*', { toggles: [ { From c973f0682350324372ebb11d775995f08608af51 Mon Sep 17 00:00:00 2001 From: Martin Marosi Date: Wed, 26 Jun 2024 15:22:19 +0200 Subject: [PATCH 029/151] Duplicate FF context before updating preview env. --- src/state/atoms/releaseAtom.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/state/atoms/releaseAtom.ts b/src/state/atoms/releaseAtom.ts index dc8fae6ac..1b6587a36 100644 --- a/src/state/atoms/releaseAtom.ts +++ b/src/state/atoms/releaseAtom.ts @@ -12,9 +12,13 @@ export const isPreviewAtom = atomWithToggle(undefined, async (isPreview) => { } if (unleashClientExists()) { // Required to change the `platform.chrome.ui.preview` context in the feature flags, TS is bugged - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - getUnleashClient().updateContext({ 'platform.chrome.ui.preview': isPreview }); + getUnleashClient().updateContext({ + // make sure to re-use the prev context + ...getUnleashClient().getContext(), + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + 'platform.chrome.ui.preview': isPreview, + }); } } catch (error) { console.error('Failed to update the visibility functions or feature flags context', error); From 3605216e02aa738fbedbcc83d5b9e2d53186802a Mon Sep 17 00:00:00 2001 From: Radek Kaluzik Date: Thu, 13 Jun 2024 16:54:03 +0200 Subject: [PATCH 030/151] 'Manage this event' link presets the right bundle tab and configuration tab --- cypress/component/NotificationDrawer/NotificationDrawer.cy.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cypress/component/NotificationDrawer/NotificationDrawer.cy.tsx b/cypress/component/NotificationDrawer/NotificationDrawer.cy.tsx index c6164bea3..f169a420c 100644 --- a/cypress/component/NotificationDrawer/NotificationDrawer.cy.tsx +++ b/cypress/component/NotificationDrawer/NotificationDrawer.cy.tsx @@ -13,6 +13,7 @@ const notificationDrawerData: NotificationData[] = [ created: new Date().toString(), description: 'This is a test notification', source: 'openshift', + bundle: 'rhel', }, { id: '2', @@ -21,6 +22,7 @@ const notificationDrawerData: NotificationData[] = [ created: new Date().toString(), description: 'This is a test notification', source: 'console', + bundle: 'rhel', }, { id: '3', @@ -29,6 +31,7 @@ const notificationDrawerData: NotificationData[] = [ created: new Date().toString(), description: 'This is a test notification', source: 'console', + bundle: 'rhel', }, ]; From f3694364c75e1ebc7250277c6673afd43a8d6d76 Mon Sep 17 00:00:00 2001 From: Adam Jetmar Date: Fri, 14 Jun 2024 15:08:07 +0200 Subject: [PATCH 031/151] Jotai migration activeApp --- src/layouts/DefaultLayout.test.js | 75 ++++++++++++------- .../__snapshots__/DefaultLayout.test.js.snap | 1 + src/redux/chromeReducers.ts | 7 -- src/redux/index.ts | 3 - src/redux/store.d.ts | 1 - src/state/atoms/activeAppAtom.ts | 3 + src/state/chromeStore.ts | 2 + src/utils/useOuiaTags.ts | 8 +- 8 files changed, 61 insertions(+), 39 deletions(-) create mode 100644 src/state/atoms/activeAppAtom.ts diff --git a/src/layouts/DefaultLayout.test.js b/src/layouts/DefaultLayout.test.js index c9c1fadbf..08b10bfb4 100644 --- a/src/layouts/DefaultLayout.test.js +++ b/src/layouts/DefaultLayout.test.js @@ -4,6 +4,20 @@ import DefaultLayout from './DefaultLayout'; import { render } from '@testing-library/react'; import configureStore from 'redux-mock-store'; import { Provider } from 'react-redux'; +import { Provider as ProviderJotai } from 'jotai'; +import { useHydrateAtoms } from 'jotai/utils'; +import { activeAppAtom } from '../state/atoms/activeAppAtom'; + +const HydrateAtoms = ({ initialValues, children }) => { + useHydrateAtoms(initialValues); + return children; +}; + +const TestProvider = ({ initialValues, children }) => ( + + {children} + +); jest.mock('../state/atoms/releaseAtom', () => { const util = jest.requireActual('../state/atoms/utils'); @@ -28,7 +42,6 @@ describe('DefaultLayout', () => { mockStore = configureStore(); initialState = { chrome: { - activeApp: 'some-app', activeLocation: 'some-location', appId: 'app-id', navigation: { @@ -51,11 +64,13 @@ describe('DefaultLayout', () => { it('should render correctly - no data', async () => { const store = mockStore({ chrome: {} }); const { container } = render( - - - - - + + + + + + + ); expect(container.querySelector('#chrome-app-render-root')).toMatchSnapshot(); }); @@ -63,11 +78,13 @@ describe('DefaultLayout', () => { it('should render correctly', () => { const store = mockStore(initialState); const { container } = render( - - - - - + + + + + + + ); expect(container.querySelector('#chrome-app-render-root')).toMatchSnapshot(); }); @@ -81,11 +98,13 @@ describe('DefaultLayout', () => { globalFilter: {}, }); const { container } = render( - - - - - + + + + + + + ); expect(container.querySelector('#chrome-app-render-root')).toMatchSnapshot(); }); @@ -98,11 +117,13 @@ describe('DefaultLayout', () => { }, }); const { container } = render( - - - - - + + + + + + + ); expect(container.querySelector('#chrome-app-render-root')).toMatchSnapshot(); }); @@ -116,11 +137,13 @@ describe('DefaultLayout', () => { }, }); const { container } = render( - - - - - + + + + + + + ); expect(container.querySelector('#chrome-app-render-root')).toMatchSnapshot(); }); diff --git a/src/layouts/__snapshots__/DefaultLayout.test.js.snap b/src/layouts/__snapshots__/DefaultLayout.test.js.snap index 621b7a96d..653dada8b 100644 --- a/src/layouts/__snapshots__/DefaultLayout.test.js.snap +++ b/src/layouts/__snapshots__/DefaultLayout.test.js.snap @@ -4,6 +4,7 @@ exports[`DefaultLayout should render correctly - no data 1`] = `
diff --git a/src/redux/chromeReducers.ts b/src/redux/chromeReducers.ts index d7424b796..0bfff2347 100644 --- a/src/redux/chromeReducers.ts +++ b/src/redux/chromeReducers.ts @@ -5,13 +5,6 @@ import { NavItem, Navigation } from '../@types/types'; import { ITLess, highlightItems, levelArray } from '../utils/common'; import { AccessRequest, ChromeState } from './store'; -export function appNavClick(state: ChromeState, { payload }: { payload: { id: string } }): ChromeState { - return { - ...state, - activeApp: payload.id, - }; -} - export function loginReducer(state: ChromeState, { payload }: { payload: ChromeUser }): ChromeState { const missingIDP = ITLess() && !Object.prototype.hasOwnProperty.call(payload?.identity, 'idp'); return { diff --git a/src/redux/index.ts b/src/redux/index.ts index 5d27dfd2d..7654e981b 100644 --- a/src/redux/index.ts +++ b/src/redux/index.ts @@ -3,7 +3,6 @@ import { applyReducerHash } from '@redhat-cloud-services/frontend-components-uti import { accessRequestsNotificationsReducer, addQuickstartstoApp, - appNavClick, clearQuickstartsReducer, disableQuickstartsReducer, documentTitleReducer, @@ -33,7 +32,6 @@ import { } from './globalFilterReducers'; import { ADD_QUICKSTARTS_TO_APP, - APP_NAV_CLICK, CHROME_GET_ALL_SIDS, CHROME_GET_ALL_TAGS, CHROME_GET_ALL_WORKLOADS, @@ -60,7 +58,6 @@ import { ChromeState, GlobalFilterState, ReduxState } from './store'; import { AnyAction } from 'redux'; const reducers = { - [APP_NAV_CLICK]: appNavClick, [USER_LOGIN]: loginReducer, [CHROME_PAGE_ACTION]: onPageAction, [CHROME_PAGE_OBJECT]: onPageObjectId, diff --git a/src/redux/store.d.ts b/src/redux/store.d.ts index 0b1fa639c..871b9cc59 100644 --- a/src/redux/store.d.ts +++ b/src/redux/store.d.ts @@ -10,7 +10,6 @@ export type InternalNavigation = { export type AccessRequest = { request_id: string; created: string; seen: boolean }; export type ChromeState = { - activeApp?: string; activeProduct?: string; missingIDP?: boolean; pageAction?: string; diff --git a/src/state/atoms/activeAppAtom.ts b/src/state/atoms/activeAppAtom.ts new file mode 100644 index 000000000..b89b8753e --- /dev/null +++ b/src/state/atoms/activeAppAtom.ts @@ -0,0 +1,3 @@ +import { atom } from 'jotai'; + +export const activeAppAtom = atom(undefined); diff --git a/src/state/chromeStore.ts b/src/state/chromeStore.ts index e727b8f8c..b176137be 100644 --- a/src/state/chromeStore.ts +++ b/src/state/chromeStore.ts @@ -5,6 +5,7 @@ import { isPreviewAtom } from './atoms/releaseAtom'; import { isBeta } from '../utils/common'; import { gatewayErrorAtom } from './atoms/gatewayErrorAtom'; import { isFeedbackModalOpenAtom } from './atoms/feedbackModalAtom'; +import { activeAppAtom } from './atoms/activeAppAtom'; const chromeStore = createStore(); @@ -16,6 +17,7 @@ chromeStore.set(gatewayErrorAtom, undefined); chromeStore.set(isFeedbackModalOpenAtom, false); // is set in bootstrap chromeStore.set(isPreviewAtom, false); +chromeStore.set(activeAppAtom, undefined); // globally handle subscription to activeModuleAtom chromeStore.sub(activeModuleAtom, () => { diff --git a/src/utils/useOuiaTags.ts b/src/utils/useOuiaTags.ts index ddf77018a..a12835737 100644 --- a/src/utils/useOuiaTags.ts +++ b/src/utils/useOuiaTags.ts @@ -3,6 +3,8 @@ import { shallowEqual, useSelector } from 'react-redux'; import { useLocation } from 'react-router-dom'; import { ReduxState } from '../redux/store'; import { isAnsible } from '../hooks/useBundle'; +import { activeAppAtom } from '../state/atoms/activeAppAtom'; +import { useAtomValue } from 'jotai'; export type OuiaTags = { landing?: 'true' | 'false'; @@ -19,10 +21,12 @@ const useOuiaTags = () => { 'data-ouia-safe': 'true', }); const { pathname } = useLocation(); - const { pageAction, pageObjectId, activeApp } = useSelector( - ({ chrome: { pageAction, pageObjectId, activeApp } }: ReduxState) => ({ pageAction, pageObjectId, activeApp }), + const { pageAction, pageObjectId } = useSelector( + ({ chrome: { pageAction, pageObjectId } }: ReduxState) => ({ pageAction, pageObjectId }), shallowEqual ); + const activeApp = useAtomValue(activeAppAtom); + useEffect(() => { setState(() => { const result: OuiaTags = { From a49ea34ecd6f564f6d2a0297c814869e13596f72 Mon Sep 17 00:00:00 2001 From: Adam Jetmar Date: Thu, 27 Jun 2024 12:36:24 +0200 Subject: [PATCH 032/151] Update activeApp atoms and tests --- src/chrome/create-chrome.test.ts | 2 + src/chrome/create-chrome.ts | 19 +- src/components/ChromeLink/ChromeLink.test.js | 179 ++++++++++--------- src/components/ChromeLink/ChromeLink.tsx | 10 +- src/components/RootApp/ScalprumRoot.tsx | 11 ++ src/redux/action-types.ts | 2 - src/redux/actions.ts | 5 +- src/state/atoms/activeAppAtom.test.tsx | 139 ++++++++++++++ src/state/atoms/activeAppAtom.ts | 38 ++++ src/utils/consts.ts | 16 +- 10 files changed, 309 insertions(+), 112 deletions(-) create mode 100644 src/state/atoms/activeAppAtom.test.tsx diff --git a/src/chrome/create-chrome.test.ts b/src/chrome/create-chrome.test.ts index 0f07ad858..8d3f48da9 100644 --- a/src/chrome/create-chrome.test.ts +++ b/src/chrome/create-chrome.test.ts @@ -121,6 +121,8 @@ describe('create chrome', () => { setPageMetadata: jest.fn(), useGlobalFilter: jest.fn(), registerModule: jest.fn(), + addNavListener: jest.fn(), + deleteNavListener: jest.fn(), }; beforeAll(() => { const mockAuthMethods = { diff --git a/src/chrome/create-chrome.ts b/src/chrome/create-chrome.ts index 441281adf..3739eda36 100644 --- a/src/chrome/create-chrome.ts +++ b/src/chrome/create-chrome.ts @@ -1,14 +1,12 @@ import { createFetchPermissionsWatcher } from '../auth/fetchPermissions'; -import { AppNavigationCB, ChromeAPI, GenericCB, NavDOMEvent } from '@redhat-cloud-services/types'; +import { AppNavigationCB, ChromeAPI, GenericCB } from '@redhat-cloud-services/types'; import { Store } from 'redux'; import { AnalyticsBrowser } from '@segment/analytics-next'; import get from 'lodash/get'; import Cookies from 'js-cookie'; import { - AppNavClickItem, appAction, - appNavClick, appObjectId, globalFilterScope, removeGlobalFilter, @@ -37,6 +35,7 @@ import requestPdf from '../pdf/requestPdf'; import chromeStore from '../state/chromeStore'; import { isFeedbackModalOpenAtom } from '../state/atoms/feedbackModalAtom'; import { usePendoFeedback } from '../components/Feedback'; +import { NavListener, activeAppAtom } from '../state/atoms/activeAppAtom'; export type CreateChromeContextConfig = { useGlobalFilter: (callback: (selectedTags?: FlagTagsFilter) => any) => ReturnType; @@ -48,6 +47,8 @@ export type CreateChromeContextConfig = { chromeAuth: ChromeAuthContextValue; registerModule: (payload: RegisterModulePayload) => void; isPreview: boolean; + addNavListener: (cb: NavListener) => number; + deleteNavListener: (id: number) => void; }; export const createChromeContext = ({ @@ -60,6 +61,8 @@ export const createChromeContext = ({ registerModule, chromeAuth, isPreview, + addNavListener, + deleteNavListener, }: CreateChromeContextConfig): ChromeAPI => { const fetchPermissions = createFetchPermissionsWatcher(chromeAuth.getUser); const visibilityFunctions = getVisibilityFunctions(); @@ -67,7 +70,7 @@ export const createChromeContext = ({ const actions = { appAction: (action: string) => dispatch(appAction(action)), appObjectId: (objectId: string) => dispatch(appObjectId(objectId)), - appNavClick: (item: AppNavClickItem, event?: NavDOMEvent) => dispatch(appNavClick(item, event)), + appNavClick: (item: string) => chromeStore.set(activeAppAtom, item), globalFilterScope: (scope: string) => dispatch(globalFilterScope(scope)), registerModule: (module: string, manifest?: string) => registerModule({ module, manifest }), removeGlobalFilter: (isHidden: boolean) => { @@ -76,13 +79,17 @@ export const createChromeContext = ({ }, }; - const on = (type: keyof typeof PUBLIC_EVENTS, callback: AppNavigationCB | GenericCB) => { + const on = (type: keyof typeof PUBLIC_EVENTS | 'APP_NAVIGATION', callback: AppNavigationCB | GenericCB) => { + if (type === 'APP_NAVIGATION') { + const listenerId = addNavListener(callback); + return () => deleteNavListener(listenerId); + } if (!Object.prototype.hasOwnProperty.call(PUBLIC_EVENTS, type)) { throw new Error(`Unknown event type: ${type}`); } const [listener, selector] = PUBLIC_EVENTS[type]; - if (type !== 'APP_NAVIGATION' && typeof selector === 'string') { + if (typeof selector === 'string') { (callback as GenericCB)({ data: get(store.getState(), selector) || {}, }); diff --git a/src/components/ChromeLink/ChromeLink.test.js b/src/components/ChromeLink/ChromeLink.test.js index 1909c0668..580b9839e 100644 --- a/src/components/ChromeLink/ChromeLink.test.js +++ b/src/components/ChromeLink/ChromeLink.test.js @@ -6,7 +6,6 @@ import createMockStore from 'redux-mock-store'; import { MemoryRouter } from 'react-router-dom'; import ChromeLink from './ChromeLink'; import NavContext from '../Navigation/navContext'; -import { APP_NAV_CLICK } from '../../redux/action-types'; const LinkContext = ({ store, @@ -48,85 +47,105 @@ describe('ChromeLink', () => { expect(getAllByTestId('router-link')).toHaveLength(1); }); - test('should dispatch appNavClick with correct actionId for top level route', () => { - const store = mockStore({ - chrome: { - moduleRoutes: [], - activeModule: 'testModule', - modules: { - testModule: {}, - }, - }, - }); - const { - container: { firstElementChild: buttton }, - } = render( - - Test module link - - ); - - act(() => { - fireEvent.click(buttton); - }); - - expect(store.getActions()).toEqual([ - { - type: APP_NAV_CLICK, - payload: { - id: '/', - event: { - id: '/', - navId: '/', - href: '/insights/foo', - type: 'click', - target: expect.any(Element), - }, - }, - }, - ]); - }); - - test('should dispatch appNavClick with correct actionId for nested route', () => { - const store = mockStore({ - chrome: { - moduleRoutes: [], - activeModule: 'testModule', - modules: { - testModule: {}, - }, - }, - }); - const { - container: { firstElementChild: buttton }, - } = render( - - - Test module link - - - ); - - act(() => { - fireEvent.click(buttton); - }); - - expect(store.getActions()).toEqual([ - { - type: APP_NAV_CLICK, - payload: { - id: 'bar', - event: { - id: 'bar', - navId: 'bar', - href: '/insights/foo/bar', - type: 'click', - target: expect.any(Element), - }, - }, - }, - ]); - }); + // const store = mockStore({ + // chrome: { + // moduleRoutes: [], + // activeModule: 'testModule', + // modules: { + // testModule: {}, + // }, + // }, + // }); + // const { + // container: { firstElementChild: buttton }, + // } = render( + // + // Test module link + // + // ); + + // act(() => { + // fireEvent.click(buttton); + // }); + + // expect(store.getActions()).toEqual([ + // { + // type: 'APP_NAV_CLICK', + // payload: { + // id: '/', + // event: { + // id: '/', + // navId: '/', + // href: '/insights/foo', + // type: 'click', + // target: expect.any(Element), + // }, + // }, + // }, + // ]); + // let chromeContext: Context; + // let contextValue: ChromeAPI; + + // const Wrapper = () => { + // const addNavListener = useSetAtom(addNavListenerAtom); + // return ( + // + // 'stage' } as any}> + // + // + // + // ); + // }; + // render(); + // const button = screen.getByText('Add NavListener'); + // fireEvent.click(button); + + // expect(mockNavListener).not.toHaveBeenCalled(); + + // mockNavListener(sampleNavEvent); + // expect(mockNavListener).toHaveBeenCalledWith(sampleNavEvent); + + + // test('should dispatch appNavClick with correct actionId for nested route', () => { + // const store = mockStore({ + // chrome: { + // moduleRoutes: [], + // activeModule: 'testModule', + // modules: { + // testModule: {}, + // }, + // }, + // }); + // const { + // container: { firstElementChild: buttton }, + // } = render( + // + // + // Test module link + // + // + // ); + + // act(() => { + // fireEvent.click(buttton); + // }); + + // expect(store.getActions()).toEqual([ + // { + // type: 'APP_NAV_CLICK', + // payload: { + // id: 'bar', + // event: { + // id: 'bar', + // navId: 'bar', + // href: '/insights/foo/bar', + // type: 'click', + // target: expect.any(Element), + // }, + // }, + // }, + // ]); + // }); test('should not trigger onLinkClick callback', () => { const onLinkClickSpy = jest.fn(); diff --git a/src/components/ChromeLink/ChromeLink.tsx b/src/components/ChromeLink/ChromeLink.tsx index 1ef4845b5..a716d0a3a 100644 --- a/src/components/ChromeLink/ChromeLink.tsx +++ b/src/components/ChromeLink/ChromeLink.tsx @@ -1,14 +1,13 @@ import React, { memo, useContext, useMemo, useRef } from 'react'; import { NavLink } from 'react-router-dom'; -import { useDispatch } from 'react-redux'; import { preloadModule } from '@scalprum/core'; -import { appNavClick } from '../../redux/actions'; import NavContext, { OnLinkClick } from '../Navigation/navContext'; import { NavDOMEvent } from '../../@types/types'; -import { useAtomValue } from 'jotai'; +import { useAtomValue, useSetAtom } from 'jotai'; import { activeModuleAtom } from '../../state/atoms/activeModuleAtom'; import { moduleRoutesAtom } from '../../state/atoms/chromeModuleAtom'; +import { triggerNavListenersAtom } from '../../state/atoms/activeAppAtom'; interface RefreshLinkProps extends React.HTMLAttributes { isExternal?: boolean; @@ -32,6 +31,7 @@ const LinkWrapper: React.FC = memo( ({ href = '', isBeta, onLinkClick, className, currAppId, appId, children, tabIndex, ...props }) => { const linkRef = useRef(null); const moduleRoutes = useAtomValue(moduleRoutesAtom); + const triggerNavListener = useSetAtom(triggerNavListenersAtom); const moduleEntry = useMemo(() => moduleRoutes?.find((route) => href?.includes(route.path)), [href, appId]); const preloadTimeout = useRef(); let actionId = href.split('/').slice(2).join('/'); @@ -57,7 +57,6 @@ const LinkWrapper: React.FC = memo( */ type: 'click', }; - const dispatch = useDispatch(); const onClick = (event: React.MouseEvent) => { if (event.ctrlKey || event.shiftKey) { return false; @@ -72,7 +71,8 @@ const LinkWrapper: React.FC = memo( * Add reference to the DOM link element */ domEvent.target = linkRef.current; - dispatch(appNavClick({ id: actionId }, domEvent)); + // dispatch(appNavClick({ id: actionId }, domEvent)); + triggerNavListener({ navId: actionId, domEvent }); }; // turns /settings/rbac/roles -> settings_rbac_roles diff --git a/src/components/RootApp/ScalprumRoot.tsx b/src/components/RootApp/ScalprumRoot.tsx index 1f3a2cebf..7942f3520 100644 --- a/src/components/RootApp/ScalprumRoot.tsx +++ b/src/components/RootApp/ScalprumRoot.tsx @@ -35,7 +35,11 @@ import ChromeAuthContext from '../../auth/ChromeAuthContext'; import { onRegisterModuleWriteAtom } from '../../state/atoms/chromeModuleAtom'; import useTabName from '../../hooks/useTabName'; import { NotificationData, notificationDrawerDataAtom } from '../../state/atoms/notificationDrawerAtom'; +<<<<<<< HEAD import { isPreviewAtom } from '../../state/atoms/releaseAtom'; +======= +import { addNavListenerAtom, deleteNavListenerAtom } from '../../state/atoms/activeAppAtom'; +>>>>>>> febf850c (Update activeApp atoms and tests) const ProductSelection = lazy(() => import('../Stratosphere/ProductSelection')); @@ -58,7 +62,12 @@ const ScalprumRoot = memo( const chromeAuth = useContext(ChromeAuthContext); const registerModule = useSetAtom(onRegisterModuleWriteAtom); const populateNotifications = useSetAtom(notificationDrawerDataAtom); +<<<<<<< HEAD const isPreview = useAtomValue(isPreviewAtom); +======= + const addNavListener = useSetAtom(addNavListenerAtom); + const deleteNavListener = useSetAtom(deleteNavListenerAtom); +>>>>>>> febf850c (Update activeApp atoms and tests) const store = useStore(); const mutableChromeApi = useRef(); @@ -155,6 +164,8 @@ const ScalprumRoot = memo( chromeAuth, registerModule, isPreview, + addNavListener, + deleteNavListener, }); // reset chrome object after token (user) updates/changes }, [chromeAuth.token, isPreview]); diff --git a/src/redux/action-types.ts b/src/redux/action-types.ts index 0690ff205..64f76ecc7 100644 --- a/src/redux/action-types.ts +++ b/src/redux/action-types.ts @@ -1,7 +1,5 @@ export const USER_LOGIN = '@@chrome/user-login'; -export const APP_NAV_CLICK = '@@chrome/app-nav-click'; - export const CHROME_PAGE_ACTION = '@@chrome/app-page-action'; export const CHROME_PAGE_OBJECT = '@@chrome/app-object-id'; diff --git a/src/redux/actions.ts b/src/redux/actions.ts index bd33f2b31..88cf682fc 100644 --- a/src/redux/actions.ts +++ b/src/redux/actions.ts @@ -2,7 +2,7 @@ import * as actionTypes from './action-types'; import { getAllSIDs, getAllTags, getAllWorkloads } from '../components/GlobalFilter/tagsApi'; import type { TagFilterOptions, TagPagination } from '../components/GlobalFilter/tagsApi'; import type { ChromeUser } from '@redhat-cloud-services/types'; -import type { FlagTagsFilter, NavDOMEvent, NavItem, Navigation } from '../@types/types'; +import type { FlagTagsFilter, NavItem, Navigation } from '../@types/types'; import type { AccessRequest } from './store'; import type { QuickStart } from '@patternfly/quickstarts'; @@ -18,9 +18,6 @@ export type AppNavClickItem = { id?: string; custom?: boolean }; /* *TODO: The event type is deliberately nonse. It will start failing once we mirate rest of the app and we will figure out the correct type */ -export function appNavClick(item: AppNavClickItem, event?: NavDOMEvent) { - return { type: actionTypes.APP_NAV_CLICK, payload: { ...(item || {}), id: item?.id, event } }; -} export function appAction(action: string) { return { type: actionTypes.CHROME_PAGE_ACTION, payload: action }; diff --git a/src/state/atoms/activeAppAtom.test.tsx b/src/state/atoms/activeAppAtom.test.tsx new file mode 100644 index 000000000..a8172c5dd --- /dev/null +++ b/src/state/atoms/activeAppAtom.test.tsx @@ -0,0 +1,139 @@ +import React, { useEffect, useState } from 'react'; +import { fireEvent, render, renderHook, screen, waitFor } from '@testing-library/react'; +import ChromeLink from '../../components/ChromeLink'; +import { activeNavListenersAtom, addNavListenerAtom, deleteNavListenerAtom, triggerNavListenersAtom } from './activeAppAtom'; +import { Provider as ProviderJotai, useAtomValue, useSetAtom } from 'jotai'; +import { useHydrateAtoms } from 'jotai/utils'; +import { MemoryRouter } from 'react-router-dom'; +import { NavDOMEvent } from '@redhat-cloud-services/types'; + +const HydrateAtoms = ({ initialValues, children }: { initialValues: any; children: React.ReactNode }) => { + useHydrateAtoms(initialValues); + return children; +}; + +const TestProvider = ({ initialValues, children }: { initialValues: any; children: React.ReactNode }) => ( + + {children} + +); + +test('addNavListenerAtom should add a listener', async () => { + const mockNavListener = jest.fn(); + + const MockComponent = () => { + const addNavListener = useSetAtom(addNavListenerAtom); + + useEffect(() => { + addNavListener(mockNavListener); + }, []); + + return ( + + + Add Event Listener + + + ); + }; + + const { getByText } = render( + + + + ); + + fireEvent.click(getByText('Add Event Listener')); + + await waitFor(() => { + expect(mockNavListener).toHaveBeenCalled(); + }); +}); + +test('deleteNavListenerAtom should remove a listener by id', async () => { + let listenerId: number; + const mockNavListener = jest.fn(); + + const MockComponent = () => { + const addNavListener = useSetAtom(addNavListenerAtom); + const deleteNavListener = useSetAtom(deleteNavListenerAtom); + + useEffect(() => { + listenerId = addNavListener(mockNavListener); + }, [addNavListener]); + + useEffect(() => { + if (listenerId) { + deleteNavListener(listenerId); + } + }, [deleteNavListener, listenerId]); + + return null; + }; + + render( + + + + ); + + await waitFor(() => { + const activeNavListeners = renderHook(() => useAtomValue(activeNavListenersAtom)).result.current; + expect(activeNavListeners[listenerId]).toBeUndefined(); + }); +}); + +test('triggerNavListenersAtom should call all activeListeners', async () => { + const mockNavListener1 = jest.fn(); + const mockNavListener2 = jest.fn(); + + const sampleNavEvent: { + nav: string; + domEvent: NavDOMEvent; + } = { + nav: 'sample-id', + domEvent: { + href: 'foo', + id: 'bar', + navId: 'baz', + type: 'quazz', + target: {} as any, + }, + }; + + const MockComponent = () => { + const triggerNavListeners = useSetAtom(triggerNavListenersAtom); + return ( + + ); + }; + + await render( + + + + ); + + await fireEvent.click(screen.getByText('Foo')); + + await waitFor(() => { + expect(mockNavListener1).toHaveBeenCalledWith(sampleNavEvent); + expect(mockNavListener2).toHaveBeenCalledWith(sampleNavEvent); + }); +}); diff --git a/src/state/atoms/activeAppAtom.ts b/src/state/atoms/activeAppAtom.ts index b89b8753e..36a53ef7a 100644 --- a/src/state/atoms/activeAppAtom.ts +++ b/src/state/atoms/activeAppAtom.ts @@ -1,3 +1,41 @@ +import { NavDOMEvent } from '@redhat-cloud-services/types'; import { atom } from 'jotai'; +export type NavEvent = { navId?: string; domEvent: NavDOMEvent }; +export type NavListener = (navEvent: NavEvent) => void; + export const activeAppAtom = atom(undefined); +export const activeNavListenersAtom = atom<{ [listenerId: number]: NavListener | undefined }>({}); +export const addNavListenerAtom = atom(null, (_get, set, navListener: NavListener) => { + const listenerId = Date.now(); + set(activeNavListenersAtom, (prev) => { + return { ...prev, [listenerId]: navListener }; + }); + return listenerId; +}); +export const deleteNavListenerAtom = atom(null, (get, set, id: number) => { + set(activeNavListenersAtom, (prev) => { + return { ...prev, [id]: undefined }; + }); +}); + +export const triggerNavListenersAtom = atom(null, (get, _set, event: NavEvent) => { + const activeNavListeners = get(activeNavListenersAtom); + Object.values(activeNavListeners).forEach((el) => { + el?.(event); + }); +}); + +// APP_NAVIGATION: [ +// (callback: (navEvent: { navId?: string; domEvent: NavDOMEvent }) => void) => { +// const appNavListener: Listener<{ event: NavDOMEvent; id?: string }> = { +// on: 'APP_NAV_CLICK', +// callback: ({ data }) => { +// if (data.id !== undefined || data.event) { +// callback({ navId: data.id, domEvent: data.event }); +// } +// }, +// }; +// return appNavListener; +// }, +// ], diff --git a/src/utils/consts.ts b/src/utils/consts.ts index 4b13bd206..2f07236d3 100644 --- a/src/utils/consts.ts +++ b/src/utils/consts.ts @@ -1,7 +1,7 @@ import { ITLess } from './common'; import { AppNavigationCB, ChromeAuthOptions, GenericCB, NavDOMEvent } from '../@types/types'; import { Listener } from '@redhat-cloud-services/frontend-components-utilities/MiddlewareListener'; -import { APP_NAV_CLICK, GLOBAL_FILTER_UPDATE } from '../redux/action-types'; +import { GLOBAL_FILTER_UPDATE } from '../redux/action-types'; export const noAuthParam = 'noauth'; export const offlineToken = '2402500adeacc30eb5c5a8a5e2e0ec1f'; @@ -72,23 +72,9 @@ export const defaultAuthOptions: ChromeAuthOptions = { export const OFFLINE_REDIRECT_STORAGE_KEY = 'chrome.offline.redirectUri'; export const PUBLIC_EVENTS: { - APP_NAVIGATION: [(callback: AppNavigationCB) => Listener]; NAVIGATION_TOGGLE: [(callback: GenericCB) => Listener]; GLOBAL_FILTER_UPDATE: [(callback: GenericCB) => Listener, string]; } = { - APP_NAVIGATION: [ - (callback: (navEvent: { navId?: string; domEvent: NavDOMEvent }) => void) => { - const appNavListener: Listener<{ event: NavDOMEvent; id?: string }> = { - on: APP_NAV_CLICK, - callback: ({ data }) => { - if (data.id !== undefined || data.event) { - callback({ navId: data.id, domEvent: data.event }); - } - }, - }; - return appNavListener; - }, - ], NAVIGATION_TOGGLE: [ (callback: (...args: unknown[]) => void) => { console.error('NAVIGATION_TOGGLE event is deprecated and will be removed in future versions of chrome.'); From b580aa96c2db4e76976108e060daddbda0edc4eb Mon Sep 17 00:00:00 2001 From: Adam Jetmar Date: Thu, 27 Jun 2024 14:43:06 +0200 Subject: [PATCH 033/151] Fix CI bugs --- src/components/ChromeLink/ChromeLink.test.js | 100 ------------------- src/components/RootApp/ScalprumRoot.tsx | 6 -- src/state/atoms/activeAppAtom.test.tsx | 2 +- src/utils/consts.ts | 2 +- 4 files changed, 2 insertions(+), 108 deletions(-) diff --git a/src/components/ChromeLink/ChromeLink.test.js b/src/components/ChromeLink/ChromeLink.test.js index 580b9839e..daae5bba6 100644 --- a/src/components/ChromeLink/ChromeLink.test.js +++ b/src/components/ChromeLink/ChromeLink.test.js @@ -47,106 +47,6 @@ describe('ChromeLink', () => { expect(getAllByTestId('router-link')).toHaveLength(1); }); - // const store = mockStore({ - // chrome: { - // moduleRoutes: [], - // activeModule: 'testModule', - // modules: { - // testModule: {}, - // }, - // }, - // }); - // const { - // container: { firstElementChild: buttton }, - // } = render( - // - // Test module link - // - // ); - - // act(() => { - // fireEvent.click(buttton); - // }); - - // expect(store.getActions()).toEqual([ - // { - // type: 'APP_NAV_CLICK', - // payload: { - // id: '/', - // event: { - // id: '/', - // navId: '/', - // href: '/insights/foo', - // type: 'click', - // target: expect.any(Element), - // }, - // }, - // }, - // ]); - // let chromeContext: Context; - // let contextValue: ChromeAPI; - - // const Wrapper = () => { - // const addNavListener = useSetAtom(addNavListenerAtom); - // return ( - // - // 'stage' } as any}> - // - // - // - // ); - // }; - // render(); - // const button = screen.getByText('Add NavListener'); - // fireEvent.click(button); - - // expect(mockNavListener).not.toHaveBeenCalled(); - - // mockNavListener(sampleNavEvent); - // expect(mockNavListener).toHaveBeenCalledWith(sampleNavEvent); - - - // test('should dispatch appNavClick with correct actionId for nested route', () => { - // const store = mockStore({ - // chrome: { - // moduleRoutes: [], - // activeModule: 'testModule', - // modules: { - // testModule: {}, - // }, - // }, - // }); - // const { - // container: { firstElementChild: buttton }, - // } = render( - // - // - // Test module link - // - // - // ); - - // act(() => { - // fireEvent.click(buttton); - // }); - - // expect(store.getActions()).toEqual([ - // { - // type: 'APP_NAV_CLICK', - // payload: { - // id: 'bar', - // event: { - // id: 'bar', - // navId: 'bar', - // href: '/insights/foo/bar', - // type: 'click', - // target: expect.any(Element), - // }, - // }, - // }, - // ]); - // }); - test('should not trigger onLinkClick callback', () => { const onLinkClickSpy = jest.fn(); const store = mockStore({ diff --git a/src/components/RootApp/ScalprumRoot.tsx b/src/components/RootApp/ScalprumRoot.tsx index 7942f3520..e1947b9e6 100644 --- a/src/components/RootApp/ScalprumRoot.tsx +++ b/src/components/RootApp/ScalprumRoot.tsx @@ -35,11 +35,8 @@ import ChromeAuthContext from '../../auth/ChromeAuthContext'; import { onRegisterModuleWriteAtom } from '../../state/atoms/chromeModuleAtom'; import useTabName from '../../hooks/useTabName'; import { NotificationData, notificationDrawerDataAtom } from '../../state/atoms/notificationDrawerAtom'; -<<<<<<< HEAD import { isPreviewAtom } from '../../state/atoms/releaseAtom'; -======= import { addNavListenerAtom, deleteNavListenerAtom } from '../../state/atoms/activeAppAtom'; ->>>>>>> febf850c (Update activeApp atoms and tests) const ProductSelection = lazy(() => import('../Stratosphere/ProductSelection')); @@ -62,12 +59,9 @@ const ScalprumRoot = memo( const chromeAuth = useContext(ChromeAuthContext); const registerModule = useSetAtom(onRegisterModuleWriteAtom); const populateNotifications = useSetAtom(notificationDrawerDataAtom); -<<<<<<< HEAD const isPreview = useAtomValue(isPreviewAtom); -======= const addNavListener = useSetAtom(addNavListenerAtom); const deleteNavListener = useSetAtom(deleteNavListenerAtom); ->>>>>>> febf850c (Update activeApp atoms and tests) const store = useStore(); const mutableChromeApi = useRef(); diff --git a/src/state/atoms/activeAppAtom.test.tsx b/src/state/atoms/activeAppAtom.test.tsx index a8172c5dd..874102387 100644 --- a/src/state/atoms/activeAppAtom.test.tsx +++ b/src/state/atoms/activeAppAtom.test.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect } from 'react'; import { fireEvent, render, renderHook, screen, waitFor } from '@testing-library/react'; import ChromeLink from '../../components/ChromeLink'; import { activeNavListenersAtom, addNavListenerAtom, deleteNavListenerAtom, triggerNavListenersAtom } from './activeAppAtom'; diff --git a/src/utils/consts.ts b/src/utils/consts.ts index 2f07236d3..d9ef4871e 100644 --- a/src/utils/consts.ts +++ b/src/utils/consts.ts @@ -1,5 +1,5 @@ import { ITLess } from './common'; -import { AppNavigationCB, ChromeAuthOptions, GenericCB, NavDOMEvent } from '../@types/types'; +import { ChromeAuthOptions, GenericCB } from '../@types/types'; import { Listener } from '@redhat-cloud-services/frontend-components-utilities/MiddlewareListener'; import { GLOBAL_FILTER_UPDATE } from '../redux/action-types'; From 65d6566dad029fbcf0ff026f27894aeac70a1132 Mon Sep 17 00:00:00 2001 From: Martin Marosi Date: Tue, 18 Jun 2024 13:31:28 +0200 Subject: [PATCH 034/151] Relog user after JWT token expired. --- .../ErrorComponents/DefaultErrorComponent.tsx | 3 ++- .../ErrorComponents/ErrorBoundary.tsx | 3 ++- src/utils/iqeEnablement.ts | 4 +-- src/utils/responseInterceptors.ts | 25 +++++++++++++++++-- 4 files changed, 29 insertions(+), 6 deletions(-) diff --git a/src/components/ErrorComponents/DefaultErrorComponent.tsx b/src/components/ErrorComponents/DefaultErrorComponent.tsx index 8ede6a381..d60105ce8 100644 --- a/src/components/ErrorComponents/DefaultErrorComponent.tsx +++ b/src/components/ErrorComponents/DefaultErrorComponent.tsx @@ -24,6 +24,7 @@ export type DefaultErrorComponentProps = { errorInfo?: { componentStack?: string; }; + signIn?: () => Promise; }; const DefaultErrorComponent = (props: DefaultErrorComponentProps) => { @@ -66,7 +67,7 @@ const DefaultErrorComponent = (props: DefaultErrorComponentProps) => { }, [props.error, activeModule]); // second level of error capture if xhr/fetch interceptor fails - const gatewayError = get3scaleError(props.error as any); + const gatewayError = get3scaleError(props.error as any, props.signIn); if (gatewayError) { return ; } diff --git a/src/components/ErrorComponents/ErrorBoundary.tsx b/src/components/ErrorComponents/ErrorBoundary.tsx index 4c87766f8..86dad8bf3 100644 --- a/src/components/ErrorComponents/ErrorBoundary.tsx +++ b/src/components/ErrorComponents/ErrorBoundary.tsx @@ -11,6 +11,7 @@ type ErrorBoundaryState = { class ErrorBoundary extends React.Component< { children: React.ReactNode; + singIn?: () => Promise; }, ErrorBoundaryState > { @@ -33,7 +34,7 @@ class ErrorBoundary extends React.Component< render() { if (this.state.hasError) { - return ; + return ; } return this.props.children; diff --git a/src/utils/iqeEnablement.ts b/src/utils/iqeEnablement.ts index 0368b4dae..10a8133a3 100644 --- a/src/utils/iqeEnablement.ts +++ b/src/utils/iqeEnablement.ts @@ -130,7 +130,7 @@ export function init(chromeStore: ReturnType, authRef: React } this.onload = function () { if (this.status >= 400) { - const gatewayError = get3scaleError(this.response); + const gatewayError = get3scaleError(this.response, authRef.current.signinRedirect); if (this.status === 403 && this.responseText.includes(DENIED_CROSS_CHECK)) { crossAccountBouncer(); // check for 3scale error @@ -178,7 +178,7 @@ export function init(chromeStore: ReturnType, authRef: React try { const isJson = resCloned?.headers?.get('content-type')?.includes('application/json'); const data = isJson ? await resCloned.json() : await resCloned.text(); - const gatewayError = get3scaleError(data); + const gatewayError = get3scaleError(data, authRef.current.signinRedirect); if (gatewayError) { chromeStore.set(gatewayErrorAtom, gatewayError); } diff --git a/src/utils/responseInterceptors.ts b/src/utils/responseInterceptors.ts index 534cf1dcf..22c7f8c87 100644 --- a/src/utils/responseInterceptors.ts +++ b/src/utils/responseInterceptors.ts @@ -1,9 +1,24 @@ -export type ThreeScaleError = { complianceError?: boolean; status: number; source?: string; detail: string; meta?: { response_by: string } }; +// eslint-disable-next-line no-restricted-imports +import { AuthContextProps } from 'react-oidc-context'; + +export type ThreeScaleError = { + data?: string; + complianceError?: boolean; + status: number; + source?: string; + detail: string; + meta?: { response_by: string }; +}; export const COMPLIACE_ERROR_CODES = ['ERROR_OFAC', 'ERROR_T5', 'ERROR_EXPORT_CONTROL']; const errorCodeRegexp = new RegExp(`(${COMPLIACE_ERROR_CODES.join('|')})`); -export function get3scaleError(response: string | { errors: ThreeScaleError[] }) { +export function get3scaleError(response: string | { errors: ThreeScaleError[] }, signIn?: AuthContextProps['signinRedirect']) { + if (signIn && typeof response !== 'string' && isTokenExpiredError(response)) { + signIn(); + return; + } + // attempt to parse XHR response let parsedResponse: ThreeScaleError[]; try { @@ -40,3 +55,9 @@ export function get3scaleError(response: string | { errors: ThreeScaleError[] }) function isComplianceError(response = '') { return !!response.match(errorCodeRegexp); } + +const TOKEN_EXPIRED_MATCHER = `Invalid JWT token - 'exp' claim expired at`; + +function isTokenExpiredError(error?: { errors?: ThreeScaleError[] }) { + return error?.errors?.find(({ status, detail }) => status === 401 && detail?.includes(TOKEN_EXPIRED_MATCHER)); +} From 8367904b54a986cef988009dc3c05bef06ea5cfb Mon Sep 17 00:00:00 2001 From: Adam Jetmar Date: Fri, 28 Jun 2024 12:25:28 +0200 Subject: [PATCH 035/151] Remove old code --- src/components/ChromeLink/ChromeLink.tsx | 1 - src/state/atoms/activeAppAtom.ts | 16 +--------------- 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/src/components/ChromeLink/ChromeLink.tsx b/src/components/ChromeLink/ChromeLink.tsx index a716d0a3a..d955cae50 100644 --- a/src/components/ChromeLink/ChromeLink.tsx +++ b/src/components/ChromeLink/ChromeLink.tsx @@ -71,7 +71,6 @@ const LinkWrapper: React.FC = memo( * Add reference to the DOM link element */ domEvent.target = linkRef.current; - // dispatch(appNavClick({ id: actionId }, domEvent)); triggerNavListener({ navId: actionId, domEvent }); }; diff --git a/src/state/atoms/activeAppAtom.ts b/src/state/atoms/activeAppAtom.ts index 36a53ef7a..b43fccce3 100644 --- a/src/state/atoms/activeAppAtom.ts +++ b/src/state/atoms/activeAppAtom.ts @@ -24,18 +24,4 @@ export const triggerNavListenersAtom = atom(null, (get, _set, event: NavEvent) = Object.values(activeNavListeners).forEach((el) => { el?.(event); }); -}); - -// APP_NAVIGATION: [ -// (callback: (navEvent: { navId?: string; domEvent: NavDOMEvent }) => void) => { -// const appNavListener: Listener<{ event: NavDOMEvent; id?: string }> = { -// on: 'APP_NAV_CLICK', -// callback: ({ data }) => { -// if (data.id !== undefined || data.event) { -// callback({ navId: data.id, domEvent: data.event }); -// } -// }, -// }; -// return appNavListener; -// }, -// ], +}); \ No newline at end of file From c30af18f377b9eabc691bf1b5bc44892b510bfc2 Mon Sep 17 00:00:00 2001 From: Adam Jetmar Date: Fri, 28 Jun 2024 12:30:54 +0200 Subject: [PATCH 036/151] Add space --- src/state/atoms/activeAppAtom.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/state/atoms/activeAppAtom.ts b/src/state/atoms/activeAppAtom.ts index b43fccce3..4e3879003 100644 --- a/src/state/atoms/activeAppAtom.ts +++ b/src/state/atoms/activeAppAtom.ts @@ -24,4 +24,4 @@ export const triggerNavListenersAtom = atom(null, (get, _set, event: NavEvent) = Object.values(activeNavListeners).forEach((el) => { el?.(event); }); -}); \ No newline at end of file +}); From 7f55da41eb90bd72bca6671409546a28cd2602ab Mon Sep 17 00:00:00 2001 From: Martin Marosi Date: Fri, 28 Jun 2024 11:57:23 +0200 Subject: [PATCH 037/151] Update minor dependecy versions. --- .../NotificationDrawer.cy.tsx | 8 +- jest.config.js | 3 - package-lock.json | 3395 +++++------------ package.json | 112 +- .../initializeAccessRequestCookies.test.ts | 7 + .../DrawerPanelContent.tsx | 1 - src/redux/redux-config.ts | 3 +- 7 files changed, 1106 insertions(+), 2423 deletions(-) diff --git a/cypress/component/NotificationDrawer/NotificationDrawer.cy.tsx b/cypress/component/NotificationDrawer/NotificationDrawer.cy.tsx index f169a420c..46278c4ad 100644 --- a/cypress/component/NotificationDrawer/NotificationDrawer.cy.tsx +++ b/cypress/component/NotificationDrawer/NotificationDrawer.cy.tsx @@ -112,8 +112,8 @@ describe('Notification Drawer', () => { cy.get('#drawer-toggle').click(); cy.get('.pf-m-read').should('have.length', 0); // select all notifications - cy.get('[aria-label="notifications-bulk-select"]').click(); - cy.get('[data-ouia-component-id="notifications-bulk-select-select-all"]').click(); + cy.get('[data-ouia-component-id="BulkSelect"]').click(); + cy.get('[data-ouia-component-id="BulkSelectList-select-all"]').click(); // mark selected as read cy.get('#notifications-actions-toggle').click(); cy.contains('Mark selected as read').click(); @@ -129,8 +129,8 @@ describe('Notification Drawer', () => { cy.get('#drawer-toggle').click(); cy.get('.pf-m-read').should('have.length', 3); // select all notifications - cy.get('[aria-label="notifications-bulk-select"]').click(); - cy.get('[data-ouia-component-id="notifications-bulk-select-select-all"]').click(); + cy.get('[data-ouia-component-id="BulkSelect"]').click(); + cy.get('[data-ouia-component-id="BulkSelectList-select-all"]').click(); // mark selected as unread cy.get('#notifications-actions-toggle').click(); cy.contains('Mark selected as unread').click(); diff --git a/jest.config.js b/jest.config.js index f6e729b95..36338691d 100644 --- a/jest.config.js +++ b/jest.config.js @@ -18,9 +18,6 @@ module.exports = { $schema: 'http://json.schemastore.org/swcrc', jsc: { - experimental: { - plugins: [['jest_workaround', {}]], - }, parser: { jsx: true, syntax: 'typescript', diff --git a/package-lock.json b/package-lock.json index 57d313542..244dc10e8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,132 +10,132 @@ "hasInstallScript": true, "license": "MIT", "dependencies": { - "@data-driven-forms/pf4-component-mapper": "^3.22.1", - "@data-driven-forms/react-form-renderer": "^3.22.1", + "@data-driven-forms/pf4-component-mapper": "^3.22.4", + "@data-driven-forms/react-form-renderer": "^3.22.4", "@formatjs/cli": "4.8.4", "@openshift/dynamic-plugin-sdk": "^5.0.1", - "@orama/orama": "^2.0.3", - "@patternfly/patternfly": "^5.3.0", - "@patternfly/quickstarts": "^5.4.0-prerelease.1", - "@patternfly/react-charts": "^7.3.0", - "@patternfly/react-core": "^5.3.0", - "@patternfly/react-icons": "^5.3.0", - "@patternfly/react-tokens": "^5.3.0", - "@redhat-cloud-services/chrome": "^1.0.9", + "@orama/orama": "^2.0.21", + "@patternfly/patternfly": "^5.3.1", + "@patternfly/quickstarts": "^5.4.0-prerelease.4", + "@patternfly/react-charts": "^7.3.1", + "@patternfly/react-core": "^5.3.3", + "@patternfly/react-icons": "^5.3.2", + "@patternfly/react-table": "^5.3.3", + "@patternfly/react-tokens": "^5.3.1", + "@redhat-cloud-services/chrome": "^1.0.10", "@redhat-cloud-services/entitlements-client": "1.2.0", - "@redhat-cloud-services/frontend-components": "^4.2.2", + "@redhat-cloud-services/frontend-components": "^4.2.11", "@redhat-cloud-services/frontend-components-notifications": "^4.1.0", - "@redhat-cloud-services/frontend-components-pdf-generator": "4.0.4", - "@redhat-cloud-services/frontend-components-utilities": "^4.0.2", + "@redhat-cloud-services/frontend-components-pdf-generator": "4.0.5", + "@redhat-cloud-services/frontend-components-utilities": "^4.0.13", "@redhat-cloud-services/host-inventory-client": "1.2.0", "@redhat-cloud-services/rbac-client": "1.2.0", "@scalprum/core": "^0.7.0", - "@scalprum/react-core": "^0.7.0", - "@segment/analytics-next": "^1.62.0", - "@sentry/react": "^7.91.0", - "@sentry/tracing": "^7.91.0", - "@types/intercom-web": "^2.8.24", + "@scalprum/react-core": "^0.8.0", + "@segment/analytics-next": "^1.70.0", + "@sentry/react": "^7.118.0", + "@sentry/tracing": "^7.118.0", + "@types/intercom-web": "^2.8.26", "@unleash/proxy-client-react": "^3.6.0", "abortcontroller-polyfill": "^1.7.5", - "axios": "^0.28.0", + "axios": "^0.28.1", "axios-cache-interceptor": "^0.10.7", "axios-mock-adapter": "^1.22.0", "broadcast-channel": "^4.20.2", "classnames": "^2.5.1", "commander": "^10.0.1", "history": "^5.3.0", - "jotai": "^2.6.1", + "jotai": "^2.8.4", "js-cookie": "^3.0.5", "js-yaml": "^4.1.0", "localforage": "^1.10.0", "lodash": "^4.17.21", "oidc-client-ts": "^2.4.0", "pf-4-styles": "npm:@patternfly/patternfly@^4.224.5", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-intl": "^6.5.5", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-intl": "^6.6.8", "react-oidc-context": "^2.3.1", "react-redux": "^8.1.3", - "react-router-dom": "^6.21.1", + "react-router-dom": "^6.24.0", "redux": "^4.2.1", "redux-promise-middleware": "^6.2.0", - "sanitize-html": "^2.12.1", + "sanitize-html": "^2.13.0", "title-case": "^3.0.3", "urijs": "^1.19.11" }, "devDependencies": { - "@cypress/code-coverage": "^3.12.16", + "@cypress/code-coverage": "^3.12.39", "@openshift/dynamic-plugin-sdk-webpack": "^3.0.1", - "@pmmmwh/react-refresh-webpack-plugin": "^0.5.11", + "@pmmmwh/react-refresh-webpack-plugin": "^0.5.15", "@redhat-cloud-services/eslint-config-redhat-cloud-services": "^1.3.0", - "@redhat-cloud-services/frontend-components-config-utilities": "^3.0.4", - "@redhat-cloud-services/types": "^1.0.10", + "@redhat-cloud-services/frontend-components-config-utilities": "^3.0.7", + "@redhat-cloud-services/types": "^1.0.11", "@simonsmith/cypress-image-snapshot": "^8.1.2", - "@swc/core": "^1.3.102", - "@swc/jest": "^0.2.29", + "@swc/core": "^1.6.5", + "@swc/jest": "^0.2.36", "@testing-library/jest-dom": "^5.17.0", - "@testing-library/react": "^14.1.2", + "@testing-library/react": "^14.3.1", "@testing-library/user-event": "^14.5.2", - "@types/jest": "^29.5.11", + "@types/jest": "^29.5.12", "@types/js-cookie": "^3.0.6", - "@types/lodash": "^4.14.202", - "@types/react": "^18.2.46", - "@types/react-dom": "^18.2.18", - "@types/redux-logger": "^3.0.12", + "@types/lodash": "^4.17.6", + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", + "@types/redux-logger": "3.0.12", "@types/redux-mock-store": "^1.0.6", - "@types/sanitize-html": "^2.9.5", + "@types/sanitize-html": "^2.11.0", "@types/urijs": "^1.19.25", - "@typescript-eslint/eslint-plugin": "^6.17.0", - "@typescript-eslint/parser": "^6.17.0", + "@typescript-eslint/eslint-plugin": "^6.21.0", + "@typescript-eslint/parser": "^6.21.0", "assert": "^2.1.0", "browserify-zlib": "^0.2.0", "buffer": "^6.0.3", "clean-webpack-plugin": "^4.0.0", - "css-loader": "^6.8.1", - "cypress": "^13.6.2", - "cypress-localstorage-commands": "^2.2.5", - "eslint": "^8.56.0", + "css-loader": "^6.11.0", + "cypress": "^13.12.0", + "cypress-localstorage-commands": "^2.2.6", + "eslint": "^8.57.0", "fork-ts-checker-webpack-plugin": "^7.3.0", "git-revision-webpack-plugin": "^5.0.0", "glob": "^7.2.3", "html-webpack-plugin": "^5.6.0", "identity-obj-proxy": "^3.0.0", "jest": "^29.7.0", - "jest_workaround": "^0.79.19", "jest-environment-jsdom": "^29.7.0", "jest-mock-axios": "^4.7.3", "jsdom": "^21.1.2", "jws": "^4.0.0", "madge": "^6.1.0", - "mini-css-extract-plugin": "^2.7.6", + "mini-css-extract-plugin": "^2.9.0", "mkdir": "^0.0.2", "ncp": "^2.0.0", "node-sass-package-importer": "^5.3.3", "npm-run-all": "^4.1.5", "path-browserify": "^1.0.1", "process": "^0.11.10", - "react-refresh": "^0.14.0", + "react-refresh": "^0.14.2", "redux-logger": "^3.0.6", "redux-mock-store": "^1.5.4", "resolve-url-loader": "^5.0.0", "rgb-hex": "^4.1.0", "rimraf": "^4.4.1", - "sass": "^1.69.6", + "sass": "^1.77.6", "sass-loader": "^13.3.3", "source-map-loader": "^4.0.2", "stream-browserify": "^3.0.0", - "style-loader": "^3.3.3", - "swc-loader": "^0.2.3", - "swc-plugin-coverage-instrument": "^0.0.20", + "style-loader": "^3.3.4", + "swc-loader": "^0.2.6", + "swc-plugin-coverage-instrument": "^0.0.21", "terser-webpack-plugin": "^5.3.10", - "typescript": "^5.3.3", + "typescript": "^5.5.2", "url": "^0.11.3", - "utility-types": "^3.10.0", + "utility-types": "^3.11.0", "wait-on": "^7.2.0", - "webpack": "^5.89.0", - "webpack-bundle-analyzer": "^4.10.1", + "webpack": "^5.92.1", + "webpack-bundle-analyzer": "^4.10.2", "webpack-cli": "^5.1.4", - "webpack-dev-server": "^4.15.1", + "webpack-dev-server": "^4.15.2", "whatwg-fetch": "^3.6.20", "yargs": "^17.7.2" }, @@ -1975,9 +1975,9 @@ } }, "node_modules/@cypress/code-coverage": { - "version": "3.12.16", - "resolved": "https://registry.npmjs.org/@cypress/code-coverage/-/code-coverage-3.12.16.tgz", - "integrity": "sha512-15/zoCVljS4npV/FyYzcDTeCsOMdKxwC5xgMt2oyD1SL0SgvkDzG+YWHOAuPivuGX8y8OqKdCjFROO69FFbeKg==", + "version": "3.12.39", + "resolved": "https://registry.npmjs.org/@cypress/code-coverage/-/code-coverage-3.12.39.tgz", + "integrity": "sha512-ja7I/GRmkSAW9e3O7pideWcNUEHao0WT6sRyXQEURoxkJUASJssJ7Kb/bd3eMYmkUCiD5CRFqWR5BGF4mWVaUw==", "dev": true, "dependencies": { "@cypress/webpack-preprocessor": "^6.0.0", @@ -2148,9 +2148,9 @@ } }, "node_modules/@data-driven-forms/common": { - "version": "3.22.1", - "resolved": "https://registry.npmjs.org/@data-driven-forms/common/-/common-3.22.1.tgz", - "integrity": "sha512-LhawxYmfKfODpC0q9o57+q2b6frER0W9PpDINUMxSCzMiTcrZXRC2XgPhkVyWiaTdWvzNzXcWCkbzcuIym2EJQ==", + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/@data-driven-forms/common/-/common-3.22.4.tgz", + "integrity": "sha512-r5mOk96OfbupWqF0Pi3NpP0djAg4q6sz+4KCH04xvLr+tVi7SswVOWejIfWoY70o0YYVLQM3enlkERNmoS5upA==", "dependencies": { "clsx": "^1.0.4", "lodash": "^4.17.15", @@ -2162,17 +2162,17 @@ } }, "node_modules/@data-driven-forms/pf4-component-mapper": { - "version": "3.22.1", - "resolved": "https://registry.npmjs.org/@data-driven-forms/pf4-component-mapper/-/pf4-component-mapper-3.22.1.tgz", - "integrity": "sha512-9rnUcSnN9yt62qDDOVRrqiVm3wLXL4KITB0njAiBg+yHUE2VkUufNkG5TYl7tLpeqcLIMJeB+1ljqXjN5bVo+g==", + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/@data-driven-forms/pf4-component-mapper/-/pf4-component-mapper-3.22.4.tgz", + "integrity": "sha512-bWGZP0/KoemeHfwuXLcveJ9yA3KeYgtXyGB+DlLg4mdaqYDAGu5e56qkXhkOHp/hKIUM9R0Hd4UedL/Z2zCEfw==", "dependencies": { - "@data-driven-forms/common": "^3.22.1", + "@data-driven-forms/common": "^3.22.4", "downshift": "^5.4.3", "lodash": "^4.17.21", "prop-types": "^15.7.2" }, "peerDependencies": { - "@data-driven-forms/react-form-renderer": "^3.22.1", + "@data-driven-forms/react-form-renderer": "^3.22.4", "@patternfly/react-core": "^5.0.0", "@patternfly/react-icons": "^5.0.0", "react": "^17.0.2 || ^18.0.0", @@ -2180,9 +2180,9 @@ } }, "node_modules/@data-driven-forms/react-form-renderer": { - "version": "3.22.1", - "resolved": "https://registry.npmjs.org/@data-driven-forms/react-form-renderer/-/react-form-renderer-3.22.1.tgz", - "integrity": "sha512-lDfTH85PRhSq6fF5fL03mHyj4eUVuQ9v5ZTXep7rDK6pOA8Klt80er92Q/ddu7Jd6zijX56edAE3FvnsB+pKIA==", + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/@data-driven-forms/react-form-renderer/-/react-form-renderer-3.22.4.tgz", + "integrity": "sha512-39tvEvBHn2vYtFaxigmgM26XZeec+xjxYzougi7eyr3QGheVvy8JO2nyzYBX1wKfyALhaonfQbi7m/ak1V0BNg==", "dependencies": { "final-form": "^4.20.4", "final-form-arrays": "^3.0.2", @@ -2319,9 +2319,9 @@ } }, "node_modules/@eslint/js": { - "version": "8.56.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", - "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -2494,20 +2494,20 @@ } }, "node_modules/@formatjs/intl": { - "version": "2.9.9", - "resolved": "https://registry.npmjs.org/@formatjs/intl/-/intl-2.9.9.tgz", - "integrity": "sha512-JI3CNgL2Zdg5lv9ncT2sYKqbAj2RGrCbdzaCckIxMPxn4QuHuOVvYUGmBAXVusBmfG/0sxLmMrnwnBioz+QKdA==", + "version": "2.10.4", + "resolved": "https://registry.npmjs.org/@formatjs/intl/-/intl-2.10.4.tgz", + "integrity": "sha512-56483O+HVcL0c7VucAS2tyH020mt9XTozZO67cwtGg0a7KWDukS/FzW3OnvaHmTHDuYsoPIzO+ZHVfU6fT/bJw==", "dependencies": { - "@formatjs/ecma402-abstract": "1.18.0", + "@formatjs/ecma402-abstract": "2.0.0", "@formatjs/fast-memoize": "2.2.0", - "@formatjs/icu-messageformat-parser": "2.7.3", - "@formatjs/intl-displaynames": "6.6.4", - "@formatjs/intl-listformat": "7.5.3", - "intl-messageformat": "10.5.8", + "@formatjs/icu-messageformat-parser": "2.7.8", + "@formatjs/intl-displaynames": "6.6.8", + "@formatjs/intl-listformat": "7.5.7", + "intl-messageformat": "10.5.14", "tslib": "^2.4.0" }, "peerDependencies": { - "typescript": "5" + "typescript": "^4.7 || 5" }, "peerDependenciesMeta": { "typescript": { @@ -2516,55 +2516,55 @@ } }, "node_modules/@formatjs/intl-displaynames": { - "version": "6.6.4", - "resolved": "https://registry.npmjs.org/@formatjs/intl-displaynames/-/intl-displaynames-6.6.4.tgz", - "integrity": "sha512-ET8KQ+L9Q0K8x1SnJQa4DNssUcbATlMopWqYvGGR8yAvw5qwAQc1fv+DshCoZNIE9pbcue0IGC4kWNAkWqlFag==", + "version": "6.6.8", + "resolved": "https://registry.npmjs.org/@formatjs/intl-displaynames/-/intl-displaynames-6.6.8.tgz", + "integrity": "sha512-Lgx6n5KxN16B3Pb05z3NLEBQkGoXnGjkTBNCZI+Cn17YjHJ3fhCeEJJUqRlIZmJdmaXQhjcQVDp6WIiNeRYT5g==", "dependencies": { - "@formatjs/ecma402-abstract": "1.18.0", - "@formatjs/intl-localematcher": "0.5.2", + "@formatjs/ecma402-abstract": "2.0.0", + "@formatjs/intl-localematcher": "0.5.4", "tslib": "^2.4.0" } }, "node_modules/@formatjs/intl-displaynames/node_modules/@formatjs/ecma402-abstract": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.18.0.tgz", - "integrity": "sha512-PEVLoa3zBevWSCZzPIM/lvPCi8P5l4G+NXQMc/CjEiaCWgyHieUoo0nM7Bs0n/NbuQ6JpXEolivQ9pKSBHaDlA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.0.0.tgz", + "integrity": "sha512-rRqXOqdFmk7RYvj4khklyqzcfQl9vEL/usogncBHRZfZBDOwMGuSRNFl02fu5KGHXdbinju+YXyuR+Nk8xlr/g==", "dependencies": { - "@formatjs/intl-localematcher": "0.5.2", + "@formatjs/intl-localematcher": "0.5.4", "tslib": "^2.4.0" } }, "node_modules/@formatjs/intl-displaynames/node_modules/@formatjs/intl-localematcher": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.2.tgz", - "integrity": "sha512-txaaE2fiBMagLrR4jYhxzFO6wEdEG4TPMqrzBAcbr4HFUYzH/YC+lg6OIzKCHm8WgDdyQevxbAAV1OgcXctuGw==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.4.tgz", + "integrity": "sha512-zTwEpWOzZ2CiKcB93BLngUX59hQkuZjT2+SAQEscSm52peDW/getsawMcWF1rGRpMCX6D7nSJA3CzJ8gn13N/g==", "dependencies": { "tslib": "^2.4.0" } }, "node_modules/@formatjs/intl-listformat": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/@formatjs/intl-listformat/-/intl-listformat-7.5.3.tgz", - "integrity": "sha512-l7EOr0Yh1m8KagytukB90yw81uyzrM7amKFrgxXqphz4KeSIL0KPa68lPsdtZ+JmQB73GaDQRwLOwUKFZ1VZPQ==", + "version": "7.5.7", + "resolved": "https://registry.npmjs.org/@formatjs/intl-listformat/-/intl-listformat-7.5.7.tgz", + "integrity": "sha512-MG2TSChQJQT9f7Rlv+eXwUFiG24mKSzmF144PLb8m8OixyXqn4+YWU+5wZracZGCgVTVmx8viCf7IH3QXoiB2g==", "dependencies": { - "@formatjs/ecma402-abstract": "1.18.0", - "@formatjs/intl-localematcher": "0.5.2", + "@formatjs/ecma402-abstract": "2.0.0", + "@formatjs/intl-localematcher": "0.5.4", "tslib": "^2.4.0" } }, "node_modules/@formatjs/intl-listformat/node_modules/@formatjs/ecma402-abstract": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.18.0.tgz", - "integrity": "sha512-PEVLoa3zBevWSCZzPIM/lvPCi8P5l4G+NXQMc/CjEiaCWgyHieUoo0nM7Bs0n/NbuQ6JpXEolivQ9pKSBHaDlA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.0.0.tgz", + "integrity": "sha512-rRqXOqdFmk7RYvj4khklyqzcfQl9vEL/usogncBHRZfZBDOwMGuSRNFl02fu5KGHXdbinju+YXyuR+Nk8xlr/g==", "dependencies": { - "@formatjs/intl-localematcher": "0.5.2", + "@formatjs/intl-localematcher": "0.5.4", "tslib": "^2.4.0" } }, "node_modules/@formatjs/intl-listformat/node_modules/@formatjs/intl-localematcher": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.2.tgz", - "integrity": "sha512-txaaE2fiBMagLrR4jYhxzFO6wEdEG4TPMqrzBAcbr4HFUYzH/YC+lg6OIzKCHm8WgDdyQevxbAAV1OgcXctuGw==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.4.tgz", + "integrity": "sha512-zTwEpWOzZ2CiKcB93BLngUX59hQkuZjT2+SAQEscSm52peDW/getsawMcWF1rGRpMCX6D7nSJA3CzJ8gn13N/g==", "dependencies": { "tslib": "^2.4.0" } @@ -2578,37 +2578,37 @@ } }, "node_modules/@formatjs/intl/node_modules/@formatjs/ecma402-abstract": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.18.0.tgz", - "integrity": "sha512-PEVLoa3zBevWSCZzPIM/lvPCi8P5l4G+NXQMc/CjEiaCWgyHieUoo0nM7Bs0n/NbuQ6JpXEolivQ9pKSBHaDlA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.0.0.tgz", + "integrity": "sha512-rRqXOqdFmk7RYvj4khklyqzcfQl9vEL/usogncBHRZfZBDOwMGuSRNFl02fu5KGHXdbinju+YXyuR+Nk8xlr/g==", "dependencies": { - "@formatjs/intl-localematcher": "0.5.2", + "@formatjs/intl-localematcher": "0.5.4", "tslib": "^2.4.0" } }, "node_modules/@formatjs/intl/node_modules/@formatjs/icu-messageformat-parser": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.7.3.tgz", - "integrity": "sha512-X/jy10V9S/vW+qlplqhMUxR8wErQ0mmIYSq4mrjpjDl9mbuGcCILcI1SUYkL5nlM4PJqpc0KOS0bFkkJNPxYRw==", + "version": "2.7.8", + "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.7.8.tgz", + "integrity": "sha512-nBZJYmhpcSX0WeJ5SDYUkZ42AgR3xiyhNCsQweFx3cz/ULJjym8bHAzWKvG5e2+1XO98dBYC0fWeeAECAVSwLA==", "dependencies": { - "@formatjs/ecma402-abstract": "1.18.0", - "@formatjs/icu-skeleton-parser": "1.7.0", + "@formatjs/ecma402-abstract": "2.0.0", + "@formatjs/icu-skeleton-parser": "1.8.2", "tslib": "^2.4.0" } }, "node_modules/@formatjs/intl/node_modules/@formatjs/icu-skeleton-parser": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.7.0.tgz", - "integrity": "sha512-Cfdo/fgbZzpN/jlN/ptQVe0lRHora+8ezrEeg2RfrNjyp+YStwBy7cqDY8k5/z2LzXg6O0AdzAV91XS0zIWv+A==", + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.2.tgz", + "integrity": "sha512-k4ERKgw7aKGWJZgTarIcNEmvyTVD9FYh0mTrrBMHZ1b8hUu6iOJ4SzsZlo3UNAvHYa+PnvntIwRPt1/vy4nA9Q==", "dependencies": { - "@formatjs/ecma402-abstract": "1.18.0", + "@formatjs/ecma402-abstract": "2.0.0", "tslib": "^2.4.0" } }, "node_modules/@formatjs/intl/node_modules/@formatjs/intl-localematcher": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.2.tgz", - "integrity": "sha512-txaaE2fiBMagLrR4jYhxzFO6wEdEG4TPMqrzBAcbr4HFUYzH/YC+lg6OIzKCHm8WgDdyQevxbAAV1OgcXctuGw==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.4.tgz", + "integrity": "sha512-zTwEpWOzZ2CiKcB93BLngUX59hQkuZjT2+SAQEscSm52peDW/getsawMcWF1rGRpMCX6D7nSJA3CzJ8gn13N/g==", "dependencies": { "tslib": "^2.4.0" } @@ -2629,13 +2629,14 @@ } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.13", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", - "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "deprecated": "Use @eslint/config-array instead", "dev": true, "dependencies": { - "@humanwhocodes/object-schema": "^2.0.1", - "debug": "^4.1.1", + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", "minimatch": "^3.0.5" }, "engines": { @@ -2656,9 +2657,10 @@ } }, "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", - "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", "dev": true }, "node_modules/@istanbuljs/load-nyc-config": { @@ -2786,32 +2788,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/console/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/console/node_modules/@types/yargs": { - "version": "17.0.32", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", - "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, "node_modules/@jest/console/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -2929,32 +2905,6 @@ } } }, - "node_modules/@jest/core/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/core/node_modules/@types/yargs": { - "version": "17.0.32", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", - "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, "node_modules/@jest/core/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -3058,15 +3008,15 @@ } }, "node_modules/@jest/create-cache-key-function": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/create-cache-key-function/-/create-cache-key-function-27.5.1.tgz", - "integrity": "sha512-dmH1yW+makpTSURTy8VzdUwFnfQh1G8R+DxO2Ho2FFmBbKFEVm+3jWdvFhE2VqB/LATCTokkP0dotjyQyw5/AQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/create-cache-key-function/-/create-cache-key-function-29.7.0.tgz", + "integrity": "sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA==", "dev": true, "dependencies": { - "@jest/types": "^27.5.1" + "@jest/types": "^29.6.3" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/environment": { @@ -3084,102 +3034,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/environment/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/environment/node_modules/@types/yargs": { - "version": "17.0.32", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", - "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@jest/environment/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/environment/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/environment/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/environment/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/environment/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/environment/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@jest/expect": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", @@ -3222,33 +3076,65 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/fake-timers/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", "dev": true, "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/fake-timers/node_modules/@types/yargs": { - "version": "17.0.32", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", - "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", "dev": true, "dependencies": { - "@types/yargs-parser": "*" + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/@jest/fake-timers/node_modules/ansi-styles": { + "node_modules/@jest/reporters/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", @@ -3263,7 +3149,7 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@jest/fake-timers/node_modules/chalk": { + "node_modules/@jest/reporters/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", @@ -3279,7 +3165,7 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@jest/fake-timers/node_modules/color-convert": { + "node_modules/@jest/reporters/node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", @@ -3291,13 +3177,13 @@ "node": ">=7.0.0" } }, - "node_modules/@jest/fake-timers/node_modules/color-name": { + "node_modules/@jest/reporters/node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "node_modules/@jest/fake-timers/node_modules/has-flag": { + "node_modules/@jest/reporters/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", @@ -3306,7 +3192,7 @@ "node": ">=8" } }, - "node_modules/@jest/fake-timers/node_modules/supports-color": { + "node_modules/@jest/reporters/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", @@ -3318,277 +3204,27 @@ "node": ">=8" } }, - "node_modules/@jest/globals": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", - "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", "dev": true, "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/types": "^29.6.3", - "jest-mock": "^29.7.0" + "@sinclair/typebox": "^0.27.8" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/globals/node_modules/@jest/types": { + "node_modules/@jest/source-map": { "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", "dev": true, "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/globals/node_modules/@types/yargs": { - "version": "17.0.32", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", - "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@jest/globals/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/globals/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/globals/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/globals/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/globals/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/globals/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/reporters": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", - "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", - "dev": true, - "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^6.0.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0", - "v8-to-istanbul": "^9.0.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/reporters/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/reporters/node_modules/@types/yargs": { - "version": "17.0.32", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", - "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@jest/reporters/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/reporters/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/reporters/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/reporters/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/reporters/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/reporters/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "dev": true, - "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/source-map": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", - "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", - "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.18", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -3600,109 +3236,13 @@ "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", "dev": true, "dependencies": { - "@jest/console": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/test-result/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/test-result/node_modules/@types/yargs": { - "version": "17.0.32", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", - "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@jest/test-result/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/test-result/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/test-result/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/test-result/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/test-result/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/test-result/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/test-sequencer": { @@ -3746,32 +3286,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/transform/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/transform/node_modules/@types/yargs": { - "version": "17.0.32", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", - "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, "node_modules/@jest/transform/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -3843,19 +3357,20 @@ } }, "node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", "dev": true, "dependencies": { + "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", - "@types/yargs": "^16.0.0", + "@types/yargs": "^17.0.8", "chalk": "^4.0.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/types/node_modules/ansi-styles": { @@ -4112,22 +3627,22 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/@orama/orama": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@orama/orama/-/orama-2.0.3.tgz", - "integrity": "sha512-8BXTrXqP+kcyIExipZyf6voB3pzGPREh1BUrIqEP7V4PJwN/SnEcLJsafyPiPFM23fPSyH9krwLrXzvisLL19A==", + "version": "2.0.21", + "resolved": "https://registry.npmjs.org/@orama/orama/-/orama-2.0.21.tgz", + "integrity": "sha512-XZpyjyNw4Mcpg94+gUvxmyyiFTZAdXvJ1aFPqHABarofu86oNwZL5tQUIj84xa1lrQiaeSxNKNBw0w4y5We5Yg==", "engines": { "node": ">= 16.0.0" } }, "node_modules/@patternfly/patternfly": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/@patternfly/patternfly/-/patternfly-5.3.0.tgz", - "integrity": "sha512-93uWA15bOJDgu8NF2iReWbbNtWdtM+v7iaDpK33mJChgej+whiFpGLtQPI2jFk1aVW3rDpbt4qm4OaNinpzSsg==" + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@patternfly/patternfly/-/patternfly-5.3.1.tgz", + "integrity": "sha512-KYIr9pKRTzHZNGuDuaa5j5CaZyLltvotPFGG1BiJalBDBGSOyk0BZCgHLowm4txKZXrLhorEuuv9XLrMQL8eoA==" }, "node_modules/@patternfly/quickstarts": { - "version": "5.4.0-prerelease.1", - "resolved": "https://registry.npmjs.org/@patternfly/quickstarts/-/quickstarts-5.4.0-prerelease.1.tgz", - "integrity": "sha512-Sl9LdZh2mbk1q2NaEG6Tmopl9KbUoKKl9jgQU9zwBaI+u5x7ZiVbD2Q0uAyliraKQNZPUs86TA0roAeYh3iFeg==", + "version": "5.4.0-prerelease.4", + "resolved": "https://registry.npmjs.org/@patternfly/quickstarts/-/quickstarts-5.4.0-prerelease.4.tgz", + "integrity": "sha512-ezRTU6St4Gy7dYWxg/a7VvD4ZQJ/76KzaoFMxiB3ZgMnmMmmYWAJYPv0V0UJhQO07D8182PGf8G7XTrKA4b6bQ==", "dependencies": { "@patternfly/react-catalog-view-extension": "^5.0.0", "dompurify": "^2.2.6", @@ -4154,12 +3669,12 @@ } }, "node_modules/@patternfly/react-charts": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/@patternfly/react-charts/-/react-charts-7.3.0.tgz", - "integrity": "sha512-J6d/bFolI3zUOvJoK4lEveNeXZeJNfBq+iXgQ/mImESyW0H7MSebMcVB4d+NC6JX0QykuaOEn/7YMJMU9K73tw==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@patternfly/react-charts/-/react-charts-7.3.1.tgz", + "integrity": "sha512-2E2KxfrlSOyTiK2+51tx9BOJSyvrf88Bq0ErHAzy9XKEf6JHmFIvJXSGbmrhCycJoFHR1LZqQukLyPASKiHtpw==", "dependencies": { - "@patternfly/react-styles": "^5.3.0", - "@patternfly/react-tokens": "^5.3.0", + "@patternfly/react-styles": "^5.3.1", + "@patternfly/react-tokens": "^5.3.1", "hoist-non-react-statics": "^3.3.0", "lodash": "^4.17.21", "tslib": "^2.5.0", @@ -4211,13 +3726,13 @@ } }, "node_modules/@patternfly/react-core": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/@patternfly/react-core/-/react-core-5.3.0.tgz", - "integrity": "sha512-nMf8yrul3u+4+ch7IMsE+/3Rzmor8/yEUk8zzD9bYGRxjwniu1RqCF8NdgPvMw2C7Hz7xtpwsgXDfG4n8qd12g==", + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/@patternfly/react-core/-/react-core-5.3.3.tgz", + "integrity": "sha512-qq3j0M+Vi+Xmd+a/MhRhGgjdRh9Hnm79iA+L935HwMIVDcIWRYp6Isib/Ha4+Jk+f3Qdl0RT3dBDvr/4m6OpVQ==", "dependencies": { - "@patternfly/react-icons": "^5.3.0", - "@patternfly/react-styles": "^5.3.0", - "@patternfly/react-tokens": "^5.3.0", + "@patternfly/react-icons": "^5.3.2", + "@patternfly/react-styles": "^5.3.1", + "@patternfly/react-tokens": "^5.3.1", "focus-trap": "7.5.2", "react-dropzone": "^14.2.3", "tslib": "^2.5.0" @@ -4228,28 +3743,28 @@ } }, "node_modules/@patternfly/react-icons": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/@patternfly/react-icons/-/react-icons-5.3.0.tgz", - "integrity": "sha512-oBdaK4Gz7yivNE7jQg46sPzfZakg7oxo5aSMLc0N6haOmDEegiTurNex+h+/z0oBPqzZC+cIQRaBeXEgXGwc9Q==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/@patternfly/react-icons/-/react-icons-5.3.2.tgz", + "integrity": "sha512-GEygYbl0H4zD8nZuTQy2dayKIrV2bMMeWKSOEZ16Y3EYNgYVUOUnN+J0naAEuEGH39Xb1DE9n+XUbE1PC4CxPA==", "peerDependencies": { "react": "^17 || ^18", "react-dom": "^17 || ^18" } }, "node_modules/@patternfly/react-styles": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/@patternfly/react-styles/-/react-styles-5.3.0.tgz", - "integrity": "sha512-/EdkURW+v7Rzw/CiEqL+NfGtLvLMGIwOEyDhvlMDbRip2usGw4HLZv3Bep0cJe29zOeY27cDVZDM1HfyXLebtw==" + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@patternfly/react-styles/-/react-styles-5.3.1.tgz", + "integrity": "sha512-H6uBoFH3bJjD6PP75qZ4k+2TtF59vxf9sIVerPpwrGJcRgBZbvbMZCniSC3+S2LQ8DgXLnDvieq78jJzHz0hiA==" }, "node_modules/@patternfly/react-table": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@patternfly/react-table/-/react-table-5.1.2.tgz", - "integrity": "sha512-C+ctkW6oWmdVhGv1rawVlo54baSu5G3ja3ZDtBjVsgMmpsGD0GIBXpvwtFO+OJVeY7T6qXHInMyuW3QNz/0rog==", + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/@patternfly/react-table/-/react-table-5.3.3.tgz", + "integrity": "sha512-uaRmsJABvVPH8gYTh+EUcDz61knIxe9qor/VGUYDLONYBL5G3IaltwG42IsJ9jShxiwFmIPy+QARPpaadTpv5w==", "dependencies": { - "@patternfly/react-core": "^5.1.2", - "@patternfly/react-icons": "^5.1.2", - "@patternfly/react-styles": "^5.1.2", - "@patternfly/react-tokens": "^5.1.2", + "@patternfly/react-core": "^5.3.3", + "@patternfly/react-icons": "^5.3.2", + "@patternfly/react-styles": "^5.3.1", + "@patternfly/react-tokens": "^5.3.1", "lodash": "^4.17.19", "tslib": "^2.5.0" }, @@ -4259,24 +3774,22 @@ } }, "node_modules/@patternfly/react-tokens": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/@patternfly/react-tokens/-/react-tokens-5.3.0.tgz", - "integrity": "sha512-24ZY5hgwt11InW3XtINM5p9Fo1hDiVor6Q4uphPZh8Mt89AsZZw1UweTaGg54I0Ah2Wzv6rkQy51LX7tZtIwjQ==" + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@patternfly/react-tokens/-/react-tokens-5.3.1.tgz", + "integrity": "sha512-VYK0uVP2/2RJ7ZshJCCLeq0Boih5I1bv+9Z/Bg6h12dCkLs85XsxAX9Ve+BGIo5DF54/mzcRHE1RKYap4ISXuw==" }, "node_modules/@pmmmwh/react-refresh-webpack-plugin": { - "version": "0.5.11", - "resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.11.tgz", - "integrity": "sha512-7j/6vdTym0+qZ6u4XbSAxrWBGYSdCfTzySkj7WAFgDLmSyWlOrWvpyzxlFh5jtw9dn0oL/jtW+06XfFiisN3JQ==", + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.15.tgz", + "integrity": "sha512-LFWllMA55pzB9D34w/wXUCf8+c+IYKuJDgxiZ3qMhl64KRMBHYM1I3VdGaD2BV5FNPV2/S2596bppxHbv2ZydQ==", "dev": true, "dependencies": { - "ansi-html-community": "^0.0.8", - "common-path-prefix": "^3.0.0", + "ansi-html": "^0.0.9", "core-js-pure": "^3.23.3", "error-stack-parser": "^2.0.6", - "find-up": "^5.0.0", "html-entities": "^2.1.0", "loader-utils": "^2.0.4", - "schema-utils": "^3.0.0", + "schema-utils": "^4.2.0", "source-map": "^0.7.3" }, "engines": { @@ -4288,7 +3801,7 @@ "sockjs-client": "^1.4.0", "type-fest": ">=0.17.0 <5.0.0", "webpack": ">=4.43.0 <6.0.0", - "webpack-dev-server": "3.x || 4.x", + "webpack-dev-server": "3.x || 4.x || 5.x", "webpack-hot-middleware": "2.x", "webpack-plugin-serve": "0.x || 1.x" }, @@ -4313,6 +3826,59 @@ } } }, + "node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/ajv": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.16.0.tgz", + "integrity": "sha512-F0twR8U1ZU67JIEtekUcLkXkoO5mMMmgGD8sK/xUFzJ805jxHQl92hImFAqqXMyMYjSPOyUPAwHYhB72g5sTXw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.4.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/schema-utils": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", + "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, "node_modules/@polka/url": { "version": "1.0.0-next.24", "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.24.tgz", @@ -4396,14 +3962,14 @@ } }, "node_modules/@redhat-cloud-services/chrome": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@redhat-cloud-services/chrome/-/chrome-1.0.9.tgz", - "integrity": "sha512-vv6X3OM1iUtcfYg79ntLMrWYFxx6WRlhYGVUdZ2Y76/GqJaSbaSV5j31ezAPsQ0yQKswVTJaklbLThY6w1JDKA==", + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@redhat-cloud-services/chrome/-/chrome-1.0.10.tgz", + "integrity": "sha512-w25v3INup3NO/6KeHfR3P49nhZlmSA6XwPN8VRX0YDyM82loO9osydU8mghqQN+HRrfBNPFguZW5zn5LugFUZw==", "dependencies": { "lodash": "^4.17.21" }, "peerDependencies": { - "@scalprum/react-core": "^0.7.0", + "@scalprum/react-core": "^0.8.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.0.0" @@ -4445,22 +4011,22 @@ } }, "node_modules/@redhat-cloud-services/frontend-components": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@redhat-cloud-services/frontend-components/-/frontend-components-4.2.2.tgz", - "integrity": "sha512-mXaxcWQR/8fsFPkXX1jVSQsa8grXy69v7hRU8OGXd4Xr14oXB37IzGyxeF7H7FT+fK9GAGkh0JWYpIlkyYWWFw==", + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/@redhat-cloud-services/frontend-components/-/frontend-components-4.2.11.tgz", + "integrity": "sha512-Mp8hksPvUOcTtybKGjaZoE6Pwl68Z1T7raKzDpIqoFgwz6T/KRzyRGNy5JD1QpB6lR/kN3QIw8CXAfZEvSe+Qw==", "dependencies": { - "@patternfly/react-component-groups": "^5.0.0-prerelease.7", + "@patternfly/react-component-groups": "^5.0.0", "@redhat-cloud-services/frontend-components-utilities": "^4.0.0", - "@redhat-cloud-services/types": "^0.0.24", + "@redhat-cloud-services/types": "^1.0.9", "@scalprum/core": "^0.7.0", "@scalprum/react-core": "^0.7.0", - "sanitize-html": "^2.7.2" + "classnames": "^2.2.5", + "sanitize-html": "^2.12.1" }, "peerDependencies": { "@patternfly/react-core": "^5.0.0", "@patternfly/react-icons": "^5.0.0", "@patternfly/react-table": "^5.0.0", - "classnames": "^2.2.5", "lodash": "^4.17.15", "prop-types": "^15.6.2", "react": "^18.2.0", @@ -4471,12 +4037,12 @@ } }, "node_modules/@redhat-cloud-services/frontend-components-config-utilities": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@redhat-cloud-services/frontend-components-config-utilities/-/frontend-components-config-utilities-3.0.4.tgz", - "integrity": "sha512-09nzlC7BOOuPF9MBHvLpd1MUYFJ8uz6FN80nbAc9sSMosrBH5R+BWQ/rbRUlEOnX3NdicQxx3Xg9EVPoTOTJmg==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@redhat-cloud-services/frontend-components-config-utilities/-/frontend-components-config-utilities-3.0.7.tgz", + "integrity": "sha512-StufcZhPSYCjkrrt8YbkyZ3VlKzq8Vz2+2ro5BofFengs8E6MTSaIX4yeLAZl7q05/l4rXERA5BBWMkVtrEEnw==", "dev": true, "dependencies": { - "@openshift/dynamic-plugin-sdk-webpack": "^3.0.0", + "@openshift/dynamic-plugin-sdk-webpack": "^4.0.1", "chalk": "^4.1.2", "node-fetch": "2.6.7" }, @@ -4484,6 +4050,23 @@ "webpack": "^5.0.0" } }, + "node_modules/@redhat-cloud-services/frontend-components-config-utilities/node_modules/@openshift/dynamic-plugin-sdk-webpack": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@openshift/dynamic-plugin-sdk-webpack/-/dynamic-plugin-sdk-webpack-4.1.0.tgz", + "integrity": "sha512-Pkq6R+fkoE0llgv9WJBcotViAPywrzDkpWK0HSTmrVyfEuWS5cuZUs8ono6L5w9BqDBRXm3ceEuUAZA/Zrar1w==", + "dev": true, + "dependencies": { + "lodash": "^4.17.21", + "semver": "^7.3.7", + "yup": "^0.32.11" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "webpack": "^5.75.0" + } + }, "node_modules/@redhat-cloud-services/frontend-components-config-utilities/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -4542,6 +4125,18 @@ "node": ">=8" } }, + "node_modules/@redhat-cloud-services/frontend-components-config-utilities/node_modules/semver": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@redhat-cloud-services/frontend-components-config-utilities/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -4582,9 +4177,9 @@ } }, "node_modules/@redhat-cloud-services/frontend-components-pdf-generator": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@redhat-cloud-services/frontend-components-pdf-generator/-/frontend-components-pdf-generator-4.0.4.tgz", - "integrity": "sha512-uOpQBufxcJrXYbPXY/yvgPytWCyNlrfkv+5zrOvfLaU9rQdt8a6LL2qQR1LsOzidBfRqd9WYPWbotKlzSYK5hg==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@redhat-cloud-services/frontend-components-pdf-generator/-/frontend-components-pdf-generator-4.0.5.tgz", + "integrity": "sha512-GWlZxAm1DFBfjI5Fl7IWZf11Iwq8h6Q73OvLa6sVmZcC9KNOqLpXowj5pYnWTxqQoSiKeqjLpznq7P0Ly0Ntaw==", "dependencies": { "@patternfly/react-charts": "6.3.9", "@patternfly/react-core": "^5.0.0", @@ -4593,7 +4188,7 @@ "@patternfly/react-tokens": "^5.0.0", "@react-pdf/renderer": "^1.6.8", "@redhat-cloud-services/frontend-components": "^4.0.0", - "@scalprum/react-core": "^0.6.5", + "@scalprum/react-core": "^0.7.0", "react": "16.12.0", "react-dom": "16.12.0", "rgb-hex": "^3.0.0" @@ -4675,53 +4270,13 @@ "react": "^16.0.0 || ^17.0.0" } }, - "node_modules/@redhat-cloud-services/frontend-components-pdf-generator/node_modules/@scalprum/core": { - "version": "0.6.6", - "resolved": "https://registry.npmjs.org/@scalprum/core/-/core-0.6.6.tgz", - "integrity": "sha512-Paj2Ok50LD7oTKK5av5xgaB3mZyAzHthlhlTv2rPXnMGaAhY3VryOYQttzUMIfojm+EByrKp30hdZ+WbTW70oQ==", - "dependencies": { - "@openshift/dynamic-plugin-sdk": "^4.0.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@redhat-cloud-services/frontend-components-pdf-generator/node_modules/@scalprum/core/node_modules/@openshift/dynamic-plugin-sdk": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@openshift/dynamic-plugin-sdk/-/dynamic-plugin-sdk-4.0.0.tgz", - "integrity": "sha512-OQsRqpRFz8IO6dZP6oKqdS7fLpdK25jxteevhussWFDd6RETNaLAG9GaSfvN0oigrzNIUTwH59kJx8PP8PrMug==", - "dependencies": { - "lodash": "^4.17.21", - "semver": "^7.3.7", - "uuid": "^8.3.2", - "yup": "^0.32.11" - }, - "peerDependencies": { - "react": "^17 || ^18" - } - }, - "node_modules/@redhat-cloud-services/frontend-components-pdf-generator/node_modules/@scalprum/core/node_modules/react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", - "peer": true, - "dependencies": { - "loose-envify": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@redhat-cloud-services/frontend-components-pdf-generator/node_modules/@scalprum/core/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - }, "node_modules/@redhat-cloud-services/frontend-components-pdf-generator/node_modules/@scalprum/react-core": { - "version": "0.6.7", - "resolved": "https://registry.npmjs.org/@scalprum/react-core/-/react-core-0.6.7.tgz", - "integrity": "sha512-3LZyG8ts+skEKxytpEyNEAc6Oh9zV9UIhNse6HEk4fvgZXfgC1xaw913UrBNFosaieFTgICBGc+Tp9mhjRNwSw==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@scalprum/react-core/-/react-core-0.7.1.tgz", + "integrity": "sha512-CdLSwg46MYyDqCjWGuim/u0HF6GaPPGxXRD6AkWCkB4o3feBep+2zRAjzNfi3IlhNufWaiswxLpThhTLG4cgMg==", "dependencies": { - "@openshift/dynamic-plugin-sdk": "^4.0.0", - "@scalprum/core": "^0.6.5", + "@openshift/dynamic-plugin-sdk": "^5.0.1", + "@scalprum/core": "^0.7.0", "lodash": "^4.17.0" }, "peerDependencies": { @@ -4729,20 +4284,6 @@ "react-dom": ">=16.8.0 || >=17.0.0 || ^18.0.0" } }, - "node_modules/@redhat-cloud-services/frontend-components-pdf-generator/node_modules/@scalprum/react-core/node_modules/@openshift/dynamic-plugin-sdk": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@openshift/dynamic-plugin-sdk/-/dynamic-plugin-sdk-4.0.0.tgz", - "integrity": "sha512-OQsRqpRFz8IO6dZP6oKqdS7fLpdK25jxteevhussWFDd6RETNaLAG9GaSfvN0oigrzNIUTwH59kJx8PP8PrMug==", - "dependencies": { - "lodash": "^4.17.21", - "semver": "^7.3.7", - "uuid": "^8.3.2", - "yup": "^0.32.11" - }, - "peerDependencies": { - "react": "^17 || ^18" - } - }, "node_modules/@redhat-cloud-services/frontend-components-pdf-generator/node_modules/d3-array": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz", @@ -4824,17 +4365,6 @@ "delaunator": "^4.0.0" } }, - "node_modules/@redhat-cloud-services/frontend-components-pdf-generator/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@redhat-cloud-services/frontend-components-pdf-generator/node_modules/react": { "version": "16.12.0", "resolved": "https://registry.npmjs.org/react/-/react-16.12.0.tgz", @@ -4901,20 +4431,6 @@ "object-assign": "^4.1.1" } }, - "node_modules/@redhat-cloud-services/frontend-components-pdf-generator/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@redhat-cloud-services/frontend-components-pdf-generator/node_modules/tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", @@ -5146,21 +4662,16 @@ "victory-core": "^34.3.12" } }, - "node_modules/@redhat-cloud-services/frontend-components-pdf-generator/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, "node_modules/@redhat-cloud-services/frontend-components-utilities": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/@redhat-cloud-services/frontend-components-utilities/-/frontend-components-utilities-4.0.10.tgz", - "integrity": "sha512-SAMXD9ciFk8aDXjt+9FnaY6VP+6RTuNswFn7a+TNROBIa0CNqb7O0x5y0gZs/Pc67xrbuxF9K5RG98qwLGdNOQ==", + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/@redhat-cloud-services/frontend-components-utilities/-/frontend-components-utilities-4.0.13.tgz", + "integrity": "sha512-UBvfUlrf0IVPRcqhipRXGBNIuMP1+CG4FRNyGKCb8PuZM/iPX951JmEbelgBAyEwKGffnEPojP0A+9Xu6iFlJg==", "dependencies": { - "@redhat-cloud-services/rbac-client": "^1.0.100", - "@redhat-cloud-services/types": "^0.0.24", + "@redhat-cloud-services/rbac-client": "^1.0.111 || 2.x", + "@redhat-cloud-services/types": "^1.0.9", "@sentry/browser": "^5.30.0", "awesome-debounce-promise": "^2.1.0", - "axios": "^0.28.1", + "axios": "^0.28.1 || ^1.7.0", "commander": "^2.20.3", "mkdirp": "^1.0.4", "p-all": "^5.0.0", @@ -5175,20 +4686,24 @@ "react-router-dom": "^5.0.0 || ^6.0.0" } }, - "node_modules/@redhat-cloud-services/frontend-components-utilities/node_modules/@redhat-cloud-services/types": { - "version": "0.0.24", - "resolved": "https://registry.npmjs.org/@redhat-cloud-services/types/-/types-0.0.24.tgz", - "integrity": "sha512-P50stc+mnWLycID46/AKmD/760r5N1eoam//O6MUVriqVorUdht7xkUL78aJZU1vw8WW6xlrDHwz3F6BM148qg==" - }, "node_modules/@redhat-cloud-services/frontend-components-utilities/node_modules/commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" }, - "node_modules/@redhat-cloud-services/frontend-components/node_modules/@redhat-cloud-services/types": { - "version": "0.0.24", - "resolved": "https://registry.npmjs.org/@redhat-cloud-services/types/-/types-0.0.24.tgz", - "integrity": "sha512-P50stc+mnWLycID46/AKmD/760r5N1eoam//O6MUVriqVorUdht7xkUL78aJZU1vw8WW6xlrDHwz3F6BM148qg==" + "node_modules/@redhat-cloud-services/frontend-components/node_modules/@scalprum/react-core": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@scalprum/react-core/-/react-core-0.7.1.tgz", + "integrity": "sha512-CdLSwg46MYyDqCjWGuim/u0HF6GaPPGxXRD6AkWCkB4o3feBep+2zRAjzNfi3IlhNufWaiswxLpThhTLG4cgMg==", + "dependencies": { + "@openshift/dynamic-plugin-sdk": "^5.0.1", + "@scalprum/core": "^0.7.0", + "lodash": "^4.17.0" + }, + "peerDependencies": { + "react": ">=16.8.0 || >=17.0.0 || ^18.0.0", + "react-dom": ">=16.8.0 || >=17.0.0 || ^18.0.0" + } }, "node_modules/@redhat-cloud-services/host-inventory-client": { "version": "1.2.0", @@ -5225,15 +4740,14 @@ } }, "node_modules/@redhat-cloud-services/types": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/@redhat-cloud-services/types/-/types-1.0.10.tgz", - "integrity": "sha512-4geiWnE60jYpRcNwxbx+dZBSv9Mh5VXPPcS0E9UGE4pszAsy25rgZvJ/C8tP9HymsH/Q1rn/3DKvBBulXAPGAg==", - "dev": true + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@redhat-cloud-services/types/-/types-1.0.11.tgz", + "integrity": "sha512-Ml1OsVEa8lUq3t2xUC6CJzD8p/E446HOk36D8aDMJ1c8gum7nndweLXz2Vdq+zewCF2RE+Dbe9E0QNJSfJcRHA==" }, "node_modules/@remix-run/router": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.14.1.tgz", - "integrity": "sha512-Qg4DMQsfPNAs88rb2xkdk03N3bjK4jgX5fR24eHCTR9q6PrhZQZ4UJBPzCHJkIpTRN1UKxx2DzjZmnC+7Lj0Ow==", + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.17.0.tgz", + "integrity": "sha512-2D6XaHEVvkCn682XBnipbJjgZUU7xjLtA4dGJRBVUKpEaDYOZMENZoZjAOSb7qirxt5RupjzZxz4fK2FO+EFPw==", "engines": { "node": ">=14.0.0" } @@ -5247,104 +4761,53 @@ "tslib": "^2.6.2" } }, - "node_modules/@scalprum/react-core": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@scalprum/react-core/-/react-core-0.7.0.tgz", - "integrity": "sha512-vi1THIHlm8VezAvOhPrDyyP4qDoRITQgpbwAyAXvak0ZbUilc4Pk0y8IoZ2qh0ymh1C7GGxGy5CEBjsX9bY0/g==", - "dependencies": { - "@openshift/dynamic-plugin-sdk": "^5.0.1", - "@scalprum/core": "^0.6.6", - "lodash": "^4.17.0" - }, - "peerDependencies": { - "react": ">=16.8.0 || >=17.0.0 || ^18.0.0", - "react-dom": ">=16.8.0 || >=17.0.0 || ^18.0.0" - } - }, - "node_modules/@scalprum/react-core/node_modules/@scalprum/core": { - "version": "0.6.6", - "resolved": "https://registry.npmjs.org/@scalprum/core/-/core-0.6.6.tgz", - "integrity": "sha512-Paj2Ok50LD7oTKK5av5xgaB3mZyAzHthlhlTv2rPXnMGaAhY3VryOYQttzUMIfojm+EByrKp30hdZ+WbTW70oQ==", - "dependencies": { - "@openshift/dynamic-plugin-sdk": "^4.0.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@scalprum/react-core/node_modules/@scalprum/core/node_modules/@openshift/dynamic-plugin-sdk": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@openshift/dynamic-plugin-sdk/-/dynamic-plugin-sdk-4.0.0.tgz", - "integrity": "sha512-OQsRqpRFz8IO6dZP6oKqdS7fLpdK25jxteevhussWFDd6RETNaLAG9GaSfvN0oigrzNIUTwH59kJx8PP8PrMug==", - "dependencies": { - "lodash": "^4.17.21", - "semver": "^7.3.7", - "uuid": "^8.3.2", - "yup": "^0.32.11" - }, - "peerDependencies": { - "react": "^17 || ^18" - } - }, - "node_modules/@scalprum/react-core/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@scalprum/react-core/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "node_modules/@scalprum/react-core": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@scalprum/react-core/-/react-core-0.8.0.tgz", + "integrity": "sha512-ADqL6GdjT0ihVfTtIqu8vvYgwUb3IiUGXj9md1+h6tL8yshbq+FAezwuD9jhX7qcjfgL1Sl3g/faO2/SE9jPvw==", "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" + "@openshift/dynamic-plugin-sdk": "^5.0.1", + "@scalprum/core": "^0.7.0", + "lodash": "^4.17.0" }, - "engines": { - "node": ">=10" + "peerDependencies": { + "react": ">=16.8.0 || >=17.0.0 || ^18.0.0", + "react-dom": ">=16.8.0 || >=17.0.0 || ^18.0.0" } }, - "node_modules/@scalprum/react-core/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, "node_modules/@segment/analytics-core": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@segment/analytics-core/-/analytics-core-1.4.0.tgz", - "integrity": "sha512-rLUv5Se0iDccykxY8bWUuoZT4gg8fNW00zMPqkJN+ONfj5/P1eaGQgygq2EHlR9j20a7tNtp5Y9bZ4rLzViIXQ==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@segment/analytics-core/-/analytics-core-1.6.0.tgz", + "integrity": "sha512-bn9X++IScUfpT7aJGjKU/yJAu/Ko2sYD6HsKA70Z2560E89x30pqgqboVKY8kootvQnT4UKCJiUr5NDMgjmWdQ==", "dependencies": { "@lukeed/uuid": "^2.0.0", - "@segment/analytics-generic-utils": "1.1.0", + "@segment/analytics-generic-utils": "1.2.0", "dset": "^3.1.2", "tslib": "^2.4.1" } }, "node_modules/@segment/analytics-generic-utils": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@segment/analytics-generic-utils/-/analytics-generic-utils-1.1.0.tgz", - "integrity": "sha512-nOgmbfsKD0jFzH3df+PtjLq3qTspdcFpIy/F5ziho5qiE+QATM8wY9TpvCNBbcHr2f3OGzT6SgjJLFlmM5Yb+w==" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@segment/analytics-generic-utils/-/analytics-generic-utils-1.2.0.tgz", + "integrity": "sha512-DfnW6mW3YQOLlDQQdR89k4EqfHb0g/3XvBXkovH1FstUN93eL1kfW9CsDcVQyH3bAC5ZsFyjA/o/1Q2j0QeoWw==", + "dependencies": { + "tslib": "^2.4.1" + } }, "node_modules/@segment/analytics-next": { - "version": "1.62.0", - "resolved": "https://registry.npmjs.org/@segment/analytics-next/-/analytics-next-1.62.0.tgz", - "integrity": "sha512-RuamvHQPVAkwO9/SSd/ioC/fT+4BaWvDlgkCKQi1DHXT4+5HG7bJROIUBgrHDmXEOVavnH7U97BIkZ1tgXEchw==", + "version": "1.70.0", + "resolved": "https://registry.npmjs.org/@segment/analytics-next/-/analytics-next-1.70.0.tgz", + "integrity": "sha512-6pAnJfnhv33lCvIuvMcxco4XXt09tBOog5IcWM2/T3HPeSpg9rigm4qRLpNLEgv40BGRhHC3szHsY9z9oF6peQ==", "dependencies": { "@lukeed/uuid": "^2.0.0", - "@segment/analytics-core": "1.4.0", - "@segment/analytics-generic-utils": "1.1.0", + "@segment/analytics-core": "1.6.0", + "@segment/analytics-generic-utils": "1.2.0", "@segment/analytics.js-video-plugins": "^0.2.1", "@segment/facade": "^3.4.9", "@segment/tsub": "^2.0.0", "dset": "^3.1.2", "js-cookie": "3.0.1", "node-fetch": "^2.6.7", - "spark-md5": "^3.0.1", "tslib": "^2.4.1", "unfetch": "^4.1.0" } @@ -5409,88 +4872,133 @@ } }, "node_modules/@sentry-internal/feedback": { - "version": "7.91.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-7.91.0.tgz", - "integrity": "sha512-SJKTSaz68F5YIwF79EttBm915M2LnacgZMYRnRumyTmMKnebGhYQLwWbZdpaDvOa1U18dgRajDX8Qed/8A3tXw==", + "version": "7.118.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-7.118.0.tgz", + "integrity": "sha512-IYOGRcqIqKJJpMwBBv+0JTu0FPpXnakJYvOx/XEa/SNyF5+l7b9gGEjUVWh1ok50kTLW/XPnpnXNAGQcoKHg+w==", "dependencies": { - "@sentry/core": "7.91.0", - "@sentry/types": "7.91.0", - "@sentry/utils": "7.91.0" + "@sentry/core": "7.118.0", + "@sentry/types": "7.118.0", + "@sentry/utils": "7.118.0" }, "engines": { "node": ">=12" } }, "node_modules/@sentry-internal/feedback/node_modules/@sentry/core": { - "version": "7.91.0", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.91.0.tgz", - "integrity": "sha512-tu+gYq4JrTdrR+YSh5IVHF0fJi/Pi9y0HZ5H9HnYy+UMcXIotxf6hIEaC6ZKGeLWkGXffz2gKpQLe/g6vy/lPA==", + "version": "7.118.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.118.0.tgz", + "integrity": "sha512-ol0xBdp3/K11IMAYSQE0FMxBOOH9hMsb/rjxXWe0hfM5c72CqYWL3ol7voPci0GELJ5CZG+9ImEU1V9r6gK64g==", "dependencies": { - "@sentry/types": "7.91.0", - "@sentry/utils": "7.91.0" + "@sentry/types": "7.118.0", + "@sentry/utils": "7.118.0" }, "engines": { "node": ">=8" } }, "node_modules/@sentry-internal/feedback/node_modules/@sentry/types": { - "version": "7.91.0", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.91.0.tgz", - "integrity": "sha512-bcQnb7J3P3equbCUc+sPuHog2Y47yGD2sCkzmnZBjvBT0Z1B4f36fI/5WjyZhTjLSiOdg3F2otwvikbMjmBDew==", + "version": "7.118.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.118.0.tgz", + "integrity": "sha512-2drqrD2+6kgeg+W/ycmiti3G4lJrV3hGjY9PpJ3bJeXrh6T2+LxKPzlgSEnKFaeQWkXdZ4eaUbtTXVebMjb5JA==", "engines": { "node": ">=8" } }, "node_modules/@sentry-internal/feedback/node_modules/@sentry/utils": { - "version": "7.91.0", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.91.0.tgz", - "integrity": "sha512-fvxjrEbk6T6Otu++Ax9ntlQ0sGRiwSC179w68aC3u26Wr30FAIRKqHTCCdc2jyWk7Gd9uWRT/cq+g8NG/8BfSg==", + "version": "7.118.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.118.0.tgz", + "integrity": "sha512-43qItc/ydxZV1Zb3Kn2M54RwL9XXFa3IAYBO8S82Qvq5YUYmU2AmJ1jgg7DabXlVSWgMA1HntwqnOV3JLaEnTQ==", + "dependencies": { + "@sentry/types": "7.118.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry-internal/replay-canvas": { + "version": "7.118.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-7.118.0.tgz", + "integrity": "sha512-XxHlCClvrxmVKpiZetFYyiBaPQNiojoBGFFVgbbWBIAPc+fWeLJ2BMoQEBjn/0NA/8u8T6lErK5YQo/eIx9+XQ==", + "dependencies": { + "@sentry/core": "7.118.0", + "@sentry/replay": "7.118.0", + "@sentry/types": "7.118.0", + "@sentry/utils": "7.118.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@sentry-internal/replay-canvas/node_modules/@sentry/core": { + "version": "7.118.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.118.0.tgz", + "integrity": "sha512-ol0xBdp3/K11IMAYSQE0FMxBOOH9hMsb/rjxXWe0hfM5c72CqYWL3ol7voPci0GELJ5CZG+9ImEU1V9r6gK64g==", + "dependencies": { + "@sentry/types": "7.118.0", + "@sentry/utils": "7.118.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry-internal/replay-canvas/node_modules/@sentry/types": { + "version": "7.118.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.118.0.tgz", + "integrity": "sha512-2drqrD2+6kgeg+W/ycmiti3G4lJrV3hGjY9PpJ3bJeXrh6T2+LxKPzlgSEnKFaeQWkXdZ4eaUbtTXVebMjb5JA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry-internal/replay-canvas/node_modules/@sentry/utils": { + "version": "7.118.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.118.0.tgz", + "integrity": "sha512-43qItc/ydxZV1Zb3Kn2M54RwL9XXFa3IAYBO8S82Qvq5YUYmU2AmJ1jgg7DabXlVSWgMA1HntwqnOV3JLaEnTQ==", "dependencies": { - "@sentry/types": "7.91.0" + "@sentry/types": "7.118.0" }, "engines": { "node": ">=8" } }, "node_modules/@sentry-internal/tracing": { - "version": "7.91.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.91.0.tgz", - "integrity": "sha512-JH5y6gs6BS0its7WF2DhySu7nkhPDfZcdpAXldxzIlJpqFkuwQKLU5nkYJpiIyZz1NHYYtW5aum2bV2oCOdDRA==", + "version": "7.118.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.118.0.tgz", + "integrity": "sha512-dERAshKlQLrBscHSarhHyUeGsu652bDTUN1FK0m4e3X48M3I5/s+0N880Qjpe5MprNLcINlaIgdQ9jkisvxjfw==", "dependencies": { - "@sentry/core": "7.91.0", - "@sentry/types": "7.91.0", - "@sentry/utils": "7.91.0" + "@sentry/core": "7.118.0", + "@sentry/types": "7.118.0", + "@sentry/utils": "7.118.0" }, "engines": { "node": ">=8" } }, "node_modules/@sentry-internal/tracing/node_modules/@sentry/core": { - "version": "7.91.0", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.91.0.tgz", - "integrity": "sha512-tu+gYq4JrTdrR+YSh5IVHF0fJi/Pi9y0HZ5H9HnYy+UMcXIotxf6hIEaC6ZKGeLWkGXffz2gKpQLe/g6vy/lPA==", + "version": "7.118.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.118.0.tgz", + "integrity": "sha512-ol0xBdp3/K11IMAYSQE0FMxBOOH9hMsb/rjxXWe0hfM5c72CqYWL3ol7voPci0GELJ5CZG+9ImEU1V9r6gK64g==", "dependencies": { - "@sentry/types": "7.91.0", - "@sentry/utils": "7.91.0" + "@sentry/types": "7.118.0", + "@sentry/utils": "7.118.0" }, "engines": { "node": ">=8" } }, "node_modules/@sentry-internal/tracing/node_modules/@sentry/types": { - "version": "7.91.0", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.91.0.tgz", - "integrity": "sha512-bcQnb7J3P3equbCUc+sPuHog2Y47yGD2sCkzmnZBjvBT0Z1B4f36fI/5WjyZhTjLSiOdg3F2otwvikbMjmBDew==", + "version": "7.118.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.118.0.tgz", + "integrity": "sha512-2drqrD2+6kgeg+W/ycmiti3G4lJrV3hGjY9PpJ3bJeXrh6T2+LxKPzlgSEnKFaeQWkXdZ4eaUbtTXVebMjb5JA==", "engines": { "node": ">=8" } }, "node_modules/@sentry-internal/tracing/node_modules/@sentry/utils": { - "version": "7.91.0", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.91.0.tgz", - "integrity": "sha512-fvxjrEbk6T6Otu++Ax9ntlQ0sGRiwSC179w68aC3u26Wr30FAIRKqHTCCdc2jyWk7Gd9uWRT/cq+g8NG/8BfSg==", + "version": "7.118.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.118.0.tgz", + "integrity": "sha512-43qItc/ydxZV1Zb3Kn2M54RwL9XXFa3IAYBO8S82Qvq5YUYmU2AmJ1jgg7DabXlVSWgMA1HntwqnOV3JLaEnTQ==", "dependencies": { - "@sentry/types": "7.91.0" + "@sentry/types": "7.118.0" }, "engines": { "node": ">=8" @@ -5553,6 +5061,51 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, + "node_modules/@sentry/integrations": { + "version": "7.118.0", + "resolved": "https://registry.npmjs.org/@sentry/integrations/-/integrations-7.118.0.tgz", + "integrity": "sha512-C2rR4NvIMjokF8jP5qzSf1o2zxDx7IeYnr8u15Kb2+HdZtX559owALR0hfgwnfeElqMhGlJBaKUWZ48lXJMzCQ==", + "dependencies": { + "@sentry/core": "7.118.0", + "@sentry/types": "7.118.0", + "@sentry/utils": "7.118.0", + "localforage": "^1.8.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/integrations/node_modules/@sentry/core": { + "version": "7.118.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.118.0.tgz", + "integrity": "sha512-ol0xBdp3/K11IMAYSQE0FMxBOOH9hMsb/rjxXWe0hfM5c72CqYWL3ol7voPci0GELJ5CZG+9ImEU1V9r6gK64g==", + "dependencies": { + "@sentry/types": "7.118.0", + "@sentry/utils": "7.118.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/integrations/node_modules/@sentry/types": { + "version": "7.118.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.118.0.tgz", + "integrity": "sha512-2drqrD2+6kgeg+W/ycmiti3G4lJrV3hGjY9PpJ3bJeXrh6T2+LxKPzlgSEnKFaeQWkXdZ4eaUbtTXVebMjb5JA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/integrations/node_modules/@sentry/utils": { + "version": "7.118.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.118.0.tgz", + "integrity": "sha512-43qItc/ydxZV1Zb3Kn2M54RwL9XXFa3IAYBO8S82Qvq5YUYmU2AmJ1jgg7DabXlVSWgMA1HntwqnOV3JLaEnTQ==", + "dependencies": { + "@sentry/types": "7.118.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/@sentry/minimal": { "version": "5.30.0", "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-5.30.0.tgz", @@ -5572,13 +5125,14 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "node_modules/@sentry/react": { - "version": "7.91.0", - "resolved": "https://registry.npmjs.org/@sentry/react/-/react-7.91.0.tgz", - "integrity": "sha512-7JH2rWaX3WKHHvBcZQ4f/KnkYIXTf7hMojRFncUwPocdtDlhJw/JUvjAYNpEysixXIgsMes3B32lmtZjGjRhwQ==", - "dependencies": { - "@sentry/browser": "7.91.0", - "@sentry/types": "7.91.0", - "@sentry/utils": "7.91.0", + "version": "7.118.0", + "resolved": "https://registry.npmjs.org/@sentry/react/-/react-7.118.0.tgz", + "integrity": "sha512-oEYe5TGk8S7YzPsFqDf4xDHjfzs35/QFE+dou3S2d24OYpso8Tq4C5f1VzYmnOOyy85T7JNicYLSo0n0NSJvQg==", + "dependencies": { + "@sentry/browser": "7.118.0", + "@sentry/core": "7.118.0", + "@sentry/types": "7.118.0", + "@sentry/utils": "7.118.0", "hoist-non-react-statics": "^3.3.2" }, "engines": { @@ -5589,103 +5143,105 @@ } }, "node_modules/@sentry/react/node_modules/@sentry/browser": { - "version": "7.91.0", - "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.91.0.tgz", - "integrity": "sha512-lJv3x/xekzC/biiyAsVCioq2XnKNOZhI6jY3ZzLJZClYV8eKRi7D3KCsHRvMiCdGak1d/6sVp8F4NYY+YiWy1Q==", + "version": "7.118.0", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.118.0.tgz", + "integrity": "sha512-8onDOFV1VLEoBuqA5yaJeR3FF1JNuxr5C7p1oN3OwY724iTVqQnOLmZKZaSnHV3RkY67wKDGQkQIie14sc+42g==", "dependencies": { - "@sentry-internal/feedback": "7.91.0", - "@sentry-internal/tracing": "7.91.0", - "@sentry/core": "7.91.0", - "@sentry/replay": "7.91.0", - "@sentry/types": "7.91.0", - "@sentry/utils": "7.91.0" + "@sentry-internal/feedback": "7.118.0", + "@sentry-internal/replay-canvas": "7.118.0", + "@sentry-internal/tracing": "7.118.0", + "@sentry/core": "7.118.0", + "@sentry/integrations": "7.118.0", + "@sentry/replay": "7.118.0", + "@sentry/types": "7.118.0", + "@sentry/utils": "7.118.0" }, "engines": { "node": ">=8" } }, "node_modules/@sentry/react/node_modules/@sentry/core": { - "version": "7.91.0", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.91.0.tgz", - "integrity": "sha512-tu+gYq4JrTdrR+YSh5IVHF0fJi/Pi9y0HZ5H9HnYy+UMcXIotxf6hIEaC6ZKGeLWkGXffz2gKpQLe/g6vy/lPA==", + "version": "7.118.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.118.0.tgz", + "integrity": "sha512-ol0xBdp3/K11IMAYSQE0FMxBOOH9hMsb/rjxXWe0hfM5c72CqYWL3ol7voPci0GELJ5CZG+9ImEU1V9r6gK64g==", "dependencies": { - "@sentry/types": "7.91.0", - "@sentry/utils": "7.91.0" + "@sentry/types": "7.118.0", + "@sentry/utils": "7.118.0" }, "engines": { "node": ">=8" } }, "node_modules/@sentry/react/node_modules/@sentry/types": { - "version": "7.91.0", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.91.0.tgz", - "integrity": "sha512-bcQnb7J3P3equbCUc+sPuHog2Y47yGD2sCkzmnZBjvBT0Z1B4f36fI/5WjyZhTjLSiOdg3F2otwvikbMjmBDew==", + "version": "7.118.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.118.0.tgz", + "integrity": "sha512-2drqrD2+6kgeg+W/ycmiti3G4lJrV3hGjY9PpJ3bJeXrh6T2+LxKPzlgSEnKFaeQWkXdZ4eaUbtTXVebMjb5JA==", "engines": { "node": ">=8" } }, "node_modules/@sentry/react/node_modules/@sentry/utils": { - "version": "7.91.0", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.91.0.tgz", - "integrity": "sha512-fvxjrEbk6T6Otu++Ax9ntlQ0sGRiwSC179w68aC3u26Wr30FAIRKqHTCCdc2jyWk7Gd9uWRT/cq+g8NG/8BfSg==", + "version": "7.118.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.118.0.tgz", + "integrity": "sha512-43qItc/ydxZV1Zb3Kn2M54RwL9XXFa3IAYBO8S82Qvq5YUYmU2AmJ1jgg7DabXlVSWgMA1HntwqnOV3JLaEnTQ==", "dependencies": { - "@sentry/types": "7.91.0" + "@sentry/types": "7.118.0" }, "engines": { "node": ">=8" } }, "node_modules/@sentry/replay": { - "version": "7.91.0", - "resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-7.91.0.tgz", - "integrity": "sha512-XwbesnLLNtaVXKtDoyBB96GxJuhGi9zy3a662Ba/McmumCnkXrMQYpQPh08U7MgkTyDRgjDwm7PXDhiKpcb03g==", + "version": "7.118.0", + "resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-7.118.0.tgz", + "integrity": "sha512-boQfCL+1L/tSZ9Huwi00+VtU+Ih1Lcg8HtxBuAsBCJR9pQgUL5jp7ECYdTeeHyCh/RJO7JqV1CEoGTgohe10mA==", "dependencies": { - "@sentry-internal/tracing": "7.91.0", - "@sentry/core": "7.91.0", - "@sentry/types": "7.91.0", - "@sentry/utils": "7.91.0" + "@sentry-internal/tracing": "7.118.0", + "@sentry/core": "7.118.0", + "@sentry/types": "7.118.0", + "@sentry/utils": "7.118.0" }, "engines": { "node": ">=12" } }, "node_modules/@sentry/replay/node_modules/@sentry/core": { - "version": "7.91.0", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.91.0.tgz", - "integrity": "sha512-tu+gYq4JrTdrR+YSh5IVHF0fJi/Pi9y0HZ5H9HnYy+UMcXIotxf6hIEaC6ZKGeLWkGXffz2gKpQLe/g6vy/lPA==", + "version": "7.118.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.118.0.tgz", + "integrity": "sha512-ol0xBdp3/K11IMAYSQE0FMxBOOH9hMsb/rjxXWe0hfM5c72CqYWL3ol7voPci0GELJ5CZG+9ImEU1V9r6gK64g==", "dependencies": { - "@sentry/types": "7.91.0", - "@sentry/utils": "7.91.0" + "@sentry/types": "7.118.0", + "@sentry/utils": "7.118.0" }, "engines": { "node": ">=8" } }, "node_modules/@sentry/replay/node_modules/@sentry/types": { - "version": "7.91.0", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.91.0.tgz", - "integrity": "sha512-bcQnb7J3P3equbCUc+sPuHog2Y47yGD2sCkzmnZBjvBT0Z1B4f36fI/5WjyZhTjLSiOdg3F2otwvikbMjmBDew==", + "version": "7.118.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.118.0.tgz", + "integrity": "sha512-2drqrD2+6kgeg+W/ycmiti3G4lJrV3hGjY9PpJ3bJeXrh6T2+LxKPzlgSEnKFaeQWkXdZ4eaUbtTXVebMjb5JA==", "engines": { "node": ">=8" } }, "node_modules/@sentry/replay/node_modules/@sentry/utils": { - "version": "7.91.0", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.91.0.tgz", - "integrity": "sha512-fvxjrEbk6T6Otu++Ax9ntlQ0sGRiwSC179w68aC3u26Wr30FAIRKqHTCCdc2jyWk7Gd9uWRT/cq+g8NG/8BfSg==", + "version": "7.118.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.118.0.tgz", + "integrity": "sha512-43qItc/ydxZV1Zb3Kn2M54RwL9XXFa3IAYBO8S82Qvq5YUYmU2AmJ1jgg7DabXlVSWgMA1HntwqnOV3JLaEnTQ==", "dependencies": { - "@sentry/types": "7.91.0" + "@sentry/types": "7.118.0" }, "engines": { "node": ">=8" } }, "node_modules/@sentry/tracing": { - "version": "7.91.0", - "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-7.91.0.tgz", - "integrity": "sha512-IlSAMvqfCL/2TwwN4Tmk6bGMgilGruv5oIJ1GMenVZk53bHwjpjzMbd0ms8+S5zJwAgTQXoCbRhaFFrNmptteQ==", + "version": "7.118.0", + "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-7.118.0.tgz", + "integrity": "sha512-7A5wRbwMjPCgX+UlLnR9vYabtgWlHat1qKDW6mH8Idlvf16b5xd9y+jCY9/tyayJlX3oOntv5Yrbk4eM3fjGxw==", "dependencies": { - "@sentry-internal/tracing": "7.91.0" + "@sentry-internal/tracing": "7.118.0" }, "engines": { "node": ">=8" @@ -8721,14 +8277,14 @@ } }, "node_modules/@swc/core": { - "version": "1.3.102", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.102.tgz", - "integrity": "sha512-OAjNLY/f6QWKSDzaM3bk31A+OYHu6cPa9P/rFIx8X5d24tHXUpRiiq6/PYI6SQRjUPlB72GjsjoEU8F+ALadHg==", + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.6.5.tgz", + "integrity": "sha512-tyVvUK/HDOUUsK6/GmWvnqUtD9oDpPUA4f7f7JCOV8hXxtfjMtAZeBKf93yrB1XZet69TDR7EN0hFC6i4MF0Ig==", "dev": true, "hasInstallScript": true, "dependencies": { - "@swc/counter": "^0.1.1", - "@swc/types": "^0.1.5" + "@swc/counter": "^0.1.3", + "@swc/types": "^0.1.9" }, "engines": { "node": ">=10" @@ -8738,19 +8294,19 @@ "url": "https://opencollective.com/swc" }, "optionalDependencies": { - "@swc/core-darwin-arm64": "1.3.102", - "@swc/core-darwin-x64": "1.3.102", - "@swc/core-linux-arm-gnueabihf": "1.3.102", - "@swc/core-linux-arm64-gnu": "1.3.102", - "@swc/core-linux-arm64-musl": "1.3.102", - "@swc/core-linux-x64-gnu": "1.3.102", - "@swc/core-linux-x64-musl": "1.3.102", - "@swc/core-win32-arm64-msvc": "1.3.102", - "@swc/core-win32-ia32-msvc": "1.3.102", - "@swc/core-win32-x64-msvc": "1.3.102" + "@swc/core-darwin-arm64": "1.6.5", + "@swc/core-darwin-x64": "1.6.5", + "@swc/core-linux-arm-gnueabihf": "1.6.5", + "@swc/core-linux-arm64-gnu": "1.6.5", + "@swc/core-linux-arm64-musl": "1.6.5", + "@swc/core-linux-x64-gnu": "1.6.5", + "@swc/core-linux-x64-musl": "1.6.5", + "@swc/core-win32-arm64-msvc": "1.6.5", + "@swc/core-win32-ia32-msvc": "1.6.5", + "@swc/core-win32-x64-msvc": "1.6.5" }, "peerDependencies": { - "@swc/helpers": "^0.5.0" + "@swc/helpers": "*" }, "peerDependenciesMeta": { "@swc/helpers": { @@ -8759,9 +8315,9 @@ } }, "node_modules/@swc/core-darwin-arm64": { - "version": "1.3.102", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.102.tgz", - "integrity": "sha512-CJDxA5Wd2cUMULj3bjx4GEoiYyyiyL8oIOu4Nhrs9X+tlg8DnkCm4nI57RJGP8Mf6BaXPIJkHX8yjcefK2RlDA==", + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.6.5.tgz", + "integrity": "sha512-RGQhMdni2v1/ANQ/2K+F+QYdzaucekYBewZcX1ogqJ8G5sbPaBdYdDN1qQ4kHLCIkPtGP6qC7c71qPEqL2RidQ==", "cpu": [ "arm64" ], @@ -8775,9 +8331,9 @@ } }, "node_modules/@swc/core-darwin-x64": { - "version": "1.3.102", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.3.102.tgz", - "integrity": "sha512-X5akDkHwk6oAer49oER0qZMjNMkLH3IOZaV1m98uXIasAGyjo5WH1MKPeMLY1sY6V6TrufzwiSwD4ds571ytcg==", + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.6.5.tgz", + "integrity": "sha512-/pSN0/Jtcbbb9+ovS9rKxR3qertpFAM3OEJr/+Dh/8yy7jK5G5EFPIrfsw/7Q5987ERPIJIH6BspK2CBB2tgcg==", "cpu": [ "x64" ], @@ -8791,9 +8347,9 @@ } }, "node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.3.102", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.3.102.tgz", - "integrity": "sha512-kJH3XtZP9YQdjq/wYVBeFuiVQl4HaC4WwRrIxAHwe2OyvrwUI43dpW3LpxSggBnxXcVCXYWf36sTnv8S75o2Gw==", + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.6.5.tgz", + "integrity": "sha512-B0g/dROCE747RRegs/jPHuKJgwXLracDhnqQa80kFdgWEMjlcb7OMCgs5OX86yJGRS4qcYbiMGD0Pp7Kbqn3yw==", "cpu": [ "arm" ], @@ -8807,9 +8363,9 @@ } }, "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.3.102", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.102.tgz", - "integrity": "sha512-flQP2WDyCgO24WmKA1wjjTx+xfCmavUete2Kp6yrM+631IHLGnr17eu7rYJ/d4EnDBId/ytMyrnWbTVkaVrpbQ==", + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.6.5.tgz", + "integrity": "sha512-W8meapgXTq8AOtSvDG4yKR8ant2WWD++yOjgzAleB5VAC+oC+aa8YJROGxj8HepurU8kurqzcialwoMeq5SZZQ==", "cpu": [ "arm64" ], @@ -8823,9 +8379,9 @@ } }, "node_modules/@swc/core-linux-arm64-musl": { - "version": "1.3.102", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.102.tgz", - "integrity": "sha512-bQEQSnC44DyoIGLw1+fNXKVGoCHi7eJOHr8BdH0y1ooy9ArskMjwobBFae3GX4T1AfnrTaejyr0FvLYIb0Zkog==", + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.6.5.tgz", + "integrity": "sha512-jyCKqoX50Fg8rJUQqh4u5PqnE7nqYKXHjVH2WcYr114/MU21zlsI+YL6aOQU1XP8bJQ2gPQ1rnlnGJdEHiKS/w==", "cpu": [ "arm64" ], @@ -8839,9 +8395,9 @@ } }, "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.3.102", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.102.tgz", - "integrity": "sha512-dFvnhpI478svQSxqISMt00MKTDS0e4YtIr+ioZDG/uJ/q+RpcNy3QI2KMm05Fsc8Y0d4krVtvCKWgfUMsJZXAg==", + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.6.5.tgz", + "integrity": "sha512-G6HmUn/RRIlXC0YYFfBz2qh6OZkHS/KUPkhoG4X9ADcgWXXjOFh6JrefwsYj8VBAJEnr5iewzjNfj+nztwHaeA==", "cpu": [ "x64" ], @@ -8855,9 +8411,9 @@ } }, "node_modules/@swc/core-linux-x64-musl": { - "version": "1.3.102", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.102.tgz", - "integrity": "sha512-+a0M3CvjeIRNA/jTCzWEDh2V+mhKGvLreHOL7J97oULZy5yg4gf7h8lQX9J8t9QLbf6fsk+0F8bVH1Ie/PbXjA==", + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.6.5.tgz", + "integrity": "sha512-AQpBjBnelQDSbeTJA50AXdS6+CP66LsXIMNTwhPSgUfE7Bx1ggZV11Fsi4Q5SGcs6a8Qw1cuYKN57ZfZC5QOuA==", "cpu": [ "x64" ], @@ -8871,9 +8427,9 @@ } }, "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.3.102", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.102.tgz", - "integrity": "sha512-w76JWLjkZNOfkB25nqdWUNCbt0zJ41CnWrJPZ+LxEai3zAnb2YtgB/cCIrwxDebRuMgE9EJXRj7gDDaTEAMOOQ==", + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.6.5.tgz", + "integrity": "sha512-MZTWM8kUwS30pVrtbzSGEXtek46aXNb/mT9D6rsS7NvOuv2w+qZhjR1rzf4LNbbn5f8VnR4Nac1WIOYZmfC5ng==", "cpu": [ "arm64" ], @@ -8887,9 +8443,9 @@ } }, "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.3.102", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.102.tgz", - "integrity": "sha512-vlDb09HiGqKwz+2cxDS9T5/461ipUQBplvuhW+cCbzzGuPq8lll2xeyZU0N1E4Sz3MVdSPx1tJREuRvlQjrwNg==", + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.6.5.tgz", + "integrity": "sha512-WZdu4gISAr3yOm1fVwKhhk6+MrP7kVX0KMP7+ZQFTN5zXQEiDSDunEJKVgjMVj3vlR+6mnAqa/L0V9Qa8+zKlQ==", "cpu": [ "ia32" ], @@ -8903,9 +8459,9 @@ } }, "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.3.102", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.102.tgz", - "integrity": "sha512-E/jfSD7sShllxBwwgDPeXp1UxvIqehj/ShSUqq1pjR/IDRXngcRSXKJK92mJkNFY7suH6BcCWwzrxZgkO7sWmw==", + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.6.5.tgz", + "integrity": "sha512-ezXgucnMTzlFIxQZw7ls/5r2hseFaRoDL04cuXUOs97E8r+nJSmFsRQm/ygH5jBeXNo59nyZCalrjJAjwfgACA==", "cpu": [ "x64" ], @@ -8919,18 +8475,19 @@ } }, "node_modules/@swc/counter": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.2.tgz", - "integrity": "sha512-9F4ys4C74eSTEUNndnER3VJ15oru2NumfQxS8geE+f3eB5xvfxpWyqE5XlVnxb/R14uoXi6SLbBwwiDSkv+XEw==", + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", "dev": true }, "node_modules/@swc/jest": { - "version": "0.2.29", - "resolved": "https://registry.npmjs.org/@swc/jest/-/jest-0.2.29.tgz", - "integrity": "sha512-8reh5RvHBsSikDC3WGCd5ZTd2BXKkyOdK7QwynrCH58jk2cQFhhHhFBg/jvnWZehUQe/EoOImLENc9/DwbBFow==", + "version": "0.2.36", + "resolved": "https://registry.npmjs.org/@swc/jest/-/jest-0.2.36.tgz", + "integrity": "sha512-8X80dp81ugxs4a11z1ka43FPhP+/e+mJNXJSxiNYk8gIX/jPBtY4gQTrKu/KIoco8bzKuPI5lUxjfLiGsfvnlw==", "dev": true, "dependencies": { - "@jest/create-cache-key-function": "^27.4.2", + "@jest/create-cache-key-function": "^29.7.0", + "@swc/counter": "^0.1.3", "jsonc-parser": "^3.2.0" }, "engines": { @@ -8941,10 +8498,13 @@ } }, "node_modules/@swc/types": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.5.tgz", - "integrity": "sha512-myfUej5naTBWnqOCc/MdVOLVjXUXtIA+NpDrDBKJtLLg2shUjBu3cZmB/85RyitKc55+lUUyl7oRfLOvkr2hsw==", - "dev": true + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.9.tgz", + "integrity": "sha512-qKnCno++jzcJ4lM4NTfYifm1EFSCeIfKiAHAfkENZAV5Kl9PjJIyd2yeeVv6c/2CckuLyv2NmRC5pv6pm2WQBg==", + "dev": true, + "dependencies": { + "@swc/counter": "^0.1.3" + } }, "node_modules/@testing-library/dom": { "version": "9.3.3", @@ -9125,9 +8685,9 @@ } }, "node_modules/@testing-library/react": { - "version": "14.1.2", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-14.1.2.tgz", - "integrity": "sha512-z4p7DVBTPjKM5qDZ0t5ZjzkpSNb+fZy1u6bzO7kk8oeGagpPCAtgh4cx1syrfp7a+QWkM021jGqjJaxJJnXAZg==", + "version": "14.3.1", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-14.3.1.tgz", + "integrity": "sha512-H99XjUhWQw0lTgyMN05W3xQG1Nh4lq574D8keFf1dDoNTJgp66VbJozRaczoF+wsiaPJNt/TcnfpLGufGxSrZQ==", "dev": true, "dependencies": { "@babel/runtime": "^7.12.5", @@ -9415,9 +8975,9 @@ } }, "node_modules/@types/intercom-web": { - "version": "2.8.24", - "resolved": "https://registry.npmjs.org/@types/intercom-web/-/intercom-web-2.8.24.tgz", - "integrity": "sha512-MGhZqr/xaEnc5JmAlbhXhrjazUh3z0kybYQRNS2DD4dBevD6feIsw2UCaP+KeBx8n8rtHsc4kg/hfVT5+6dXRg==" + "version": "2.8.26", + "resolved": "https://registry.npmjs.org/@types/intercom-web/-/intercom-web-2.8.26.tgz", + "integrity": "sha512-2vV/eEpzRgQkhFC+nq1uoU1dZr+kgIsZAIDQy+1wonbM5fPPrZeWdfJyunzEdfJt6L4lmnCXnh1LyjR+t8k9tA==" }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", @@ -9444,9 +9004,9 @@ } }, "node_modules/@types/jest": { - "version": "29.5.11", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.11.tgz", - "integrity": "sha512-S2mHmYIVe13vrm6q4kN6fLYYAka15ALQki/vgDC3mIukEOx8WJlv0kQPM+d4w8Gp6u0uSdKND04IlTXBv0rwnQ==", + "version": "29.5.12", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.12.tgz", + "integrity": "sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==", "dev": true, "dependencies": { "expect": "^29.0.0", @@ -9531,9 +9091,9 @@ "dev": true }, "node_modules/@types/lodash": { - "version": "4.14.202", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.202.tgz", - "integrity": "sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==" + "version": "4.17.6", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.6.tgz", + "integrity": "sha512-OpXEVoCKSS3lQqjx9GGGOapBeuW5eUboYHRlHP9urXPX25IKZ6AnP5ZRxtVf63iieUbsHxLn8NQ5Nlftc6yzAA==" }, "node_modules/@types/mime": { "version": "1.3.5", @@ -9594,19 +9154,18 @@ "dev": true }, "node_modules/@types/react": { - "version": "18.2.46", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.46.tgz", - "integrity": "sha512-nNCvVBcZlvX4NU1nRRNV/mFl1nNRuTuslAJglQsq+8ldXe5Xv0Wd2f7WTE3jOxhLH2BFfiZGC6GCp+kHQbgG+w==", + "version": "18.3.3", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz", + "integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==", "dependencies": { "@types/prop-types": "*", - "@types/scheduler": "*", "csstype": "^3.0.2" } }, "node_modules/@types/react-dom": { - "version": "18.2.18", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.18.tgz", - "integrity": "sha512-TJxDm6OfAX2KJWJdMEVTwWke5Sc/E/RlnPGvGfS0W7+6ocy2xhDVQVh/KvC2Uf7kACs+gDytdusDSdWfWkaNzw==", + "version": "18.3.0", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz", + "integrity": "sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==", "devOptional": true, "dependencies": { "@types/react": "*" @@ -9637,23 +9196,18 @@ "dev": true }, "node_modules/@types/sanitize-html": { - "version": "2.9.5", - "resolved": "https://registry.npmjs.org/@types/sanitize-html/-/sanitize-html-2.9.5.tgz", - "integrity": "sha512-2Sr1vd8Dw+ypsg/oDDfZ57OMSG2Befs+l2CMyCC5bVSK3CpE7lTB2aNlbbWzazgVA+Qqfuholwom6x/mWd1qmw==", + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@types/sanitize-html/-/sanitize-html-2.11.0.tgz", + "integrity": "sha512-7oxPGNQHXLHE48r/r/qjn7q0hlrs3kL7oZnGj0Wf/h9tj/6ibFyRkNbsDxaBBZ4XUZ0Dx5LGCyDJ04ytSofacQ==", "dev": true, "dependencies": { "htmlparser2": "^8.0.0" } }, - "node_modules/@types/scheduler": { - "version": "0.16.8", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz", - "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==" - }, "node_modules/@types/semver": { - "version": "7.5.6", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", - "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", + "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", "dev": true }, "node_modules/@types/send": { @@ -9749,9 +9303,9 @@ } }, "node_modules/@types/yargs": { - "version": "16.0.9", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz", - "integrity": "sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==", + "version": "17.0.32", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", + "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", "dev": true, "dependencies": { "@types/yargs-parser": "*" @@ -9779,16 +9333,16 @@ "integrity": "sha512-TlYNIa9XHCGYRqVrijiDVj72Sc4Yd9At0NYEaHm8Su94GwcsXRq5y1AhA8tZUVNXYRCNpGWuOWI4VQ+R58MgUw==" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.17.0.tgz", - "integrity": "sha512-Vih/4xLXmY7V490dGwBQJTpIZxH4ZFH6eCVmQ4RFkB+wmaCTDAx4dtgoWwMNGKLkqRY1L6rPqzEbjorRnDo4rQ==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz", + "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.17.0", - "@typescript-eslint/type-utils": "6.17.0", - "@typescript-eslint/utils": "6.17.0", - "@typescript-eslint/visitor-keys": "6.17.0", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/type-utils": "6.21.0", + "@typescript-eslint/utils": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -9814,9 +9368,9 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/types": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.17.0.tgz", - "integrity": "sha512-qRKs9tvc3a4RBcL/9PXtKSehI/q8wuU9xYJxe97WFxnzH8NWWtcW3ffNS+EWg8uPvIerhjsEZ+rHtDqOCiH57A==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -9827,12 +9381,12 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.17.0.tgz", - "integrity": "sha512-H6VwB/k3IuIeQOyYczyyKN8wH6ed8EwliaYHLxOIhyF0dYEIsN8+Bk3GE19qafeMKyZJJHP8+O1HiFhFLUNKSg==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.17.0", + "@typescript-eslint/types": "6.21.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -9889,15 +9443,15 @@ "dev": true }, "node_modules/@typescript-eslint/parser": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.17.0.tgz", - "integrity": "sha512-C4bBaX2orvhK+LlwrY8oWGmSl4WolCfYm513gEccdWZj0CwGadbIADb0FtVEcI+WzUyjyoBj2JRP8g25E6IB8A==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", + "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "6.17.0", - "@typescript-eslint/types": "6.17.0", - "@typescript-eslint/typescript-estree": "6.17.0", - "@typescript-eslint/visitor-keys": "6.17.0", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4" }, "engines": { @@ -9917,9 +9471,9 @@ } }, "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.17.0.tgz", - "integrity": "sha512-qRKs9tvc3a4RBcL/9PXtKSehI/q8wuU9xYJxe97WFxnzH8NWWtcW3ffNS+EWg8uPvIerhjsEZ+rHtDqOCiH57A==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -9930,13 +9484,13 @@ } }, "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.17.0.tgz", - "integrity": "sha512-gVQe+SLdNPfjlJn5VNGhlOhrXz4cajwFd5kAgWtZ9dCZf4XJf8xmgCTLIqec7aha3JwgLI2CK6GY1043FRxZwg==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", + "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.17.0", - "@typescript-eslint/visitor-keys": "6.17.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -9958,12 +9512,12 @@ } }, "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.17.0.tgz", - "integrity": "sha512-H6VwB/k3IuIeQOyYczyyKN8wH6ed8EwliaYHLxOIhyF0dYEIsN8+Bk3GE19qafeMKyZJJHP8+O1HiFhFLUNKSg==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.17.0", + "@typescript-eslint/types": "6.21.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -9995,18 +9549,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/@typescript-eslint/parser/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@typescript-eslint/parser/node_modules/minimatch": { "version": "9.0.3", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", @@ -10023,13 +9565,10 @@ } }, "node_modules/@typescript-eslint/parser/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, "bin": { "semver": "bin/semver.js" }, @@ -10037,20 +9576,14 @@ "node": ">=10" } }, - "node_modules/@typescript-eslint/parser/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.17.0.tgz", - "integrity": "sha512-RX7a8lwgOi7am0k17NUO0+ZmMOX4PpjLtLRgLmT1d3lBYdWH4ssBUbwdmc5pdRX8rXon8v9x8vaoOSpkHfcXGA==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", + "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.17.0", - "@typescript-eslint/visitor-keys": "6.17.0" + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -10061,9 +9594,9 @@ } }, "node_modules/@typescript-eslint/scope-manager/node_modules/@typescript-eslint/types": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.17.0.tgz", - "integrity": "sha512-qRKs9tvc3a4RBcL/9PXtKSehI/q8wuU9xYJxe97WFxnzH8NWWtcW3ffNS+EWg8uPvIerhjsEZ+rHtDqOCiH57A==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -10074,12 +9607,12 @@ } }, "node_modules/@typescript-eslint/scope-manager/node_modules/@typescript-eslint/visitor-keys": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.17.0.tgz", - "integrity": "sha512-H6VwB/k3IuIeQOyYczyyKN8wH6ed8EwliaYHLxOIhyF0dYEIsN8+Bk3GE19qafeMKyZJJHP8+O1HiFhFLUNKSg==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.17.0", + "@typescript-eslint/types": "6.21.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -10103,13 +9636,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.17.0.tgz", - "integrity": "sha512-hDXcWmnbtn4P2B37ka3nil3yi3VCQO2QEB9gBiHJmQp5wmyQWqnjA85+ZcE8c4FqnaB6lBwMrPkgd4aBYz3iNg==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz", + "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.17.0", - "@typescript-eslint/utils": "6.17.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/utils": "6.21.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -10130,9 +9663,9 @@ } }, "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.17.0.tgz", - "integrity": "sha512-qRKs9tvc3a4RBcL/9PXtKSehI/q8wuU9xYJxe97WFxnzH8NWWtcW3ffNS+EWg8uPvIerhjsEZ+rHtDqOCiH57A==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -10143,13 +9676,13 @@ } }, "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.17.0.tgz", - "integrity": "sha512-gVQe+SLdNPfjlJn5VNGhlOhrXz4cajwFd5kAgWtZ9dCZf4XJf8xmgCTLIqec7aha3JwgLI2CK6GY1043FRxZwg==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", + "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.17.0", - "@typescript-eslint/visitor-keys": "6.17.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -10171,12 +9704,12 @@ } }, "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.17.0.tgz", - "integrity": "sha512-H6VwB/k3IuIeQOyYczyyKN8wH6ed8EwliaYHLxOIhyF0dYEIsN8+Bk3GE19qafeMKyZJJHP8+O1HiFhFLUNKSg==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.17.0", + "@typescript-eslint/types": "6.21.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -10208,18 +9741,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/@typescript-eslint/type-utils/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@typescript-eslint/type-utils/node_modules/minimatch": { "version": "9.0.3", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", @@ -10236,13 +9757,10 @@ } }, "node_modules/@typescript-eslint/type-utils/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, "bin": { "semver": "bin/semver.js" }, @@ -10250,12 +9768,6 @@ "node": ">=10" } }, - "node_modules/@typescript-eslint/type-utils/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/@typescript-eslint/types": { "version": "5.62.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", @@ -10330,17 +9842,17 @@ "dev": true }, "node_modules/@typescript-eslint/utils": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.17.0.tgz", - "integrity": "sha512-LofsSPjN/ITNkzV47hxas2JCsNCEnGhVvocfyOcLzT9c/tSZE7SfhS/iWtzP1lKNOEfLhRTZz6xqI8N2RzweSQ==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", + "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.17.0", - "@typescript-eslint/types": "6.17.0", - "@typescript-eslint/typescript-estree": "6.17.0", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", "semver": "^7.5.4" }, "engines": { @@ -10355,9 +9867,9 @@ } }, "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.17.0.tgz", - "integrity": "sha512-qRKs9tvc3a4RBcL/9PXtKSehI/q8wuU9xYJxe97WFxnzH8NWWtcW3ffNS+EWg8uPvIerhjsEZ+rHtDqOCiH57A==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -10368,13 +9880,13 @@ } }, "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.17.0.tgz", - "integrity": "sha512-gVQe+SLdNPfjlJn5VNGhlOhrXz4cajwFd5kAgWtZ9dCZf4XJf8xmgCTLIqec7aha3JwgLI2CK6GY1043FRxZwg==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", + "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.17.0", - "@typescript-eslint/visitor-keys": "6.17.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -10396,12 +9908,12 @@ } }, "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/visitor-keys": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.17.0.tgz", - "integrity": "sha512-H6VwB/k3IuIeQOyYczyyKN8wH6ed8EwliaYHLxOIhyF0dYEIsN8+Bk3GE19qafeMKyZJJHP8+O1HiFhFLUNKSg==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.17.0", + "@typescript-eslint/types": "6.21.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -10433,18 +9945,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/@typescript-eslint/utils/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@typescript-eslint/utils/node_modules/minimatch": { "version": "9.0.3", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", @@ -10461,13 +9961,10 @@ } }, "node_modules/@typescript-eslint/utils/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, "bin": { "semver": "bin/semver.js" }, @@ -10475,12 +9972,6 @@ "node": ">=10" } }, - "node_modules/@typescript-eslint/utils/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/@typescript-eslint/visitor-keys": { "version": "5.62.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", @@ -10615,9 +10106,9 @@ "integrity": "sha512-rIwlkkP1n4uKrRzivAKPZIEkHiuwY5mmhMJ2nZKCBLz8lTUlE73rQh4n1OnnMurXt1vcUNyH4ZPfdh8QweTjpQ==" }, "node_modules/@webassemblyjs/ast": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", - "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", + "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", "dev": true, "dependencies": { "@webassemblyjs/helper-numbers": "1.11.6", @@ -10637,9 +10128,9 @@ "dev": true }, "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz", - "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", + "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==", "dev": true }, "node_modules/@webassemblyjs/helper-numbers": { @@ -10660,15 +10151,15 @@ "dev": true }, "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz", - "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", + "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6" + "@webassemblyjs/wasm-gen": "1.12.1" } }, "node_modules/@webassemblyjs/ieee754": { @@ -10696,28 +10187,28 @@ "dev": true }, "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz", - "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", + "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/helper-wasm-section": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6", - "@webassemblyjs/wasm-opt": "1.11.6", - "@webassemblyjs/wasm-parser": "1.11.6", - "@webassemblyjs/wast-printer": "1.11.6" + "@webassemblyjs/helper-wasm-section": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-opt": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1", + "@webassemblyjs/wast-printer": "1.12.1" } }, "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz", - "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", + "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/ast": "1.12.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.6", "@webassemblyjs/ieee754": "1.11.6", "@webassemblyjs/leb128": "1.11.6", @@ -10725,24 +10216,24 @@ } }, "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz", - "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", + "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6", - "@webassemblyjs/wasm-parser": "1.11.6" + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1" } }, "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz", - "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", + "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/ast": "1.12.1", "@webassemblyjs/helper-api-error": "1.11.6", "@webassemblyjs/helper-wasm-bytecode": "1.11.6", "@webassemblyjs/ieee754": "1.11.6", @@ -10751,12 +10242,12 @@ } }, "node_modules/@webassemblyjs/wast-printer": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz", - "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", + "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/ast": "1.12.1", "@xtuc/long": "4.2.2" } }, @@ -10863,10 +10354,10 @@ "acorn-walk": "^8.0.2" } }, - "node_modules/acorn-import-assertions": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", - "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", + "node_modules/acorn-import-attributes": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", "dev": true, "peerDependencies": { "acorn": "^8" @@ -11025,6 +10516,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/ansi-html": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.9.tgz", + "integrity": "sha512-ozbS3LuenHVxNRh/wdnN16QapUHzauqSomAl1jwwJRRsGwFwtj644lIhxfWu0Fy0acCij2+AEgHvjscq3dlVXg==", + "dev": true, + "engines": [ + "node >= 0.8.0" + ], + "bin": { + "ansi-html": "bin/ansi-html" + } + }, "node_modules/ansi-html-community": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", @@ -12750,7 +12253,8 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==", - "dev": true + "dev": true, + "peer": true }, "node_modules/common-tags": { "version": "1.8.2", @@ -12957,32 +12461,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/create-jest/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/create-jest/node_modules/@types/yargs": { - "version": "17.0.32", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", - "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, "node_modules/create-jest/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -13129,19 +12607,19 @@ } }, "node_modules/css-loader": { - "version": "6.8.1", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.8.1.tgz", - "integrity": "sha512-xDAXtEVGlD0gJ07iclwWVkLoZOpEvAWaSyf6W18S2pOC//K8+qUDIx8IIT3D+HjnmkJPQeesOPv5aiUaJsCM2g==", + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.11.0.tgz", + "integrity": "sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==", "dev": true, "dependencies": { "icss-utils": "^5.1.0", - "postcss": "^8.4.21", - "postcss-modules-extract-imports": "^3.0.0", - "postcss-modules-local-by-default": "^4.0.3", - "postcss-modules-scope": "^3.0.0", + "postcss": "^8.4.33", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", "postcss-modules-values": "^4.0.0", "postcss-value-parser": "^4.2.0", - "semver": "^7.3.8" + "semver": "^7.5.4" }, "engines": { "node": ">= 12.13.0" @@ -13151,7 +12629,16 @@ "url": "https://opencollective.com/webpack" }, "peerDependencies": { + "@rspack/core": "0.x || 1.x", "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } } }, "node_modules/css-loader/node_modules/lru-cache": { @@ -13394,21 +12881,20 @@ } }, "node_modules/cypress": { - "version": "13.6.2", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.6.2.tgz", - "integrity": "sha512-TW3bGdPU4BrfvMQYv1z3oMqj71YI4AlgJgnrycicmPZAXtvywVFZW9DAToshO65D97rCWfG/kqMFsYB6Kp91gQ==", + "version": "13.12.0", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.12.0.tgz", + "integrity": "sha512-udzS2JilmI9ApO/UuqurEwOvThclin5ntz7K0BtnHBs+tg2Bl9QShLISXpSEMDv/u8b6mqdoAdyKeZiSqKWL8g==", "dev": true, "hasInstallScript": true, "dependencies": { "@cypress/request": "^3.0.0", "@cypress/xvfb": "^1.2.4", - "@types/node": "^18.17.5", "@types/sinonjs__fake-timers": "8.1.1", "@types/sizzle": "^2.3.2", "arch": "^2.2.0", "blob-util": "^2.0.2", "bluebird": "^3.7.2", - "buffer": "^5.6.0", + "buffer": "^5.7.1", "cachedir": "^2.3.0", "chalk": "^4.1.0", "check-more-types": "^2.24.0", @@ -13426,7 +12912,7 @@ "figures": "^3.2.0", "fs-extra": "^9.1.0", "getos": "^3.2.1", - "is-ci": "^3.0.0", + "is-ci": "^3.0.1", "is-installed-globally": "~0.4.0", "lazy-ass": "^1.6.0", "listr2": "^3.8.3", @@ -13452,9 +12938,9 @@ } }, "node_modules/cypress-localstorage-commands": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/cypress-localstorage-commands/-/cypress-localstorage-commands-2.2.5.tgz", - "integrity": "sha512-07zpwzWdY+uPi1NEHFhWQNylIJqRxR78Ile05L6WT8h1Gz0OaxgBSZRuzp+pqUni/3Pk4d2ieq/cSh++ZmujEA==", + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/cypress-localstorage-commands/-/cypress-localstorage-commands-2.2.6.tgz", + "integrity": "sha512-l3nZ+Lu6YbBWk4UIfNrRkNK56BkF8zVbCrqzCf35x4Nlx2wA2r0spBOZXnKjbutQZgo6qDqtS8uXoSqV36YM1Q==", "dev": true, "engines": { "node": ">=14.0.0" @@ -13463,15 +12949,6 @@ "cypress": ">=2.1.0" } }, - "node_modules/cypress/node_modules/@types/node": { - "version": "18.19.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.4.tgz", - "integrity": "sha512-xNzlUhzoHotIsnFoXmJB+yWmBvFZgKCI9TtPIEdYIMM1KWfwuY8zh7wvc1u1OAXlC7dlf6mZVx/s+Y5KfFz19A==", - "dev": true, - "dependencies": { - "undici-types": "~5.26.4" - } - }, "node_modules/cypress/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -14668,9 +14145,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.15.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", - "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", + "version": "5.17.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.0.tgz", + "integrity": "sha512-dwDPwZL0dmye8Txp2gzFmA6sxALaSvdRDjPH0viLcKrtlOL3tw62nWWweVD1SdILDTJrbrL6tdWVN58Wo6U3eA==", "dev": true, "dependencies": { "graceful-fs": "^4.2.4", @@ -14937,16 +14414,16 @@ } }, "node_modules/eslint": { - "version": "8.56.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", - "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.56.0", - "@humanwhocodes/config-array": "^0.11.13", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", @@ -16201,20 +15678,6 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -17289,48 +16752,48 @@ } }, "node_modules/intl-messageformat": { - "version": "10.5.8", - "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.5.8.tgz", - "integrity": "sha512-NRf0jpBWV0vd671G5b06wNofAN8tp7WWDogMZyaU8GUAsmbouyvgwmFJI7zLjfAMpm3zK+vSwRP3jzaoIcMbaA==", + "version": "10.5.14", + "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.5.14.tgz", + "integrity": "sha512-IjC6sI0X7YRjjyVH9aUgdftcmZK7WXdHeil4KwbjDnRWjnVitKpAx3rr6t6di1joFp5188VqKcobOPA6mCLG/w==", "dependencies": { - "@formatjs/ecma402-abstract": "1.18.0", + "@formatjs/ecma402-abstract": "2.0.0", "@formatjs/fast-memoize": "2.2.0", - "@formatjs/icu-messageformat-parser": "2.7.3", + "@formatjs/icu-messageformat-parser": "2.7.8", "tslib": "^2.4.0" } }, "node_modules/intl-messageformat/node_modules/@formatjs/ecma402-abstract": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.18.0.tgz", - "integrity": "sha512-PEVLoa3zBevWSCZzPIM/lvPCi8P5l4G+NXQMc/CjEiaCWgyHieUoo0nM7Bs0n/NbuQ6JpXEolivQ9pKSBHaDlA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.0.0.tgz", + "integrity": "sha512-rRqXOqdFmk7RYvj4khklyqzcfQl9vEL/usogncBHRZfZBDOwMGuSRNFl02fu5KGHXdbinju+YXyuR+Nk8xlr/g==", "dependencies": { - "@formatjs/intl-localematcher": "0.5.2", + "@formatjs/intl-localematcher": "0.5.4", "tslib": "^2.4.0" } }, "node_modules/intl-messageformat/node_modules/@formatjs/icu-messageformat-parser": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.7.3.tgz", - "integrity": "sha512-X/jy10V9S/vW+qlplqhMUxR8wErQ0mmIYSq4mrjpjDl9mbuGcCILcI1SUYkL5nlM4PJqpc0KOS0bFkkJNPxYRw==", + "version": "2.7.8", + "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.7.8.tgz", + "integrity": "sha512-nBZJYmhpcSX0WeJ5SDYUkZ42AgR3xiyhNCsQweFx3cz/ULJjym8bHAzWKvG5e2+1XO98dBYC0fWeeAECAVSwLA==", "dependencies": { - "@formatjs/ecma402-abstract": "1.18.0", - "@formatjs/icu-skeleton-parser": "1.7.0", + "@formatjs/ecma402-abstract": "2.0.0", + "@formatjs/icu-skeleton-parser": "1.8.2", "tslib": "^2.4.0" } }, "node_modules/intl-messageformat/node_modules/@formatjs/icu-skeleton-parser": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.7.0.tgz", - "integrity": "sha512-Cfdo/fgbZzpN/jlN/ptQVe0lRHora+8ezrEeg2RfrNjyp+YStwBy7cqDY8k5/z2LzXg6O0AdzAV91XS0zIWv+A==", + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.2.tgz", + "integrity": "sha512-k4ERKgw7aKGWJZgTarIcNEmvyTVD9FYh0mTrrBMHZ1b8hUu6iOJ4SzsZlo3UNAvHYa+PnvntIwRPt1/vy4nA9Q==", "dependencies": { - "@formatjs/ecma402-abstract": "1.18.0", + "@formatjs/ecma402-abstract": "2.0.0", "tslib": "^2.4.0" } }, "node_modules/intl-messageformat/node_modules/@formatjs/intl-localematcher": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.2.tgz", - "integrity": "sha512-txaaE2fiBMagLrR4jYhxzFO6wEdEG4TPMqrzBAcbr4HFUYzH/YC+lg6OIzKCHm8WgDdyQevxbAAV1OgcXctuGw==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.4.tgz", + "integrity": "sha512-zTwEpWOzZ2CiKcB93BLngUX59hQkuZjT2+SAQEscSm52peDW/getsawMcWF1rGRpMCX6D7nSJA3CzJ8gn13N/g==", "dependencies": { "tslib": "^2.4.0" } @@ -18217,16 +17680,6 @@ } } }, - "node_modules/jest_workaround": { - "version": "0.79.19", - "resolved": "https://registry.npmjs.org/jest_workaround/-/jest_workaround-0.79.19.tgz", - "integrity": "sha512-g/MtKSwyb4Ohnd5GHeJaduTgznkyst81x+eUBGOSGK7f8doWuRMPpt6XM/13sM2jLB2QNzT/7Djj7o2PhsozIA==", - "dev": true, - "peerDependencies": { - "@swc/core": "^1.3.68", - "@swc/jest": "^0.2.26" - } - }, "node_modules/jest-changed-files": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", @@ -18312,34 +17765,8 @@ "slash": "^3.0.0", "stack-utils": "^2.0.3" }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-circus/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-circus/node_modules/@types/yargs": { - "version": "17.0.32", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", - "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-circus/node_modules/ansi-styles": { @@ -18477,32 +17904,6 @@ } } }, - "node_modules/jest-cli/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-cli/node_modules/@types/yargs": { - "version": "17.0.32", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", - "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, "node_modules/jest-cli/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -18618,32 +18019,6 @@ } } }, - "node_modules/jest-config/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-config/node_modules/@types/yargs": { - "version": "17.0.32", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", - "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, "node_modules/jest-config/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -18891,32 +18266,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-each/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-each/node_modules/@types/yargs": { - "version": "17.0.32", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", - "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, "node_modules/jest-each/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -19046,81 +18395,6 @@ } } }, - "node_modules/jest-environment-jsdom/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-environment-jsdom/node_modules/@types/yargs": { - "version": "17.0.32", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", - "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-environment-jsdom/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-environment-jsdom/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-environment-jsdom/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-environment-jsdom/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, "node_modules/jest-environment-jsdom/node_modules/cssstyle": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", @@ -19153,15 +18427,6 @@ "node": ">=12" } }, - "node_modules/jest-environment-jsdom/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-environment-jsdom/node_modules/jsdom": { "version": "20.0.3", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz", @@ -19207,18 +18472,6 @@ } } }, - "node_modules/jest-environment-jsdom/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-environment-jsdom/node_modules/tr46": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", @@ -19228,133 +18481,37 @@ "punycode": "^2.1.1" }, "engines": { - "node": ">=12" - } - }, - "node_modules/jest-environment-jsdom/node_modules/whatwg-url": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", - "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", - "dev": true, - "dependencies": { - "tr46": "^3.0.0", - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/jest-environment-node": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", - "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", - "dev": true, - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-environment-node/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-environment-node/node_modules/@types/yargs": { - "version": "17.0.32", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", - "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-environment-node/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-environment-node/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-environment-node/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" + "node": ">=12" } }, - "node_modules/jest-environment-node/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-environment-node/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/jest-environment-jsdom/node_modules/whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", "dev": true, + "dependencies": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + }, "engines": { - "node": ">=8" + "node": ">=12" } }, - "node_modules/jest-environment-node/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-get-type": { @@ -19391,102 +18548,6 @@ "fsevents": "^2.3.2" } }, - "node_modules/jest-haste-map/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-haste-map/node_modules/@types/yargs": { - "version": "17.0.32", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", - "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-haste-map/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-haste-map/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-haste-map/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-haste-map/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-haste-map/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-haste-map/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-image-snapshot": { "version": "6.4.0", "resolved": "https://registry.npmjs.org/jest-image-snapshot/-/jest-image-snapshot-6.4.0.tgz", @@ -19778,32 +18839,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-message-util/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-message-util/node_modules/@types/yargs": { - "version": "17.0.32", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", - "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, "node_modules/jest-message-util/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -19931,102 +18966,6 @@ "synchronous-promise": "^2.0.17" } }, - "node_modules/jest-mock/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-mock/node_modules/@types/yargs": { - "version": "17.0.32", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", - "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-mock/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-mock/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-mock/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-mock/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-mock/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-mock/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-pnp-resolver": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", @@ -20179,41 +19118,15 @@ "jest-resolve": "^29.7.0", "jest-runtime": "^29.7.0", "jest-util": "^29.7.0", - "jest-watcher": "^29.7.0", - "jest-worker": "^29.7.0", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-runner/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-runner/node_modules/@types/yargs": { - "version": "17.0.32", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", - "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, "node_modules/jest-runner/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -20317,32 +19230,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-runtime/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-runtime/node_modules/@types/yargs": { - "version": "17.0.32", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", - "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, "node_modules/jest-runtime/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -20444,32 +19331,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-snapshot/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/@types/yargs": { - "version": "17.0.32", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", - "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, "node_modules/jest-snapshot/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -20622,32 +19483,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-util/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-util/node_modules/@types/yargs": { - "version": "17.0.32", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", - "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, "node_modules/jest-util/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -20735,32 +19570,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-validate/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-validate/node_modules/@types/yargs": { - "version": "17.0.32", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", - "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, "node_modules/jest-validate/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -20894,32 +19703,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-watcher/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-watcher/node_modules/@types/yargs": { - "version": "17.0.32", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", - "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, "node_modules/jest-watcher/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -21029,102 +19812,6 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/jest/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest/node_modules/@types/yargs": { - "version": "17.0.32", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", - "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/joi": { "version": "17.11.0", "resolved": "https://registry.npmjs.org/joi/-/joi-17.11.0.tgz", @@ -21139,9 +19826,9 @@ } }, "node_modules/jotai": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/jotai/-/jotai-2.6.1.tgz", - "integrity": "sha512-GLQtAnA9iEKRMXnyCjf1azIxfQi5JausX2EI5qSlb59j4i73ZEyV/EXPDEAQj4uQNZYEefi3degv/Pw3+L/Dtg==", + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/jotai/-/jotai-2.8.4.tgz", + "integrity": "sha512-f6jwjhBJcDtpeauT2xH01gnqadKEySwwt1qNBLvAXcnojkmb76EdqRt05Ym8IamfHGAQz2qMKAwftnyjeSoHAA==", "engines": { "node": ">=12.20.0" }, @@ -22355,12 +21042,13 @@ } }, "node_modules/mini-css-extract-plugin": { - "version": "2.7.6", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.7.6.tgz", - "integrity": "sha512-Qk7HcgaPkGG6eD77mLvZS1nmxlao3j+9PkrT9Uc7HAE1id3F41+DdBRYRYkbyfNRGzm8/YWtzhw7nVPmwhqTQw==", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.0.tgz", + "integrity": "sha512-Zs1YsZVfemekSZG+44vBsYTLQORkPMwnlv+aehcxK/NLKC+EGhDB39/YePYYqx/sTk6NnYpuqikhSn7+JIevTA==", "dev": true, "dependencies": { - "schema-utils": "^4.0.0" + "schema-utils": "^4.0.0", + "tapable": "^2.2.1" }, "engines": { "node": ">= 12.13.0" @@ -24171,9 +22859,9 @@ } }, "node_modules/postcss": { - "version": "8.4.32", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz", - "integrity": "sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==", + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", "funding": [ { "type": "opencollective", @@ -24191,16 +22879,16 @@ "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "source-map-js": "^1.2.0" }, "engines": { "node": "^10 || ^12 || >=14" } }, "node_modules/postcss-modules-extract-imports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", - "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", + "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", "dev": true, "engines": { "node": "^10 || ^12 || >= 14" @@ -24210,9 +22898,9 @@ } }, "node_modules/postcss-modules-local-by-default": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.3.tgz", - "integrity": "sha512-2/u2zraspoACtrbFRnTijMiQtb4GW4BvatjaG/bCjYQo8kLTdevCUlwuBHx2sCnSyrI3x3qj4ZK1j5LQBgzmwA==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.5.tgz", + "integrity": "sha512-6MieY7sIfTK0hYfafw1OMEG+2bg8Q1ocHCpoWLqOKj3JXlKu4G7btkmM/B7lFubYkYWmRSPLZi5chid63ZaZYw==", "dev": true, "dependencies": { "icss-utils": "^5.0.0", @@ -24227,9 +22915,9 @@ } }, "node_modules/postcss-modules-scope": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.1.0.tgz", - "integrity": "sha512-SaIbK8XW+MZbd0xHPf7kdfA/3eOt7vxJ72IRecn3EzuZVLr1r0orzf0MX/pN8m+NMDoo6X/SQd8oeKqGZd8PXg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.0.tgz", + "integrity": "sha512-oq+g1ssrsZOsx9M96c5w8laRmvEu9C3adDSjI8oTcbfkrTE8hx/zfyobUoWIxaKPO8bt6S62kxpw5GqypEw1QQ==", "dev": true, "dependencies": { "postcss-selector-parser": "^6.0.4" @@ -24273,9 +22961,9 @@ } }, "node_modules/postcss-selector-parser": { - "version": "6.0.15", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.15.tgz", - "integrity": "sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.0.tgz", + "integrity": "sha512-UMz42UD0UY0EApS0ZL9o1XnLhSTtvvvLe5Dc2H2O56fvRZi+KulDyf5ctDhhtYJBGKStV2FL1fy6253cmLgqVQ==", "dev": true, "dependencies": { "cssesc": "^3.0.0", @@ -24974,9 +23662,9 @@ } }, "node_modules/react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "dependencies": { "loose-envify": "^1.1.0" }, @@ -25001,15 +23689,15 @@ "integrity": "sha512-I+vcaK9t4+kypiSgaiVWAipqHRXYmZIuAiS8vzFvXHHXVigg/sMKwlRgLy6LH2i3rmP+0Vzfl5lFsFRwF1r3pg==" }, "node_modules/react-dom": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", - "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "dependencies": { "loose-envify": "^1.1.0", - "scheduler": "^0.23.0" + "scheduler": "^0.23.2" }, "peerDependencies": { - "react": "^18.2.0" + "react": "^18.3.1" } }, "node_modules/react-dropzone": { @@ -25064,24 +23752,24 @@ } }, "node_modules/react-intl": { - "version": "6.5.5", - "resolved": "https://registry.npmjs.org/react-intl/-/react-intl-6.5.5.tgz", - "integrity": "sha512-cI5UKvBh4tc1zxLIziHBYGMX3dhYWDEFlvUDVN6NfT2i96zTXz/zH2AmM8+2waqgOhwkFUzd+7kK1G9q7fiC2g==", - "dependencies": { - "@formatjs/ecma402-abstract": "1.18.0", - "@formatjs/icu-messageformat-parser": "2.7.3", - "@formatjs/intl": "2.9.9", - "@formatjs/intl-displaynames": "6.6.4", - "@formatjs/intl-listformat": "7.5.3", + "version": "6.6.8", + "resolved": "https://registry.npmjs.org/react-intl/-/react-intl-6.6.8.tgz", + "integrity": "sha512-M0pkhzcgV31h++2901BiRXWl69hp2zPyLxRrSwRjd1ErXbNoubz/f4M6DrRTd4OiSUrT4ajRQzrmtS5plG4FtA==", + "dependencies": { + "@formatjs/ecma402-abstract": "2.0.0", + "@formatjs/icu-messageformat-parser": "2.7.8", + "@formatjs/intl": "2.10.4", + "@formatjs/intl-displaynames": "6.6.8", + "@formatjs/intl-listformat": "7.5.7", "@types/hoist-non-react-statics": "^3.3.1", "@types/react": "16 || 17 || 18", "hoist-non-react-statics": "^3.3.2", - "intl-messageformat": "10.5.8", + "intl-messageformat": "10.5.14", "tslib": "^2.4.0" }, "peerDependencies": { "react": "^16.6.0 || 17 || 18", - "typescript": "5" + "typescript": "^4.7 || 5" }, "peerDependenciesMeta": { "typescript": { @@ -25090,37 +23778,37 @@ } }, "node_modules/react-intl/node_modules/@formatjs/ecma402-abstract": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.18.0.tgz", - "integrity": "sha512-PEVLoa3zBevWSCZzPIM/lvPCi8P5l4G+NXQMc/CjEiaCWgyHieUoo0nM7Bs0n/NbuQ6JpXEolivQ9pKSBHaDlA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.0.0.tgz", + "integrity": "sha512-rRqXOqdFmk7RYvj4khklyqzcfQl9vEL/usogncBHRZfZBDOwMGuSRNFl02fu5KGHXdbinju+YXyuR+Nk8xlr/g==", "dependencies": { - "@formatjs/intl-localematcher": "0.5.2", + "@formatjs/intl-localematcher": "0.5.4", "tslib": "^2.4.0" } }, "node_modules/react-intl/node_modules/@formatjs/icu-messageformat-parser": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.7.3.tgz", - "integrity": "sha512-X/jy10V9S/vW+qlplqhMUxR8wErQ0mmIYSq4mrjpjDl9mbuGcCILcI1SUYkL5nlM4PJqpc0KOS0bFkkJNPxYRw==", + "version": "2.7.8", + "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.7.8.tgz", + "integrity": "sha512-nBZJYmhpcSX0WeJ5SDYUkZ42AgR3xiyhNCsQweFx3cz/ULJjym8bHAzWKvG5e2+1XO98dBYC0fWeeAECAVSwLA==", "dependencies": { - "@formatjs/ecma402-abstract": "1.18.0", - "@formatjs/icu-skeleton-parser": "1.7.0", + "@formatjs/ecma402-abstract": "2.0.0", + "@formatjs/icu-skeleton-parser": "1.8.2", "tslib": "^2.4.0" } }, "node_modules/react-intl/node_modules/@formatjs/icu-skeleton-parser": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.7.0.tgz", - "integrity": "sha512-Cfdo/fgbZzpN/jlN/ptQVe0lRHora+8ezrEeg2RfrNjyp+YStwBy7cqDY8k5/z2LzXg6O0AdzAV91XS0zIWv+A==", + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.2.tgz", + "integrity": "sha512-k4ERKgw7aKGWJZgTarIcNEmvyTVD9FYh0mTrrBMHZ1b8hUu6iOJ4SzsZlo3UNAvHYa+PnvntIwRPt1/vy4nA9Q==", "dependencies": { - "@formatjs/ecma402-abstract": "1.18.0", + "@formatjs/ecma402-abstract": "2.0.0", "tslib": "^2.4.0" } }, "node_modules/react-intl/node_modules/@formatjs/intl-localematcher": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.2.tgz", - "integrity": "sha512-txaaE2fiBMagLrR4jYhxzFO6wEdEG4TPMqrzBAcbr4HFUYzH/YC+lg6OIzKCHm8WgDdyQevxbAAV1OgcXctuGw==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.4.tgz", + "integrity": "sha512-zTwEpWOzZ2CiKcB93BLngUX59hQkuZjT2+SAQEscSm52peDW/getsawMcWF1rGRpMCX6D7nSJA3CzJ8gn13N/g==", "dependencies": { "tslib": "^2.4.0" } @@ -25207,20 +23895,20 @@ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" }, "node_modules/react-refresh": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", - "integrity": "sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==", + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", + "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", "dev": true, "engines": { "node": ">=0.10.0" } }, "node_modules/react-router": { - "version": "6.21.1", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.21.1.tgz", - "integrity": "sha512-W0l13YlMTm1YrpVIOpjCADJqEUpz1vm+CMo47RuFX4Ftegwm6KOYsL5G3eiE52jnJpKvzm6uB/vTKTPKM8dmkA==", + "version": "6.24.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.24.0.tgz", + "integrity": "sha512-sQrgJ5bXk7vbcC4BxQxeNa5UmboFm35we1AFK0VvQaz9g0LzxEIuLOhHIoZ8rnu9BO21ishGeL9no1WB76W/eg==", "dependencies": { - "@remix-run/router": "1.14.1" + "@remix-run/router": "1.17.0" }, "engines": { "node": ">=14.0.0" @@ -25230,12 +23918,12 @@ } }, "node_modules/react-router-dom": { - "version": "6.21.1", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.21.1.tgz", - "integrity": "sha512-QCNrtjtDPwHDO+AO21MJd7yIcr41UetYt5jzaB9Y1UYaPTCnVuJq6S748g1dE11OQlCFIQg+RtAA1SEZIyiBeA==", + "version": "6.24.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.24.0.tgz", + "integrity": "sha512-960sKuau6/yEwS8e+NVEidYQb1hNjAYM327gjEyXlc6r3Skf2vtwuJ2l7lssdegD2YjoKG5l8MsVyeTDlVeY8g==", "dependencies": { - "@remix-run/router": "1.14.1", - "react-router": "6.21.1" + "@remix-run/router": "1.17.0", + "react-router": "6.24.0" }, "engines": { "node": ">=14.0.0" @@ -25974,9 +24662,9 @@ "dev": true }, "node_modules/sanitize-html": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.12.1.tgz", - "integrity": "sha512-Plh+JAn0UVDpBRP/xEjsk+xDCoOvMBwQUf/K+/cBAVuTbtX8bj2VB7S1sL1dssVpykqp0/KPSesHrqXtokVBpA==", + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.13.0.tgz", + "integrity": "sha512-Xff91Z+4Mz5QiNSLdLWwjgBDm5b1RU6xBT0+12rapjiaR7SwfRdjw8f+6Rir2MXKLrDicRFHdb51hGOAxmsUIA==", "dependencies": { "deepmerge": "^4.2.2", "escape-string-regexp": "^4.0.0", @@ -25998,9 +24686,9 @@ } }, "node_modules/sass": { - "version": "1.69.6", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.69.6.tgz", - "integrity": "sha512-qbRr3k9JGHWXCvZU77SD2OTwUlC+gNT+61JOLcmLm+XqH4h/5D+p4IIsxvpkB89S9AwJOyb5+rWNpIucaFxSFQ==", + "version": "1.77.6", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.77.6.tgz", + "integrity": "sha512-ByXE1oLD79GVq9Ht1PeHWCPMPB8XHpBuz1r85oByKHjZY6qV6rWnQovQzXJXuQ/XyE1Oj3iPk3lo28uzaRA2/Q==", "dev": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", @@ -26085,9 +24773,9 @@ } }, "node_modules/scheduler": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", - "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", "dependencies": { "loose-envify": "^1.1.0" } @@ -26531,9 +25219,9 @@ } }, "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", "engines": { "node": ">=0.10.0" } @@ -26577,11 +25265,6 @@ "node": ">=0.10.0" } }, - "node_modules/spark-md5": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/spark-md5/-/spark-md5-3.0.2.tgz", - "integrity": "sha512-wcFzz9cDfbuqe0FZzfi2or1sgyIrsDwmPwfZC4hiNidPdPINjeUwNfv5kldczoEAcjl9Y1L3SM7Uz2PUEQzxQw==" - }, "node_modules/spawn-wrap": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", @@ -26982,9 +25665,9 @@ } }, "node_modules/style-loader": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.3.tgz", - "integrity": "sha512-53BiGLXAcll9maCYtZi2RCQZKa8NQQai5C4horqKyRmHj9H7QmcUyucrH+4KW/gBQbXM2AsB0axoEcFZPlfPcw==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.4.tgz", + "integrity": "sha512-0WqXzrsMTyb8yjZJHDqwmnwRJvhALK9LfRtRc6B4UTWe8AijYLZYZ9thuJTZc2VfQWINADW/j+LiJnfy2RoC1w==", "dev": true, "engines": { "node": ">= 12.13.0" @@ -27053,19 +25736,22 @@ } }, "node_modules/swc-loader": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/swc-loader/-/swc-loader-0.2.3.tgz", - "integrity": "sha512-D1p6XXURfSPleZZA/Lipb3A8pZ17fP4NObZvFCDjK/OKljroqDpPmsBdTraWhVBqUNpcWBQY1imWdoPScRlQ7A==", + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/swc-loader/-/swc-loader-0.2.6.tgz", + "integrity": "sha512-9Zi9UP2YmDpgmQVbyOPJClY0dwf58JDyDMQ7uRc4krmc72twNI2fvlBWHLqVekBpPc7h5NJkGVT1zNDxFrqhvg==", "dev": true, + "dependencies": { + "@swc/counter": "^0.1.3" + }, "peerDependencies": { "@swc/core": "^1.2.147", "webpack": ">=2" } }, "node_modules/swc-plugin-coverage-instrument": { - "version": "0.0.20", - "resolved": "https://registry.npmjs.org/swc-plugin-coverage-instrument/-/swc-plugin-coverage-instrument-0.0.20.tgz", - "integrity": "sha512-WXTGILCZE2hW61yrmxi6doN/UB4RT2K1JJSQVPn9JMJ6X4WJpZsesHi4lHy6qRKVsNIlHZvTWofkpuRi/WQUig==", + "version": "0.0.21", + "resolved": "https://registry.npmjs.org/swc-plugin-coverage-instrument/-/swc-plugin-coverage-instrument-0.0.21.tgz", + "integrity": "sha512-KC1VX4PhSnjoOYY/5HKRL08KwO3GsFfMFpcOxatY9EeeEycsmKQFRj2kNAv/602d9l1Qgodhz1BDAdsT7OQrUA==", "dev": true }, "node_modules/symbol-observable": { @@ -27638,9 +26324,9 @@ } }, "node_modules/typescript": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", - "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.2.tgz", + "integrity": "sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew==", "devOptional": true, "bin": { "tsc": "bin/tsc", @@ -27665,12 +26351,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true - }, "node_modules/unfetch": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/unfetch/-/unfetch-4.2.0.tgz", @@ -27918,9 +26598,9 @@ "dev": true }, "node_modules/utility-types": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.10.0.tgz", - "integrity": "sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg==", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.11.0.tgz", + "integrity": "sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==", "dev": true, "engines": { "node": ">= 4" @@ -29276,9 +27956,9 @@ } }, "node_modules/watchpack": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", - "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.1.tgz", + "integrity": "sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg==", "dev": true, "dependencies": { "glob-to-regexp": "^0.4.1", @@ -29316,34 +27996,34 @@ } }, "node_modules/webpack": { - "version": "5.89.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.89.0.tgz", - "integrity": "sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw==", + "version": "5.92.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.92.1.tgz", + "integrity": "sha512-JECQ7IwJb+7fgUFBlrJzbyu3GEuNBcdqr1LD7IbSzwkSmIevTm8PF+wej3Oxuz/JFBUZ6O1o43zsPkwm1C4TmA==", "dev": true, "dependencies": { "@types/eslint-scope": "^3.7.3", - "@types/estree": "^1.0.0", - "@webassemblyjs/ast": "^1.11.5", - "@webassemblyjs/wasm-edit": "^1.11.5", - "@webassemblyjs/wasm-parser": "^1.11.5", + "@types/estree": "^1.0.5", + "@webassemblyjs/ast": "^1.12.1", + "@webassemblyjs/wasm-edit": "^1.12.1", + "@webassemblyjs/wasm-parser": "^1.12.1", "acorn": "^8.7.1", - "acorn-import-assertions": "^1.9.0", - "browserslist": "^4.14.5", + "acorn-import-attributes": "^1.9.5", + "browserslist": "^4.21.10", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.15.0", + "enhanced-resolve": "^5.17.0", "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.9", + "graceful-fs": "^4.2.11", "json-parse-even-better-errors": "^2.3.1", "loader-runner": "^4.2.0", "mime-types": "^2.1.27", "neo-async": "^2.6.2", "schema-utils": "^3.2.0", "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.7", - "watchpack": "^2.4.0", + "terser-webpack-plugin": "^5.3.10", + "watchpack": "^2.4.1", "webpack-sources": "^3.2.3" }, "bin": { @@ -29363,9 +28043,9 @@ } }, "node_modules/webpack-bundle-analyzer": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.10.1.tgz", - "integrity": "sha512-s3P7pgexgT/HTUSYgxJyn28A+99mmLq4HsJepMPzu0R8ImJc52QNqaFYW1Z2z2uIb1/J3eYgaAWVpaC+v/1aAQ==", + "version": "4.10.2", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.10.2.tgz", + "integrity": "sha512-vJptkMm9pk5si4Bv922ZbKLV8UTT4zib4FPgXMhgzUny0bfDDkLXAVQs3ly3fS4/TN9ROFtb0NFrm04UXFE/Vw==", "dev": true, "dependencies": { "@discoveryjs/json-ext": "0.5.7", @@ -29376,7 +28056,6 @@ "escape-string-regexp": "^4.0.0", "gzip-size": "^6.0.0", "html-escaper": "^2.0.2", - "is-plain-object": "^5.0.0", "opener": "^1.5.2", "picocolors": "^1.0.0", "sirv": "^2.0.3", @@ -29411,9 +28090,9 @@ } }, "node_modules/webpack-bundle-analyzer/node_modules/ws": { - "version": "7.5.10", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "version": "7.5.9", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", "dev": true, "engines": { "node": ">=8.3.0" @@ -29553,9 +28232,9 @@ } }, "node_modules/webpack-dev-server": { - "version": "4.15.1", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.1.tgz", - "integrity": "sha512-5hbAst3h3C3L8w6W4P96L5vaV0PxSmJhxZvWKYIdgxOQm8pNZ5dEOmmSLBVpP85ReeyRt6AS1QJNyo/oFFPeVA==", + "version": "4.15.2", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.2.tgz", + "integrity": "sha512-0XavAZbNJ5sDrCbkpWL8mia0o5WPOd2YGtxrEiZkBK9FjLppIUK2TgxK6qGD2P3hUXTJNNPVibrerKcx5WkR1g==", "dev": true, "dependencies": { "@types/bonjour": "^3.5.9", @@ -29586,7 +28265,7 @@ "serve-index": "^1.9.1", "sockjs": "^0.3.24", "spdy": "^4.0.2", - "webpack-dev-middleware": "^5.3.1", + "webpack-dev-middleware": "^5.3.4", "ws": "^8.13.0" }, "bin": { @@ -29943,9 +28622,9 @@ } }, "node_modules/ws": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", - "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", + "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", "dev": true, "engines": { "node": ">=10.0.0" diff --git a/package.json b/package.json index 7cd55272a..9bf4fd608 100644 --- a/package.json +++ b/package.json @@ -52,132 +52,132 @@ }, "homepage": "https://github.com/RedHatInsights/insights-chrome#readme", "devDependencies": { - "@cypress/code-coverage": "^3.12.16", + "@cypress/code-coverage": "^3.12.39", "@openshift/dynamic-plugin-sdk-webpack": "^3.0.1", - "@pmmmwh/react-refresh-webpack-plugin": "^0.5.11", + "@pmmmwh/react-refresh-webpack-plugin": "^0.5.15", "@redhat-cloud-services/eslint-config-redhat-cloud-services": "^1.3.0", - "@redhat-cloud-services/frontend-components-config-utilities": "^3.0.4", - "@redhat-cloud-services/types": "^1.0.10", + "@redhat-cloud-services/frontend-components-config-utilities": "^3.0.7", + "@redhat-cloud-services/types": "^1.0.11", "@simonsmith/cypress-image-snapshot": "^8.1.2", - "@swc/core": "^1.3.102", - "@swc/jest": "^0.2.29", + "@swc/core": "^1.6.5", + "@swc/jest": "^0.2.36", "@testing-library/jest-dom": "^5.17.0", - "@testing-library/react": "^14.1.2", + "@testing-library/react": "^14.3.1", "@testing-library/user-event": "^14.5.2", - "@types/jest": "^29.5.11", + "@types/jest": "^29.5.12", "@types/js-cookie": "^3.0.6", - "@types/lodash": "^4.14.202", - "@types/react": "^18.2.46", - "@types/react-dom": "^18.2.18", - "@types/redux-logger": "^3.0.12", + "@types/lodash": "^4.17.6", + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", + "@types/redux-logger": "3.0.12", "@types/redux-mock-store": "^1.0.6", - "@types/sanitize-html": "^2.9.5", + "@types/sanitize-html": "^2.11.0", "@types/urijs": "^1.19.25", - "@typescript-eslint/eslint-plugin": "^6.17.0", - "@typescript-eslint/parser": "^6.17.0", + "@typescript-eslint/eslint-plugin": "^6.21.0", + "@typescript-eslint/parser": "^6.21.0", "assert": "^2.1.0", "browserify-zlib": "^0.2.0", "buffer": "^6.0.3", "clean-webpack-plugin": "^4.0.0", - "css-loader": "^6.8.1", - "cypress": "^13.6.2", - "cypress-localstorage-commands": "^2.2.5", - "eslint": "^8.56.0", + "css-loader": "^6.11.0", + "cypress": "^13.12.0", + "cypress-localstorage-commands": "^2.2.6", + "eslint": "^8.57.0", "fork-ts-checker-webpack-plugin": "^7.3.0", "git-revision-webpack-plugin": "^5.0.0", "glob": "^7.2.3", "html-webpack-plugin": "^5.6.0", "identity-obj-proxy": "^3.0.0", "jest": "^29.7.0", - "jest_workaround": "^0.79.19", "jest-environment-jsdom": "^29.7.0", "jest-mock-axios": "^4.7.3", "jsdom": "^21.1.2", "jws": "^4.0.0", "madge": "^6.1.0", - "mini-css-extract-plugin": "^2.7.6", + "mini-css-extract-plugin": "^2.9.0", "mkdir": "^0.0.2", "ncp": "^2.0.0", "node-sass-package-importer": "^5.3.3", "npm-run-all": "^4.1.5", "path-browserify": "^1.0.1", "process": "^0.11.10", - "react-refresh": "^0.14.0", + "react-refresh": "^0.14.2", "redux-logger": "^3.0.6", "redux-mock-store": "^1.5.4", "resolve-url-loader": "^5.0.0", "rgb-hex": "^4.1.0", "rimraf": "^4.4.1", - "sass": "^1.69.6", + "sass": "^1.77.6", "sass-loader": "^13.3.3", "source-map-loader": "^4.0.2", "stream-browserify": "^3.0.0", - "style-loader": "^3.3.3", - "swc-loader": "^0.2.3", - "swc-plugin-coverage-instrument": "^0.0.20", + "style-loader": "^3.3.4", + "swc-loader": "^0.2.6", + "swc-plugin-coverage-instrument": "^0.0.21", "terser-webpack-plugin": "^5.3.10", - "typescript": "^5.3.3", + "typescript": "^5.5.2", "url": "^0.11.3", - "utility-types": "^3.10.0", + "utility-types": "^3.11.0", "wait-on": "^7.2.0", - "webpack": "^5.89.0", - "webpack-bundle-analyzer": "^4.10.1", + "webpack": "^5.92.1", + "webpack-bundle-analyzer": "^4.10.2", "webpack-cli": "^5.1.4", - "webpack-dev-server": "^4.15.1", + "webpack-dev-server": "^4.15.2", "whatwg-fetch": "^3.6.20", "yargs": "^17.7.2" }, "dependencies": { - "@data-driven-forms/pf4-component-mapper": "^3.22.1", - "@data-driven-forms/react-form-renderer": "^3.22.1", + "@data-driven-forms/pf4-component-mapper": "^3.22.4", + "@data-driven-forms/react-form-renderer": "^3.22.4", "@formatjs/cli": "4.8.4", "@openshift/dynamic-plugin-sdk": "^5.0.1", - "@orama/orama": "^2.0.3", - "@patternfly/patternfly": "^5.3.0", - "@patternfly/quickstarts": "^5.4.0-prerelease.1", - "@patternfly/react-charts": "^7.3.0", - "@patternfly/react-core": "^5.3.0", - "@patternfly/react-icons": "^5.3.0", - "@patternfly/react-tokens": "^5.3.0", - "@redhat-cloud-services/chrome": "^1.0.9", + "@orama/orama": "^2.0.21", + "@patternfly/patternfly": "^5.3.1", + "@patternfly/quickstarts": "^5.4.0-prerelease.4", + "@patternfly/react-charts": "^7.3.1", + "@patternfly/react-core": "^5.3.3", + "@patternfly/react-icons": "^5.3.2", + "@patternfly/react-table": "^5.3.3", + "@patternfly/react-tokens": "^5.3.1", + "@redhat-cloud-services/chrome": "^1.0.10", "@redhat-cloud-services/entitlements-client": "1.2.0", - "@redhat-cloud-services/frontend-components": "^4.2.2", + "@redhat-cloud-services/frontend-components": "^4.2.11", "@redhat-cloud-services/frontend-components-notifications": "^4.1.0", - "@redhat-cloud-services/frontend-components-pdf-generator": "4.0.4", - "@redhat-cloud-services/frontend-components-utilities": "^4.0.2", + "@redhat-cloud-services/frontend-components-pdf-generator": "4.0.5", + "@redhat-cloud-services/frontend-components-utilities": "^4.0.13", "@redhat-cloud-services/host-inventory-client": "1.2.0", "@redhat-cloud-services/rbac-client": "1.2.0", "@scalprum/core": "^0.7.0", - "@scalprum/react-core": "^0.7.0", - "@segment/analytics-next": "^1.62.0", - "@sentry/react": "^7.91.0", - "@sentry/tracing": "^7.91.0", - "@types/intercom-web": "^2.8.24", + "@scalprum/react-core": "^0.8.0", + "@segment/analytics-next": "^1.70.0", + "@sentry/react": "^7.118.0", + "@sentry/tracing": "^7.118.0", + "@types/intercom-web": "^2.8.26", "@unleash/proxy-client-react": "^3.6.0", "abortcontroller-polyfill": "^1.7.5", - "axios": "^0.28.0", + "axios": "^0.28.1", "axios-cache-interceptor": "^0.10.7", "axios-mock-adapter": "^1.22.0", "broadcast-channel": "^4.20.2", "classnames": "^2.5.1", "commander": "^10.0.1", "history": "^5.3.0", - "jotai": "^2.6.1", + "jotai": "^2.8.4", "js-cookie": "^3.0.5", "js-yaml": "^4.1.0", "localforage": "^1.10.0", "lodash": "^4.17.21", "oidc-client-ts": "^2.4.0", "pf-4-styles": "npm:@patternfly/patternfly@^4.224.5", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-intl": "^6.5.5", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-intl": "^6.6.8", "react-oidc-context": "^2.3.1", "react-redux": "^8.1.3", - "react-router-dom": "^6.21.1", + "react-router-dom": "^6.24.0", "redux": "^4.2.1", "redux-promise-middleware": "^6.2.0", - "sanitize-html": "^2.12.1", + "sanitize-html": "^2.13.0", "title-case": "^3.0.3", "urijs": "^1.19.11" }, diff --git a/src/auth/initializeAccessRequestCookies.test.ts b/src/auth/initializeAccessRequestCookies.test.ts index 11fa99827..85867e24a 100644 --- a/src/auth/initializeAccessRequestCookies.test.ts +++ b/src/auth/initializeAccessRequestCookies.test.ts @@ -4,6 +4,13 @@ import * as crossAccountBouncer from './crossAccountBouncer'; import Cookies from 'js-cookie'; import { ACTIVE_REMOTE_REQUEST, CROSS_ACCESS_ACCOUNT_NUMBER } from '../utils/consts'; +jest.mock('./crossAccountBouncer', () => { + return { + __esModule: true, + default: jest.fn(), + }; +}); + describe('initializeAccessRequestCookies', () => { const mockCrossAccountBouncer = jest.spyOn(crossAccountBouncer, 'default'); const mockCookiesGet = jest.spyOn(Cookies, 'get'); diff --git a/src/components/NotificationsDrawer/DrawerPanelContent.tsx b/src/components/NotificationsDrawer/DrawerPanelContent.tsx index 2d2bcf42f..28412f649 100644 --- a/src/components/NotificationsDrawer/DrawerPanelContent.tsx +++ b/src/components/NotificationsDrawer/DrawerPanelContent.tsx @@ -273,7 +273,6 @@ const DrawerPanelBase = ({ innerRef }: DrawerPanelProps) => { ]} count={notifications.filter(({ selected }) => selected).length} checked={notifications.length > 0 && notifications.every(({ selected }) => selected)} - ouiaId="notifications-bulk-select" /> ) => ( diff --git a/src/redux/redux-config.ts b/src/redux/redux-config.ts index 9381bcbd5..a81c5d89f 100644 --- a/src/redux/redux-config.ts +++ b/src/redux/redux-config.ts @@ -5,8 +5,9 @@ import MiddlewareListener from '@redhat-cloud-services/frontend-components-utili import logger from 'redux-logger'; import promise from 'redux-promise-middleware'; import { ReduxState } from './store'; +import { Middleware } from 'redux'; -const basicMiddlewares = []; +const basicMiddlewares: Middleware[] = []; if (process.env.NODE_ENV === 'development' || (window && window.localStorage.getItem('chrome:redux:debug') === 'true')) { basicMiddlewares.push(logger); } From 1e6933f6986c932a14c9909a887ecbc0cc408103 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 28 Jun 2024 11:20:03 +0000 Subject: [PATCH 038/151] Bump ws from 7.5.9 to 7.5.10 Bumps [ws](https://github.com/websockets/ws) from 7.5.9 to 7.5.10. - [Release notes](https://github.com/websockets/ws/releases) - [Commits](https://github.com/websockets/ws/compare/7.5.9...7.5.10) --- updated-dependencies: - dependency-name: ws dependency-type: indirect ... Signed-off-by: dependabot[bot] --- package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 244dc10e8..c652d908a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28090,9 +28090,9 @@ } }, "node_modules/webpack-bundle-analyzer/node_modules/ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "dev": true, "engines": { "node": ">=8.3.0" @@ -28622,9 +28622,9 @@ } }, "node_modules/ws": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", - "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "dev": true, "engines": { "node": ">=10.0.0" From d01ed0e9e16945b879710bbb08b5104e8fe392b8 Mon Sep 17 00:00:00 2001 From: Karel Hala Date: Fri, 28 Jun 2024 13:47:15 +0200 Subject: [PATCH 039/151] Send user to User access overview --- src/components/Header/Tools.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Header/Tools.tsx b/src/components/Header/Tools.tsx index 029f45bc8..01cc090c0 100644 --- a/src/components/Header/Tools.tsx +++ b/src/components/Header/Tools.tsx @@ -116,7 +116,7 @@ const Tools = () => { const intl = useIntl(); const location = useLocation(); const settingsPath = isITLessEnv ? `/settings/my-user-access` : enableIntegrations ? `/settings/integrations` : '/settings/sources'; - const identityAndAccessManagmentPath = '/iam/user-access/users'; + const identityAndAccessManagmentPath = '/iam/user-access/overview'; const betaSwitcherTitle = `${isPreview ? intl.formatMessage(messages.stopUsing) : intl.formatMessage(messages.use)} ${intl.formatMessage( messages.betaRelease )}`; From 8eb267cb1945c7cb84f89c02a300d762ca4be187 Mon Sep 17 00:00:00 2001 From: Martin Marosi Date: Fri, 28 Jun 2024 14:20:58 +0200 Subject: [PATCH 040/151] Use stricter to rule to patch hac link targets. --- .../SharedScope/RouterOverrides.cy.tsx | 62 +++++++++++++++++++ src/chrome/update-shared-scope.ts | 4 +- 2 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 cypress/component/SharedScope/RouterOverrides.cy.tsx diff --git a/cypress/component/SharedScope/RouterOverrides.cy.tsx b/cypress/component/SharedScope/RouterOverrides.cy.tsx new file mode 100644 index 000000000..aa5d776c2 --- /dev/null +++ b/cypress/component/SharedScope/RouterOverrides.cy.tsx @@ -0,0 +1,62 @@ +import { getSharedScope } from '@scalprum/core'; +import ScalprumProvider from '@scalprum/react-core'; +import React, { ComponentType, PropsWithChildren, Suspense, lazy, useEffect, useState } from 'react'; +import updateSharedScope, { hacApps } from '../../../src/chrome/update-shared-scope'; +import { BrowserRouter, Link, Route, Routes } from 'react-router-dom'; + +const PatchedLink = lazy>>(async () => { + const m = await getSharedScope()['react-router-dom']['*'].get(); + return { + default: m().NavLink, + }; +}); + +const ScalprumBase = ({ children }: PropsWithChildren) => { + const [ok, setOk] = useState(false); + useEffect(() => { + updateSharedScope(); + setTimeout(() => { + // delay the render to ensure the shared scope was patched + setOk(true); + }); + }, []); + return {ok && Loading...
}>{children}}; +}; + +describe('RouterOverrides', () => { + const cases = [ + ...hacApps.map((app) => ({ + name: `Should prefix hacApp link pathname with /hac for ${app}`, + pathname: `/foo/hac${app}`, + hasPrefix: true, + })), + { + name: 'Should not prefix hacApp link pathname with "hac" substring somewhere in the pathname', + pathname: '/foo/hac-e2e-user/application/bar', + hasPrefix: false, + }, + ]; + + cases.forEach(({ name, pathname, hasPrefix }) => { + it(name, () => { + cy.mount( + +
+ navigate +
+ + + trigger} /> + + +
+ ); + + cy.contains('navigate').click(); + cy.contains('trigger') + .first() + .invoke('attr', 'href') + .should('eq', `${hasPrefix ? '/hac' : ''}/application-pipeline/workspaces`); + }); + }); +}); diff --git a/src/chrome/update-shared-scope.ts b/src/chrome/update-shared-scope.ts index 3051b2b0e..f14d923af 100644 --- a/src/chrome/update-shared-scope.ts +++ b/src/chrome/update-shared-scope.ts @@ -2,11 +2,11 @@ import { getSharedScope, initSharedScope } from '@scalprum/core'; import { LinkProps, NavLinkProps, NavigateOptions, NavigateProps, Path, To } from 'react-router-dom'; -const hacApps = ['/application-pipeline', '/stonesoup', '/app-studio']; +export const hacApps = ['/application-pipeline', '/stonesoup', '/app-studio']; const updateSharedScope = () => { const calculateTo = (to: To) => { - if (window.location.pathname.includes('/hac')) { + if (window.location.pathname.match(/(\/hac\/|\/hac$)/)) { // FIXME: Create a global dynamic plugin solution to scope plugin nested routes if (typeof to === 'string' && !to.startsWith('/hac') && to.startsWith('/') && hacApps.some((item) => to.startsWith(item))) { return `/hac${to}`; From 39abe8b55d9679c4b327dff8baec01689e16c4b8 Mon Sep 17 00:00:00 2001 From: Martin Marosi Date: Mon, 1 Jul 2024 13:17:49 +0200 Subject: [PATCH 041/151] Evaluate search results visibility. --- src/@types/types.d.ts | 5 +++ src/components/Search/SearchInput.tsx | 5 ++- src/state/atoms/localSearchAtom.ts | 6 +++ src/state/atoms/releaseAtom.ts | 2 + src/utils/isNavItemVisible.ts | 11 ++++- src/utils/localSearch.ts | 60 ++++++++++++++++++++------- 6 files changed, 72 insertions(+), 17 deletions(-) diff --git a/src/@types/types.d.ts b/src/@types/types.d.ts index 74b844551..388931e4f 100644 --- a/src/@types/types.d.ts +++ b/src/@types/types.d.ts @@ -229,3 +229,8 @@ export type BundleNav = { title?: string; links: NavItem[]; }; + +export enum ReleaseEnv { + STABLE = 'STABLE', + PREVIEW = 'PREVIEW', +} diff --git a/src/components/Search/SearchInput.tsx b/src/components/Search/SearchInput.tsx index d76e28b5b..f92c31644 100644 --- a/src/components/Search/SearchInput.tsx +++ b/src/components/Search/SearchInput.tsx @@ -17,6 +17,8 @@ import SearchDescription from './SearchDescription'; import { useAtomValue } from 'jotai'; import { asyncLocalOrama } from '../../state/atoms/localSearchAtom'; import { localQuery } from '../../utils/localSearch'; +import { isPreviewAtom } from '../../state/atoms/releaseAtom'; +import { ReleaseEnv } from '../../@types/types.d'; export type SearchInputprops = { isExpanded?: boolean; @@ -49,6 +51,7 @@ const SearchInput = ({ onStateChange }: SearchInputListener) => { const [searchValue, setSearchValue] = useState(''); const [isFetching, setIsFetching] = useState(false); const [searchItems, setSearchItems] = useState([]); + const isPreview = useAtomValue(isPreviewAtom); const { ready, analytics } = useSegment(); const blockCloseEvent = useRef(false); const asyncLocalOramaData = useAtomValue(asyncLocalOrama); @@ -146,7 +149,7 @@ const SearchInput = ({ onStateChange }: SearchInputListener) => { const handleChange: SearchInputProps['onChange'] = async (_e, value) => { setSearchValue(value); setIsFetching(true); - const results = await localQuery(asyncLocalOramaData, value); + const results = await localQuery(asyncLocalOramaData, value, isPreview ? ReleaseEnv.PREVIEW : ReleaseEnv.STABLE); setSearchItems(results ?? []); isMounted.current && setIsFetching(false); if (ready && analytics) { diff --git a/src/state/atoms/localSearchAtom.ts b/src/state/atoms/localSearchAtom.ts index b00c9cc3b..7e89c5669 100644 --- a/src/state/atoms/localSearchAtom.ts +++ b/src/state/atoms/localSearchAtom.ts @@ -3,6 +3,7 @@ import { Orama, create, insert } from '@orama/orama'; import { getChromeStaticPathname } from '../../utils/common'; import axios from 'axios'; +import { NavItemPermission } from '../../@types/types'; type IndexEntry = { icon?: string; @@ -14,6 +15,7 @@ type IndexEntry = { relative_uri: string; poc_description_t: string; alt_title: string[]; + permissions?: NavItemPermission[]; }; type SearchEntry = { @@ -27,6 +29,9 @@ type SearchEntry = { altTitle?: string[]; }; +export const SearchPermissions = new Map(); +export const SearchPermissionsCache = new Map(); + const asyncSearchIndexAtom = atom(async () => { const staticPath = getChromeStaticPathname('search'); const { data: rawIndex } = await axios.get(`${staticPath}/search-index.json`); @@ -43,6 +48,7 @@ const asyncSearchIndexAtom = atom(async () => { return; } idSet.add(entry.id); + SearchPermissions.set(entry.id, entry.permissions ?? []); searchIndex.push({ title: entry.title[0], uri: entry.uri, diff --git a/src/state/atoms/releaseAtom.ts b/src/state/atoms/releaseAtom.ts index 1b6587a36..b19c87817 100644 --- a/src/state/atoms/releaseAtom.ts +++ b/src/state/atoms/releaseAtom.ts @@ -2,9 +2,11 @@ import axios from 'axios'; import { updateVisibilityFunctionsBeta, visibilityFunctionsExist } from '../../utils/VisibilitySingleton'; import { atomWithToggle } from './utils'; import { getUnleashClient, unleashClientExists } from '../../components/FeatureFlags/unleashClient'; +import { SearchPermissionsCache } from './localSearchAtom'; export const isPreviewAtom = atomWithToggle(undefined, async (isPreview) => { try { + SearchPermissionsCache.clear(); // Required to change the `isBeta` function return value in the visibility functions if (visibilityFunctionsExist()) { updateVisibilityFunctionsBeta(isPreview); diff --git a/src/utils/isNavItemVisible.ts b/src/utils/isNavItemVisible.ts index 44c5f0122..5a861d6e2 100644 --- a/src/utils/isNavItemVisible.ts +++ b/src/utils/isNavItemVisible.ts @@ -11,7 +11,16 @@ const visibilityHandler = async ({ method, args }: NavItemPermission) => { export const isNavItemVisible = (permissions: NavItemPermission | NavItemPermission[]) => Promise.all(flatMap(Array.isArray(permissions) ? permissions : [permissions], visibilityHandler)).then((visibility) => visibility.every(Boolean)); -export const evaluateVisibility = async (navItem: NavItem) => { +export type ItemWithPermissionsConfig = T & { + permissions?: NavItemPermission | NavItemPermission[]; + isHidden?: boolean; + groupId?: string; + navItems?: ItemWithPermissionsConfig[]; + expandable?: boolean; + routes?: ItemWithPermissionsConfig[]; +}; + +export const evaluateVisibility = async (navItem: ItemWithPermissionsConfig) => { /** * Skip evaluation for hidden items */ diff --git a/src/utils/localSearch.ts b/src/utils/localSearch.ts index fb0b5952a..92782a68c 100644 --- a/src/utils/localSearch.ts +++ b/src/utils/localSearch.ts @@ -1,5 +1,8 @@ import { search } from '@orama/orama'; import { fuzzySearch } from './levenshtein-search'; +import { SearchPermissions, SearchPermissionsCache } from '../state/atoms/localSearchAtom'; +import { evaluateVisibility } from './isNavItemVisible'; +import { ReleaseEnv } from '../@types/types.d'; type HighlightCategories = 'title' | 'description'; @@ -17,6 +20,7 @@ type ResultItem = { description: string; bundleTitle: string; pathname: string; + id: string; }; const resultCache: { @@ -67,19 +71,32 @@ function highlightText(term: string, text: string, category: HighlightCategories return internalText; } -export const localQuery = async (db: any, term: string) => { +async function checkResultPermissions(id: string, env: ReleaseEnv = ReleaseEnv.STABLE) { + const cacheKey = `${env}-${id}`; + const cacheHit = SearchPermissionsCache.get(cacheKey); + if (cacheHit) { + return cacheHit; + } + const permissions = SearchPermissions.get(id); + const result = !!(await evaluateVisibility({ id, permissions }))?.isHidden; + SearchPermissionsCache.set(cacheKey, result); + return result; +} + +export const localQuery = async (db: any, term: string, env: ReleaseEnv = ReleaseEnv.STABLE) => { try { - let results: ResultItem[] | undefined = resultCache[term]; + const cacheKey = `${env}-${term}`; + let results: ResultItem[] | undefined = resultCache[cacheKey]; if (results) { return results; } + results = []; const r = await search(db, { term, threshold: 0.5, tolerance: 1.5, properties: ['title', 'description', 'altTitle'], - limit: 10, boost: { title: 10, altTitle: 5, @@ -87,20 +104,33 @@ export const localQuery = async (db: any, term: string) => { }, }); - results = r.hits.map(({ document: { title, description, bundleTitle, pathname } }) => { - let matchedTitle = title; - let matchedDescription = description; - matchedTitle = highlightText(term, matchedTitle, 'title'); - matchedDescription = highlightText(term, matchedDescription, 'description'); - - return { - title: matchedTitle, - description: matchedDescription, + const searches: Promise[] = []; + for (const hit of r.hits) { + if (searches.length === 10) { + break; + } + const { + document: { id }, + } = hit; + const res = await checkResultPermissions(id); + // skip hidden items + if (!res) { + searches.push(hit.document); + } + } + const validResults = await Promise.all(searches); + for (let i = 0; i < Math.min(10, validResults.length); i += 1) { + const { title, description, bundleTitle, pathname, id } = validResults[i]; + results.push({ + title: highlightText(term, title, 'title'), + description: highlightText(term, description, 'description'), bundleTitle, pathname, - }; - }); - resultCache[term] = results; + id, + }); + } + + resultCache[cacheKey] = results; return results; } catch (error) { console.log(error); From c486db88907053455e692d06e191d96b197c9115 Mon Sep 17 00:00:00 2001 From: Bryan Florkiewicz Date: Mon, 1 Jul 2024 18:25:17 -0400 Subject: [PATCH 042/151] Disable VA for itless envs --- src/layouts/DefaultLayout.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/layouts/DefaultLayout.tsx b/src/layouts/DefaultLayout.tsx index 4f3dfce01..8d373031d 100644 --- a/src/layouts/DefaultLayout.tsx +++ b/src/layouts/DefaultLayout.tsx @@ -29,6 +29,7 @@ import { useFlag } from '@unleash/proxy-client-react'; import ChromeAuthContext from '../auth/ChromeAuthContext'; import VirtualAssistant from '../components/Routes/VirtualAssistant'; import { notificationDrawerExpandedAtom } from '../state/atoms/notificationDrawerAtom'; +import { ITLess } from '../utils/common'; type ShieldedRootProps = { hideNav?: boolean; @@ -100,7 +101,7 @@ const DefaultLayout: React.FC = ({ hasBanner, selectedAccoun
)} - + {ITLess() ? null : } {Footer} From 7697151094d0f038c1d316f2aff61f9601851c1f Mon Sep 17 00:00:00 2001 From: Martin Marosi Date: Mon, 1 Jul 2024 09:26:19 +0200 Subject: [PATCH 043/151] Add preview docs. --- docs/preview.md | 127 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 docs/preview.md diff --git a/docs/preview.md b/docs/preview.md new file mode 100644 index 000000000..a5feda794 --- /dev/null +++ b/docs/preview.md @@ -0,0 +1,127 @@ +# UI Preview environment + +Preview environment exists to allow the users to opt-in into experimental version of the HCC UI. This environment can contain future features. The environment is not considered stable! + +## Legacy preview environment + +> This environment is being decommissioned on **August 1, 2024**. + +This environment can be access on any valid HCC URL simply by adding `/preview` to the URL host. This environment uses standalone deployments of the frontend modules (deployments can share builds). + +## Upcoming preview environment + +As of June 2024, the new preview environment is available. The HCC UI will be switched to this new preview environment on **August 1, 2024**. + +### Accessing new preview + +To test the preview functionality before the switch a localStorage flag needs to ne enabled. In the browser console use this command: + +```js +window.insights.chrome.enable.forceLocalPreview() +``` + +After this, reload the browser window. + +### Toggling preview + +To toggle between stable/preview environments, use the preview toggle. Changing the URL will no longer have any effect. + +### Distinguishing between stable/preview environments + +## Using preview + +To enable/disable preview features use one of the following: + +> Using feature flags is recommended + +### Feature flags + +Feature flags can be used to enable/disable preview features. Chrome UI is adding a `platform.chrome.ui.preview` context variable. Use this context field in feature flags to enable/disable preview features. + +#### React components + +Follow the official docs: https://docs.getunleash.io/reference/sdks/react#check-feature-toggle-status + +```jsx +import { useFlag } from '@unleash/proxy-client-react'; + +const TestComponent = () => { + const enabled = useFlag('foo.bar'); + + if (enabled) { + return ; + } + return ; +}; + +export default TestComponent; +``` + +#### Navigation + +To conditionally display left navigation items based on feature flag values, use the `featureFlag` visibility function in the nav item definition: + +```JSON +{ + "id": "foo", + "appId": "foo", + "title": "Foo", + "href": "/foo/bar", + "permissions": [ + { + "method": "featureFlag", + "args": ["foo.bar", true] + } + ] +} +``` + +#### Search index + +To conditionally display items based on feature flags, use the `featureFlag` visibility functions in the associated link or static entry. + +```JSON +{ + "id": "foo", + "appId": "foo", + "title": "Foo", + "href": "/foo/bar", + "permissions": [ + { + "method": "featureFlag", + "args": ["foo.bar", true] + } + ] +} +``` + +### `isBeta` Chrome API + +> This API is deprecated. Please use the feature flags. + +#### React components + +Use the `useChrome` hook to access the `isBeta` method: + +```jsx +import useChrome from '@redhat-cloud-services/frontend-components/useChrome'; + +const Component = () => { + const { isBeta } = useChrome() + + const isPreview = isBeta() + + return ( + ... + ) + +} +``` + +#### Navigation + +Not supported via chrome API + +#### Search index + +Not supported via chrome API From b0f8908c7e010575ef087a06793f1004b14b5b43 Mon Sep 17 00:00:00 2001 From: ewinchel Date: Tue, 2 Jul 2024 10:20:24 -0400 Subject: [PATCH 044/151] Update empty state styling --- .../FavoriteServices/EmptyState.scss | 6 +-- .../FavoriteServices/EmptyState.tsx | 43 +++++++++++-------- 2 files changed, 26 insertions(+), 23 deletions(-) diff --git a/src/components/FavoriteServices/EmptyState.scss b/src/components/FavoriteServices/EmptyState.scss index 246257b6c..487f5b28e 100644 --- a/src/components/FavoriteServices/EmptyState.scss +++ b/src/components/FavoriteServices/EmptyState.scss @@ -1,8 +1,6 @@ @import "~@redhat-cloud-services/frontend-components-utilities/styles/_all"; @import '~@patternfly/patternfly/patternfly-addons.scss'; -.chr-l-stack__item-centered, .chr-c-card-centered { - display: flex; - justify-content: center; - text-align: center; +.pf-v5-l-flex { + height: 100%; } diff --git a/src/components/FavoriteServices/EmptyState.tsx b/src/components/FavoriteServices/EmptyState.tsx index 0b86565ff..f535698fb 100644 --- a/src/components/FavoriteServices/EmptyState.tsx +++ b/src/components/FavoriteServices/EmptyState.tsx @@ -1,5 +1,6 @@ import { Button } from '@patternfly/react-core/dist/dynamic/components/Button'; -import { StackItem } from '@patternfly/react-core/dist/dynamic/layouts/Stack'; +import { Flex } from '@patternfly/react-core/dist/dynamic/layouts/Flex'; +import { Stack, StackItem } from '@patternfly/react-core/dist/dynamic/layouts/Stack'; import { Text, TextContent } from '@patternfly/react-core/dist/dynamic/components/Text'; import React from 'react'; @@ -9,24 +10,28 @@ import './EmptyState.scss'; const EmptyState = () => ( <> - - favoriting image - - - - - No favorited services - - - Add a service to your favorites to get started here. - - - - - - + + + + favoriting image + + + + + No favorited services + + + Add a service to your favorites to get started here. + + + + + + + + ); From 1bf0c0fc8ba079d1fd8baead57ef3b525e160f16 Mon Sep 17 00:00:00 2001 From: Adam Jetmar Date: Tue, 2 Jul 2024 14:11:24 +0200 Subject: [PATCH 045/151] Set up global learning resources page --- locales/translation-template.json | 4 ++++ src/components/Header/Tools.tsx | 10 +++++++++- src/locales/Messages.ts | 5 +++++ src/locales/data.json | 1 + 4 files changed, 19 insertions(+), 1 deletion(-) diff --git a/locales/translation-template.json b/locales/translation-template.json index 03ef10fc6..aa397c17e 100644 --- a/locales/translation-template.json +++ b/locales/translation-template.json @@ -31,6 +31,10 @@ "defaultMessage": "API documentation", "description": "API documentation" }, + "globalLearningResourcesPage": { + "defaultMessage": "All learning resources", + "description": "All learning resources" + }, "authFailure": { "defaultMessage": "Authorization failure", "description": "Authorization failure" diff --git a/src/components/Header/Tools.tsx b/src/components/Header/Tools.tsx index 029f45bc8..d600078ac 100644 --- a/src/components/Header/Tools.tsx +++ b/src/components/Header/Tools.tsx @@ -109,6 +109,7 @@ const Tools = () => { }); const [isPreview, setIsPreview] = useAtom(isPreviewAtom); const enableIntegrations = useFlag('platform.sources.integrations'); + const enableGlobalLearningResourcesPage = useFlag('platform.learning-resources.global-learning-resources'); const { xs } = useWindowWidth(); const { user, token } = useContext(ChromeAuthContext); const unreadNotifications = useAtomValue(unreadNotificationsAtom); @@ -233,12 +234,19 @@ const Tools = () => { onClick: () => window.open('https://access.redhat.com/documentation/en-us/red_hat_insights', '_blank'), isHidden: getSection() !== 'insights' || isITLessEnv, }, - { title: intl.formatMessage(messages.demoMode), onClick: () => cookie.set('cs_demo', 'true') && window.location.reload(), isHidden: !isDemoAcc, }, + ...(enableGlobalLearningResourcesPage + ? [ + { + title: intl.formatMessage(messages.globalLearningResourcesPage), + onClick: () => window.open('/staging/global-learning-resources-page', '_blank'), + }, + ] + : []), ]; /* Combine aboutMenuItems with a settings link on mobile */ diff --git a/src/locales/Messages.ts b/src/locales/Messages.ts index b8f264e16..e9871814b 100644 --- a/src/locales/Messages.ts +++ b/src/locales/Messages.ts @@ -327,6 +327,11 @@ export default defineMessages({ description: 'Demo mode', defaultMessage: 'Demo mode', }, + globalLearningResourcesPage: { + id: 'globalLearningResourcesPage', + description: 'All learning resources', + defaultMessage: 'Global learning resources page', + }, loggedOut: { id: 'loggedOut', description: 'You have successfully logged out.', diff --git a/src/locales/data.json b/src/locales/data.json index bdf4e6c00..1b5e11540 100644 --- a/src/locales/data.json +++ b/src/locales/data.json @@ -34,6 +34,7 @@ "getSupport": "Get help from Red Hat support.", "globalFilterNotApplicable": "Global filter is not applicable for this page", "globalRuntimeErrorId": "Sentry error ID: {errorId}", + "globalLearningResourcesPage": "Global learning resources page", "helpUsImproveHCC": "Help us improve the Red Hat Hybrid Cloud Console.", "home": "Home", "howIsConsoleExperience": "What has your console experience been like so far?", From 2aa8665f65df251344d314c10032553a4ba32daa Mon Sep 17 00:00:00 2001 From: ewinchel Date: Tue, 2 Jul 2024 11:35:19 -0400 Subject: [PATCH 046/151] lint fix --- .../FavoriteServices/EmptyState.tsx | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/components/FavoriteServices/EmptyState.tsx b/src/components/FavoriteServices/EmptyState.tsx index f535698fb..0b3f808aa 100644 --- a/src/components/FavoriteServices/EmptyState.tsx +++ b/src/components/FavoriteServices/EmptyState.tsx @@ -10,28 +10,28 @@ import './EmptyState.scss'; const EmptyState = () => ( <> - - - - favoriting image - - - - - No favorited services - - - Add a service to your favorites to get started here. - - - - - - - - + + + + favoriting image + + + + + No favorited services + + + Add a service to your favorites to get started here. + + + + + + + + ); From aec3115470c86ddad6b2f8a1e7fd48779ffc74f4 Mon Sep 17 00:00:00 2001 From: InsaneZein Date: Wed, 3 Jul 2024 03:11:17 -0500 Subject: [PATCH 047/151] new empty state; change notification limit to 50 (#2884) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * new empty state; change notification limit to 50 * remove log; fix Stack import; move sevenDaysAgo function to utils * switch to using axios params attribute --------- Co-authored-by: Martin Maroši --- .../DrawerPanelContent.tsx | 52 +++++++++++++------ src/components/RootApp/ScalprumRoot.tsx | 10 +++- src/utils/common.ts | 6 +++ 3 files changed, 51 insertions(+), 17 deletions(-) diff --git a/src/components/NotificationsDrawer/DrawerPanelContent.tsx b/src/components/NotificationsDrawer/DrawerPanelContent.tsx index 28412f649..bc4d37e54 100644 --- a/src/components/NotificationsDrawer/DrawerPanelContent.tsx +++ b/src/components/NotificationsDrawer/DrawerPanelContent.tsx @@ -36,6 +36,7 @@ import { } from '../../state/atoms/notificationDrawerAtom'; import BulkSelect from '@redhat-cloud-services/frontend-components/BulkSelect'; import axios from 'axios'; +import { Stack, StackItem } from '@patternfly/react-core/dist/dynamic/layouts/Stack'; export type DrawerPanelProps = { innerRef: React.Ref; @@ -49,23 +50,44 @@ const EmptyNotifications = ({ isOrgAdmin, onLinkClick }: { onLinkClick: () => vo {isOrgAdmin ? ( - - Try  - - checking your notification preferences - -  and managing the  - - notification configuration - -  for your organization. - + + + There are currently no notifications for you. + + + + Try  + + checking your notification preferences + +  and managing the  + + notification configuration + +  for your organization. + + + ) : ( <> - - Configure notification settings - - .Contact your organization administrator. + + + There are currently no notifications for you. + + + + Check your Notification Preferences + + + + + View the Event log to see all fired events + + + + Contact your organization administrator + + )} diff --git a/src/components/RootApp/ScalprumRoot.tsx b/src/components/RootApp/ScalprumRoot.tsx index e1947b9e6..74a8a4001 100644 --- a/src/components/RootApp/ScalprumRoot.tsx +++ b/src/components/RootApp/ScalprumRoot.tsx @@ -27,7 +27,7 @@ import Footer, { FooterProps } from '../Footer/Footer'; import updateSharedScope from '../../chrome/update-shared-scope'; import useBundleVisitDetection from '../../hooks/useBundleVisitDetection'; import chromeApiWrapper from './chromeApiWrapper'; -import { ITLess } from '../../utils/common'; +import { ITLess, getSevenDaysAgo } from '../../utils/common'; import InternalChromeContext from '../../utils/internalChromeContext'; import useChromeServiceEvents from '../../hooks/useChromeServiceEvents'; import useTrackPendoUsage from '../../hooks/useTrackPendoUsage'; @@ -75,7 +75,13 @@ const ScalprumRoot = memo( async function getNotifications() { try { - const { data } = await axios.get<{ data: NotificationData[] }>('/api/notifications/v1/notifications/drawer'); + const { data } = await axios.get<{ data: NotificationData[] }>(`/api/notifications/v1/notifications/drawer`, { + params: { + limit: 50, + sort_by: 'read:asc', + startDate: getSevenDaysAgo(), + }, + }); populateNotifications(data?.data || []); } catch (error) { console.error('Unable to get Notifications ', error); diff --git a/src/utils/common.ts b/src/utils/common.ts index 8535c037e..015053d90 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -472,3 +472,9 @@ export function findNavLeafPath( // converts text to an identifier in title case export const titleToId = (title: string) => title?.replace(/(?:^\w|[A-Z]|\b\w)/g, (word) => word.toUpperCase()).replace(/\s+/g, ''); + +export function getSevenDaysAgo(): string { + const today = new Date(); + const sevenDaysAgo = new Date(today.setDate(today.getDate() - 7)); + return sevenDaysAgo.toISOString().split('.')[0]; +} From 8320c8a22cba6331b346fa1144d74ef9ce91e75a Mon Sep 17 00:00:00 2001 From: Martin Marosi Date: Wed, 3 Jul 2024 14:05:23 +0200 Subject: [PATCH 048/151] Update wait on host value. --- scripts/e2e.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/e2e.js b/scripts/e2e.js index 789dc71fb..310e62ddd 100644 --- a/scripts/e2e.js +++ b/scripts/e2e.js @@ -4,7 +4,7 @@ const { spawn, execSync } = require('child_process'); const waitOn = require('wait-on'); const options = { - resources: ['https://localhost:1337/webpack-dev-server'], + resources: ['https://127.0.0.1:1337/webpack-dev-server'], delay: 6000, interval: 3000, // wait for 3 sec validateStatus: function (status) { From e3445429a021d47333dc76ae4ec64228b50b3036 Mon Sep 17 00:00:00 2001 From: "Victor M." Date: Wed, 3 Jul 2024 18:19:55 +0200 Subject: [PATCH 049/151] fix: set rhel8 label --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index e048ccd93..18c530f1f 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -14,7 +14,7 @@ def secrets = [ ] pipeline { - agent { label 'insights' } + agent { label 'rhel8' } options { timestamps() } From 8f4c9f568dbfc2d066e3a06c49415ed009da6ea7 Mon Sep 17 00:00:00 2001 From: Cody Mitchell Date: Thu, 4 Jul 2024 03:38:39 -0400 Subject: [PATCH 050/151] Filter in notifications drawer (#2875) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix filter to check bundle instead of source * fetch filter configuration from API * fix linting issue * sort by bundle instead of source in tests * handle call to get filter bundles in drawer test * remove null from bundle children type --------- Co-authored-by: Martin Maroši --- .../NotificationDrawer.cy.tsx | 25 +++++++++++---- .../DrawerPanelContent.tsx | 32 +++++++++++++++++-- .../notificationDrawerUtils.ts | 7 ---- src/state/atoms/notificationDrawerAtom.ts | 1 + 4 files changed, 50 insertions(+), 15 deletions(-) diff --git a/cypress/component/NotificationDrawer/NotificationDrawer.cy.tsx b/cypress/component/NotificationDrawer/NotificationDrawer.cy.tsx index 46278c4ad..ffb299d49 100644 --- a/cypress/component/NotificationDrawer/NotificationDrawer.cy.tsx +++ b/cypress/component/NotificationDrawer/NotificationDrawer.cy.tsx @@ -12,8 +12,8 @@ const notificationDrawerData: NotificationData[] = [ read: false, created: new Date().toString(), description: 'This is a test notification', - source: 'openshift', - bundle: 'rhel', + source: 'rhel', + bundle: 'openshift', }, { id: '2', @@ -21,8 +21,8 @@ const notificationDrawerData: NotificationData[] = [ read: false, created: new Date().toString(), description: 'This is a test notification', - source: 'console', - bundle: 'rhel', + source: 'rhel', + bundle: 'console', }, { id: '3', @@ -30,8 +30,8 @@ const notificationDrawerData: NotificationData[] = [ read: false, created: new Date().toString(), description: 'This is a test notification', - source: 'console', - bundle: 'rhel', + source: 'rhel', + bundle: 'console', }, ]; @@ -138,6 +138,19 @@ describe('Notification Drawer', () => { }); it('should select console filter', () => { + cy.intercept('GET', 'http://localhost:8080/api/notifications/v1/notifications/facets/bundles', { + statusCode: 200, + body: [ + { + name: 'console', + displayName: 'Console', + }, + { + name: 'openshift', + displayName: 'OpenShift', + }, + ], + }); cy.mount(); cy.get('#populate-notifications').click(); cy.get('#drawer-toggle').click(); diff --git a/src/components/NotificationsDrawer/DrawerPanelContent.tsx b/src/components/NotificationsDrawer/DrawerPanelContent.tsx index bc4d37e54..cbdb88471 100644 --- a/src/components/NotificationsDrawer/DrawerPanelContent.tsx +++ b/src/components/NotificationsDrawer/DrawerPanelContent.tsx @@ -21,7 +21,6 @@ import EllipsisVIcon from '@patternfly/react-icons/dist/dynamic/icons/ellipsis-v import orderBy from 'lodash/orderBy'; import { Link, useNavigate } from 'react-router-dom'; import NotificationItem from './NotificationItem'; -import { filterConfig } from './notificationDrawerUtils'; import ChromeAuthContext from '../../auth/ChromeAuthContext'; import InternalChromeContext from '../../utils/internalChromeContext'; import { @@ -38,6 +37,18 @@ import BulkSelect from '@redhat-cloud-services/frontend-components/BulkSelect'; import axios from 'axios'; import { Stack, StackItem } from '@patternfly/react-core/dist/dynamic/layouts/Stack'; +interface Bundle { + id: string; + name: string; + displayName: string; + children: Bundle[]; +} + +interface FilterConfigItem { + title: string; + value: string; +} + export type DrawerPanelProps = { innerRef: React.Ref; }; @@ -109,6 +120,7 @@ const DrawerPanelBase = ({ innerRef }: DrawerPanelProps) => { const [hasNotificationsPermissions, setHasNotificationsPermissions] = useState(false); const updateNotificationRead = useSetAtom(updateNotificationReadAtom); const updateAllNotificationsSelected = useSetAtom(updateNotificationsSelectedAtom); + const [filterConfig, setFilterConfig] = useState([]); useEffect(() => { let mounted = true; @@ -124,7 +136,23 @@ const DrawerPanelBase = ({ innerRef }: DrawerPanelProps) => { ); } }; + const fetchFilterConfig = async () => { + try { + const response = await axios.get('/api/notifications/v1/notifications/facets/bundles'); + if (mounted) { + setFilterConfig( + response.data.map((bundle: Bundle) => ({ + title: bundle.displayName, + value: bundle.name, + })) + ); + } + } catch (error) { + console.error('Failed to fetch filter configuration:', error); + } + }; fetchPermissions(); + fetchFilterConfig(); return () => { mounted = false; }; @@ -133,7 +161,7 @@ const DrawerPanelBase = ({ innerRef }: DrawerPanelProps) => { const filteredNotifications = useMemo( () => (activeFilters || []).reduce( - (acc: NotificationData[], chosenFilter: string) => [...acc, ...notifications.filter(({ source }) => source === chosenFilter)], + (acc: NotificationData[], chosenFilter: string) => [...acc, ...notifications.filter(({ bundle }) => bundle === chosenFilter)], [] ), [activeFilters] diff --git a/src/components/NotificationsDrawer/notificationDrawerUtils.ts b/src/components/NotificationsDrawer/notificationDrawerUtils.ts index 9f0fc96e8..5aa3a3974 100644 --- a/src/components/NotificationsDrawer/notificationDrawerUtils.ts +++ b/src/components/NotificationsDrawer/notificationDrawerUtils.ts @@ -1,10 +1,3 @@ -export const filterConfig = [ - { title: 'Console', value: 'console' }, - { title: 'OpenShift', value: 'openshift' }, - { title: 'Red Hat Enterprise Linux', value: 'rhel' }, - { title: 'Ansible Automation Platform', value: 'ansible' }, -]; - export const testData = [ { id: '1', diff --git a/src/state/atoms/notificationDrawerAtom.ts b/src/state/atoms/notificationDrawerAtom.ts index 3f184ecee..02ff86c32 100644 --- a/src/state/atoms/notificationDrawerAtom.ts +++ b/src/state/atoms/notificationDrawerAtom.ts @@ -7,6 +7,7 @@ export type NotificationData = { read: boolean; selected?: boolean; source: string; + bundle: string; created: string; }; From 73d3bf0a6fde083f80276d6ec8e4c9ebd49dae11 Mon Sep 17 00:00:00 2001 From: Radek Kaluzik Date: Wed, 3 Jul 2024 14:44:31 +0200 Subject: [PATCH 051/151] Add mark-down to notifications drawer --- jest.config.js | 2 + package-lock.json | 1391 ++++++++++++++++- package.json | 2 + src/__mocks__/empty-mock.js | 3 + .../NotificationsDrawer/NotificationItem.tsx | 6 +- 5 files changed, 1392 insertions(+), 12 deletions(-) create mode 100644 src/__mocks__/empty-mock.js diff --git a/jest.config.js b/jest.config.js index 36338691d..77bd8cb5e 100644 --- a/jest.config.js +++ b/jest.config.js @@ -40,5 +40,7 @@ module.exports = { moduleNameMapper: { '\\.(css|scss)$': 'identity-obj-proxy', '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '/src/__mocks__/fileMock.js', + 'react-markdown': '/src/__mocks__/empty-mock.js', + 'remark-gfm': '/src/__mocks__/empty-mock.js', }, }; diff --git a/package-lock.json b/package-lock.json index c652d908a..c1f8e9516 100644 --- a/package-lock.json +++ b/package-lock.json @@ -55,11 +55,13 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "react-intl": "^6.6.8", + "react-markdown": "^9.0.1", "react-oidc-context": "^2.3.1", "react-redux": "^8.1.3", "react-router-dom": "^6.24.0", "redux": "^4.2.1", "redux-promise-middleware": "^6.2.0", + "remark-gfm": "^4.0.0", "sanitize-html": "^2.13.0", "title-case": "^3.0.3", "urijs": "^1.19.11" @@ -8868,6 +8870,14 @@ "resolved": "https://registry.npmjs.org/@types/debounce-promise/-/debounce-promise-3.1.9.tgz", "integrity": "sha512-awNxydYSU+E2vL7EiOAMtiSOfL5gUM5X4YSE2A92qpxDJQ/rXz6oMPYBFDcDywlUmvIDI6zsqgq17cGm5CITQw==" }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "dependencies": { + "@types/ms": "*" + } + }, "node_modules/@types/eslint": { "version": "8.56.0", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.0.tgz", @@ -8893,6 +8903,14 @@ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.50.tgz", "integrity": "sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw==" }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "dependencies": { + "@types/estree": "*" + } + }, "node_modules/@types/express": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", @@ -8944,6 +8962,14 @@ "@types/node": "*" } }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/hoist-non-react-statics": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz", @@ -9095,6 +9121,14 @@ "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.6.tgz", "integrity": "sha512-OpXEVoCKSS3lQqjx9GGGOapBeuW5eUboYHRlHP9urXPX25IKZ6AnP5ZRxtVf63iieUbsHxLn8NQ5Nlftc6yzAA==" }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", @@ -9107,6 +9141,11 @@ "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", "dev": true }, + "node_modules/@types/ms": { + "version": "0.7.34", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz", + "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==" + }, "node_modules/@types/node": { "version": "14.18.63", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz", @@ -9282,6 +9321,11 @@ "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", "dev": true }, + "node_modules/@types/unist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", + "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==" + }, "node_modules/@types/urijs": { "version": "1.19.25", "resolved": "https://registry.npmjs.org/@types/urijs/-/urijs-1.19.25.tgz", @@ -10004,8 +10048,7 @@ "node_modules/@ungap/structured-clone": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", - "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", - "dev": true + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==" }, "node_modules/@unleash/proxy-client-react": { "version": "3.6.0", @@ -11333,6 +11376,15 @@ "@babel/core": "^7.0.0" } }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -11866,6 +11918,15 @@ "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", "dev": true }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -11954,6 +12015,42 @@ "node": ">=10" } }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/check-more-types": { "version": "2.24.0", "resolved": "https://registry.npmjs.org/check-more-types/-/check-more-types-2.24.0.tgz", @@ -12241,6 +12338,15 @@ "node": ">= 0.8" } }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/commander": { "version": "10.0.1", "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", @@ -13288,7 +13394,6 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, "dependencies": { "ms": "2.1.2" }, @@ -13316,6 +13421,18 @@ "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", "dev": true }, + "node_modules/decode-named-character-reference": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz", + "integrity": "sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/dedent": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz", @@ -13707,6 +13824,14 @@ "node": ">=4.2.0" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "engines": { + "node": ">=6" + } + }, "node_modules/destroy": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", @@ -13887,6 +14012,18 @@ "node": ">=4.2.0" } }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/dfa": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/dfa/-/dfa-1.2.0.tgz", @@ -14812,6 +14949,15 @@ "node": ">=4.0" } }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/estree-walker": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", @@ -15017,8 +15163,7 @@ "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" }, "node_modules/extract-zip": { "version": "2.0.1", @@ -16157,6 +16302,49 @@ "node": ">= 0.4" } }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.0.tgz", + "integrity": "sha512-H/y0+IWPdsLLS738P8tDnrQ8Z+dj12zQQ6WC11TIM21C8WFVoIxcqWXf2H3hiTVZjF1AWqoimGwrTWecWrnmRQ==", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-object": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-jsx-runtime/node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==" + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -16322,6 +16510,15 @@ "node": ">= 12" } }, + "node_modules/html-url-attributes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.0.tgz", + "integrity": "sha512-/sXbVCWayk6GDVg3ctOX6nxaVj7So40FcFAnWlWGNAB1LpYKcV5Cd10APjPjW80O7zYW2MsjBV4zZ7IZO5fVow==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/html-webpack-plugin": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.0.tgz", @@ -16720,6 +16917,11 @@ "node": ">=10" } }, + "node_modules/inline-style-parser": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.3.tgz", + "integrity": "sha512-qlD8YNDqyTKTyuITrDOffsl6Tdhv+UC4hcdAVuQsK4IMQ99nSgd1MIA/Q+jQYoh9r3hVUXhYh7urSRmXPkW04g==" + }, "node_modules/internal-slot": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.6.tgz", @@ -16807,6 +17009,28 @@ "node": ">= 10" } }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-arguments": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", @@ -16968,6 +17192,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-docker": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", @@ -17047,6 +17280,15 @@ "node": ">=0.10.0" } }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-in-browser": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz", @@ -20666,6 +20908,15 @@ "node": ">=8" } }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -20916,6 +21167,281 @@ "tmpl": "1.0.5" } }, + "node_modules/markdown-table": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.3.tgz", + "integrity": "sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/mdast-util-find-and-replace": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.1.tgz", + "integrity": "sha512-SG21kZHGC3XRTSUhtofZkBzZTJNM5ecCi0SK2IMKmSXR8vO3peL+kb1O0z7Zl83jKtutG4k5Wv/W7V3/YHvzPA==", + "dependencies": { + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.1.tgz", + "integrity": "sha512-aJEUyzZ6TzlsX2s5B4Of7lN7EQtAxvtradMMglCQDyaTFgse6CmtmdJ15ElnVRlCg1vpNyVtbem0PWzlNieZsA==", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.0.0.tgz", + "integrity": "sha512-dgQEX5Amaq+DuUqf26jJqSK9qgixgd6rYDHAv4aTBuA92cTknZlKpPfa86Z/s8Dj8xsAQpFfBmPUHWJBWqS4Bw==", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.0", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.0.tgz", + "integrity": "sha512-FyzMsduZZHSc3i0Px3PQcBT4WJY/X/RCtEJKuybiC6sjPqLv7h1yqAkmILZtuxMSsUyaLUWNp71+vQH2zqp5cg==", + "dependencies": { + "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.0.0.tgz", + "integrity": "sha512-5jOT2boTSVkMnQ7LTrd6n/18kqwjmuYqo7JUPe+tRCY6O7dAuTFMtTPauYYrMPpox9hlN0uOx/FL8XvEfG9/mQ==", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.0.tgz", + "integrity": "sha512-fGCu8eWdKUKNu5mohVGkhBXCXGnOTLuFqOvGMvdikr+J1w7lDJgxThOKpwRWzzbyXAU2hhSwsmssOY4yTokluw==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.1.2.tgz", + "integrity": "sha512-eKMQDeywY2wlHc97k5eD8VC+9ASMjN8ItEZQNGwJ6E0XWKiW/Z0V5/H8pvoXUf+y+Mj0VIgeRRbujBmFn4FTyA==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-remove-position": "^5.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", + "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.0.tgz", + "integrity": "sha512-SR2VnIEdVNCJbP6y7kVTJgPLifdr8WEU440fQec7qHoHOUz/oJ2jmNRqdDQ3rbiStOXb2mCDGTuwsK5OPUgYlQ==", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/media-engine": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/media-engine/-/media-engine-1.0.3.tgz", @@ -20980,10 +21506,545 @@ "node": ">= 0.6" } }, - "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "node_modules/micromark": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.0.tgz", + "integrity": "sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.1.tgz", + "integrity": "sha512-CUQyKr1e///ZODyD1U3xit6zXwy1a8q2a1S1HKtIlmgvurrEpaw/Y9y6KSIbF8P59cn/NjzHyO+Q2fAyYLQrAA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.0.0.tgz", + "integrity": "sha512-rTHfnpt/Q7dEAK1Y5ii0W8bhfJlVJFnJMHIPisfPK3gpVNuOP0VnRl96+YJ3RYWV/P4gFeQoGKNlT3RhuvpqAg==", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.0.0.tgz", + "integrity": "sha512-6Rzu0CYRKDv3BfLAUnZsSlzx3ak6HAoI85KTiijuKIz5UxZxbUI+pD6oHgw+6UtQuiRwnGRhzMmPRv4smcz0fg==", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-c3BR1ClMp5fxxmwP6AoOY2fXO9U8uFMKs4ADD66ahLTNcwzSCyRVU4k7LPV5Nxo/VJiR4TdzxRQY2v3qIUceCw==", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.0.0.tgz", + "integrity": "sha512-PoHlhypg1ItIucOaHmKE8fbin3vTLpDOUg8KAr8gRCF1MOZI9Nquq2i/44wFvviM4WuxJzc3demT8Y3dkfvYrw==", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.0.1.tgz", + "integrity": "sha512-cY5PzGcnULaN5O7T+cOzfMoHjBW7j+T9D2sucA5d/KbsBTPcYdebm9zUd9zzdgJGCwahV+/W78Z3nbulBYVbTw==", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.0.tgz", + "integrity": "sha512-j9DGrQLm/Uhl2tCzcbLhy5kXsgkHUrjJHg4fFAeoMRwJmJerT9aw4FEhIbZStWN8A3qMwOp1uzHr4UL8AInxtA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.0.tgz", + "integrity": "sha512-RR3i96ohZGde//4WSe/dJsxOX6vxIg9TimLAS3i4EhBAFx8Sm5SmqVfR8E87DPSR31nEAjZfbt91OMZWcNgdZw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz", + "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.0.tgz", + "integrity": "sha512-jY8CSxmpWLOxS+t8W+FG3Xigc0RDQA9bKMY/EwILvsesiRniiVMejYTE4wumNc2f4UbAa4WsHqe3J1QS1sli+A==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.0.tgz", + "integrity": "sha512-28kbwaBjc5yAI1XadbdPYHX/eDnqaUFVikLwrO7FDnKG7lpgxnvk/XGRhX/PN0mOZ+dBSZ+LgunHS+6tYQAzhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", + "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.0.tgz", + "integrity": "sha512-anK8SWmNphkXdaKgz5hJvGa7l00qmcaUQoMYsBwDlSKFKjc6gjGXPDw3FNL3Nbwq5L8gE+RCbGqTw49FK5Qyvg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.0.tgz", + "integrity": "sha512-S0ze2R9GH+fu41FA7pbSqNWObo/kzwf8rN/+IGlW/4tC6oACOs8B++bh+i9bVyNnwCcuksbFwsBme5OCKXCwIw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.0.tgz", + "integrity": "sha512-vZZio48k7ON0fVS3CUgFatWHoKbbLTK/rT7pzpJ4Bjp5JjkZeasRfrS9wsBdDJK2cJLHMckXZdzPSSr1B8a4oQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.1.tgz", + "integrity": "sha512-bmkNc7z8Wn6kgjZmVHOX3SowGmVdhYS7yBpMnuMnPzDq/6xwVA604DuOXMZTO1lvq01g+Adfa0pE2UKGlxL1XQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.0.tgz", + "integrity": "sha512-r4Sc6leeUTn3P6gk20aFMj2ntPwn6qpDZqWvYmAG6NgvFTIlj4WtrAudLi65qYoaGdXYViXYw2pkmn7QnIFasA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.0.tgz", + "integrity": "sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.0.tgz", + "integrity": "sha512-xNn4Pqkj2puRhKdKTm8t1YHC/BAjx6CEwRFXntTaRf/x16aqka6ouVoutm+QdkISTlT7e2zU7U4ZdlDLJd2Mcw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.0.tgz", + "integrity": "sha512-2xhYT0sfo85FMrUPtHcPo2rrp1lwbDEEzpx7jiH2xXJLqBuy4H0GgXk5ToU8IEwoROtXuL8ND0ttVa4rNqYK3w==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.0.tgz", + "integrity": "sha512-6KU6qO7DZ7GJkaCgwBNtplXCvGkJToU86ybBAUdavvgsCiG8lSSvYxr9MhwmQ+udpzywHsl4RpGJsYWG1pDOcA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.0.tgz", + "integrity": "sha512-WhYv5UEcZrbAtlsnPuChHUAsu/iBPOVaEVsntLBIdpibO0ddy8OzavZz3iL2xVvBZOpolujSliP65Kq0/7KIYw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.0.1.tgz", + "integrity": "sha512-jZNtiFl/1aY73yS3UGQkutD0UbhTt68qnRpw2Pifmz5wV9h8gOVsN70v+Lq/f1rKaU/W8pxRe8y8Q9FX1AOe1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", + "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.0.tgz", + "integrity": "sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", "dependencies": { "braces": "^3.0.2", "picomatch": "^2.3.1" @@ -21242,8 +22303,7 @@ "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/multicast-dns": { "version": "7.2.5", @@ -22438,6 +23498,30 @@ "node": ">=6" } }, + "node_modules/parse-entities": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.1.tgz", + "integrity": "sha512-SWzvYcSJh4d/SGLIOQfZ/CoNv6BTlI6YEQ7Nj82oDVnRpwe/Z/F1EMx42x3JAOwGBlCjeCH0BRJQbQ/opHL17w==", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" + }, "node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -23457,6 +24541,15 @@ "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz", "integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==" }, + "node_modules/property-information": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz", + "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -23839,6 +24932,31 @@ "react": ">=16.8.6" } }, + "node_modules/react-markdown": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-9.0.1.tgz", + "integrity": "sha512-186Gw/vF1uRkydbsOIkcGXw7aHq0sZOCRFFjGrr7b9+nVZg4UfA4enXCaxm4fUzecU38sWfrNDitGhshuU7rdg==", + "dependencies": { + "@types/hast": "^3.0.0", + "devlop": "^1.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "html-url-attributes": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=18", + "react": ">=18" + } + }, "node_modules/react-oidc-context": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/react-oidc-context/-/react-oidc-context-2.3.1.tgz", @@ -24192,6 +25310,68 @@ "node": ">=4" } }, + "node_modules/remark-gfm": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.0.tgz", + "integrity": "sha512-U92vJgBPkbw4Zfu/IiW2oTZLSL3Zpv+uI7My2eq8JxKgqraFdU8YUGicEJCEgSbeaG+QDFqIcwwfMTOEelPxuA==", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-gfm": "^3.0.0", + "micromark-extension-gfm": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.0.tgz", + "integrity": "sha512-z3tJrAs2kIs1AqIIy6pzHmAHlF1hWQ+OdY4/hv+Wxe35EhyLKcajL33iUEn3ScxtFox9nUvRufR/Zre8Q08H/g==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/renderkid": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", @@ -25265,6 +26445,15 @@ "node": ">=0.10.0" } }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/spawn-wrap": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", @@ -25596,6 +26785,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/stringify-object": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", @@ -25680,6 +26882,14 @@ "webpack": "^5.0.0" } }, + "node_modules/style-to-object": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.6.tgz", + "integrity": "sha512-khxq+Qm3xEyZfKd/y9L3oIWQimxuc4STrQKtQn8aSDRHb8mFgpukgX1hdzfrMEW6JCjyJ8p89x+IUMVnCBI1PA==", + "dependencies": { + "inline-style-parser": "0.2.3" + } + }, "node_modules/stylus-lookup": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/stylus-lookup/-/stylus-lookup-3.0.2.tgz", @@ -26101,6 +27311,24 @@ "node": ">=14" } }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/ts-api-utils": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", @@ -26414,12 +27642,117 @@ "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==" }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unified/node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/uniq": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", "integrity": "sha512-Gw+zz50YNKPDKXs+9d+aKAjVwpjNwqzvNpLigIruT4HA9lMZNdMqs9x07kKHB/L9WRzqp4+DlTU5s4wG2esdoA==", "dev": true }, + "node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-remove-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-5.0.0.tgz", + "integrity": "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/universalify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", @@ -26670,6 +28003,33 @@ "extsprintf": "^1.2.0" } }, + "node_modules/vfile": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.1.tgz", + "integrity": "sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", + "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/victory": { "version": "34.3.12", "resolved": "https://registry.npmjs.org/victory/-/victory-34.3.12.tgz", @@ -28746,6 +30106,15 @@ "engines": { "node": ">=10" } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } } } } diff --git a/package.json b/package.json index 9bf4fd608..02bb3e045 100644 --- a/package.json +++ b/package.json @@ -172,11 +172,13 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "react-intl": "^6.6.8", + "react-markdown": "^9.0.1", "react-oidc-context": "^2.3.1", "react-redux": "^8.1.3", "react-router-dom": "^6.24.0", "redux": "^4.2.1", "redux-promise-middleware": "^6.2.0", + "remark-gfm": "^4.0.0", "sanitize-html": "^2.13.0", "title-case": "^3.0.3", "urijs": "^1.19.11" diff --git a/src/__mocks__/empty-mock.js b/src/__mocks__/empty-mock.js new file mode 100644 index 000000000..ad6627489 --- /dev/null +++ b/src/__mocks__/empty-mock.js @@ -0,0 +1,3 @@ +const EmptyMock = {}; + +export default EmptyMock; diff --git a/src/components/NotificationsDrawer/NotificationItem.tsx b/src/components/NotificationsDrawer/NotificationItem.tsx index 42e3aa633..8fd750e93 100644 --- a/src/components/NotificationsDrawer/NotificationItem.tsx +++ b/src/components/NotificationsDrawer/NotificationItem.tsx @@ -15,6 +15,8 @@ import EllipsisVIcon from '@patternfly/react-icons/dist/dynamic/icons/ellipsis-v import DateFormat from '@redhat-cloud-services/frontend-components/DateFormat'; import { NotificationData, updateNotificationReadAtom, updateNotificationSelectedAtom } from '../../state/atoms/notificationDrawerAtom'; import axios from 'axios'; +import ReactMarkdown from 'react-markdown'; +import remarkGfm from 'remark-gfm'; interface NotificationItemProps { notification: NotificationData; @@ -83,7 +85,9 @@ const NotificationItem: React.FC = ({ notification, onNav - {notification.description} + + {notification.description} +
From fbed933a0b804d6bfa7ea3c45106a21f24fe2b96 Mon Sep 17 00:00:00 2001 From: Radek Kaluzik Date: Wed, 3 Jul 2024 11:31:44 +0200 Subject: [PATCH 052/151] Update URL params for notitication items to point to its configuration when clicked on Manage this event --- src/components/NotificationsDrawer/NotificationItem.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/NotificationsDrawer/NotificationItem.tsx b/src/components/NotificationsDrawer/NotificationItem.tsx index 8fd750e93..86bb7e8d3 100644 --- a/src/components/NotificationsDrawer/NotificationItem.tsx +++ b/src/components/NotificationsDrawer/NotificationItem.tsx @@ -48,7 +48,10 @@ const NotificationItem: React.FC = ({ notification, onNav const notificationDropdownItems = [ {`Mark as ${!notification.read ? 'read' : 'unread'}`}, - onNavigateTo('settings/notifications/configure-events')}> + onNavigateTo(`/settings/notifications/configure-events?bundle=${notification.bundle}&tab=configuration`)} + > Manage this event , ]; From 12841039dc71765eedbfd73939e76be3906a9375 Mon Sep 17 00:00:00 2001 From: Martin Marosi Date: Mon, 8 Jul 2024 14:11:56 +0200 Subject: [PATCH 053/151] Expose WS subscription chrome API. --- docs/wsSubscription.md | 49 ++++++++++++++++ package-lock.json | 8 +-- package.json | 2 +- src/chrome/create-chrome.test.ts | 1 + src/chrome/create-chrome.ts | 5 +- src/components/RootApp/ScalprumRoot.tsx | 3 +- src/hooks/useChromeServiceEvents.ts | 75 ++++++++++++++++++------- 7 files changed, 115 insertions(+), 28 deletions(-) create mode 100644 docs/wsSubscription.md diff --git a/docs/wsSubscription.md b/docs/wsSubscription.md new file mode 100644 index 000000000..4863b88f1 --- /dev/null +++ b/docs/wsSubscription.md @@ -0,0 +1,49 @@ +# Websocket subscription API + +> This API is experimental and is restricted only to the notification drawer and other internal chrome APIs. If you are interested in using the WS API, contact the platform experience services team. + +## Subscribing to an event + +To consume events, the following information is necessary +- the event type +- the event payload shape + +Once this information is know, you can subscribe using the chrome API: + +```tsx +import useChrome from '@redhat-cloud-services/frontend-components/useChrome'; +import { ChromeWsPayload, ChromeWsEventTypes } from '@redhat-cloud-services/types'; +// depends on the event type +type EventPayload = { + description: string; + id: string; + read: boolean; + title: string; +}; + +const eventType: ChromeWsEventTypes = 'foo.bar'; + +const ConsumerComponent = () => { + const { addWsEventListener } = useChrome(); + const [data, setData] = useState[]>([]) + + function handleWsEvent(event: ChromeWsPayload) { + // handle the event according to requirements + setData(prev => [...prev, event]) + } + + useEffect(() => { + const unRegister = addWsEventListener(eventType, handleWsEvent) + return () => { + // Do not forget to clean the listener once the component is removed from VDOM + unRegister() + } + }, []) + + return ( + // somehow use the data + ) +} + + +``` \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index c1f8e9516..668f3c236 100644 --- a/package-lock.json +++ b/package-lock.json @@ -72,7 +72,7 @@ "@pmmmwh/react-refresh-webpack-plugin": "^0.5.15", "@redhat-cloud-services/eslint-config-redhat-cloud-services": "^1.3.0", "@redhat-cloud-services/frontend-components-config-utilities": "^3.0.7", - "@redhat-cloud-services/types": "^1.0.11", + "@redhat-cloud-services/types": "^1.0.12", "@simonsmith/cypress-image-snapshot": "^8.1.2", "@swc/core": "^1.6.5", "@swc/jest": "^0.2.36", @@ -4742,9 +4742,9 @@ } }, "node_modules/@redhat-cloud-services/types": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@redhat-cloud-services/types/-/types-1.0.11.tgz", - "integrity": "sha512-Ml1OsVEa8lUq3t2xUC6CJzD8p/E446HOk36D8aDMJ1c8gum7nndweLXz2Vdq+zewCF2RE+Dbe9E0QNJSfJcRHA==" + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@redhat-cloud-services/types/-/types-1.0.12.tgz", + "integrity": "sha512-Ib4ZdgtWDgn3B/o2x/QuMu81X8HO+OmIcvgTAZl3rMyndg/c6g6cb95ZPs011c08oD5AQLptRiG/AsUVwOvkXQ==" }, "node_modules/@remix-run/router": { "version": "1.17.0", diff --git a/package.json b/package.json index 02bb3e045..1aed8ac5a 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ "@pmmmwh/react-refresh-webpack-plugin": "^0.5.15", "@redhat-cloud-services/eslint-config-redhat-cloud-services": "^1.3.0", "@redhat-cloud-services/frontend-components-config-utilities": "^3.0.7", - "@redhat-cloud-services/types": "^1.0.11", + "@redhat-cloud-services/types": "^1.0.12", "@simonsmith/cypress-image-snapshot": "^8.1.2", "@swc/core": "^1.6.5", "@swc/jest": "^0.2.36", diff --git a/src/chrome/create-chrome.test.ts b/src/chrome/create-chrome.test.ts index 8d3f48da9..2bffc4c8f 100644 --- a/src/chrome/create-chrome.test.ts +++ b/src/chrome/create-chrome.test.ts @@ -93,6 +93,7 @@ describe('create chrome', () => { }; const chromeContextOptionsMock = { + addWsEventListener: jest.fn(), store: createStore(() => ({})) as Store, // getUser: () => Promise.resolve(mockUser), chromeAuth: chromeAuthMock, diff --git a/src/chrome/create-chrome.ts b/src/chrome/create-chrome.ts index 3739eda36..0ee46909f 100644 --- a/src/chrome/create-chrome.ts +++ b/src/chrome/create-chrome.ts @@ -1,5 +1,5 @@ import { createFetchPermissionsWatcher } from '../auth/fetchPermissions'; -import { AppNavigationCB, ChromeAPI, GenericCB } from '@redhat-cloud-services/types'; +import { AddChromeWsEventListener, AppNavigationCB, ChromeAPI, GenericCB } from '@redhat-cloud-services/types'; import { Store } from 'redux'; import { AnalyticsBrowser } from '@segment/analytics-next'; import get from 'lodash/get'; @@ -49,6 +49,7 @@ export type CreateChromeContextConfig = { isPreview: boolean; addNavListener: (cb: NavListener) => number; deleteNavListener: (id: number) => void; + addWsEventListener: AddChromeWsEventListener; }; export const createChromeContext = ({ @@ -63,6 +64,7 @@ export const createChromeContext = ({ isPreview, addNavListener, deleteNavListener, + addWsEventListener, }: CreateChromeContextConfig): ChromeAPI => { const fetchPermissions = createFetchPermissionsWatcher(chromeAuth.getUser); const visibilityFunctions = getVisibilityFunctions(); @@ -108,6 +110,7 @@ export const createChromeContext = ({ const api: ChromeAPI = { ...actions, + addWsEventListener, auth: { getRefreshToken: chromeAuth.getRefreshToken, getToken: chromeAuth.getToken, diff --git a/src/components/RootApp/ScalprumRoot.tsx b/src/components/RootApp/ScalprumRoot.tsx index 74a8a4001..e21ee5643 100644 --- a/src/components/RootApp/ScalprumRoot.tsx +++ b/src/components/RootApp/ScalprumRoot.tsx @@ -67,7 +67,7 @@ const ScalprumRoot = memo( const mutableChromeApi = useRef(); // initialize WS event handling - useChromeServiceEvents(); + const addWsEventListener = useChromeServiceEvents(); // track pendo usage useTrackPendoUsage(); // setting default tab title @@ -166,6 +166,7 @@ const ScalprumRoot = memo( isPreview, addNavListener, deleteNavListener, + addWsEventListener, }); // reset chrome object after token (user) updates/changes }, [chromeAuth.token, isPreview]); diff --git a/src/hooks/useChromeServiceEvents.ts b/src/hooks/useChromeServiceEvents.ts index 8686edee8..1ba1cb7b7 100644 --- a/src/hooks/useChromeServiceEvents.ts +++ b/src/hooks/useChromeServiceEvents.ts @@ -3,45 +3,59 @@ import { useFlag } from '@unleash/proxy-client-react'; import { setCookie } from '../auth/setCookie'; import ChromeAuthContext from '../auth/ChromeAuthContext'; import { useSetAtom } from 'jotai'; -import { NotificationData, NotificationsPayload, addNotificationAtom } from '../state/atoms/notificationDrawerAtom'; +import { NotificationData, addNotificationAtom } from '../state/atoms/notificationDrawerAtom'; +import { AddChromeWsEventListener, ChromeWsEventListener, ChromeWsEventTypes, ChromeWsPayload } from '@redhat-cloud-services/types'; -const NOTIFICATION_DRAWER = 'com.redhat.console.notifications.drawer'; -const SAMPLE_EVENT = 'sample.type'; +const NOTIFICATION_DRAWER: ChromeWsEventTypes = 'com.redhat.console.notifications.drawer'; +const ALL_TYPES: ChromeWsEventTypes[] = [NOTIFICATION_DRAWER]; +type Payload = NotificationData; -const ALL_TYPES = [NOTIFICATION_DRAWER, SAMPLE_EVENT] as const; -type EventTypes = (typeof ALL_TYPES)[number]; +function isGenericEvent(event: unknown): event is ChromeWsPayload { + return typeof event === 'object' && event !== null && ALL_TYPES.includes((event as Record).type); +} -type SamplePayload = { - foo: string; +type WsEventListenersRegistry = { + [type in ChromeWsEventTypes]: Map>; }; -type Payload = NotificationsPayload | SamplePayload; -interface GenericEvent { - type: EventTypes; - data: T; -} - -function isGenericEvent(event: unknown): event is GenericEvent { - return typeof event === 'object' && event !== null && ALL_TYPES.includes((event as Record).type); -} +// needs to be outside rendring cycle to preserver clients when chrome API changes +const wsEventListenersRegistry: WsEventListenersRegistry = { + [NOTIFICATION_DRAWER]: new Map(), +}; -const useChromeServiceEvents = () => { +const useChromeServiceEvents = (): AddChromeWsEventListener => { const connection = useRef(); const addNotification = useSetAtom(addNotificationAtom); const isNotificationsEnabled = useFlag('platform.chrome.notifications-drawer'); const { token, tokenExpires } = useContext(ChromeAuthContext); - const handlerMap: { [key in EventTypes]: (payload: GenericEvent) => void } = useMemo( + const removeEventListener = (id: symbol) => { + const type = id.description as ChromeWsEventTypes; + wsEventListenersRegistry[type].delete(id); + }; + + const addEventListener: AddChromeWsEventListener = (type: ChromeWsEventTypes, listener: ChromeWsEventListener) => { + const id = Symbol(type); + wsEventListenersRegistry[type].set(id, listener); + return () => removeEventListener(id); + }; + + const triggerListeners = (type: ChromeWsEventTypes, data: ChromeWsPayload) => { + wsEventListenersRegistry[type].forEach((cb) => cb(data)); + }; + + const handlerMap: { [key in ChromeWsEventTypes]: (payload: ChromeWsPayload) => void } = useMemo( () => ({ - [NOTIFICATION_DRAWER]: (data: GenericEvent) => { + [NOTIFICATION_DRAWER]: (data: ChromeWsPayload) => { + triggerListeners(NOTIFICATION_DRAWER, data); + // TODO: Move away from chrome once the portal content is moved to notifications addNotification(data.data as unknown as NotificationData); }, - [SAMPLE_EVENT]: (data: GenericEvent) => console.log('Received sample payload', data), }), [] ); - function handleEvent(type: EventTypes, data: GenericEvent): void { + function handleEvent(type: ChromeWsEventTypes, data: ChromeWsPayload): void { handlerMap[type](data); } @@ -69,6 +83,23 @@ const useChromeServiceEvents = () => { console.error('Handler failed when processing WS payload: ', data, error); } }; + + socket.onclose = () => { + // renew connection on close + // pod was restarted or network issue + setTimeout(() => { + createConnection(); + }, 2000); + }; + + socket.onerror = (error) => { + console.error('WS connection error: ', error); + // renew connection on error + // data was unable to be sent + setTimeout(() => { + createConnection(); + }, 2000); + }; } }; @@ -82,6 +113,8 @@ const useChromeServiceEvents = () => { console.error('Unable to establish WS connection'); } }, [isNotificationsEnabled]); + + return addEventListener; }; export default useChromeServiceEvents; From 8b966b4065d462d73b5933ada368c4523f9ac319 Mon Sep 17 00:00:00 2001 From: epwinchell <1287144+epwinchell@users.noreply.github.com> Date: Mon, 8 Jul 2024 15:50:38 -0400 Subject: [PATCH 054/151] Revert "Update empty state styling" --- .../FavoriteServices/EmptyState.scss | 6 ++- .../FavoriteServices/EmptyState.tsx | 43 ++++++++----------- 2 files changed, 23 insertions(+), 26 deletions(-) diff --git a/src/components/FavoriteServices/EmptyState.scss b/src/components/FavoriteServices/EmptyState.scss index 487f5b28e..246257b6c 100644 --- a/src/components/FavoriteServices/EmptyState.scss +++ b/src/components/FavoriteServices/EmptyState.scss @@ -1,6 +1,8 @@ @import "~@redhat-cloud-services/frontend-components-utilities/styles/_all"; @import '~@patternfly/patternfly/patternfly-addons.scss'; -.pf-v5-l-flex { - height: 100%; +.chr-l-stack__item-centered, .chr-c-card-centered { + display: flex; + justify-content: center; + text-align: center; } diff --git a/src/components/FavoriteServices/EmptyState.tsx b/src/components/FavoriteServices/EmptyState.tsx index 0b3f808aa..0b86565ff 100644 --- a/src/components/FavoriteServices/EmptyState.tsx +++ b/src/components/FavoriteServices/EmptyState.tsx @@ -1,6 +1,5 @@ import { Button } from '@patternfly/react-core/dist/dynamic/components/Button'; -import { Flex } from '@patternfly/react-core/dist/dynamic/layouts/Flex'; -import { Stack, StackItem } from '@patternfly/react-core/dist/dynamic/layouts/Stack'; +import { StackItem } from '@patternfly/react-core/dist/dynamic/layouts/Stack'; import { Text, TextContent } from '@patternfly/react-core/dist/dynamic/components/Text'; import React from 'react'; @@ -10,28 +9,24 @@ import './EmptyState.scss'; const EmptyState = () => ( <> - - - - favoriting image - - - - - No favorited services - - - Add a service to your favorites to get started here. - - - - - - - - + + favoriting image + + + + + No favorited services + + + Add a service to your favorites to get started here. + + + + + + ); From d4da42d247798e854bd95c9aa03828560653f5be Mon Sep 17 00:00:00 2001 From: ewinchel Date: Tue, 9 Jul 2024 09:44:21 -0400 Subject: [PATCH 055/151] Update empty state styling --- .../FavoriteServices/EmptyState.scss | 9 ++-- .../FavoriteServices/EmptyState.tsx | 44 +++++++++++-------- 2 files changed, 30 insertions(+), 23 deletions(-) diff --git a/src/components/FavoriteServices/EmptyState.scss b/src/components/FavoriteServices/EmptyState.scss index 246257b6c..f7b3feacc 100644 --- a/src/components/FavoriteServices/EmptyState.scss +++ b/src/components/FavoriteServices/EmptyState.scss @@ -1,8 +1,9 @@ @import "~@redhat-cloud-services/frontend-components-utilities/styles/_all"; @import '~@patternfly/patternfly/patternfly-addons.scss'; -.chr-l-stack__item-centered, .chr-c-card-centered { - display: flex; - justify-content: center; - text-align: center; +.chrome-favoriteServices { + .pf-v5-l-flex { + height: 100%; + background: yellow; + } } diff --git a/src/components/FavoriteServices/EmptyState.tsx b/src/components/FavoriteServices/EmptyState.tsx index 0b86565ff..221d6e067 100644 --- a/src/components/FavoriteServices/EmptyState.tsx +++ b/src/components/FavoriteServices/EmptyState.tsx @@ -1,5 +1,6 @@ import { Button } from '@patternfly/react-core/dist/dynamic/components/Button'; -import { StackItem } from '@patternfly/react-core/dist/dynamic/layouts/Stack'; +import { Flex } from '@patternfly/react-core/dist/dynamic/layouts/Flex'; +import { Stack, StackItem } from '@patternfly/react-core/dist/dynamic/layouts/Stack'; import { Text, TextContent } from '@patternfly/react-core/dist/dynamic/components/Text'; import React from 'react'; @@ -9,25 +10,30 @@ import './EmptyState.scss'; const EmptyState = () => ( <> - - favoriting image - - - - - No favorited services - - - Add a service to your favorites to get started here. - - - - - - + + + + favoriting image + + + + + No favorited services + + + Add a service to your favorites to get started here. + + + + + + + + ); export default EmptyState; + From 2c4a8f7c3239da7a1e4f0ffc6288d80611cde098 Mon Sep 17 00:00:00 2001 From: epwinchell <1287144+epwinchell@users.noreply.github.com> Date: Tue, 9 Jul 2024 09:45:59 -0400 Subject: [PATCH 056/151] Update EmptyState.scss --- src/components/FavoriteServices/EmptyState.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/FavoriteServices/EmptyState.scss b/src/components/FavoriteServices/EmptyState.scss index f7b3feacc..012aadc1b 100644 --- a/src/components/FavoriteServices/EmptyState.scss +++ b/src/components/FavoriteServices/EmptyState.scss @@ -4,6 +4,5 @@ .chrome-favoriteServices { .pf-v5-l-flex { height: 100%; - background: yellow; } } From 27faaf590c3b148357420f9027446c6f8efc1250 Mon Sep 17 00:00:00 2001 From: epwinchell <1287144+epwinchell@users.noreply.github.com> Date: Tue, 9 Jul 2024 11:33:51 -0400 Subject: [PATCH 057/151] lint fix --- src/components/FavoriteServices/EmptyState.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/FavoriteServices/EmptyState.tsx b/src/components/FavoriteServices/EmptyState.tsx index 221d6e067..0b3f808aa 100644 --- a/src/components/FavoriteServices/EmptyState.tsx +++ b/src/components/FavoriteServices/EmptyState.tsx @@ -36,4 +36,3 @@ const EmptyState = () => ( ); export default EmptyState; - From ef7ba6c0bc9b766ddd7c8e59dbbcb40ff94a4573 Mon Sep 17 00:00:00 2001 From: Joachim Schuler Date: Thu, 11 Jul 2024 11:53:17 -0400 Subject: [PATCH 058/151] Remove unnecessary icon margins if no icon present --- src/components/Navigation/ChromeNavGroup.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/Navigation/ChromeNavGroup.tsx b/src/components/Navigation/ChromeNavGroup.tsx index 48fe7fb3e..5ead39da8 100644 --- a/src/components/Navigation/ChromeNavGroup.tsx +++ b/src/components/Navigation/ChromeNavGroup.tsx @@ -14,9 +14,11 @@ const ChromeNavGroup = ({ navItems, isHidden, icon, title }: ChromeNavGroupProps const groupTitle = (
- - {icon && sectionTitleMapper[icon]} - + {icon && ( + + {sectionTitleMapper[icon]} + + )} {title}
); From 6f0541e6cf9c419438800abea9938dd750591540 Mon Sep 17 00:00:00 2001 From: Bryan Florkiewicz Date: Mon, 15 Jul 2024 18:14:35 -0400 Subject: [PATCH 059/151] Add search feedback buttons --- src/components/Search/SearchFeedback.scss | 5 +++ src/components/Search/SearchFeedback.tsx | 38 +++++++++++++++++++++++ src/components/Search/SearchInput.tsx | 26 ++++++++++------ src/components/Search/SearchTypes.ts | 6 ++++ 4 files changed, 65 insertions(+), 10 deletions(-) create mode 100644 src/components/Search/SearchFeedback.scss create mode 100644 src/components/Search/SearchFeedback.tsx create mode 100644 src/components/Search/SearchTypes.ts diff --git a/src/components/Search/SearchFeedback.scss b/src/components/Search/SearchFeedback.scss new file mode 100644 index 000000000..09cfb8fa9 --- /dev/null +++ b/src/components/Search/SearchFeedback.scss @@ -0,0 +1,5 @@ +.chr-c-search-feedback { + display: flex; + justify-content: flex-end; + align-items: center; +} diff --git a/src/components/Search/SearchFeedback.tsx b/src/components/Search/SearchFeedback.tsx new file mode 100644 index 000000000..59e2c74b1 --- /dev/null +++ b/src/components/Search/SearchFeedback.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import './SearchFeedback.scss'; + +import { Icon } from '@patternfly/react-core/dist/dynamic/components/Icon'; +import OutlinedThumbsUpIcon from '@patternfly/react-icons/dist/dynamic/icons/outlined-thumbs-up-icon'; +import OutlinedThumbsDownIcon from '@patternfly/react-icons/dist/dynamic/icons/outlined-thumbs-down-icon'; +import { MenuGroup, MenuItem } from '@patternfly/react-core/dist/dynamic/components/Menu'; +import { useSegment } from '../../analytics/useSegment'; +import type { SearchItem } from './SearchTypes'; +import { SegmentEvent } from '@segment/analytics-next'; + +export type SearchFeedbackProps = { + query: string; + results: SearchItem[]; +}; + +const SearchFeedback = ({ query, results }: SearchFeedbackProps) => { + const { ready, analytics } = useSegment(); + const trackFeedback = (type: string | SegmentEvent) => { + ready && analytics && analytics.track(type, { query, results }); + }; + return ( + + trackFeedback('chrome.search-query-feedback-positive')}> + + + + + trackFeedback('chrome.search-query-feedback-negative')}> + + + + + + ); +}; + +export default SearchFeedback; diff --git a/src/components/Search/SearchInput.tsx b/src/components/Search/SearchInput.tsx index f92c31644..9b8cba947 100644 --- a/src/components/Search/SearchInput.tsx +++ b/src/components/Search/SearchInput.tsx @@ -1,7 +1,7 @@ import React, { useCallback, useEffect, useRef, useState } from 'react'; import debounce from 'lodash/debounce'; import { Bullseye } from '@patternfly/react-core/dist/dynamic/layouts/Bullseye'; -import { Menu, MenuContent, MenuGroup, MenuItem, MenuList } from '@patternfly/react-core/dist/dynamic/components/Menu'; +import { Menu, MenuContent, MenuFooter, MenuGroup, MenuItem, MenuList } from '@patternfly/react-core/dist/dynamic/components/Menu'; import { SearchInput as PFSearchInput, SearchInputProps } from '@patternfly/react-core/dist/dynamic/components/SearchInput'; import { Spinner } from '@patternfly/react-core/dist/dynamic/components/Spinner'; import { Popper } from '@patternfly/react-core/dist/dynamic/helpers/Popper/Popper'; @@ -19,6 +19,8 @@ import { asyncLocalOrama } from '../../state/atoms/localSearchAtom'; import { localQuery } from '../../utils/localSearch'; import { isPreviewAtom } from '../../state/atoms/releaseAtom'; import { ReleaseEnv } from '../../@types/types.d'; +import type { SearchItem } from './SearchTypes'; +import SearchFeedback from './SearchFeedback'; export type SearchInputprops = { isExpanded?: boolean; @@ -35,13 +37,6 @@ const getMaxMenuHeight = (menuElement?: HTMLDivElement | null) => { return bodyHeight - menuTopOffset - 4; }; -type SearchItem = { - title: string; - bundleTitle: string; - description: string; - pathname: string; -}; - type SearchInputListener = { onStateChange: (isOpen: boolean) => void; }; @@ -197,8 +192,18 @@ const SearchInput = ({ onStateChange }: SearchInputListener) => { className={isExpanded ? 'pf-u-flex-grow-1' : 'chr-c-search__collapsed'} /> ); + + let menuFooter; + if (searchItems.length > 0 && !isFetching) { + menuFooter = ( + + + + ); + } + const menu = ( - + {isFetching ? ( @@ -207,7 +212,7 @@ const SearchInput = ({ onStateChange }: SearchInputListener) => { ) : ( <> - 0 ? `Top ${searchItems.length} results` : undefined}> + 0 ? `Top ${searchItems.length} results` : undefined} className="pf-v5-u-px-md"> {searchItems.map((item, index) => ( { @@ -235,6 +240,7 @@ const SearchInput = ({ onStateChange }: SearchInputListener) => { {searchItems.length === 0 && !isFetching && } + {menuFooter} ); diff --git a/src/components/Search/SearchTypes.ts b/src/components/Search/SearchTypes.ts new file mode 100644 index 000000000..a99bf509f --- /dev/null +++ b/src/components/Search/SearchTypes.ts @@ -0,0 +1,6 @@ +export type SearchItem = { + title: string; + bundleTitle: string; + description: string; + pathname: string; +}; From c5aab88b9fee6f6bcdeea9d1a8aa99f0f4190227 Mon Sep 17 00:00:00 2001 From: Martin Marosi Date: Thu, 18 Jul 2024 11:14:15 +0200 Subject: [PATCH 060/151] Merge Chrome global error boundaries into one. --- src/auth/OIDCConnector/OIDCProvider.tsx | 23 ++++++------ src/auth/OIDCConnector/OIDCStateReloader.tsx | 36 ------------------- .../ErrorComponents/ErrorBoundary.tsx | 16 +++++++++ 3 files changed, 26 insertions(+), 49 deletions(-) delete mode 100644 src/auth/OIDCConnector/OIDCStateReloader.tsx diff --git a/src/auth/OIDCConnector/OIDCProvider.tsx b/src/auth/OIDCConnector/OIDCProvider.tsx index bdac4000c..bc9c455b2 100644 --- a/src/auth/OIDCConnector/OIDCProvider.tsx +++ b/src/auth/OIDCConnector/OIDCProvider.tsx @@ -6,7 +6,6 @@ import platformUrl from '../platformUrl'; import { OIDCSecured } from './OIDCSecured'; import AppPlaceholder from '../../components/AppPlaceholder'; import { postbackUrlSetup } from '../offline'; -import OIDCStateReloader from './OIDCStateReloader'; const LOCAL_PREVIEW = localStorage.getItem('chrome:local-preview') === 'true'; // TODO: remove this once the local preview is enabled by default @@ -76,18 +75,16 @@ const OIDCProvider: React.FC = ({ children }) => { } return ( - - - - {children} - - - + + + {children} + + ); }; diff --git a/src/auth/OIDCConnector/OIDCStateReloader.tsx b/src/auth/OIDCConnector/OIDCStateReloader.tsx deleted file mode 100644 index 988415721..000000000 --- a/src/auth/OIDCConnector/OIDCStateReloader.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import React, { PropsWithChildren } from 'react'; - -const INVALID_AUTH_STATE_ERROR = 'No matching state found in storage'; - -export default class OIDCStateReloader extends React.Component { - state: { hasError: boolean } = { - hasError: false, - }; - - static getDerivedStateFromError() { - return { hasError: true }; - } - handleInvalidAuthState(): void { - const repairedUrl = new URL(window.location.href); - // remove invalid SSO state and force re authentication - repairedUrl.hash = ''; - // remove possibly broken local storage state from client - localStorage.clear(); - // hard page reload - window.location.href = repairedUrl.toString(); - } - - componentDidCatch(error: Error): void { - // handle invalid auth state error - if (typeof error.message === 'string' && error.message === INVALID_AUTH_STATE_ERROR) { - this.handleInvalidAuthState(); - } - } - - render() { - if (this.state.hasError) { - return null; - } - return this.props.children; - } -} diff --git a/src/components/ErrorComponents/ErrorBoundary.tsx b/src/components/ErrorComponents/ErrorBoundary.tsx index 86dad8bf3..f7ac14bfe 100644 --- a/src/components/ErrorComponents/ErrorBoundary.tsx +++ b/src/components/ErrorComponents/ErrorBoundary.tsx @@ -8,6 +8,8 @@ type ErrorBoundaryState = { errorInfo?: any; }; +const INVALID_AUTH_STATE_ERROR = 'No matching state found in storage'; + class ErrorBoundary extends React.Component< { children: React.ReactNode; @@ -23,6 +25,16 @@ class ErrorBoundary extends React.Component< return { hasError: true }; } + handleInvalidAuthState(): void { + const repairedUrl = new URL(window.location.href); + // remove invalid SSO state and force re authentication + repairedUrl.hash = ''; + // remove possibly broken local storage state from client + localStorage.clear(); + // hard page reload + window.location.href = repairedUrl.toString(); + } + componentDidCatch(error: any, errorInfo: any) { console.error('Chrome encountered an error!', error); this.setState((prev) => ({ @@ -30,6 +42,10 @@ class ErrorBoundary extends React.Component< error, errorInfo, })); + + if (typeof error.message === 'string' && error.message === INVALID_AUTH_STATE_ERROR) { + this.handleInvalidAuthState(); + } } render() { From c4a54c3ae372023a8091742ca90827bebe7e320a Mon Sep 17 00:00:00 2001 From: Joachim Schuler Date: Thu, 18 Jul 2024 10:34:50 -0400 Subject: [PATCH 061/151] update snapshots --- .../__snapshots__/ChromeNavGroup.test.js.snap | 14 -------------- .../ChromeNavItemFactory.test.js.snap | 7 ------- 2 files changed, 21 deletions(-) diff --git a/src/components/Navigation/__snapshots__/ChromeNavGroup.test.js.snap b/src/components/Navigation/__snapshots__/ChromeNavGroup.test.js.snap index ba853eafd..92b471793 100644 --- a/src/components/Navigation/__snapshots__/ChromeNavGroup.test.js.snap +++ b/src/components/Navigation/__snapshots__/ChromeNavGroup.test.js.snap @@ -13,13 +13,6 @@ exports[`ChromeNavGroup should render nav item group 1`] = ` id="Foo" >
- - - Foo
@@ -85,13 +78,6 @@ exports[`ChromeNavGroup should render nav item group with items 1`] = ` id="Foo" >
- - - Foo
diff --git a/src/components/Navigation/__snapshots__/ChromeNavItemFactory.test.js.snap b/src/components/Navigation/__snapshots__/ChromeNavItemFactory.test.js.snap index 18a3403a9..103e33b81 100644 --- a/src/components/Navigation/__snapshots__/ChromeNavItemFactory.test.js.snap +++ b/src/components/Navigation/__snapshots__/ChromeNavItemFactory.test.js.snap @@ -80,13 +80,6 @@ exports[`ChromeNavItemFactory should render chrome group nav item 1`] = ` id="group" >
- - - group
From 78b3589ea7e0969fd55da468c74f614bbe2bd276 Mon Sep 17 00:00:00 2001 From: Martin Marosi Date: Fri, 19 Jul 2024 09:57:07 +0200 Subject: [PATCH 062/151] Use suspense loader utility from FEC utils. --- package-lock.json | 8 +++---- package.json | 2 +- src/bootstrap.tsx | 4 ++-- src/hooks/useAsyncLoader.ts | 43 ------------------------------------- 4 files changed, 7 insertions(+), 50 deletions(-) delete mode 100644 src/hooks/useAsyncLoader.ts diff --git a/package-lock.json b/package-lock.json index 668f3c236..828730244 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,7 +27,7 @@ "@redhat-cloud-services/frontend-components": "^4.2.11", "@redhat-cloud-services/frontend-components-notifications": "^4.1.0", "@redhat-cloud-services/frontend-components-pdf-generator": "4.0.5", - "@redhat-cloud-services/frontend-components-utilities": "^4.0.13", + "@redhat-cloud-services/frontend-components-utilities": "^4.0.14", "@redhat-cloud-services/host-inventory-client": "1.2.0", "@redhat-cloud-services/rbac-client": "1.2.0", "@scalprum/core": "^0.7.0", @@ -4665,9 +4665,9 @@ } }, "node_modules/@redhat-cloud-services/frontend-components-utilities": { - "version": "4.0.13", - "resolved": "https://registry.npmjs.org/@redhat-cloud-services/frontend-components-utilities/-/frontend-components-utilities-4.0.13.tgz", - "integrity": "sha512-UBvfUlrf0IVPRcqhipRXGBNIuMP1+CG4FRNyGKCb8PuZM/iPX951JmEbelgBAyEwKGffnEPojP0A+9Xu6iFlJg==", + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/@redhat-cloud-services/frontend-components-utilities/-/frontend-components-utilities-4.0.14.tgz", + "integrity": "sha512-Xf3zvEZ9nPZJ24qlDYtIkmFW/zsWwQHzU4rfaftNDol7mP+X21PunW3pMb9YSCDzRiqKiwWDUjtgiVjiuNTb3w==", "dependencies": { "@redhat-cloud-services/rbac-client": "^1.0.111 || 2.x", "@redhat-cloud-services/types": "^1.0.9", diff --git a/package.json b/package.json index 1aed8ac5a..9e7200761 100644 --- a/package.json +++ b/package.json @@ -144,7 +144,7 @@ "@redhat-cloud-services/frontend-components": "^4.2.11", "@redhat-cloud-services/frontend-components-notifications": "^4.1.0", "@redhat-cloud-services/frontend-components-pdf-generator": "4.0.5", - "@redhat-cloud-services/frontend-components-utilities": "^4.0.13", + "@redhat-cloud-services/frontend-components-utilities": "^4.0.14", "@redhat-cloud-services/host-inventory-client": "1.2.0", "@redhat-cloud-services/rbac-client": "1.2.0", "@scalprum/core": "^0.7.0", diff --git a/src/bootstrap.tsx b/src/bootstrap.tsx index 17393c2c5..6a3a06281 100644 --- a/src/bootstrap.tsx +++ b/src/bootstrap.tsx @@ -16,9 +16,9 @@ import chromeStore from './state/chromeStore'; import { GenerateId } from '@patternfly/react-core/dist/dynamic/helpers/GenerateId/GenerateId'; import { isPreviewAtom } from './state/atoms/releaseAtom'; import AppPlaceholder from './components/AppPlaceholder'; -import useAsyncLoader from './hooks/useAsyncLoader'; import { ChromeUserConfig, initChromeUserConfig } from './utils/initUserConfig'; import ChromeAuthContext from './auth/ChromeAuthContext'; +import useSuspenseLoader from '@redhat-cloud-services/frontend-components-utilities/useSuspenseLoader/useSuspenseLoader'; const isITLessEnv = ITLess(); const language: keyof typeof messages = 'en'; @@ -68,7 +68,7 @@ const ConfigLoader = () => { function initFail() { initPreview(false); } - const { loader } = useAsyncLoader(initChromeUserConfig, initSuccess, initFail); + const { loader } = useSuspenseLoader(initChromeUserConfig, initSuccess, initFail); const [cookieElement, setCookieElement] = useState(null); return ( }> diff --git a/src/hooks/useAsyncLoader.ts b/src/hooks/useAsyncLoader.ts deleted file mode 100644 index b2d210a0e..000000000 --- a/src/hooks/useAsyncLoader.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { useRef } from 'react'; - -function useAsyncLoader, T extends Array>( - asyncMethod: (...args: T) => Promise, - afterResolve?: (result: R) => void, - afterReject?: (error: any) => void -) { - const storage = useRef<{ resolved: boolean; rejected: boolean; promise?: Promise; result?: R }>({ - resolved: false, - rejected: false, - promise: undefined, - result: undefined, - }); - - return { - loader: (...args: Parameters) => { - if (storage.current.rejected) return; - - if (storage.current.resolved) return storage.current.result; - - if (storage.current.promise) throw storage.current.promise; - - storage.current.promise = asyncMethod(...args) - .then((res) => { - storage.current.promise = undefined; - storage.current.resolved = true; - storage.current.result = res; - afterResolve?.(res); - return res; - }) - .catch((error) => { - storage.current.promise = undefined; - storage.current.rejected = true; - afterReject?.(error); - return error; - }); - - throw storage.current.promise; - }, - }; -} - -export default useAsyncLoader; From 24beadfab7690651465ac8ff0ceb7d1ff0f79789 Mon Sep 17 00:00:00 2001 From: Martin Marosi Date: Tue, 23 Jul 2024 08:49:43 +0200 Subject: [PATCH 063/151] Limit WS connection retries. --- src/hooks/useChromeServiceEvents.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/hooks/useChromeServiceEvents.ts b/src/hooks/useChromeServiceEvents.ts index 1ba1cb7b7..21e60db05 100644 --- a/src/hooks/useChromeServiceEvents.ts +++ b/src/hooks/useChromeServiceEvents.ts @@ -6,6 +6,7 @@ import { useSetAtom } from 'jotai'; import { NotificationData, addNotificationAtom } from '../state/atoms/notificationDrawerAtom'; import { AddChromeWsEventListener, ChromeWsEventListener, ChromeWsEventTypes, ChromeWsPayload } from '@redhat-cloud-services/types'; +const RETRY_LIMIT = 5; const NOTIFICATION_DRAWER: ChromeWsEventTypes = 'com.redhat.console.notifications.drawer'; const ALL_TYPES: ChromeWsEventTypes[] = [NOTIFICATION_DRAWER]; type Payload = NotificationData; @@ -28,6 +29,7 @@ const useChromeServiceEvents = (): AddChromeWsEventListener => { const addNotification = useSetAtom(addNotificationAtom); const isNotificationsEnabled = useFlag('platform.chrome.notifications-drawer'); const { token, tokenExpires } = useContext(ChromeAuthContext); + const retries = useRef(0); const removeEventListener = (id: symbol) => { const type = id.description as ChromeWsEventTypes; @@ -71,6 +73,7 @@ const useChromeServiceEvents = (): AddChromeWsEventListener => { connection.current = socket; socket.onmessage = (event) => { + retries.current = 0; const { data } = event; try { const payload = JSON.parse(data); @@ -97,7 +100,11 @@ const useChromeServiceEvents = (): AddChromeWsEventListener => { // renew connection on error // data was unable to be sent setTimeout(() => { - createConnection(); + if (retries.current < RETRY_LIMIT) { + createConnection(); + } + + retries.current += 1; }, 2000); }; } From cedd86cad6892738f71d0e18a2873555ff16939b Mon Sep 17 00:00:00 2001 From: Martin Marosi Date: Tue, 23 Jul 2024 09:54:31 +0200 Subject: [PATCH 064/151] Move debugger redux state to Jotai. --- src/chrome/create-chrome.ts | 19 +++++++-------- src/components/Debugger/DebuggerModal.tsx | 10 +++----- src/components/RootApp/RootApp.tsx | 3 ++- src/redux/action-types.ts | 2 -- src/redux/actions.ts | 10 -------- src/redux/chromeReducers.ts | 28 ----------------------- src/redux/index.ts | 6 ----- src/redux/store.d.ts | 2 -- src/state/atoms/debuggerModalatom.ts | 4 ++++ src/state/chromeStore.ts | 2 ++ 10 files changed, 19 insertions(+), 67 deletions(-) create mode 100644 src/state/atoms/debuggerModalatom.ts diff --git a/src/chrome/create-chrome.ts b/src/chrome/create-chrome.ts index 0ee46909f..e6a18a626 100644 --- a/src/chrome/create-chrome.ts +++ b/src/chrome/create-chrome.ts @@ -5,15 +5,7 @@ import { AnalyticsBrowser } from '@segment/analytics-next'; import get from 'lodash/get'; import Cookies from 'js-cookie'; -import { - appAction, - appObjectId, - globalFilterScope, - removeGlobalFilter, - toggleDebuggerButton, - toggleDebuggerModal, - toggleGlobalFilter, -} from '../redux/actions'; +import { appAction, appObjectId, globalFilterScope, removeGlobalFilter, toggleGlobalFilter } from '../redux/actions'; import { ITLess, getEnv, getEnvDetails, isProd, updateDocumentTitle } from '../utils/common'; import { createSupportCase } from '../utils/createCase'; import debugFunctions from '../utils/debugFunctions'; @@ -36,6 +28,7 @@ import chromeStore from '../state/chromeStore'; import { isFeedbackModalOpenAtom } from '../state/atoms/feedbackModalAtom'; import { usePendoFeedback } from '../components/Feedback'; import { NavListener, activeAppAtom } from '../state/atoms/activeAppAtom'; +import { isDebuggerEnabledAtom } from '../state/atoms/debuggerModalatom'; export type CreateChromeContextConfig = { useGlobalFilter: (callback: (selectedTags?: FlagTagsFilter) => any) => ReturnType; @@ -185,8 +178,12 @@ export const createChromeContext = ({ toggleFeedbackModal: (isOpen: boolean) => { chromeStore.set(isFeedbackModalOpenAtom, isOpen); }, - enableDebugging: () => dispatch(toggleDebuggerButton(true)), - toggleDebuggerModal: (isOpen: boolean) => dispatch(toggleDebuggerModal(isOpen)), + enableDebugging: () => { + chromeStore.set(isDebuggerEnabledAtom, true); + }, + toggleDebuggerModal: (isOpen: boolean) => { + chromeStore.set(isDebuggerEnabledAtom, isOpen); + }, // FIXME: Update types once merged quickStarts: quickstartsAPI as unknown as ChromeAPI['quickStarts'], helpTopics, diff --git a/src/components/Debugger/DebuggerModal.tsx b/src/components/Debugger/DebuggerModal.tsx index 22eb012ce..59cb88453 100644 --- a/src/components/Debugger/DebuggerModal.tsx +++ b/src/components/Debugger/DebuggerModal.tsx @@ -4,26 +4,22 @@ import { Modal, ModalVariant } from '@patternfly/react-core/dist/dynamic/compone import { TextContent } from '@patternfly/react-core/dist/dynamic/components/Text'; import { BugIcon } from '@patternfly/react-icons/dist/dynamic/icons/bug-icon'; import { ChromeUser } from '@redhat-cloud-services/types'; -import { useDispatch, useSelector } from 'react-redux'; import { DeepRequired } from 'utility-types'; - -import { toggleDebuggerModal } from '../../redux/actions'; -import { ReduxState } from '../../redux/store'; import { MenuToggle, MenuToggleElement } from '@patternfly/react-core/dist/dynamic/components/MenuToggle'; import { Select, SelectList, SelectOption } from '@patternfly/react-core/dist/dynamic/components/Select'; import DebuggerTable from './DebuggerTable'; import './Debugger.scss'; +import { useAtom } from 'jotai'; +import { isDebuggerModalOpenAtom } from '../../state/atoms/debuggerModalatom'; export type DebuggerModalProps = { user: DeepRequired; }; const DebuggerModal = ({ user }: DebuggerModalProps) => { - const isOpen = useSelector(({ chrome: { isDebuggerModalOpen } }) => isDebuggerModalOpen); - const dispatch = useDispatch(); - const setIsModalOpen = (isOpen: boolean) => dispatch(toggleDebuggerModal(isOpen)); + const [isOpen, setIsModalOpen] = useAtom(isDebuggerModalOpenAtom); const [isDropdownOpen, setIsOpen] = React.useState(false); const [selected, setSelected] = React.useState('Entitlements'); const menuRef = React.useRef(null); diff --git a/src/components/RootApp/RootApp.tsx b/src/components/RootApp/RootApp.tsx index 6ada6b464..e69b41727 100644 --- a/src/components/RootApp/RootApp.tsx +++ b/src/components/RootApp/RootApp.tsx @@ -21,6 +21,7 @@ import { FooterProps } from '../Footer/Footer'; import ChromeAuthContext, { ChromeAuthContextValue } from '../../auth/ChromeAuthContext'; import { activeModuleAtom } from '../../state/atoms/activeModuleAtom'; import { scalprumConfigAtom } from '../../state/atoms/scalprumConfigAtom'; +import { isDebuggerEnabledAtom } from '../../state/atoms/debuggerModalatom'; const NotEntitledModal = lazy(() => import('../NotEntitledModal')); const Debugger = lazy(() => import('../Debugger')); @@ -41,7 +42,7 @@ const RootApp = memo((props: RootAppProps) => { }: ReduxState) => Object.values(quickstarts).flat() ); const { user } = useContext(ChromeAuthContext) as DeepRequired; - const isDebuggerEnabled = useSelector(({ chrome: { isDebuggerEnabled } }) => isDebuggerEnabled); + const isDebuggerEnabled = useAtomValue(isDebuggerEnabledAtom); // verify use loged in scopes useUserSSOScopes(); diff --git a/src/redux/action-types.ts b/src/redux/action-types.ts index 64f76ecc7..a6e27f5c2 100644 --- a/src/redux/action-types.ts +++ b/src/redux/action-types.ts @@ -14,8 +14,6 @@ export const GLOBAL_FILTER_REMOVE = '@@chrome/global-filter-remove'; export const LOAD_NAVIGATION_LANDING_PAGE = '@@chrome/load-navigation-landing-page'; export const LOAD_LEFT_NAVIGATION_SEGMENT = '@@chrome/load-navigation-segment'; -export const TOGGLE_DEBUGGER_MODAL = '@@chrome/toggle-debugger-modal'; -export const TOGGLE_DEBUGGER_BUTTON = '@@chrome/toggle-debugger-button'; export const UPDATE_ACCESS_REQUESTS_NOTIFICATIONS = '@@chrome/update-access-requests-notifications'; export const MARK_REQUEST_NOTIFICATION_SEEN = '@@chrome/mark-request-notification-seen'; export const UPDATE_DOCUMENT_TITLE_REDUCER = '@@chrome/update-document-title'; diff --git a/src/redux/actions.ts b/src/redux/actions.ts index 88cf682fc..08e4d3ede 100644 --- a/src/redux/actions.ts +++ b/src/redux/actions.ts @@ -98,16 +98,6 @@ export const onToggle = () => ({ type: 'NAVIGATION_TOGGLE', }); -export const toggleDebuggerModal = (payload: boolean) => ({ - type: actionTypes.TOGGLE_DEBUGGER_MODAL, - payload, -}); - -export const toggleDebuggerButton = (payload: boolean) => ({ - type: actionTypes.TOGGLE_DEBUGGER_BUTTON, - payload, -}); - export const updateAccessRequestsNotifications = (payload: { count: number; data: AccessRequest[] }) => ({ type: actionTypes.UPDATE_ACCESS_REQUESTS_NOTIFICATIONS, payload, diff --git a/src/redux/chromeReducers.ts b/src/redux/chromeReducers.ts index 0bfff2347..bc144dec6 100644 --- a/src/redux/chromeReducers.ts +++ b/src/redux/chromeReducers.ts @@ -73,34 +73,6 @@ export function loadNavigationSegmentReducer( return state; } -export function toggleDebuggerModal( - state: ChromeState, - { - payload, - }: { - payload: boolean; - } -): ChromeState { - return { - ...state, - isDebuggerModalOpen: payload, - }; -} - -export function toggleDebuggerButton( - state: ChromeState, - { - payload, - }: { - payload: boolean; - } -): ChromeState { - return { - ...state, - isDebuggerEnabled: payload, - }; -} - export function accessRequestsNotificationsReducer( state: ChromeState, { payload: { count, data } }: { payload: { count: number; data: AccessRequest[] } } diff --git a/src/redux/index.ts b/src/redux/index.ts index 7654e981b..7bd2285c3 100644 --- a/src/redux/index.ts +++ b/src/redux/index.ts @@ -14,8 +14,6 @@ import { onPageAction, onPageObjectId, populateQuickstartsReducer, - toggleDebuggerButton, - toggleDebuggerModal, } from './chromeReducers'; import { globalFilterDefaultState, @@ -48,8 +46,6 @@ import { MARK_ACTIVE_PRODUCT, MARK_REQUEST_NOTIFICATION_SEEN, POPULATE_QUICKSTARTS_CATALOG, - TOGGLE_DEBUGGER_BUTTON, - TOGGLE_DEBUGGER_MODAL, UPDATE_ACCESS_REQUESTS_NOTIFICATIONS, UPDATE_DOCUMENT_TITLE_REDUCER, USER_LOGIN, @@ -63,8 +59,6 @@ const reducers = { [CHROME_PAGE_OBJECT]: onPageObjectId, [LOAD_NAVIGATION_LANDING_PAGE]: loadNavigationLandingPageReducer, [LOAD_LEFT_NAVIGATION_SEGMENT]: loadNavigationSegmentReducer, - [TOGGLE_DEBUGGER_MODAL]: toggleDebuggerModal, - [TOGGLE_DEBUGGER_BUTTON]: toggleDebuggerButton, [UPDATE_ACCESS_REQUESTS_NOTIFICATIONS]: accessRequestsNotificationsReducer, [MARK_REQUEST_NOTIFICATION_SEEN]: markAccessRequestRequestReducer, [POPULATE_QUICKSTARTS_CATALOG]: populateQuickstartsReducer, diff --git a/src/redux/store.d.ts b/src/redux/store.d.ts index 871b9cc59..96d816ee2 100644 --- a/src/redux/store.d.ts +++ b/src/redux/store.d.ts @@ -15,8 +15,6 @@ export type ChromeState = { pageAction?: string; pageObjectId?: string; navigation: InternalNavigation; - isDebuggerModalOpen?: boolean; - isDebuggerEnabled?: boolean; accessRequests: { count: number; data: AccessRequest[]; diff --git a/src/state/atoms/debuggerModalatom.ts b/src/state/atoms/debuggerModalatom.ts new file mode 100644 index 000000000..e9a80737d --- /dev/null +++ b/src/state/atoms/debuggerModalatom.ts @@ -0,0 +1,4 @@ +import { atomWithToggle } from './utils'; + +export const isDebuggerModalOpenAtom = atomWithToggle(false); +export const isDebuggerEnabledAtom = atomWithToggle(false); diff --git a/src/state/chromeStore.ts b/src/state/chromeStore.ts index b176137be..197007d73 100644 --- a/src/state/chromeStore.ts +++ b/src/state/chromeStore.ts @@ -6,6 +6,7 @@ import { isBeta } from '../utils/common'; import { gatewayErrorAtom } from './atoms/gatewayErrorAtom'; import { isFeedbackModalOpenAtom } from './atoms/feedbackModalAtom'; import { activeAppAtom } from './atoms/activeAppAtom'; +import { isDebuggerEnabledAtom } from './atoms/debuggerModalatom'; const chromeStore = createStore(); @@ -18,6 +19,7 @@ chromeStore.set(isFeedbackModalOpenAtom, false); // is set in bootstrap chromeStore.set(isPreviewAtom, false); chromeStore.set(activeAppAtom, undefined); +chromeStore.set(isDebuggerEnabledAtom, false); // globally handle subscription to activeModuleAtom chromeStore.sub(activeModuleAtom, () => { From 6b44d6e7b93652fd05cde478b6d19ef505837adb Mon Sep 17 00:00:00 2001 From: Martin Marosi Date: Tue, 23 Jul 2024 12:20:33 +0200 Subject: [PATCH 065/151] Move cross request notifier state to Jotai. --- .../CrossRequestNotifier.cy.tsx | 149 ++++++++++++++++++ .../CrossRequestNotifier.tsx | 6 +- src/redux/action-types.ts | 2 - src/redux/actions.ts | 11 -- src/redux/chromeReducers.ts | 38 +---- src/redux/index.ts | 16 -- src/redux/store.d.ts | 12 +- src/state/atoms/accessRequestsAtom.ts | 32 ++++ src/utils/useAccessRequestNotifier.ts | 62 +++++--- 9 files changed, 232 insertions(+), 96 deletions(-) create mode 100644 cypress/component/UserAccessRequests/CrossRequestNotifier.cy.tsx create mode 100644 src/state/atoms/accessRequestsAtom.ts diff --git a/cypress/component/UserAccessRequests/CrossRequestNotifier.cy.tsx b/cypress/component/UserAccessRequests/CrossRequestNotifier.cy.tsx new file mode 100644 index 000000000..09de265e6 --- /dev/null +++ b/cypress/component/UserAccessRequests/CrossRequestNotifier.cy.tsx @@ -0,0 +1,149 @@ +import React from 'react'; + +import { IntlProvider } from 'react-intl'; +import CrossRequestNotifier from '../../../src/components/CrossRequestNotifier/CrossRequestNotifier'; +import ChromeAuthContext from '../../../src/auth/ChromeAuthContext'; +import { AccessRequest } from '../../../src/state/atoms/accessRequestsAtom'; +import { BrowserRouter } from 'react-router-dom'; + +const Wrapper = () => ( + + + + + + + +); + +describe('', () => { + const sampleAccessRequest: AccessRequest = { + request_id: 'foo', + seen: false, + created: '2021-08-10T14:00:00.000Z', + }; + + it('Should show alert about unread access request', () => { + cy.intercept('GET', '/api/rbac/v1/cross-account-requests/?limit=10&status=pending&order_by=-created', { + statusCode: 200, + body: { + data: [sampleAccessRequest] as AccessRequest[], + meta: { + count: 1, + }, + }, + }); + cy.mount(); + cy.contains('View request').should('exist'); + }); + + it('Should not show alert about read access request', () => { + cy.intercept('GET', '/api/rbac/v1/cross-account-requests/?limit=10&status=pending&order_by=-created', { + statusCode: 200, + body: { + data: [{ ...sampleAccessRequest, seen: false }] as AccessRequest[], + meta: { + count: 1, + }, + }, + }); + cy.mount(); + cy.contains('View request').should('not.exist'); + }); + + it('Should show only one alert even if mulple access reqeusts are available', () => { + cy.intercept('GET', '/api/rbac/v1/cross-account-requests/?limit=10&status=pending&order_by=-created', { + statusCode: 200, + body: { + data: [sampleAccessRequest, sampleAccessRequest] as AccessRequest[], + meta: { + count: 1, + }, + }, + }); + cy.mount(); + cy.contains('View request').should('have.length', 1); + }); + + it('Should close alert after clicking on the clsoe button', () => { + cy.intercept('GET', '/api/rbac/v1/cross-account-requests/?limit=10&status=pending&order_by=-created', { + statusCode: 200, + body: { + data: [sampleAccessRequest] as AccessRequest[], + meta: { + count: 1, + }, + }, + }); + cy.mount(); + cy.contains('View request').should('exist'); + cy.get('.pf-v5-c-alert__action').click(); + cy.contains('View request').should('not.exist'); + }); + + it('Alert should disaapear after 10s', () => { + cy.intercept('GET', '/api/rbac/v1/cross-account-requests/?limit=10&status=pending&order_by=-created', { + statusCode: 200, + body: { + data: [sampleAccessRequest] as AccessRequest[], + meta: { + count: 1, + }, + }, + }); + cy.mount(); + cy.contains('View request').should('exist'); + cy.wait(10001); + cy.contains('View request').should('not.exist'); + }); + + it.only('Should show new alert after polling sends new data.', () => { + let requests = 0; + cy.intercept('GET', '/api/rbac/v1/cross-account-requests/?limit=10&status=pending&order_by=-created', (req) => { + req.reply({ + statusCode: 200, + body: { + data: + requests > 0 + ? [ + { ...sampleAccessRequest, seen: true }, + { ...sampleAccessRequest, seen: false, request_id: 'bar' }, + ] + : [{ ...sampleAccessRequest, seen: true }], + meta: { + count: 1, + }, + }, + }); + requests += 1; + }).as('pollRequest'); + cy.mount(); + cy.wait('@pollRequest'); + cy.contains('View request').should('not.exist'); + // wait for the polling to fetch new data + cy.wait('@pollRequest', { timeout: 50000 }); + cy.contains('View request').should('exist'); + }); +}); diff --git a/src/components/CrossRequestNotifier/CrossRequestNotifier.tsx b/src/components/CrossRequestNotifier/CrossRequestNotifier.tsx index 34f5a3a21..ec670e41f 100644 --- a/src/components/CrossRequestNotifier/CrossRequestNotifier.tsx +++ b/src/components/CrossRequestNotifier/CrossRequestNotifier.tsx @@ -12,8 +12,8 @@ const ACCOUNT_TIMEOUT_ID = 'account_timeout'; const CrossRequestNotifier = () => { const [, forceRender] = useReducer((state) => state + 1, 0); - const [{ data }, markRead] = useAccessRequestNotifier(); - const crossAccountNotifications = data.filter(({ seen }) => !seen); + const [{ accessRequestData }, markRead] = useAccessRequestNotifier(); + const crossAccountNotifications = accessRequestData.filter(({ seen }) => !seen); const intl = useIntl(); @@ -37,7 +37,7 @@ const CrossRequestNotifier = () => { autoDismiss: false, }); - const DescriptionComponent = ({ id, markRead }: { id: string; markRead: (id: string) => void }) => ( + const DescriptionComponent = ({ id, markRead }: { id: string | number; markRead: (id: string | number) => void }) => ( markRead(id)}> {intl.formatMessage(messages.viewRequest)} diff --git a/src/redux/action-types.ts b/src/redux/action-types.ts index a6e27f5c2..f316d96d7 100644 --- a/src/redux/action-types.ts +++ b/src/redux/action-types.ts @@ -14,8 +14,6 @@ export const GLOBAL_FILTER_REMOVE = '@@chrome/global-filter-remove'; export const LOAD_NAVIGATION_LANDING_PAGE = '@@chrome/load-navigation-landing-page'; export const LOAD_LEFT_NAVIGATION_SEGMENT = '@@chrome/load-navigation-segment'; -export const UPDATE_ACCESS_REQUESTS_NOTIFICATIONS = '@@chrome/update-access-requests-notifications'; -export const MARK_REQUEST_NOTIFICATION_SEEN = '@@chrome/mark-request-notification-seen'; export const UPDATE_DOCUMENT_TITLE_REDUCER = '@@chrome/update-document-title'; export const MARK_ACTIVE_PRODUCT = '@@chrome/mark-active-product'; diff --git a/src/redux/actions.ts b/src/redux/actions.ts index 08e4d3ede..3dfc6fd13 100644 --- a/src/redux/actions.ts +++ b/src/redux/actions.ts @@ -3,7 +3,6 @@ import { getAllSIDs, getAllTags, getAllWorkloads } from '../components/GlobalFil import type { TagFilterOptions, TagPagination } from '../components/GlobalFilter/tagsApi'; import type { ChromeUser } from '@redhat-cloud-services/types'; import type { FlagTagsFilter, NavItem, Navigation } from '../@types/types'; -import type { AccessRequest } from './store'; import type { QuickStart } from '@patternfly/quickstarts'; export function userLogIn(user: ChromeUser | boolean) { @@ -98,16 +97,6 @@ export const onToggle = () => ({ type: 'NAVIGATION_TOGGLE', }); -export const updateAccessRequestsNotifications = (payload: { count: number; data: AccessRequest[] }) => ({ - type: actionTypes.UPDATE_ACCESS_REQUESTS_NOTIFICATIONS, - payload, -}); - -export const markAccessRequestNotification = (payload: string | number) => ({ - type: actionTypes.MARK_REQUEST_NOTIFICATION_SEEN, - payload, -}); - export const populateQuickstartsCatalog = (app: string, quickstarts: QuickStart[]) => ({ type: actionTypes.POPULATE_QUICKSTARTS_CATALOG, payload: { diff --git a/src/redux/chromeReducers.ts b/src/redux/chromeReducers.ts index bc144dec6..83abdb790 100644 --- a/src/redux/chromeReducers.ts +++ b/src/redux/chromeReducers.ts @@ -1,9 +1,8 @@ import { QuickStart } from '@patternfly/quickstarts'; import { ChromeUser } from '@redhat-cloud-services/types'; -import { REQUESTS_COUNT, REQUESTS_DATA } from '../utils/consts'; import { NavItem, Navigation } from '../@types/types'; import { ITLess, highlightItems, levelArray } from '../utils/common'; -import { AccessRequest, ChromeState } from './store'; +import { ChromeState } from './store'; export function loginReducer(state: ChromeState, { payload }: { payload: ChromeUser }): ChromeState { const missingIDP = ITLess() && !Object.prototype.hasOwnProperty.call(payload?.identity, 'idp'); @@ -73,41 +72,6 @@ export function loadNavigationSegmentReducer( return state; } -export function accessRequestsNotificationsReducer( - state: ChromeState, - { payload: { count, data } }: { payload: { count: number; data: AccessRequest[] } } -): ChromeState { - const newData = data.map(({ request_id, created, seen }) => ({ - request_id, - created, - seen: seen === true || !!state.accessRequests.data.find((item) => request_id === item.request_id)?.seen || false, - })); - localStorage.setItem(REQUESTS_COUNT, newData.length.toString()); - localStorage.setItem(REQUESTS_DATA, JSON.stringify(newData)); - return { - ...state, - accessRequests: { - ...state.accessRequests, - count, - hasUnseen: newData.length > 0, - data: newData, - }, - }; -} - -export function markAccessRequestRequestReducer(state: ChromeState, { payload }: { payload: string }): ChromeState { - const newData = state.accessRequests.data.map((item) => (item.request_id === payload ? { ...item, seen: true } : item)); - localStorage.setItem(REQUESTS_DATA, JSON.stringify(newData)); - return { - ...state, - accessRequests: { - ...state.accessRequests, - hasUnseen: newData.length > 0, - data: newData, - }, - }; -} - export function populateQuickstartsReducer( state: ChromeState, { payload: { app, quickstarts } }: { payload: { app: string; quickstarts: QuickStart[] } } diff --git a/src/redux/index.ts b/src/redux/index.ts index 7bd2285c3..0c5b8e5dc 100644 --- a/src/redux/index.ts +++ b/src/redux/index.ts @@ -1,7 +1,6 @@ import { applyReducerHash } from '@redhat-cloud-services/frontend-components-utilities/ReducerRegistry'; import { - accessRequestsNotificationsReducer, addQuickstartstoApp, clearQuickstartsReducer, disableQuickstartsReducer, @@ -9,7 +8,6 @@ import { loadNavigationLandingPageReducer, loadNavigationSegmentReducer, loginReducer, - markAccessRequestRequestReducer, markActiveProduct, onPageAction, onPageObjectId, @@ -44,9 +42,7 @@ import { LOAD_LEFT_NAVIGATION_SEGMENT, LOAD_NAVIGATION_LANDING_PAGE, MARK_ACTIVE_PRODUCT, - MARK_REQUEST_NOTIFICATION_SEEN, POPULATE_QUICKSTARTS_CATALOG, - UPDATE_ACCESS_REQUESTS_NOTIFICATIONS, UPDATE_DOCUMENT_TITLE_REDUCER, USER_LOGIN, } from './action-types'; @@ -59,8 +55,6 @@ const reducers = { [CHROME_PAGE_OBJECT]: onPageObjectId, [LOAD_NAVIGATION_LANDING_PAGE]: loadNavigationLandingPageReducer, [LOAD_LEFT_NAVIGATION_SEGMENT]: loadNavigationSegmentReducer, - [UPDATE_ACCESS_REQUESTS_NOTIFICATIONS]: accessRequestsNotificationsReducer, - [MARK_REQUEST_NOTIFICATION_SEEN]: markAccessRequestRequestReducer, [POPULATE_QUICKSTARTS_CATALOG]: populateQuickstartsReducer, [ADD_QUICKSTARTS_TO_APP]: addQuickstartstoApp, [DISABLE_QUICKSTARTS]: disableQuickstartsReducer, @@ -85,11 +79,6 @@ const globalFilter = { export const chromeInitialState: ReduxState = { chrome: { navigation: {}, - accessRequests: { - hasUnseen: false, - count: 0, - data: [], - }, quickstarts: { quickstarts: {}, }, @@ -105,11 +94,6 @@ export default function (): { chrome: ( state = { navigation: {}, - accessRequests: { - count: 0, - data: [], - hasUnseen: false, - }, quickstarts: { quickstarts: {}, }, diff --git a/src/redux/store.d.ts b/src/redux/store.d.ts index 96d816ee2..50df23a64 100644 --- a/src/redux/store.d.ts +++ b/src/redux/store.d.ts @@ -7,19 +7,17 @@ export type InternalNavigation = { landingPage?: NavItem[]; }; -export type AccessRequest = { request_id: string; created: string; seen: boolean }; - export type ChromeState = { activeProduct?: string; missingIDP?: boolean; pageAction?: string; pageObjectId?: string; navigation: InternalNavigation; - accessRequests: { - count: number; - data: AccessRequest[]; - hasUnseen: boolean; - }; + // accessRequests: { + // count: number; + // data: AccessRequest[]; + // hasUnseen: boolean; + // }; initialHash?: string; quickstarts: { disabled?: boolean; diff --git a/src/state/atoms/accessRequestsAtom.ts b/src/state/atoms/accessRequestsAtom.ts new file mode 100644 index 000000000..7d03d1fb1 --- /dev/null +++ b/src/state/atoms/accessRequestsAtom.ts @@ -0,0 +1,32 @@ +import { atom } from 'jotai'; +import { REQUESTS_COUNT, REQUESTS_DATA } from '../../utils/consts'; + +export type AccessRequest = { request_id: string | number; created: string; seen: boolean }; + +export const accessReqeustsCountAtom = atom(0); +export const hasUnseenAccessRequestsAtom = atom(false); +export const accessRequestsDataAtom = atom([]); + +export const setAccessRequestsDataAtom = atom(null, (get, set, { count, data }: { count: number; data: AccessRequest[] }) => { + const accessRequestData = get(accessRequestsDataAtom); + const newData = data.map(({ request_id, created, seen }) => ({ + request_id, + created, + seen: seen === true || !!accessRequestData.find((item) => request_id === item.request_id)?.seen || false, + })); + localStorage.setItem(REQUESTS_COUNT, newData.length.toString()); + localStorage.setItem(REQUESTS_DATA, JSON.stringify(newData)); + + set(accessReqeustsCountAtom, count); + set(hasUnseenAccessRequestsAtom, newData.length > 0); + set(accessRequestsDataAtom, newData); +}); + +export const markAccessRequestsRequestAtom = atom(null, (get, set, payload: string | number) => { + const accessRequestData = get(accessRequestsDataAtom); + + const newData = accessRequestData.map((item) => (item.request_id === payload ? { ...item, seen: true } : item)); + localStorage.setItem(REQUESTS_DATA, JSON.stringify(newData)); + set(hasUnseenAccessRequestsAtom, newData.length > 0); + set(accessRequestsDataAtom, newData); +}); diff --git a/src/utils/useAccessRequestNotifier.ts b/src/utils/useAccessRequestNotifier.ts index d26020630..5284858eb 100644 --- a/src/utils/useAccessRequestNotifier.ts +++ b/src/utils/useAccessRequestNotifier.ts @@ -1,26 +1,40 @@ import axios from 'axios'; -import { useContext, useEffect, useRef } from 'react'; -import { batch, useDispatch, useSelector } from 'react-redux'; +import { useContext, useEffect, useMemo, useRef } from 'react'; import { REQUESTS_COUNT, REQUESTS_DATA } from './consts'; -import { markAccessRequestNotification, updateAccessRequestsNotifications } from '../redux/actions'; -import { ReduxState } from '../redux/store'; import ChromeAuthContext from '../auth/ChromeAuthContext'; +import { useAtomValue, useSetAtom } from 'jotai'; +import { + AccessRequest, + accessReqeustsCountAtom, + accessRequestsDataAtom, + hasUnseenAccessRequestsAtom, + markAccessRequestsRequestAtom, + setAccessRequestsDataAtom, +} from '../state/atoms/accessRequestsAtom'; -const useAccessRequestNotifier = (): [ReduxState['chrome']['accessRequests'], (id: string | number) => void] => { +const useAccessRequestNotifier = (): [ + { + accessRequestData: AccessRequest[]; + hasUnseen: boolean; + accessRequestCount: number; + }, + (id: string | number) => void +] => { const { user } = useContext(ChromeAuthContext); const isMounted = useRef(false); - const state = useSelector(({ chrome: { accessRequests } }: ReduxState) => accessRequests); - const dispatch = useDispatch(); + const accessRequestData = useAtomValue(accessRequestsDataAtom); + const hasUnseen = useAtomValue(hasUnseenAccessRequestsAtom); + const accessRequestCount = useAtomValue(accessReqeustsCountAtom); + const setAccessRequestsData = useSetAtom(setAccessRequestsDataAtom); + const markAccessRequestsRequest = useSetAtom(markAccessRequestsRequestAtom); const markRead = (id: string | number) => { if (id === 'mark-all') { - batch(() => { - state.data.forEach(({ request_id }) => { - dispatch(markAccessRequestNotification(request_id)); - }); + accessRequestData.forEach(({ request_id }) => { + markAccessRequestsRequest(request_id); }); } else { - dispatch(markAccessRequestNotification(id)); + markAccessRequestsRequest(id); } }; @@ -33,7 +47,7 @@ const useAccessRequestNotifier = (): [ReduxState['chrome']['accessRequests'], (i }, }) => { if (isMounted.current) { - dispatch(updateAccessRequestsNotifications({ count, data })); + setAccessRequestsData({ count, data }); } } ); @@ -41,12 +55,11 @@ const useAccessRequestNotifier = (): [ReduxState['chrome']['accessRequests'], (i useEffect(() => { isMounted.current = true; - dispatch( - updateAccessRequestsNotifications({ - count: parseInt(localStorage.getItem(REQUESTS_COUNT) || '0'), - data: JSON.parse(localStorage.getItem(REQUESTS_DATA) || '[]'), - }) - ); + setAccessRequestsData({ + count: parseInt(localStorage.getItem(REQUESTS_COUNT) || '0'), + data: JSON.parse(localStorage.getItem(REQUESTS_DATA) || '[]'), + }); + return () => { isMounted.current = false; }; @@ -57,7 +70,7 @@ const useAccessRequestNotifier = (): [ReduxState['chrome']['accessRequests'], (i * register notifier only for org admin */ let interval: NodeJS.Timer | undefined = undefined; - if (user?.identity?.user?.is_org_admin && interval) { + if (user?.identity?.user?.is_org_admin && !interval) { try { notifier(); interval = setInterval(notifier, 20000); @@ -75,6 +88,15 @@ const useAccessRequestNotifier = (): [ReduxState['chrome']['accessRequests'], (i }; }, [user]); + const state = useMemo( + () => ({ + accessRequestData, + hasUnseen, + accessRequestCount, + }), + [accessRequestData, hasUnseen, accessRequestCount] + ); + return [state, markRead]; }; From 5c3aa592eec406fbfe7c6be61e5bec4065f1449c Mon Sep 17 00:00:00 2001 From: Martin Marosi Date: Tue, 23 Jul 2024 12:44:11 +0200 Subject: [PATCH 066/151] Gate WS onclose retry connection. --- src/hooks/useChromeServiceEvents.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/hooks/useChromeServiceEvents.ts b/src/hooks/useChromeServiceEvents.ts index 21e60db05..2bf11dc9a 100644 --- a/src/hooks/useChromeServiceEvents.ts +++ b/src/hooks/useChromeServiceEvents.ts @@ -91,7 +91,11 @@ const useChromeServiceEvents = (): AddChromeWsEventListener => { // renew connection on close // pod was restarted or network issue setTimeout(() => { - createConnection(); + if (retries.current < RETRY_LIMIT) { + createConnection(); + } + + retries.current += 1; }, 2000); }; From 90ecd1e1cc7e1be8eba6c8efbd472e936951a8ee Mon Sep 17 00:00:00 2001 From: Austin Pinkerton Date: Tue, 23 Jul 2024 15:28:07 -0400 Subject: [PATCH 067/151] chore: add OUIAs for main links in all-services dropdown --- .../AllServicesDropdown/PlatformServicesLinks.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/AllServicesDropdown/PlatformServicesLinks.tsx b/src/components/AllServicesDropdown/PlatformServicesLinks.tsx index bfb019116..53d6c6a1c 100644 --- a/src/components/AllServicesDropdown/PlatformServicesLinks.tsx +++ b/src/components/AllServicesDropdown/PlatformServicesLinks.tsx @@ -4,13 +4,13 @@ import ChromeLink from '../ChromeLink'; const PlatformServiceslinks = () => { return ( <> - + Red Hat Ansible Platform - + Red Hat Enterprise Linux - + Red Hat OpenShift From 2aa9a84bddb6cc8f291a16216868673d1ad20531 Mon Sep 17 00:00:00 2001 From: Bryan Florkiewicz Date: Tue, 23 Jul 2024 20:29:00 -0400 Subject: [PATCH 068/151] Throttle search feedback and add test coverage --- src/components/Search/SearchFeedback.test.tsx | 113 ++++++++++++++++++ src/components/Search/SearchFeedback.tsx | 6 +- 2 files changed, 117 insertions(+), 2 deletions(-) create mode 100644 src/components/Search/SearchFeedback.test.tsx diff --git a/src/components/Search/SearchFeedback.test.tsx b/src/components/Search/SearchFeedback.test.tsx new file mode 100644 index 000000000..8256d0cd4 --- /dev/null +++ b/src/components/Search/SearchFeedback.test.tsx @@ -0,0 +1,113 @@ +import React from 'react'; +import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import SearchFeedback from './SearchFeedback'; +import { SearchItem } from './SearchTypes'; +import { useSegment } from '../../analytics/useSegment'; + +jest.mock('../../analytics/useSegment'); + +describe('SearchFeedback', () => { + let results: SearchItem[]; + let query: string; + + beforeEach(() => { + results = [ + { + title: 'foo', + bundleTitle: 'foobundle', + description: 'Foo service', + pathname: '/foo/bar', + }, + ]; + query = 'foo'; + (useSegment as jest.Mock).mockImplementation(() => ({ + ready: false, + analytics: undefined, + })); + }); + + it('should render correctly', () => { + render(); + expect(useSegment).toHaveBeenCalled(); + expect(screen.getAllByRole('menuitem').length).toBe(2); + }); + + it('should handle analytics not ready on click', async () => { + render(); + const thumbsUpButton = screen.getAllByRole('menuitem')[0]; + await waitFor(() => { + fireEvent.click(thumbsUpButton); + }); + expect(useSegment).toHaveBeenCalled(); + }); + + it('should handle analytics object undefined on click', async () => { + (useSegment as jest.Mock).mockImplementation(() => ({ + ready: true, + analytics: undefined, + })); + render(); + const thumbsUpButton = screen.getAllByRole('menuitem')[0]; + await waitFor(() => { + fireEvent.click(thumbsUpButton); + }); + expect(useSegment).toHaveBeenCalled(); + }); + + it('should call trackFeedback() on click when analytics ready - positive feedback', async () => { + const track = jest.fn(); + (useSegment as jest.Mock).mockImplementation(() => ({ + ready: true, + analytics: { + track, + }, + })); + render(); + const thumbsUpButton = screen.getAllByRole('menuitem')[0]; + await waitFor(() => { + fireEvent.click(thumbsUpButton); + }); + expect(useSegment).toHaveBeenCalled(); + expect(track).toHaveBeenCalledWith('chrome.search-query-feedback-positive', { query, results }); + }); + + it('should call trackFeedback() on click when analytics ready - negative feedback', async () => { + const track = jest.fn(); + (useSegment as jest.Mock).mockImplementation(() => ({ + ready: true, + analytics: { + track, + }, + })); + render(); + const thumbsDownButton = screen.getAllByRole('menuitem')[1]; + await waitFor(() => { + fireEvent.click(thumbsDownButton); + }); + expect(useSegment).toHaveBeenCalled(); + expect(track).toHaveBeenCalledWith('chrome.search-query-feedback-negative', { query, results }); + }); + + it('should throttle trackFeedback() calls for many consecutive clicks', async () => { + const track = jest.fn(); + (useSegment as jest.Mock).mockImplementation(() => ({ + ready: true, + analytics: { + track, + }, + })); + render(); + const thumbsUpButton = screen.getAllByRole('menuitem')[0]; + await Promise.all([ + waitFor(() => { + fireEvent.click(thumbsUpButton); + }), + waitFor(() => { + fireEvent.click(thumbsUpButton); + }), + ]); + expect(useSegment).toHaveBeenCalled(); + expect(track).toHaveBeenCalledWith('chrome.search-query-feedback-positive', { query, results }); + expect(track.mock.calls.length).toEqual(1); + }); +}); diff --git a/src/components/Search/SearchFeedback.tsx b/src/components/Search/SearchFeedback.tsx index 59e2c74b1..f86006e0e 100644 --- a/src/components/Search/SearchFeedback.tsx +++ b/src/components/Search/SearchFeedback.tsx @@ -1,5 +1,6 @@ import React from 'react'; import './SearchFeedback.scss'; +import throttle from 'lodash/throttle'; import { Icon } from '@patternfly/react-core/dist/dynamic/components/Icon'; import OutlinedThumbsUpIcon from '@patternfly/react-icons/dist/dynamic/icons/outlined-thumbs-up-icon'; @@ -19,14 +20,15 @@ const SearchFeedback = ({ query, results }: SearchFeedbackProps) => { const trackFeedback = (type: string | SegmentEvent) => { ready && analytics && analytics.track(type, { query, results }); }; + const throttledTrackFeedback = throttle(trackFeedback, 5000); return ( - trackFeedback('chrome.search-query-feedback-positive')}> + throttledTrackFeedback('chrome.search-query-feedback-positive')}> - trackFeedback('chrome.search-query-feedback-negative')}> + throttledTrackFeedback('chrome.search-query-feedback-negative')}> From 5fd849ed68e0ded33f859a4f55067775f6955ac3 Mon Sep 17 00:00:00 2001 From: Janet Cobb Date: Thu, 25 Jul 2024 10:17:22 -0400 Subject: [PATCH 069/151] Reduce amount of search highlighting (#2907) * Rewrite string marking code The previous code seems to start marking the wrong positions after the first mark * Put search debug logging in one place * Only show matches with the lowest distance * Correctly handle matches starting at index 0 * Make string highlighting case insensitive * Handle preconditions in applyMarks * Remove logging in asciiLowercase * Fix lint error --- src/utils/levenshtein-search.ts | 84 +++++++++++++--------------- src/utils/localSearch.ts | 98 ++++++++++++++++++++++----------- 2 files changed, 104 insertions(+), 78 deletions(-) diff --git a/src/utils/levenshtein-search.ts b/src/utils/levenshtein-search.ts index 61c9ce5bf..88693534a 100644 --- a/src/utils/levenshtein-search.ts +++ b/src/utils/levenshtein-search.ts @@ -30,6 +30,14 @@ function makeChar2needleIdx(needle: string, maxDist: number) { return res; } +const debugFlag = false; + +export type Match = { + start: number; + end: number; + dist: number; +}; + export function* fuzzySearch(needle: string, haystack: string, maxDist: number) { if (needle.length > haystack.length + maxDist) return; @@ -46,8 +54,33 @@ export function* fuzzySearch(needle: string, haystack: string, maxDist: number) } else if (ngramLen >= 10) { yield* fuzzySearchNgrams(needle, haystack, maxDist); } else { - yield* fuzzySearchCandidates(needle, haystack, maxDist); + const generator = fuzzySearchCandidates(needle, haystack, maxDist); + + if (debugFlag) { + for (const match of generator) { + console.log('search match', match); + yield match; + } + } else { + yield* generator; + } + } +} + +export function minimumDistanceMatches(matches: Match[]): Match[] { + let minDist: number | null = null; + let out: Match[] = []; + + for (const match of matches) { + if (minDist === null || match.dist < minDist) { + minDist = match.dist; + out = [match]; + } else if (match.dist === minDist) { + out.push(match); + } } + + return out; } function _expand(needle: string, haystack: string, maxDist: number) { @@ -173,15 +206,12 @@ function* fuzzySearchNgrams(needle: string, haystack: string, maxDist: number) { } type BoundedMetadata = { - start: number; - end: number; + startIdx: number; + needleIdx: number; dist: number; - startIdx?: number; - needleIdx?: number; }; function* fuzzySearchCandidates(needle: string, haystack: string, maxDist: number) { - const debugFlag = false; if (debugFlag) console.log(`fuzzySearchCandidates(${needle}, ${haystack}, ${maxDist})`); // prepare some often used things in advance @@ -190,8 +220,8 @@ function* fuzzySearchCandidates(needle: string, haystack: string, maxDist: numbe if (needleLen > haystackLen + maxDist) return; const char2needleIdx = makeChar2needleIdx(needle, maxDist); - let prevCandidates: Partial[] = []; // candidates from the last iteration - let candidates: Partial[] = []; // new candidates from the current iteration + let prevCandidates: BoundedMetadata[] = []; // candidates from the last iteration + let candidates: BoundedMetadata[] = []; // new candidates from the current iteration // iterate over the chars in the haystack, updating the candidates for each for (let i = 0; i < haystack.length; i++) { @@ -203,15 +233,6 @@ function* fuzzySearchCandidates(needle: string, haystack: string, maxDist: numbe const needleIdx = char2needleIdx[haystackChar]; if (needleIdx !== undefined) { if (needleIdx + 1 === needleLen) { - if (debugFlag) { - console.log( - `yield ${{ - start: i, - end: i + 1, - dist: needleIdx, - }}` - ); - } yield { start: i, end: i + 1, @@ -231,15 +252,6 @@ function* fuzzySearchCandidates(needle: string, haystack: string, maxDist: numbe if (candidate.needleIdx && needle[candidate.needleIdx] === haystackChar) { // if reached the end of the needle, return a match if (candidate.needleIdx + 1 === needleLen) { - if (debugFlag) { - console.log( - `yield ${{ - start: candidate.startIdx, - end: i + 1, - dist: candidate.dist, - }}` - ); - } yield { start: candidate.startIdx, end: i + 1, @@ -264,15 +276,6 @@ function* fuzzySearchCandidates(needle: string, haystack: string, maxDist: numbe for (let nSkipped = 1; nSkipped <= maxDist - (candidate.dist ?? 0); nSkipped++) { if ((candidate.needleIdx ?? 0) + nSkipped === needleLen) { - if (debugFlag) { - console.log( - `yield ${{ - start: candidate.startIdx, - end: i + 1, - dist: (candidate.dist ?? 0) + nSkipped, - }}` - ); - } yield { start: candidate.startIdx, end: i + 1, @@ -281,15 +284,6 @@ function* fuzzySearchCandidates(needle: string, haystack: string, maxDist: numbe break; } else if (candidate.needleIdx && needle[candidate.needleIdx + nSkipped] === haystackChar) { if (candidate.needleIdx + nSkipped + 1 === needleLen) { - if (debugFlag) { - console.log( - `yield ${{ - start: candidate.startIdx, - end: i + 1, - dist: (candidate.dist ?? 0) + nSkipped, - }}` - ); - } yield { start: candidate.startIdx, end: i + 1, @@ -316,7 +310,7 @@ function* fuzzySearchCandidates(needle: string, haystack: string, maxDist: numbe } } - if (debugFlag) console.log(candidates); + if (debugFlag) console.log('Candidates: ', candidates); } for (const candidate of candidates) { diff --git a/src/utils/localSearch.ts b/src/utils/localSearch.ts index 92782a68c..b7313b9b9 100644 --- a/src/utils/localSearch.ts +++ b/src/utils/localSearch.ts @@ -1,8 +1,8 @@ import { search } from '@orama/orama'; -import { fuzzySearch } from './levenshtein-search'; +import { ReleaseEnv } from '../@types/types.d'; import { SearchPermissions, SearchPermissionsCache } from '../state/atoms/localSearchAtom'; import { evaluateVisibility } from './isNavItemVisible'; -import { ReleaseEnv } from '../@types/types.d'; +import { Match as FuzzySearchMatch, fuzzySearch, minimumDistanceMatches } from './levenshtein-search'; type HighlightCategories = 'title' | 'description'; @@ -27,48 +27,80 @@ const resultCache: { [term: string]: ResultItem[]; } = {}; -const START_MARK_LENGTH = 6; -const END_MARK_LENGTH = START_MARK_LENGTH + 1; -const OFFSET_BASE = START_MARK_LENGTH + END_MARK_LENGTH; +// merge overlapping marks into smaller sets +// example: start: 1, end: 5, start: 3, end: 7 => start: 1, end: 7 +function joinMatchPositions(marks: FuzzySearchMatch[]) { + return marks + .toSorted((a, b) => a.start! - b.start!) + .reduce<{ start: number; end: number }[]>((acc, { start, end }) => { + const bounded = acc.findIndex((o) => { + return (o.start >= start && o.start <= end) || (start >= o.start && start <= o.end); + }); + + if (bounded >= 0) { + acc[bounded] = { start: Math.min(start, acc[bounded].start), end: Math.max(end, acc[bounded].end) }; + } else { + acc.push({ start, end }); + } + + return acc; + }, []); +} + +function applyMarks(text: string, marks: { start: number; end: number }[]) { + const sortedMarks = marks.toSorted((a, b) => a.start - b.start); + + let out = ''; + let prevEnd = 0; + + for (const mark of sortedMarks) { + if (mark.end < prevEnd) { + throw new Error(`Invalid mark overlap: { start: ${mark.start}, end: ${mark.end} } overlaps with mark ending at ${prevEnd}`); + } + + out += text.substring(prevEnd, mark.start); + out += `${text.substring(mark.start, mark.end)}`; + + prevEnd = mark.end; + } + + out += text.substring(prevEnd, text.length); -function markText(text: string, start: number, end: number, offset: number) { - const markStart = OFFSET_BASE * offset + start + offset * 2 - 1; - const markEnd = OFFSET_BASE * offset + end + offset * 2; - return `${text.substring(0, markStart)}${text.substring(markStart, markEnd)}${text.substring(markEnd)}`; + return out; +} + +const LOWERCASE_A = 'a'.charCodeAt(0) as number; +const UPPERCASE_A = 'A'.charCodeAt(0) as number; +const UPPERCASE_Z = 'Z'.charCodeAt(0) as number; + +// ASCII lowercase, which preserves length (unlink toLowerCase). +function asciiLowercase(value: string) { + const out = []; + + for (let i = 0; i < value.length; ++i) { + const codeUnit = value.charCodeAt(i) as number; + const adjusted = codeUnit >= UPPERCASE_A && codeUnit <= UPPERCASE_Z ? codeUnit - UPPERCASE_A + LOWERCASE_A : codeUnit; + + out.push(adjusted); + } + + return String.fromCharCode(...out); } function highlightText(term: string, text: string, category: HighlightCategories) { const key = `${term}-${text}`; + // check cache if (matchCache[category]?.[key]) { return matchCache[category][key]; } - let internalText = text; - // generate fuzzy matches - const res = fuzzySearch(term, internalText, 2); - const marks = [...res].sort((a, b) => a.start! - b.start!); - // merge overlapping marks into smaller sets - // example: start: 1, end: 5, start: 3, end: 7 => start: 1, end: 7 - const merged = marks.reduce<{ start: number; end: number }[]>((acc, { start, end }) => { - if (!start || !end) return acc; - const bounded = acc.findIndex((o) => { - return (o.start >= start && o.start <= end) || (start >= o.start && start <= o.end); - }); - if (bounded >= 0) { - acc[bounded] = { start: Math.min(start, acc[bounded].start), end: Math.max(end, acc[bounded].end) }; - } else { - acc.push({ start, end }); - } - return acc; - }, []); - // mark text from reduced match set - merged.forEach(({ start, end }, index) => { - internalText = markText(internalText, start!, end, index); - }); + + const mergedMarks = joinMatchPositions(minimumDistanceMatches([...fuzzySearch(asciiLowercase(term), asciiLowercase(text), 2)])); + const markedText = applyMarks(text, mergedMarks); // cache result - matchCache[category][key] = internalText; - return internalText; + matchCache[category][key] = markedText; + return markedText; } async function checkResultPermissions(id: string, env: ReleaseEnv = ReleaseEnv.STABLE) { From bf9966d6bde364a3f024edcfe63b68e2e81e0fa6 Mon Sep 17 00:00:00 2001 From: Janet Cobb Date: Mon, 29 Jul 2024 03:29:05 -0400 Subject: [PATCH 070/151] Decrease spacing between search item title/description (#2908) * Decrease spacing between search item title/description This should also fix the bug where the search title starts spanning three lines (name, pipe, then bundle), which appears to be caused by some Javascript making the TextContent styling reassert itself. * Make search title classname prop optional --------- Co-authored-by: Martin Marosi --- src/components/Search/SearchInput.tsx | 2 +- src/components/Search/SearchTitle.scss | 4 ++++ src/components/Search/SearchTitle.tsx | 25 ++++++++++++++----------- 3 files changed, 19 insertions(+), 12 deletions(-) create mode 100644 src/components/Search/SearchTitle.scss diff --git a/src/components/Search/SearchInput.tsx b/src/components/Search/SearchInput.tsx index f92c31644..15da18922 100644 --- a/src/components/Search/SearchInput.tsx +++ b/src/components/Search/SearchInput.tsx @@ -225,7 +225,7 @@ const SearchInput = ({ onStateChange }: SearchInputListener) => { className="pf-v5-u-mb-xs" component={(props) => } > - + ))} diff --git a/src/components/Search/SearchTitle.scss b/src/components/Search/SearchTitle.scss new file mode 100644 index 000000000..85031e25c --- /dev/null +++ b/src/components/Search/SearchTitle.scss @@ -0,0 +1,4 @@ +.chr-search-title-content small { + // Value stolen from Patternfly TextContent, but the appropriate variable isn't available here. + font-size: 0.875rem; +} diff --git a/src/components/Search/SearchTitle.tsx b/src/components/Search/SearchTitle.tsx index 7d67965df..810a56987 100644 --- a/src/components/Search/SearchTitle.tsx +++ b/src/components/Search/SearchTitle.tsx @@ -1,18 +1,21 @@ import React from 'react'; -import { Text, TextContent } from '@patternfly/react-core/dist/dynamic/components/Text'; +import './SearchTitle.scss'; -const SearchTitle = ({ title, bundleTitle }: { title: string; bundleTitle: string }) => { +const SearchTitle = ({ title, bundleTitle, className = '' }: { title: string; bundleTitle: string; className?: string }) => { const showBundleTitle = bundleTitle.replace(/\s/g, '').length > 0; return ( - - - {showBundleTitle && ( - - | - - )} - {showBundleTitle && } - +
+ + + + {showBundleTitle && ( + <> + | + + + )} + +
); }; From 1471b6e60599ddeeedefbb26e525254d730fc22c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Maro=C5=A1i?= Date: Mon, 29 Jul 2024 09:39:41 +0200 Subject: [PATCH 071/151] Migrate chrome navigation state to Jotai. (#2906) --- src/components/AppFilter/useAppFilter.ts | 5 +- src/components/Navigation/DynamicNav.tsx | 43 ++- src/hooks/useBreadcrumbsLinks.ts | 5 +- src/redux/action-types.ts | 3 - src/redux/actions.ts | 17 +- src/redux/chromeReducers.test.js | 92 ----- src/redux/chromeReducers.ts | 49 +-- src/redux/index.ts | 8 - src/redux/store.d.ts | 14 +- src/state/atoms/navigationAtom.test.ts | 72 ++++ src/state/atoms/navigationAtom.ts | 46 +++ src/utils/useNavigation.test.js | 424 ++++++----------------- src/utils/useNavigation.ts | 27 +- 13 files changed, 264 insertions(+), 541 deletions(-) create mode 100644 src/state/atoms/navigationAtom.test.ts create mode 100644 src/state/atoms/navigationAtom.ts diff --git a/src/components/AppFilter/useAppFilter.ts b/src/components/AppFilter/useAppFilter.ts index 8bf9846ad..6e8d9a002 100644 --- a/src/components/AppFilter/useAppFilter.ts +++ b/src/components/AppFilter/useAppFilter.ts @@ -1,13 +1,12 @@ import axios from 'axios'; import { useEffect, useState } from 'react'; -import { useSelector } from 'react-redux'; import { BundleNavigation, ChromeModule, NavItem } from '../../@types/types'; -import { ReduxState } from '../../redux/store'; import { getChromeStaticPathname } from '../../utils/common'; import { evaluateVisibility } from '../../utils/isNavItemVisible'; import { useAtomValue } from 'jotai'; import { chromeModulesAtom } from '../../state/atoms/chromeModuleAtom'; import { isPreviewAtom } from '../../state/atoms/releaseAtom'; +import { navigationAtom } from '../../state/atoms/navigationAtom'; const LOCAL_PREVIEW = localStorage.getItem('chrome:local-preview') === 'true'; @@ -101,7 +100,7 @@ const useAppFilter = () => { }, }, }); - const existingSchemas = useSelector(({ chrome: { navigation } }: ReduxState) => navigation); + const existingSchemas = useAtomValue(navigationAtom); const modules = useAtomValue(chromeModulesAtom); const handleBundleData = async ({ data: { id, navItems, title } }: { data: BundleNavigation }) => { diff --git a/src/components/Navigation/DynamicNav.tsx b/src/components/Navigation/DynamicNav.tsx index f9c3862b3..aa67c6817 100644 --- a/src/components/Navigation/DynamicNav.tsx +++ b/src/components/Navigation/DynamicNav.tsx @@ -3,12 +3,11 @@ import { useLoadModule } from '@scalprum/react-core'; import { Skeleton, SkeletonSize } from '@redhat-cloud-services/frontend-components/Skeleton'; import { NavItem } from '@patternfly/react-core/dist/dynamic/components/Nav'; import { useLocation } from 'react-router-dom'; -import { useDispatch, useSelector } from 'react-redux'; import isEqual from 'lodash/isEqual'; import ChromeNavItem from './ChromeNavItem'; -import { loadLeftNavSegment } from '../../redux/actions'; -import { ReduxState } from '../../redux/store'; import { DynamicNavProps, NavItem as NavItemType, Navigation } from '../../@types/types'; +import { useSetAtom } from 'jotai'; +import { getDynamicSegmentItemsAtom, getNavigationSegmentAtom, setNavigationSegmentAtom } from '../../state/atoms/navigationAtom'; const toArray = (value: NavItemType | NavItemType[]) => (Array.isArray(value) ? value : [value]); const mergeArrays = (orig: any[], index: number, value: any[]) => [...orig.slice(0, index), ...toArray(value), ...orig.slice(index)]; @@ -20,11 +19,11 @@ const isRootNavigation = (schema?: Navigation | NavItemType[]): schema is Naviga const HookedNavigation = ({ useNavigation, dynamicNav, pathname, ...props }: DynamicNavProps) => { const currentNamespace = pathname.split('/')[1]; const [isLoaded, setIsLoaded] = useState(false); - const dispatch = useDispatch(); - const schema = useSelector(({ chrome: { navigation } }: ReduxState) => navigation[currentNamespace]); - const currNav = useSelector(({ chrome: { navigation } }: ReduxState) => - (navigation[currentNamespace] as Navigation | undefined)?.navItems?.filter((item) => item.dynamicNav === dynamicNav) - ); + const getSchema = useSetAtom(getNavigationSegmentAtom); + const getCurrNav = useSetAtom(getDynamicSegmentItemsAtom); + const setnavigationSegment = useSetAtom(setNavigationSegmentAtom); + const schema = getSchema(currentNamespace); + const currNav = getCurrNav(currentNamespace, dynamicNav); const newNav = useNavigation({ schema, dynamicNav, currentNamespace, currNav }); useEffect(() => { if (newNav) { @@ -36,21 +35,19 @@ const HookedNavigation = ({ useNavigation, dynamicNav, pathname, ...props }: Dyn if (!isEqual(newValue, currNav) && isRootNavigation(schema)) { const currNavIndex = schema.navItems.findIndex((item) => item.dynamicNav === dynamicNav); if (currNavIndex !== -1) { - dispatch( - loadLeftNavSegment( - { - ...schema, - navItems: mergeArrays( - schema.navItems.filter((item) => !(item.dynamicNav && item.dynamicNav === dynamicNav)), - currNavIndex, - newValue - ), - }, - currentNamespace, - pathname, - true - ) - ); + setnavigationSegment({ + schema: { + ...schema, + navItems: mergeArrays( + schema.navItems.filter((item) => !(item.dynamicNav && item.dynamicNav === dynamicNav)), + currNavIndex, + newValue + ), + }, + segment: currentNamespace, + pathname, + shouldMerge: true, + }); } } setIsLoaded(true); diff --git a/src/hooks/useBreadcrumbsLinks.ts b/src/hooks/useBreadcrumbsLinks.ts index ccbfff798..20b0d743b 100644 --- a/src/hooks/useBreadcrumbsLinks.ts +++ b/src/hooks/useBreadcrumbsLinks.ts @@ -1,20 +1,19 @@ -import { useSelector } from 'react-redux'; import { useEffect, useMemo, useState } from 'react'; import { matchRoutes, useLocation } from 'react-router-dom'; import { Required } from 'utility-types'; -import { ReduxState } from '../redux/store'; import useBundle from './useBundle'; import { NavItem } from '../@types/types'; import { findNavLeafPath } from '../utils/common'; import { extractNavItemGroups, isNavItems } from '../utils/fetchNavigationFiles'; import { useAtomValue } from 'jotai'; import { moduleRoutesAtom } from '../state/atoms/chromeModuleAtom'; +import { navigationAtom } from '../state/atoms/navigationAtom'; const useBreadcrumbsLinks = () => { const { bundleId, bundleTitle } = useBundle(); const routes = useAtomValue(moduleRoutesAtom); - const navigation = useSelector(({ chrome: { navigation } }: ReduxState) => navigation); + const navigation = useAtomValue(navigationAtom); const { pathname } = useLocation(); const [segments, setSegments] = useState[]>([]); const wildCardRoutes = useMemo(() => routes.map((item) => ({ ...item, path: `${item.path}/*` })), [routes]); diff --git a/src/redux/action-types.ts b/src/redux/action-types.ts index f316d96d7..d42236008 100644 --- a/src/redux/action-types.ts +++ b/src/redux/action-types.ts @@ -11,9 +11,6 @@ export const GLOBAL_FILTER_UPDATE = '@@chrome/global-filter-update'; export const GLOBAL_FILTER_TOGGLE = '@@chrome/global-filter-toggle'; export const GLOBAL_FILTER_REMOVE = '@@chrome/global-filter-remove'; -export const LOAD_NAVIGATION_LANDING_PAGE = '@@chrome/load-navigation-landing-page'; -export const LOAD_LEFT_NAVIGATION_SEGMENT = '@@chrome/load-navigation-segment'; - export const UPDATE_DOCUMENT_TITLE_REDUCER = '@@chrome/update-document-title'; export const MARK_ACTIVE_PRODUCT = '@@chrome/mark-active-product'; diff --git a/src/redux/actions.ts b/src/redux/actions.ts index 3dfc6fd13..e4c56f0ad 100644 --- a/src/redux/actions.ts +++ b/src/redux/actions.ts @@ -2,7 +2,7 @@ import * as actionTypes from './action-types'; import { getAllSIDs, getAllTags, getAllWorkloads } from '../components/GlobalFilter/tagsApi'; import type { TagFilterOptions, TagPagination } from '../components/GlobalFilter/tagsApi'; import type { ChromeUser } from '@redhat-cloud-services/types'; -import type { FlagTagsFilter, NavItem, Navigation } from '../@types/types'; +import type { FlagTagsFilter } from '../@types/types'; import type { QuickStart } from '@patternfly/quickstarts'; export function userLogIn(user: ChromeUser | boolean) { @@ -75,21 +75,6 @@ export function removeGlobalFilter(isHidden = true) { }; } -export const loadNavigationLandingPage = (schema: NavItem[]) => ({ - type: actionTypes.LOAD_NAVIGATION_LANDING_PAGE, - payload: schema, -}); - -export const loadLeftNavSegment = (schema: Navigation, segment: string, pathName: string, shouldMerge?: boolean) => ({ - type: actionTypes.LOAD_LEFT_NAVIGATION_SEGMENT, - payload: { - segment, - schema, - pathName, - shouldMerge, - }, -}); - /** * @deprecated */ diff --git a/src/redux/chromeReducers.test.js b/src/redux/chromeReducers.test.js index 36f393624..07e6946d7 100644 --- a/src/redux/chromeReducers.test.js +++ b/src/redux/chromeReducers.test.js @@ -106,98 +106,6 @@ describe('Reducers', () => { }); }); - describe('loadNavigationSegmentReducer', () => { - const navigation = { test: { navItems: [], sortedLinks: [] } }; - it('should create new segment', () => { - const newNav = { - navItems: [ - { - href: '/something', - }, - ], - }; - const result = reducers.loadNavigationSegmentReducer( - { - navigation: {}, - }, - { - payload: { - segment: 'test', - schema: newNav, - }, - } - ); - expect(result).toEqual({ - navigation: { - ...navigation, - test: { - ...navigation.test, - navItems: newNav.navItems, - sortedLinks: ['/something'], - }, - }, - }); - }); - - it('should replace schema', () => { - const newNav = { navItems: [{ href: '/another' }, { href: '/different' }] }; - const result = reducers.loadNavigationSegmentReducer( - { - navigation: { - ...navigation, - test: { - ...navigation.test, - navItems: [{ href: '/something' }], - sortedLinks: ['/something'], - }, - }, - }, - { - payload: { - segment: 'test', - schema: newNav, - shouldMerge: true, - }, - } - ); - expect(result).toEqual({ - navigation: { - ...navigation, - test: { - ...navigation.test, - navItems: newNav.navItems, - sortedLinks: ['/different', '/another'], - }, - }, - }); - }); - - it('should highlight items', () => { - const result = reducers.loadNavigationSegmentReducer( - { - navigation: {}, - }, - { - payload: { - segment: 'test', - schema: { navItems: [{ href: '/something' }] }, - pathName: '/something', - }, - } - ); - expect(result).toEqual({ - navigation: { - ...navigation, - test: { - ...navigation.test, - navItems: [{ href: '/something', active: true }], - sortedLinks: ['/something'], - }, - }, - }); - }); - }); - describe('Add Quickstarts to App', () => { let prevState; beforeEach( diff --git a/src/redux/chromeReducers.ts b/src/redux/chromeReducers.ts index 83abdb790..0db46961e 100644 --- a/src/redux/chromeReducers.ts +++ b/src/redux/chromeReducers.ts @@ -1,7 +1,6 @@ import { QuickStart } from '@patternfly/quickstarts'; import { ChromeUser } from '@redhat-cloud-services/types'; -import { NavItem, Navigation } from '../@types/types'; -import { ITLess, highlightItems, levelArray } from '../utils/common'; +import { ITLess } from '../utils/common'; import { ChromeState } from './store'; export function loginReducer(state: ChromeState, { payload }: { payload: ChromeUser }): ChromeState { @@ -26,52 +25,6 @@ export function onPageObjectId(state: ChromeState, { payload }: { payload: strin }; } -export function loadNavigationLandingPageReducer(state: ChromeState, { payload }: { payload: NavItem[] }): ChromeState { - return { - ...state, - navigation: { - ...state.navigation, - landingPage: payload, - }, - }; -} - -function isNavigation(nav?: Navigation | NavItem[]): nav is Navigation { - return !Array.isArray(nav); -} - -export function loadNavigationSegmentReducer( - state: ChromeState, - { - payload: { segment, schema, pathName, shouldMerge }, - }: { - payload: { - segment: string; - schema: Navigation; - pathName: string; - shouldMerge?: boolean; - }; - } -): ChromeState { - const mergedSchema = shouldMerge || !state.navigation?.[segment] ? schema : state.navigation?.[segment]; - if (isNavigation(mergedSchema)) { - // Landing page navgation has different siganture - const sortedLinks = levelArray(mergedSchema?.navItems).sort((a, b) => (a.length < b.length ? 1 : -1)); - return { - ...state, - navigation: { - ...state.navigation, - [segment]: { - ...mergedSchema, - navItems: pathName ? highlightItems(pathName, mergedSchema.navItems, sortedLinks) : mergedSchema.navItems, - sortedLinks, - }, - }, - }; - } - return state; -} - export function populateQuickstartsReducer( state: ChromeState, { payload: { app, quickstarts } }: { payload: { app: string; quickstarts: QuickStart[] } } diff --git a/src/redux/index.ts b/src/redux/index.ts index 0c5b8e5dc..04154b0c2 100644 --- a/src/redux/index.ts +++ b/src/redux/index.ts @@ -5,8 +5,6 @@ import { clearQuickstartsReducer, disableQuickstartsReducer, documentTitleReducer, - loadNavigationLandingPageReducer, - loadNavigationSegmentReducer, loginReducer, markActiveProduct, onPageAction, @@ -39,8 +37,6 @@ import { GLOBAL_FILTER_SCOPE, GLOBAL_FILTER_TOGGLE, GLOBAL_FILTER_UPDATE, - LOAD_LEFT_NAVIGATION_SEGMENT, - LOAD_NAVIGATION_LANDING_PAGE, MARK_ACTIVE_PRODUCT, POPULATE_QUICKSTARTS_CATALOG, UPDATE_DOCUMENT_TITLE_REDUCER, @@ -53,8 +49,6 @@ const reducers = { [USER_LOGIN]: loginReducer, [CHROME_PAGE_ACTION]: onPageAction, [CHROME_PAGE_OBJECT]: onPageObjectId, - [LOAD_NAVIGATION_LANDING_PAGE]: loadNavigationLandingPageReducer, - [LOAD_LEFT_NAVIGATION_SEGMENT]: loadNavigationSegmentReducer, [POPULATE_QUICKSTARTS_CATALOG]: populateQuickstartsReducer, [ADD_QUICKSTARTS_TO_APP]: addQuickstartstoApp, [DISABLE_QUICKSTARTS]: disableQuickstartsReducer, @@ -78,7 +72,6 @@ const globalFilter = { export const chromeInitialState: ReduxState = { chrome: { - navigation: {}, quickstarts: { quickstarts: {}, }, @@ -93,7 +86,6 @@ export default function (): { return { chrome: ( state = { - navigation: {}, quickstarts: { quickstarts: {}, }, diff --git a/src/redux/store.d.ts b/src/redux/store.d.ts index 50df23a64..e517a1417 100644 --- a/src/redux/store.d.ts +++ b/src/redux/store.d.ts @@ -1,23 +1,13 @@ import { QuickStart } from '@patternfly/quickstarts'; -import { FlagTagsFilter, NavItem, Navigation } from '../@types/types'; - -export type InternalNavigation = { - [key: string]: Navigation | NavItem[] | undefined; - landingPage?: NavItem[]; -}; +import { FlagTagsFilter } from '../@types/types'; export type ChromeState = { activeProduct?: string; missingIDP?: boolean; pageAction?: string; pageObjectId?: string; - navigation: InternalNavigation; - // accessRequests: { - // count: number; - // data: AccessRequest[]; - // hasUnseen: boolean; - // }; + // navigation: InternalNavigation; initialHash?: string; quickstarts: { disabled?: boolean; diff --git a/src/state/atoms/navigationAtom.test.ts b/src/state/atoms/navigationAtom.test.ts new file mode 100644 index 000000000..18ee5a87f --- /dev/null +++ b/src/state/atoms/navigationAtom.test.ts @@ -0,0 +1,72 @@ +import { createStore } from 'jotai'; + +import { getDynamicSegmentItemsAtom, getNavigationSegmentAtom, navigationAtom, setNavigationSegmentAtom } from './navigationAtom'; + +describe('navigationAtom', () => { + test('should highlight nav item', () => { + const store = createStore(); + store.set(navigationAtom, {}); + store.set(setNavigationSegmentAtom, { + segment: 'test', + schema: { navItems: [{ title: 'test', href: '/test' }], sortedLinks: [] }, + pathname: '/test', + }); + const navigation = store.get(navigationAtom); + expect(navigation['test']).toBeTruthy(); + const navItem = navigation['test'].navItems[0]; + expect(navItem.active).toEqual(true); + }); + + test('should replace schema', () => { + const store = createStore(); + store.set(navigationAtom, {}); + store.set(setNavigationSegmentAtom, { + segment: 'test', + schema: { navItems: [{ title: 'test', href: '/test' }], sortedLinks: [] }, + pathname: '/test', + }); + let navigation = store.get(navigationAtom); + expect(navigation['test']).toBeTruthy(); + expect(navigation['test'].navItems[0].title).toEqual('test'); + + store.set(setNavigationSegmentAtom, { + segment: 'test', + schema: { navItems: [{ title: 'test2', href: '/test2' }], sortedLinks: [] }, + pathname: '/test2', + shouldMerge: true, + }); + navigation = store.get(navigationAtom); + expect(navigation['test']).toBeTruthy(); + expect(navigation['test'].navItems[0].title).toEqual('test2'); + }); + + test('should create new navigation sergment', () => { + const schema = { navItems: [], sortedLinks: [] }; + const store = createStore(); + store.set(navigationAtom, {}); + store.set(setNavigationSegmentAtom, { + segment: 'test', + schema, + pathname: '/test', + }); + const navigation = store.get(navigationAtom); + expect(navigation['test']).toBeTruthy(); + expect(navigation['test']).toEqual(schema); + }); + + test('should get navigation segment', () => { + const store = createStore(); + store.set(navigationAtom, { test: { navItems: [{ title: 'test', href: '/test' }], sortedLinks: [] } }); + const segment = store.set(getNavigationSegmentAtom, 'test'); + expect(segment).toBeTruthy(); + expect(segment.navItems[0].title).toEqual('test'); + }); + + test('should get dynamic segment items', () => { + const store = createStore(); + store.set(navigationAtom, { test: { navItems: [{ title: 'foobar', href: '/test', dynamicNav: 'dynamic' }], sortedLinks: [] } }); + const items = store.set(getDynamicSegmentItemsAtom, 'test', 'dynamic'); + expect(items).toBeTruthy(); + expect(items[0].title).toEqual('foobar'); + }); +}); diff --git a/src/state/atoms/navigationAtom.ts b/src/state/atoms/navigationAtom.ts new file mode 100644 index 000000000..66e2c0396 --- /dev/null +++ b/src/state/atoms/navigationAtom.ts @@ -0,0 +1,46 @@ +import { atom } from 'jotai'; +import { NavItem, Navigation } from '../../@types/types.d'; +import { highlightItems, levelArray } from '../../utils/common'; + +export type InternalNavigation = { + [key: string]: Navigation; +}; + +type SetSegmentPayload = { + segment: string; + schema: Navigation; + pathname?: string; + shouldMerge?: boolean; +}; + +function isNavigation(nav?: Navigation | NavItem[]): nav is Navigation { + return !Array.isArray(nav); +} + +export const navigationAtom = atom({}); +export const setNavigationSegmentAtom = atom(null, (_get, set, { segment, schema, pathname, shouldMerge }: SetSegmentPayload) => { + set(navigationAtom, (prev) => { + const mergedSchema = shouldMerge || !prev[segment] ? schema : prev[segment]; + if (isNavigation(mergedSchema)) { + const sortedLinks = levelArray(mergedSchema.navItems).sort((a, b) => (a.length < b.length ? 1 : -1)); + return { + ...prev, + [segment]: { + ...mergedSchema, + navItems: pathname ? highlightItems(pathname, mergedSchema.navItems, sortedLinks) : mergedSchema.navItems, + sortedLinks, + }, + }; + } + return prev; + }); +}); +export const getNavigationSegmentAtom = atom(null, (get, _set, segment: string) => { + const navigation = get(navigationAtom); + return navigation[segment]; +}); +export const getDynamicSegmentItemsAtom = atom(null, (get, _set, segment: string, dynamicNav: string) => { + const navigation = get(navigationAtom); + const nav = navigation[segment]; + return nav?.navItems?.filter((item) => item.dynamicNav === dynamicNav); +}); diff --git a/src/utils/useNavigation.test.js b/src/utils/useNavigation.test.js index 6f9ce9086..834d57e6e 100644 --- a/src/utils/useNavigation.test.js +++ b/src/utils/useNavigation.test.js @@ -2,9 +2,8 @@ import React, { Fragment, useEffect } from 'react'; import { act, renderHook } from '@testing-library/react'; import { MemoryRouter, Route, Routes, useNavigate } from 'react-router-dom'; -import { Provider } from 'react-redux'; -import configureStore from 'redux-mock-store'; - +import { Provider as JotaiProvider, createStore } from 'jotai'; +import { useHydrateAtoms } from 'jotai/utils'; import useNavigation from './useNavigation'; jest.mock('axios', () => { @@ -30,6 +29,7 @@ jest.mock('@scalprum/core', () => { import * as axios from 'axios'; import FlagProvider, { UnleashClient } from '@unleash/proxy-client-react'; import { initializeVisibilityFunctions } from './VisibilitySingleton'; +import { navigationAtom } from '../state/atoms/navigationAtom'; jest.mock('@unleash/proxy-client-react', () => { const actual = jest.requireActual('@unleash/proxy-client-react'); @@ -63,19 +63,38 @@ const testClient = new UnleashClient({ fetch: () => ({}), }); +const HydrateAtoms = ({ initialValues, children }) => { + useHydrateAtoms(initialValues); + return children; +}; + +const TestProvider = ({ initialValues, children, store }) => { + useEffect(() => { + if (store) { + initialValues.forEach(([atom, value]) => { + store.set(atom, value); + }); + } + }, []); + return ( + + {children} + + ); +}; + // eslint-disable-next-line react/prop-types -const RouterDummy = ({ store, children, path }) => ( +const RouterDummy = ({ children, path, initialValues, store }) => ( - + {children} - + ); describe('useNavigation', () => { - const mockStore = configureStore(); beforeAll(() => { initializeVisibilityFunctions({ getUser() { @@ -88,25 +107,21 @@ describe('useNavigation', () => { test('should update on namespace change', async () => { const axiosGetSpy = jest.spyOn(axios.default, 'get'); - const store = mockStore({ - chrome: { - navigation: { - insights: { - id: 'insights', - navItems: [], - }, - ansible: { - id: 'ansible', - navItems: [], - }, - }, + const navigation = { + insights: { + id: 'insights', + navItems: [], }, - }); + ansible: { + id: 'ansible', + navItems: [], + }, + }; axiosGetSpy.mockImplementation(() => Promise.resolve({ data: { navItems: [] } })); const createWrapper = (props) => { function Wrapper({ children }) { return ( - + {children} ); @@ -144,142 +159,18 @@ describe('useNavigation', () => { describe('isHidden flag', () => { test('should propagate navigation item isHidden flag', async () => { const axiosGetSpy = jest.spyOn(axios.default, 'get'); + const store = createStore(); const navItem = { href: '/foo', title: 'bar', isHidden: true, }; - const store = mockStore({ - chrome: { - navigation: { - insights: { - id: 'insights', - navItems: [navItem], - }, - }, + const navigation = { + insights: { + id: 'insights', + navItems: [navItem], }, - }); - axiosGetSpy.mockImplementation(() => - Promise.resolve({ - data: { - navItems: [navItem], - }, - }) - ); - const wrapper = ({ children }) => ( - - {children} - - ); - - await act(async () => { - await renderHook(() => useNavigation(), { - wrapper, - }); - }); - - expect(store.getActions()).toEqual([ - { - type: '@@chrome/load-navigation-segment', - payload: { - segment: 'insights', - pathName: '/insights', - schema: { - navItems: [ - expect.objectContaining({ - isHidden: true, - }), - ], - }, - }, - }, - ]); - }); - - test('should mark navigation item as hidden', async () => { - const axiosGetSpy = jest.spyOn(axios.default, 'get'); - const navItem = { - href: '/foo', - title: 'bar', - permissions: [ - { - method: 'isOrgAdmin', - }, - ], - }; - const store = mockStore({ - chrome: { - navigation: { - insights: { - id: 'insights', - navItems: [navItem], - }, - }, - }, - }); - axiosGetSpy.mockImplementation(() => - Promise.resolve({ - data: { - navItems: [navItem], - }, - }) - ); - const wrapper = ({ children }) => ( - - {children} - - ); - - await act(async () => { - await renderHook(() => useNavigation(), { - wrapper, - }); - }); - - expect(store.getActions()).toEqual([ - { - type: '@@chrome/load-navigation-segment', - payload: { - segment: 'insights', - pathName: '/insights', - schema: { - navItems: [ - expect.objectContaining({ - isHidden: true, - }), - ], - }, - }, - }, - ]); - }); - - test('should mark navigation group items as hidden', async () => { - const axiosGetSpy = jest.spyOn(axios.default, 'get'); - const navItem = { - groupId: 'foo', - title: 'bar', - navItems: [ - { - href: '/bar', - permissions: [ - { - method: 'isOrgAdmin', - }, - ], - }, - ], }; - const store = mockStore({ - chrome: { - navigation: { - insights: { - id: 'insights', - navItems: [navItem], - }, - }, - }, - }); axiosGetSpy.mockImplementation(() => Promise.resolve({ data: { @@ -288,7 +179,7 @@ describe('useNavigation', () => { }) ); const wrapper = ({ children }) => ( - + {children} ); @@ -299,97 +190,19 @@ describe('useNavigation', () => { }); }); - expect(store.getActions()).toEqual([ - { - type: '@@chrome/load-navigation-segment', - payload: { - segment: 'insights', - pathName: '/insights', - schema: { - navItems: [ - expect.objectContaining({ - groupId: 'foo', - navItems: [ - expect.objectContaining({ - href: '/bar', - isHidden: true, - }), - ], - }), - ], - }, - }, - }, - ]); - }); - - test('should mark expandable item routes as hidden', async () => { - const axiosGetSpy = jest.spyOn(axios.default, 'get'); - const navItem = { - title: 'bar', - expandable: true, - routes: [ - { - href: '/bar', - permissions: [ - { - method: 'isOrgAdmin', - }, - ], - }, - ], - }; - const store = mockStore({ - chrome: { - navigation: { - insights: { - id: 'insights', - navItems: [navItem], - }, - }, + const data = store.get(navigationAtom); + + expect(data).toEqual({ + insights: { + id: 'insights', + navItems: [ + expect.objectContaining({ + isHidden: true, + }), + ], + sortedLinks: ['/foo'], }, }); - axiosGetSpy.mockImplementation(() => - Promise.resolve({ - data: { - navItems: [navItem], - }, - }) - ); - const wrapper = ({ children }) => ( - - {children} - - ); - - await act(async () => { - await renderHook(() => useNavigation(), { - wrapper, - }); - }); - - expect(store.getActions()).toEqual([ - { - type: '@@chrome/load-navigation-segment', - payload: { - segment: 'insights', - pathName: '/insights', - schema: { - navItems: [ - expect.objectContaining({ - expandable: true, - routes: [ - expect.objectContaining({ - href: '/bar', - isHidden: true, - }), - ], - }), - ], - }, - }, - }, - ]); }); }); @@ -425,16 +238,13 @@ describe('useNavigation', () => { }, ], }; - const store = mockStore({ - chrome: { - navigation: { - insights: { - id: 'insights', - navItems: [navItem], - }, - }, + const navigation = { + insights: { + id: 'insights', + navItems: [navItem], }, - }); + }; + const store = createStore(); axiosGetSpy.mockImplementation(() => Promise.resolve({ data: { @@ -443,7 +253,7 @@ describe('useNavigation', () => { }) ); const wrapper = ({ children }) => ( - + {children} ); @@ -454,18 +264,14 @@ describe('useNavigation', () => { }); }); - expect(store.getActions()).toEqual([ - { - type: '@@chrome/load-navigation-segment', - payload: { - segment: 'insights', - pathName: '/insights', - schema: { - navItems: expect.any(Array), - }, - }, + const data = store.get(navigationAtom); + expect(data).toEqual({ + insights: { + id: 'insights', + navItems: expect.any(Array), + sortedLinks: ['/baz/bar/quaxx', '/baz/bar', '/foo/bar', '/baz', '/bar'], }, - ]); + }); }); }); @@ -476,16 +282,13 @@ describe('useNavigation', () => { title: 'bar', href: '/insights', }; - const store = mockStore({ - chrome: { - navigation: { - insights: { - id: 'insights', - navItems: [navItem], - }, - }, + const navigation = { + insights: { + id: 'insights', + navItems: [navItem], }, - }); + }; + const store = createStore(); axiosGetSpy.mockImplementation(() => Promise.resolve({ data: { @@ -494,7 +297,7 @@ describe('useNavigation', () => { }) ); const wrapper = ({ children }) => ( - + {children} ); @@ -505,27 +308,18 @@ describe('useNavigation', () => { }); }); - expect(store.getActions()).toEqual([ - { - type: '@@chrome/load-navigation-segment', - payload: { - segment: 'insights', - pathName: '/insights', - schema: { - navItems: [ - { - href: '/insights', - isHidden: false, - title: 'bar', - }, - ], - }, - }, + const data = store.get(navigationAtom); + + expect(data).toEqual({ + insights: { + id: 'insights', + navItems: [{ title: 'bar', href: '/insights', active: true }], + sortedLinks: ['/insights'], }, - ]); + }); }); - test('should mark nested /insights/dashboard nav item as its parent as active', async () => { + test.only('should mark nested /insights/dashboard nav item as its parent as active', async () => { const axiosGetSpy = jest.spyOn(axios.default, 'get'); const navItem = { expandable: true, @@ -536,16 +330,13 @@ describe('useNavigation', () => { }, ], }; - const store = mockStore({ - chrome: { - navigation: { - insights: { - id: 'insights', - navItems: [navItem], - }, - }, + const navigation = { + insights: { + id: 'insights', + navItems: [navItem], }, - }); + }; + const store = createStore(); axiosGetSpy.mockImplementation(() => Promise.resolve({ data: { @@ -554,7 +345,7 @@ describe('useNavigation', () => { }) ); const wrapper = ({ children }) => ( - + {children} ); @@ -565,30 +356,27 @@ describe('useNavigation', () => { }); }); - expect(store.getActions()).toEqual([ - { - type: '@@chrome/load-navigation-segment', - payload: { - segment: 'insights', - pathName: '/insights/dashboard', - schema: { - navItems: [ + const data = store.get(navigationAtom); + + expect(data).toEqual({ + insights: { + id: 'insights', + navItems: [ + { + expandable: true, + title: 'bar', + routes: [ { - expandable: true, - isHidden: false, - title: 'bar', - routes: [ - { - href: '/insights/dashboard', - isHidden: false, - }, - ], + href: '/insights/dashboard', + active: true, }, ], + active: true, }, - }, + ], + sortedLinks: ['/insights/dashboard'], }, - ]); + }); }); }); }); diff --git a/src/utils/useNavigation.ts b/src/utils/useNavigation.ts index d11ad4e82..467c185b8 100644 --- a/src/utils/useNavigation.ts +++ b/src/utils/useNavigation.ts @@ -1,17 +1,15 @@ import axios from 'axios'; import { useAtomValue, useSetAtom } from 'jotai'; import { useContext, useEffect, useRef, useState } from 'react'; -import { batch, useDispatch, useSelector } from 'react-redux'; -import { loadLeftNavSegment } from '../redux/actions'; import { useLocation, useNavigate } from 'react-router-dom'; import { BLOCK_CLEAR_GATEWAY_ERROR, getChromeStaticPathname } from './common'; import { evaluateVisibility } from './isNavItemVisible'; import { QuickStartContext } from '@patternfly/quickstarts'; import { useFlagsStatus } from '@unleash/proxy-client-react'; import { BundleNavigation, NavItem, Navigation } from '../@types/types'; -import { ReduxState } from '../redux/store'; import { clearGatewayErrorAtom } from '../state/atoms/gatewayErrorAtom'; import { isPreviewAtom } from '../state/atoms/releaseAtom'; +import { navigationAtom, setNavigationSegmentAtom } from '../state/atoms/navigationAtom'; function cleanNavItemsHref(navItem: NavItem) { const result = { ...navItem }; @@ -48,13 +46,14 @@ const appendQSSearch = (currentSearch: string, activeQuickStartID: string) => { const useNavigation = () => { const { flagsReady, flagsError } = useFlagsStatus(); const clearGatewayError = useSetAtom(clearGatewayErrorAtom); - const dispatch = useDispatch(); const location = useLocation(); const navigate = useNavigate(); const { pathname } = location; const { activeQuickStartID } = useContext(QuickStartContext); const currentNamespace = pathname.split('/')[1]; - const schema = useSelector(({ chrome: { navigation } }: ReduxState) => navigation[currentNamespace] as Navigation); + const navigation = useAtomValue(navigationAtom); + const schema = navigation[currentNamespace]; + const setNavigationSegment = useSetAtom(setNavigationSegmentAtom); const [noNav, setNoNav] = useState(false); const isPreview = useAtomValue(isPreviewAtom); @@ -70,21 +69,19 @@ const useNavigation = () => { const registerLocationObserver = (initialPathname: string, schema: Navigation) => { let prevPathname = initialPathname; - dispatch(loadLeftNavSegment(schema, currentNamespace, initialPathname)); + setNavigationSegment({ schema, segment: currentNamespace, pathname: initialPathname }); return new MutationObserver((mutations) => { mutations.forEach(() => { const newPathname = window.location.pathname; if (newPathname !== prevPathname) { prevPathname = newPathname; - batch(() => { - dispatch(loadLeftNavSegment(schema, currentNamespace, prevPathname)); - /** - * Clean gateway error on URL change - */ - if (localStorage.getItem(BLOCK_CLEAR_GATEWAY_ERROR) !== 'true') { - clearGatewayError(); - } - }); + setNavigationSegment({ schema, segment: currentNamespace, pathname: prevPathname }); + /** + * Clean gateway error on URL change + */ + if (localStorage.getItem(BLOCK_CLEAR_GATEWAY_ERROR) !== 'true') { + clearGatewayError(); + } } setTimeout(() => { From 2a121835b0b1f250469e21d9a760f31eb273c324 Mon Sep 17 00:00:00 2001 From: Martin Marosi Date: Mon, 29 Jul 2024 10:52:51 +0200 Subject: [PATCH 072/151] Migrate quickstarts state to Jotai. --- .../QuickStart/useQuickstartsStates.ts | 17 ++--- .../QuickstartsCatalogRoute.tsx | 12 +-- src/components/RootApp/RootApp.tsx | 31 ++++---- src/redux/action-types.ts | 5 -- src/redux/actions.ts | 28 ------- src/redux/chromeReducers.test.js | 61 ---------------- src/redux/chromeReducers.ts | 59 --------------- src/redux/index.ts | 35 +-------- src/redux/store.d.ts | 9 --- src/state/atoms/quickstartsAtom.test.tsx | 73 +++++++++++++++++++ src/state/atoms/quickstartsAtom.ts | 35 +++++++++ 11 files changed, 137 insertions(+), 228 deletions(-) create mode 100644 src/state/atoms/quickstartsAtom.test.tsx create mode 100644 src/state/atoms/quickstartsAtom.ts diff --git a/src/components/QuickStart/useQuickstartsStates.ts b/src/components/QuickStart/useQuickstartsStates.ts index 3377bacba..6500eb5ac 100644 --- a/src/components/QuickStart/useQuickstartsStates.ts +++ b/src/components/QuickStart/useQuickstartsStates.ts @@ -1,14 +1,15 @@ import axios from 'axios'; import { useContext, useEffect, useState } from 'react'; -import { useDispatch } from 'react-redux'; import { QuickStart, QuickStartState } from '@patternfly/quickstarts'; -import { populateQuickstartsCatalog } from '../../redux/actions'; import ChromeAuthContext from '../../auth/ChromeAuthContext'; +import { useSetAtom } from 'jotai'; +import { populateQuickstartsAppAtom } from '../../state/atoms/quickstartsAtom'; const useQuickstartsStates = () => { - const dispatch = useDispatch(); const auth = useContext(ChromeAuthContext); const accountId = auth.user.identity?.internal?.account_id; + const populateQuickstarts = useSetAtom(populateQuickstartsAppAtom); + const [allQuickStartStates, setAllQuickStartStatesInternal] = useState<{ [key: string | number]: QuickStartState }>({}); const [activeQuickStartID, setActiveQuickStartIDInternal] = useState(''); @@ -68,12 +69,10 @@ const useQuickstartsStates = () => { name, }, }); - dispatch( - populateQuickstartsCatalog( - 'default', - data.map(({ content }) => content) - ) - ); + populateQuickstarts({ + app: 'default', + quickstarts: data.map(({ content }) => content), + }); setActiveQuickStartID(name); } catch (error) { diff --git a/src/components/QuickstartsCatalogRoute/QuickstartsCatalogRoute.tsx b/src/components/QuickstartsCatalogRoute/QuickstartsCatalogRoute.tsx index 83cc57642..8a51292c7 100644 --- a/src/components/QuickstartsCatalogRoute/QuickstartsCatalogRoute.tsx +++ b/src/components/QuickstartsCatalogRoute/QuickstartsCatalogRoute.tsx @@ -1,21 +1,15 @@ import React from 'react'; -import { useSelector } from 'react-redux'; import QuickStartCatalog from '../QuickStart/QuickStartCatalog'; import { useIntl } from 'react-intl'; import messages from '../../locales/Messages'; -import { ReduxState } from '../../redux/store'; import { getUrl } from '../../hooks/useBundle'; +import { useAtom } from 'jotai'; +import { quickstartsDisabledAtom } from '../../state/atoms/quickstartsAtom'; const QuickstartCatalogRoute = () => { const intl = useIntl(); const bundle = getUrl('bundle'); - const disabled = useSelector( - ({ - chrome: { - quickstarts: { disabled }, - }, - }: ReduxState) => disabled - ); + const disabled = useAtom(quickstartsDisabledAtom); if (disabled) { return ( diff --git a/src/components/RootApp/RootApp.tsx b/src/components/RootApp/RootApp.tsx index e69b41727..b226064bf 100644 --- a/src/components/RootApp/RootApp.tsx +++ b/src/components/RootApp/RootApp.tsx @@ -1,18 +1,15 @@ -import React, { Suspense, lazy, memo, useContext, useEffect } from 'react'; +import React, { Suspense, lazy, memo, useContext, useEffect, useMemo } from 'react'; import { unstable_HistoryRouter as HistoryRouter, HistoryRouterProps } from 'react-router-dom'; import { HelpTopicContainer, QuickStart, QuickStartContainer, QuickStartContainerProps } from '@patternfly/quickstarts'; -import { useAtomValue } from 'jotai'; +import { useAtomValue, useSetAtom } from 'jotai'; import chromeHistory from '../../utils/chromeHistory'; import { FeatureFlagsProvider } from '../FeatureFlags'; import ScalprumRoot from './ScalprumRoot'; -import { useDispatch, useSelector } from 'react-redux'; -import { addQuickstart as addQuickstartAction, clearQuickstarts, populateQuickstartsCatalog } from '../../redux/actions'; import { LazyQuickStartCatalog } from '../QuickStart/LazyQuickStartCatalog'; import useQuickstartsStates from '../QuickStart/useQuickstartsStates'; import useHelpTopicState from '../QuickStart/useHelpTopicState'; import validateQuickstart from '../QuickStart/quickstartValidation'; import SegmentProvider from '../../analytics/SegmentProvider'; -import { ReduxState } from '../../redux/store'; import { ITLess, chunkLoadErrorRefreshKey, getRouterBasename } from '../../utils/common'; import useUserSSOScopes from '../../hooks/useUserSSOScopes'; import { DeepRequired } from 'utility-types'; @@ -22,6 +19,7 @@ import ChromeAuthContext, { ChromeAuthContextValue } from '../../auth/ChromeAuth import { activeModuleAtom } from '../../state/atoms/activeModuleAtom'; import { scalprumConfigAtom } from '../../state/atoms/scalprumConfigAtom'; import { isDebuggerEnabledAtom } from '../../state/atoms/debuggerModalatom'; +import { addQuickstartToAppAtom, clearQuickstartsAtom, populateQuickstartsAppAtom, quickstartsAtom } from '../../state/atoms/quickstartsAtom'; const NotEntitledModal = lazy(() => import('../NotEntitledModal')); const Debugger = lazy(() => import('../Debugger')); @@ -32,15 +30,12 @@ const RootApp = memo((props: RootAppProps) => { const config = useAtomValue(scalprumConfigAtom); const { activateQuickstart, allQuickStartStates, setAllQuickStartStates, activeQuickStartID, setActiveQuickStartID } = useQuickstartsStates(); const { helpTopics, addHelpTopics, disableTopics, enableTopics } = useHelpTopicState(); - const dispatch = useDispatch(); const activeModule = useAtomValue(activeModuleAtom); - const quickStarts = useSelector( - ({ - chrome: { - quickstarts: { quickstarts }, - }, - }: ReduxState) => Object.values(quickstarts).flat() - ); + const quickstartsData = useAtomValue(quickstartsAtom); + const quickStarts = useMemo(() => Object.values(quickstartsData).flat(), [quickstartsData]); + const clearQuickstarts = useSetAtom(clearQuickstartsAtom); + const populateQuickstarts = useSetAtom(populateQuickstartsAppAtom); + const addQuickstartToApp = useSetAtom(addQuickstartToAppAtom); const { user } = useContext(ChromeAuthContext) as DeepRequired; const isDebuggerEnabled = useAtomValue(isDebuggerEnabledAtom); @@ -48,7 +43,7 @@ const RootApp = memo((props: RootAppProps) => { useUserSSOScopes(); useEffect(() => { - dispatch(clearQuickstarts(activeQuickStartID)); + clearQuickstarts(activeQuickStartID); if (activeModule) { let timeout: NodeJS.Timeout; const moduleStorageKey = `${chunkLoadErrorRefreshKey}-${activeModule}`; @@ -78,11 +73,15 @@ const RootApp = memo((props: RootAppProps) => { * @param {array} qs Array of quick starts */ const updateQuickStarts = (key: string, qs: QuickStart[]) => { - dispatch(populateQuickstartsCatalog(key, qs)); + populateQuickstarts({ app: key, quickstarts: qs }); }; const addQuickstart = (key: string, qs: QuickStart): boolean => { - return validateQuickstart(key, qs) ? !!dispatch(addQuickstartAction(key, qs)) : false; + if (validateQuickstart(key, qs)) { + addQuickstartToApp({ app: key, quickstart: qs }); + return true; + } + return false; }; const quickStartProps: QuickStartContainerProps = { diff --git a/src/redux/action-types.ts b/src/redux/action-types.ts index d42236008..3e04999e4 100644 --- a/src/redux/action-types.ts +++ b/src/redux/action-types.ts @@ -15,8 +15,3 @@ export const UPDATE_DOCUMENT_TITLE_REDUCER = '@@chrome/update-document-title'; export const MARK_ACTIVE_PRODUCT = '@@chrome/mark-active-product'; export const STORE_INITIAL_HASH = '@@chrome/store-initial-hash'; - -export const POPULATE_QUICKSTARTS_CATALOG = '@@chrome/populate-quickstarts-catalog'; -export const ADD_QUICKSTARTS_TO_APP = '@@chrome/add-quickstart'; -export const DISABLE_QUICKSTARTS = '@@chrome/disable-quickstarts'; -export const CLEAR_QUICKSTARTS = '@@chrome/clear-quickstarts'; diff --git a/src/redux/actions.ts b/src/redux/actions.ts index e4c56f0ad..f4a3b617a 100644 --- a/src/redux/actions.ts +++ b/src/redux/actions.ts @@ -3,7 +3,6 @@ import { getAllSIDs, getAllTags, getAllWorkloads } from '../components/GlobalFil import type { TagFilterOptions, TagPagination } from '../components/GlobalFilter/tagsApi'; import type { ChromeUser } from '@redhat-cloud-services/types'; import type { FlagTagsFilter } from '../@types/types'; -import type { QuickStart } from '@patternfly/quickstarts'; export function userLogIn(user: ChromeUser | boolean) { return { @@ -82,33 +81,6 @@ export const onToggle = () => ({ type: 'NAVIGATION_TOGGLE', }); -export const populateQuickstartsCatalog = (app: string, quickstarts: QuickStart[]) => ({ - type: actionTypes.POPULATE_QUICKSTARTS_CATALOG, - payload: { - app, - quickstarts, - }, -}); - -export const addQuickstart = (app: string, quickstart: QuickStart) => ({ - type: actionTypes.ADD_QUICKSTARTS_TO_APP, - payload: { - app, - quickstart, - }, -}); - -export const clearQuickstarts = (activeQuickstart?: string) => ({ - type: actionTypes.CLEAR_QUICKSTARTS, - payload: { - activeQuickstart, - }, -}); - -export const disableQuickstarts = () => ({ - type: actionTypes.DISABLE_QUICKSTARTS, -}); - export const updateDocumentTitle = (title: string) => ({ type: actionTypes.UPDATE_DOCUMENT_TITLE_REDUCER, payload: title, diff --git a/src/redux/chromeReducers.test.js b/src/redux/chromeReducers.test.js index 07e6946d7..061947e7b 100644 --- a/src/redux/chromeReducers.test.js +++ b/src/redux/chromeReducers.test.js @@ -105,65 +105,4 @@ describe('Reducers', () => { }); }); }); - - describe('Add Quickstarts to App', () => { - let prevState; - beforeEach( - () => - (prevState = { - quickstarts: { - quickstarts: { - foo: [], - }, - }, - }) - ); - - it('Add to empty quickstart', () => { - prevState = reducers.addQuickstartstoApp(prevState, { app: 'foo', quickstart: 123 }); - expect(prevState.quickstarts.quickstarts.foo).toEqual([123]); - }); - - it('Add to non empty quickstart', () => { - prevState = { - quickstarts: { - quickstarts: { - foo: [666], - }, - }, - }; - prevState = reducers.addQuickstartstoApp(prevState, { app: 'foo', quickstart: 123 }); - expect(prevState.quickstarts.quickstarts.foo).toEqual([666, 123]); - }); - - it('Wrong type - name not string', () => { - prevState = { - quickstarts: { - quickstarts: { - foo: [], - }, - }, - }; - try { - reducers.addQuickstartstoApp(prevState, { app: 62, quickstart: 123 }); - } catch (e) { - expect(e).toBe('"qs.metadata.name" must be type of string.'); - } - }); - - it('Wrong type - key not string', () => { - prevState = { - quickstarts: { - quickstarts: { - foo: [], - }, - }, - }; - try { - reducers.addQuickstartstoApp(prevState, { 111: 'foo', quickstart: 123 }); - } catch (e) { - expect(e).toBe('"key" must be type of string.'); - } - }); - }); }); diff --git a/src/redux/chromeReducers.ts b/src/redux/chromeReducers.ts index 0db46961e..74874dfac 100644 --- a/src/redux/chromeReducers.ts +++ b/src/redux/chromeReducers.ts @@ -1,4 +1,3 @@ -import { QuickStart } from '@patternfly/quickstarts'; import { ChromeUser } from '@redhat-cloud-services/types'; import { ITLess } from '../utils/common'; import { ChromeState } from './store'; @@ -25,64 +24,6 @@ export function onPageObjectId(state: ChromeState, { payload }: { payload: strin }; } -export function populateQuickstartsReducer( - state: ChromeState, - { payload: { app, quickstarts } }: { payload: { app: string; quickstarts: QuickStart[] } } -): ChromeState { - return { - ...state, - quickstarts: { - ...state.quickstarts, - quickstarts: { - [app]: quickstarts, - }, - }, - }; -} - -export function addQuickstartstoApp(state: ChromeState, { app, quickstart }: { app: string; quickstart: QuickStart }) { - return { - ...state, - quickstarts: { - ...state.quickstarts, - quickstarts: { - ...state.quickstarts.quickstarts, - [app]: [...(state.quickstarts?.quickstarts?.[app] ? state.quickstarts?.quickstarts?.[app] || [] : []), quickstart], - }, - }, - }; -} - -export function disableQuickstartsReducer(state: ChromeState): ChromeState { - return { - ...state, - quickstarts: { - ...state.quickstarts, - disabled: true, - }, - }; -} - -export function clearQuickstartsReducer( - state: ChromeState, - { payload: { activeQuickstart } }: { payload: { activeQuickstart?: string } } -): ChromeState { - return { - ...state, - quickstarts: { - ...state.quickstarts, - // do not remove currently opened quickstart - quickstarts: Object.entries(state.quickstarts.quickstarts)?.reduce( - (acc, [namespace, quickstarts]) => ({ - ...acc, - [namespace]: Array.isArray(quickstarts) ? quickstarts.filter((qs) => qs?.metadata?.name === activeQuickstart) : quickstarts, - }), - {} - ), - }, - }; -} - export function documentTitleReducer(state: ChromeState, { payload }: { payload: string }): ChromeState { return { ...state, diff --git a/src/redux/index.ts b/src/redux/index.ts index 04154b0c2..820284014 100644 --- a/src/redux/index.ts +++ b/src/redux/index.ts @@ -1,16 +1,6 @@ import { applyReducerHash } from '@redhat-cloud-services/frontend-components-utilities/ReducerRegistry'; -import { - addQuickstartstoApp, - clearQuickstartsReducer, - disableQuickstartsReducer, - documentTitleReducer, - loginReducer, - markActiveProduct, - onPageAction, - onPageObjectId, - populateQuickstartsReducer, -} from './chromeReducers'; +import { documentTitleReducer, loginReducer, markActiveProduct, onPageAction, onPageObjectId } from './chromeReducers'; import { globalFilterDefaultState, onGetAllSIDs, @@ -25,20 +15,16 @@ import { onTagSelect, } from './globalFilterReducers'; import { - ADD_QUICKSTARTS_TO_APP, CHROME_GET_ALL_SIDS, CHROME_GET_ALL_TAGS, CHROME_GET_ALL_WORKLOADS, CHROME_PAGE_ACTION, CHROME_PAGE_OBJECT, - CLEAR_QUICKSTARTS, - DISABLE_QUICKSTARTS, GLOBAL_FILTER_REMOVE, GLOBAL_FILTER_SCOPE, GLOBAL_FILTER_TOGGLE, GLOBAL_FILTER_UPDATE, MARK_ACTIVE_PRODUCT, - POPULATE_QUICKSTARTS_CATALOG, UPDATE_DOCUMENT_TITLE_REDUCER, USER_LOGIN, } from './action-types'; @@ -49,12 +35,8 @@ const reducers = { [USER_LOGIN]: loginReducer, [CHROME_PAGE_ACTION]: onPageAction, [CHROME_PAGE_OBJECT]: onPageObjectId, - [POPULATE_QUICKSTARTS_CATALOG]: populateQuickstartsReducer, - [ADD_QUICKSTARTS_TO_APP]: addQuickstartstoApp, - [DISABLE_QUICKSTARTS]: disableQuickstartsReducer, [UPDATE_DOCUMENT_TITLE_REDUCER]: documentTitleReducer, [MARK_ACTIVE_PRODUCT]: markActiveProduct, - [CLEAR_QUICKSTARTS]: clearQuickstartsReducer, }; const globalFilter = { @@ -71,11 +53,7 @@ const globalFilter = { }; export const chromeInitialState: ReduxState = { - chrome: { - quickstarts: { - quickstarts: {}, - }, - }, + chrome: {}, globalFilter: globalFilterDefaultState, }; @@ -84,14 +62,7 @@ export default function (): { globalFilter: (state: GlobalFilterState, action: AnyAction) => ChromeState; } { return { - chrome: ( - state = { - quickstarts: { - quickstarts: {}, - }, - }, - action - ) => applyReducerHash(reducers)(state, action), + chrome: (state = {}, action) => applyReducerHash(reducers)(state, action), globalFilter: (state = globalFilterDefaultState, action) => applyReducerHash(globalFilter)(state, action), }; } diff --git a/src/redux/store.d.ts b/src/redux/store.d.ts index e517a1417..ab78b7147 100644 --- a/src/redux/store.d.ts +++ b/src/redux/store.d.ts @@ -1,5 +1,3 @@ -import { QuickStart } from '@patternfly/quickstarts'; - import { FlagTagsFilter } from '../@types/types'; export type ChromeState = { @@ -7,14 +5,7 @@ export type ChromeState = { missingIDP?: boolean; pageAction?: string; pageObjectId?: string; - // navigation: InternalNavigation; initialHash?: string; - quickstarts: { - disabled?: boolean; - quickstarts: { - [key: string]: QuickStart[]; - }; - }; documentTitle?: string; }; diff --git a/src/state/atoms/quickstartsAtom.test.tsx b/src/state/atoms/quickstartsAtom.test.tsx new file mode 100644 index 000000000..bf94ad25e --- /dev/null +++ b/src/state/atoms/quickstartsAtom.test.tsx @@ -0,0 +1,73 @@ +import { createStore } from 'jotai'; +import { addQuickstartToAppAtom, clearQuickstartsAtom, populateQuickstartsAppAtom, quickstartsAtom } from './quickstartsAtom'; +import { QuickStart } from '@patternfly/quickstarts'; + +const dummyQuickstart: QuickStart = { + metadata: { + name: 'dummy', + }, + spec: { + displayName: 'Foo', + description: 'bar', + icon: '', + }, +}; + +describe('quickstartsAtom', () => { + test('should add new quickstart to existing app', () => { + const store = createStore(); + store.set(quickstartsAtom, { foo: [] }); + store.set(addQuickstartToAppAtom, { app: 'foo', quickstart: { ...dummyQuickstart } }); + + const { foo } = store.get(quickstartsAtom); + expect(foo).toHaveLength(1); + expect(foo[0].metadata.name).toBe('dummy'); + }); + + test('should append new quickstart to existing app', () => { + const store = createStore(); + store.set(quickstartsAtom, { foo: [{ ...dummyQuickstart }] }); + store.set(addQuickstartToAppAtom, { app: 'foo', quickstart: { ...dummyQuickstart, metadata: { name: 'dummy2' } } }); + + const { foo } = store.get(quickstartsAtom); + expect(foo).toHaveLength(2); + expect(foo[1].metadata.name).toBe('dummy2'); + }); + + test('should populate new app with quickstarts', () => { + const store = createStore(); + store.set(quickstartsAtom, {}); + store.set(populateQuickstartsAppAtom, { app: 'foo', quickstarts: [{ ...dummyQuickstart }] }); + + const { foo } = store.get(quickstartsAtom); + expect(foo).toHaveLength(1); + expect(foo[0].metadata.name).toBe('dummy'); + }); + + test('should clear all quickstarts', () => { + const store = createStore(); + store.set(quickstartsAtom, { + bar: [{ ...dummyQuickstart, metadata: { name: 'active' } }], + baz: [{ ...dummyQuickstart, metadata: { name: 'bla' } }], + foo: [{ ...dummyQuickstart }], + }); + + store.set(clearQuickstartsAtom); + const quickstarts = store.get(quickstartsAtom); + expect(quickstarts).toEqual({}); + }); + + test('should clear all quickstarts except active', () => { + const store = createStore(); + store.set(quickstartsAtom, { + bar: [{ ...dummyQuickstart, metadata: { name: 'active' } }], + baz: [{ ...dummyQuickstart, metadata: { name: 'bla' } }], + foo: [{ ...dummyQuickstart }], + }); + + store.set(clearQuickstartsAtom, 'active'); + + const quickstarts = store.get(quickstartsAtom); + expect(quickstarts).toEqual({ bar: [{ ...dummyQuickstart, metadata: { name: 'active' } }] }); + }); +}); diff --git a/src/state/atoms/quickstartsAtom.ts b/src/state/atoms/quickstartsAtom.ts new file mode 100644 index 000000000..651147bd0 --- /dev/null +++ b/src/state/atoms/quickstartsAtom.ts @@ -0,0 +1,35 @@ +import { QuickStart } from '@patternfly/quickstarts'; +import { atom } from 'jotai'; +import { atomWithToggle } from './utils'; + +export const quickstartsDisabledAtom = atomWithToggle(false); +export const quickstartsAtom = atom<{ [key: string]: QuickStart[] }>({}); + +export const populateQuickstartsAppAtom = atom(null, (_get, set, { app, quickstarts }: { app: string; quickstarts: QuickStart[] }) => { + set(quickstartsAtom, (prev) => ({ + ...prev, + [app]: quickstarts, + })); +}); + +export const addQuickstartToAppAtom = atom(null, (_get, set, { app, quickstart }: { app: string; quickstart: QuickStart }) => { + set(quickstartsAtom, (prev) => ({ + ...prev, + [app]: [...(prev?.[app] ?? []), quickstart], + })); +}); + +export const clearQuickstartsAtom = atom(null, (_get, set, activeQuickstart?: string) => { + set(quickstartsAtom, (prev) => { + // keep currently opened quickstart + return Object.entries(prev).reduce<{ [key: string]: QuickStart[] }>((acc, [namespace, quickstarts]) => { + const clearedQuickstarts = quickstarts.filter((qs) => { + return qs?.metadata?.name === activeQuickstart; + }); + if (clearedQuickstarts.length > 0) { + acc[namespace] = clearedQuickstarts; + } + return acc; + }, {}); + }); +}); From af0b9859a68568ab393fbfb72c26a9f0c8521530 Mon Sep 17 00:00:00 2001 From: Martin Marosi Date: Mon, 29 Jul 2024 11:44:19 +0200 Subject: [PATCH 073/151] Remove obsolete document title redux state. --- src/bootstrap.tsx | 11 ++--------- src/redux/action-types.ts | 1 - src/redux/actions.ts | 5 ----- src/redux/chromeReducers.ts | 7 ------- src/redux/index.ts | 4 +--- src/redux/store.d.ts | 1 - 6 files changed, 3 insertions(+), 26 deletions(-) diff --git a/src/bootstrap.tsx b/src/bootstrap.tsx index 6a3a06281..0c201d627 100644 --- a/src/bootstrap.tsx +++ b/src/bootstrap.tsx @@ -1,6 +1,6 @@ -import React, { Suspense, useContext, useEffect, useMemo, useState } from 'react'; +import React, { Suspense, useContext, useEffect, useState } from 'react'; import { createRoot } from 'react-dom/client'; -import { Provider, useSelector } from 'react-redux'; +import { Provider } from 'react-redux'; import { IntlProvider, ReactIntlErrorCode } from 'react-intl'; import { Provider as JotaiProvider, useSetAtom } from 'jotai'; @@ -8,7 +8,6 @@ import { spinUpStore } from './redux/redux-config'; import RootApp from './components/RootApp'; import registerAnalyticsObserver from './analytics/analyticsObserver'; import { ITLess, getEnv, trustarcScriptSetup } from './utils/common'; -import { ReduxState } from './redux/store'; import OIDCProvider from './auth/OIDCConnector/OIDCProvider'; import messages from './locales/data.json'; import ErrorBoundary from './components/ErrorComponents/ErrorBoundary'; @@ -47,16 +46,10 @@ const App = ({ initApp }: { initApp: (...args: Parameters chrome?.documentTitle); const [cookieElement, setCookieElement] = useState(null); useInitializeAnalytics(); - useMemo(() => { - const title = typeof documentTitle === 'string' ? `${documentTitle} | Hybrid Cloud Console` : 'Hybrid Cloud Console'; - document.title = title; - }, [documentTitle]); - return ; }; diff --git a/src/redux/action-types.ts b/src/redux/action-types.ts index 3e04999e4..17bb1a40e 100644 --- a/src/redux/action-types.ts +++ b/src/redux/action-types.ts @@ -11,7 +11,6 @@ export const GLOBAL_FILTER_UPDATE = '@@chrome/global-filter-update'; export const GLOBAL_FILTER_TOGGLE = '@@chrome/global-filter-toggle'; export const GLOBAL_FILTER_REMOVE = '@@chrome/global-filter-remove'; -export const UPDATE_DOCUMENT_TITLE_REDUCER = '@@chrome/update-document-title'; export const MARK_ACTIVE_PRODUCT = '@@chrome/mark-active-product'; export const STORE_INITIAL_HASH = '@@chrome/store-initial-hash'; diff --git a/src/redux/actions.ts b/src/redux/actions.ts index f4a3b617a..e56d71c71 100644 --- a/src/redux/actions.ts +++ b/src/redux/actions.ts @@ -81,11 +81,6 @@ export const onToggle = () => ({ type: 'NAVIGATION_TOGGLE', }); -export const updateDocumentTitle = (title: string) => ({ - type: actionTypes.UPDATE_DOCUMENT_TITLE_REDUCER, - payload: title, -}); - export const markActiveProduct = (product?: string) => ({ type: actionTypes.MARK_ACTIVE_PRODUCT, payload: product, diff --git a/src/redux/chromeReducers.ts b/src/redux/chromeReducers.ts index 74874dfac..d16f7ca46 100644 --- a/src/redux/chromeReducers.ts +++ b/src/redux/chromeReducers.ts @@ -24,13 +24,6 @@ export function onPageObjectId(state: ChromeState, { payload }: { payload: strin }; } -export function documentTitleReducer(state: ChromeState, { payload }: { payload: string }): ChromeState { - return { - ...state, - documentTitle: payload, - }; -} - export function markActiveProduct(state: ChromeState, { payload }: { payload?: string }): ChromeState { return { ...state, diff --git a/src/redux/index.ts b/src/redux/index.ts index 820284014..cdaa2e956 100644 --- a/src/redux/index.ts +++ b/src/redux/index.ts @@ -1,6 +1,6 @@ import { applyReducerHash } from '@redhat-cloud-services/frontend-components-utilities/ReducerRegistry'; -import { documentTitleReducer, loginReducer, markActiveProduct, onPageAction, onPageObjectId } from './chromeReducers'; +import { loginReducer, markActiveProduct, onPageAction, onPageObjectId } from './chromeReducers'; import { globalFilterDefaultState, onGetAllSIDs, @@ -25,7 +25,6 @@ import { GLOBAL_FILTER_TOGGLE, GLOBAL_FILTER_UPDATE, MARK_ACTIVE_PRODUCT, - UPDATE_DOCUMENT_TITLE_REDUCER, USER_LOGIN, } from './action-types'; import { ChromeState, GlobalFilterState, ReduxState } from './store'; @@ -35,7 +34,6 @@ const reducers = { [USER_LOGIN]: loginReducer, [CHROME_PAGE_ACTION]: onPageAction, [CHROME_PAGE_OBJECT]: onPageObjectId, - [UPDATE_DOCUMENT_TITLE_REDUCER]: documentTitleReducer, [MARK_ACTIVE_PRODUCT]: markActiveProduct, }; diff --git a/src/redux/store.d.ts b/src/redux/store.d.ts index ab78b7147..1ae8a2209 100644 --- a/src/redux/store.d.ts +++ b/src/redux/store.d.ts @@ -6,7 +6,6 @@ export type ChromeState = { pageAction?: string; pageObjectId?: string; initialHash?: string; - documentTitle?: string; }; export type GlobalFilterWorkloads = { From 165b6f8b2bc21a7949a0a204cf18b204d14335fa Mon Sep 17 00:00:00 2001 From: Martin Marosi Date: Mon, 29 Jul 2024 12:26:08 +0200 Subject: [PATCH 074/151] Remove obsolete initial hash state variable. --- src/chrome/create-chrome.ts | 12 ------------ src/redux/action-types.ts | 2 -- src/redux/store.d.ts | 1 - 3 files changed, 15 deletions(-) diff --git a/src/chrome/create-chrome.ts b/src/chrome/create-chrome.ts index e6a18a626..15f193643 100644 --- a/src/chrome/create-chrome.ts +++ b/src/chrome/create-chrome.ts @@ -15,7 +15,6 @@ import { middlewareListener } from '../redux/redux-config'; import { clearAnsibleTrialFlag, isAnsibleTrialFlagActive, setAnsibleTrialFlag } from '../utils/isAnsibleTrialFlagActive'; import chromeHistory from '../utils/chromeHistory'; import { ReduxState } from '../redux/store'; -import { STORE_INITIAL_HASH } from '../redux/action-types'; import { FlagTagsFilter } from '../@types/types'; import useBundle, { bundleMapping, getUrl } from '../hooks/useBundle'; import { warnDuplicatePkg } from './warnDuplicatePackages'; @@ -146,17 +145,6 @@ export const createChromeContext = ({ }, identifyApp, hideGlobalFilter: (isHidden: boolean) => { - const initialHash = store.getState()?.chrome?.initialHash; - /** - * Restore app URL hash fragment after the global filter is disabled - */ - if (initialHash) { - chromeHistory.replace({ - ...chromeHistory.location, - hash: initialHash, - }); - dispatch({ type: STORE_INITIAL_HASH }); - } dispatch(toggleGlobalFilter(isHidden)); }, isBeta: () => isPreview, diff --git a/src/redux/action-types.ts b/src/redux/action-types.ts index 17bb1a40e..c8f1537d6 100644 --- a/src/redux/action-types.ts +++ b/src/redux/action-types.ts @@ -12,5 +12,3 @@ export const GLOBAL_FILTER_TOGGLE = '@@chrome/global-filter-toggle'; export const GLOBAL_FILTER_REMOVE = '@@chrome/global-filter-remove'; export const MARK_ACTIVE_PRODUCT = '@@chrome/mark-active-product'; - -export const STORE_INITIAL_HASH = '@@chrome/store-initial-hash'; diff --git a/src/redux/store.d.ts b/src/redux/store.d.ts index 1ae8a2209..1ed850ad0 100644 --- a/src/redux/store.d.ts +++ b/src/redux/store.d.ts @@ -5,7 +5,6 @@ export type ChromeState = { missingIDP?: boolean; pageAction?: string; pageObjectId?: string; - initialHash?: string; }; export type GlobalFilterWorkloads = { From 576ed3ce623802faae865441deb6df34f3d888b3 Mon Sep 17 00:00:00 2001 From: Martin Marosi Date: Mon, 29 Jul 2024 12:31:35 +0200 Subject: [PATCH 075/151] Migrate active product Redux state to Jotai. --- .../ErrorComponents/GatewayErrorComponent.tsx | 9 ++++----- src/components/Navigation/ChromeNavItem.tsx | 10 +++++----- src/components/Stratosphere/RedirectBanner.tsx | 6 +++--- src/redux/action-types.ts | 2 -- src/redux/actions.ts | 5 ----- src/redux/chromeReducers.ts | 7 ------- src/redux/index.ts | 4 +--- src/redux/store.d.ts | 1 - src/state/atoms/activeProductAtom.ts | 3 +++ 9 files changed, 16 insertions(+), 31 deletions(-) create mode 100644 src/state/atoms/activeProductAtom.ts diff --git a/src/components/ErrorComponents/GatewayErrorComponent.tsx b/src/components/ErrorComponents/GatewayErrorComponent.tsx index 85c25fe16..bbbe7b725 100644 --- a/src/components/ErrorComponents/GatewayErrorComponent.tsx +++ b/src/components/ErrorComponents/GatewayErrorComponent.tsx @@ -1,16 +1,15 @@ import React, { Fragment } from 'react'; import NotAuthorized from '@redhat-cloud-services/frontend-components/NotAuthorized'; -import { useSelector } from 'react-redux'; import sanitizeHtml from 'sanitize-html'; +import { useAtomValue } from 'jotai'; +import { Text, TextContent } from '@patternfly/react-core/dist/dynamic/components/Text'; -import type { ReduxState } from '../../redux/store'; import ChromeLink from '../ChromeLink/ChromeLink'; -import { Text, TextContent } from '@patternfly/react-core/dist/dynamic/components/Text'; import { useIntl } from 'react-intl'; import Messages from '../../locales/Messages'; import { ThreeScaleError } from '../../utils/responseInterceptors'; -import { useAtomValue } from 'jotai'; import { activeModuleAtom } from '../../state/atoms/activeModuleAtom'; +import { activeProductAtom } from '../../state/atoms/activeProductAtom'; export type GatewayErrorComponentProps = { error: ThreeScaleError; @@ -51,7 +50,7 @@ const Description = ({ detail, complianceError }: DescriptionProps) => { const GatewayErrorComponent = ({ error }: GatewayErrorComponentProps) => { const activeModule = useAtomValue(activeModuleAtom); - const activeProduct = useSelector((state: ReduxState) => state.chrome.activeProduct); + const activeProduct = useAtomValue(activeProductAtom); // get active product, fallback to module name if product is not defined const serviceName = activeProduct || activeModule; return } serviceName={serviceName} />; diff --git a/src/components/Navigation/ChromeNavItem.tsx b/src/components/Navigation/ChromeNavItem.tsx index 4a351b176..7430951c6 100644 --- a/src/components/Navigation/ChromeNavItem.tsx +++ b/src/components/Navigation/ChromeNavItem.tsx @@ -9,14 +9,14 @@ import StarIcon from '@patternfly/react-icons/dist/dynamic/icons/star-icon'; import { titleCase } from 'title-case'; import classNames from 'classnames'; import get from 'lodash/get'; -import { useAtomValue } from 'jotai'; +import { useAtomValue, useSetAtom } from 'jotai'; import ChromeLink, { LinkWrapperProps } from '../ChromeLink/ChromeLink'; -import { useDispatch, useSelector } from 'react-redux'; -import { markActiveProduct } from '../../redux/actions'; +import { useSelector } from 'react-redux'; import { ChromeNavItemProps } from '../../@types/types'; import useFavoritePagesWrapper from '../../hooks/useFavoritePagesWrapper'; import { isPreviewAtom } from '../../state/atoms/releaseAtom'; +import { activeProductAtom } from '../../state/atoms/activeProductAtom'; const ChromeNavItem = ({ appId, @@ -33,13 +33,13 @@ const ChromeNavItem = ({ }: ChromeNavItemProps) => { const isPreview = useAtomValue(isPreviewAtom); const hasNotifier = useSelector((state) => get(state, notifier)); - const dispatch = useDispatch(); + const markActiveProduct = useSetAtom(activeProductAtom); const { favoritePages } = useFavoritePagesWrapper(); const isFavorited = useMemo(() => favoritePages.find(({ favorite, pathname }) => favorite && pathname === href), [href, favoritePages]); useEffect(() => { if (active) { - dispatch(markActiveProduct(product)); + markActiveProduct(product); } }, [active]); diff --git a/src/components/Stratosphere/RedirectBanner.tsx b/src/components/Stratosphere/RedirectBanner.tsx index ca6ef25f0..f7d7a2ec7 100644 --- a/src/components/Stratosphere/RedirectBanner.tsx +++ b/src/components/Stratosphere/RedirectBanner.tsx @@ -1,16 +1,16 @@ import React from 'react'; import { Alert, AlertActionCloseButton } from '@patternfly/react-core/dist/dynamic/components/Alert'; import { Text, TextContent } from '@patternfly/react-core/dist/dynamic/components/Text'; -import { useSelector } from 'react-redux'; import { useLocation, useNavigate } from 'react-router-dom'; -import { ReduxState } from '../../redux/store'; +import { useAtomValue } from 'jotai'; import useMarketplacePartner from '../../hooks/useMarketplacePartner'; +import { activeProductAtom } from '../../state/atoms/activeProductAtom'; const RedirectBanner = () => { const { pathname, hash, state } = useLocation(); const { partnerId, partner, removePartnerParam } = useMarketplacePartner(); const navigate = useNavigate(); - const product = useSelector((state) => state.chrome.activeProduct); + const product = useAtomValue(activeProductAtom); const handleClose = () => { // remove only the flag search param diff --git a/src/redux/action-types.ts b/src/redux/action-types.ts index c8f1537d6..bac1c40e2 100644 --- a/src/redux/action-types.ts +++ b/src/redux/action-types.ts @@ -10,5 +10,3 @@ export const GLOBAL_FILTER_SCOPE = '@@chrome/set-global-filter-scope'; export const GLOBAL_FILTER_UPDATE = '@@chrome/global-filter-update'; export const GLOBAL_FILTER_TOGGLE = '@@chrome/global-filter-toggle'; export const GLOBAL_FILTER_REMOVE = '@@chrome/global-filter-remove'; - -export const MARK_ACTIVE_PRODUCT = '@@chrome/mark-active-product'; diff --git a/src/redux/actions.ts b/src/redux/actions.ts index e56d71c71..d5d0aca1b 100644 --- a/src/redux/actions.ts +++ b/src/redux/actions.ts @@ -80,8 +80,3 @@ export function removeGlobalFilter(isHidden = true) { export const onToggle = () => ({ type: 'NAVIGATION_TOGGLE', }); - -export const markActiveProduct = (product?: string) => ({ - type: actionTypes.MARK_ACTIVE_PRODUCT, - payload: product, -}); diff --git a/src/redux/chromeReducers.ts b/src/redux/chromeReducers.ts index d16f7ca46..24fb1866b 100644 --- a/src/redux/chromeReducers.ts +++ b/src/redux/chromeReducers.ts @@ -23,10 +23,3 @@ export function onPageObjectId(state: ChromeState, { payload }: { payload: strin pageObjectId: payload, }; } - -export function markActiveProduct(state: ChromeState, { payload }: { payload?: string }): ChromeState { - return { - ...state, - activeProduct: payload, - }; -} diff --git a/src/redux/index.ts b/src/redux/index.ts index cdaa2e956..0acca4145 100644 --- a/src/redux/index.ts +++ b/src/redux/index.ts @@ -1,6 +1,6 @@ import { applyReducerHash } from '@redhat-cloud-services/frontend-components-utilities/ReducerRegistry'; -import { loginReducer, markActiveProduct, onPageAction, onPageObjectId } from './chromeReducers'; +import { loginReducer, onPageAction, onPageObjectId } from './chromeReducers'; import { globalFilterDefaultState, onGetAllSIDs, @@ -24,7 +24,6 @@ import { GLOBAL_FILTER_SCOPE, GLOBAL_FILTER_TOGGLE, GLOBAL_FILTER_UPDATE, - MARK_ACTIVE_PRODUCT, USER_LOGIN, } from './action-types'; import { ChromeState, GlobalFilterState, ReduxState } from './store'; @@ -34,7 +33,6 @@ const reducers = { [USER_LOGIN]: loginReducer, [CHROME_PAGE_ACTION]: onPageAction, [CHROME_PAGE_OBJECT]: onPageObjectId, - [MARK_ACTIVE_PRODUCT]: markActiveProduct, }; const globalFilter = { diff --git a/src/redux/store.d.ts b/src/redux/store.d.ts index 1ed850ad0..63684003c 100644 --- a/src/redux/store.d.ts +++ b/src/redux/store.d.ts @@ -1,7 +1,6 @@ import { FlagTagsFilter } from '../@types/types'; export type ChromeState = { - activeProduct?: string; missingIDP?: boolean; pageAction?: string; pageObjectId?: string; diff --git a/src/state/atoms/activeProductAtom.ts b/src/state/atoms/activeProductAtom.ts new file mode 100644 index 000000000..4800559ba --- /dev/null +++ b/src/state/atoms/activeProductAtom.ts @@ -0,0 +1,3 @@ +import { atom } from 'jotai'; + +export const activeProductAtom = atom(undefined); From d1b7b70d0e8894148571cc5c1b46475a4acac0ab Mon Sep 17 00:00:00 2001 From: Martin Marosi Date: Mon, 29 Jul 2024 12:45:54 +0200 Subject: [PATCH 076/151] Remove obsolete IDP checker. --- src/components/IDPChecker/IDPChecker.test.js | 161 ------------------ src/components/IDPChecker/IDPChecker.tsx | 60 ------- .../__snapshots__/IDPChecker.test.js.snap | 11 -- src/redux/action-types.ts | 2 - src/redux/actions.ts | 12 -- src/redux/chromeReducers.ts | 10 -- src/redux/index.ts | 4 +- src/redux/store.d.ts | 1 - 8 files changed, 1 insertion(+), 260 deletions(-) delete mode 100644 src/components/IDPChecker/IDPChecker.test.js delete mode 100644 src/components/IDPChecker/IDPChecker.tsx delete mode 100644 src/components/IDPChecker/__snapshots__/IDPChecker.test.js.snap diff --git a/src/components/IDPChecker/IDPChecker.test.js b/src/components/IDPChecker/IDPChecker.test.js deleted file mode 100644 index eb0a35a10..000000000 --- a/src/components/IDPChecker/IDPChecker.test.js +++ /dev/null @@ -1,161 +0,0 @@ -import React from 'react'; -import axios from 'axios'; -import { render, screen } from '@testing-library/react'; -import { Provider } from 'react-redux'; -import { act } from 'react-dom/test-utils'; -import configureStore from 'redux-mock-store'; -import * as utils from '../../utils/common'; -import IDPChecker from './IDPChecker'; -import ChromeAuthContext from '../../auth/ChromeAuthContext'; - -jest.mock('../../utils/common', () => { - const utils = jest.requireActual('../../utils/common'); - return { - __esModule: true, - ...utils, - ITLess: jest.fn(), - }; -}); - -jest.mock('axios', () => { - const axios = jest.requireActual('axios'); - return { - __esModule: true, - ...axios, - get: jest.fn(), - }; -}); - -describe('', () => { - const ITLessSpy = jest.spyOn(utils, 'ITLess'); - const getSpy = jest.spyOn(axios, 'get'); - let mockStore; - const initialState = { - chrome: { - user: { - foo: 'bar', - }, - missingIDP: false, - }, - }; - - beforeEach(() => { - mockStore = configureStore(); - ITLessSpy.mockReturnValue(true); - getSpy.mockImplementation(() => Promise.resolve({})); - }); - - test('should render children in non fedRamp env', () => { - ITLessSpy.mockReturnValueOnce(false); - const store = mockStore(initialState); - const { container, queryAllByTestId } = render( - - - - OK - - - - ); - - expect(queryAllByTestId('foo')).toHaveLength(1); - expect(container).toMatchSnapshot(); - }); - - test('should render error state if IDP is missing in fedramp env', async () => { - const store = mockStore({ - ...initialState, - chrome: { - missingIDP: true, - }, - }); - - await act(async () => { - render( - - - - OK - - - - ); - }); - - expect(screen.queryAllByTestId('foo')).toHaveLength(0); - expect(screen.getAllByText('Authorization failure')).toHaveLength(1); - }); - - test('should render error state if IDP test API returns 403', async () => { - getSpy.mockImplementationOnce(() => - Promise.reject({ - response: { - status: 403, - }, - message: 'Insights authorization failed - account number not in allow list', - }) - ); - const store = mockStore(initialState); - - await act(async () => { - render( - - - - OK - - - - ); - }); - - expect(screen.queryAllByTestId('foo')).toHaveLength(0); - expect(screen.getAllByText('Authorization failure')).toHaveLength(1); - }); - - test('should render children if IDP validation passes', async () => { - const store = mockStore(initialState); - - await act(async () => { - render( - - - - OK - - - - ); - }); - - expect(screen.queryAllByTestId('foo')).toHaveLength(1); - }); -}); diff --git a/src/components/IDPChecker/IDPChecker.tsx b/src/components/IDPChecker/IDPChecker.tsx deleted file mode 100644 index 2c8af73db..000000000 --- a/src/components/IDPChecker/IDPChecker.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import React, { Fragment, useContext, useEffect, useRef, useState } from 'react'; -import axios from 'axios'; -import { useSelector } from 'react-redux'; - -import { ITLess } from '../../utils/common'; -import IDPError from '../ErrorComponents/IDPError'; -import { ReduxState } from '../../redux/store'; -import ChromeAuthContext from '../../auth/ChromeAuthContext'; - -const IDPStatuses = { - OK: 'OK', - UNKNOWN: 'UNKNOWN', - PENDING: 'PENDING', - ERROR: 'ERROR', -}; - -const IDPChecker: React.FunctionComponent = ({ children }) => { - const ITLessEnv = ITLess(); - const missingIDP = useSelector(({ chrome }: ReduxState) => chrome?.missingIDP); - const [status, setStatus] = useState(() => { - if (ITLessEnv) { - return missingIDP === true ? IDPStatuses.ERROR : IDPStatuses.UNKNOWN; - } - return IDPStatuses.OK; - }); - const auth = useContext(ChromeAuthContext); - const allowStateChange = useRef(ITLessEnv); - - useEffect(() => { - if (ITLessEnv && status !== IDPStatuses.PENDING && auth.ready) { - allowStateChange.current && setStatus(IDPStatuses.PENDING); - axios - .get('/api/entitlements/v1/services') - .then(() => { - allowStateChange.current && setStatus(IDPStatuses.OK); - }) - .catch((err) => { - const authError = err.response.status === 403 && err.message === 'Insights authorization failed - account number not in allow list'; - allowStateChange.current && setStatus(authError ? IDPStatuses.ERROR : IDPStatuses.OK); - }); - } - }, [auth.ready, missingIDP]); - - useEffect(() => { - if (missingIDP === true) { - allowStateChange.current && setStatus(IDPStatuses.ERROR); - allowStateChange.current = false; - } - }, [missingIDP]); - - if (status === IDPStatuses.OK) { - return {children}; - } - if (status === IDPStatuses.ERROR) { - return ; - } - return null; -}; - -export default IDPChecker; diff --git a/src/components/IDPChecker/__snapshots__/IDPChecker.test.js.snap b/src/components/IDPChecker/__snapshots__/IDPChecker.test.js.snap deleted file mode 100644 index 6712f0132..000000000 --- a/src/components/IDPChecker/__snapshots__/IDPChecker.test.js.snap +++ /dev/null @@ -1,11 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[` should render children in non fedRamp env 1`] = ` -
- - OK - -
-`; diff --git a/src/redux/action-types.ts b/src/redux/action-types.ts index bac1c40e2..a314ba43e 100644 --- a/src/redux/action-types.ts +++ b/src/redux/action-types.ts @@ -1,5 +1,3 @@ -export const USER_LOGIN = '@@chrome/user-login'; - export const CHROME_PAGE_ACTION = '@@chrome/app-page-action'; export const CHROME_PAGE_OBJECT = '@@chrome/app-object-id'; diff --git a/src/redux/actions.ts b/src/redux/actions.ts index d5d0aca1b..1b34efb9d 100644 --- a/src/redux/actions.ts +++ b/src/redux/actions.ts @@ -1,22 +1,10 @@ import * as actionTypes from './action-types'; import { getAllSIDs, getAllTags, getAllWorkloads } from '../components/GlobalFilter/tagsApi'; import type { TagFilterOptions, TagPagination } from '../components/GlobalFilter/tagsApi'; -import type { ChromeUser } from '@redhat-cloud-services/types'; import type { FlagTagsFilter } from '../@types/types'; -export function userLogIn(user: ChromeUser | boolean) { - return { - type: actionTypes.USER_LOGIN, - payload: user, - }; -} - export type AppNavClickItem = { id?: string; custom?: boolean }; -/* - *TODO: The event type is deliberately nonse. It will start failing once we mirate rest of the app and we will figure out the correct type - */ - export function appAction(action: string) { return { type: actionTypes.CHROME_PAGE_ACTION, payload: action }; } diff --git a/src/redux/chromeReducers.ts b/src/redux/chromeReducers.ts index 24fb1866b..6844fe8ec 100644 --- a/src/redux/chromeReducers.ts +++ b/src/redux/chromeReducers.ts @@ -1,15 +1,5 @@ -import { ChromeUser } from '@redhat-cloud-services/types'; -import { ITLess } from '../utils/common'; import { ChromeState } from './store'; -export function loginReducer(state: ChromeState, { payload }: { payload: ChromeUser }): ChromeState { - const missingIDP = ITLess() && !Object.prototype.hasOwnProperty.call(payload?.identity, 'idp'); - return { - ...state, - missingIDP, - }; -} - export function onPageAction(state: ChromeState, { payload }: { payload: string }): ChromeState { return { ...state, diff --git a/src/redux/index.ts b/src/redux/index.ts index 0acca4145..45573b3bd 100644 --- a/src/redux/index.ts +++ b/src/redux/index.ts @@ -1,6 +1,6 @@ import { applyReducerHash } from '@redhat-cloud-services/frontend-components-utilities/ReducerRegistry'; -import { loginReducer, onPageAction, onPageObjectId } from './chromeReducers'; +import { onPageAction, onPageObjectId } from './chromeReducers'; import { globalFilterDefaultState, onGetAllSIDs, @@ -24,13 +24,11 @@ import { GLOBAL_FILTER_SCOPE, GLOBAL_FILTER_TOGGLE, GLOBAL_FILTER_UPDATE, - USER_LOGIN, } from './action-types'; import { ChromeState, GlobalFilterState, ReduxState } from './store'; import { AnyAction } from 'redux'; const reducers = { - [USER_LOGIN]: loginReducer, [CHROME_PAGE_ACTION]: onPageAction, [CHROME_PAGE_OBJECT]: onPageObjectId, }; diff --git a/src/redux/store.d.ts b/src/redux/store.d.ts index 63684003c..5ec42282c 100644 --- a/src/redux/store.d.ts +++ b/src/redux/store.d.ts @@ -1,7 +1,6 @@ import { FlagTagsFilter } from '../@types/types'; export type ChromeState = { - missingIDP?: boolean; pageAction?: string; pageObjectId?: string; }; From c8d8fbdfb45e9319af85500b13fa584a22d620e5 Mon Sep 17 00:00:00 2001 From: Martin Marosi Date: Mon, 29 Jul 2024 13:16:52 +0200 Subject: [PATCH 077/151] Move page attributes state from Redux to Jotai. --- src/chrome/create-chrome.ts | 7 +- src/layouts/DefaultLayout.test.js | 114 ++++++++++-------------------- src/redux/action-types.ts | 3 - src/redux/actions.test.js | 34 +-------- src/redux/actions.ts | 8 --- src/redux/chromeReducers.test.js | 108 ---------------------------- src/redux/chromeReducers.ts | 15 ---- src/redux/index.ts | 10 +-- src/redux/store.d.ts | 1 - src/state/atoms/pageAtom.ts | 4 ++ src/state/chromeStore.ts | 4 ++ src/utils/useOuiaTags.ts | 9 +-- 12 files changed, 55 insertions(+), 262 deletions(-) delete mode 100644 src/redux/chromeReducers.test.js delete mode 100644 src/redux/chromeReducers.ts create mode 100644 src/state/atoms/pageAtom.ts diff --git a/src/chrome/create-chrome.ts b/src/chrome/create-chrome.ts index 15f193643..3b5d27385 100644 --- a/src/chrome/create-chrome.ts +++ b/src/chrome/create-chrome.ts @@ -5,7 +5,7 @@ import { AnalyticsBrowser } from '@segment/analytics-next'; import get from 'lodash/get'; import Cookies from 'js-cookie'; -import { appAction, appObjectId, globalFilterScope, removeGlobalFilter, toggleGlobalFilter } from '../redux/actions'; +import { globalFilterScope, removeGlobalFilter, toggleGlobalFilter } from '../redux/actions'; import { ITLess, getEnv, getEnvDetails, isProd, updateDocumentTitle } from '../utils/common'; import { createSupportCase } from '../utils/createCase'; import debugFunctions from '../utils/debugFunctions'; @@ -28,6 +28,7 @@ import { isFeedbackModalOpenAtom } from '../state/atoms/feedbackModalAtom'; import { usePendoFeedback } from '../components/Feedback'; import { NavListener, activeAppAtom } from '../state/atoms/activeAppAtom'; import { isDebuggerEnabledAtom } from '../state/atoms/debuggerModalatom'; +import { appActionAtom, pageObjectIdAtom } from '../state/atoms/pageAtom'; export type CreateChromeContextConfig = { useGlobalFilter: (callback: (selectedTags?: FlagTagsFilter) => any) => ReturnType; @@ -62,8 +63,8 @@ export const createChromeContext = ({ const visibilityFunctions = getVisibilityFunctions(); const dispatch = store.dispatch; const actions = { - appAction: (action: string) => dispatch(appAction(action)), - appObjectId: (objectId: string) => dispatch(appObjectId(objectId)), + appAction: (action: string) => chromeStore.set(appActionAtom, action), + appObjectId: (objectId: string) => chromeStore.set(pageObjectIdAtom, objectId), appNavClick: (item: string) => chromeStore.set(activeAppAtom, item), globalFilterScope: (scope: string) => dispatch(globalFilterScope(scope)), registerModule: (module: string, manifest?: string) => registerModule({ module, manifest }), diff --git a/src/layouts/DefaultLayout.test.js b/src/layouts/DefaultLayout.test.js index 08b10bfb4..6c8ba6d70 100644 --- a/src/layouts/DefaultLayout.test.js +++ b/src/layouts/DefaultLayout.test.js @@ -2,19 +2,19 @@ import React from 'react'; import { MemoryRouter } from 'react-router-dom'; import DefaultLayout from './DefaultLayout'; import { render } from '@testing-library/react'; -import configureStore from 'redux-mock-store'; -import { Provider } from 'react-redux'; + import { Provider as ProviderJotai } from 'jotai'; import { useHydrateAtoms } from 'jotai/utils'; import { activeAppAtom } from '../state/atoms/activeAppAtom'; +import { appActionAtom, pageObjectIdAtom } from '../state/atoms/pageAtom'; const HydrateAtoms = ({ initialValues, children }) => { useHydrateAtoms(initialValues); return children; }; -const TestProvider = ({ initialValues, children }) => ( - +const TestProvider = ({ initialValues, children, store }) => ( + {children} ); @@ -28,8 +28,6 @@ jest.mock('../state/atoms/releaseAtom', () => { }); describe('DefaultLayout', () => { - let initialState; - let mockStore; let config; beforeEach(() => { @@ -39,110 +37,74 @@ describe('DefaultLayout', () => { appName: 'foo', }, }; - mockStore = configureStore(); - initialState = { - chrome: { - activeLocation: 'some-location', - appId: 'app-id', - navigation: { - '/': { - navItems: [], - }, - insights: { - navItems: [], - }, - }, - }, - globalFilter: { - tags: {}, - sid: {}, - workloads: {}, - }, - }; }); it('should render correctly - no data', async () => { - const store = mockStore({ chrome: {} }); const { container } = render( - - - - - + + + ); expect(container.querySelector('#chrome-app-render-root')).toMatchSnapshot(); }); it('should render correctly', () => { - const store = mockStore(initialState); const { container } = render( - - - - - + + + ); expect(container.querySelector('#chrome-app-render-root')).toMatchSnapshot(); }); it('should render correctly with pageAction', () => { - const store = mockStore({ - chrome: { - ...initialState.chrome, - pageAction: 'some-action', - }, - globalFilter: {}, - }); const { container } = render( - - - - - - + + + + ); expect(container.querySelector('#chrome-app-render-root')).toMatchSnapshot(); }); it('should render correctly with pageObjectId', () => { - const store = mockStore({ - chrome: { - ...initialState.chrome, - pageObjectId: 'some-object-id', - }, - }); const { container } = render( - - - - - - + + + + ); expect(container.querySelector('#chrome-app-render-root')).toMatchSnapshot(); }); it('should render correctly with pageObjectId and pageAction', () => { - const store = mockStore({ - chrome: { - ...initialState.chrome, - pageAction: 'some-action', - pageObjectId: 'some-object-id', - }, - }); const { container } = render( - - - - - - + + + + ); expect(container.querySelector('#chrome-app-render-root')).toMatchSnapshot(); diff --git a/src/redux/action-types.ts b/src/redux/action-types.ts index a314ba43e..68172886b 100644 --- a/src/redux/action-types.ts +++ b/src/redux/action-types.ts @@ -1,6 +1,3 @@ -export const CHROME_PAGE_ACTION = '@@chrome/app-page-action'; -export const CHROME_PAGE_OBJECT = '@@chrome/app-object-id'; - export const CHROME_GET_ALL_TAGS = '@@chrome/get-all-tags'; export const CHROME_GET_ALL_SIDS = '@@chrome/get-all-sids'; export const CHROME_GET_ALL_WORKLOADS = '@@chrome/get-all-workloads'; diff --git a/src/redux/actions.test.js b/src/redux/actions.test.js index 42a60d327..ac050453d 100644 --- a/src/redux/actions.test.js +++ b/src/redux/actions.test.js @@ -1,36 +1,4 @@ -import { appAction, appObjectId, toggleGlobalFilter } from './actions'; - -describe('appAction', () => { - it('should return correct action with data', () => { - expect(appAction('test-action')).toEqual({ - type: '@@chrome/app-page-action', - payload: 'test-action', - }); - }); - - it('should return correct action without data', () => { - expect(appAction()).toEqual({ - type: '@@chrome/app-page-action', - payload: undefined, - }); - }); -}); - -describe('appObjectId', () => { - it('should return correct action with data', () => { - expect(appObjectId('test-id')).toEqual({ - type: '@@chrome/app-object-id', - payload: 'test-id', - }); - }); - - it('should return correct action without data', () => { - expect(appObjectId()).toEqual({ - type: '@@chrome/app-object-id', - payload: undefined, - }); - }); -}); +import { toggleGlobalFilter } from './actions'; describe('toggleGlobalFilter', () => { it('should return correct action with isHidden = true', () => { diff --git a/src/redux/actions.ts b/src/redux/actions.ts index 1b34efb9d..fb3dedfaa 100644 --- a/src/redux/actions.ts +++ b/src/redux/actions.ts @@ -5,14 +5,6 @@ import type { FlagTagsFilter } from '../@types/types'; export type AppNavClickItem = { id?: string; custom?: boolean }; -export function appAction(action: string) { - return { type: actionTypes.CHROME_PAGE_ACTION, payload: action }; -} - -export function appObjectId(objectId: string) { - return { type: actionTypes.CHROME_PAGE_OBJECT, payload: objectId }; -} - export function fetchAllTags(filters?: TagFilterOptions, pagination?: TagPagination) { return { type: actionTypes.CHROME_GET_ALL_TAGS, diff --git a/src/redux/chromeReducers.test.js b/src/redux/chromeReducers.test.js deleted file mode 100644 index 061947e7b..000000000 --- a/src/redux/chromeReducers.test.js +++ /dev/null @@ -1,108 +0,0 @@ -import * as actions from './actions'; -import * as reducers from './chromeReducers'; - -const mockNav = [ - { - id: 'dashboard', - title: 'Dashboard', - }, - { - id: 'advisor', - title: 'Insights', - subItems: [ - { - id: 'actions', - title: 'Actions', - default: true, - }, - { - id: 'rules', - title: 'Rules', - }, - ], - }, - { - id: 'vulnerability', - title: 'Vulnerability', - }, - { - id: 'inventory', - title: 'Inventory', - }, - { - id: 'remediations', - title: 'Remediations', - }, -]; - -describe('Reducers', () => { - describe('Navigation', () => { - it('finds the app using identifyApp()', () => { - expect(() => actions.identifyApp('inventory', mockNav)).not.toThrowError('unknown app identifier: inventory'); - }); - }); - - describe('onPageAction', () => { - it('should add new pageAction', () => { - const state = reducers.onPageAction({ someState: {} }, actions.appAction('test-action')); - expect(state).toEqual({ - someState: {}, - pageAction: 'test-action', - }); - }); - - it('should remove pageAction', () => { - const state = reducers.onPageAction({ someState: {}, pageAction: 'test-action' }, actions.appAction()); - expect(state).toEqual({ - someState: {}, - pageAction: undefined, - }); - }); - - it('should replace pageAction', () => { - const state = reducers.onPageAction( - { - someState: {}, - pageAction: 'test-action', - }, - actions.appAction('different-action') - ); - expect(state).toEqual({ - someState: {}, - pageAction: 'different-action', - }); - }); - }); - - describe('onPageObjectId', () => { - it('should add new pageObjectId', () => { - const state = reducers.onPageObjectId({ someState: {} }, actions.appObjectId('test-object-id')); - expect(state).toEqual({ - someState: {}, - pageObjectId: 'test-object-id', - }); - }); - - it('should remove pageObjectId', () => { - const state = reducers.onPageObjectId({ someState: {}, pageObjectId: 'test-object-id' }, actions.appObjectId()); - expect(state).toEqual({ - someState: {}, - pageObjectId: undefined, - }); - }); - - it('should replace pageObjectId', () => { - const state = reducers.onPageObjectId( - { - someState: {}, - pageObjectId: 'test-object-id', - }, - actions.appObjectId('different-object-id') - ); - expect(state).toEqual({ - someState: {}, - pageObjectId: 'different-object-id', - }); - }); - }); -}); diff --git a/src/redux/chromeReducers.ts b/src/redux/chromeReducers.ts deleted file mode 100644 index 6844fe8ec..000000000 --- a/src/redux/chromeReducers.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { ChromeState } from './store'; - -export function onPageAction(state: ChromeState, { payload }: { payload: string }): ChromeState { - return { - ...state, - pageAction: payload, - }; -} - -export function onPageObjectId(state: ChromeState, { payload }: { payload: string }): ChromeState { - return { - ...state, - pageObjectId: payload, - }; -} diff --git a/src/redux/index.ts b/src/redux/index.ts index 45573b3bd..dfca213be 100644 --- a/src/redux/index.ts +++ b/src/redux/index.ts @@ -1,6 +1,5 @@ import { applyReducerHash } from '@redhat-cloud-services/frontend-components-utilities/ReducerRegistry'; -import { onPageAction, onPageObjectId } from './chromeReducers'; import { globalFilterDefaultState, onGetAllSIDs, @@ -18,8 +17,6 @@ import { CHROME_GET_ALL_SIDS, CHROME_GET_ALL_TAGS, CHROME_GET_ALL_WORKLOADS, - CHROME_PAGE_ACTION, - CHROME_PAGE_OBJECT, GLOBAL_FILTER_REMOVE, GLOBAL_FILTER_SCOPE, GLOBAL_FILTER_TOGGLE, @@ -28,11 +25,6 @@ import { import { ChromeState, GlobalFilterState, ReduxState } from './store'; import { AnyAction } from 'redux'; -const reducers = { - [CHROME_PAGE_ACTION]: onPageAction, - [CHROME_PAGE_OBJECT]: onPageObjectId, -}; - const globalFilter = { [`${CHROME_GET_ALL_TAGS}_FULFILLED`]: onGetAllTags, [`${CHROME_GET_ALL_TAGS}_PENDING`]: onGetAllTagsPending, @@ -56,7 +48,7 @@ export default function (): { globalFilter: (state: GlobalFilterState, action: AnyAction) => ChromeState; } { return { - chrome: (state = {}, action) => applyReducerHash(reducers)(state, action), + chrome: (state = {}, action) => applyReducerHash({})(state, action), globalFilter: (state = globalFilterDefaultState, action) => applyReducerHash(globalFilter)(state, action), }; } diff --git a/src/redux/store.d.ts b/src/redux/store.d.ts index 5ec42282c..b59c7375b 100644 --- a/src/redux/store.d.ts +++ b/src/redux/store.d.ts @@ -2,7 +2,6 @@ import { FlagTagsFilter } from '../@types/types'; export type ChromeState = { pageAction?: string; - pageObjectId?: string; }; export type GlobalFilterWorkloads = { diff --git a/src/state/atoms/pageAtom.ts b/src/state/atoms/pageAtom.ts new file mode 100644 index 000000000..582762fc5 --- /dev/null +++ b/src/state/atoms/pageAtom.ts @@ -0,0 +1,4 @@ +import { atom } from 'jotai'; + +export const pageObjectIdAtom = atom(undefined); +export const appActionAtom = atom(undefined); diff --git a/src/state/chromeStore.ts b/src/state/chromeStore.ts index 197007d73..e2a6f567a 100644 --- a/src/state/chromeStore.ts +++ b/src/state/chromeStore.ts @@ -7,6 +7,7 @@ import { gatewayErrorAtom } from './atoms/gatewayErrorAtom'; import { isFeedbackModalOpenAtom } from './atoms/feedbackModalAtom'; import { activeAppAtom } from './atoms/activeAppAtom'; import { isDebuggerEnabledAtom } from './atoms/debuggerModalatom'; +import { appActionAtom, pageObjectIdAtom } from './atoms/pageAtom'; const chromeStore = createStore(); @@ -20,6 +21,9 @@ chromeStore.set(isFeedbackModalOpenAtom, false); chromeStore.set(isPreviewAtom, false); chromeStore.set(activeAppAtom, undefined); chromeStore.set(isDebuggerEnabledAtom, false); +// page actions +chromeStore.set(pageObjectIdAtom, undefined); +chromeStore.set(appActionAtom, undefined); // globally handle subscription to activeModuleAtom chromeStore.sub(activeModuleAtom, () => { diff --git a/src/utils/useOuiaTags.ts b/src/utils/useOuiaTags.ts index a12835737..40a84e392 100644 --- a/src/utils/useOuiaTags.ts +++ b/src/utils/useOuiaTags.ts @@ -1,10 +1,9 @@ import { useEffect, useState } from 'react'; -import { shallowEqual, useSelector } from 'react-redux'; import { useLocation } from 'react-router-dom'; -import { ReduxState } from '../redux/store'; import { isAnsible } from '../hooks/useBundle'; import { activeAppAtom } from '../state/atoms/activeAppAtom'; import { useAtomValue } from 'jotai'; +import { appActionAtom, pageObjectIdAtom } from '../state/atoms/pageAtom'; export type OuiaTags = { landing?: 'true' | 'false'; @@ -21,10 +20,8 @@ const useOuiaTags = () => { 'data-ouia-safe': 'true', }); const { pathname } = useLocation(); - const { pageAction, pageObjectId } = useSelector( - ({ chrome: { pageAction, pageObjectId } }: ReduxState) => ({ pageAction, pageObjectId }), - shallowEqual - ); + const pageObjectId = useAtomValue(pageObjectIdAtom); + const pageAction = useAtomValue(appActionAtom); const activeApp = useAtomValue(activeAppAtom); useEffect(() => { From 19bf606e15fe0ca7dbf1879dd27514bed8679fc0 Mon Sep 17 00:00:00 2001 From: Martin Marosi Date: Mon, 29 Jul 2024 13:18:15 +0200 Subject: [PATCH 078/151] Remove empty chrome reducer. --- src/redux/index.ts | 7 ++----- src/redux/store.d.ts | 5 ----- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/src/redux/index.ts b/src/redux/index.ts index dfca213be..fe158f4c9 100644 --- a/src/redux/index.ts +++ b/src/redux/index.ts @@ -22,7 +22,7 @@ import { GLOBAL_FILTER_TOGGLE, GLOBAL_FILTER_UPDATE, } from './action-types'; -import { ChromeState, GlobalFilterState, ReduxState } from './store'; +import { GlobalFilterState, ReduxState } from './store'; import { AnyAction } from 'redux'; const globalFilter = { @@ -39,16 +39,13 @@ const globalFilter = { }; export const chromeInitialState: ReduxState = { - chrome: {}, globalFilter: globalFilterDefaultState, }; export default function (): { - chrome: (state: ChromeState, action: AnyAction) => ChromeState; - globalFilter: (state: GlobalFilterState, action: AnyAction) => ChromeState; + globalFilter: (state: GlobalFilterState, action: AnyAction) => GlobalFilterState; } { return { - chrome: (state = {}, action) => applyReducerHash({})(state, action), globalFilter: (state = globalFilterDefaultState, action) => applyReducerHash(globalFilter)(state, action), }; } diff --git a/src/redux/store.d.ts b/src/redux/store.d.ts index b59c7375b..b68017e73 100644 --- a/src/redux/store.d.ts +++ b/src/redux/store.d.ts @@ -1,9 +1,5 @@ import { FlagTagsFilter } from '../@types/types'; -export type ChromeState = { - pageAction?: string; -}; - export type GlobalFilterWorkloads = { selected?: boolean; page?: number; @@ -78,6 +74,5 @@ export type GlobalFilterState = { }; export type ReduxState = { - chrome: ChromeState; globalFilter: GlobalFilterState; }; From adba3fe1df8d1b0db6968e91e78de1d786fac487 Mon Sep 17 00:00:00 2001 From: Martin Marosi Date: Wed, 31 Jul 2024 09:24:40 +0200 Subject: [PATCH 079/151] Remove preview release. --- README.md | 8 ++-- config/webpack.config.js | 4 +- config/webpack.plugins.js | 3 +- package.json | 3 +- src/analytics/index.ts | 2 +- src/auth/OIDCConnector/OIDCProvider.tsx | 10 ++--- src/auth/OIDCConnector/utils.ts | 6 +-- src/components/AppFilter/useAppFilter.ts | 9 +--- .../Header/HeaderTests/Tools.test.js | 16 +------ src/components/Header/PreviewAlert.tsx | 21 ++------- src/components/Header/Tools.tsx | 36 ++-------------- src/components/RootApp/RootApp.tsx | 4 +- src/hooks/useAllLinks.ts | 9 ++-- src/hooks/useBundle.ts | 5 +-- src/hooks/useFavoritedServices.ts | 5 +-- src/index.ts | 19 +------- src/state/atoms/scalprumConfigAtom.ts | 9 +--- src/state/chromeStore.ts | 3 +- src/utils/cache.ts | 19 +------- src/utils/common.ts | 43 +++---------------- src/utils/createCase.ts | 17 +++----- src/utils/debugFunctions.ts | 1 - src/utils/fetchNavigationFiles.ts | 9 +--- src/utils/initUserConfig.ts | 24 +++-------- src/utils/useNavigation.ts | 6 +-- 25 files changed, 58 insertions(+), 233 deletions(-) diff --git a/README.md b/README.md index f669b794e..38f66849c 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ To run a script you have to install dependencies `npm install`. Then you are fre > npm run dev ``` -3. Open browser at `https://stage.foo.redhat.com:1337/` or Open browser at `https://stage.foo.redhat.com:1337/preview`. +3. Open browser at `https://stage.foo.redhat.com:1337/`. ### Running chrome with other applications locally @@ -64,8 +64,8 @@ You can spin chrome locally together with other applications. Use `LOCAL_APPS` t For illustration, to deploy Advisor together with Insights Chrome, you would require to -1. Run Advisor on any available port with `npm run start -- --port=8004` or `npm run start:beta -- --port=8004`, -2. Run Chrome and list the Advisor's port: `LOCAL_APPS=advisor:8004~http npm run dev` or `LOCAL_APPS=advisor:8004~http npm run dev:beta`. +1. Run Advisor on any available port with `npm run start -- --port=8004`, +2. Run Chrome and list the Advisor's port: `LOCAL_APPS=advisor:8004~http npm run dev`. #### Example 2 (using devServer route) @@ -85,7 +85,7 @@ devServer: { } ... ``` -3. Run insights-chrome with `npm run dev` or `npm run dev:beta`. +3. Run insights-chrome with `npm run dev`. ## Local search development diff --git a/config/webpack.config.js b/config/webpack.config.js index 80166f883..3b8969f0b 100644 --- a/config/webpack.config.js +++ b/config/webpack.config.js @@ -25,7 +25,7 @@ const PFGenerator = asGenerator((item, ...rest) => { return defaultTuples; }); -const publicPath = process.env.BETA === 'true' ? '/beta/apps/chrome/js/' : '/apps/chrome/js/'; +const publicPath = '/apps/chrome/js/'; const commonConfig = ({ dev }) => { /** @type { import("webpack").Configuration } */ return { @@ -150,7 +150,7 @@ const commonConfig = ({ dev }) => { ...proxy({ env: 'stage-beta', port: 1337, - appUrl: [/^\/*$/, /^\/beta\/*$/, /^\/preview\/*$/], + appUrl: [/^\/*$/], useProxy: true, publicPath, proxyVerbose: true, diff --git a/config/webpack.plugins.js b/config/webpack.plugins.js index 1ba60bc27..742efdb7d 100644 --- a/config/webpack.plugins.js +++ b/config/webpack.plugins.js @@ -58,8 +58,7 @@ const plugins = (dev = false, beta = false, restricted = false) => { inject: 'body', minify: false, filename: dev ? 'index.html' : '../index.html', - // FIXME: Change to /preview on May - base: beta ? '/beta/' : '/', + base: '/', templateParameters: { pf4styles: `/${beta ? 'beta/' : ''}apps/chrome/js/pf/pf4-v4.css`, pf5styles: `/${beta ? 'beta/' : ''}apps/chrome/js/pf/pf4-v5.css`, diff --git a/package.json b/package.json index 9e7200761..0fb5603e3 100644 --- a/package.json +++ b/package.json @@ -188,5 +188,6 @@ }, "nyc": { "report-dir": "cypress-coverage" - } + }, + "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" } diff --git a/src/analytics/index.ts b/src/analytics/index.ts index cbd78a516..543c44ced 100644 --- a/src/analytics/index.ts +++ b/src/analytics/index.ts @@ -15,7 +15,7 @@ function isInternalFlag(email: string, isInternal = false) { } function getUrl(type?: string, isPreview = false) { - if (['/beta', '/preview', '/'].includes(window.location.pathname)) { + if (['/'].includes(window.location.pathname)) { return 'landing'; } diff --git a/src/auth/OIDCConnector/OIDCProvider.tsx b/src/auth/OIDCConnector/OIDCProvider.tsx index bc9c455b2..b53800b3e 100644 --- a/src/auth/OIDCConnector/OIDCProvider.tsx +++ b/src/auth/OIDCConnector/OIDCProvider.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useMemo, useState } from 'react'; -import { DEFAULT_SSO_ROUTES, ITLess, isBeta, loadFedModules } from '../../utils/common'; +import { DEFAULT_SSO_ROUTES, ITLess, loadFedModules } from '../../utils/common'; import { AuthProvider, AuthProviderProps } from 'react-oidc-context'; import { WebStorageStateStore } from 'oidc-client-ts'; import platformUrl from '../platformUrl'; @@ -7,10 +7,6 @@ import { OIDCSecured } from './OIDCSecured'; import AppPlaceholder from '../../components/AppPlaceholder'; import { postbackUrlSetup } from '../offline'; -const LOCAL_PREVIEW = localStorage.getItem('chrome:local-preview') === 'true'; -// TODO: remove this once the local preview is enabled by default -const betaPartial = LOCAL_PREVIEW ? '' : isBeta() ? '/beta' : ''; - const OIDCProvider: React.FC = ({ children }) => { const [cookieElement, setCookieElement] = useState(null); const [state, setState] = useState< @@ -42,7 +38,7 @@ const OIDCProvider: React.FC = ({ children }) => { const authProviderProps: AuthProviderProps = useMemo( () => ({ client_id: ITLess() ? 'console-dot' : 'cloud-services', - silent_redirect_uri: `https://${window.location.host}${betaPartial}/apps/chrome/silent-check-sso.html`, + silent_redirect_uri: `https://${window.location.host}/apps/chrome/silent-check-sso.html`, automaticSilentRenew: true, redirect_uri: `${window.location.origin}`, authority: `${state?.ssoUrl}`, @@ -52,7 +48,7 @@ const OIDCProvider: React.FC = ({ children }) => { authorization_endpoint: `${state?.ssoUrl}realms/redhat-external/protocol/openid-connect/auth`, token_endpoint: `${state?.ssoUrl}realms/redhat-external/protocol/openid-connect/token`, end_session_endpoint: `${state?.ssoUrl}realms/redhat-external/protocol/openid-connect/logout`, - check_session_iframe: `https://${window.location.host}${betaPartial}/apps/chrome/silent-check-sso.html`, + check_session_iframe: `https://${window.location.host}/apps/chrome/silent-check-sso.html`, revocation_endpoint: `${state?.ssoUrl}realms/redhat-external/protocol/openid-connect/revoke`, }, // removes code_challenge query param from the url diff --git a/src/auth/OIDCConnector/utils.ts b/src/auth/OIDCConnector/utils.ts index 8a319fc12..42b002964 100644 --- a/src/auth/OIDCConnector/utils.ts +++ b/src/auth/OIDCConnector/utils.ts @@ -1,5 +1,5 @@ import { AuthContextProps } from 'react-oidc-context'; -import { ITLess, LOGIN_SCOPES_STORAGE_KEY, deleteLocalStorageItems, getRouterBasename, isBeta } from '../../utils/common'; +import { ITLess, LOGIN_SCOPES_STORAGE_KEY, deleteLocalStorageItems } from '../../utils/common'; import { GLOBAL_FILTER_KEY, OFFLINE_REDIRECT_STORAGE_KEY } from '../../utils/consts'; import Cookies from 'js-cookie'; import logger from '../logger'; @@ -44,8 +44,6 @@ export async function logout(auth: AuthContextProps, bounce?: boolean) { key.startsWith(GLOBAL_FILTER_KEY) ); deleteLocalStorageItems([...keys, OFFLINE_REDIRECT_STORAGE_KEY, LOGIN_SCOPES_STORAGE_KEY]); - // FIXME: Remove this one local preview is enabled by default - const pathname = isBeta() ? getRouterBasename() : ''; if (bounce) { const eightSeconds = new Date(new Date().getTime() + 8 * 1000); Cookies.set('cs_loggedOut', 'true', { @@ -53,7 +51,7 @@ export async function logout(auth: AuthContextProps, bounce?: boolean) { }); await auth.signoutRedirect({ redirectTarget: 'top', - post_logout_redirect_uri: `${window.location.origin}${pathname}`, + post_logout_redirect_uri: window.location.origin, id_token_hint: undefined, }); } else { diff --git a/src/components/AppFilter/useAppFilter.ts b/src/components/AppFilter/useAppFilter.ts index 6e8d9a002..149b2c9d1 100644 --- a/src/components/AppFilter/useAppFilter.ts +++ b/src/components/AppFilter/useAppFilter.ts @@ -5,11 +5,8 @@ import { getChromeStaticPathname } from '../../utils/common'; import { evaluateVisibility } from '../../utils/isNavItemVisible'; import { useAtomValue } from 'jotai'; import { chromeModulesAtom } from '../../state/atoms/chromeModuleAtom'; -import { isPreviewAtom } from '../../state/atoms/releaseAtom'; import { navigationAtom } from '../../state/atoms/navigationAtom'; -const LOCAL_PREVIEW = localStorage.getItem('chrome:local-preview') === 'true'; - export type AppFilterBucket = { id: string; title: string; @@ -81,7 +78,6 @@ type AppFilterState = { }; const useAppFilter = () => { - const isPreview = useAtomValue(isPreviewAtom); const [state, setState] = useState({ isLoaded: false, isLoading: false, @@ -183,10 +179,7 @@ const useAppFilter = () => { .get(`${getChromeStaticPathname('navigation')}/${fragment}-navigation.json?ts=${Date.now()}`) // fallback static CSC for EE env .catch(() => { - // FIXME: Remove this once local preview is enabled by default - // No /beta will be needed in the future - const previewFragment = LOCAL_PREVIEW ? '' : isPreview ? '/beta' : ''; - return axios.get(`${previewFragment}/config/chrome/${fragment}-navigation.json?ts=${Date.now()}`); + return axios.get(`$/config/chrome/${fragment}-navigation.json?ts=${Date.now()}`); }) .then(handleBundleData) .then(() => Object.values(existingSchemas).map((data) => handleBundleData({ data } as { data: BundleNavigation }))) diff --git a/src/components/Header/HeaderTests/Tools.test.js b/src/components/Header/HeaderTests/Tools.test.js index 051207a1c..cfe8aa669 100644 --- a/src/components/Header/HeaderTests/Tools.test.js +++ b/src/components/Header/HeaderTests/Tools.test.js @@ -1,5 +1,5 @@ import React from 'react'; -import Tools, { switchRelease } from '../Tools'; +import Tools from '../Tools'; import { act, render } from '@testing-library/react'; import { Provider } from 'react-redux'; import { createStore } from 'redux'; @@ -48,18 +48,4 @@ describe('Tools', () => { }); expect(container.querySelector('div')).toMatchSnapshot(); }); - - it('should switch release correctly', () => { - const cases = [ - ['/beta/settings/rbac', '/settings/rbac'], - ['/preview/settings/rbac', '/settings/rbac'], - ['/settings/rbac', '/settings/rbac'], - ]; - - cases.forEach(([input, expected]) => { - window.location.href = ''; - switchRelease(true, input); - expect(window.location.href).toEqual(expected); - }); - }); }); diff --git a/src/components/Header/PreviewAlert.tsx b/src/components/Header/PreviewAlert.tsx index 3e86d91cb..58caab9b3 100644 --- a/src/components/Header/PreviewAlert.tsx +++ b/src/components/Header/PreviewAlert.tsx @@ -3,34 +3,21 @@ import cookie from 'js-cookie'; import HeaderAlert from './HeaderAlert'; import { useAtom } from 'jotai'; import { isPreviewAtom } from '../../state/atoms/releaseAtom'; -import { isBeta } from '../../utils/common'; import { AlertActionLink, AlertVariant } from '@patternfly/react-core/dist/dynamic/components/Alert'; -import { useLocation } from 'react-router-dom'; -import { useFlag } from '@unleash/proxy-client-react'; -const LOCAL_PREVIEW = localStorage.getItem('chrome:local-preview') === 'true'; - -const PreviewAlert = ({ switchRelease }: { switchRelease: (isBeta: boolean, pathname: string, previewEnabled: boolean) => void }) => { +const PreviewAlert = () => { const [isPreview, togglePreview] = useAtom(isPreviewAtom); const [prevPreviewValue, setPrevPreviewValue] = useState(isPreview); - const location = useLocation(); - const previewEnabled = useFlag('platform.chrome.preview'); - - // FIXME: Remove the cookie check once the local preview is enabled by default - const shouldRenderAlert = LOCAL_PREVIEW ? isPreview !== prevPreviewValue : cookie.get('cs_toggledRelease') === 'true'; - const isPreviewEnabled = LOCAL_PREVIEW ? isPreview : isBeta(); + const shouldRenderAlert = isPreview !== prevPreviewValue; function handlePreviewToggle() { - if (!LOCAL_PREVIEW) { - switchRelease(isPreviewEnabled, location.pathname, previewEnabled); - } togglePreview(); } return shouldRenderAlert ? ( @@ -47,7 +34,7 @@ const PreviewAlert = ({ switchRelease }: { switchRelease: (isBeta: boolean, path onClick={() => { handlePreviewToggle(); }} - >{`${isPreviewEnabled ? 'Disable' : 'Enable'} preview`} + >{`${isPreview ? 'Disable' : 'Enable'} preview`} } onDismiss={() => { diff --git a/src/components/Header/Tools.tsx b/src/components/Header/Tools.tsx index 47c2676f5..03acac923 100644 --- a/src/components/Header/Tools.tsx +++ b/src/components/Header/Tools.tsx @@ -1,6 +1,5 @@ /* eslint-disable @typescript-eslint/ban-ts-comment */ import React, { memo, useContext, useEffect, useState } from 'react'; -import { useLocation } from 'react-router-dom'; import { useAtom, useAtomValue } from 'jotai'; import { Button } from '@patternfly/react-core/dist/dynamic/components/Button'; import { Divider } from '@patternfly/react-core/dist/dynamic/components/Divider'; @@ -16,7 +15,7 @@ import UserToggle from './UserToggle'; import ToolbarToggle, { ToolbarToggleDropdownItem } from './ToolbarToggle'; import SettingsToggle, { SettingsToggleDropdownGroup } from './SettingsToggle'; import cookie from 'js-cookie'; -import { ITLess, getRouterBasename, getSection } from '../../utils/common'; +import { ITLess, getSection } from '../../utils/common'; import { useIntl } from 'react-intl'; import { useFlag } from '@unleash/proxy-client-react'; import messages from '../../locales/Messages'; @@ -28,26 +27,8 @@ import { isPreviewAtom } from '../../state/atoms/releaseAtom'; import { notificationDrawerExpandedAtom, unreadNotificationsAtom } from '../../state/atoms/notificationDrawerAtom'; import PreviewAlert from './PreviewAlert'; -const LOCAL_PREVIEW = localStorage.getItem('chrome:local-preview') === 'true'; - const isITLessEnv = ITLess(); -/** - * @deprecated Switch release will be replaced by the internal chrome state variable - */ -export const switchRelease = (isBeta: boolean, pathname: string, previewEnabled: boolean) => { - cookie.set('cs_toggledRelease', 'true'); - const previewFragment = getRouterBasename(pathname); - - let href = ''; - if (isBeta) { - href = pathname.replace(previewFragment.includes('beta') ? /\/beta/ : /\/preview/, ''); - } else { - href = previewEnabled ? `/preview${pathname}` : `/beta${pathname}`; - } - window.location.href = href; -}; - const InternalButton = () => ( - - + + + 'stage' } as any}> + + {!removeComponent ? : null} + + + + ); }; @@ -54,14 +59,16 @@ describe('Feedback Modal', () => { const CustomComponent = () => { return ( - - - 'stage' } as any}> - - - - - + + + + 'stage' } as any}> + + + + + + ); }; diff --git a/docs/api.md b/docs/api.md index 04d759141..98ba920b9 100644 --- a/docs/api.md +++ b/docs/api.md @@ -186,6 +186,21 @@ chrome.createCase({ }) ``` +You can also configure the version and product of the support cases: + +```js +const chrome = useChrome() + +chrome.createCase({ + supportCaseData: { + product: 'Red Hat Insights', + version: 'Advisor', + }, + caseFields: {} + ... +}) +``` + ## Deprecated functions * `chrome.navigation` this is a legacy function and is no longer supported. Invoking it has no effect. diff --git a/src/@types/types.d.ts b/src/@types/types.d.ts index 388931e4f..86369e1af 100644 --- a/src/@types/types.d.ts +++ b/src/@types/types.d.ts @@ -138,15 +138,19 @@ export type RouteDefinition = { props?: any; }; -export type ModuleRoute = - | { - isFedramp?: boolean; - pathname: string; - exact?: boolean; - dynamic?: boolean; - props?: Record; - } - | string; +export type SupportCaseConfig = { + product: string; + version: string; +}; + +export type ModuleRoute = { + isFedramp?: boolean; + pathname: string; + exact?: boolean; + dynamic?: boolean; + props?: Record; + supportCaseData?: SupportCaseConfig; +}; export type RemoteModule = { module: string; @@ -157,6 +161,7 @@ export type ChromeModule = { manifestLocation: string; ssoUrl?: string; config?: { + supportCaseData?: SupportCaseConfig; ssoUrl?: string; fullProfile?: boolean; props?: Record; diff --git a/src/chrome/create-chrome.ts b/src/chrome/create-chrome.ts index 3b5d27385..7def356a0 100644 --- a/src/chrome/create-chrome.ts +++ b/src/chrome/create-chrome.ts @@ -139,7 +139,7 @@ export const createChromeContext = ({ return environment; }, getAvailableBundles: () => Object.entries(bundleMapping).map(([key, value]) => ({ id: key, title: value })), - createCase: (fields?: any) => chromeAuth.getUser().then((user) => createSupportCase(user!.identity, chromeAuth.token, fields)), + createCase: (options?: any) => chromeAuth.getUser().then((user) => createSupportCase(user!.identity, chromeAuth.token, options)), getUserPermissions: async (app = '', bypassCache?: boolean) => { const token = await chromeAuth.getToken(); return fetchPermissions(token, app, bypassCache); diff --git a/src/components/Feedback/FeedbackModal.tsx b/src/components/Feedback/FeedbackModal.tsx index 943c9276a..e72128706 100644 --- a/src/components/Feedback/FeedbackModal.tsx +++ b/src/components/Feedback/FeedbackModal.tsx @@ -27,6 +27,7 @@ import './Feedback.scss'; import ChromeAuthContext from '../../auth/ChromeAuthContext'; import { useSegment } from '../../analytics/useSegment'; import { isPreviewAtom } from '../../state/atoms/releaseAtom'; +import useSupportCaseData from '../../hooks/useSupportCaseData'; const FEEDBACK_OPEN_EVENT = 'chrome.feedback.open'; @@ -56,6 +57,7 @@ const FeedbackModal = memo(() => { setModalPage('feedbackHome'); }; const isPreview = useAtomValue(isPreviewAtom); + const supportCaseData = useSupportCaseData(); const ModalDescription = ({ modalPage }: { modalPage: FeedbackPages }) => { switch (modalPage) { @@ -79,7 +81,7 @@ const FeedbackModal = memo(() => { className="pf-v5-u-mb-lg" isSelectableRaised isCompact - onClick={() => createSupportCase(user.identity, chromeAuth.token, isPreview)} + onClick={() => createSupportCase(user.identity, chromeAuth.token, isPreview, { supportCaseData })} > diff --git a/src/components/Header/Tools.tsx b/src/components/Header/Tools.tsx index 03acac923..f5c4d4f9d 100644 --- a/src/components/Header/Tools.tsx +++ b/src/components/Header/Tools.tsx @@ -26,6 +26,7 @@ import ChromeAuthContext from '../../auth/ChromeAuthContext'; import { isPreviewAtom } from '../../state/atoms/releaseAtom'; import { notificationDrawerExpandedAtom, unreadNotificationsAtom } from '../../state/atoms/notificationDrawerAtom'; import PreviewAlert from './PreviewAlert'; +import useSupportCaseData from '../../hooks/useSupportCaseData'; const isITLessEnv = ITLess(); @@ -174,6 +175,7 @@ const Tools = () => { }); } }, [user]); + const supportCaseData = useSupportCaseData(); const supportOptionsUrl = () => { return isITLessEnv ? 'https://redhatgov.servicenowservices.com/css' : 'https://access.redhat.com/support'; @@ -188,7 +190,7 @@ const Tools = () => { }, { title: intl.formatMessage(messages.openSupportCase), - onClick: () => createSupportCase(user.identity, token, isPreview), + onClick: () => createSupportCase(user.identity, token, isPreview, { supportCaseData }), isDisabled: window.location.href.includes('/application-services') && !isRhosakEntitled, isHidden: isITLessEnv, }, diff --git a/src/hooks/useSupportCaseData.ts b/src/hooks/useSupportCaseData.ts new file mode 100644 index 000000000..562524363 --- /dev/null +++ b/src/hooks/useSupportCaseData.ts @@ -0,0 +1,42 @@ +import { useMemo } from 'react'; +import { useAtomValue } from 'jotai'; +import { matchPath, useLocation } from 'react-router-dom'; +import { chromeModulesAtom } from '../state/atoms/chromeModuleAtom'; +import { ModuleRoute, SupportCaseConfig } from '../@types/types'; +import { activeModuleAtom } from '../state/atoms/activeModuleAtom'; + +function findRouteSupportCaseData(routes: ModuleRoute[], currentPathname: string): SupportCaseConfig | undefined { + const sortedModules = routes.sort((a, b) => b.pathname.length - a.pathname.length); + const matchedRoute = sortedModules.find(({ pathname }) => { + return matchPath( + { + path: pathname, + end: false, + }, + currentPathname + ); + }); + + return matchedRoute?.supportCaseData; +} + +const useSupportCaseData = (): SupportCaseConfig | undefined => { + const scope = useAtomValue(activeModuleAtom); + const location = useLocation(); + const modules = useAtomValue(chromeModulesAtom); + const moduleConfig = useMemo(() => (scope ? modules[scope] : undefined), [modules, scope]); + const supportCaseData = useMemo(() => { + if (!moduleConfig?.modules) { + return undefined; + } + const routeCaseData = findRouteSupportCaseData( + moduleConfig.modules.flatMap(({ routes }) => routes), + location.pathname + ); + return routeCaseData ?? moduleConfig?.config?.supportCaseData; + }, [moduleConfig, location]); + + return supportCaseData; +}; + +export default useSupportCaseData; diff --git a/src/utils/createCase.ts b/src/utils/createCase.ts index 96bc41943..de53bf0f8 100644 --- a/src/utils/createCase.ts +++ b/src/utils/createCase.ts @@ -9,6 +9,7 @@ import { ChromeUser } from '@redhat-cloud-services/types'; import { getUrl } from '../hooks/useBundle'; import chromeStore from '../state/chromeStore'; import { activeModuleAtom } from '../state/atoms/activeModuleAtom'; +import { SupportCaseConfig } from '../@types/types'; // Lit of products that are bundles const BUNDLE_PRODUCTS = [ @@ -72,8 +73,9 @@ export async function createSupportCase( userInfo: ChromeUser['identity'], token: string, isPreview: boolean, - fields?: { - caseFields: Record; + options?: { + supportCaseData?: SupportCaseConfig | undefined; + caseFields?: Record; } ) { const currentProduct = registerProduct() || 'Other'; @@ -82,6 +84,7 @@ export async function createSupportCase( const { src_hash, app_name } = { src_hash: productData?.src_hash, app_name: productData?.app_name ?? getUrl('app') }; const portalUrl = `${getEnvDetails()?.portal}`; const caseUrl = `${portalUrl}${HYDRA_ENDPOINT}`; + const { supportCaseData, ...fields } = options ?? { caseFields: {} }; log('Creating a support case'); @@ -112,7 +115,12 @@ export async function createSupportCase( .then((response) => response.json()) .then((data) => { if (data) { - const query = URI(`?seSessionId=${data.session.id}&product=${data.sessionDetails.product}&version=${src_hash}`).normalize(); + // FIXME: Use the URLSearchParams API instead of URI.js + const query = URI( + `?seSessionId=${data.session.id}&product=${supportCaseData?.product ?? data.sessionDetails.product}&version=${ + supportCaseData?.version ?? src_hash + }` + ).normalize(); window.open(`${portalUrl}/support/cases/#/case/new/open-case/describe-issue${query.readable()}`); return createSupportSentry(data.session.id, fields); } From e2d4f889f91bb621758098fe81f9fd36cb02daa0 Mon Sep 17 00:00:00 2001 From: Martin Marosi Date: Wed, 14 Aug 2024 13:02:36 +0200 Subject: [PATCH 082/151] Add missing scalprum core to shared scope. --- config/webpack.plugins.js | 1 + 1 file changed, 1 insertion(+) diff --git a/config/webpack.plugins.js b/config/webpack.plugins.js index 742efdb7d..b28d07268 100644 --- a/config/webpack.plugins.js +++ b/config/webpack.plugins.js @@ -47,6 +47,7 @@ const plugins = (dev = false, beta = false, restricted = false) => { { '@openshift/dynamic-plugin-sdk': { singleton: true, requiredVersion: deps['@openshift/dynamic-plugin-sdk'] } }, { '@patternfly/quickstarts': { singleton: true, requiredVersion: deps['@patternfly/quickstarts'] } }, { '@redhat-cloud-services/chrome': { singleton: true, requiredVersion: deps['@redhat-cloud-services/chrome'] } }, + { '@scalprum/core': { singleton: true, requiredVersion: deps['@scalprum/core'] } }, { '@scalprum/react-core': { singleton: true, requiredVersion: deps['@scalprum/react-core'] } }, { '@unleash/proxy-client-react': { singleton: true, requiredVersion: deps['@unleash/proxy-client-react'] } }, getDynamicModules(process.cwd()), From d6da3d227b3cf964e54b2c3b71f5f990fcdefdea Mon Sep 17 00:00:00 2001 From: Martin Marosi Date: Thu, 15 Aug 2024 09:52:53 +0200 Subject: [PATCH 083/151] Add option to disable PF4 styling. --- docs/disablingPf4.md | 11 +++++++++++ src/index.ejs | 2 +- src/index.ts | 6 ++++++ src/utils/debugFunctions.ts | 3 +++ src/utils/removePf4Styles.ts | 8 ++++++++ 5 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 docs/disablingPf4.md create mode 100644 src/utils/removePf4Styles.ts diff --git a/docs/disablingPf4.md b/docs/disablingPf4.md new file mode 100644 index 000000000..66b7b0892 --- /dev/null +++ b/docs/disablingPf4.md @@ -0,0 +1,11 @@ +# Disabling PF4 styling + +> Note: This flag is mean to be used for debugging purposes to help visually identify usage of outdated PF version + +## To remove PF4 styling support follow these steps + +1. Open your browser developer tool and access the "console" tab +2. Run this command: `window.insights.chrome.enable.disabledPf4()` +3. Refresh the browser page + +> Note: The flag uses localStorage for storage. The browser will remember the flag until the local storage is cleared. To remove the flag run `localStorage.clear()` command in you console and refresh the page. diff --git a/src/index.ejs b/src/index.ejs index 3c965890d..c5ad192f0 100644 --- a/src/index.ejs +++ b/src/index.ejs @@ -12,7 +12,7 @@ - +