Skip to content

Commit 268df68

Browse files
authored
Rework modular architecture deployment modes. (#15)
Signed-off-by: Lucas Fernandez <[email protected]>
1 parent 12729ec commit 268df68

File tree

12 files changed

+143
-80
lines changed

12 files changed

+143
-80
lines changed

README.md

Lines changed: 108 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,114 @@ The repository contains the following modules:
7373
- **style**: Global styles
7474
- **types**: TypeScript type definitions
7575

76+
## Provider Setup
77+
78+
The Modular Architecture library requires proper context setup to function correctly. The library provides two essential providers that must be configured at the root of your application:
79+
80+
### ModularArchContextProvider
81+
82+
The `ModularArchContextProvider` is **mandatory** for using this library. It provides essential configuration and state management for:
83+
84+
- **Deployment mode configuration** (Standalone, Federated, or Kubeflow)
85+
- **Namespace management** and selection
86+
- **API endpoint configuration**
87+
- **Script loading state** for Kubeflow integration
88+
- **Mandatory namespace enforcement** when required
89+
90+
### ThemeProvider
91+
92+
The `ThemeProvider` manages UI theming and supports:
93+
94+
- **PatternFly theme** (default)
95+
- **Material-UI theme** with CSS variables support
96+
- **Dynamic theme switching** at runtime
97+
98+
### Complete Setup Example
99+
100+
Here's how to properly set up your application root with both providers:
101+
102+
```typescript
103+
import React from 'react';
104+
import { createRoot } from 'react-dom/client';
105+
import { BrowserRouter as Router } from 'react-router-dom';
106+
import {
107+
ModularArchContextProvider,
108+
ThemeProvider,
109+
BrowserStorageContextProvider,
110+
NotificationContextProvider,
111+
DeploymentMode,
112+
Theme
113+
} from 'mod-arch-shared';
114+
115+
// Define your configuration
116+
const modularArchConfig: ModularArchConfig = {
117+
deploymentMode: DeploymentMode.Standalone, // or Federated, Kubeflow
118+
URL_PREFIX: '/api',
119+
BFF_API_VERSION: 'v1',
120+
// Optional: Force a specific namespace
121+
// mandatoryNamespace: 'production'
122+
};
123+
124+
const container = document.getElementById('root');
125+
const root = createRoot(container!);
126+
127+
root.render(
128+
<React.StrictMode>
129+
<Router>
130+
<ModularArchContextProvider config={modularArchConfig}>
131+
<ThemeProvider theme={Theme.Patternfly}>
132+
<BrowserStorageContextProvider>
133+
<NotificationContextProvider>
134+
<App />
135+
</NotificationContextProvider>
136+
</BrowserStorageContextProvider>
137+
</ThemeProvider>
138+
</ModularArchContextProvider>
139+
</Router>
140+
</React.StrictMode>,
141+
);
142+
```
143+
144+
### Configuration Options
145+
146+
#### Deployment Modes
147+
148+
- **`DeploymentMode.Standalone`**: For single-application deployments
149+
- **`DeploymentMode.Federated`**: For micro-frontend architectures
150+
- **`DeploymentMode.Kubeflow`**: For integration with Kubeflow environments
151+
152+
#### Theme Options
153+
154+
- **`Theme.Patternfly`**: Red Hat PatternFly design system (default)
155+
- **`Theme.MUI`**: Material-UI design system with CSS variables
156+
157+
### Using the Context in Components
158+
159+
Once providers are set up, you can access the configuration and state throughout your application:
160+
161+
```typescript
162+
import { useModularArchContext, useThemeContext } from 'mod-arch-shared';
163+
164+
const MyComponent = () => {
165+
const {
166+
config,
167+
namespaces,
168+
preferredNamespace,
169+
updatePreferredNamespace
170+
} = useModularArchContext();
171+
172+
const { theme } = useThemeContext();
173+
174+
return (
175+
<div>
176+
<p>Current deployment mode: {config.deploymentMode}</p>
177+
<p>Available namespaces: {namespaces.length}</p>
178+
<p>Current theme: {theme}</p>
179+
</div>
180+
);
181+
};
182+
```
183+
76184
## Configuration
77185

78186
### ModularArchConfig
@@ -82,7 +190,6 @@ The library supports various configuration options through the `ModularArchConfi
82190
```typescript
83191
interface ModularArchConfig {
84192
deploymentMode: DeploymentMode;
85-
platformMode: PlatformMode;
86193
URL_PREFIX: string;
87194
BFF_API_VERSION: string;
88195
mandatoryNamespace?: string; // Optional: Force a specific namespace
@@ -96,7 +203,6 @@ The `mandatoryNamespace` option allows you to enforce a specific namespace throu
96203
```typescript
97204
const config = {
98205
deploymentMode: DeploymentMode.Standalone,
99-
platformMode: PlatformMode.Default,
100206
URL_PREFIX: '/api',
101207
BFF_API_VERSION: 'v1',
102208
mandatoryNamespace: 'production' // Force the use of 'production' namespace

components/__tests__/NavBar.test.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import React from 'react';
33
import { render, screen } from '@testing-library/react';
44
import * as useFetchStateModule from '~/utilities/useFetchState';
55
import { ModularArchContextProvider } from '~/context/ModularArchContext';
6-
import { DeploymentMode, PlatformMode } from '~/utilities';
6+
import { DeploymentMode } from '~/utilities';
77
import { ModularArchConfig } from '~/types';
88
import NavBar from '../NavBar';
99

@@ -20,10 +20,8 @@ const mockUseFetchState = useFetchStateModule.useFetchState as jest.MockedFuncti
2020
const createMockConfig = (
2121
mandatoryNamespace?: string,
2222
deploymentMode: DeploymentMode = DeploymentMode.Standalone,
23-
platformMode: PlatformMode = PlatformMode.Default,
2423
): ModularArchConfig => ({
2524
deploymentMode,
26-
platformMode,
2725
URL_PREFIX: 'test',
2826
BFF_API_VERSION: 'v1',
2927
...(mandatoryNamespace && { mandatoryNamespace }),

context/ModularArchContext.tsx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export const ModularArchContextProvider: React.FC<ModularArchContextProviderProp
2828
children,
2929
config,
3030
}) => {
31-
const { deploymentMode, platformMode, mandatoryNamespace } = config;
31+
const { deploymentMode, mandatoryNamespace } = config;
3232

3333
const [scriptLoaded, setScriptLoaded] = useState(false);
3434
const [preferredNamespace, setPreferredNamespace] = useState<Namespace | undefined>(undefined);
@@ -55,15 +55,14 @@ export const ModularArchContextProvider: React.FC<ModularArchContextProviderProp
5555
useEffect(() => {
5656
kubeflowScriptLoader(
5757
deploymentMode,
58-
platformMode,
5958
() => setScriptLoaded(true),
6059
(scriptError) => {
6160
// eslint-disable-next-line no-console
6261
console.error('Error loading kubeflow script:', scriptError);
6362
setScriptLoaded(true); // Still set to true to not block the UI
6463
},
6564
);
66-
}, [deploymentMode, platformMode]);
65+
}, [deploymentMode]);
6766

6867
// Namespace selector for kubeflow integration
6968
useEffect(() => {
@@ -74,7 +73,6 @@ export const ModularArchContextProvider: React.FC<ModularArchContextProviderProp
7473

7574
kubeflowNamespaceLoader(
7675
deploymentMode,
77-
platformMode,
7876
scriptLoaded,
7977
(newNamespace: string) => {
8078
setPreferredNamespace({ name: newNamespace });
@@ -84,7 +82,7 @@ export const ModularArchContextProvider: React.FC<ModularArchContextProviderProp
8482
},
8583
mandatoryNamespace,
8684
);
87-
}, [deploymentMode, platformMode, scriptLoaded, mandatoryNamespace]);
85+
}, [deploymentMode, scriptLoaded, mandatoryNamespace]);
8886

8987
const contextValue = React.useMemo(
9088
() => ({

context/ThemeContext.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ type ThemeProviderProps = {
1010
};
1111

1212
export const ThemeContext = React.createContext({
13-
theme: Theme.Default,
13+
theme: Theme.Patternfly,
1414
});
1515

1616
export const ThemeProvider: React.FC<ThemeProviderProps> = ({
17-
theme = Theme.Default,
17+
theme = Theme.Patternfly,
1818
children,
1919
}) => {
2020
const themeValue = React.useMemo(() => ({ theme }), [theme]);

context/__tests__/ModularArchContext.test.tsx

Lines changed: 8 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import '@testing-library/jest-dom';
22
import * as React from 'react';
33
import { render, screen, act, waitFor } from '@testing-library/react';
44
import { ModularArchContext, ModularArchContextProvider } from '~/context/ModularArchContext';
5-
import { DeploymentMode, PlatformMode } from '~/utilities';
5+
import { DeploymentMode } from '~/utilities';
66
import { ModularArchConfig } from '~/types';
77
import * as useFetchStateModule from '~/utilities/useFetchState';
88

@@ -18,11 +18,9 @@ const mockNamespaces = [{ name: 'namespace-2' }, { name: 'namespace-3' }, { name
1818

1919
const createMockConfig = (
2020
deploymentMode: DeploymentMode = DeploymentMode.Standalone,
21-
platformMode: PlatformMode = PlatformMode.Default,
2221
mandatoryNamespace?: string,
2322
): ModularArchConfig => ({
2423
deploymentMode,
25-
platformMode,
2624
URL_PREFIX: 'model-registry',
2725
BFF_API_VERSION: 'v1',
2826
...(mandatoryNamespace && { mandatoryNamespace }),
@@ -48,7 +46,6 @@ describe('ModularArchContext', () => {
4846
return (
4947
<div>
5048
<div data-testid="deployment-mode">{config.deploymentMode}</div>
51-
<div data-testid="platform-mode">{config.platformMode}</div>
5249
<div data-testid="loading-state">{namespacesLoaded.toString()}</div>
5350
<div data-testid="error-state">{namespacesLoadError?.message || 'no-error'}</div>
5451
<div data-testid="init-error">{initializationError?.message || 'no-init-error'}</div>
@@ -83,7 +80,6 @@ describe('ModularArchContext', () => {
8380
);
8481

8582
expect(screen.getByTestId('deployment-mode')).toHaveTextContent('standalone');
86-
expect(screen.getByTestId('platform-mode')).toHaveTextContent('default');
8783
expect(screen.getByTestId('loading-state')).toHaveTextContent('true');
8884
expect(screen.getByTestId('error-state')).toHaveTextContent('no-error');
8985
expect(screen.getByTestId('namespaces')).toHaveTextContent('');
@@ -128,7 +124,7 @@ describe('ModularArchContext', () => {
128124
expect(screen.getByTestId('preferred')).toHaveTextContent('none');
129125
});
130126

131-
it('should initialize central dashboard client when integrated', async () => {
127+
it('should initialize central dashboard client when kubeflow', async () => {
132128
mockUseFetchState.mockReturnValue([mockNamespaces, true, undefined, jest.fn()]);
133129

134130
// Mock fetch to resolve successfully for script loading
@@ -174,7 +170,7 @@ describe('ModularArchContext', () => {
174170
return mockScript as Node;
175171
});
176172

177-
const config = createMockConfig(DeploymentMode.Integrated, PlatformMode.Kubeflow);
173+
const config = createMockConfig(DeploymentMode.Kubeflow);
178174

179175
render(
180176
<ModularArchContextProvider config={config}>
@@ -265,7 +261,7 @@ describe('ModularArchContext', () => {
265261
return mockScript as HTMLScriptElement;
266262
});
267263

268-
const config = createMockConfig(DeploymentMode.Integrated, PlatformMode.Kubeflow);
264+
const config = createMockConfig(DeploymentMode.Kubeflow);
269265

270266
render(
271267
<ModularArchContextProvider config={config}>
@@ -335,7 +331,7 @@ describe('ModularArchContext', () => {
335331
});
336332
(global.fetch as jest.Mock).mockReturnValue(promise);
337333

338-
const config = createMockConfig(DeploymentMode.Integrated, PlatformMode.Kubeflow);
334+
const config = createMockConfig(DeploymentMode.Kubeflow);
339335

340336
render(
341337
<ModularArchContextProvider config={config}>
@@ -374,11 +370,7 @@ describe('ModularArchContext', () => {
374370
jest.fn(),
375371
]);
376372

377-
const config = createMockConfig(
378-
DeploymentMode.Standalone,
379-
PlatformMode.Default,
380-
mandatoryNamespace,
381-
);
373+
const config = createMockConfig(DeploymentMode.Federated);
382374

383375
render(
384376
<ModularArchContextProvider config={config}>
@@ -400,11 +392,7 @@ describe('ModularArchContext', () => {
400392
jest.fn(),
401393
]);
402394

403-
const config = createMockConfig(
404-
DeploymentMode.Standalone,
405-
PlatformMode.Default,
406-
mandatoryNamespace,
407-
);
395+
const config = createMockConfig(DeploymentMode.Federated, mandatoryNamespace);
408396

409397
render(
410398
<ModularArchContextProvider config={config}>
@@ -464,11 +452,7 @@ describe('ModularArchContext', () => {
464452
},
465453
};
466454

467-
const config = createMockConfig(
468-
DeploymentMode.Integrated,
469-
PlatformMode.Kubeflow,
470-
mandatoryNamespace,
471-
);
455+
const config = createMockConfig(DeploymentMode.Kubeflow, mandatoryNamespace);
472456

473457
render(
474458
<ModularArchContextProvider config={config}>

hooks/__tests__/useModularArchContext.test.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import '@testing-library/jest-dom';
22
import React from 'react';
33
import { renderHook } from '@testing-library/react';
44
import { ModularArchContextProvider } from '~/context/ModularArchContext';
5-
import { DeploymentMode, PlatformMode } from '~/utilities';
5+
import { DeploymentMode } from '~/utilities';
66
import { useModularArchContext } from '../useModularArchContext';
77

88
// Mock the k8s API module
@@ -12,7 +12,6 @@ jest.mock('~/api/k8s', () => ({
1212

1313
const mockConfig = {
1414
deploymentMode: DeploymentMode.Standalone,
15-
platformMode: PlatformMode.Default,
1615
URL_PREFIX: 'test',
1716
BFF_API_VERSION: 'v1',
1817
};

hooks/__tests__/useNamespaces.test.tsx

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import '@testing-library/jest-dom';
22
import { renderHook } from '@testing-library/react';
33
import * as useFetchStateModule from '~/utilities/useFetchState';
4-
import { DeploymentMode, PlatformMode } from '~/utilities';
4+
import { DeploymentMode } from '~/utilities';
55
import { ModularArchConfig } from '~/types';
66
import { useNamespacesWithConfig } from '../useNamespaces';
77

@@ -17,11 +17,9 @@ const mockUseFetchState = useFetchStateModule.useFetchState as jest.MockedFuncti
1717

1818
const createMockConfig = (
1919
mandatoryNamespace?: string,
20-
deploymentMode: DeploymentMode = DeploymentMode.Standalone,
21-
platformMode: PlatformMode = PlatformMode.Default,
20+
deploymentMode: DeploymentMode = DeploymentMode.Federated,
2221
): ModularArchConfig => ({
2322
deploymentMode,
24-
platformMode,
2523
URL_PREFIX: 'test',
2624
BFF_API_VERSION: 'v1',
2725
...(mandatoryNamespace && { mandatoryNamespace }),
@@ -64,8 +62,8 @@ describe('useNamespacesWithConfig', () => {
6462
expect(result.current[2]).toBeUndefined(); // error
6563
});
6664

67-
it('should return empty array for kubeflow integrated mode when no mandatory namespace', () => {
68-
const config = createMockConfig(undefined, DeploymentMode.Integrated, PlatformMode.Kubeflow);
65+
it('should return empty array for kubeflow mode when no mandatory namespace', () => {
66+
const config = createMockConfig(undefined, DeploymentMode.Kubeflow);
6967

7068
mockUseFetchState.mockReturnValue([[], true, undefined, jest.fn()]);
7169

@@ -76,13 +74,9 @@ describe('useNamespacesWithConfig', () => {
7674
expect(result.current[2]).toBeUndefined(); // error
7775
});
7876

79-
it('should return mandatory namespace even in kubeflow integrated mode', () => {
77+
it('should return mandatory namespace even in kubeflow mode', () => {
8078
const mandatoryNamespace = 'mandatory-namespace';
81-
const config = createMockConfig(
82-
mandatoryNamespace,
83-
DeploymentMode.Integrated,
84-
PlatformMode.Kubeflow,
85-
);
79+
const config = createMockConfig(mandatoryNamespace, DeploymentMode.Kubeflow);
8680

8781
mockUseFetchState.mockReturnValue([[{ name: mandatoryNamespace }], true, undefined, jest.fn()]);
8882

0 commit comments

Comments
 (0)