@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.
Files changed (65) hide show
  1. package/README.md +1 -9
  2. package/__mocks__/react-i18next.js +54 -0
  3. package/{.babelrc → babel.config.json} +2 -1
  4. package/dist/266.openmrs-esm-implementer-tools-app.js +3 -0
  5. package/dist/266.openmrs-esm-implementer-tools-app.js.LICENSE.txt +8 -0
  6. package/dist/266.openmrs-esm-implementer-tools-app.js.map +1 -0
  7. package/dist/319.openmrs-esm-implementer-tools-app.js +1 -0
  8. package/dist/472.openmrs-esm-implementer-tools-app.js +2 -0
  9. package/dist/472.openmrs-esm-implementer-tools-app.js.map +1 -0
  10. package/dist/574.openmrs-esm-implementer-tools-app.js +1 -0
  11. package/dist/728.openmrs-esm-implementer-tools-app.js +2 -0
  12. package/dist/728.openmrs-esm-implementer-tools-app.js.map +1 -0
  13. package/dist/965.openmrs-esm-implementer-tools-app.js +3 -0
  14. package/dist/965.openmrs-esm-implementer-tools-app.js.LICENSE.txt +5 -0
  15. package/dist/965.openmrs-esm-implementer-tools-app.js.map +1 -0
  16. package/dist/openmrs-esm-implementer-tools-app.js +1 -1
  17. package/dist/openmrs-esm-implementer-tools-app.js.buildmanifest.json +170 -0
  18. package/dist/openmrs-esm-implementer-tools-app.js.map +1 -1
  19. package/jest.config.js +1 -2
  20. package/package.json +13 -20
  21. package/src/backend-dependencies/backend-dependencies.component.tsx +97 -0
  22. package/src/backend-dependencies/openmrs-backend-dependencies.ts +113 -93
  23. package/src/backend-dependencies/useBackendDependencies.ts +16 -0
  24. package/src/config-edit-button/config-edit-button.component.tsx +3 -1
  25. package/src/configuration/config-tree-for-module.component.tsx +1 -1
  26. package/src/configuration/config-tree.component.tsx +2 -9
  27. package/src/configuration/configuration.component.tsx +101 -31
  28. package/src/configuration/configuration.test.tsx +6 -6
  29. package/src/configuration/description.component.tsx +3 -1
  30. package/src/configuration/display-value.tsx +4 -4
  31. package/src/configuration/editable-value.component.tsx +5 -2
  32. package/src/configuration/extension-slots-config-tree.tsx +10 -14
  33. package/src/configuration/layout/subtree.component.tsx +1 -1
  34. package/src/configuration/layout/tree-container.component.tsx +1 -1
  35. package/src/configuration/value-editor.tsx +1 -1
  36. package/src/configuration/value-editors/array-editor.tsx +3 -3
  37. package/src/configuration/value-editors/extension-slot-add.tsx +1 -1
  38. package/src/configuration/value-editors/extension-slot-order.tsx +0 -4
  39. package/src/configuration/value-editors/extension-slot-remove.tsx +19 -7
  40. package/src/configuration/value-editors/object-editor.tsx +3 -3
  41. package/src/configuration/value-editors/value-editor-field.tsx +1 -3
  42. package/src/implementer-tools.button.tsx +33 -0
  43. package/src/implementer-tools.component.tsx +45 -81
  44. package/src/implementer-tools.styles.css +5 -9
  45. package/src/index.ts +35 -9
  46. package/src/popup/popup.component.tsx +11 -27
  47. package/src/popup/popup.styles.css +1 -1
  48. package/src/store.ts +27 -32
  49. package/src/ui-editor/extension-overlay.component.tsx +1 -1
  50. package/src/ui-editor/styles.css +7 -0
  51. package/src/ui-editor/ui-editor.tsx +49 -21
  52. package/translations/en.json +12 -0
  53. package/translations/es.json +13 -0
  54. package/tsconfig.json +1 -0
  55. package/webpack.config.js +1 -66
  56. package/__mocks__/openmrs-esm-framework.mock.tsx +0 -142
  57. package/dist/1.openmrs-esm-implementer-tools-app.js +0 -15
  58. package/dist/1.openmrs-esm-implementer-tools-app.js.map +0 -1
  59. package/dist/2.openmrs-esm-implementer-tools-app.js +0 -2
  60. package/dist/2.openmrs-esm-implementer-tools-app.js.map +0 -1
  61. package/src/backend-dependencies/backend-dependecies.component.tsx +0 -106
  62. package/src/backend-dependencies/backend-dependencies-style.css +0 -4
  63. package/src/backend-dependencies/helpers/backend-dependecies-helper.test.ts +0 -127
  64. package/src/backend-dependencies/helpers/backend-dependecies-helper.ts +0 -70
  65. package/src/set-public-path.ts +0 -3
@@ -1,64 +1,81 @@
1
- import * as semver from "semver";
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
- const installedBackendModules: Array<Record<string, string>> = [];
6
- const modulesWithMissingBackendModules: MissingBackendModules[] = [];
7
- const modulesWithWrongBackendModulesVersion: MissingBackendModules[] = [];
4
+ export type ResolvedBackendModuleType = "missing" | "version-mismatch" | "okay";
8
5
 
9
- let memorizedUnresolvedsBackendDeps: UnresolvedBackendDependencies;
6
+ export interface ResolvedBackendModule {
7
+ name: string;
8
+ requiredVersion: string;
9
+ installedVersion?: string;
10
+ type: ResolvedBackendModuleType;
11
+ }
10
12
 
11
- async function initInstalledBackendModules() {
12
- try {
13
- const response = await fetchInstalledBackendModules();
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
- export async function checkModules(): Promise<UnresolvedBackendDependencies> {
23
- if (memorizedUnresolvedsBackendDeps) return memorizedUnresolvedsBackendDeps;
18
+ interface Module {
19
+ moduleName: string;
20
+ backendDependencies: Record<string, string>;
21
+ }
24
22
 
25
- const modules = (window.installedModules ?? [])
26
- .filter((module) => module[1].backendDependencies)
27
- .map((module) => ({
28
- backendDependencies: module[1].backendDependencies,
29
- moduleName: module[0],
30
- }));
23
+ interface BackendModule {
24
+ uuid: string;
25
+ version: string;
26
+ }
31
27
 
32
- await initInstalledBackendModules();
33
- modules.forEach((module) => checkIfModulesAreInstalled(module));
28
+ let cachedFrontendModules: Array<FrontendModule>;
34
29
 
35
- memorizedUnresolvedsBackendDeps = {
36
- modulesWithMissingBackendModules,
37
- modulesWithWrongBackendModulesVersion,
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 memorizedUnresolvedsBackendDeps;
38
+ return [];
41
39
  }
42
40
 
43
- function checkIfModulesAreInstalled(module: Module) {
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
- if (missingBackendModule.length > 0) {
51
- modulesWithMissingBackendModules.push({
52
- moduleName: module.moduleName,
53
- backendModules: missingBackendModule,
54
- });
55
- }
56
- if (installedAndRequiredModules.length > 0) {
57
- modulesWithWrongBackendModulesVersion.push({
58
- moduleName: module.moduleName,
59
- backendModules: getMisMatchedBackendModules(installedAndRequiredModules),
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
- return missingModules.map((key) => {
82
- return { uuid: key, version: requiredBackendModules[key] };
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
- const requiredModules = Object.keys(requiredBackendModules).map((key) => {
90
- return { uuid: key, version: requiredBackendModules[key] };
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 getMisMatchedBackendModules(
100
- installedAndRequiredBackendModules: BackendModule[]
123
+ function getInstalledVersion(
124
+ installedAndRequiredBackendModule: BackendModule,
125
+ installedBackendModules: Array<BackendModule>
101
126
  ) {
102
- let misMatchedBackendModules: BackendModule[] = [];
103
- for (let uuid in installedAndRequiredBackendModules) {
104
- const installedVersion = installedBackendModules[uuid].version;
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
- export interface UnresolvedBackendDependencies {
128
- modulesWithMissingBackendModules: MissingBackendModules[];
129
- modulesWithWrongBackendModulesVersion: MissingBackendModules[];
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
- export interface BackendModule {
133
- uuid: string;
134
- version: string;
135
- requiredVersion?: string;
141
+ return "okay";
136
142
  }
137
143
 
138
- export interface MissingBackendModules {
139
- moduleName: string;
140
- backendModules: BackendModule[];
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 interface Module {
144
- moduleName: string;
145
- backendDependencies: Record<string, string>;
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
  }
@@ -16,7 +16,7 @@ export function ConfigTreeForModule({
16
16
  return (
17
17
  <TreeContainer>
18
18
  <ExtensionSlotsConfigTree
19
- config={config.extensions}
19
+ extensionsConfig={config.extensions}
20
20
  moduleName={moduleName}
21
21
  />
22
22
  <ConfigSubtree
@@ -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 Button from "carbon-components-react/es/components/Button";
7
- import Toggle from "carbon-components-react/es/components/Toggle";
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 { Column, Grid, Row } from "carbon-components-react/es/components/Grid";
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 { implementerToolsStore, ImplementerToolsStore } from "../store";
27
+ import cloneDeep from "lodash-es/cloneDeep";
28
+ import isEmpty from "lodash-es/isEmpty";
29
+ import styles from "./configuration.styles.css";
20
30
 
21
- export type ConfigurationProps = {
22
- setHasAlert(value: boolean): void;
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
- export function Configuration({ setHasAlert }: ConfigurationProps) {
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={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 setHasAlert={() => {}} />);
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) => <p key={v}>{v}</p>)
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(), ["config", ...path])
139
+ unset(temporaryConfigStore.getState(), [
140
+ "config",
141
+ ...path,
142
+ ]) as any
140
143
  );
141
144
  }}
142
145
  />