@truedat/dd 7.8.4 → 7.8.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@truedat/dd",
3
- "version": "7.8.4",
3
+ "version": "7.8.6",
4
4
  "description": "Truedat Web Data Dictionary",
5
5
  "sideEffects": false,
6
6
  "module": "src/index.js",
@@ -48,7 +48,7 @@
48
48
  "@testing-library/jest-dom": "^6.6.3",
49
49
  "@testing-library/react": "^16.3.0",
50
50
  "@testing-library/user-event": "^14.6.1",
51
- "@truedat/test": "7.8.4",
51
+ "@truedat/test": "7.8.6",
52
52
  "identity-obj-proxy": "^3.0.0",
53
53
  "jest": "^29.7.0",
54
54
  "redux-saga-test-plan": "^4.0.6"
@@ -83,5 +83,5 @@
83
83
  "svg-pan-zoom": "^3.6.2",
84
84
  "swr": "^2.3.3"
85
85
  },
86
- "gitHead": "9132f94c91f8a749e0363ef1d6ce8af40284236d"
86
+ "gitHead": "8dbca36fe9ab1cb4e8455c6a9d122fe289759b72"
87
87
  }
@@ -8,12 +8,14 @@ import { useIntl, FormattedMessage } from "react-intl";
8
8
  import { checkoutGrantRequest } from "../routines";
9
9
  import StructureGrantCart from "./StructureGrantCart";
10
10
  import StructureGrantCartInformation from "./StructureGrantCartInformation";
11
+ import { useState } from "react";
11
12
 
12
13
  export const StructureGrantCartCheckout = ({
13
14
  grantRequestsCart,
14
15
  checkoutGrantRequest,
15
16
  }) => {
16
17
  const { formatMessage } = useIntl();
18
+ const [isRequesting, setIsRequesting] = useState(false);
17
19
 
18
20
  const { template, templateContent, modificationGrant, validCart, user } =
19
21
  grantRequestsCart;
@@ -37,6 +39,7 @@ export const StructureGrantCartCheckout = ({
37
39
  created_by_id: user?.id,
38
40
  }),
39
41
  };
42
+ setIsRequesting(true);
40
43
  checkoutGrantRequest(payload);
41
44
  };
42
45
 
@@ -62,7 +65,7 @@ export const StructureGrantCartCheckout = ({
62
65
  <Button
63
66
  primary
64
67
  onClick={checkout}
65
- disabled={!validCart || !user.valid}
68
+ disabled={!validCart || !user.valid || isRequesting}
66
69
  content={formatMessage({ id: "actions.save" })}
67
70
  />
68
71
  </GridColumn>
@@ -1,14 +1,11 @@
1
+ import _ from "lodash/fp";
1
2
  import { lazy } from "react";
2
3
  import PropTypes from "prop-types";
3
- import { connect } from "react-redux";
4
+ import { useSelector } from "react-redux";
4
5
  import { Route, Routes } from "react-router";
5
6
  import { ErrorBoundary } from "@truedat/core/components";
6
- import {
7
- STRUCTURE_LINKS_NEW,
8
- STRUCTURE_STRUCTURE_LINKS_NEW,
9
- STRUCTURE_NOTES_EDIT,
10
- } from "@truedat/core/routes";
11
7
  import { getActiveTab } from "../selectors/getActiveTab";
8
+ import { useStructureCustomTabs } from "../hooks/useCustomTabs";
12
9
  import {
13
10
  getTabVisibility,
14
11
  getLinkedImplementationsToStructuresColumns,
@@ -43,7 +40,19 @@ const RuleImplementationsTable = lazy(
43
40
  () => import("@truedat/dq/components/RuleImplementationsTable")
44
41
  );
45
42
 
46
- export const StructureTabPane = ({ activeTab, tabVisibility, columns }) => {
43
+ const CustomTab = ({ tabs, activeTab }) => {
44
+ if (_.isEmpty(tabs)) return null;
45
+ const tab = _.find((tab) => (tab.name === activeTab))(tabs);
46
+ return tab?.content;
47
+ }
48
+
49
+ export const StructureTabPane = ({ path }) => {
50
+ const customTabs = useStructureCustomTabs();
51
+ const tabVisibility = useSelector(getTabVisibility);
52
+ const structureTabsOrder = useSelector(state => state.structureTabsOrder);
53
+ const columns = useSelector(state => getLinkedImplementationsToStructuresColumns(state));
54
+ const activeTab = getActiveTab({ tabVisibility, path, structureTabsOrder, customTabs });
55
+
47
56
  return (
48
57
  <ErrorBoundary>
49
58
  {activeTab === "fields" && <StructureFields />}
@@ -83,24 +92,11 @@ export const StructureTabPane = ({ activeTab, tabVisibility, columns }) => {
83
92
  )}
84
93
  {activeTab === "lineage" && <StructureLineage />}
85
94
  {activeTab === "impact" && <StructureImpact />}
95
+ {<CustomTab tabs={customTabs} activeTab={activeTab} />}
86
96
  </ErrorBoundary>
87
97
  );
88
98
  };
89
99
 
90
- StructureTabPane.propTypes = {
91
- activeTab: PropTypes.string,
92
- tabVisibility: PropTypes.object,
93
- columns: PropTypes.array,
94
- };
95
-
96
- const mapStateToProps = ({ structureTabsOrder, ...state }, { path }) => {
97
- const tabVisibility = getTabVisibility(state);
98
- const activeTab = getActiveTab(tabVisibility, path, structureTabsOrder);
99
- return {
100
- tabVisibility,
101
- activeTab,
102
- columns: getLinkedImplementationsToStructuresColumns(state),
103
- };
104
- };
100
+ StructureTabPane.propTypes = { path: PropTypes.string, };
105
101
 
106
- export default connect(mapStateToProps)(StructureTabPane);
102
+ export default StructureTabPane;
@@ -1,3 +1,4 @@
1
+ import _ from "lodash/fp";
1
2
  import { lazy } from "react";
2
3
  import { Route, Routes } from "react-router";
3
4
  import {
@@ -21,6 +22,7 @@ import {
21
22
  STRUCTURE_VERSIONS,
22
23
  STRUCTURE_VERSION_VERSIONS,
23
24
  } from "@truedat/core/routes";
25
+ import { useStructureCustomTabs } from "../hooks/useCustomTabs";
24
26
  import { useParams } from "react-router";
25
27
  import StructureTabPane from "./StructureTabPane";
26
28
 
@@ -32,8 +34,9 @@ const StructureEventsLoader = () => {
32
34
  return <EventsLoader resource_id={id} resource_type="data_structure" />;
33
35
  };
34
36
 
35
- export const StructureTabPaneRoutes = ({}) => (
36
- <Routes>
37
+ export const StructureTabPaneRoutes = ({ }) => {
38
+ const customTabs = useStructureCustomTabs();
39
+ return <Routes>
37
40
  <Route index exact element={<StructureTabPane path={STRUCTURE} />} />
38
41
 
39
42
  <Route path="versions">
@@ -110,7 +113,10 @@ export const StructureTabPaneRoutes = ({}) => (
110
113
  path="grants"
111
114
  element={<StructureTabPane path={STRUCTURE_GRANTS} />}
112
115
  />
116
+ {_.map((tab) => (
117
+ <Route path={tab.name} element={<StructureTabPane path={tab.route} />} />
118
+ ))(customTabs)}
113
119
  </Routes>
114
- );
120
+ };
115
121
 
116
122
  export default StructureTabPaneRoutes;
@@ -1,3 +1,4 @@
1
+ import _ from "lodash/fp";
1
2
  import { Route, Routes } from "react-router";
2
3
  import {
3
4
  STRUCTURE,
@@ -20,64 +21,72 @@ import {
20
21
  STRUCTURE_VERSION_VERSIONS,
21
22
  STRUCTURE_METADATA,
22
23
  } from "@truedat/core/routes";
24
+ import { useStructureCustomTabs } from "../hooks/useCustomTabs";
23
25
  import StructureTabs from "./StructureTabs";
24
26
 
25
- export const StructureTabRoutes = () => (
26
- <Routes>
27
- <Route index exact element={<StructureTabs path={STRUCTURE} />} />
27
+ export const StructureTabRoutes = () => {
28
+ const customTabs = useStructureCustomTabs();
28
29
 
29
- <Route path="versions">
30
- <Route index element={<StructureTabs path={STRUCTURE_VERSIONS} />} />
30
+ return (
31
+ <Routes>
32
+ <Route index exact element={<StructureTabs path={STRUCTURE} />} />
31
33
 
32
- <Route path=":version">
33
- <Route index element={<StructureTabs path={STRUCTURE_VERSION} />} />
34
- <Route
35
- path="versions"
36
- element={<StructureTabs path={STRUCTURE_VERSION_VERSIONS} />}
37
- />
38
- <Route
39
- path="fields"
40
- element={<StructureTabs path={STRUCTURE_VERSION_FIELDS} />}
41
- />
34
+ <Route path="versions">
35
+ <Route index element={<StructureTabs path={STRUCTURE_VERSIONS} />} />
36
+
37
+ <Route path=":version">
38
+ <Route index element={<StructureTabs path={STRUCTURE_VERSION} />} />
39
+ <Route
40
+ path="versions"
41
+ element={<StructureTabs path={STRUCTURE_VERSION_VERSIONS} />}
42
+ />
43
+ <Route
44
+ path="fields"
45
+ element={<StructureTabs path={STRUCTURE_VERSION_FIELDS} />}
46
+ />
47
+ </Route>
42
48
  </Route>
43
- </Route>
44
49
 
45
- <Route
46
- path="profile"
47
- element={<StructureTabs path={STRUCTURE_PROFILE} />}
48
- />
49
- <Route path="links/*" element={<StructureTabs path={STRUCTURE_LINKS} />} />
50
- <Route
51
- path="structureLinks/*"
52
- element={<StructureTabs path={STRUCTURE_STRUCTURE_LINKS} />}
53
- />
54
- <Route path="events" element={<StructureTabs path={STRUCTURE_EVENTS} />} />
55
- <Route
56
- path="metadata"
57
- element={<StructureTabs path={STRUCTURE_METADATA} />}
58
- />
59
- <Route path="notes/*" element={<StructureTabs path={STRUCTURE_NOTES} />} />
60
- <Route
61
- path="members"
62
- element={<StructureTabs path={STRUCTURE_MEMBERS} />}
63
- />
64
- <Route path="rules" element={<StructureTabs path={STRUCTURE_RULES} />} />
65
- <Route
66
- path="lineage"
67
- element={<StructureTabs path={STRUCTURE_LINEAGE} />}
68
- />
69
- <Route
70
- path="children"
71
- element={<StructureTabs path={STRUCTURE_CHILDREN} />}
72
- />
73
- <Route path="fields" element={<StructureTabs path={STRUCTURE_FIELDS} />} />
74
- <Route
75
- path="parents"
76
- element={<StructureTabs path={STRUCTURE_PARENTS} />}
77
- />
78
- <Route path="impact" element={<StructureTabs path={STRUCTURE_IMPACT} />} />
79
- <Route path="grants" element={<StructureTabs path={STRUCTURE_GRANTS} />} />
80
- </Routes>
81
- );
50
+ <Route
51
+ path="profile"
52
+ element={<StructureTabs path={STRUCTURE_PROFILE} />}
53
+ />
54
+ <Route path="links/*" element={<StructureTabs path={STRUCTURE_LINKS} />} />
55
+ <Route
56
+ path="structureLinks/*"
57
+ element={<StructureTabs path={STRUCTURE_STRUCTURE_LINKS} />}
58
+ />
59
+ <Route path="events" element={<StructureTabs path={STRUCTURE_EVENTS} />} />
60
+ <Route
61
+ path="metadata"
62
+ element={<StructureTabs path={STRUCTURE_METADATA} />}
63
+ />
64
+ <Route path="notes/*" element={<StructureTabs path={STRUCTURE_NOTES} />} />
65
+ <Route
66
+ path="members"
67
+ element={<StructureTabs path={STRUCTURE_MEMBERS} />}
68
+ />
69
+ <Route path="rules" element={<StructureTabs path={STRUCTURE_RULES} />} />
70
+ <Route
71
+ path="lineage"
72
+ element={<StructureTabs path={STRUCTURE_LINEAGE} />}
73
+ />
74
+ <Route
75
+ path="children"
76
+ element={<StructureTabs path={STRUCTURE_CHILDREN} />}
77
+ />
78
+ <Route path="fields" element={<StructureTabs path={STRUCTURE_FIELDS} />} />
79
+ <Route
80
+ path="parents"
81
+ element={<StructureTabs path={STRUCTURE_PARENTS} />}
82
+ />
83
+ <Route path="impact" element={<StructureTabs path={STRUCTURE_IMPACT} />} />
84
+ <Route path="grants" element={<StructureTabs path={STRUCTURE_GRANTS} />} />
85
+ {_.map((tab) => (
86
+ <Route path={tab.name} element={<StructureTabs path={`/structures/:id/${tab.name}`} />} />
87
+ ))(customTabs)}
88
+ </Routes>
89
+ )
90
+ };
82
91
 
83
92
  export default StructureTabRoutes;
@@ -1,10 +1,12 @@
1
1
  import _ from "lodash/fp";
2
+ import { compile } from "path-to-regexp";
2
3
  import PropTypes from "prop-types";
3
4
  import { Divider, Menu } from "semantic-ui-react";
4
5
  import { Link } from "react-router";
5
- import { connect } from "react-redux";
6
+ import { useSelector } from "react-redux";
6
7
  import { useIntl } from "react-intl";
7
8
  import { linkTo } from "@truedat/core/routes";
9
+ import { useStructureCustomTabs } from "../hooks/useCustomTabs";
8
10
  import { getActiveTab } from "../selectors/getActiveTab";
9
11
  import { getTabVisibility } from "../selectors/getTabVisibility";
10
12
 
@@ -19,157 +21,151 @@ const versionsLink = ({ id }, version) =>
19
21
  ? linkTo.STRUCTURE_VERSIONS({ id })
20
22
  : linkTo.STRUCTURE_VERSION_VERSIONS({ id, version });
21
23
 
22
- export const StructureTabs = ({
23
- activeTab,
24
- tabVisibility: {
25
- children,
26
- events,
27
- fields,
28
- grants,
29
- impact,
30
- lineage,
31
- links,
32
- structureLinks,
33
- metadata,
34
- notes,
35
- parents,
36
- profile,
37
- rules,
38
- roles,
39
- versions,
40
- } = {},
41
- structure,
42
- structureTabsOrder,
43
- version,
44
- }) => {
24
+ export const StructureTabs = ({ path }) => {
45
25
  const { formatMessage } = useIntl();
26
+ const customTabs = useStructureCustomTabs();
27
+ const version = useSelector(state => state.structureVersion);
28
+ const structure = useSelector(state => state.structure);
29
+ const structureTabsOrder = useSelector(state => state.structureTabsOrder);
30
+ const tabVisibility = useSelector(getTabVisibility);
31
+ const activeTab = getActiveTab({ tabVisibility, path, structureTabsOrder, customTabs });
32
+
33
+ const customTabsItems = _.map((tab) => ({
34
+ active: activeTab === tab.name,
35
+ as: Link,
36
+ to: compile(tab.route)({ id: structure.id }),
37
+ content: formatMessage({ id: `tabs.dd.${tab.name}` }),
38
+ key: tab.name,
39
+ }))(customTabs);
40
+
46
41
  const items = !_.isEmpty(structure)
47
42
  ? _.filter("content")([
48
- {
49
- active: activeTab === "fields",
50
- as: Link,
51
- to: structureFields(structure, version),
52
- content: fields ? formatMessage({ id: "tabs.dd.fields" }) : false,
53
- key: "fields",
54
- },
55
- {
56
- active: activeTab === "children",
57
- as: Link,
58
- to: linkTo.STRUCTURE_CHILDREN(structure),
59
- content: children ? formatMessage({ id: "tabs.dd.children" }) : false,
60
- key: "children",
61
- },
62
- {
63
- active: activeTab === "notes",
64
- as: Link,
65
- to: linkTo.STRUCTURE_NOTES(structure),
66
- content: notes ? formatMessage({ id: "tabs.dd.notes" }) : false,
67
- key: "notes",
68
- },
69
- {
70
- active: activeTab === "parents",
71
- as: Link,
72
- to: linkTo.STRUCTURE_PARENTS(structure),
73
- content: parents ? formatMessage({ id: "tabs.dd.parents" }) : false,
74
- key: "parents",
75
- },
76
- {
77
- active: activeTab === "grants",
78
- as: Link,
79
- to: linkTo.STRUCTURE_GRANTS(structure),
80
- content: grants ? formatMessage({ id: "tabs.dd.grants" }) : false,
81
- key: "grants",
82
- },
83
- {
84
- active: activeTab === "metadata",
85
- as: Link,
86
- to: linkTo.STRUCTURE_METADATA(structure),
87
- content: metadata ? formatMessage({ id: "tabs.dd.metadata" }) : false,
88
- key: "metadata",
89
- },
90
- {
91
- active: activeTab === "profile",
92
- as: Link,
93
- to: linkTo.STRUCTURE_PROFILE(structure),
94
- content: profile ? formatMessage({ id: "tabs.dd.profile" }) : false,
95
- key: "profile",
96
- },
97
- {
98
- active: activeTab === "rules",
99
- as: Link,
100
- to: linkTo.STRUCTURE_RULES(structure),
101
- content: rules ? formatMessage({ id: "tabs.dd.rules" }) : false,
102
- key: "rules",
103
- },
104
- {
105
- active: activeTab === "links",
106
- as: Link,
107
- to: linkTo.STRUCTURE_LINKS(structure),
108
- content: links ? formatMessage({ id: "tabs.dd.links" }) : false,
109
- key: "links",
110
- },
111
- {
112
- active: activeTab === "structureLinks",
113
- as: Link,
114
- to: linkTo.STRUCTURE_STRUCTURE_LINKS(structure),
115
- content: structureLinks
116
- ? formatMessage({ id: "tabs.dd.structureLinks" })
117
- : false,
118
- key: "structureLinks",
119
- },
120
- {
121
- active: activeTab === "lineage",
122
- as: Link,
123
- to: linkTo.STRUCTURE_LINEAGE(structure),
124
- content: lineage ? formatMessage({ id: "tabs.dd.lineage" }) : false,
125
- key: "lineage",
126
- },
127
- {
128
- active: activeTab === "impact",
129
- as: Link,
130
- to: linkTo.STRUCTURE_IMPACT(structure),
131
- content: impact ? formatMessage({ id: "tabs.dd.impact" }) : false,
132
- key: "impact",
133
- },
134
- {
135
- active: activeTab === "versions",
136
- as: Link,
137
- to: versionsLink(structure, version),
138
- content: versions ? formatMessage({ id: "tabs.dd.versions" }) : false,
139
- key: "versions",
140
- },
141
- {
142
- active: activeTab === "events",
143
- as: Link,
144
- to: linkTo.STRUCTURE_EVENTS(structure),
145
- content: events ? formatMessage({ id: "tabs.dd.audit" }) : false,
146
- key: "events",
147
- },
148
- {
149
- active: activeTab === "roles",
150
- as: Link,
151
- to: linkTo.STRUCTURE_MEMBERS(structure),
152
- content: roles ? formatMessage({ id: "tabs.dd.roles" }) : false,
153
- key: "roles",
154
- },
155
- ])
43
+ {
44
+ active: activeTab === "fields",
45
+ as: Link,
46
+ to: structureFields(structure, version),
47
+ content: tabVisibility?.fields ? formatMessage({ id: "tabs.dd.fields" }) : false,
48
+ key: "fields",
49
+ },
50
+ {
51
+ active: activeTab === "children",
52
+ as: Link,
53
+ to: linkTo.STRUCTURE_CHILDREN(structure),
54
+ content: tabVisibility?.children ? formatMessage({ id: "tabs.dd.children" }) : false,
55
+ key: "children",
56
+ },
57
+ {
58
+ active: activeTab === "notes",
59
+ as: Link,
60
+ to: linkTo.STRUCTURE_NOTES(structure),
61
+ content: tabVisibility?.notes ? formatMessage({ id: "tabs.dd.notes" }) : false,
62
+ key: "notes",
63
+ },
64
+ {
65
+ active: activeTab === "parents",
66
+ as: Link,
67
+ to: linkTo.STRUCTURE_PARENTS(structure),
68
+ content: tabVisibility?.parents ? formatMessage({ id: "tabs.dd.parents" }) : false,
69
+ key: "parents",
70
+ },
71
+ {
72
+ active: activeTab === "grants",
73
+ as: Link,
74
+ to: linkTo.STRUCTURE_GRANTS(structure),
75
+ content: tabVisibility?.grants ? formatMessage({ id: "tabs.dd.grants" }) : false,
76
+ key: "grants",
77
+ },
78
+ {
79
+ active: activeTab === "metadata",
80
+ as: Link,
81
+ to: linkTo.STRUCTURE_METADATA(structure),
82
+ content: tabVisibility?.metadata ? formatMessage({ id: "tabs.dd.metadata" }) : false,
83
+ key: "metadata",
84
+ },
85
+ {
86
+ active: activeTab === "profile",
87
+ as: Link,
88
+ to: linkTo.STRUCTURE_PROFILE(structure),
89
+ content: tabVisibility?.profile ? formatMessage({ id: "tabs.dd.profile" }) : false,
90
+ key: "profile",
91
+ },
92
+ {
93
+ active: activeTab === "rules",
94
+ as: Link,
95
+ to: linkTo.STRUCTURE_RULES(structure),
96
+ content: tabVisibility?.rules ? formatMessage({ id: "tabs.dd.rules" }) : false,
97
+ key: "rules",
98
+ },
99
+ {
100
+ active: activeTab === "links",
101
+ as: Link,
102
+ to: linkTo.STRUCTURE_LINKS(structure),
103
+ content: tabVisibility?.links ? formatMessage({ id: "tabs.dd.links" }) : false,
104
+ key: "links",
105
+ },
106
+ {
107
+ active: activeTab === "structureLinks",
108
+ as: Link,
109
+ to: linkTo.STRUCTURE_STRUCTURE_LINKS(structure),
110
+ content: tabVisibility?.structureLinks
111
+ ? formatMessage({ id: "tabs.dd.structureLinks" })
112
+ : false,
113
+ key: "structureLinks",
114
+ },
115
+ {
116
+ active: activeTab === "lineage",
117
+ as: Link,
118
+ to: linkTo.STRUCTURE_LINEAGE(structure),
119
+ content: tabVisibility?.lineage ? formatMessage({ id: "tabs.dd.lineage" }) : false,
120
+ key: "lineage",
121
+ },
122
+ {
123
+ active: activeTab === "impact",
124
+ as: Link,
125
+ to: linkTo.STRUCTURE_IMPACT(structure),
126
+ content: tabVisibility?.impact ? formatMessage({ id: "tabs.dd.impact" }) : false,
127
+ key: "impact",
128
+ },
129
+ {
130
+ active: activeTab === "versions",
131
+ as: Link,
132
+ to: versionsLink(structure, version),
133
+ content: tabVisibility?.versions ? formatMessage({ id: "tabs.dd.versions" }) : false,
134
+ key: "versions",
135
+ },
136
+ {
137
+ active: activeTab === "events",
138
+ as: Link,
139
+ to: linkTo.STRUCTURE_EVENTS(structure),
140
+ content: tabVisibility?.events ? formatMessage({ id: "tabs.dd.audit" }) : false,
141
+ key: "events",
142
+ },
143
+ {
144
+ active: activeTab === "roles",
145
+ as: Link,
146
+ to: linkTo.STRUCTURE_MEMBERS(structure),
147
+ content: tabVisibility?.roles ? formatMessage({ id: "tabs.dd.roles" }) : false,
148
+ key: "roles",
149
+ },
150
+ ...customTabsItems,
151
+ ])
156
152
  : null;
157
153
 
158
154
  structureTabsOrder
159
155
  ? items.sort((a, b) => {
160
- const aIndex = _.indexOf(a.key)(structureTabsOrder);
161
- const bIndex = _.indexOf(b.key)(structureTabsOrder);
162
- if (aIndex !== -1 && bIndex !== -1) {
163
- return aIndex - bIndex;
164
- }
165
- if (aIndex !== -1) {
166
- return -1;
167
- }
168
- if (bIndex !== -1) {
169
- return 1;
170
- }
171
- return 0;
172
- })
156
+ const aIndex = _.indexOf(a.key)(structureTabsOrder);
157
+ const bIndex = _.indexOf(b.key)(structureTabsOrder);
158
+ if (aIndex !== -1 && bIndex !== -1) {
159
+ return aIndex - bIndex;
160
+ }
161
+ if (aIndex !== -1) {
162
+ return -1;
163
+ }
164
+ if (bIndex !== -1) {
165
+ return 1;
166
+ }
167
+ return 0;
168
+ })
173
169
  : items;
174
170
 
175
171
  return (
@@ -187,27 +183,6 @@ export const StructureTabs = ({
187
183
  );
188
184
  };
189
185
 
190
- StructureTabs.propTypes = {
191
- structure: PropTypes.object,
192
- structureTabsOrder: PropTypes.array,
193
- version: PropTypes.string,
194
- activeTab: PropTypes.string,
195
- tabVisibility: PropTypes.object,
196
- };
197
-
198
- const mapStateToProps = ({ structureTabsOrder, ...state }, ownProps) => {
199
- const { path } = ownProps;
200
- const { structureVersion: version, structure } = state;
201
- const tabVisibility = getTabVisibility(state, path);
202
- const activeTab = getActiveTab(tabVisibility, path, structureTabsOrder);
203
-
204
- return {
205
- activeTab,
206
- structure,
207
- structureTabsOrder,
208
- tabVisibility,
209
- version,
210
- };
211
- };
186
+ StructureTabs.propTypes = { path: PropTypes.string, };
212
187
 
213
- export default connect(mapStateToProps)(StructureTabs);
188
+ export default StructureTabs;
@@ -1,7 +1,27 @@
1
1
  import React from "react";
2
2
  import { render, waitForLoad } from "@truedat/test/render";
3
+ import { waitFor } from "@testing-library/react";
3
4
  import { StructureTabPane } from "../StructureTabPane";
4
- import { STRUCTURE_NOTES_EDIT } from "@truedat/core/routes";
5
+ import {
6
+ STRUCTURE_CHILDREN,
7
+ STRUCTURE_FIELDS,
8
+ STRUCTURE_METADATA,
9
+ STRUCTURE_NOTES,
10
+ STRUCTURE_NOTES_EDIT,
11
+ STRUCTURE_PARENTS,
12
+ STRUCTURE_PROFILE,
13
+ STRUCTURE_STRUCTURE_LINKS,
14
+ STRUCTURE_MEMBERS,
15
+ STRUCTURE_RULES,
16
+ STRUCTURE_LINKS,
17
+ STRUCTURE_VERSIONS,
18
+ STRUCTURE_GRANTS,
19
+ STRUCTURE_EVENTS,
20
+ STRUCTURE_LINEAGE,
21
+ STRUCTURE_IMPACT,
22
+ } from "@truedat/core/routes";
23
+ import * as getTabVisibilityModule from "../../selectors/getTabVisibility";
24
+ import * as useCustomTabsModule from "../../hooks/useCustomTabs";
5
25
 
6
26
  jest.mock("../StructureFields", () => () => <div>StructureFields</div>);
7
27
  jest.mock("../StructureNotes", () => () => <div>StructureNotes</div>);
@@ -48,18 +68,21 @@ describe("<StructureTabPane />", () => {
48
68
  rules: true,
49
69
  };
50
70
 
71
+ const state = {
72
+ structureTabsOrder: [],
73
+ };
74
+
51
75
  it("renders correctly with fields tab", async () => {
52
- const rendered = render(
53
- <StructureTabPane activeTab="fields" tabVisibility={tabVisibility} />
54
- );
76
+ jest.spyOn(getTabVisibilityModule, "getTabVisibility").mockReturnValue(tabVisibility);
77
+ const rendered = render(<StructureTabPane path={STRUCTURE_FIELDS} />, { state });
55
78
  await waitForLoad(rendered);
56
79
  expect(rendered.container).toMatchSnapshot();
57
80
  });
58
81
 
59
82
  it("renders correctly with notes edit route", async () => {
60
83
  const rendered = render(
61
- <StructureTabPane activeTab="notes" tabVisibility={tabVisibility} />,
62
- { routes: [STRUCTURE_NOTES_EDIT] }
84
+ <StructureTabPane path={STRUCTURE_NOTES_EDIT} />,
85
+ { routes: [STRUCTURE_NOTES_EDIT], state }
63
86
  );
64
87
  await waitForLoad(rendered);
65
88
  expect(rendered.container).toMatchSnapshot();
@@ -67,120 +90,120 @@ describe("<StructureTabPane />", () => {
67
90
 
68
91
  it("renders correctly with notes default route", async () => {
69
92
  const rendered = render(
70
- <StructureTabPane activeTab="notes" tabVisibility={tabVisibility} />
93
+ <StructureTabPane path={STRUCTURE_NOTES} />, { state }
71
94
  );
72
95
  await waitForLoad(rendered);
73
96
  expect(rendered.container).toMatchSnapshot();
74
97
  });
75
98
 
76
99
  it("renders correctly with grants tab", async () => {
77
- const rendered = render(
78
- <StructureTabPane activeTab="grants" tabVisibility={tabVisibility} />
79
- );
100
+ jest.spyOn(getTabVisibilityModule, "getTabVisibility").mockReturnValue(tabVisibility);
101
+ const rendered = render(<StructureTabPane path={STRUCTURE_GRANTS} />, { state });
80
102
  await waitForLoad(rendered);
81
103
  expect(rendered.container).toMatchSnapshot();
82
104
  });
83
105
 
84
106
  it("renders correctly with profile tab", async () => {
107
+ jest.spyOn(getTabVisibilityModule, "getTabVisibility").mockReturnValue(tabVisibility);
85
108
  const rendered = render(
86
- <StructureTabPane activeTab="profile" tabVisibility={tabVisibility} />
109
+ <StructureTabPane path={STRUCTURE_PROFILE} />, { state }
87
110
  );
88
111
  await waitForLoad(rendered);
89
112
  expect(rendered.container).toMatchSnapshot();
90
113
  });
91
114
 
92
115
  it("renders correctly with metadata tab", async () => {
93
- const rendered = render(
94
- <StructureTabPane activeTab="metadata" tabVisibility={tabVisibility} />
95
- );
116
+ jest.spyOn(getTabVisibilityModule, "getTabVisibility").mockReturnValue(tabVisibility);
117
+ const rendered = render(<StructureTabPane path={STRUCTURE_METADATA} />, { state });
96
118
  await waitForLoad(rendered);
97
119
  expect(rendered.container).toMatchSnapshot();
98
120
  });
99
121
 
100
122
  it("renders correctly with rules tab", async () => {
101
- const rendered = render(
102
- <StructureTabPane
103
- activeTab="rules"
104
- tabVisibility={tabVisibility}
105
- columns={[]}
106
- />
107
- );
123
+ jest.spyOn(getTabVisibilityModule, "getTabVisibility").mockReturnValue(tabVisibility);
124
+ const rendered = render(<StructureTabPane path={STRUCTURE_RULES} />, { state });
108
125
  await waitForLoad(rendered);
109
126
  expect(rendered.container).toMatchSnapshot();
110
127
  });
111
128
 
112
129
  it("renders correctly with links tab", async () => {
113
- const rendered = render(
114
- <StructureTabPane activeTab="links" tabVisibility={tabVisibility} />
115
- );
130
+ jest.spyOn(getTabVisibilityModule, "getTabVisibility").mockReturnValue(tabVisibility);
131
+ const rendered = render(<StructureTabPane path={STRUCTURE_LINKS} />, { state });
116
132
  await waitForLoad(rendered);
117
133
  expect(rendered.container).toMatchSnapshot();
118
134
  });
119
135
 
120
136
  it("renders correctly with structure links tab", async () => {
121
- const rendered = render(
122
- <StructureTabPane
123
- activeTab="structureLinks"
124
- tabVisibility={tabVisibility}
125
- />
137
+ jest.spyOn(getTabVisibilityModule, "getTabVisibility").mockReturnValue(tabVisibility);
138
+ const rendered = render(<StructureTabPane path={STRUCTURE_STRUCTURE_LINKS} />, { state }
126
139
  );
127
140
  await waitForLoad(rendered);
128
141
  expect(rendered.container).toMatchSnapshot();
129
142
  });
130
143
 
131
144
  it("renders correctly with parents tab", async () => {
132
- const rendered = render(
133
- <StructureTabPane activeTab="parents" tabVisibility={tabVisibility} />
134
- );
145
+ jest.spyOn(getTabVisibilityModule, "getTabVisibility").mockReturnValue(tabVisibility);
146
+ const rendered = render(<StructureTabPane path={STRUCTURE_PARENTS} />, { state });
135
147
  await waitForLoad(rendered);
136
148
  expect(rendered.container).toMatchSnapshot();
137
149
  });
138
150
 
139
151
  it("renders correctly with children tab", async () => {
140
- const rendered = render(
141
- <StructureTabPane activeTab="children" tabVisibility={tabVisibility} />
142
- );
152
+ jest.spyOn(getTabVisibilityModule, "getTabVisibility").mockReturnValue(tabVisibility);
153
+ const rendered = render(<StructureTabPane path={STRUCTURE_CHILDREN} />, { state });
143
154
  await waitForLoad(rendered);
144
155
  expect(rendered.container).toMatchSnapshot();
145
156
  });
146
157
 
147
158
  it("renders correctly with versions tab", async () => {
148
- const rendered = render(
149
- <StructureTabPane activeTab="versions" tabVisibility={tabVisibility} />
150
- );
159
+ jest.spyOn(getTabVisibilityModule, "getTabVisibility").mockReturnValue(tabVisibility);
160
+ const rendered = render(<StructureTabPane path={STRUCTURE_VERSIONS} />, { state });
151
161
  await waitForLoad(rendered);
152
162
  expect(rendered.container).toMatchSnapshot();
153
163
  });
154
164
 
155
165
  it("renders correctly with events tab", async () => {
156
- const rendered = render(
157
- <StructureTabPane activeTab="events" tabVisibility={tabVisibility} />
158
- );
166
+ jest.spyOn(getTabVisibilityModule, "getTabVisibility").mockReturnValue(tabVisibility);
167
+ const rendered = render(<StructureTabPane path={STRUCTURE_EVENTS} />, { state });
159
168
  await waitForLoad(rendered);
160
169
  expect(rendered.container).toMatchSnapshot();
161
170
  });
162
171
 
163
172
  it("renders correctly with roles tab", async () => {
164
- const rendered = render(
165
- <StructureTabPane activeTab="roles" tabVisibility={tabVisibility} />
166
- );
173
+ jest.spyOn(getTabVisibilityModule, "getTabVisibility").mockReturnValue(tabVisibility);
174
+ const rendered = render(<StructureTabPane path={STRUCTURE_MEMBERS} />, { state });
167
175
  await waitForLoad(rendered);
168
176
  expect(rendered.container).toMatchSnapshot();
169
177
  });
170
178
 
171
179
  it("renders correctly with lineage tab", async () => {
180
+ jest.spyOn(getTabVisibilityModule, "getTabVisibility").mockReturnValue(tabVisibility);
172
181
  const rendered = render(
173
- <StructureTabPane activeTab="lineage" tabVisibility={tabVisibility} />
182
+ <StructureTabPane path={STRUCTURE_LINEAGE} />, { state }
174
183
  );
175
184
  await waitForLoad(rendered);
176
185
  expect(rendered.container).toMatchSnapshot();
177
186
  });
178
187
 
179
188
  it("renders correctly with impact tab", async () => {
189
+ jest.spyOn(getTabVisibilityModule, "getTabVisibility").mockReturnValue(tabVisibility);
190
+ const rendered = render(
191
+ <StructureTabPane path={STRUCTURE_IMPACT} />, { state }
192
+ );
193
+ await waitForLoad(rendered);
194
+ expect(rendered.container).toMatchSnapshot();
195
+ });
196
+
197
+ it("renders correctly with custom tab", async () => {
198
+ jest.spyOn(getTabVisibilityModule, "getTabVisibility").mockReturnValue(tabVisibility);
199
+ jest.spyOn(useCustomTabsModule, "useStructureCustomTabs").mockReturnValue([{ name: "custom", route: "/structures/:id/custom", content: <p>Custom</p> },]);
180
200
  const rendered = render(
181
- <StructureTabPane activeTab="impact" tabVisibility={tabVisibility} />
201
+ <StructureTabPane path={"/structures/:id/custom"} />, { state }
182
202
  );
183
203
  await waitForLoad(rendered);
204
+ await waitFor(() => {
205
+ expect(rendered.getByText("Custom")).toBeInTheDocument();
206
+ });
184
207
  expect(rendered.container).toMatchSnapshot();
185
208
  });
186
209
  });
@@ -1,6 +1,7 @@
1
1
  import _ from "lodash/fp";
2
2
  import { render, waitForLoad } from "@truedat/test/render";
3
3
  import { StructureTabs } from "../StructureTabs";
4
+ import * as getTabVisibilityModule from "../../selectors/getTabVisibility";
4
5
 
5
6
  describe("<StructureTabs />", () => {
6
7
  const tabs = [
@@ -27,8 +28,10 @@ describe("<StructureTabs />", () => {
27
28
  const activeTab = "metadata";
28
29
 
29
30
  it("matches the latest snapshot: " + activeTab, async () => {
30
- const props = { structure, version, tabVisibility, activeTab };
31
- const rendered = render(<StructureTabs {...props} />);
31
+ jest.spyOn(getTabVisibilityModule, "getTabVisibility").mockReturnValue(tabVisibility);
32
+
33
+ const props = { path: "/structures/:id/metadata" };
34
+ const rendered = render(<StructureTabs {...props} />, { state: { structure, structureVersion: version, structureTabsOrder: [] } });
32
35
  await waitForLoad(rendered);
33
36
  expect(rendered.container).toMatchSnapshot();
34
37
  });
@@ -8,6 +8,14 @@ exports[`<StructureTabPane /> renders correctly with children tab 1`] = `
8
8
  </div>
9
9
  `;
10
10
 
11
+ exports[`<StructureTabPane /> renders correctly with custom tab 1`] = `
12
+ <div>
13
+ <p>
14
+ Custom
15
+ </p>
16
+ </div>
17
+ `;
18
+
11
19
  exports[`<StructureTabPane /> renders correctly with events tab 1`] = `
12
20
  <div>
13
21
  <div>
@@ -0,0 +1,14 @@
1
+ import _ from "lodash/fp";
2
+ import { useWebContext } from "@truedat/core/webContext";
3
+ import { useMemo } from "react";
4
+
5
+ export function useStructureCustomTabs() {
6
+ const { structureTabs } = useWebContext();
7
+
8
+ return useMemo(() => {
9
+ if (!structureTabs?.customTabs) return [];
10
+ return _.map((tab) => ({ ...tab, route: `/structures/:id/${tab.name}` }))(
11
+ structureTabs.customTabs
12
+ );
13
+ }, [structureTabs]);
14
+ }
@@ -17,6 +17,8 @@ import {
17
17
  STRUCTURE_VERSION_VERSIONS,
18
18
  STRUCTURE_GRANTS,
19
19
  } from "@truedat/core/routes";
20
+ const customPath = "/structures/:id/custom";
21
+
20
22
  import { defaultTab, getActiveTab } from "../getActiveTab";
21
23
 
22
24
  describe("selectors: defaultTab", () => {
@@ -79,25 +81,26 @@ describe("selectors: getActiveTab", () => {
79
81
  const tabVisibility = { fields: true };
80
82
 
81
83
  it("should determine value according to path and tab visibility", () => {
82
- expect(getActiveTab(tabVisibility, STRUCTURE)).toBe("fields");
83
- expect(getActiveTab(tabVisibility, STRUCTURE_VERSION)).toBe("fields");
84
- expect(getActiveTab(tabVisibility, STRUCTURE_CHILDREN)).toBe("children");
85
- expect(getActiveTab(tabVisibility, STRUCTURE_NOTES)).toBe("notes");
86
- expect(getActiveTab(tabVisibility, STRUCTURE_NOTES_EDIT)).toBe("notes");
87
- expect(getActiveTab(tabVisibility, STRUCTURE_PARENTS)).toBe("parents");
88
- expect(getActiveTab(tabVisibility, STRUCTURE_PROFILE)).toBe("profile");
89
- expect(getActiveTab(tabVisibility, STRUCTURE_RULES)).toBe("rules");
90
- expect(getActiveTab(tabVisibility, STRUCTURE_MEMBERS)).toBe("roles");
91
- expect(getActiveTab(tabVisibility, STRUCTURE_LINKS)).toBe("links");
92
- expect(getActiveTab(tabVisibility, STRUCTURE_LINKS_NEW)).toBe("links");
93
- expect(getActiveTab(tabVisibility, STRUCTURE_VERSIONS)).toBe("versions");
94
- expect(getActiveTab(tabVisibility, STRUCTURE_VERSION_VERSIONS)).toBe(
84
+ expect(getActiveTab({ tabVisibility, path: STRUCTURE })).toBe("fields");
85
+ expect(getActiveTab({ tabVisibility, path: STRUCTURE_VERSION })).toBe("fields");
86
+ expect(getActiveTab({ tabVisibility, path: STRUCTURE_CHILDREN })).toBe("children");
87
+ expect(getActiveTab({ tabVisibility, path: STRUCTURE_NOTES })).toBe("notes");
88
+ expect(getActiveTab({ tabVisibility, path: STRUCTURE_NOTES_EDIT })).toBe("notes");
89
+ expect(getActiveTab({ tabVisibility, path: STRUCTURE_PARENTS })).toBe("parents");
90
+ expect(getActiveTab({ tabVisibility, path: STRUCTURE_PROFILE })).toBe("profile");
91
+ expect(getActiveTab({ tabVisibility, path: STRUCTURE_RULES })).toBe("rules");
92
+ expect(getActiveTab({ tabVisibility, path: STRUCTURE_MEMBERS })).toBe("roles");
93
+ expect(getActiveTab({ tabVisibility, path: STRUCTURE_LINKS })).toBe("links");
94
+ expect(getActiveTab({ tabVisibility, path: STRUCTURE_LINKS_NEW })).toBe("links");
95
+ expect(getActiveTab({ tabVisibility, path: STRUCTURE_VERSIONS })).toBe("versions");
96
+ expect(getActiveTab({ tabVisibility, path: STRUCTURE_VERSION_VERSIONS })).toBe(
95
97
  "versions"
96
98
  );
97
- expect(getActiveTab(tabVisibility, STRUCTURE_EVENTS)).toBe("events");
98
- expect(getActiveTab(tabVisibility, STRUCTURE_LINEAGE)).toBe("lineage");
99
- expect(getActiveTab(tabVisibility, STRUCTURE_IMPACT)).toBe("impact");
100
- expect(getActiveTab(tabVisibility, STRUCTURE_GRANTS)).toBe("grants");
101
- expect(getActiveTab(tabVisibility, "foo")).toBe(undefined);
99
+ expect(getActiveTab({ tabVisibility, path: STRUCTURE_EVENTS })).toBe("events");
100
+ expect(getActiveTab({ tabVisibility, path: STRUCTURE_LINEAGE })).toBe("lineage");
101
+ expect(getActiveTab({ tabVisibility, path: STRUCTURE_IMPACT })).toBe("impact");
102
+ expect(getActiveTab({ tabVisibility, path: STRUCTURE_GRANTS })).toBe("grants");
103
+ expect(getActiveTab({ tabVisibility, path: "foo" })).toBe(undefined);
104
+ expect(getActiveTab({ tabVisibility, path: customPath, customTabs: [{ name: "custom", route: customPath }] })).toBe("custom");
102
105
  });
103
106
  });
@@ -23,7 +23,7 @@ import {
23
23
  STRUCTURE_IMPACT,
24
24
  } from "@truedat/core/routes";
25
25
 
26
- export const defaultTab = (tabVisibility, tabsOrder = []) => {
26
+ export const defaultTab = (tabVisibility, tabsOrder = [], customTabs = []) => {
27
27
  const defaultOrder = [
28
28
  "fields",
29
29
  "children",
@@ -36,15 +36,18 @@ export const defaultTab = (tabVisibility, tabsOrder = []) => {
36
36
  "roles",
37
37
  "links",
38
38
  "versions",
39
+ ..._.map((tab) => tab.name)(customTabs),
39
40
  ];
40
41
 
41
42
  return _.find((tab) => tabVisibility[tab])([...tabsOrder, ...defaultOrder]);
42
43
  };
43
44
 
44
- export const getActiveTab = (tabVisibility, path, tabsOrder) => {
45
+ const getCustomTabs = (customTabs) => _.map((tab) => [_.eq(tab.route), _.constant(tab.name)])(customTabs)
46
+
47
+ export const getActiveTab = ({ tabVisibility, path, tabsOrder, customTabs }) => {
45
48
  return _.cond([
46
- [_.eq(STRUCTURE), _.constant(defaultTab(tabVisibility, tabsOrder))],
47
- [_.eq(STRUCTURE_VERSION), _.constant(defaultTab(tabVisibility, tabsOrder))],
49
+ [_.eq(STRUCTURE), _.constant(defaultTab(tabVisibility, tabsOrder, customTabs))],
50
+ [_.eq(STRUCTURE_VERSION), _.constant(defaultTab(tabVisibility, tabsOrder, customTabs))],
48
51
  [_.eq(STRUCTURE_VERSION_FIELDS), _.constant("fields")],
49
52
  [_.eq(STRUCTURE_FIELDS), _.constant("fields")],
50
53
  [_.eq(STRUCTURE_CHILDREN), _.constant("children")],
@@ -64,5 +67,6 @@ export const getActiveTab = (tabVisibility, path, tabsOrder) => {
64
67
  [_.eq(STRUCTURE_EVENTS), _.constant("events")],
65
68
  [_.eq(STRUCTURE_LINEAGE), _.constant("lineage")],
66
69
  [_.eq(STRUCTURE_IMPACT), _.constant("impact")],
70
+ ...getCustomTabs(customTabs),
67
71
  ])(path);
68
72
  };
@@ -1,4 +1,5 @@
1
1
  import _ from "lodash/fp";
2
+ import { createSelector } from "reselect";
2
3
  import { getStructureToConceptLinks } from "./getStructureLinks";
3
4
  import { metadataViewsSelector } from "./metadataViewsSelector";
4
5
 
@@ -58,7 +59,7 @@ const eventsTabVisible = _.flow(
58
59
  );
59
60
  const grantsTabVisible = _.flow(_.path("structure.grants"), notEmpty);
60
61
 
61
- export const getTabVisibility = (state) => ({
62
+ export const getTabVisibility = createSelector([state => state], (state) => ({
62
63
  fields: fieldsTabVisible(state),
63
64
  profile: profileTabVisible(state),
64
65
  links: linksTabVisible(state),
@@ -74,4 +75,4 @@ export const getTabVisibility = (state) => ({
74
75
  children: childrenTabVisible(state),
75
76
  grants: grantsTabVisible(state),
76
77
  roles: rolesTabVisible(state),
77
- });
78
+ }));