Skip to content

Commit

Permalink
ARTESCA-13969 // Migration to React 18
Browse files Browse the repository at this point in the history
ARTESCA-13969 // Migration to React 18

refactor: update NotificationCenter styles and improve test assertions

refactor: migrate to React 18's createRoot for rendering

refactor: implement external store for WebFingers context and update related hooks

refactor: remove console logs and simplify state update logic in WebFingersStore

refactor: update dependencies to React 18 and related packages
  • Loading branch information
hervedombya committed Nov 25, 2024
1 parent 14a55a3 commit f08ccc2
Show file tree
Hide file tree
Showing 9 changed files with 15,015 additions and 541 deletions.
14,815 changes: 14,579 additions & 236 deletions shell-ui/package-lock.json

Large diffs are not rendered by default.

24 changes: 12 additions & 12 deletions shell-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@
"@rspack/core": "^0.7.5",
"@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^5.11.9",
"@testing-library/react": "^11.2.5",
"@testing-library/react-hooks": "^5.1.1",
"@testing-library/react": "^15.0.7",
"@testing-library/react-hooks": "^8.0.1",
"@testing-library/user-event": "^13.0.10",
"@types/react": "^17.0.39",
"@types/react-dom": "^17.0.13",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@types/react-router": "^5.1.20",
"@types/react-router-dom": "^5.2.0",
"@types/styled-components": "^5.1.26",
"@types/react-test-renderer": "^18.3.0",
"@types/styled-components": "^5.1.34",
"babel-jest": "^26.6.3",
"babel-loader": "^8.2.2",
"fs-extra": "^10.0.0",
Expand All @@ -39,24 +39,24 @@
"jest-preview": "^0.3.1",
"msw": "0.36.8",
"node-fetch": "^2.6.1",
"react-test-renderer": "^17.0.2",
"react-test-renderer": "^18.3.1",
"ts-node": "^10.9.2"
},
"dependencies": {
"@scality/core-ui": "0.151.0",
"@scality/module-federation": "^1.3.4",
"@scality/core-ui": "git+https://github.com/scality/core-ui#bf0c36da657737f47dabe310bb1a20c136877970",
"@scality/module-federation": "git+https://github.com/scality/module-federation#129815715e9fc7cb7cbe4417f536679183c49725",
"downshift": "^8.0.0",
"jest-environment-jsdom": "^29.7.0",
"oidc-client-ts": "^3.0.1",
"oidc-react": "^3.2.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-error-boundary": "^4.0.2",
"react-intl": "^5.15.3",
"react-query": "^3.34.0",
"react-router": "5.2.0",
"react-router-dom": "5.2.0",
"styled-components": "^5.2.1",
"styled-components": "^5.3.11",
"typescript": "^5.6.3"
}
}
12 changes: 5 additions & 7 deletions shell-ui/src/auth/useFirstTimeLogin.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { useFirstTimeLogin } from './FirstTimeLoginProvider';
import { wrapper } from '../navbar/index.spec';
import { configurationHandlers } from '../FederatedApp.spec';
import { setupServer } from 'msw/node';
import { waitFor } from '@testing-library/react';

const server = setupServer(...configurationHandlers);

Expand All @@ -28,14 +29,11 @@ describe('useFirstTimeLogin hook', () => {

it('should return firstTimeLogin as true if the user is logging in for the first time', async () => {
//S
const { result, waitForNextUpdate } = renderHook(
() => useFirstTimeLogin(),
{ wrapper },
);
//E
await waitForNextUpdate();
const { result } = renderHook(() => useFirstTimeLogin(), { wrapper });
//V
expect(result.current.firstTimeLogin).toEqual(true);
await waitFor(() => {
expect(result.current.firstTimeLogin).toEqual(true);
});
});

it('should return firstTimeLogin as false if the user is NOT logging in for the first time', async () => {
Expand Down
18 changes: 8 additions & 10 deletions shell-ui/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App, { ShellTypes } from './FederatedApp';
import { NotificationCenterContextType } from './NotificationCenterProvider';
import { History } from 'history';
import {
BuildtimeWebFinger,
RuntimeWebFinger,
} from './initFederation/ConfigurationProviders';
import { createRoot } from 'react-dom/client';
import App from './FederatedApp';

ReactDOM.render(<App />, document.getElementById('app'));
const rootElement = document.getElementById('app');

if (rootElement) {
const root = createRoot(rootElement);
root.render(<App />);
}
109 changes: 84 additions & 25 deletions shell-ui/src/initFederation/ConfigurationProviders.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,12 @@ import { ErrorPage500 } from '@scality/core-ui/dist/components/error-pages/Error
import { IconName } from '@scality/core-ui/dist/components/icon/Icon.component';
import { Loader } from '@scality/core-ui/dist/components/loader/Loader.component';
import { SolutionUI } from '@scality/module-federation';
import React, { createContext, useContext } from 'react';
import React, { useMemo, useSyncExternalStore } from 'react';
import { useQueries, UseQueryResult } from 'react-query';
import { useShellConfig } from './ShellConfigProvider';
import { useShellHistory } from './ShellHistoryProvider';
import { useDeployedApps, useDeployedAppsRetriever } from './UIListProvider';

if (!window.shellContexts) {
// @ts-expect-error - FIXME when you are working on it
window.shellContexts = {};
}

if (!window.shellContexts.WebFingersContext) {
window.shellContexts.WebFingersContext = createContext<
| null
| UseQueryResult<
BuildtimeWebFinger | RuntimeWebFinger<Record<string, unknown>>,
unknown
>[]
>(null);
}

export type OAuth2ProxyConfig = {
kind: 'OAuth2Proxy'; //todo : add other entries
};
Expand Down Expand Up @@ -85,10 +70,8 @@ export function useConfigRetriever(): {
name: string;
}) => (T extends 'build' ? BuildtimeWebFinger : RuntimeWebFinger<T>) | null;
} {
const { state: webFingerContextValue } = useWebFingersStore();
const { retrieveDeployedApps } = useDeployedAppsRetriever();
const webFingerContextValue = useContext(
window.shellContexts.WebFingersContext,
);

if (!webFingerContextValue) {
throw new Error(
Expand Down Expand Up @@ -142,15 +125,18 @@ export function useConfig<T extends 'build' | Record<string, unknown>>({
configType: T extends 'build' ? 'build' : 'run';
name: string;
}): null | T extends 'build' ? BuildtimeWebFinger : RuntimeWebFinger<T> {
// Utiliser le nouveau hook useWebFingersStore
const { state: webFingerContextValue } = useWebFingersStore();

// Utiliser le retrieveConfiguration du hook useConfigRetriever
const { retrieveConfiguration } = useConfigRetriever();
const webFingerContextValue = useContext(
window.shellContexts.WebFingersContext,
);

if (!webFingerContextValue) {
// Vérifier que le contexte est disponible
if (!webFingerContextValue || webFingerContextValue.length === 0) {
throw new Error("Can't use useConfig outside of ConfigurationProvider");
}

// Récupérer et retourner la configuration
return retrieveConfiguration({
configType,
name,
Expand Down Expand Up @@ -179,6 +165,72 @@ export type NonFederatedView = {
icon?: IconName;
};
export type ViewDefinition = FederatedView | NonFederatedView;

// External store implementation
class WebFingersStore {
private listeners: Set<() => void> = new Set();
private _state: UseQueryResult<
BuildtimeWebFinger | RuntimeWebFinger<Record<string, unknown>>,
unknown
>[] = [];

subscribe = (listener: () => void) => {
this.listeners.add(listener);
return () => {
this.listeners.delete(listener);
};
};

getState = () => {
return this._state;
};

private isStateEqual(
currentState: UseQueryResult<
BuildtimeWebFinger | RuntimeWebFinger<Record<string, unknown>>,
unknown
>[],
newState: UseQueryResult<
BuildtimeWebFinger | RuntimeWebFinger<Record<string, unknown>>,
unknown
>[],
) {
return (
currentState.length === newState.length &&
currentState.every(
(item, index) =>
JSON.stringify(item) === JSON.stringify(newState[index]),
)
);
}

updateState = (
newState: UseQueryResult<
BuildtimeWebFinger | RuntimeWebFinger<Record<string, unknown>>,
unknown
>[],
) => {
if (!this.isStateEqual(this._state, newState)) {
this._state = newState;
this.listeners.forEach((listener) => listener());
}
};
}

const webFingersStore = new WebFingersStore();

export function useWebFingersStore() {
const state = useSyncExternalStore(
webFingersStore.subscribe,
webFingersStore.getState,
);

return {
state,
updateWebFingersState: webFingersStore.updateState,
};
}

export function useDiscoveredViews(): ViewDefinition[] {
const { retrieveConfiguration } = useConfigRetriever();
const { retrieveDeployedApps } = useDeployedAppsRetriever();
Expand Down Expand Up @@ -286,6 +338,7 @@ export const ConfigurationProvider = ({
}: {
children: React.ReactNode;
}) => {
const { updateWebFingersState } = useWebFingersStore();
const deployedUIs = useDeployedApps();
const results = useQueries(
deployedUIs.flatMap((ui) => [
Expand Down Expand Up @@ -323,6 +376,11 @@ export const ConfigurationProvider = ({
},
]),
);

useMemo(() => {
updateWebFingersState(results);
}, [results]);

const statuses = Array.from(new Set(results.map((result) => result.status)));
const globalStatus = statuses.includes('error')
? 'error'
Expand All @@ -333,13 +391,14 @@ export const ConfigurationProvider = ({
: statuses.includes('idle') && statuses.includes('success')
? 'loading'
: 'success';

return (
<window.shellContexts.WebFingersContext.Provider value={results}>
<>
{(globalStatus === 'loading' || globalStatus === 'idle') && (
<Loader size="massive" centered={true} aria-label="loading" />
)}
{globalStatus === 'error' && <ErrorPage500 data-cy="sc-error-page500" />}
{globalStatus === 'success' && children}
</window.shellContexts.WebFingersContext.Provider>
</>
);
};
2 changes: 1 addition & 1 deletion shell-ui/src/navbar/__TESTS__/testMultipleHooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export type RenderAdditionalHook = <THookResult>(
};

export function prepareRenderMultipleHooks(options: {
wrapper: FunctionComponent<PropsWithChildren<Record<string, never>>>;
wrapper: FunctionComponent<React.PropsWithChildren<PropsWithChildren<Record<string, never>>>>;
}): {
renderAdditionalHook: RenderAdditionalHook;
waitForWrapperToBeReady: () => Promise<void>;
Expand Down
Loading

0 comments on commit f08ccc2

Please sign in to comment.