@equinor/fusion-framework-react-app 14.0.3 → 14.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -2
- package/dist/esm/ag-grid/community.js +12 -0
- package/dist/esm/ag-grid/community.js.map +1 -0
- package/dist/esm/ag-grid/enterprise.js +12 -0
- package/dist/esm/ag-grid/enterprise.js.map +1 -0
- package/dist/esm/ag-grid/react.js +12 -0
- package/dist/esm/ag-grid/react.js.map +1 -0
- package/dist/esm/ag-grid/testing.js +19 -0
- package/dist/esm/ag-grid/testing.js.map +1 -0
- package/dist/esm/ag-grid/theme.js +13 -0
- package/dist/esm/ag-grid/theme.js.map +1 -0
- package/dist/esm/create-component.js +1 -1
- package/dist/esm/msal/useToken.js +12 -3
- package/dist/esm/msal/useToken.js.map +1 -1
- package/dist/esm/version.js +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/ag-grid/community.d.ts +11 -0
- package/dist/types/ag-grid/enterprise.d.ts +11 -0
- package/dist/types/ag-grid/react.d.ts +11 -0
- package/dist/types/ag-grid/testing.d.ts +18 -0
- package/dist/types/ag-grid/theme.d.ts +13 -0
- package/dist/types/create-component.d.ts +1 -1
- package/dist/types/version.d.ts +1 -1
- package/package.json +80 -25
- package/CHANGELOG.md +0 -2730
- package/docs/ag-grid.md +0 -70
- package/docs/analytics.md +0 -122
- package/docs/app.md +0 -148
- package/docs/apploader.md +0 -110
- package/docs/bookmark.md +0 -101
- package/docs/context.md +0 -86
- package/docs/feature-flag.md +0 -119
- package/docs/framework.md +0 -93
- package/docs/help-center.md +0 -88
- package/docs/http.md +0 -118
- package/docs/msal.md +0 -291
- package/docs/navigation.md +0 -80
- package/docs/routing.md +0 -86
- package/docs/settings.md +0 -139
- package/src/__tests__/render-app.test.tsx +0 -113
- package/src/__tests__/useStateSyncEvents.test.ts +0 -57
- package/src/ag-grid/useTheme.ts +0 -21
- package/src/analytics/README.md +0 -148
- package/src/analytics/index.ts +0 -9
- package/src/analytics/useTrackFeature.ts +0 -73
- package/src/apploader/Apploader.tsx +0 -50
- package/src/apploader/README.md +0 -81
- package/src/apploader/index.ts +0 -10
- package/src/apploader/useApploader.ts +0 -107
- package/src/bookmark/index.ts +0 -20
- package/src/bookmark/useBookmark.ts +0 -25
- package/src/bookmark/useCurrentBookmark.ts +0 -54
- package/src/context/index.ts +0 -14
- package/src/context/useContextProvider.ts +0 -12
- package/src/context/useCurrentContext.ts +0 -20
- package/src/create-component.tsx +0 -120
- package/src/create-legacy-app.tsx +0 -51
- package/src/feature-flag/README.md +0 -20
- package/src/feature-flag/enable-feature-flag.ts +0 -79
- package/src/feature-flag/index.ts +0 -17
- package/src/feature-flag/useFeature.ts +0 -90
- package/src/framework/index.ts +0 -14
- package/src/framework/useFrameworkCurrentContext.ts +0 -15
- package/src/help-center/README.md +0 -21
- package/src/help-center/event-name.ts +0 -1
- package/src/help-center/index.ts +0 -11
- package/src/help-center/useHelpCenter.ts +0 -162
- package/src/http/index.ts +0 -10
- package/src/http/selectors.ts +0 -9
- package/src/index.ts +0 -46
- package/src/make-component.tsx +0 -102
- package/src/msal/index.ts +0 -16
- package/src/msal/useAccessToken.ts +0 -26
- package/src/msal/useCurrentAccount.ts +0 -21
- package/src/msal/useToken.ts +0 -52
- package/src/navigation/index.ts +0 -10
- package/src/navigation/useNavigationModule.ts +0 -10
- package/src/navigation/useRouter.ts +0 -35
- package/src/render-app.ts +0 -35
- package/src/render-component.tsx +0 -55
- package/src/routing/index.ts +0 -33
- package/src/settings/README.md +0 -123
- package/src/settings/index.ts +0 -12
- package/src/settings/useAppSetting.ts +0 -113
- package/src/settings/useAppSettings.ts +0 -103
- package/src/settings/useAppSettingsStatus.ts +0 -52
- package/src/state/index.ts +0 -12
- package/src/state/useAppState.ts +0 -299
- package/src/state/useStateSyncEvents.ts +0 -59
- package/src/useAppEnvironmentVariables.ts +0 -59
- package/src/useAppModule.ts +0 -45
- package/src/useAppModules.ts +0 -21
- package/src/version.ts +0 -2
- package/tsconfig.json +0 -48
- package/vitest.config.ts +0 -15
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
import type { ComponentRenderArgs } from '../create-component';
|
|
3
|
-
import type { Fusion } from '@equinor/fusion-framework-react';
|
|
4
|
-
import type { AppEnv } from '@equinor/fusion-framework-app';
|
|
5
|
-
|
|
6
|
-
// Mock dependencies before importing
|
|
7
|
-
vi.mock('../create-component', () => ({
|
|
8
|
-
createComponent: vi.fn(),
|
|
9
|
-
}));
|
|
10
|
-
|
|
11
|
-
vi.mock('../render-component', () => ({
|
|
12
|
-
renderComponent: vi.fn(),
|
|
13
|
-
}));
|
|
14
|
-
|
|
15
|
-
import { renderApp } from '../render-app';
|
|
16
|
-
import { createComponent } from '../create-component';
|
|
17
|
-
import { renderComponent } from '../render-component';
|
|
18
|
-
|
|
19
|
-
describe('renderApp', () => {
|
|
20
|
-
let mockFusion: Fusion;
|
|
21
|
-
let mockEnv: AppEnv;
|
|
22
|
-
let mockElement: HTMLElement;
|
|
23
|
-
let mockTeardown: () => void;
|
|
24
|
-
let mockComponentRenderer: ReturnType<typeof createComponent>;
|
|
25
|
-
let mockRenderFunction: ReturnType<typeof renderComponent>;
|
|
26
|
-
|
|
27
|
-
beforeEach(() => {
|
|
28
|
-
// Setup mock data
|
|
29
|
-
mockFusion = {} as Fusion;
|
|
30
|
-
mockEnv = {} as AppEnv;
|
|
31
|
-
mockElement = document.createElement('div');
|
|
32
|
-
mockTeardown = vi.fn();
|
|
33
|
-
|
|
34
|
-
// Create a mock component renderer
|
|
35
|
-
// createComponent returns a ComponentRenderer function: (fusion, env) => React.LazyExoticComponent
|
|
36
|
-
mockComponentRenderer = vi.fn() as unknown as ReturnType<typeof createComponent>;
|
|
37
|
-
|
|
38
|
-
// Create a mock render function
|
|
39
|
-
mockRenderFunction = vi.fn(() => mockTeardown) as unknown as ReturnType<typeof renderComponent>;
|
|
40
|
-
|
|
41
|
-
// Mock renderComponent to return our mock render function
|
|
42
|
-
vi.mocked(renderComponent).mockReturnValue(mockRenderFunction);
|
|
43
|
-
|
|
44
|
-
// Mock createComponent to return our mock component renderer
|
|
45
|
-
vi.mocked(createComponent).mockReturnValue(mockComponentRenderer);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it('should create a render function that calls createComponent with provided arguments', () => {
|
|
49
|
-
const TestComponent = () => <div>Test</div>;
|
|
50
|
-
const mockConfigure = vi.fn();
|
|
51
|
-
|
|
52
|
-
renderApp(TestComponent, mockConfigure);
|
|
53
|
-
|
|
54
|
-
expect(createComponent).toHaveBeenCalledWith(TestComponent, mockConfigure);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it('should create a render function that uses renderComponent with the component renderer', () => {
|
|
58
|
-
const TestComponent = () => <div>Test</div>;
|
|
59
|
-
const appRender = renderApp(TestComponent);
|
|
60
|
-
|
|
61
|
-
// Call the render function
|
|
62
|
-
const teardown = appRender(mockElement, { fusion: mockFusion, env: mockEnv });
|
|
63
|
-
|
|
64
|
-
expect(renderComponent).toHaveBeenCalledWith(mockComponentRenderer);
|
|
65
|
-
expect(mockRenderFunction).toHaveBeenCalledWith(mockElement, {
|
|
66
|
-
fusion: mockFusion,
|
|
67
|
-
env: mockEnv,
|
|
68
|
-
});
|
|
69
|
-
expect(teardown).toBe(mockTeardown);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it('should return a teardown function that unmounts the component', () => {
|
|
73
|
-
const TestComponent = () => <div>Test</div>;
|
|
74
|
-
const appRender = renderApp(TestComponent);
|
|
75
|
-
|
|
76
|
-
const teardown = appRender(mockElement, { fusion: mockFusion, env: mockEnv });
|
|
77
|
-
|
|
78
|
-
expect(typeof teardown).toBe('function');
|
|
79
|
-
teardown();
|
|
80
|
-
expect(mockTeardown).toHaveBeenCalled();
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it('should work without a configure callback', () => {
|
|
84
|
-
vi.clearAllMocks();
|
|
85
|
-
const TestComponent = () => <div>Test</div>;
|
|
86
|
-
const appRender = renderApp(TestComponent);
|
|
87
|
-
|
|
88
|
-
// When no configure callback is provided, it may be called with just the component
|
|
89
|
-
// or with undefined as the second parameter
|
|
90
|
-
expect(createComponent).toHaveBeenCalled();
|
|
91
|
-
const calls = vi.mocked(createComponent).mock.calls;
|
|
92
|
-
expect(calls[calls.length - 1][0]).toBe(TestComponent);
|
|
93
|
-
expect(calls[calls.length - 1][1]).toBeUndefined();
|
|
94
|
-
|
|
95
|
-
const teardown = appRender(mockElement, { fusion: mockFusion, env: mockEnv });
|
|
96
|
-
|
|
97
|
-
expect(teardown).toBe(mockTeardown);
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
it('should pass component renderer args correctly to renderComponent', () => {
|
|
101
|
-
const TestComponent = () => <div>Test</div>;
|
|
102
|
-
const appRender = renderApp(TestComponent);
|
|
103
|
-
|
|
104
|
-
const args: ComponentRenderArgs = {
|
|
105
|
-
fusion: mockFusion,
|
|
106
|
-
env: mockEnv,
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
appRender(mockElement, args);
|
|
110
|
-
|
|
111
|
-
expect(mockRenderFunction).toHaveBeenCalledWith(mockElement, args);
|
|
112
|
-
});
|
|
113
|
-
});
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
-
import { Subject } from 'rxjs';
|
|
3
|
-
|
|
4
|
-
import { act, renderHook } from '@testing-library/react';
|
|
5
|
-
|
|
6
|
-
import { StateSyncEvent, type StateSyncEventType } from '@equinor/fusion-framework-module-state';
|
|
7
|
-
|
|
8
|
-
const event$ = new Subject<StateSyncEventType>();
|
|
9
|
-
|
|
10
|
-
vi.mock('../useAppModule', () => ({
|
|
11
|
-
default: () => ({ event$ }),
|
|
12
|
-
}));
|
|
13
|
-
|
|
14
|
-
import { useStateSyncEvents } from '../state/useStateSyncEvents';
|
|
15
|
-
|
|
16
|
-
describe('useStateSyncEvents', () => {
|
|
17
|
-
it('collects dispatched onStateSync.* events, oldest first', () => {
|
|
18
|
-
const { result } = renderHook(() => useStateSyncEvents(10));
|
|
19
|
-
|
|
20
|
-
expect(result.current).toEqual([]);
|
|
21
|
-
|
|
22
|
-
act(() => {
|
|
23
|
-
event$.next(new StateSyncEvent.Status({ detail: { status: 'active' } }));
|
|
24
|
-
event$.next(new StateSyncEvent.Status({ detail: { status: 'paused' } }));
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
expect(result.current).toHaveLength(2);
|
|
28
|
-
expect(result.current[0].detail.status).toBe('active');
|
|
29
|
-
expect(result.current[1].detail.status).toBe('paused');
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
it('ignores events unrelated to state sync and trims the log to the given limit', () => {
|
|
33
|
-
const { result } = renderHook(() => useStateSyncEvents(1));
|
|
34
|
-
|
|
35
|
-
act(() => {
|
|
36
|
-
event$.next(new StateSyncEvent.Status({ detail: { status: 'active' } }));
|
|
37
|
-
event$.next(
|
|
38
|
-
new StateSyncEvent.Error({ detail: { error: new Error('boom'), type: 'error' } }),
|
|
39
|
-
);
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
expect(result.current).toHaveLength(1);
|
|
43
|
-
expect(result.current[0]).toBeInstanceOf(StateSyncEvent.Error);
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it('unsubscribes from the event stream on unmount', () => {
|
|
47
|
-
const { result, unmount } = renderHook(() => useStateSyncEvents(10));
|
|
48
|
-
|
|
49
|
-
unmount();
|
|
50
|
-
|
|
51
|
-
act(() => {
|
|
52
|
-
event$.next(new StateSyncEvent.Status({ detail: { status: 'active' } }));
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
expect(result.current).toEqual([]);
|
|
56
|
-
});
|
|
57
|
-
});
|
package/src/ag-grid/useTheme.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { useAppModule } from '../useAppModule';
|
|
2
|
-
import type { AgGridModule } from '@equinor/fusion-framework-module-ag-grid';
|
|
3
|
-
import type { Theme } from '@equinor/fusion-framework-module-ag-grid/themes';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* React hook that returns the current AG Grid theme from the application-scoped
|
|
7
|
-
* AG Grid module.
|
|
8
|
-
*
|
|
9
|
-
* @returns The active {@link Theme} object.
|
|
10
|
-
* @throws If the AG Grid module is not registered in the application.
|
|
11
|
-
*/
|
|
12
|
-
export const useTheme = (): Theme => {
|
|
13
|
-
const agGrid = useAppModule<AgGridModule>('agGrid');
|
|
14
|
-
|
|
15
|
-
// Fail fast when the AG Grid module has not been registered on the app
|
|
16
|
-
if (!agGrid) {
|
|
17
|
-
throw new Error('agGrid module is not available');
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
return agGrid.theme as Theme;
|
|
21
|
-
};
|
package/src/analytics/README.md
DELETED
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
## Analytics React Hooks
|
|
2
|
-
|
|
3
|
-
React hooks for tracking application feature usage through the Fusion analytics module.
|
|
4
|
-
|
|
5
|
-
These hooks are available from `@equinor/fusion-framework-react-app/analytics`. The analytics module is enabled and configured by the hosting portal, so **apps running inside a Fusion portal can use these hooks immediately without any setup**.
|
|
6
|
-
|
|
7
|
-
### Why track feature usage?
|
|
8
|
-
|
|
9
|
-
Understanding how users interact with your application helps you make informed decisions about what to build, improve, or remove. Without usage data, you are guessing which features matter.
|
|
10
|
-
|
|
11
|
-
Common reasons to add feature tracking:
|
|
12
|
-
|
|
13
|
-
- **Measure adoption** — find out whether a newly released feature is actually being used, and by how many users across different contexts.
|
|
14
|
-
- **Identify unused features** — discover features that can be simplified or removed, reducing maintenance cost and complexity.
|
|
15
|
-
- **Understand user workflows** — see which paths users take through your application, revealing unexpected patterns or friction points.
|
|
16
|
-
- **Support data-driven prioritisation** — back up roadmap discussions with real usage numbers instead of assumptions.
|
|
17
|
-
- **Debug user-reported issues** — correlate analytics events with error reports to understand what the user was doing when something went wrong.
|
|
18
|
-
|
|
19
|
-
### When to use `useTrackFeature`
|
|
20
|
-
|
|
21
|
-
Use `useTrackFeature` when you want to record a discrete user action or application milestone from a React component. Typical use cases include:
|
|
22
|
-
|
|
23
|
-
| Use case | What to track | Example event name |
|
|
24
|
-
|----------|---------------|-------------------|
|
|
25
|
-
| Button or action clicks | User triggers a specific workflow | `'export-clicked'`, `'filter-applied'` |
|
|
26
|
-
| Page or component views | A section of the app is displayed | `'dashboard-loaded'`, `'settings-opened'` |
|
|
27
|
-
| Feature gate checks | A feature behind a flag is accessed | `'beta-feature-used'` |
|
|
28
|
-
| Form submissions | A user completes a form or wizard step | `'report-submitted'`, `'wizard-step-3'` |
|
|
29
|
-
| Error recovery actions | A user retries or dismisses an error | `'retry-clicked'`, `'error-dismissed'` |
|
|
30
|
-
| Search and filtering | A user interacts with data exploration tools | `'search-executed'`, `'date-range-changed'` |
|
|
31
|
-
|
|
32
|
-
> [!TIP]
|
|
33
|
-
> You do not need to track every click. Focus on actions that answer a question about user behavior or feature value.
|
|
34
|
-
|
|
35
|
-
### useTrackFeature
|
|
36
|
-
|
|
37
|
-
Returns a stable callback for tracking feature usage events. Each event automatically includes the current app key and active context as attributes, so downstream dashboards can group events by application and context without extra work.
|
|
38
|
-
|
|
39
|
-
**Signature:**
|
|
40
|
-
|
|
41
|
-
```typescript
|
|
42
|
-
function useTrackFeature(): (name: string, data?: AnyValueMap) => void;
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
| Parameter | Type | Description |
|
|
46
|
-
|-----------|------|-------------|
|
|
47
|
-
| `name` | `string` | Name of the feature being tracked |
|
|
48
|
-
| `data` | `AnyValueMap` (optional) | Additional key-value pairs to include with the event |
|
|
49
|
-
|
|
50
|
-
#### Track a button click
|
|
51
|
-
|
|
52
|
-
```tsx
|
|
53
|
-
import { useCallback } from 'react';
|
|
54
|
-
import { useTrackFeature } from '@equinor/fusion-framework-react-app/analytics';
|
|
55
|
-
|
|
56
|
-
const MyButton = () => {
|
|
57
|
-
const trackFeature = useTrackFeature();
|
|
58
|
-
|
|
59
|
-
const handleClick = useCallback(() => {
|
|
60
|
-
trackFeature('button-clicked');
|
|
61
|
-
}, [trackFeature]);
|
|
62
|
-
|
|
63
|
-
return <button onClick={handleClick}>Click me</button>;
|
|
64
|
-
};
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
#### Track with additional data
|
|
68
|
-
|
|
69
|
-
Pass a second argument to include custom attributes with the event:
|
|
70
|
-
|
|
71
|
-
```tsx
|
|
72
|
-
import { useCallback } from 'react';
|
|
73
|
-
import { useTrackFeature } from '@equinor/fusion-framework-react-app/analytics';
|
|
74
|
-
|
|
75
|
-
const SaveButton = ({ section }: { section: string }) => {
|
|
76
|
-
const trackFeature = useTrackFeature();
|
|
77
|
-
|
|
78
|
-
const handleSave = useCallback(() => {
|
|
79
|
-
trackFeature('save-clicked', { section, timestamp: Date.now() });
|
|
80
|
-
}, [trackFeature, section]);
|
|
81
|
-
|
|
82
|
-
return <button onClick={handleSave}>Save</button>;
|
|
83
|
-
};
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
#### Track on component mount
|
|
87
|
-
|
|
88
|
-
Use `useEffect` to track when a component or page loads:
|
|
89
|
-
|
|
90
|
-
```tsx
|
|
91
|
-
import { useEffect } from 'react';
|
|
92
|
-
import { useTrackFeature } from '@equinor/fusion-framework-react-app/analytics';
|
|
93
|
-
|
|
94
|
-
const Dashboard = () => {
|
|
95
|
-
const trackFeature = useTrackFeature();
|
|
96
|
-
|
|
97
|
-
useEffect(() => {
|
|
98
|
-
trackFeature('dashboard-loaded');
|
|
99
|
-
}, [trackFeature]);
|
|
100
|
-
|
|
101
|
-
return <div>Dashboard content</div>;
|
|
102
|
-
};
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
#### Combine click and mount tracking
|
|
106
|
-
|
|
107
|
-
```tsx
|
|
108
|
-
import { useCallback, useEffect } from 'react';
|
|
109
|
-
import { useTrackFeature } from '@equinor/fusion-framework-react-app/analytics';
|
|
110
|
-
|
|
111
|
-
const SomeComponent = () => {
|
|
112
|
-
const trackFeature = useTrackFeature();
|
|
113
|
-
|
|
114
|
-
useEffect(() => {
|
|
115
|
-
trackFeature('SomeComponent loaded');
|
|
116
|
-
trackFeature('some feature happened', { extra: 'data', foo: 'bar' });
|
|
117
|
-
}, [trackFeature]);
|
|
118
|
-
|
|
119
|
-
const handleOnClick = useCallback(() => {
|
|
120
|
-
trackFeature('button-clicked');
|
|
121
|
-
}, [trackFeature]);
|
|
122
|
-
|
|
123
|
-
return <button onClick={handleOnClick}>Click me</button>;
|
|
124
|
-
};
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
### Prerequisites
|
|
128
|
-
|
|
129
|
-
The analytics module must be enabled for these hooks to work. **In most cases, no app-level configuration is needed** — the hosting Fusion portal already enables and configures analytics with the appropriate adapters and collectors. Your app inherits this automatically.
|
|
130
|
-
|
|
131
|
-
If the module is not available (e.g. in a standalone or custom portal setup), `useTrackFeature` logs an exception via the telemetry provider instead of throwing, so your app will not crash.
|
|
132
|
-
|
|
133
|
-
#### Custom or standalone setups
|
|
134
|
-
|
|
135
|
-
If you are building a custom portal or running outside the standard Fusion portal, you need to enable the analytics module yourself:
|
|
136
|
-
|
|
137
|
-
```typescript
|
|
138
|
-
import { enableAnalytics } from '@equinor/fusion-framework-module-analytics';
|
|
139
|
-
import { ConsoleAnalyticsAdapter } from '@equinor/fusion-framework-module-analytics/adapters';
|
|
140
|
-
|
|
141
|
-
const configure = (configurator) => {
|
|
142
|
-
enableAnalytics(configurator, (builder) => {
|
|
143
|
-
builder.setAdapter('console', async () => new ConsoleAnalyticsAdapter());
|
|
144
|
-
});
|
|
145
|
-
};
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
See the [analytics module documentation](https://equinor.github.io/fusion-framework/modules/analytics/) for adapter and collector setup.
|
package/src/analytics/index.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Analytics sub-path entry-point.
|
|
3
|
-
*
|
|
4
|
-
* Provides the {@link useTrackFeature} hook for tracking application-level
|
|
5
|
-
* analytics events through the Fusion analytics module.
|
|
6
|
-
*
|
|
7
|
-
* @packageDocumentation
|
|
8
|
-
*/
|
|
9
|
-
export { useTrackFeature } from './useTrackFeature';
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import { useFrameworkModule } from '@equinor/fusion-framework-react';
|
|
2
|
-
import type { AppModule } from '@equinor/fusion-framework-module-app';
|
|
3
|
-
import type { AnalyticsModule, AnyValueMap } from '@equinor/fusion-framework-module-analytics';
|
|
4
|
-
import { useCallback } from 'react';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* React hook that returns a callback for tracking application feature usage
|
|
8
|
-
* via the Fusion analytics module.
|
|
9
|
-
*
|
|
10
|
-
* The tracked event includes the current app key and context (if available)
|
|
11
|
-
* as attributes, enabling downstream analytics dashboards to group events
|
|
12
|
-
* by application and context.
|
|
13
|
-
*
|
|
14
|
-
* @returns A `trackFeature` callback: `(name: string, data?: AnyValueMap) => void`.
|
|
15
|
-
*
|
|
16
|
-
* @example
|
|
17
|
-
* ```tsx
|
|
18
|
-
* const trackFeature = useTrackFeature();
|
|
19
|
-
*
|
|
20
|
-
* const handleClick = useCallback(() => {
|
|
21
|
-
* trackFeature('button-click', { section: 'header' });
|
|
22
|
-
* }, [trackFeature]);
|
|
23
|
-
* ```
|
|
24
|
-
*/
|
|
25
|
-
export const useTrackFeature = () => {
|
|
26
|
-
const analyticsProvider = useFrameworkModule<AnalyticsModule>('analytics');
|
|
27
|
-
const appProvider = useFrameworkModule<AppModule>('app');
|
|
28
|
-
const contextProvider = useFrameworkModule('context');
|
|
29
|
-
const telemetryProvider = useFrameworkModule('telemetry');
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Tracks a analytics event.
|
|
33
|
-
*
|
|
34
|
-
* Can be used both in useCallback or useEffects - see README.md for examples.
|
|
35
|
-
*
|
|
36
|
-
* @param name - The feature to track
|
|
37
|
-
* @param data - Optional map of additional key-value pairs to include with the analytics event
|
|
38
|
-
*/
|
|
39
|
-
const trackFeature = useCallback(
|
|
40
|
-
(name: string, data?: AnyValueMap) => {
|
|
41
|
-
// Report to telemetry when analytics tracking is not available so the gap is visible
|
|
42
|
-
if (!analyticsProvider) {
|
|
43
|
-
telemetryProvider?.trackException({
|
|
44
|
-
name: 'AnalyticsProviderNotFound',
|
|
45
|
-
exception: new Error(`Analytics provider not found`),
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
analyticsProvider?.trackAnalytic({
|
|
50
|
-
name: 'app-feature',
|
|
51
|
-
value: {
|
|
52
|
-
feature: name,
|
|
53
|
-
data,
|
|
54
|
-
},
|
|
55
|
-
attributes: {
|
|
56
|
-
appKey: appProvider?.current?.appKey,
|
|
57
|
-
context: contextProvider?.currentContext
|
|
58
|
-
? {
|
|
59
|
-
id: contextProvider.currentContext.id,
|
|
60
|
-
externalId: contextProvider.currentContext.externalId,
|
|
61
|
-
title: contextProvider.currentContext.title,
|
|
62
|
-
type: contextProvider.currentContext.type.id,
|
|
63
|
-
source: contextProvider.currentContext.source,
|
|
64
|
-
}
|
|
65
|
-
: undefined,
|
|
66
|
-
},
|
|
67
|
-
});
|
|
68
|
-
},
|
|
69
|
-
[analyticsProvider, appProvider, contextProvider, telemetryProvider],
|
|
70
|
-
);
|
|
71
|
-
|
|
72
|
-
return trackFeature;
|
|
73
|
-
};
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { useEffect, useRef, type ReactElement } from 'react';
|
|
2
|
-
import { useApploader } from './useApploader';
|
|
3
|
-
|
|
4
|
-
export type ApploaderProps = {
|
|
5
|
-
appKey: string;
|
|
6
|
-
};
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Apploader component
|
|
10
|
-
*
|
|
11
|
-
* Embeds a Fusion child application inside a parent Fusion application.
|
|
12
|
-
* Handles loading and error states, and mounts the child app's DOM element into a container div.
|
|
13
|
-
*
|
|
14
|
-
* @param { ApploaderProps } props - The props for the component.
|
|
15
|
-
* @param { string } props.appKey - The key of the Fusion app to load and mount.
|
|
16
|
-
* @returns { ReactElement } The rendered component, which displays loading, error, or the embedded app.
|
|
17
|
-
*
|
|
18
|
-
* @example
|
|
19
|
-
* <Apploader appKey="my-app" />
|
|
20
|
-
*/
|
|
21
|
-
export const Apploader = ({ appKey }: ApploaderProps): ReactElement => {
|
|
22
|
-
const refWrapp = useRef<HTMLDivElement | null>(null);
|
|
23
|
-
const { loading, error, appRef } = useApploader({ appKey });
|
|
24
|
-
|
|
25
|
-
useEffect(() => {
|
|
26
|
-
// Wait until both the wrapper element and the loaded app element are available
|
|
27
|
-
if (!refWrapp.current || !appRef.current) {
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
refWrapp.current.appendChild(appRef.current);
|
|
32
|
-
}, [appRef.current]);
|
|
33
|
-
|
|
34
|
-
// Show a loading placeholder while the app is being fetched and initialized
|
|
35
|
-
if (loading) {
|
|
36
|
-
return <div>Loading {appKey}</div>;
|
|
37
|
-
}
|
|
38
|
-
// Surface the load error instead of rendering the app
|
|
39
|
-
if (error) {
|
|
40
|
-
return (
|
|
41
|
-
<div>
|
|
42
|
-
Error loading {appKey}. Error: {error.message}
|
|
43
|
-
</div>
|
|
44
|
-
);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return <div ref={refWrapp} />;
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
export default Apploader;
|
package/src/apploader/README.md
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
# `@equinor/fusion-framework-react-app/apploader`
|
|
2
|
-
|
|
3
|
-
> [!NOTE]
|
|
4
|
-
> This file is kept for legacy tooling. The canonical documentation is in [`packages/react/app/docs/apploader.md`](../../docs/apploader.md) and published via VuePress.
|
|
5
|
-
|
|
6
|
-
[Apploader](#apploader) component and [useApploader](#useapploader) is intended to be used to embed Fusion applications inside other Fusion application.
|
|
7
|
-
|
|
8
|
-
> [!WARNING]
|
|
9
|
-
> `Apploader` is an experimental poc.
|
|
10
|
-
>
|
|
11
|
-
> The embedded application will likely have issues with routing, context and other framework functionality, so use with care.
|
|
12
|
-
>
|
|
13
|
-
> Should only be used to embed 'simple' applications like **PowerBI** and **PowerApps**.
|
|
14
|
-
|
|
15
|
-
## Apploader
|
|
16
|
-
|
|
17
|
-
React component for embeding a Fusion child application inside a parent Fusion application.
|
|
18
|
-
|
|
19
|
-
Handles loading and error states, and mounts the child app's DOM element into a container div.
|
|
20
|
-
|
|
21
|
-
If you need to customise the error and loading messages, then use the hook ``useApploader`` and create your own component.
|
|
22
|
-
|
|
23
|
-
### Example usage
|
|
24
|
-
|
|
25
|
-
```typescript
|
|
26
|
-
<Apploader appKey="my-app" />
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
## useApploader
|
|
30
|
-
|
|
31
|
-
A React hook for dynamically loading and mounting a Fusion child app inside a parent Fusion app. Handles loading state, error reporting, and provides a reference to the mounted app’s DOM element.
|
|
32
|
-
|
|
33
|
-
### Signature
|
|
34
|
-
|
|
35
|
-
```typescript
|
|
36
|
-
useApploader({ appKey }: { appKey: string }): {
|
|
37
|
-
loading: boolean;
|
|
38
|
-
error: Error | undefined;
|
|
39
|
-
appRef: React.RefObject<HTMLDivElement | null>;
|
|
40
|
-
}
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
### Parameters
|
|
44
|
-
|
|
45
|
-
`appKey (string)`: The key of the Fusion app to load and mount.
|
|
46
|
-
|
|
47
|
-
### Returns
|
|
48
|
-
|
|
49
|
-
- **loading** ``(boolean)``: true while the app is loading.
|
|
50
|
-
- **error** ``(Error | undefined)``: Error object if loading fails, otherwise undefined.
|
|
51
|
-
- **appRef** ``(React.RefObject<HTMLDivElement | null>)``: Ref to the DOM element where the child app is mounted.
|
|
52
|
-
|
|
53
|
-
### Usage Example
|
|
54
|
-
|
|
55
|
-
```typescript
|
|
56
|
-
import React, { useEffect, useRef } from 'react';
|
|
57
|
-
import { useApploader } from './useAppLoader';
|
|
58
|
-
|
|
59
|
-
const MyAppLoader = ({ appKey }: { appKey: string }) => {
|
|
60
|
-
const wrapperRef = useRef<HTMLDivElement | null>(null);
|
|
61
|
-
const { loading, error, appRef } = useApploader({ appKey });
|
|
62
|
-
|
|
63
|
-
useEffect(() => {
|
|
64
|
-
if (wrapperRef.current && appRef.current) {
|
|
65
|
-
wrapperRef.current.appendChild(appRef.current);
|
|
66
|
-
}
|
|
67
|
-
}, [appRef.current]);
|
|
68
|
-
|
|
69
|
-
if (loading) return <div>Loading {appKey}...</div>;
|
|
70
|
-
if (error) return <div>Error loading {appKey}: {error.message}</div>;
|
|
71
|
-
|
|
72
|
-
return <div ref={wrapperRef} />;
|
|
73
|
-
};
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
### Notes
|
|
77
|
-
|
|
78
|
-
- The hook is designed to be used in a parent Fusion app context.
|
|
79
|
-
- The returned appRef should be appended to a container element in your component.
|
|
80
|
-
- Handles subscription and cleanup automatically.
|
|
81
|
-
- Useful for micro-frontend scenarios where apps are loaded dynamically.
|
package/src/apploader/index.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Apploader sub-path entry-point.
|
|
3
|
-
*
|
|
4
|
-
* Provides the {@link Apploader} component and {@link useApploader} hook
|
|
5
|
-
* for embedding Fusion child applications inside a parent Fusion application.
|
|
6
|
-
*
|
|
7
|
-
* @packageDocumentation
|
|
8
|
-
*/
|
|
9
|
-
export { Apploader, type ApploaderProps } from './Apploader';
|
|
10
|
-
export { useApploader } from './useApploader';
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
-
|
|
3
|
-
import { useFramework } from '@equinor/fusion-framework-react';
|
|
4
|
-
import type { AppModule } from '@equinor/fusion-framework-module-app';
|
|
5
|
-
|
|
6
|
-
import type { ApploaderProps } from './Apploader';
|
|
7
|
-
import type { AppInitializeResult } from '@equinor/fusion-framework-module-app/app';
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* React hook for dynamically loading and mounting a Fusion child app inside a parent Fusion app.
|
|
11
|
-
* Handles loading state, error reporting, and provides a reference to the mounted app’s DOM element.
|
|
12
|
-
*
|
|
13
|
-
* @param { Object } params
|
|
14
|
-
* @param { string } params.appKey - The key of the Fusion app to load and mount.
|
|
15
|
-
* @returns {{
|
|
16
|
-
* loading: boolean,
|
|
17
|
-
* error: Error | undefined,
|
|
18
|
-
* appRef: React.RefObject<HTMLDivElement | null>
|
|
19
|
-
* }} An object containing loading state, error, and a ref to the mounted app element.
|
|
20
|
-
*
|
|
21
|
-
* @example
|
|
22
|
-
* ```typescript
|
|
23
|
-
* const { loading, error, appRef } = useApploader({ appKey: 'my-app' });
|
|
24
|
-
*
|
|
25
|
-
* useEffect(() => {
|
|
26
|
-
* if (containerRef.current && appRef.current) {
|
|
27
|
-
* containerRef.current.appendChild(appRef.current);
|
|
28
|
-
* }
|
|
29
|
-
* }, [appRef.current]);
|
|
30
|
-
*
|
|
31
|
-
* if (loading) return <div>Loading...</div>;
|
|
32
|
-
* if (error) return <div>Error: {error.message}</div>;
|
|
33
|
-
* return <div ref={containerRef} />;
|
|
34
|
-
* ```
|
|
35
|
-
*/
|
|
36
|
-
export const useApploader = ({
|
|
37
|
-
appKey,
|
|
38
|
-
}: ApploaderProps): {
|
|
39
|
-
loading: boolean;
|
|
40
|
-
error: Error | undefined;
|
|
41
|
-
appRef: React.RefObject<HTMLDivElement | null>;
|
|
42
|
-
} => {
|
|
43
|
-
const [loading, setLoading] = useState(true);
|
|
44
|
-
const [error, setError] = useState<Error | undefined>();
|
|
45
|
-
const appRef = useRef<HTMLDivElement | null>(null);
|
|
46
|
-
const fusion = useFramework<[AppModule]>();
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* The current fusion app.
|
|
50
|
-
* aka the parent app that is loading the child app.
|
|
51
|
-
*/
|
|
52
|
-
const fusionApp = useMemo(() => fusion.modules.app.current, [fusion]);
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* The app to be mounted
|
|
56
|
-
* aka the child app that is being loaded.
|
|
57
|
-
*/
|
|
58
|
-
const loadedApp = useMemo(() => fusion.modules.app.createApp({ appKey }), [fusion, appKey]);
|
|
59
|
-
|
|
60
|
-
useEffect(() => {
|
|
61
|
-
setLoading(true);
|
|
62
|
-
setError(undefined);
|
|
63
|
-
const subscription$ = loadedApp?.initialize().subscribe({
|
|
64
|
-
next: (instance: AppInitializeResult) => {
|
|
65
|
-
const { manifest, script, config } = instance;
|
|
66
|
-
/* Application Element for mounting */
|
|
67
|
-
appRef.current = document.createElement('div');
|
|
68
|
-
appRef.current.id = manifest.appKey;
|
|
69
|
-
appRef.current.style.display = 'contents';
|
|
70
|
-
|
|
71
|
-
/* Use basename of current fusionApp */
|
|
72
|
-
const basename = `/apps/${fusionApp?.appKey}`;
|
|
73
|
-
|
|
74
|
-
try {
|
|
75
|
-
const render = script.renderApp ?? script.default;
|
|
76
|
-
// Prefer the modern renderApp export, falling back to the default export
|
|
77
|
-
if (render) {
|
|
78
|
-
return render(appRef.current, {
|
|
79
|
-
fusion,
|
|
80
|
-
env: { basename, config, manifest, props: {} },
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
throw Error('Application is not supported, no render function provided');
|
|
84
|
-
} catch (error) {
|
|
85
|
-
console.error('App loading Error: ', error);
|
|
86
|
-
setError(error as Error);
|
|
87
|
-
}
|
|
88
|
-
},
|
|
89
|
-
complete: () => {
|
|
90
|
-
setLoading(false);
|
|
91
|
-
},
|
|
92
|
-
error: (error: Error) => {
|
|
93
|
-
setError(error);
|
|
94
|
-
setLoading(false);
|
|
95
|
-
},
|
|
96
|
-
});
|
|
97
|
-
return () => {
|
|
98
|
-
subscription$.unsubscribe();
|
|
99
|
-
};
|
|
100
|
-
}, [fusionApp, loadedApp, fusion]);
|
|
101
|
-
|
|
102
|
-
return {
|
|
103
|
-
loading,
|
|
104
|
-
error,
|
|
105
|
-
appRef,
|
|
106
|
-
};
|
|
107
|
-
};
|