orc-shared 1.2.0-dev.1 → 1.2.0-dev.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/actions/applications.js +1 -1
- package/dist/actions/authentication.js +1 -1
- package/dist/actions/countries.js +1 -1
- package/dist/actions/locale.js +1 -1
- package/dist/actions/makeApiAction.js +1 -1
- package/dist/actions/metadata.js +1 -1
- package/dist/actions/modules.js +63 -1
- package/dist/actions/navigation.js +1 -1
- package/dist/actions/requestsApi.js +8 -8
- package/dist/actions/scopes.js +59 -22
- package/dist/actions/timezones.js +1 -1
- package/dist/actions/toasts.js +1 -1
- package/dist/actions/versionInfo.js +1 -1
- package/dist/actions/view.js +1 -1
- package/dist/buildStore.js +1 -1
- package/dist/components/AppFrame/About.js +4 -3
- package/dist/components/AppFrame/MenuItem.js +6 -15
- package/dist/components/AppFrame/Preferences.js +1 -1
- package/dist/components/AppFrame/Sidebar.js +20 -9
- package/dist/components/AppFrame/Topbar.js +1 -1
- package/dist/components/ApplicationModuleLoader.js +143 -0
- package/dist/components/Authenticate.js +13 -13
- package/dist/components/CategoryList.js +1 -1
- package/dist/components/Checkbox.js +1 -1
- package/dist/components/DropMenu/Menu.js +1 -1
- package/dist/components/DropMenu/index.js +1 -1
- package/dist/components/Form/FieldList.js +1 -1
- package/dist/components/Form/Form.js +1 -1
- package/dist/components/Form/Inputs/Button.js +1 -1
- package/dist/components/Form/Inputs/FieldButtons.js +1 -1
- package/dist/components/Form/Inputs/Number.js +1 -1
- package/dist/components/Form/Inputs/ReadOnly.js +1 -1
- package/dist/components/Form/Inputs/SmallButton.js +1 -1
- package/dist/components/Form/Inputs/Text.js +1 -1
- package/dist/components/Form/Inputs/Time.js +1 -1
- package/dist/components/Form/Inputs/Toggles.js +1 -1
- package/dist/components/Form/Inputs/Translation.js +1 -1
- package/dist/components/List/HeadCell.js +1 -1
- package/dist/components/List/List.js +1 -1
- package/dist/components/List/Row.js +1 -1
- package/dist/components/MaterialUI/DataDisplay/List.js +1 -1
- package/dist/components/MaterialUI/DataDisplay/NotificationProps.js +1 -1
- package/dist/components/MaterialUI/DataDisplay/PredefinedElements/Translations.js +1 -1
- package/dist/components/MaterialUI/DataDisplay/Table.js +1 -1
- package/dist/components/MaterialUI/DataDisplay/TableProps.js +1 -1
- package/dist/components/MaterialUI/DataDisplay/TransferList.js +1 -1
- package/dist/components/MaterialUI/DataDisplay/chipProps.js +1 -1
- package/dist/components/MaterialUI/DataDisplay/collapsableListProps.js +1 -1
- package/dist/components/MaterialUI/DataDisplay/dividerProps.js +1 -1
- package/dist/components/MaterialUI/DataDisplay/index.js +1 -1
- package/dist/components/MaterialUI/DataDisplay/modalProps.js +1 -1
- package/dist/components/MaterialUI/DataDisplay/useTableSelection.js +1 -1
- package/dist/components/MaterialUI/Feedback/useNotification.js +1 -1
- package/dist/components/MaterialUI/Inputs/AutocompleteProps.js +1 -1
- package/dist/components/MaterialUI/Inputs/CheckboxGroupProps.js +1 -1
- package/dist/components/MaterialUI/Inputs/CheckboxProps.js +1 -1
- package/dist/components/MaterialUI/Inputs/InputBase.js +1 -1
- package/dist/components/MaterialUI/Inputs/InputBaseProps.js +1 -1
- package/dist/components/MaterialUI/Inputs/PredefinedElements/SearchControl.js +1 -1
- package/dist/components/MaterialUI/Inputs/RadioProps.js +1 -1
- package/dist/components/MaterialUI/Inputs/SelectProps.js +1 -1
- package/dist/components/MaterialUI/Inputs/Switch.js +1 -1
- package/dist/components/MaterialUI/Inputs/SwitchProps.js +1 -1
- package/dist/components/MaterialUI/Inputs/TimePicker.js +1 -1
- package/dist/components/MaterialUI/Inputs/createInput.js +1 -1
- package/dist/components/MaterialUI/Inputs/index.js +1 -1
- package/dist/components/MaterialUI/Inputs/standaloneRadioProps.js +1 -1
- package/dist/components/MaterialUI/Navigation/DropDownMenuProps.js +1 -1
- package/dist/components/MaterialUI/Navigation/ExternalLink.js +113 -0
- package/dist/components/MaterialUI/Surfaces/expansionPanelProps.js +1 -1
- package/dist/components/MaterialUI/Surfaces/paperProps.js +1 -1
- package/dist/components/MaterialUI/muiThemes.js +5 -0
- package/dist/components/MaterialUI/textProps.js +1 -1
- package/dist/components/Modules.js +126 -41
- package/dist/components/MultiSelector.js +1 -1
- package/dist/components/Navigation/Bar.js +1 -1
- package/dist/components/Navigation/Tab.js +1 -1
- package/dist/components/Navigation/useNavigationState.js +1 -1
- package/dist/components/Placeholder.js +1 -1
- package/dist/components/Routing/FullPage.js +3 -1
- package/dist/components/Routing/Page.js +5 -3
- package/dist/components/Routing/Segment.js +1 -1
- package/dist/components/Routing/SegmentPage.js +1 -1
- package/dist/components/Routing/withWaypointing.js +6 -2
- package/dist/components/Scope/ScopeNode.js +1 -1
- package/dist/components/Scope/Selector.js +1 -1
- package/dist/components/Scope/index.js +1 -1
- package/dist/components/Scope/useScopeConfirmationModalState.js +7 -16
- package/dist/components/Scope/useScopeData.js +4 -13
- package/dist/components/Scope/useScopeSelect.js +1 -1
- package/dist/components/Selector.js +1 -1
- package/dist/components/Spritesheet.js +1 -1
- package/dist/components/Switch.js +1 -1
- package/dist/components/Text.js +1 -1
- package/dist/components/ToastList.js +1 -1
- package/dist/components/Toolbar.js +1 -1
- package/dist/components/Treeview/Label.js +1 -1
- package/dist/components/Treeview/Leaf.js +1 -1
- package/dist/components/Treeview/Node.js +1 -1
- package/dist/components/Treeview/settings.js +1 -1
- package/dist/constants.js +19 -2
- package/dist/content/icons/orckestra-icon.svg +5 -0
- package/dist/content/iconsSheet.svg +8 -0
- package/dist/content/orckestra-logo-white.png +0 -0
- package/dist/hocs/withInfiniteScroll.js +1 -1
- package/dist/hooks/useDispatchWithModulesData.js +1 -1
- package/dist/hooks/useEditState.js +1 -1
- package/dist/hooks/useFullEntityEditState.js +1 -1
- package/dist/hooks/useLabelMessage.js +1 -1
- package/dist/hooks/useNavigationHandler.js +1 -1
- package/dist/hooks/useSelectorAndUnwrap.js +1 -1
- package/dist/reducers/metadata.js +1 -1
- package/dist/reducers/modules.js +39 -1
- package/dist/reducers/scopes.js +27 -0
- package/dist/reducers/settings.js +31 -2
- package/dist/schemas/countries.js +1 -1
- package/dist/schemas/definitions.js +1 -1
- package/dist/schemas/metadata.js +1 -1
- package/dist/schemas/productDefinitions.js +1 -1
- package/dist/schemas/timezones.js +1 -1
- package/dist/selectors/applications.js +1 -1
- package/dist/selectors/authentication.js +57 -18
- package/dist/selectors/countries.js +1 -1
- package/dist/selectors/locale.js +1 -1
- package/dist/selectors/metadata.js +1 -1
- package/dist/selectors/modules.js +15 -1
- package/dist/selectors/navigation.js +1 -1
- package/dist/selectors/requests.js +1 -1
- package/dist/selectors/scope.js +7 -1
- package/dist/selectors/settings.js +13 -1
- package/dist/selectors/versionInfo.js +1 -1
- package/dist/selectors/view.js +1 -1
- package/dist/spawnerMiddleware.js +1 -1
- package/dist/utils/displayModeHelper.js +1 -1
- package/dist/utils/localizationHelper.js +1 -1
- package/dist/utils/mapHelper.js +1 -1
- package/dist/utils/modelValidationHelper.js +1 -1
- package/dist/utils/parseHelper.js +1 -1
- package/dist/utils/propertyHelper.js +2 -2
- package/dist/utils/propertyValidator.js +1 -1
- package/dist/utils/setTranslationWithFallback.js +1 -1
- package/dist/utils/testUtils.js +2 -1
- package/dist/utils/unwrapImmutable.js +1 -1
- package/dist/utils/urlHelper.js +1 -1
- package/package.json +6 -5
- package/src/actions/modules.js +30 -0
- package/src/actions/modules.test.js +50 -1
- package/src/actions/scopes.js +33 -7
- package/src/actions/scopes.test.js +84 -14
- package/src/components/AppFrame/About.js +2 -2
- package/src/components/AppFrame/AppFrame.test.js +9 -0
- package/src/components/AppFrame/MenuItem.js +3 -5
- package/src/components/AppFrame/MenuItem.test.js +2 -24
- package/src/components/AppFrame/Sidebar.js +11 -12
- package/src/components/AppFrame/Sidebar.test.js +18 -0
- package/src/components/ApplicationModuleLoader.js +52 -0
- package/src/components/ApplicationModuleLoader.test.js +149 -0
- package/src/components/Authenticate.js +5 -4
- package/src/components/Authenticate.test.js +23 -4
- package/src/components/MaterialUI/Navigation/ExternalLink.js +25 -0
- package/src/components/MaterialUI/Navigation/ExternalLink.test.js +26 -0
- package/src/components/MaterialUI/muiThemes.js +5 -0
- package/src/components/Modules.js +103 -20
- package/src/components/Modules.test.js +315 -28
- package/src/components/Provision.test.js +34 -0
- package/src/components/Routing/FullPage.js +2 -1
- package/src/components/Routing/FullPage.test.js +23 -0
- package/src/components/Routing/Page.js +2 -2
- package/src/components/Routing/Page.test.js +20 -0
- package/src/components/Routing/Segment.js +1 -1
- package/src/components/Routing/withWaypointing.js +2 -2
- package/src/components/Routing/withWaypointing.test.js +33 -5
- package/src/components/Scope/useScopeConfirmationModalState.js +5 -16
- package/src/components/Scope/useScopeConfirmationModalState.test.js +39 -13
- package/src/components/Scope/useScopeData.js +0 -3
- package/src/components/Scope/useScopeData.test.js +0 -27
- package/src/constants.js +15 -0
- package/src/content/icons/orckestra-icon.svg +5 -0
- package/src/content/iconsSheet.svg +8 -0
- package/src/content/orckestra-logo-white.png +0 -0
- package/src/hocs/withScopeData.test.js +0 -31
- package/src/reducers/modules.js +48 -2
- package/src/reducers/modules.test.js +117 -2
- package/src/reducers/scopes.js +30 -0
- package/src/reducers/scopes.test.js +45 -1
- package/src/reducers/settings.js +26 -2
- package/src/reducers/settings.test.js +74 -6
- package/src/selectors/authentication.js +53 -27
- package/src/selectors/authentication.test.js +600 -12
- package/src/selectors/modules.js +7 -0
- package/src/selectors/modules.test.js +16 -1
- package/src/selectors/scope.js +2 -0
- package/src/selectors/scope.test.js +5 -0
- package/src/selectors/settings.js +6 -0
|
@@ -168,7 +168,7 @@ describe("MenuItem", () => {
|
|
|
168
168
|
</MemoryRouter>,
|
|
169
169
|
));
|
|
170
170
|
|
|
171
|
-
it("shows nothing if
|
|
171
|
+
it("shows nothing if isHidden is true", () => {
|
|
172
172
|
expect(
|
|
173
173
|
<Provider store={store}>
|
|
174
174
|
<MemoryRouter>
|
|
@@ -178,29 +178,7 @@ describe("MenuItem", () => {
|
|
|
178
178
|
icon="cake"
|
|
179
179
|
label="Test"
|
|
180
180
|
alert={{ message: "Test message", type: "warn" }}
|
|
181
|
-
|
|
182
|
-
/>
|
|
183
|
-
</MemoryRouter>
|
|
184
|
-
</Provider>,
|
|
185
|
-
"when mounted",
|
|
186
|
-
"to equal",
|
|
187
|
-
null,
|
|
188
|
-
);
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
it("shows nothing if hide selector returns true", () => {
|
|
192
|
-
const hide = state => true;
|
|
193
|
-
|
|
194
|
-
expect(
|
|
195
|
-
<Provider store={store}>
|
|
196
|
-
<MemoryRouter>
|
|
197
|
-
<MenuItem
|
|
198
|
-
id="test"
|
|
199
|
-
href="/foo/test"
|
|
200
|
-
icon="cake"
|
|
201
|
-
label="Test"
|
|
202
|
-
alert={{ message: "Test message", type: "warn" }}
|
|
203
|
-
hide={hide}
|
|
181
|
+
isHidden={true}
|
|
204
182
|
/>
|
|
205
183
|
</MemoryRouter>
|
|
206
184
|
</Provider>,
|
|
@@ -5,6 +5,7 @@ import { useLocation } from "react-router-dom";
|
|
|
5
5
|
import { getThemeProp } from "../../utils";
|
|
6
6
|
import { selectPrependHrefConfig } from "../../selectors/navigation";
|
|
7
7
|
import MenuItem from "./MenuItem";
|
|
8
|
+
import { getScopeModuleInformationSelector } from "../../selectors/modules";
|
|
8
9
|
|
|
9
10
|
export const Bar = styled.div`
|
|
10
11
|
box-sizing: border-box;
|
|
@@ -40,7 +41,13 @@ const useEnhancement = id => {
|
|
|
40
41
|
};
|
|
41
42
|
};
|
|
42
43
|
|
|
43
|
-
export const EnhancedMenuItem = ({ id, ...props }) =>
|
|
44
|
+
export const EnhancedMenuItem = ({ id, ...props }) => {
|
|
45
|
+
const defaultModule = useSelector(getScopeModuleInformationSelector);
|
|
46
|
+
|
|
47
|
+
const isHidden = !defaultModule.visibleModules.includes(id);
|
|
48
|
+
|
|
49
|
+
return <MenuItem {...props} {...useEnhancement(id)} isHidden={isHidden} />;
|
|
50
|
+
};
|
|
44
51
|
|
|
45
52
|
const LogoSvg = styled.svg`
|
|
46
53
|
flex: 0 0 auto;
|
|
@@ -53,17 +60,9 @@ const LogoSvg = styled.svg`
|
|
|
53
60
|
`;
|
|
54
61
|
|
|
55
62
|
export const Logo = () => (
|
|
56
|
-
<LogoSvg>
|
|
57
|
-
<path
|
|
58
|
-
|
|
59
|
-
d="M10.3,3.8c-1.7,0-3.5,0.7-4.7,1.9L2.8,3c1.9-1.9,4.6-3,7.5-3c0.6,0,1.3,0.1,1.9,0.2
|
|
60
|
-
l-0.9,3.7C11,3.8,10.6,3.8,10.3,3.8L10.3,3.8z M16.1,7.5c-1.4-1.4-3.2-2.1-5.1-2.1c-2,0-3.9,0.8-5.3,2.3c-1.2,1.2-1.9,2.8-2,4.5H0
|
|
61
|
-
c0.1-2.7,1-4.9,3-7c0.2-0.2,0.5-0.5,0.7-0.7l1.8,1.8l0.1-0.1c1.2-1.2,2.9-1.9,4.6-1.9c0.4,0,0.8,0,1.2,0.1l0.2,0l0.6-2.5
|
|
62
|
-
c2.3,0.3,4.3,1.2,6,2.8l0.3,0.3c2,1.9,3.1,4.5,3.3,7.3h-3.6C18.1,10.4,17.3,8.7,16.1,7.5L16.1,7.5z M19.8,3.3
|
|
63
|
-
c2.9,2.8,4.4,6.8,4,10.8c-0.3,2.8-1.5,5.4-3.4,7.6L17,19.1c0.4-0.4,0.7-0.8,1-1.2c0.9-1.2,1.4-2.7,1.6-4.2c0-0.4,0.1-0.7,0-1.1h2.7
|
|
64
|
-
l0-0.2c-0.1-3-1.4-5.7-3.5-7.8l-0.2-0.2L19.8,3.3L19.8,3.3z M16.5,18.9l-0.2,0.2l2.6,2c-2.3,2.6-5.6,4-9.1,3.8l0-4
|
|
65
|
-
c0.2,0,0.4,0,0.6,0h0c2.6,0,5-1.4,6.4-3.6l0.7,0.5C17.2,18.2,16.9,18.6,16.5,18.9L16.5,18.9z"
|
|
66
|
-
/>
|
|
63
|
+
<LogoSvg viewBox="0 0 260 260">
|
|
64
|
+
<path d="M1.11,125.62C1.11,74.94,40.22,39,93.72,39S186,74.94,186,125.62s-38.79,86.66-92.29,86.66S1.11,176.3,1.11,125.62Zm135.47,0c0-29.1-18.46-46.62-42.86-46.62S50.54,96.52,50.54,125.62s18.77,46.62,43.18,46.62S136.58,154.72,136.58,125.62Z" />
|
|
65
|
+
<circle cx="227.6" cy="181.13" r="31.29" />
|
|
67
66
|
</LogoSvg>
|
|
68
67
|
);
|
|
69
68
|
|
|
@@ -24,6 +24,15 @@ describe("Sidebar", () => {
|
|
|
24
24
|
state = Immutable.fromJS({
|
|
25
25
|
navigation: { route: { match: { params: { scope: "Global" } } }, config: { prependHref: "/Global/" } },
|
|
26
26
|
settings: { defaultScope: "myScope" },
|
|
27
|
+
modules: {
|
|
28
|
+
tree: "modulesTree",
|
|
29
|
+
visibleModules: ["a", "module123"],
|
|
30
|
+
lastScopeAndModuleSelection: {
|
|
31
|
+
scope: "Norway",
|
|
32
|
+
moduleName: "Profiles",
|
|
33
|
+
routingPerformed: false,
|
|
34
|
+
},
|
|
35
|
+
},
|
|
27
36
|
});
|
|
28
37
|
store = {
|
|
29
38
|
subscribe: () => {},
|
|
@@ -153,6 +162,15 @@ describe("EnhancedMenuItem", () => {
|
|
|
153
162
|
},
|
|
154
163
|
config: { prependHref: "/Global/" },
|
|
155
164
|
},
|
|
165
|
+
modules: {
|
|
166
|
+
tree: "modulesTree",
|
|
167
|
+
visibleModules: ["route"],
|
|
168
|
+
lastScopeAndModuleSelection: {
|
|
169
|
+
scope: "Norway",
|
|
170
|
+
moduleName: "Profiles",
|
|
171
|
+
routingPerformed: false,
|
|
172
|
+
},
|
|
173
|
+
},
|
|
156
174
|
settings: { defaultScope: "myScope" },
|
|
157
175
|
}),
|
|
158
176
|
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import React, { useEffect, useState } from "react";
|
|
2
|
+
import { useDispatch, useSelector } from "react-redux";
|
|
3
|
+
import { getDefaultScope, getScopes } from "../actions/scopes";
|
|
4
|
+
import {
|
|
5
|
+
defaultScopeSelector,
|
|
6
|
+
getApplicationModulesSelector,
|
|
7
|
+
getLoadedModulesScopeSelector,
|
|
8
|
+
} from "../selectors/settings";
|
|
9
|
+
import { Loader } from "./Authenticate";
|
|
10
|
+
import { overtureModule, scopeTypes } from "../constants";
|
|
11
|
+
import { initializeFirstModuleScope } from "../actions/modules";
|
|
12
|
+
|
|
13
|
+
const ApplicationModuleLoader = ({ children }) => {
|
|
14
|
+
const dispatch = useDispatch();
|
|
15
|
+
|
|
16
|
+
const [scopesLoaded, setScopesLoaded] = useState(false);
|
|
17
|
+
|
|
18
|
+
const loadedModules = useSelector(getLoadedModulesScopeSelector);
|
|
19
|
+
const applicationModules = useSelector(getApplicationModulesSelector);
|
|
20
|
+
const defaultScope = useSelector(defaultScopeSelector);
|
|
21
|
+
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
if (applicationModules.length > 0 && scopesLoaded === false) {
|
|
24
|
+
if (applicationModules.includes(overtureModule.System)) {
|
|
25
|
+
dispatch(initializeFirstModuleScope(scopeTypes.global));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
applicationModules.forEach(x => {
|
|
29
|
+
// For the default scope, the latest that will be returned will be the chosen one
|
|
30
|
+
dispatch(getDefaultScope(x));
|
|
31
|
+
// For scopes, they need to be merged
|
|
32
|
+
dispatch(getScopes(x));
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
setScopesLoaded(true);
|
|
36
|
+
}
|
|
37
|
+
}, [dispatch, applicationModules, scopesLoaded]);
|
|
38
|
+
|
|
39
|
+
const scopeLoadedFromAllModules =
|
|
40
|
+
applicationModules.length > 0 &&
|
|
41
|
+
applicationModules.reduce((prev, current) => prev && loadedModules.includes(current), true);
|
|
42
|
+
|
|
43
|
+
const applicationModuleReady = scopeLoadedFromAllModules && defaultScope != null;
|
|
44
|
+
|
|
45
|
+
if (!applicationModuleReady) {
|
|
46
|
+
return <Loader />;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return React.Children.only(children);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export default ApplicationModuleLoader;
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import Immutable from "immutable";
|
|
3
|
+
import { Provider } from "react-redux";
|
|
4
|
+
import { ThemeProvider } from "styled-components";
|
|
5
|
+
import { Loader } from "./Authenticate";
|
|
6
|
+
import ApplicationModuleLoader from "./ApplicationModuleLoader";
|
|
7
|
+
import { mount } from "enzyme";
|
|
8
|
+
import { getDefaultScope, getScopes } from "../actions/scopes";
|
|
9
|
+
import sinon from "sinon";
|
|
10
|
+
import { initializeFirstModuleScope } from "../actions/modules";
|
|
11
|
+
import { scopeTypes } from "../constants";
|
|
12
|
+
|
|
13
|
+
const TestComp = () => {
|
|
14
|
+
return <div className="test" />;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
jest.mock("../utils/buildUrl", () => {
|
|
18
|
+
const modExport = {};
|
|
19
|
+
modExport.loadConfig = () => Promise.resolve({});
|
|
20
|
+
modExport.buildUrl = (path = [], params = "") => "URL: " + path.join("/") + " " + JSON.stringify(params);
|
|
21
|
+
return modExport;
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe("ApplicationModuleLoader", () => {
|
|
25
|
+
let state, store;
|
|
26
|
+
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
state = Immutable.fromJS({
|
|
29
|
+
requests: {},
|
|
30
|
+
scopes: {
|
|
31
|
+
test1: {
|
|
32
|
+
id: "test1",
|
|
33
|
+
name: { "en-CA": "Test 1", "en-US": "Test 1" },
|
|
34
|
+
children: ["test2"],
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
authentication: {
|
|
38
|
+
name: "foo@bar.com",
|
|
39
|
+
},
|
|
40
|
+
settings: {
|
|
41
|
+
defaultScope: "aDefaultScope",
|
|
42
|
+
loadedModulesScope: ["moduleA", "moduleB"],
|
|
43
|
+
modules: ["moduleA", "moduleB"],
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
store = state => ({
|
|
47
|
+
subscribe: () => {},
|
|
48
|
+
getState: () => state,
|
|
49
|
+
dispatch: sinon.spy().named("dispatch"),
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("shows the component when scopes configuration are all loaded", () =>
|
|
54
|
+
expect(
|
|
55
|
+
<Provider store={store(state)}>
|
|
56
|
+
<ApplicationModuleLoader>
|
|
57
|
+
<TestComp />
|
|
58
|
+
</ApplicationModuleLoader>
|
|
59
|
+
</Provider>,
|
|
60
|
+
"when mounted",
|
|
61
|
+
"to exhaustively satisfy",
|
|
62
|
+
<TestComp />,
|
|
63
|
+
));
|
|
64
|
+
|
|
65
|
+
it("shows a load indicator component when default scope is still unknown", () => {
|
|
66
|
+
state = state.setIn(["settings", "defaultScope"], null);
|
|
67
|
+
return expect(
|
|
68
|
+
<Provider store={store(state)}>
|
|
69
|
+
<ThemeProvider theme={{}}>
|
|
70
|
+
<ApplicationModuleLoader>
|
|
71
|
+
<TestComp />
|
|
72
|
+
</ApplicationModuleLoader>
|
|
73
|
+
</ThemeProvider>
|
|
74
|
+
</Provider>,
|
|
75
|
+
"when mounted",
|
|
76
|
+
"to exhaustively satisfy",
|
|
77
|
+
<ThemeProvider theme={{}}>
|
|
78
|
+
<Loader />
|
|
79
|
+
</ThemeProvider>,
|
|
80
|
+
);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("shows a load indicator component when scopes trees are not loaded for all modules", () => {
|
|
84
|
+
state = state.setIn(["settings", "loadedModulesScope"], Immutable.fromJS(["moduleA"]));
|
|
85
|
+
return expect(
|
|
86
|
+
<Provider store={store(state)}>
|
|
87
|
+
<ThemeProvider theme={{}}>
|
|
88
|
+
<ApplicationModuleLoader>
|
|
89
|
+
<TestComp />
|
|
90
|
+
</ApplicationModuleLoader>
|
|
91
|
+
</ThemeProvider>
|
|
92
|
+
</Provider>,
|
|
93
|
+
"when mounted",
|
|
94
|
+
"to exhaustively satisfy",
|
|
95
|
+
<ThemeProvider theme={{}}>
|
|
96
|
+
<Loader />
|
|
97
|
+
</ThemeProvider>,
|
|
98
|
+
);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("scopes tree and default scope should be loaded when rendering the component", () => {
|
|
102
|
+
const theStore = store(state);
|
|
103
|
+
|
|
104
|
+
const component = (
|
|
105
|
+
<Provider store={theStore}>
|
|
106
|
+
<ThemeProvider theme={{}}>
|
|
107
|
+
<ApplicationModuleLoader>
|
|
108
|
+
<TestComp />
|
|
109
|
+
</ApplicationModuleLoader>
|
|
110
|
+
</ThemeProvider>
|
|
111
|
+
</Provider>
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
mount(component);
|
|
115
|
+
|
|
116
|
+
expect(theStore.dispatch, "to have calls satisfying", [
|
|
117
|
+
{ args: [getDefaultScope("moduleA")] },
|
|
118
|
+
{ args: [getScopes("moduleA")] },
|
|
119
|
+
{ args: [getDefaultScope("moduleB")] },
|
|
120
|
+
{ args: [getScopes("moduleB")] },
|
|
121
|
+
]);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("First module scope should be initialized when System is part application modules", () => {
|
|
125
|
+
state = state.setIn(["settings", "modules"], Immutable.fromJS(["moduleA", "System"]));
|
|
126
|
+
|
|
127
|
+
const theStore = store(state);
|
|
128
|
+
|
|
129
|
+
const component = (
|
|
130
|
+
<Provider store={theStore}>
|
|
131
|
+
<ThemeProvider theme={{}}>
|
|
132
|
+
<ApplicationModuleLoader>
|
|
133
|
+
<TestComp />
|
|
134
|
+
</ApplicationModuleLoader>
|
|
135
|
+
</ThemeProvider>
|
|
136
|
+
</Provider>
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
mount(component);
|
|
140
|
+
|
|
141
|
+
expect(theStore.dispatch, "to have calls satisfying", [
|
|
142
|
+
{ args: [initializeFirstModuleScope(scopeTypes.global)] },
|
|
143
|
+
{ args: [getDefaultScope("moduleA")] },
|
|
144
|
+
{ args: [getScopes("moduleA")] },
|
|
145
|
+
{ args: [getDefaultScope("System")] },
|
|
146
|
+
{ args: [getScopes("System")] },
|
|
147
|
+
]);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
@@ -6,10 +6,10 @@ import { useSelector } from "react-redux";
|
|
|
6
6
|
import { unwrapImmutable } from "../utils";
|
|
7
7
|
import { GET_AUTHENTICATION_PROFILE } from "../actions/authentication";
|
|
8
8
|
import { ERROR, LOGOUT } from "../reducers/request";
|
|
9
|
+
import ApplicationModuleLoader from "./ApplicationModuleLoader";
|
|
9
10
|
|
|
10
11
|
export const useAuthenticationData = () => ({
|
|
11
12
|
loading: useSelector(state => state.getIn(["requests", "actives", GET_AUTHENTICATION_PROFILE])),
|
|
12
|
-
defaultScope: useSelector(state => state.getIn(["settings", "defaultScope"])) || null,
|
|
13
13
|
authedUser: useSelector(state => state.getIn(["authentication", "name"])),
|
|
14
14
|
requestError: unwrapImmutable(useSelector(state => state.getIn(["requests", ERROR]) || null)),
|
|
15
15
|
needLogin: useSelector(state => state.getIn(["requests", LOGOUT])),
|
|
@@ -47,14 +47,15 @@ export const Error = ({ requestError, needLogin }) => {
|
|
|
47
47
|
};
|
|
48
48
|
|
|
49
49
|
const Authenticate = ({ children }) => {
|
|
50
|
-
const { loading,
|
|
51
|
-
|
|
50
|
+
const { loading, authedUser, requestError, needLogin } = useAuthenticationData();
|
|
51
|
+
|
|
52
|
+
if (loading) {
|
|
52
53
|
return <Loader />;
|
|
53
54
|
}
|
|
54
55
|
if (!authedUser) {
|
|
55
56
|
return <Error {...{ requestError, needLogin }} />;
|
|
56
57
|
} else {
|
|
57
|
-
return
|
|
58
|
+
return <ApplicationModuleLoader children={children} />;
|
|
58
59
|
}
|
|
59
60
|
};
|
|
60
61
|
|
|
@@ -7,19 +7,36 @@ import { ERROR, LOGOUT } from "../reducers/request";
|
|
|
7
7
|
import { GET_AUTHENTICATION_PROFILE } from "../actions/authentication";
|
|
8
8
|
|
|
9
9
|
const TestComp = () => {
|
|
10
|
-
return <div className="test"
|
|
10
|
+
return <div className="test" />;
|
|
11
11
|
};
|
|
12
12
|
|
|
13
|
+
jest.mock("../utils/buildUrl", () => {
|
|
14
|
+
const modExport = {};
|
|
15
|
+
modExport.loadConfig = () => Promise.resolve({});
|
|
16
|
+
modExport.buildUrl = () => "URL";
|
|
17
|
+
return modExport;
|
|
18
|
+
});
|
|
19
|
+
|
|
13
20
|
describe("Authenticate", () => {
|
|
14
21
|
let state, store;
|
|
22
|
+
|
|
15
23
|
beforeEach(() => {
|
|
16
24
|
state = Immutable.fromJS({
|
|
17
25
|
requests: {},
|
|
26
|
+
scopes: {
|
|
27
|
+
test1: {
|
|
28
|
+
id: "test1",
|
|
29
|
+
name: { "en-CA": "Test 1", "en-US": "Test 1" },
|
|
30
|
+
children: ["test2"],
|
|
31
|
+
},
|
|
32
|
+
},
|
|
18
33
|
authentication: {
|
|
19
34
|
name: "foo@bar.com",
|
|
20
35
|
},
|
|
21
36
|
settings: {
|
|
22
37
|
defaultScope: "aDefaultScope",
|
|
38
|
+
loadedModulesScope: ["moduleA", "moduleB"],
|
|
39
|
+
modules: ["moduleA", "moduleB"],
|
|
23
40
|
},
|
|
24
41
|
});
|
|
25
42
|
store = state => ({
|
|
@@ -38,7 +55,9 @@ describe("Authenticate", () => {
|
|
|
38
55
|
</Provider>,
|
|
39
56
|
"when mounted",
|
|
40
57
|
"to exhaustively satisfy",
|
|
41
|
-
<
|
|
58
|
+
<Provider store={store(state)}>
|
|
59
|
+
<TestComp />
|
|
60
|
+
</Provider>,
|
|
42
61
|
));
|
|
43
62
|
|
|
44
63
|
it("shows a load indicator component if authentication is ongoing", () => {
|
|
@@ -59,12 +78,12 @@ describe("Authenticate", () => {
|
|
|
59
78
|
);
|
|
60
79
|
});
|
|
61
80
|
|
|
62
|
-
it("shows a load indicator component
|
|
81
|
+
it("shows a load indicator component when application modules not ready", () => {
|
|
63
82
|
state = state.setIn(["settings", "defaultScope"], null);
|
|
64
83
|
return expect(
|
|
65
84
|
<Provider store={store(state)}>
|
|
66
85
|
<ThemeProvider theme={{}}>
|
|
67
|
-
<Authenticate>
|
|
86
|
+
<Authenticate applicationModuleReady={false}>
|
|
68
87
|
<TestComp />
|
|
69
88
|
</Authenticate>
|
|
70
89
|
</ThemeProvider>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import Link from "@material-ui/core/Link";
|
|
3
|
+
import Icon from "../DataDisplay/Icon";
|
|
4
|
+
import { makeStyles } from "@material-ui/core/styles";
|
|
5
|
+
|
|
6
|
+
const useStyles = makeStyles(theme => ({
|
|
7
|
+
icon: {
|
|
8
|
+
paddingLeft: theme.spacing(1),
|
|
9
|
+
width: theme.spacing(1.2),
|
|
10
|
+
color: theme.palette.primary.main,
|
|
11
|
+
},
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
const ExternalLink = ({ id, url, children }) => {
|
|
15
|
+
const classes = useStyles();
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<Link id={id} href={url} rel="noreferrer" target="_blank" underline="always">
|
|
19
|
+
{children}
|
|
20
|
+
<Icon id="open-in-new-tab" className={classes.icon} />
|
|
21
|
+
</Link>
|
|
22
|
+
);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export default ExternalLink;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import Link from "@material-ui/core/Link";
|
|
3
|
+
import Icon from "../DataDisplay/Icon";
|
|
4
|
+
import ExternalLink from "./ExternalLink";
|
|
5
|
+
|
|
6
|
+
describe("ExternalLink", () => {
|
|
7
|
+
it("render ExternalLink without errors", () => {
|
|
8
|
+
const children = <div>a div element</div>;
|
|
9
|
+
|
|
10
|
+
expect(
|
|
11
|
+
<ExternalLink id="an_Id" url="https://www.npmjs.com/package/orc-shared" children={children} />,
|
|
12
|
+
"when mounted",
|
|
13
|
+
"to satisfy",
|
|
14
|
+
<Link
|
|
15
|
+
id="an_Id"
|
|
16
|
+
href="https://www.npmjs.com/package/orc-shared"
|
|
17
|
+
rel="noreferrer"
|
|
18
|
+
target="_blank"
|
|
19
|
+
underline="always"
|
|
20
|
+
>
|
|
21
|
+
{children}
|
|
22
|
+
<Icon id="open-in-new-tab" />
|
|
23
|
+
</Link>,
|
|
24
|
+
);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
@@ -3,49 +3,122 @@ import { useHistory, useLocation } from "react-router-dom";
|
|
|
3
3
|
import { useSelector, useDispatch } from "react-redux";
|
|
4
4
|
import { Route, Switch, Redirect } from "react-router-dom";
|
|
5
5
|
import withErrorBoundary from "../hocs/withErrorBoundary";
|
|
6
|
-
import { getCurrentScope, selectRouteHref } from "../selectors/navigation";
|
|
6
|
+
import { getCurrentScope, getCurrentScopeFromRoute, selectRouteHref } from "../selectors/navigation";
|
|
7
7
|
import { isCurrentScopeAuthorizedSelector } from "../selectors/scope";
|
|
8
8
|
import Navigation from "./Navigation";
|
|
9
9
|
import FullPage from "./Routing/FullPage";
|
|
10
10
|
import { setHrefConfig } from "../actions/navigation";
|
|
11
|
-
import {
|
|
12
|
-
|
|
11
|
+
import {
|
|
12
|
+
setRoutingPerformed,
|
|
13
|
+
setModuleAsVisible,
|
|
14
|
+
setModulesStructure,
|
|
15
|
+
initializeFirstModuleScope,
|
|
16
|
+
} from "../actions/modules";
|
|
17
|
+
import { defaultScopeSelector, getApplicationModulesSelector } from "../selectors/settings";
|
|
18
|
+
import { getScopeModuleInformationSelector } from "../selectors/modules";
|
|
19
|
+
import UrlPattern from "url-pattern";
|
|
20
|
+
import { overtureModule } from "../constants";
|
|
13
21
|
|
|
14
|
-
|
|
15
|
-
const
|
|
22
|
+
const rerouteOnScopeAndModule = (history, currentRoute, scope, module) => {
|
|
23
|
+
const params = {
|
|
24
|
+
scope: scope,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const pattern = new UrlPattern(`/:scope/${module}`);
|
|
28
|
+
const href = pattern.stringify(params);
|
|
29
|
+
|
|
30
|
+
if (currentRoute !== href) {
|
|
31
|
+
history.push(href);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const Module = withErrorBoundary("Module")(({ id, config, path, error, location, match, modulePrependPath }) => {
|
|
16
36
|
const history = useHistory();
|
|
17
|
-
|
|
18
|
-
const
|
|
37
|
+
const scope = useSelector(getCurrentScopeFromRoute);
|
|
38
|
+
const currentRoute = useSelector(selectRouteHref);
|
|
39
|
+
const moduleInfo = useSelector(getScopeModuleInformationSelector);
|
|
40
|
+
|
|
41
|
+
const isVisible =
|
|
42
|
+
moduleInfo.scope != null && moduleInfo.visibleModules.length > 0 && moduleInfo.visibleModules.includes(id);
|
|
19
43
|
|
|
20
44
|
React.useEffect(() => {
|
|
21
|
-
if (
|
|
22
|
-
history.
|
|
45
|
+
if (moduleInfo.scope != null && scope === moduleInfo.scope && moduleInfo.visibleModules.length > 0 && !isVisible) {
|
|
46
|
+
rerouteOnScopeAndModule(history, currentRoute, moduleInfo.scope, moduleInfo.visibleModules[0]);
|
|
23
47
|
}
|
|
24
|
-
|
|
25
|
-
}, [isHidden, currentRoute, match.url]);
|
|
48
|
+
}, [history, scope, isVisible, currentRoute, moduleInfo.scope, moduleInfo.visibleModules]);
|
|
26
49
|
|
|
27
50
|
return (
|
|
28
|
-
<FullPage
|
|
51
|
+
<FullPage
|
|
52
|
+
path={path}
|
|
53
|
+
config={config}
|
|
54
|
+
location={location}
|
|
55
|
+
match={match}
|
|
56
|
+
modulePrependPath={modulePrependPath}
|
|
57
|
+
isVisible={isVisible}
|
|
58
|
+
/>
|
|
29
59
|
);
|
|
30
60
|
});
|
|
31
61
|
|
|
32
62
|
const getHrefFromPath = (path, scope) => path.replace(":scope", scope);
|
|
33
63
|
|
|
64
|
+
const CheckModuleVisibility = ({ id, config, moduleInfo }) => {
|
|
65
|
+
const dispatch = useDispatch();
|
|
66
|
+
const applicationModules = useSelector(getApplicationModulesSelector);
|
|
67
|
+
const scopeFromRoute = useSelector(getCurrentScopeFromRoute);
|
|
68
|
+
|
|
69
|
+
const hideSelector = state => (typeof config.hide === "function" ? config.hide(state) : () => config.hide ?? false);
|
|
70
|
+
|
|
71
|
+
const isHidden = useSelector(hideSelector(moduleInfo.scope ?? scopeFromRoute));
|
|
72
|
+
|
|
73
|
+
const moduleScope = moduleInfo.scope;
|
|
74
|
+
const moduleIsVisible = isHidden === false && !moduleInfo.visibleModules.includes(id);
|
|
75
|
+
|
|
76
|
+
React.useEffect(() => {
|
|
77
|
+
// We need to wait for the ROUTE to be set the first time in the Redux/Store before to set module's visibility
|
|
78
|
+
if (scopeFromRoute !== null || applicationModules.includes(overtureModule.System)) {
|
|
79
|
+
if (moduleScope == null) {
|
|
80
|
+
dispatch(initializeFirstModuleScope(scopeFromRoute));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (moduleIsVisible) {
|
|
84
|
+
dispatch(setModuleAsVisible(id));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}, [moduleScope, moduleIsVisible, dispatch, id, scopeFromRoute, applicationModules]);
|
|
88
|
+
|
|
89
|
+
return <React.Fragment />;
|
|
90
|
+
};
|
|
91
|
+
|
|
34
92
|
export const Modules = ({ modules, pathConfig: { customPath, ...otherConfigs } = {} }) => {
|
|
35
93
|
const dispatch = useDispatch();
|
|
36
|
-
const
|
|
94
|
+
const currentScope = useSelector(getCurrentScope);
|
|
37
95
|
const isAuthorizedScope = useSelector(isCurrentScopeAuthorizedSelector);
|
|
38
96
|
const defaultScope = useSelector(defaultScopeSelector);
|
|
39
97
|
const history = useHistory();
|
|
40
98
|
const location = useLocation();
|
|
99
|
+
const currentRoute = useSelector(selectRouteHref);
|
|
100
|
+
|
|
101
|
+
const moduleInfo = useSelector(getScopeModuleInformationSelector);
|
|
102
|
+
|
|
103
|
+
const firstModuleName = Object.keys(modules)[0];
|
|
104
|
+
|
|
105
|
+
const destinationModule =
|
|
106
|
+
moduleInfo.routingPerformed === false &&
|
|
107
|
+
moduleInfo.scope != null &&
|
|
108
|
+
moduleInfo.visibleModules.length > 0 &&
|
|
109
|
+
moduleInfo.moduleName != null
|
|
110
|
+
? moduleInfo.visibleModules.includes(moduleInfo.moduleName)
|
|
111
|
+
? moduleInfo.moduleName
|
|
112
|
+
: moduleInfo.visibleModules[0]
|
|
113
|
+
: null;
|
|
41
114
|
|
|
42
115
|
const scopePath = "/:scope/";
|
|
43
116
|
const prependPath = customPath || scopePath;
|
|
44
|
-
const prependHref = getHrefFromPath(prependPath,
|
|
117
|
+
const prependHref = getHrefFromPath(prependPath, currentScope);
|
|
45
118
|
|
|
46
119
|
Object.keys(otherConfigs).forEach(key => {
|
|
47
120
|
const moduleConfig = otherConfigs[key];
|
|
48
|
-
moduleConfig.prependHref = getHrefFromPath(moduleConfig.prependPath,
|
|
121
|
+
moduleConfig.prependHref = getHrefFromPath(moduleConfig.prependPath, currentScope);
|
|
49
122
|
});
|
|
50
123
|
|
|
51
124
|
useEffect(() => {
|
|
@@ -53,23 +126,33 @@ export const Modules = ({ modules, pathConfig: { customPath, ...otherConfigs } =
|
|
|
53
126
|
}, [dispatch, prependPath, prependHref, otherConfigs]);
|
|
54
127
|
|
|
55
128
|
const getModuleConfig = name => (otherConfigs && otherConfigs[name]) || { prependPath, prependHref };
|
|
56
|
-
const firstModuleName = Object.keys(modules)[0];
|
|
57
129
|
|
|
58
130
|
useEffect(() => {
|
|
59
131
|
const { pathname, search } = location;
|
|
60
|
-
if (!isAuthorizedScope && pathname.includes(
|
|
61
|
-
history.push(pathname.replace(
|
|
132
|
+
if (!isAuthorizedScope && pathname.includes(currentScope) && defaultScope) {
|
|
133
|
+
history.push(pathname.replace(currentScope, defaultScope) + search);
|
|
62
134
|
}
|
|
63
135
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
64
|
-
}, [isAuthorizedScope, defaultScope, history,
|
|
136
|
+
}, [isAuthorizedScope, defaultScope, history, currentScope]);
|
|
65
137
|
|
|
66
138
|
React.useEffect(() => {
|
|
67
139
|
dispatch(setModulesStructure(modules));
|
|
68
140
|
}, [dispatch, modules]);
|
|
69
141
|
|
|
142
|
+
useEffect(() => {
|
|
143
|
+
if (destinationModule != null) {
|
|
144
|
+
rerouteOnScopeAndModule(history, currentRoute, moduleInfo.scope, destinationModule);
|
|
145
|
+
|
|
146
|
+
dispatch(setRoutingPerformed());
|
|
147
|
+
}
|
|
148
|
+
}, [dispatch, history, moduleInfo.scope, destinationModule, currentRoute]);
|
|
149
|
+
|
|
70
150
|
return (
|
|
71
151
|
<React.Fragment>
|
|
72
152
|
<Navigation modules={modules} />
|
|
153
|
+
{Object.entries(modules).map(([name, module]) => (
|
|
154
|
+
<CheckModuleVisibility key={name} id={name} config={module} moduleInfo={moduleInfo} />
|
|
155
|
+
))}
|
|
73
156
|
<Switch>
|
|
74
157
|
{Object.entries(modules).map(([name, module]) => {
|
|
75
158
|
const moduleConfig = getModuleConfig(name);
|
|
@@ -79,7 +162,7 @@ export const Modules = ({ modules, pathConfig: { customPath, ...otherConfigs } =
|
|
|
79
162
|
key={name}
|
|
80
163
|
path={path}
|
|
81
164
|
render={route => (
|
|
82
|
-
<Module modulePrependPath={moduleConfig.prependPath} config={module} path={path} {...route} />
|
|
165
|
+
<Module id={name} modulePrependPath={moduleConfig.prependPath} config={module} path={path} {...route} />
|
|
83
166
|
)}
|
|
84
167
|
/>
|
|
85
168
|
);
|