@openmrs/esm-implementer-tools-app 3.1.10-pre.97 → 3.1.12-pre.599
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 +1 -9
- package/__mocks__/react-i18next.js +54 -0
- package/{.babelrc → babel.config.json} +2 -1
- package/dist/266.openmrs-esm-implementer-tools-app.js +3 -0
- package/dist/266.openmrs-esm-implementer-tools-app.js.LICENSE.txt +8 -0
- package/dist/266.openmrs-esm-implementer-tools-app.js.map +1 -0
- package/dist/319.openmrs-esm-implementer-tools-app.js +1 -0
- package/dist/472.openmrs-esm-implementer-tools-app.js +2 -0
- package/dist/472.openmrs-esm-implementer-tools-app.js.map +1 -0
- package/dist/574.openmrs-esm-implementer-tools-app.js +1 -0
- package/dist/728.openmrs-esm-implementer-tools-app.js +2 -0
- package/dist/728.openmrs-esm-implementer-tools-app.js.map +1 -0
- package/dist/965.openmrs-esm-implementer-tools-app.js +3 -0
- package/dist/965.openmrs-esm-implementer-tools-app.js.LICENSE.txt +5 -0
- package/dist/965.openmrs-esm-implementer-tools-app.js.map +1 -0
- package/dist/openmrs-esm-implementer-tools-app.js +1 -1
- package/dist/openmrs-esm-implementer-tools-app.js.buildmanifest.json +170 -0
- package/dist/openmrs-esm-implementer-tools-app.js.map +1 -1
- package/jest.config.js +1 -2
- package/package.json +13 -20
- package/src/backend-dependencies/backend-dependencies.component.tsx +97 -0
- package/src/backend-dependencies/openmrs-backend-dependencies.ts +113 -93
- package/src/backend-dependencies/useBackendDependencies.ts +16 -0
- package/src/config-edit-button/config-edit-button.component.tsx +3 -1
- package/src/configuration/config-tree-for-module.component.tsx +1 -1
- package/src/configuration/config-tree.component.tsx +2 -9
- package/src/configuration/configuration.component.tsx +101 -31
- package/src/configuration/configuration.test.tsx +6 -6
- package/src/configuration/description.component.tsx +3 -1
- package/src/configuration/display-value.tsx +4 -4
- package/src/configuration/editable-value.component.tsx +5 -2
- package/src/configuration/extension-slots-config-tree.tsx +10 -14
- package/src/configuration/layout/subtree.component.tsx +1 -1
- package/src/configuration/layout/tree-container.component.tsx +1 -1
- package/src/configuration/value-editor.tsx +1 -1
- package/src/configuration/value-editors/array-editor.tsx +3 -3
- package/src/configuration/value-editors/extension-slot-add.tsx +1 -1
- package/src/configuration/value-editors/extension-slot-order.tsx +0 -4
- package/src/configuration/value-editors/extension-slot-remove.tsx +19 -7
- package/src/configuration/value-editors/object-editor.tsx +3 -3
- package/src/configuration/value-editors/value-editor-field.tsx +1 -3
- package/src/implementer-tools.button.tsx +33 -0
- package/src/implementer-tools.component.tsx +45 -81
- package/src/implementer-tools.styles.css +5 -9
- package/src/index.ts +35 -9
- package/src/popup/popup.component.tsx +11 -27
- package/src/popup/popup.styles.css +1 -1
- package/src/store.ts +27 -32
- package/src/ui-editor/extension-overlay.component.tsx +1 -1
- package/src/ui-editor/styles.css +7 -0
- package/src/ui-editor/ui-editor.tsx +49 -21
- package/translations/en.json +12 -0
- package/translations/es.json +13 -0
- package/tsconfig.json +1 -0
- package/webpack.config.js +1 -66
- package/__mocks__/openmrs-esm-framework.mock.tsx +0 -142
- package/dist/1.openmrs-esm-implementer-tools-app.js +0 -15
- package/dist/1.openmrs-esm-implementer-tools-app.js.map +0 -1
- package/dist/2.openmrs-esm-implementer-tools-app.js +0 -2
- package/dist/2.openmrs-esm-implementer-tools-app.js.map +0 -1
- package/src/backend-dependencies/backend-dependecies.component.tsx +0 -106
- package/src/backend-dependencies/backend-dependencies-style.css +0 -4
- package/src/backend-dependencies/helpers/backend-dependecies-helper.test.ts +0 -127
- package/src/backend-dependencies/helpers/backend-dependecies-helper.ts +0 -70
- package/src/set-public-path.ts +0 -3
|
@@ -1,64 +1,81 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { openmrsFetch } from "@openmrs/esm-framework";
|
|
1
|
+
import { isVersionSatisfied, openmrsFetch } from "@openmrs/esm-framework";
|
|
3
2
|
import difference from "lodash-es/difference";
|
|
4
3
|
|
|
5
|
-
|
|
6
|
-
const modulesWithMissingBackendModules: MissingBackendModules[] = [];
|
|
7
|
-
const modulesWithWrongBackendModulesVersion: MissingBackendModules[] = [];
|
|
4
|
+
export type ResolvedBackendModuleType = "missing" | "version-mismatch" | "okay";
|
|
8
5
|
|
|
9
|
-
|
|
6
|
+
export interface ResolvedBackendModule {
|
|
7
|
+
name: string;
|
|
8
|
+
requiredVersion: string;
|
|
9
|
+
installedVersion?: string;
|
|
10
|
+
type: ResolvedBackendModuleType;
|
|
11
|
+
}
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
installedBackendModules.push(...response.data.results);
|
|
15
|
-
} catch (err) {
|
|
16
|
-
setTimeout(() => {
|
|
17
|
-
throw err;
|
|
18
|
-
});
|
|
19
|
-
}
|
|
13
|
+
export interface FrontendModule {
|
|
14
|
+
name: string;
|
|
15
|
+
dependencies: Array<ResolvedBackendModule>;
|
|
20
16
|
}
|
|
21
17
|
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
interface Module {
|
|
19
|
+
moduleName: string;
|
|
20
|
+
backendDependencies: Record<string, string>;
|
|
21
|
+
}
|
|
24
22
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
moduleName: module[0],
|
|
30
|
-
}));
|
|
23
|
+
interface BackendModule {
|
|
24
|
+
uuid: string;
|
|
25
|
+
version: string;
|
|
26
|
+
}
|
|
31
27
|
|
|
32
|
-
|
|
33
|
-
modules.forEach((module) => checkIfModulesAreInstalled(module));
|
|
28
|
+
let cachedFrontendModules: Array<FrontendModule>;
|
|
34
29
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
30
|
+
async function initInstalledBackendModules(): Promise<Array<BackendModule>> {
|
|
31
|
+
try {
|
|
32
|
+
const response = await fetchInstalledBackendModules();
|
|
33
|
+
return response.data.results;
|
|
34
|
+
} catch (err) {
|
|
35
|
+
console.error(err);
|
|
36
|
+
}
|
|
39
37
|
|
|
40
|
-
return
|
|
38
|
+
return [];
|
|
41
39
|
}
|
|
42
40
|
|
|
43
|
-
function checkIfModulesAreInstalled(
|
|
41
|
+
function checkIfModulesAreInstalled(
|
|
42
|
+
module: Module,
|
|
43
|
+
installedBackendModules: Array<BackendModule>
|
|
44
|
+
): FrontendModule {
|
|
45
|
+
const dependencies: Array<ResolvedBackendModule> = [];
|
|
46
|
+
|
|
44
47
|
const missingBackendModule = getMissingBackendModules(
|
|
45
|
-
module.backendDependencies
|
|
48
|
+
module.backendDependencies,
|
|
49
|
+
installedBackendModules
|
|
46
50
|
);
|
|
51
|
+
|
|
47
52
|
const installedAndRequiredModules = getInstalledAndRequiredBackendModules(
|
|
48
|
-
module.backendDependencies
|
|
53
|
+
module.backendDependencies,
|
|
54
|
+
installedBackendModules
|
|
49
55
|
);
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
56
|
+
|
|
57
|
+
dependencies.push(
|
|
58
|
+
...missingBackendModule.map((m) => ({
|
|
59
|
+
name: m.uuid,
|
|
60
|
+
requiredVersion: m.version,
|
|
61
|
+
type: "missing" as ResolvedBackendModuleType,
|
|
62
|
+
})),
|
|
63
|
+
...installedAndRequiredModules.map((m) => {
|
|
64
|
+
const requiredVersion = m.version;
|
|
65
|
+
const installedVersion = getInstalledVersion(m, installedBackendModules);
|
|
66
|
+
return {
|
|
67
|
+
name: m.uuid,
|
|
68
|
+
requiredVersion,
|
|
69
|
+
installedVersion,
|
|
70
|
+
type: getResolvedModuleType(requiredVersion, installedVersion),
|
|
71
|
+
};
|
|
72
|
+
})
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
name: module.moduleName,
|
|
77
|
+
dependencies,
|
|
78
|
+
};
|
|
62
79
|
}
|
|
63
80
|
|
|
64
81
|
function fetchInstalledBackendModules() {
|
|
@@ -68,27 +85,34 @@ function fetchInstalledBackendModules() {
|
|
|
68
85
|
}
|
|
69
86
|
|
|
70
87
|
function getMissingBackendModules(
|
|
71
|
-
requiredBackendModules: Record<string, string
|
|
72
|
-
|
|
88
|
+
requiredBackendModules: Record<string, string>,
|
|
89
|
+
installedBackendModules: Array<BackendModule>
|
|
90
|
+
): Array<BackendModule> {
|
|
73
91
|
const requiredBackendModulesUuids = Object.keys(requiredBackendModules);
|
|
74
92
|
const installedBackendModuleUuids = installedBackendModules.map(
|
|
75
93
|
(res) => res.uuid
|
|
76
94
|
);
|
|
95
|
+
|
|
77
96
|
const missingModules = difference(
|
|
78
97
|
requiredBackendModulesUuids,
|
|
79
98
|
installedBackendModuleUuids
|
|
80
99
|
);
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
100
|
+
|
|
101
|
+
return missingModules.map((key) => ({
|
|
102
|
+
uuid: key,
|
|
103
|
+
version: requiredBackendModules[key],
|
|
104
|
+
}));
|
|
84
105
|
}
|
|
85
106
|
|
|
86
107
|
function getInstalledAndRequiredBackendModules(
|
|
87
|
-
requiredBackendModules: Record<string, string
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
108
|
+
requiredBackendModules: Record<string, string>,
|
|
109
|
+
installedBackendModules: Array<BackendModule>
|
|
110
|
+
): Array<BackendModule> {
|
|
111
|
+
const requiredModules = Object.keys(requiredBackendModules).map((key) => ({
|
|
112
|
+
uuid: key,
|
|
113
|
+
version: requiredBackendModules[key],
|
|
114
|
+
}));
|
|
115
|
+
|
|
92
116
|
return requiredModules.filter((requiredModule) => {
|
|
93
117
|
return installedBackendModules.find((installedModule) => {
|
|
94
118
|
return requiredModule.uuid === installedModule.uuid;
|
|
@@ -96,51 +120,47 @@ function getInstalledAndRequiredBackendModules(
|
|
|
96
120
|
});
|
|
97
121
|
}
|
|
98
122
|
|
|
99
|
-
function
|
|
100
|
-
|
|
123
|
+
function getInstalledVersion(
|
|
124
|
+
installedAndRequiredBackendModule: BackendModule,
|
|
125
|
+
installedBackendModules: Array<BackendModule>
|
|
101
126
|
) {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const requiredVersion = installedAndRequiredBackendModules[uuid].version;
|
|
106
|
-
const moduleName = installedAndRequiredBackendModules[uuid].uuid;
|
|
107
|
-
|
|
108
|
-
if (!isVersionInstalled(requiredVersion, installedVersion)) {
|
|
109
|
-
misMatchedBackendModules.push({
|
|
110
|
-
uuid: moduleName,
|
|
111
|
-
requiredVersion: requiredVersion,
|
|
112
|
-
version: installedVersion,
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
return misMatchedBackendModules;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function isVersionInstalled(requiredVersion: string, installedVersion: string) {
|
|
120
|
-
return semver.eq(
|
|
121
|
-
// @ts-ignore
|
|
122
|
-
semver.coerce(requiredVersion),
|
|
123
|
-
semver.coerce(installedVersion)
|
|
127
|
+
const moduleName = installedAndRequiredBackendModule.uuid;
|
|
128
|
+
return (
|
|
129
|
+
installedBackendModules.find((mod) => mod.uuid == moduleName)?.version ?? ""
|
|
124
130
|
);
|
|
125
131
|
}
|
|
126
132
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
133
|
+
function getResolvedModuleType(
|
|
134
|
+
requiredVersion: string,
|
|
135
|
+
installedVersion: string
|
|
136
|
+
): ResolvedBackendModuleType {
|
|
137
|
+
if (!isVersionSatisfied(requiredVersion, installedVersion)) {
|
|
138
|
+
return "version-mismatch";
|
|
139
|
+
}
|
|
131
140
|
|
|
132
|
-
|
|
133
|
-
uuid: string;
|
|
134
|
-
version: string;
|
|
135
|
-
requiredVersion?: string;
|
|
141
|
+
return "okay";
|
|
136
142
|
}
|
|
137
143
|
|
|
138
|
-
export
|
|
139
|
-
|
|
140
|
-
|
|
144
|
+
export async function checkModules(): Promise<Array<FrontendModule>> {
|
|
145
|
+
if (!cachedFrontendModules) {
|
|
146
|
+
const modules = (window.installedModules ?? [])
|
|
147
|
+
.filter((module) => module[1].backendDependencies)
|
|
148
|
+
.map((module) => ({
|
|
149
|
+
backendDependencies: module[1].backendDependencies,
|
|
150
|
+
moduleName: module[0],
|
|
151
|
+
}));
|
|
152
|
+
|
|
153
|
+
const installedBackendModules = await initInstalledBackendModules();
|
|
154
|
+
cachedFrontendModules = modules.map((m) =>
|
|
155
|
+
checkIfModulesAreInstalled(m, installedBackendModules)
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return cachedFrontendModules;
|
|
141
160
|
}
|
|
142
161
|
|
|
143
|
-
export
|
|
144
|
-
|
|
145
|
-
|
|
162
|
+
export function hasInvalidDependencies(frontendModules: Array<FrontendModule>) {
|
|
163
|
+
return frontendModules.some((m) =>
|
|
164
|
+
m.dependencies.some((n) => n.type !== "okay")
|
|
165
|
+
);
|
|
146
166
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import { checkModules, FrontendModule } from "./openmrs-backend-dependencies";
|
|
3
|
+
|
|
4
|
+
export function useBackendDependencies() {
|
|
5
|
+
const [
|
|
6
|
+
modulesWithMissingBackendModules,
|
|
7
|
+
setModulesWithMissingBackendModules,
|
|
8
|
+
] = useState<Array<FrontendModule>>([]);
|
|
9
|
+
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
// loading missing modules
|
|
12
|
+
checkModules().then(setModulesWithMissingBackendModules);
|
|
13
|
+
}, []);
|
|
14
|
+
|
|
15
|
+
return modulesWithMissingBackendModules;
|
|
16
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { implementerToolsStore } from "../store";
|
|
3
|
+
import { useTranslation } from "react-i18next";
|
|
3
4
|
|
|
4
5
|
interface ConfigEditButtonProps {
|
|
5
6
|
configPath: string[];
|
|
@@ -8,13 +9,14 @@ interface ConfigEditButtonProps {
|
|
|
8
9
|
export default function ConfigEditButton({
|
|
9
10
|
configPath,
|
|
10
11
|
}: ConfigEditButtonProps) {
|
|
12
|
+
const { t } = useTranslation();
|
|
11
13
|
return (
|
|
12
14
|
<button
|
|
13
15
|
onClick={() => {
|
|
14
16
|
implementerToolsStore.setState({ configPathBeingEdited: configPath });
|
|
15
17
|
}}
|
|
16
18
|
>
|
|
17
|
-
Edit
|
|
19
|
+
{t("edit", "Edit")}
|
|
18
20
|
</button>
|
|
19
21
|
);
|
|
20
22
|
}
|
|
@@ -1,19 +1,13 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import styles from "./configuration.styles.css";
|
|
3
|
-
import Accordion,
|
|
4
|
-
AccordionItem,
|
|
5
|
-
} from "carbon-components-react/es/components/Accordion";
|
|
3
|
+
import { Accordion, AccordionItem } from "carbon-components-react";
|
|
6
4
|
import { ConfigTreeForModule } from "./config-tree-for-module.component";
|
|
7
|
-
import { useStore } from "@openmrs/esm-framework";
|
|
8
|
-
import { implementerToolsStore } from "../store";
|
|
9
5
|
|
|
10
6
|
export interface ConfigTreeProps {
|
|
11
7
|
config: Record<string, any>;
|
|
12
8
|
}
|
|
13
9
|
|
|
14
10
|
export function ConfigTree({ config }: ConfigTreeProps) {
|
|
15
|
-
const { extensionIdBySlotByModule } = useStore(implementerToolsStore);
|
|
16
|
-
|
|
17
11
|
return (
|
|
18
12
|
<div>
|
|
19
13
|
<Accordion align="start">
|
|
@@ -22,8 +16,7 @@ export function ConfigTree({ config }: ConfigTreeProps) {
|
|
|
22
16
|
.sort()
|
|
23
17
|
.map((moduleName) => {
|
|
24
18
|
const moduleConfig = config[moduleName];
|
|
25
|
-
return Object.keys(moduleConfig).length
|
|
26
|
-
extensionIdBySlotByModule[moduleName] ? (
|
|
19
|
+
return Object.keys(moduleConfig).length ? (
|
|
27
20
|
<AccordionItem
|
|
28
21
|
title={<h6 className={styles.moduleName}>{moduleName}</h6>}
|
|
29
22
|
className={styles.fullWidthAccordion}
|
|
@@ -1,26 +1,41 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import TrashCan16 from "@carbon/icons-react/es/trash-can/16";
|
|
3
|
-
import Download16 from "@carbon/icons-react/es/download/16";
|
|
1
|
+
import React, { useMemo, useState } from "react";
|
|
4
2
|
import ChevronDown16 from "@carbon/icons-react/es/chevron--down/16";
|
|
5
3
|
import ChevronUp16 from "@carbon/icons-react/es/chevron--up/16";
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import styles from "./configuration.styles.css";
|
|
4
|
+
import Download16 from "@carbon/icons-react/es/download/16";
|
|
5
|
+
import TrashCan16 from "@carbon/icons-react/es/trash-can/16";
|
|
9
6
|
import {
|
|
7
|
+
Config,
|
|
10
8
|
ConfigInternalStore,
|
|
11
9
|
configInternalStore,
|
|
12
10
|
implementerToolsConfigStore,
|
|
13
11
|
temporaryConfigStore,
|
|
12
|
+
useExtensionStore,
|
|
14
13
|
useStore,
|
|
15
14
|
} from "@openmrs/esm-framework";
|
|
16
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
Button,
|
|
17
|
+
Column,
|
|
18
|
+
Grid,
|
|
19
|
+
Row,
|
|
20
|
+
TextInput,
|
|
21
|
+
Toggle,
|
|
22
|
+
} from "carbon-components-react";
|
|
23
|
+
import { useTranslation } from "react-i18next";
|
|
24
|
+
import { implementerToolsStore, ImplementerToolsStore } from "../store";
|
|
17
25
|
import { ConfigTree } from "./config-tree.component";
|
|
18
26
|
import { Description } from "./description.component";
|
|
19
|
-
import
|
|
27
|
+
import cloneDeep from "lodash-es/cloneDeep";
|
|
28
|
+
import isEmpty from "lodash-es/isEmpty";
|
|
29
|
+
import styles from "./configuration.styles.css";
|
|
20
30
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
31
|
+
function isLeaf(configNode: Config) {
|
|
32
|
+
return (
|
|
33
|
+
configNode.hasOwnProperty("_default") ||
|
|
34
|
+
configNode["_type"] ||
|
|
35
|
+
configNode.hasOwnProperty("_value") ||
|
|
36
|
+
configNode.hasOwnProperty("_source")
|
|
37
|
+
);
|
|
38
|
+
}
|
|
24
39
|
|
|
25
40
|
const actions = {
|
|
26
41
|
toggleIsUIEditorEnabled({ isUIEditorEnabled }: ImplementerToolsStore) {
|
|
@@ -37,7 +52,29 @@ const configActions = {
|
|
|
37
52
|
},
|
|
38
53
|
};
|
|
39
54
|
|
|
40
|
-
|
|
55
|
+
interface OpenOrCloseButtonProps {
|
|
56
|
+
isConfigToolbarOpen: boolean;
|
|
57
|
+
toggleIsToolbarOpen(): void;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const OpenOrCloseButton: React.FC<OpenOrCloseButtonProps> = ({
|
|
61
|
+
isConfigToolbarOpen,
|
|
62
|
+
toggleIsToolbarOpen,
|
|
63
|
+
}) => (
|
|
64
|
+
<Button
|
|
65
|
+
hasIconOnly
|
|
66
|
+
renderIcon={isConfigToolbarOpen ? ChevronUp16 : ChevronDown16}
|
|
67
|
+
onClick={toggleIsToolbarOpen}
|
|
68
|
+
kind="ghost"
|
|
69
|
+
size="small"
|
|
70
|
+
iconDescription={`${isConfigToolbarOpen ? "Hide" : "Show"} toolbar`}
|
|
71
|
+
/>
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
export interface ConfigurationProps {}
|
|
75
|
+
|
|
76
|
+
export const Configuration: React.FC<ConfigurationProps> = () => {
|
|
77
|
+
const { t } = useTranslation();
|
|
41
78
|
const {
|
|
42
79
|
isUIEditorEnabled,
|
|
43
80
|
toggleIsUIEditorEnabled,
|
|
@@ -49,7 +86,9 @@ export function Configuration({ setHasAlert }: ConfigurationProps) {
|
|
|
49
86
|
configActions
|
|
50
87
|
);
|
|
51
88
|
const { config } = useStore(implementerToolsConfigStore);
|
|
89
|
+
const extensionStore = useExtensionStore();
|
|
52
90
|
const tempConfigStore = useStore(temporaryConfigStore);
|
|
91
|
+
const [filterText, setFilterText] = useState("");
|
|
53
92
|
const tempConfig = tempConfigStore.config;
|
|
54
93
|
const tempConfigObjUrl = new Blob(
|
|
55
94
|
[JSON.stringify(tempConfig, undefined, 2)],
|
|
@@ -58,16 +97,60 @@ export function Configuration({ setHasAlert }: ConfigurationProps) {
|
|
|
58
97
|
}
|
|
59
98
|
);
|
|
60
99
|
|
|
100
|
+
const combinedConfig = useMemo(() => {
|
|
101
|
+
const result = cloneDeep(config);
|
|
102
|
+
for (let slot of Object.values(extensionStore.slots)) {
|
|
103
|
+
for (let moduleName of Object.keys(slot.instances)) {
|
|
104
|
+
if (!result[moduleName].extensions) {
|
|
105
|
+
result[moduleName].extensions = {};
|
|
106
|
+
}
|
|
107
|
+
if (!result[moduleName].extensions[slot.name]) {
|
|
108
|
+
result[moduleName].extensions[slot.name] = {};
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return result;
|
|
113
|
+
}, [config, extensionStore]);
|
|
114
|
+
|
|
115
|
+
const filteredConfig = useMemo(() => {
|
|
116
|
+
function getRelatedBranches(inputTree: Config, filterText: string) {
|
|
117
|
+
const result = {};
|
|
118
|
+
for (let k of Object.keys(inputTree)) {
|
|
119
|
+
if (k.includes(filterText)) {
|
|
120
|
+
result[k] = cloneDeep(inputTree[k]);
|
|
121
|
+
} else {
|
|
122
|
+
if (!isLeaf(inputTree[k])) {
|
|
123
|
+
const kSubtreeResult = getRelatedBranches(inputTree[k], filterText);
|
|
124
|
+
if (!isEmpty(kSubtreeResult)) {
|
|
125
|
+
result[k] = kSubtreeResult;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return result;
|
|
131
|
+
}
|
|
132
|
+
return filterText
|
|
133
|
+
? getRelatedBranches(combinedConfig, filterText)
|
|
134
|
+
: combinedConfig;
|
|
135
|
+
}, [filterText, combinedConfig]);
|
|
136
|
+
|
|
61
137
|
return (
|
|
62
138
|
<>
|
|
63
139
|
<div className={styles.tools}>
|
|
64
140
|
{isConfigToolbarOpen ? (
|
|
65
141
|
<Grid style={{ margin: "0.25rem", padding: "0.5em 1.5em" }}>
|
|
66
142
|
<Row>
|
|
143
|
+
<Column sm={1} md={2}>
|
|
144
|
+
<TextInput
|
|
145
|
+
id="extensionSearch"
|
|
146
|
+
labelText="Search configuration"
|
|
147
|
+
onChange={(e) => setFilterText(e.target.value)}
|
|
148
|
+
/>
|
|
149
|
+
</Column>
|
|
67
150
|
<Column sm={1} md={1}>
|
|
68
151
|
<Toggle
|
|
69
152
|
id="devConfigSwitch"
|
|
70
|
-
labelText="Dev Config"
|
|
153
|
+
labelText={t("devConfig", "Dev Config")}
|
|
71
154
|
onToggle={toggleDevDefaults}
|
|
72
155
|
toggled={devDefaultsAreOn}
|
|
73
156
|
/>
|
|
@@ -75,7 +158,7 @@ export function Configuration({ setHasAlert }: ConfigurationProps) {
|
|
|
75
158
|
<Column sm={1} md={1} className={styles.actionButton}>
|
|
76
159
|
<Toggle
|
|
77
160
|
id="uiEditorSwitch"
|
|
78
|
-
labelText="UI Editor"
|
|
161
|
+
labelText={t("uiEditor", "UI Editor")}
|
|
79
162
|
toggled={isUIEditorEnabled}
|
|
80
163
|
onToggle={toggleIsUIEditorEnabled}
|
|
81
164
|
/>
|
|
@@ -89,7 +172,7 @@ export function Configuration({ setHasAlert }: ConfigurationProps) {
|
|
|
89
172
|
temporaryConfigStore.setState({ config: {} });
|
|
90
173
|
}}
|
|
91
174
|
>
|
|
92
|
-
Clear Temporary Config
|
|
175
|
+
{t("clearTemporaryConfig", "Clear Temporary Config")}
|
|
93
176
|
</Button>
|
|
94
177
|
</Column>
|
|
95
178
|
<Column sm={1} md={2} className={styles.actionButton}>
|
|
@@ -103,7 +186,7 @@ export function Configuration({ setHasAlert }: ConfigurationProps) {
|
|
|
103
186
|
download="temporary_config.json"
|
|
104
187
|
href={window.URL.createObjectURL(tempConfigObjUrl)}
|
|
105
188
|
>
|
|
106
|
-
Download Temporary Config
|
|
189
|
+
{t("downloadTemporaryConfig", "Download Temporary Config")}
|
|
107
190
|
</a>
|
|
108
191
|
</Button>
|
|
109
192
|
</Column>
|
|
@@ -127,7 +210,7 @@ export function Configuration({ setHasAlert }: ConfigurationProps) {
|
|
|
127
210
|
}}
|
|
128
211
|
>
|
|
129
212
|
<div className={styles.configTreePane}>
|
|
130
|
-
<ConfigTree config={
|
|
213
|
+
<ConfigTree config={filteredConfig} />
|
|
131
214
|
</div>
|
|
132
215
|
<div className={styles.descriptionPane}>
|
|
133
216
|
<Description />
|
|
@@ -135,17 +218,4 @@ export function Configuration({ setHasAlert }: ConfigurationProps) {
|
|
|
135
218
|
</div>
|
|
136
219
|
</>
|
|
137
220
|
);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function OpenOrCloseButton({ isConfigToolbarOpen, toggleIsToolbarOpen }) {
|
|
141
|
-
return (
|
|
142
|
-
<Button
|
|
143
|
-
hasIconOnly
|
|
144
|
-
renderIcon={isConfigToolbarOpen ? ChevronUp16 : ChevronDown16}
|
|
145
|
-
onClick={toggleIsToolbarOpen}
|
|
146
|
-
kind="ghost"
|
|
147
|
-
size="small"
|
|
148
|
-
iconDescription={`${isConfigToolbarOpen ? "Hide" : "Show"} toolbar`}
|
|
149
|
-
></Button>
|
|
150
|
-
);
|
|
151
|
-
}
|
|
221
|
+
};
|
|
@@ -99,7 +99,7 @@ describe(`<Configuration />`, () => {
|
|
|
99
99
|
});
|
|
100
100
|
|
|
101
101
|
function renderConfiguration() {
|
|
102
|
-
render(<Configuration
|
|
102
|
+
render(<Configuration />);
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
it(`renders without dying`, async () => {
|
|
@@ -127,7 +127,7 @@ describe(`<Configuration />`, () => {
|
|
|
127
127
|
fireEvent.click(valueButton);
|
|
128
128
|
const editor = await row.findByRole("checkbox");
|
|
129
129
|
fireEvent.click(editor);
|
|
130
|
-
fireEvent.click(row.getByText("Save"));
|
|
130
|
+
fireEvent.click(row.getByText("Save").parentElement as any);
|
|
131
131
|
// The mocked temporaryConfigStore.getState seems to be producing something
|
|
132
132
|
// that doesn't work right, causing the `set` call and consequently this
|
|
133
133
|
// `setState` call not to work either.
|
|
@@ -167,7 +167,7 @@ describe(`<Configuration />`, () => {
|
|
|
167
167
|
expect(mockPerformConceptSearch).toHaveBeenCalledWith("fed");
|
|
168
168
|
const targetConcept = await row.findByText("Fedora");
|
|
169
169
|
userEvent.click(targetConcept);
|
|
170
|
-
userEvent.click(row.getByText("Save"));
|
|
170
|
+
userEvent.click(row.getByText("Save").parentElement as any);
|
|
171
171
|
expect(temporaryConfigStore.setState).toHaveBeenCalledWith({
|
|
172
172
|
config: {
|
|
173
173
|
"@openmrs/mario": { hatUuid: "61523693-72e2-456d-8c64-8c5293febeb6" },
|
|
@@ -196,7 +196,7 @@ describe(`<Configuration />`, () => {
|
|
|
196
196
|
expect(editor).toHaveAttribute("type", "number");
|
|
197
197
|
userEvent.clear(editor);
|
|
198
198
|
userEvent.type(editor, "11");
|
|
199
|
-
userEvent.click(row.getByText("Save"));
|
|
199
|
+
userEvent.click(row.getByText("Save").parentElement as any);
|
|
200
200
|
expect(temporaryConfigStore.setState).toHaveBeenCalledWith({
|
|
201
201
|
config: { "@openmrs/mario": { numberFingers: 11 } },
|
|
202
202
|
});
|
|
@@ -221,7 +221,7 @@ describe(`<Configuration />`, () => {
|
|
|
221
221
|
const editor = await row.findByRole("textbox");
|
|
222
222
|
userEvent.clear(editor);
|
|
223
223
|
userEvent.type(editor, "Bowser");
|
|
224
|
-
userEvent.click(row.getByText("Save"));
|
|
224
|
+
userEvent.click(row.getByText("Save").parentElement as any);
|
|
225
225
|
expect(temporaryConfigStore.setState).toHaveBeenCalledWith({
|
|
226
226
|
config: { "@openmrs/mario": { nemesisName: "Bowser" } },
|
|
227
227
|
});
|
|
@@ -247,7 +247,7 @@ describe(`<Configuration />`, () => {
|
|
|
247
247
|
userEvent.clear(editor);
|
|
248
248
|
const newUuid = "34f03796-f0e2-4f64-9e9a-28fb49a94baf";
|
|
249
249
|
userEvent.type(editor, newUuid);
|
|
250
|
-
userEvent.click(row.getByText("Save"));
|
|
250
|
+
userEvent.click(row.getByText("Save").parentElement as any);
|
|
251
251
|
expect(temporaryConfigStore.setState).toHaveBeenCalledWith({
|
|
252
252
|
config: { "@openmrs/mario": { mustacheUuid: newUuid } },
|
|
253
253
|
});
|
|
@@ -25,7 +25,9 @@ export function Description() {
|
|
|
25
25
|
{activeItemDescription.value ? <p>Value:</p> : null}
|
|
26
26
|
<p className={styles.value}>
|
|
27
27
|
{Array.isArray(activeItemDescription.value)
|
|
28
|
-
? activeItemDescription.value.map((v) =>
|
|
28
|
+
? activeItemDescription.value.map((v, i) => (
|
|
29
|
+
<p key={`${v}-${i}`}>{v}</p>
|
|
30
|
+
))
|
|
29
31
|
: activeItemDescription.value}
|
|
30
32
|
</p>
|
|
31
33
|
</>
|
|
@@ -10,15 +10,15 @@ export function DisplayValue({ value }: DisplayValueProps) {
|
|
|
10
10
|
<>
|
|
11
11
|
{Array.isArray(value)
|
|
12
12
|
? typeof value[0] === "object"
|
|
13
|
-
? value.map((v) => (
|
|
14
|
-
<div style={{ marginBottom: "1em" }}>
|
|
13
|
+
? value.map((v, i) => (
|
|
14
|
+
<div key={`${v}-${i}`} style={{ marginBottom: "1em" }}>
|
|
15
15
|
<DisplayValue value={v} />
|
|
16
16
|
</div>
|
|
17
17
|
))
|
|
18
18
|
: value.join(", ")
|
|
19
19
|
: typeof value === "object" && value !== null
|
|
20
|
-
? Object.entries(value).map(([k, v]) => (
|
|
21
|
-
<div>
|
|
20
|
+
? Object.entries(value).map(([k, v], i) => (
|
|
21
|
+
<div key={`${k}-${i}`}>
|
|
22
22
|
{k}: <DisplayValue value={v} />
|
|
23
23
|
</div>
|
|
24
24
|
))
|
|
@@ -3,8 +3,8 @@ import isEqual from "lodash-es/isEqual";
|
|
|
3
3
|
import unset from "lodash-es/unset";
|
|
4
4
|
import cloneDeep from "lodash-es/cloneDeep";
|
|
5
5
|
import Reset16 from "@carbon/icons-react/es/reset/16";
|
|
6
|
-
import Button from "carbon-components-react/es/components/Button";
|
|
7
6
|
import styles from "./editable-value.styles.css";
|
|
7
|
+
import { Button } from "carbon-components-react";
|
|
8
8
|
import {
|
|
9
9
|
ConfigValue,
|
|
10
10
|
Validator,
|
|
@@ -136,7 +136,10 @@ export default function EditableValue({
|
|
|
136
136
|
hasIconOnly
|
|
137
137
|
onClick={() => {
|
|
138
138
|
temporaryConfigStore.setState(
|
|
139
|
-
unset(temporaryConfigStore.getState(), [
|
|
139
|
+
unset(temporaryConfigStore.getState(), [
|
|
140
|
+
"config",
|
|
141
|
+
...path,
|
|
142
|
+
]) as any
|
|
140
143
|
);
|
|
141
144
|
}}
|
|
142
145
|
/>
|