@equinor/fusion-framework-dev-portal 11.0.3 → 11.0.5
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/dist/main.js +14779 -14675
- package/package.json +48 -43
- package/CHANGELOG.md +0 -1070
- package/dev-server.ts +0 -109
- package/src/AppLoader.tsx +0 -135
- package/src/BookmarkSideSheet.tsx +0 -47
- package/src/ContextSelector/ContextSelector.tsx +0 -85
- package/src/ContextSelector/index.ts +0 -1
- package/src/ContextSelector/useContextResolver.ts +0 -337
- package/src/EquinorLoader.tsx +0 -33
- package/src/ErrorViewer.tsx +0 -26
- package/src/FusionLogo.tsx +0 -76
- package/src/Header.tsx +0 -94
- package/src/HeaderActions.tsx +0 -47
- package/src/PersonSideSheet/index.tsx +0 -65
- package/src/PersonSideSheet/sheets/FeatureSheetContent.tsx +0 -52
- package/src/PersonSideSheet/sheets/FeatureTogglerApp.tsx +0 -39
- package/src/PersonSideSheet/sheets/FeatureTogglerPortal.tsx +0 -33
- package/src/PersonSideSheet/sheets/LandingSheetContent.tsx +0 -64
- package/src/PersonSideSheet/sheets/index.ts +0 -3
- package/src/PersonSideSheet/sheets/roles/ClaimableRole.test.tsx +0 -83
- package/src/PersonSideSheet/sheets/roles/ClaimableRole.tsx +0 -229
- package/src/PersonSideSheet/sheets/roles/RolesApi.test.ts +0 -62
- package/src/PersonSideSheet/sheets/roles/RolesApi.ts +0 -112
- package/src/PersonSideSheet/sheets/roles/RolesSheetContent.test.tsx +0 -86
- package/src/PersonSideSheet/sheets/roles/RolesSheetContent.tsx +0 -207
- package/src/PersonSideSheet/sheets/roles/index.ts +0 -1
- package/src/PersonSideSheet/sheets/styled.tsx +0 -33
- package/src/PersonSideSheet/sheets/types.ts +0 -14
- package/src/Router.tsx +0 -83
- package/src/configure.ts +0 -186
- package/src/get-app-tag-from-url.ts +0 -17
- package/src/globals.d.ts +0 -32
- package/src/main.tsx +0 -43
- package/src/resources/svg.ts +0 -7
- package/src/version.ts +0 -2
- package/tsconfig.json +0 -79
- package/tsconfig.tsbuildinfo +0 -1
- package/vite.config.ts +0 -15
- package/vitest.config.ts +0 -12
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react';
|
|
2
|
-
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
3
|
-
|
|
4
|
-
import { RolesSheetContent } from './RolesSheetContent';
|
|
5
|
-
|
|
6
|
-
const mocks = vi.hoisted(() => ({
|
|
7
|
-
createClient: vi.fn(),
|
|
8
|
-
currentUser: { localAccountId: 'account-id' },
|
|
9
|
-
framework: {
|
|
10
|
-
modules: { serviceDiscovery: { createClient: vi.fn() } },
|
|
11
|
-
},
|
|
12
|
-
}));
|
|
13
|
-
|
|
14
|
-
mocks.framework.modules.serviceDiscovery.createClient = mocks.createClient;
|
|
15
|
-
|
|
16
|
-
vi.mock('@equinor/fusion-framework-react', () => ({
|
|
17
|
-
useFramework: () => mocks.framework,
|
|
18
|
-
}));
|
|
19
|
-
|
|
20
|
-
vi.mock('@equinor/fusion-framework-react/hooks', () => ({
|
|
21
|
-
useCurrentUser: () => mocks.currentUser,
|
|
22
|
-
}));
|
|
23
|
-
|
|
24
|
-
vi.mock('./ClaimableRole', () => ({
|
|
25
|
-
ClaimableRole: () => <div>Claimable role</div>,
|
|
26
|
-
}));
|
|
27
|
-
|
|
28
|
-
describe('RolesSheetContent', () => {
|
|
29
|
-
beforeEach(() => {
|
|
30
|
-
mocks.createClient.mockReset();
|
|
31
|
-
mocks.currentUser.localAccountId = 'account-id';
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
afterEach(() => {
|
|
35
|
-
cleanup();
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it('renders loading and empty states', async () => {
|
|
39
|
-
const json = vi.fn().mockResolvedValue([]);
|
|
40
|
-
mocks.createClient.mockResolvedValue({ json });
|
|
41
|
-
|
|
42
|
-
render(<RolesSheetContent navigate={vi.fn()} />);
|
|
43
|
-
|
|
44
|
-
expect(screen.getByLabelText('Loading roles')).toBeTruthy();
|
|
45
|
-
expect(await screen.findByText('You have no available roles')).toBeTruthy();
|
|
46
|
-
expect(json).toHaveBeenCalledTimes(2);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it('retries role retrieval after a failure', async () => {
|
|
50
|
-
const json = vi
|
|
51
|
-
.fn()
|
|
52
|
-
.mockRejectedValueOnce(new Error('Roles service unavailable'))
|
|
53
|
-
.mockResolvedValue([]);
|
|
54
|
-
mocks.createClient.mockResolvedValue({ json });
|
|
55
|
-
|
|
56
|
-
render(<RolesSheetContent navigate={vi.fn()} />);
|
|
57
|
-
|
|
58
|
-
expect(await screen.findByText('Roles service unavailable')).toBeTruthy();
|
|
59
|
-
fireEvent.click(screen.getByRole('button', { name: 'Retry' }));
|
|
60
|
-
|
|
61
|
-
expect(screen.getByLabelText('Loading roles')).toBeTruthy();
|
|
62
|
-
await waitFor(() => expect(mocks.createClient).toHaveBeenCalledTimes(2));
|
|
63
|
-
expect(await screen.findByText('You have no available roles')).toBeTruthy();
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it('hides stale assignments while loading roles for a changed account', async () => {
|
|
67
|
-
const initialJson = vi
|
|
68
|
-
.fn()
|
|
69
|
-
.mockResolvedValueOnce([{ id: 'old-assignment' }])
|
|
70
|
-
.mockResolvedValueOnce([]);
|
|
71
|
-
const pendingJson = vi.fn().mockReturnValue(new Promise(() => undefined));
|
|
72
|
-
mocks.createClient
|
|
73
|
-
.mockResolvedValueOnce({ json: initialJson })
|
|
74
|
-
.mockResolvedValueOnce({ json: pendingJson });
|
|
75
|
-
|
|
76
|
-
const { rerender } = render(<RolesSheetContent navigate={vi.fn()} />);
|
|
77
|
-
|
|
78
|
-
expect(await screen.findByText('Claimable role')).toBeTruthy();
|
|
79
|
-
|
|
80
|
-
mocks.currentUser.localAccountId = 'next-account-id';
|
|
81
|
-
rerender(<RolesSheetContent navigate={vi.fn()} />);
|
|
82
|
-
|
|
83
|
-
expect(screen.getByLabelText('Loading roles')).toBeTruthy();
|
|
84
|
-
expect(screen.queryByText('Claimable role')).toBeNull();
|
|
85
|
-
});
|
|
86
|
-
});
|
|
@@ -1,207 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Banner,
|
|
3
|
-
Button,
|
|
4
|
-
CircularProgress,
|
|
5
|
-
Divider,
|
|
6
|
-
Icon,
|
|
7
|
-
Tabs,
|
|
8
|
-
Typography,
|
|
9
|
-
} from '@equinor/eds-core-react';
|
|
10
|
-
import { arrow_back, verified_user } from '@equinor/eds-icons';
|
|
11
|
-
import { useFramework } from '@equinor/fusion-framework-react';
|
|
12
|
-
import { useCurrentUser } from '@equinor/fusion-framework-react/hooks';
|
|
13
|
-
import { type ReactElement, useEffect, useRef, useState } from 'react';
|
|
14
|
-
import styled from 'styled-components';
|
|
15
|
-
|
|
16
|
-
import type { SheetContentProps } from '../types';
|
|
17
|
-
import { ClaimableRole } from './ClaimableRole';
|
|
18
|
-
import { RolesApi, type ClaimableRoleAssignment, type PermanentRoleAssignment } from './RolesApi';
|
|
19
|
-
|
|
20
|
-
Icon.add({ arrow_back, verified_user });
|
|
21
|
-
|
|
22
|
-
const Styled = {
|
|
23
|
-
Content: styled.div`
|
|
24
|
-
display: flex;
|
|
25
|
-
flex-direction: column;
|
|
26
|
-
gap: 1rem;
|
|
27
|
-
padding: 1rem 0.5rem;
|
|
28
|
-
`,
|
|
29
|
-
Role: styled.div`
|
|
30
|
-
display: flex;
|
|
31
|
-
gap: 1rem;
|
|
32
|
-
align-items: center;
|
|
33
|
-
padding: 0.5rem;
|
|
34
|
-
`,
|
|
35
|
-
Indicator: styled.div<{ $active: boolean }>`
|
|
36
|
-
width: 0.25rem;
|
|
37
|
-
height: 2.5rem;
|
|
38
|
-
background: ${({ $active }) => ($active ? '#007079' : '#dcdcdc')};
|
|
39
|
-
`,
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Displays the signed-in user's consolidated claimable and permanent Fusion roles.
|
|
44
|
-
*
|
|
45
|
-
* The role collections use the same Roles V2 endpoints as the production portal.
|
|
46
|
-
*
|
|
47
|
-
* @param props.navigate - Navigates back to the person side sheet landing page.
|
|
48
|
-
* @returns A tabbed role overview with loading, error, and empty states.
|
|
49
|
-
*/
|
|
50
|
-
export const RolesSheetContent = ({ navigate }: SheetContentProps): ReactElement => {
|
|
51
|
-
const framework = useFramework();
|
|
52
|
-
const user = useCurrentUser();
|
|
53
|
-
const [tab, setTab] = useState(0);
|
|
54
|
-
const [claimableRoles, setClaimableRoles] = useState<ClaimableRoleAssignment[]>([]);
|
|
55
|
-
const [permanentRoles, setPermanentRoles] = useState<PermanentRoleAssignment[]>([]);
|
|
56
|
-
const [isLoading, setIsLoading] = useState(true);
|
|
57
|
-
const [error, setError] = useState<string>();
|
|
58
|
-
const [loadAttempt, setLoadAttempt] = useState(0);
|
|
59
|
-
const latestLoadAttempt = useRef(loadAttempt);
|
|
60
|
-
latestLoadAttempt.current = loadAttempt;
|
|
61
|
-
|
|
62
|
-
/** Starts a fresh role request after a retrieval failure. */
|
|
63
|
-
const handleRetry = (): void => {
|
|
64
|
-
setError(undefined);
|
|
65
|
-
setIsLoading(true);
|
|
66
|
-
setLoadAttempt((attempt) => attempt + 1);
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Replaces a changed assignment after activation or deactivation succeeds.
|
|
71
|
-
* @param changedAssignment - Updated claimable assignment returned by the role row.
|
|
72
|
-
*/
|
|
73
|
-
const handleClaimableRoleChange = (changedAssignment: ClaimableRoleAssignment): void => {
|
|
74
|
-
setClaimableRoles((currentRoles) => {
|
|
75
|
-
// Preserve the endpoint ordering while updating only the role that changed.
|
|
76
|
-
return currentRoles.map((assignment) =>
|
|
77
|
-
assignment.id === changedAssignment.id ? changedAssignment : assignment,
|
|
78
|
-
);
|
|
79
|
-
});
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
useEffect(() => {
|
|
83
|
-
let isActive = true;
|
|
84
|
-
const currentLoadAttempt = loadAttempt;
|
|
85
|
-
|
|
86
|
-
// Hide assignments from the previous account before resolving the next role snapshot.
|
|
87
|
-
setIsLoading(true);
|
|
88
|
-
setError(undefined);
|
|
89
|
-
|
|
90
|
-
/** Loads both role collections together so the tabs represent one consistent snapshot. */
|
|
91
|
-
const loadRoles = async (): Promise<void> => {
|
|
92
|
-
// A Roles V2 account identifier is required before either endpoint can be queried.
|
|
93
|
-
if (!user?.localAccountId) {
|
|
94
|
-
setError('Unable to resolve the signed-in Fusion account.');
|
|
95
|
-
setIsLoading(false);
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
try {
|
|
100
|
-
const client = await framework.modules.serviceDiscovery.createClient('rolesv2');
|
|
101
|
-
const rolesApi = new RolesApi(client, user.localAccountId);
|
|
102
|
-
const [claimable, permanent] = await Promise.all([
|
|
103
|
-
rolesApi.getClaimableRoles(),
|
|
104
|
-
rolesApi.getPermanentRoles(),
|
|
105
|
-
]);
|
|
106
|
-
|
|
107
|
-
// Ignore a completed request after the side sheet content has unmounted.
|
|
108
|
-
if (isActive && latestLoadAttempt.current === currentLoadAttempt) {
|
|
109
|
-
setClaimableRoles(claimable);
|
|
110
|
-
setPermanentRoles(permanent);
|
|
111
|
-
setError(undefined);
|
|
112
|
-
}
|
|
113
|
-
} catch (cause) {
|
|
114
|
-
// Keep transport details out of the side sheet while preserving a useful retry direction.
|
|
115
|
-
if (isActive && latestLoadAttempt.current === currentLoadAttempt) {
|
|
116
|
-
setError(cause instanceof Error ? cause.message : 'Failed to load roles.');
|
|
117
|
-
}
|
|
118
|
-
} finally {
|
|
119
|
-
// Avoid updating state when navigation unmounts this sheet during a request.
|
|
120
|
-
if (isActive && latestLoadAttempt.current === currentLoadAttempt) {
|
|
121
|
-
setIsLoading(false);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
void loadRoles();
|
|
127
|
-
|
|
128
|
-
return () => {
|
|
129
|
-
isActive = false;
|
|
130
|
-
};
|
|
131
|
-
}, [framework, user?.localAccountId, loadAttempt]);
|
|
132
|
-
|
|
133
|
-
// Prepare role rows before markup so the tab panels only render presentation state.
|
|
134
|
-
const claimableItems = claimableRoles.map((assignment) => (
|
|
135
|
-
<ClaimableRole
|
|
136
|
-
key={assignment.id}
|
|
137
|
-
assignment={assignment}
|
|
138
|
-
onChange={handleClaimableRoleChange}
|
|
139
|
-
/>
|
|
140
|
-
));
|
|
141
|
-
|
|
142
|
-
// Permanent assignments include scope context but do not expose activation controls.
|
|
143
|
-
const permanentItems = permanentRoles.map((assignment) => {
|
|
144
|
-
const scope =
|
|
145
|
-
!assignment.scope || assignment.scope.isGlobal ? 'Global' : assignment.scope.value;
|
|
146
|
-
return (
|
|
147
|
-
<Styled.Role key={assignment.id}>
|
|
148
|
-
<Styled.Indicator $active={true} />
|
|
149
|
-
<div>
|
|
150
|
-
<Typography>{assignment.role.displayName}</Typography>
|
|
151
|
-
<Typography variant="overline">
|
|
152
|
-
{assignment.role.name}
|
|
153
|
-
{scope ? ` (${scope})` : ''}
|
|
154
|
-
</Typography>
|
|
155
|
-
</div>
|
|
156
|
-
</Styled.Role>
|
|
157
|
-
);
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
return (
|
|
161
|
-
<section>
|
|
162
|
-
<Button variant="ghost" onClick={() => navigate()}>
|
|
163
|
-
<Icon name="arrow_back" />
|
|
164
|
-
<Icon name="verified_user" />
|
|
165
|
-
My Roles
|
|
166
|
-
</Button>
|
|
167
|
-
<Divider />
|
|
168
|
-
<Styled.Content>
|
|
169
|
-
{isLoading ? (
|
|
170
|
-
<CircularProgress aria-label="Loading roles" />
|
|
171
|
-
) : error ? (
|
|
172
|
-
<>
|
|
173
|
-
<Banner>
|
|
174
|
-
<Banner.Message>{error}</Banner.Message>
|
|
175
|
-
</Banner>
|
|
176
|
-
<Button variant="outlined" onClick={handleRetry}>
|
|
177
|
-
Retry
|
|
178
|
-
</Button>
|
|
179
|
-
</>
|
|
180
|
-
) : (
|
|
181
|
-
<Tabs activeTab={tab} onChange={(index) => setTab(Number(index))}>
|
|
182
|
-
<Tabs.List>
|
|
183
|
-
<Tabs.Tab>Claimable</Tabs.Tab>
|
|
184
|
-
<Tabs.Tab>Permanent</Tabs.Tab>
|
|
185
|
-
</Tabs.List>
|
|
186
|
-
<Tabs.Panels>
|
|
187
|
-
<Tabs.Panel>
|
|
188
|
-
{claimableItems.length > 0 ? (
|
|
189
|
-
claimableItems
|
|
190
|
-
) : (
|
|
191
|
-
<Typography>You have no available roles</Typography>
|
|
192
|
-
)}
|
|
193
|
-
</Tabs.Panel>
|
|
194
|
-
<Tabs.Panel>
|
|
195
|
-
{permanentItems.length > 0 ? (
|
|
196
|
-
permanentItems
|
|
197
|
-
) : (
|
|
198
|
-
<Typography>You have no permanent roles assigned</Typography>
|
|
199
|
-
)}
|
|
200
|
-
</Tabs.Panel>
|
|
201
|
-
</Tabs.Panels>
|
|
202
|
-
</Tabs>
|
|
203
|
-
)}
|
|
204
|
-
</Styled.Content>
|
|
205
|
-
</section>
|
|
206
|
-
);
|
|
207
|
-
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { RolesSheetContent } from './RolesSheetContent';
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import styled from 'styled-components';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Shared styled components used by feature toggle lists in the person side sheet.
|
|
5
|
-
*/
|
|
6
|
-
export const Styled = {
|
|
7
|
-
SwitchList: styled.ul`
|
|
8
|
-
list-style: none;
|
|
9
|
-
padding-left: 0;
|
|
10
|
-
`,
|
|
11
|
-
SwitchListItem: styled.li`
|
|
12
|
-
display: flex;
|
|
13
|
-
flex-flow: row nowrap;
|
|
14
|
-
justify-content: space-between;
|
|
15
|
-
margin: 1em 0;
|
|
16
|
-
border-left: 3px solid var(--eds_interactive_primary__resting, rgba(0, 112, 121, 1));
|
|
17
|
-
cursor: pointer;
|
|
18
|
-
`,
|
|
19
|
-
SwitchLabel: styled.div`
|
|
20
|
-
display: flex;
|
|
21
|
-
flex-direction: column;
|
|
22
|
-
align-items: flex-start;
|
|
23
|
-
justify-content: center;
|
|
24
|
-
width: 85%;
|
|
25
|
-
transform: scale(0.9);
|
|
26
|
-
`,
|
|
27
|
-
Switch: styled.div`
|
|
28
|
-
width: 15%;
|
|
29
|
-
display: flex;
|
|
30
|
-
justify-content: flex-end;
|
|
31
|
-
transform: scale(0.9);
|
|
32
|
-
`,
|
|
33
|
-
};
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared props for person side sheet sub-pages.
|
|
3
|
-
*
|
|
4
|
-
* Each sheet content component receives these props from the parent
|
|
5
|
-
* {@link PersonSideSheet} to support navigation between sheets.
|
|
6
|
-
*/
|
|
7
|
-
export type SheetContentProps = {
|
|
8
|
-
/** Azure AD object ID of the current user. */
|
|
9
|
-
readonly azureId?: string;
|
|
10
|
-
/** Key of the currently active sheet. */
|
|
11
|
-
readonly sheet?: string;
|
|
12
|
-
/** Navigates to a different sheet by key, or back to the landing sheet when called without arguments. */
|
|
13
|
-
navigate(sheet?: string): void;
|
|
14
|
-
};
|
package/src/Router.tsx
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import { useBookmarkNavigate } from '@equinor/fusion-framework-react-module-bookmark/portal';
|
|
2
|
-
|
|
3
|
-
import { Router as FusionRouter, Outlet, useParams } from '@equinor/fusion-framework-react-router';
|
|
4
|
-
import AppLoader from './AppLoader';
|
|
5
|
-
import { Header } from './Header';
|
|
6
|
-
|
|
7
|
-
import { styled } from 'styled-components';
|
|
8
|
-
|
|
9
|
-
const Styled = {
|
|
10
|
-
ContentContainer: styled.div`
|
|
11
|
-
display: grid;
|
|
12
|
-
grid-template-columns: 1fr;
|
|
13
|
-
grid-template-rows: 48px 1fr;
|
|
14
|
-
height: 100vh;
|
|
15
|
-
overflow: hidden;
|
|
16
|
-
grid-template-areas: 'head' 'main';
|
|
17
|
-
`,
|
|
18
|
-
Head: styled.section`
|
|
19
|
-
grid-area: head;
|
|
20
|
-
z-index: 2;
|
|
21
|
-
`,
|
|
22
|
-
Main: styled.section`
|
|
23
|
-
grid-area: main;
|
|
24
|
-
--header-height: 48px;
|
|
25
|
-
overflow: auto;
|
|
26
|
-
position: relative;
|
|
27
|
-
z-index: 1;
|
|
28
|
-
max-width: 100%;
|
|
29
|
-
display: grid;
|
|
30
|
-
`,
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Root layout component for the dev portal.
|
|
35
|
-
*
|
|
36
|
-
* Renders the header and a scrollable main area via `Outlet`. Activates
|
|
37
|
-
* bookmark-to-navigation linking through `useBookmarkNavigate`.
|
|
38
|
-
*/
|
|
39
|
-
const Root = () => {
|
|
40
|
-
useBookmarkNavigate({ resolveAppPath: (appKey: string) => `/apps/${appKey}` });
|
|
41
|
-
return (
|
|
42
|
-
<Styled.ContentContainer>
|
|
43
|
-
<Styled.Head>
|
|
44
|
-
<Header />
|
|
45
|
-
</Styled.Head>
|
|
46
|
-
<Styled.Main>
|
|
47
|
-
<Outlet />
|
|
48
|
-
</Styled.Main>
|
|
49
|
-
</Styled.ContentContainer>
|
|
50
|
-
);
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Route component that extracts the `appKey` parameter and delegates to {@link AppLoader}.
|
|
55
|
-
*/
|
|
56
|
-
const AppRoute = () => {
|
|
57
|
-
const { appKey } = useParams();
|
|
58
|
-
return appKey ? <AppLoader appKey={appKey} /> : null;
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
/** Route definitions for the dev portal. */
|
|
62
|
-
const routes = [
|
|
63
|
-
{
|
|
64
|
-
path: '/',
|
|
65
|
-
element: <Root />,
|
|
66
|
-
children: [
|
|
67
|
-
{
|
|
68
|
-
path: 'apps/:appKey/*',
|
|
69
|
-
element: <AppRoute />,
|
|
70
|
-
},
|
|
71
|
-
],
|
|
72
|
-
},
|
|
73
|
-
];
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Top-level router for the Fusion Dev Portal.
|
|
77
|
-
*
|
|
78
|
-
* Uses `@equinor/fusion-framework-react-router` which automatically connects
|
|
79
|
-
* to the framework's navigation module for history and basename.
|
|
80
|
-
*/
|
|
81
|
-
export const Router = () => {
|
|
82
|
-
return <FusionRouter routes={routes} />;
|
|
83
|
-
};
|
package/src/configure.ts
DELETED
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
import { enableAppModule, type AppModule } from '@equinor/fusion-framework-module-app';
|
|
2
|
-
import { enableBookmark } from '@equinor/fusion-framework-react-module-bookmark';
|
|
3
|
-
import type { FrameworkConfigurator } from '@equinor/fusion-framework';
|
|
4
|
-
import { enableAnalytics } from '@equinor/fusion-framework-module-analytics';
|
|
5
|
-
import { ConsoleAnalyticsAdapter } from '@equinor/fusion-framework-module-analytics/adapters';
|
|
6
|
-
import { enableContext } from '@equinor/fusion-framework-module-context';
|
|
7
|
-
import {
|
|
8
|
-
enableNavigation,
|
|
9
|
-
type NavigationModule,
|
|
10
|
-
} from '@equinor/fusion-framework-module-navigation';
|
|
11
|
-
import { enableServices } from '@equinor/fusion-framework-module-services';
|
|
12
|
-
import { enableFeatureFlagging } from '@equinor/fusion-framework-module-feature-flag';
|
|
13
|
-
import {
|
|
14
|
-
createLocalStoragePlugin,
|
|
15
|
-
createUrlPlugin,
|
|
16
|
-
} from '@equinor/fusion-framework-module-feature-flag/plugins';
|
|
17
|
-
import { enableAgGrid } from '@equinor/fusion-framework-module-ag-grid';
|
|
18
|
-
import { enableTelemetry } from '@equinor/fusion-framework-module-telemetry';
|
|
19
|
-
import {
|
|
20
|
-
enableContextNavigation,
|
|
21
|
-
legacyAppNavigationFix,
|
|
22
|
-
} from '@equinor/fusion-framework-plugin-context-navigation';
|
|
23
|
-
import {
|
|
24
|
-
buildContextUrlForStrategy,
|
|
25
|
-
resolveContextIdFromUrl,
|
|
26
|
-
} from '@equinor/fusion-framework-plugin-context-navigation/utils';
|
|
27
|
-
import { version } from './version';
|
|
28
|
-
|
|
29
|
-
declare global {
|
|
30
|
-
interface Window {
|
|
31
|
-
/**
|
|
32
|
-
* AG Grid license key for enabling enterprise features.
|
|
33
|
-
* @remarks Typically set via environment variables during build time.
|
|
34
|
-
*/
|
|
35
|
-
FUSION_AG_GRID_KEY?: string;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Configures the Fusion Dev Portal framework with all required modules.
|
|
41
|
-
*
|
|
42
|
-
* Modules enabled:
|
|
43
|
-
* - **Telemetry** — portal-scoped usage analytics with version metadata.
|
|
44
|
-
* - **App** — application manifest loading and lifecycle.
|
|
45
|
-
* - **Context** — context routing URL hooks (path generator + extractor) wired to the shared context-navigation URL utilities.
|
|
46
|
-
* - **Context Navigation plugin** — keeps the browser URL in sync with the
|
|
47
|
-
* active context, handles app-switch carry-over, and guards against
|
|
48
|
-
* accidental context loss. Telemetry is auto-resolved from the framework
|
|
49
|
-
* telemetry module.
|
|
50
|
-
* - **Navigation** — router integration with telemetry.
|
|
51
|
-
* - **Services** — standard Fusion service integrations.
|
|
52
|
-
* - **AG Grid** — enterprise license key from `window.FUSION_AG_GRID_KEY`.
|
|
53
|
-
* - **Analytics** — console adapter gated by the `fusionLogAnalytics` feature flag.
|
|
54
|
-
* - **Bookmarks** — source-system metadata identifying CLI-created bookmarks.
|
|
55
|
-
* - **Feature flags** — local-storage and URL-based flag plugins for dev toggles.
|
|
56
|
-
*
|
|
57
|
-
* On initialization, exposes all modules on `window.Fusion` for debugging.
|
|
58
|
-
*
|
|
59
|
-
* @param config - The framework configurator instance to extend with portal modules.
|
|
60
|
-
*/
|
|
61
|
-
export const configure = async (config: FrameworkConfigurator) => {
|
|
62
|
-
// Enable telemetry tracking for portal usage analytics and monitoring
|
|
63
|
-
enableTelemetry(config, {
|
|
64
|
-
attachConfiguratorEvents: true,
|
|
65
|
-
configure: (builder, ref) => {
|
|
66
|
-
// Set metadata identifying this as portal telemetry with version info
|
|
67
|
-
builder.setMetadata(() => ({
|
|
68
|
-
fusion: {
|
|
69
|
-
type: 'portal-telemetry',
|
|
70
|
-
portal: { version, name: 'Fusion Dev Portal' },
|
|
71
|
-
},
|
|
72
|
-
}));
|
|
73
|
-
// Scope telemetry events to portal-specific tracking
|
|
74
|
-
builder.setDefaultScope(['portal']);
|
|
75
|
-
// Inherit parent telemetry configuration for consistent tracking
|
|
76
|
-
builder.setParent(ref.telemetry);
|
|
77
|
-
},
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
enableAppModule(config);
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Configure context module with dev-portal URL conventions.
|
|
84
|
-
*
|
|
85
|
-
* This wires the context module's URL hooks — the path generator
|
|
86
|
-
* and path extractor — to the dev-portal's URL routing strategy.
|
|
87
|
-
*
|
|
88
|
-
* The context-navigation plugin keeps the browser URL in sync with the
|
|
89
|
-
* active context as it changes at runtime.
|
|
90
|
-
*/
|
|
91
|
-
enableContext(config, (builder) => {
|
|
92
|
-
builder.setContextPathGenerator((context, path) =>
|
|
93
|
-
buildContextUrlForStrategy(context?.id, path),
|
|
94
|
-
);
|
|
95
|
-
|
|
96
|
-
builder.setContextPathExtractor((path) => resolveContextIdFromUrl(path));
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
enableNavigation(config, {
|
|
100
|
-
configure: (config) => {
|
|
101
|
-
config.setBasename('/');
|
|
102
|
-
config.setTelemetry(async (args) => {
|
|
103
|
-
// Only provide telemetry when the telemetry module was actually enabled
|
|
104
|
-
if (args.hasModule('telemetry')) {
|
|
105
|
-
return await args.requireInstance('telemetry');
|
|
106
|
-
}
|
|
107
|
-
});
|
|
108
|
-
},
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
enableServices(config);
|
|
112
|
-
|
|
113
|
-
// Configure AG Grid with license key from environment if provided
|
|
114
|
-
enableAgGrid(config, (builder) => {
|
|
115
|
-
builder.setLicenseKey(window.FUSION_AG_GRID_KEY);
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
enableAnalytics(config, (builder) => {
|
|
119
|
-
builder.setAdapter('console', async (args) => {
|
|
120
|
-
// Only resolve the feature-flag-gated adapter when the featureFlag module is enabled
|
|
121
|
-
if (args.hasModule('featureFlag')) {
|
|
122
|
-
const featureFlagProvider = await args.requireInstance('featureFlag');
|
|
123
|
-
// Only log analytics to the console when the feature flag is explicitly enabled
|
|
124
|
-
if (featureFlagProvider.getFeature('fusionLogAnalytics')?.enabled) {
|
|
125
|
-
return new ConsoleAnalyticsAdapter();
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
});
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
// Configure bookmark functionality with CLI as the source system
|
|
132
|
-
enableBookmark(config, (builder) => {
|
|
133
|
-
// Identify bookmarks created in dev portal as coming from CLI system
|
|
134
|
-
builder.setSourceSystem({
|
|
135
|
-
subSystem: 'CLI',
|
|
136
|
-
identifier: 'fusion-cli',
|
|
137
|
-
name: 'Fusion CLI',
|
|
138
|
-
});
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
// Configure feature flags for development and demo purposes
|
|
142
|
-
enableFeatureFlagging(config, (builder) => {
|
|
143
|
-
// Add local storage plugin for persistent feature flag storage
|
|
144
|
-
builder.addPlugin(
|
|
145
|
-
createLocalStoragePlugin([
|
|
146
|
-
{
|
|
147
|
-
key: 'fusionDebug',
|
|
148
|
-
title: 'Fusion debug log',
|
|
149
|
-
description: 'Show Fusion debug log in console',
|
|
150
|
-
},
|
|
151
|
-
{
|
|
152
|
-
key: 'fusionLogAnalytics',
|
|
153
|
-
title: 'Log Fusion Analytics',
|
|
154
|
-
description: 'Show Analytics events in console',
|
|
155
|
-
},
|
|
156
|
-
{
|
|
157
|
-
key: 'pinkBg',
|
|
158
|
-
title: 'Use pink bg?',
|
|
159
|
-
description: 'When enabled the background should be pink',
|
|
160
|
-
},
|
|
161
|
-
]),
|
|
162
|
-
);
|
|
163
|
-
// Add URL plugin to allow enabling debug features via query parameters
|
|
164
|
-
builder.addPlugin(createUrlPlugin(['fusionDebug']));
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
// Keep portal URLs aligned with the active app/context combination by using
|
|
168
|
-
// the shared context-navigation plugin and the dev-portal URL helpers.
|
|
169
|
-
enableContextNavigation(config, (builder) => {
|
|
170
|
-
builder.setPortalName('dev-portal');
|
|
171
|
-
builder.setDebug(true);
|
|
172
|
-
builder.setUrlGuard(true);
|
|
173
|
-
builder.setNavigationOptions({
|
|
174
|
-
replace: false, // Use pushState for navigation to allow back button support
|
|
175
|
-
});
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
config.onInitialized<[AppModule, NavigationModule]>((modules) => {
|
|
179
|
-
// Reset legacy app routers on context navigation for apps with navigation <v7.
|
|
180
|
-
legacyAppNavigationFix({ event: modules.event });
|
|
181
|
-
|
|
182
|
-
// Expose framework modules globally for development debugging and inspection.
|
|
183
|
-
// @ts-expect-error — `window` is not typed with `Fusion`
|
|
184
|
-
window.Fusion = { modules };
|
|
185
|
-
});
|
|
186
|
-
};
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* URL search-parameter key used to specify an app version tag.
|
|
3
|
-
*
|
|
4
|
-
* When present in the URL as `?$tag=<value>`, the portal loads that
|
|
5
|
-
* specific version of the application instead of the default.
|
|
6
|
-
*/
|
|
7
|
-
const TAG = '$tag';
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Reads the application version tag from the current URL search parameters.
|
|
11
|
-
*
|
|
12
|
-
* @returns The tag string if the `$tag` search parameter is present, otherwise `null`.
|
|
13
|
-
*/
|
|
14
|
-
export const getAppTagFromUrl = (): string | null => {
|
|
15
|
-
const url = new URL(window.location.href);
|
|
16
|
-
return url.searchParams.get(TAG);
|
|
17
|
-
};
|
package/src/globals.d.ts
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Ambient JSX type declarations for Lit/WCEV web components used by dev-portal.
|
|
3
|
-
*
|
|
4
|
-
* `@types/react@19` removed the global `JSX` namespace — augmentations must
|
|
5
|
-
* target the `react` module's `JSX` namespace instead.
|
|
6
|
-
*
|
|
7
|
-
* The `@equinor/fusion-wc-person` package still uses the old `global JSX` pattern
|
|
8
|
-
* with computed-property keys so its declarations are not picked up by the compiler.
|
|
9
|
-
* This file re-declares the elements with string-literal keys in the correct location.
|
|
10
|
-
*/
|
|
11
|
-
export {};
|
|
12
|
-
|
|
13
|
-
declare module 'react' {
|
|
14
|
-
namespace JSX {
|
|
15
|
-
interface IntrinsicElements {
|
|
16
|
-
'fwc-person-avatar': React.DetailedHTMLProps<
|
|
17
|
-
React.HTMLAttributes<HTMLElement> & {
|
|
18
|
-
azureId?: string;
|
|
19
|
-
size?: string;
|
|
20
|
-
clickable?: boolean;
|
|
21
|
-
},
|
|
22
|
-
HTMLElement
|
|
23
|
-
>;
|
|
24
|
-
'fwc-person-list-item': React.DetailedHTMLProps<
|
|
25
|
-
React.HTMLAttributes<HTMLElement> & {
|
|
26
|
-
azureId?: string;
|
|
27
|
-
},
|
|
28
|
-
HTMLElement
|
|
29
|
-
>;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
}
|