@truedat/dd 6.0.1 → 6.0.3

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 (36) hide show
  1. package/package.json +6 -6
  2. package/src/api.js +2 -0
  3. package/src/components/BucketView.js +4 -3
  4. package/src/components/CatalogCustomViewCards.js +4 -1
  5. package/src/components/FilteredNav.js +92 -37
  6. package/src/components/StructureCrumbs.js +10 -8
  7. package/src/components/StructureItem.js +43 -6
  8. package/src/components/StructureItemRoot.js +6 -6
  9. package/src/components/StructureItems.js +6 -2
  10. package/src/components/StructureNav.js +10 -1
  11. package/src/components/StructureNavClassified.js +8 -1
  12. package/src/components/StructureNotesEdit.js +13 -5
  13. package/src/components/StructureSearch.js +16 -7
  14. package/src/components/StructureView.js +1 -1
  15. package/src/components/StructuresView.js +5 -5
  16. package/src/components/SystemStructures.js +7 -2
  17. package/src/components/__tests__/BucketView.spec.js +2 -1
  18. package/src/components/__tests__/FilteredNav.spec.js +76 -49
  19. package/src/components/__tests__/StructureItems.spec.js +2 -2
  20. package/src/components/__tests__/StructureNav.spec.js +1 -1
  21. package/src/components/__tests__/StructureNavClassified.spec.js +5 -1
  22. package/src/components/__tests__/StructureStructureLinks.spec.js +8 -7
  23. package/src/components/__tests__/StructureView.spec.js +1 -0
  24. package/src/components/__tests__/SystemFilteredNav.spec.js +4 -1
  25. package/src/hooks/useBucketPaths.js +11 -0
  26. package/src/hooks/useBucketStructures.js +17 -5
  27. package/src/reducers/navFilter.js +4 -1
  28. package/src/reducers/structure.js +1 -0
  29. package/src/reducers/structureFilters.js +1 -0
  30. package/src/selectors/getStructureParent.js +1 -2
  31. package/src/components/StructureNoteSuggestions.js +0 -179
  32. package/src/components/__tests__/StructureNoteSuggestions.spec.js +0 -151
  33. package/src/components/__tests__/StructureSearch.spec.js +0 -34
  34. package/src/components/__tests__/__snapshots__/StructureNoteSuggestions.spec.js.snap +0 -316
  35. package/src/components/__tests__/__snapshots__/StructureSearch.spec.js.snap +0 -18
  36. package/src/utils/bucketNav.js +0 -9
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@truedat/dd",
3
- "version": "6.0.1",
3
+ "version": "6.0.3",
4
4
  "description": "Truedat Web Data Dictionary",
5
5
  "sideEffects": false,
6
6
  "jsnext:main": "src/index.js",
@@ -34,7 +34,7 @@
34
34
  "@testing-library/jest-dom": "^5.16.5",
35
35
  "@testing-library/react": "^12.0.0",
36
36
  "@testing-library/user-event": "^13.2.1",
37
- "@truedat/test": "6.0.1",
37
+ "@truedat/test": "6.0.3",
38
38
  "babel-jest": "^28.1.0",
39
39
  "babel-plugin-dynamic-import-node": "^2.3.3",
40
40
  "babel-plugin-lodash": "^3.3.4",
@@ -88,9 +88,9 @@
88
88
  },
89
89
  "dependencies": {
90
90
  "@apollo/client": "^3.7.1",
91
- "@truedat/auth": "6.0.1",
92
- "@truedat/core": "6.0.1",
93
- "@truedat/df": "6.0.1",
91
+ "@truedat/auth": "6.0.3",
92
+ "@truedat/core": "6.0.3",
93
+ "@truedat/df": "6.0.3",
94
94
  "lodash": "^4.17.21",
95
95
  "moment": "^2.29.4",
96
96
  "path-to-regexp": "^1.7.0",
@@ -115,5 +115,5 @@
115
115
  "react-dom": ">= 16.8.6 < 17",
116
116
  "semantic-ui-react": ">= 2.0.3 < 2.2"
117
117
  },
118
- "gitHead": "2ce73d263401e0a7959f9111ba3370b6810b5420"
118
+ "gitHead": "c88ddc501214b8c4e2732654ca0052ebbcc802c9"
119
119
  }
package/src/api.js CHANGED
@@ -1,4 +1,5 @@
1
1
  const API_BUCKET_STRUCTURES = "/api/buckets/structures";
2
+ const API_BUCKET_PATHS = "/api/buckets/paths";
2
3
  const API_BUCKET_STRUCTURE = "/api/buckets/structures/:id";
3
4
  const API_DATA_STRUCTURE = "/api/data_structures/:id";
4
5
  const API_DATA_STRUCTURES_BULK_UPDATE = "/api/data_structures/bulk_update";
@@ -54,6 +55,7 @@ const API_STRUCTURE_NOTES = "/api/data_structures/:data_structure_id/notes";
54
55
 
55
56
  export {
56
57
  API_BUCKET_STRUCTURES,
58
+ API_BUCKET_PATHS,
57
59
  API_BUCKET_STRUCTURE,
58
60
  API_DATA_STRUCTURE,
59
61
  API_DATA_STRUCTURES_BULK_UPDATE,
@@ -7,7 +7,6 @@ import { useParams, useLocation } from "react-router-dom";
7
7
  import searchImage from "assets/searching.png";
8
8
  import { FormattedMessage, useIntl } from "react-intl";
9
9
  import { saveNavFilter as saveNavFilterRoutine } from "../routines";
10
- import { isBucketFilter } from "../utils/bucketNav";
11
10
  import FilteredNav from "./FilteredNav";
12
11
  import StructureCrumbs from "./StructureCrumbs";
13
12
  import StructureGrantCart from "./StructureGrantCart";
@@ -53,6 +52,7 @@ export const BucketView = ({
53
52
  getNavFilterURLParams,
54
53
  _.values,
55
54
  (keyValue) => Object.fromEntries(new Map([keyValue]).entries()),
55
+ (filter) => ({ view: "BUCKET_VIEW", filter }),
56
56
  saveNavFilter
57
57
  )(location);
58
58
  }, [location, saveNavFilter]);
@@ -68,8 +68,8 @@ export const BucketView = ({
68
68
  </Grid.Row>
69
69
  <Grid.Column width={4}>
70
70
  <Segment>
71
- {isBucketFilter(navFilter) ? (
72
- <FilteredNav filter={navFilter} />
71
+ {navFilter?.view === "BUCKET_VIEW" ? (
72
+ <FilteredNav navFilter={navFilter} />
73
73
  ) : null}
74
74
  </Segment>
75
75
  </Grid.Column>
@@ -90,6 +90,7 @@ export const BucketView = ({
90
90
  {navFilterOneProp?.propertyPath
91
91
  ? `${formatMessage({
92
92
  id: navFilterOneProp.propertyPath,
93
+ defaultMessage: navFilterOneProp.propertyPath,
93
94
  })} > ${navFilterOneProp?.propertyValue}`
94
95
  : null}
95
96
  </Header>
@@ -9,6 +9,7 @@ import { FormattedNumber } from "react-intl";
9
9
  import { CardGroupsAccordion } from "@truedat/core/components";
10
10
  import { linkTo } from "@truedat/core/routes";
11
11
  import { fetchStructureFilters as fetchStructureFiltersRoutine } from "@truedat/dd/routines";
12
+ import { Loading } from "@truedat/core/components";
12
13
 
13
14
  const moveMissingBucketToTheEnd = (buckets) =>
14
15
  _.sortBy(({ key }) => (key === "_missing" || key === "" ? 1 : 0))(buckets);
@@ -29,7 +30,9 @@ export const CatalogCustomViewCards = ({
29
30
  const { propertyPath } = useParams();
30
31
  const groups = [];
31
32
 
32
- return Object.keys(groups).length > 1 ? (
33
+ return _.isEmpty(structureFilters) ? (
34
+ <Loading />
35
+ ) : Object.keys(groups).length > 1 ? (
33
36
  <CardGroupsAccordion
34
37
  groups={groups}
35
38
  cardComponent={CatalogCustomViewCard}
@@ -1,19 +1,23 @@
1
1
  import _ from "lodash/fp";
2
- import React, { useState, useEffect } from "react";
2
+ import React, { useState, useRef } from "react";
3
3
  import PropTypes from "prop-types";
4
4
  import { connect } from "react-redux";
5
- import { lowerDeburrTrim } from "@truedat/core/services/sort";
6
- import { Button, Icon, Loader } from "semantic-ui-react";
5
+ import { Button, Icon } from "semantic-ui-react";
7
6
  import { useIntl } from "react-intl";
8
- import { useBucketStructures } from "../hooks/useBucketStructures";
9
- import { toggleStructuresAliasNameMode } from "../routines";
7
+
10
8
  import { getStructureParent } from "../selectors";
9
+ import { toggleStructuresAliasNameMode } from "../routines";
10
+ import { useBucketStructures } from "../hooks/useBucketStructures";
11
+ import { useBucketPaths } from "../hooks/useBucketPaths";
11
12
  import StructureNav from "./StructureNav";
12
13
  import StructureSearch from "./StructureSearch";
13
14
 
14
15
  export const FilteredNav = ({
15
16
  childStructures,
17
+ fnHasCatalogViewProp,
18
+ isLoading,
16
19
  parentStructure,
20
+ setSearchValue,
17
21
  structuresAliasNameMode,
18
22
  toggleStructuresAliasNameMode,
19
23
  totalStructures,
@@ -22,15 +26,6 @@ export const FilteredNav = ({
22
26
  _.has("original_name")(structure)
23
27
  )(childStructures);
24
28
  const { formatMessage } = useIntl();
25
- const [searchValue, setSearchValue] = useState("");
26
- const onChange = (searchValue) => setSearchValue(searchValue);
27
-
28
- const filteredStructures = (collection) => {
29
- const normalizedSearchValue = lowerDeburrTrim(searchValue);
30
- return _.filter((structure) =>
31
- _.contains(normalizedSearchValue)(lowerDeburrTrim(structure.name))
32
- )(collection);
33
- };
34
29
 
35
30
  return (
36
31
  <>
@@ -46,6 +41,7 @@ export const FilteredNav = ({
46
41
  icon
47
42
  data-tooltip={formatMessage({
48
43
  id: "structures.props.functional_name",
44
+ defaultMessage: "structures.props.functional_name",
49
45
  })}
50
46
  onClick={() => toggleStructuresAliasNameMode(true)}
51
47
  >
@@ -64,12 +60,14 @@ export const FilteredNav = ({
64
60
  </Button>
65
61
  </Button.Group>
66
62
  </div>
67
- <StructureSearch onChange={onChange} />
63
+ <StructureSearch onChange={setSearchValue} isLoading={isLoading} />
68
64
  <StructureNav
69
- childStructures={filteredStructures(childStructures)}
65
+ childStructures={childStructures}
70
66
  parentStructure={parentStructure}
71
67
  totalChildStructures={totalStructures}
72
68
  structuresAliasNameMode={structuresAliasNameMode}
69
+ fnHasCatalogViewProp={fnHasCatalogViewProp}
70
+ isLoading={isLoading}
73
71
  />
74
72
  </>
75
73
  );
@@ -77,48 +75,105 @@ export const FilteredNav = ({
77
75
 
78
76
  FilteredNav.propTypes = {
79
77
  childStructures: PropTypes.array,
78
+ isLoading: PropTypes.bool,
80
79
  parentStructure: PropTypes.object,
80
+ setSearchValue: PropTypes.func,
81
81
  structuresAliasNameMode: PropTypes.bool,
82
82
  toggleStructuresAliasNameMode: PropTypes.func,
83
83
  totalStructures: PropTypes.number,
84
+ fnHasCatalogViewProp: PropTypes.func,
84
85
  };
85
86
 
87
+ // ElasticSearch identifier null_value
88
+ // It is also the root ID.
89
+ const ES_ID_NULL_VALUE = 0;
90
+
86
91
  export const FilteredNavLoader = ({
87
- filter,
92
+ navFilter,
88
93
  parentStructure,
89
94
  structure,
90
95
  structuresAliasNameMode,
91
96
  toggleStructuresAliasNameMode,
92
97
  }) => {
93
- const { trigger, data, error } = useBucketStructures();
94
-
95
- useEffect(() => {
96
- const parentFilter = _.isObject(structure)
97
- ? {
98
- parent_id: _.isEmpty(structure.children)
99
- ? _.pathOr("", "parents[0].data_structure_id")(structure)
100
- : _.pathOr("", "id")(structure),
101
- }
102
- : { parent_id: "" };
103
-
104
- trigger({ ...filter, ...parentFilter });
105
- }, [trigger, structure, filter]);
106
-
107
- return !data && !error ? (
108
- <Loader />
109
- ) : (
98
+ const { data: dataBucketPaths, isLoading: isLoadingBucketPaths } =
99
+ useBucketPaths(navFilter);
100
+
101
+ const bucketPaths = dataBucketPaths?.data;
102
+
103
+ const initialTotalRef = useRef();
104
+
105
+ const subTreeChildren = (ancestry, dsid) =>
106
+ ancestry
107
+ ? _.flow(
108
+ _.map(({ data_structure_id }) => data_structure_id),
109
+ (ancestryDsIds) => [...ancestryDsIds, dsid],
110
+ (idPath) => _.get(idPath, bucketPaths?.forest),
111
+ _.keys,
112
+ (keys) => keys || []
113
+ )(ancestry)
114
+ : _.keys(bucketPaths?.forest);
115
+
116
+ const children =
117
+ navFilter.view === "SYSTEM_STRUCTURES"
118
+ ? structure?.children
119
+ : [
120
+ ...subTreeChildren(structure?.ancestry, structure?.id),
121
+ ...(bucketPaths?.filtered_children[structure?.id || 0] || []),
122
+ ];
123
+
124
+ const parentId =
125
+ structure?.parents?.[0]?.data_structure_id || ES_ID_NULL_VALUE;
126
+
127
+ const childrenOrSiblings = !_.isEmpty(children)
128
+ ? children
129
+ : [
130
+ ...subTreeChildren(structure?.ancestry.slice(0, -1), structure?.id),
131
+ ...(bucketPaths?.filtered_children[parentId] || []),
132
+ ];
133
+
134
+ // If there are children, show children (next parent_id is structure id)
135
+ // If there are no children, show siblings (next parent_id is structure parent id)
136
+ const parentOrCurrentId = _.isEmpty(children)
137
+ ? parentId
138
+ : structure?.id || ES_ID_NULL_VALUE;
139
+
140
+ const initialSearchValue = "";
141
+ const [searchValue, setSearchValue] = useState(initialSearchValue);
142
+
143
+ const { data: dataBucketStructures, isLoading } = useBucketStructures({
144
+ navFilter,
145
+ parentFilter: { parent_id: parentOrCurrentId },
146
+ ids: childrenOrSiblings,
147
+ query: searchValue,
148
+ });
149
+ if (searchValue === initialSearchValue) {
150
+ initialTotalRef.current = dataBucketStructures?.headers["x-total-count"];
151
+ }
152
+ const bucketStructures = dataBucketStructures?.data?.data;
153
+
154
+ const fnHasCatalogViewProp = (structure) =>
155
+ navFilter?.view === "BUCKET_VIEW"
156
+ ? bucketPaths?.filtered_children[parentOrCurrentId]?.includes(
157
+ structure?.id
158
+ ) || false
159
+ : undefined;
160
+
161
+ return (
110
162
  <FilteredNav
111
- childStructures={data.data.data}
163
+ childStructures={bucketStructures || []}
112
164
  parentStructure={parentStructure}
113
- totalStructures={data.headers["x-total-count"]}
165
+ totalStructures={initialTotalRef.current}
166
+ fnHasCatalogViewProp={fnHasCatalogViewProp}
114
167
  structuresAliasNameMode={structuresAliasNameMode}
115
168
  toggleStructuresAliasNameMode={toggleStructuresAliasNameMode}
169
+ setSearchValue={setSearchValue}
170
+ isLoading={isLoading || isLoadingBucketPaths}
116
171
  />
117
172
  );
118
173
  };
119
174
 
120
175
  FilteredNavLoader.propTypes = {
121
- filter: PropTypes.object,
176
+ navFilter: PropTypes.object,
122
177
  parentStructure: PropTypes.object,
123
178
  structure: PropTypes.object,
124
179
  structuresAliasNameMode: PropTypes.bool,
@@ -18,7 +18,7 @@ export const StructuresCrumb = () => (
18
18
 
19
19
  const filterQueryParams = _.flow(
20
20
  Object.entries,
21
- _.first,
21
+ _.first, // only works for one filter
22
22
  ([propertyPath, propertyValue]) => ({
23
23
  propertyPath,
24
24
  propertyValue,
@@ -27,12 +27,12 @@ const filterQueryParams = _.flow(
27
27
  );
28
28
 
29
29
  export const SystemCrumb = ({ id, name, navFilter }) => {
30
- const [propertyPath, propertyValue] = !_.isEmpty(navFilter)
31
- ? _.toPairs(navFilter)[0]
30
+ const [propertyPath, propertyValue] = !_.isEmpty(navFilter.filter)
31
+ ? _.toPairs(navFilter.filter)[0]
32
32
  : [null, null];
33
33
  return (
34
34
  <>
35
- {_.isEmpty(navFilter) || navFilter?.system_id ? (
35
+ {navFilter.view === "SYSTEM_STRUCTURES" ? (
36
36
  <Breadcrumb.Section
37
37
  as={Link}
38
38
  to={linkTo.SYSTEM_STRUCTURES({ id })}
@@ -40,25 +40,27 @@ export const SystemCrumb = ({ id, name, navFilter }) => {
40
40
  >
41
41
  {name}
42
42
  </Breadcrumb.Section>
43
- ) : (
43
+ ) : navFilter.view === "BUCKET_VIEW" ? (
44
44
  <>
45
45
  <Breadcrumb.Section
46
46
  as={Link}
47
47
  to={`${linkTo.BUCKETS_VIEW({ propertyPath })}`}
48
48
  active={false}
49
49
  >
50
- <FormattedMessage id={propertyPath} />
50
+ <FormattedMessage id={propertyPath} defaultMessage={propertyPath} />
51
51
  </Breadcrumb.Section>
52
52
  <Breadcrumb.Divider icon="right angle" />
53
53
  <Breadcrumb.Section
54
54
  as={Link}
55
- to={`${linkTo.BUCKET_VIEW()}?${filterQueryParams(navFilter)}`}
55
+ to={`${linkTo.BUCKET_VIEW()}?${filterQueryParams(
56
+ navFilter.filter
57
+ )}`}
56
58
  active={false}
57
59
  >
58
60
  {propertyValue}
59
61
  </Breadcrumb.Section>
60
62
  </>
61
- )}
63
+ ) : undefined}
62
64
  <Breadcrumb.Divider icon="right angle" />
63
65
  </>
64
66
  );
@@ -23,9 +23,12 @@ const filterQueryParams = _.flow(
23
23
  (queryParams) => new URLSearchParams(queryParams)
24
24
  );
25
25
 
26
- const bucketToBreadcrumbs = ({ navFilter, formatMessage }) => {
27
- const [propertyPath, propertyValue] = _.toPairs(navFilter)[0];
28
- return `${formatMessage({ id: propertyPath })} > ${propertyValue}`;
26
+ const bucketToBreadcrumbs = ({ filter, formatMessage }) => {
27
+ const [propertyPath, propertyValue] = _.toPairs(filter)[0];
28
+ return `${formatMessage({
29
+ id: propertyPath,
30
+ defaultMessage: propertyPath,
31
+ })} > ${propertyValue}`;
29
32
  };
30
33
 
31
34
  export const StructureItem = ({
@@ -37,11 +40,44 @@ export const StructureItem = ({
37
40
  navFilter,
38
41
  original_name,
39
42
  type,
43
+ /* hasCatalogViewProp can be:
44
+ * - true/false in bucket view (catalog view)
45
+ * - undefined in regular structure view
46
+ */
47
+ hasCatalogViewProp,
40
48
  }) => {
41
49
  const { formatMessage } = useIntl();
42
50
 
43
51
  const displayName = structuresAliasNameMode ? name : original_name || name;
44
52
 
53
+ const truthyToClassName = {
54
+ deleted_at: "deleted",
55
+ hasCatalogViewProp: "has-catalog-view-prop",
56
+ };
57
+
58
+ const notUndefinedToClassName = {
59
+ hasCatalogViewProp: "has-no-catalog-view-prop",
60
+ };
61
+
62
+ const toClassNames = _.flow(
63
+ Object.entries,
64
+ _.reduce(
65
+ (acc, [key, value]) =>
66
+ value
67
+ ? [truthyToClassName[key], ...acc]
68
+ : value !== undefined
69
+ ? [
70
+ ...(notUndefinedToClassName[key]
71
+ ? [notUndefinedToClassName[key]]
72
+ : []),
73
+ ...acc,
74
+ ]
75
+ : acc,
76
+ []
77
+ ),
78
+ _.join(" ")
79
+ );
80
+
45
81
  return (
46
82
  <List.Item
47
83
  as={active ? "a" : Link}
@@ -52,19 +88,19 @@ export const StructureItem = ({
52
88
  : type == "system" && id
53
89
  ? linkTo.SYSTEM_STRUCTURES({ id })
54
90
  : type == "bucket"
55
- ? `${linkTo.BUCKET_VIEW()}?${filterQueryParams(navFilter)}`
91
+ ? `${linkTo.BUCKET_VIEW()}?${filterQueryParams(navFilter.filter)}`
56
92
  : id
57
93
  ? linkTo.STRUCTURE({ id })
58
94
  : STRUCTURES
59
95
  }
60
- className={deleted_at ? "deleted" : undefined}
96
+ className={toClassNames({ deleted_at, hasCatalogViewProp })}
61
97
  active={active}
62
98
  >
63
99
  <List.Icon name={getIcon(formatMessage, type)} verticalAlign="middle" />
64
100
  <List.Content>
65
101
  <List.Description>
66
102
  {type == "bucket"
67
- ? bucketToBreadcrumbs({ navFilter, formatMessage })
103
+ ? bucketToBreadcrumbs({ filter: navFilter.filter, formatMessage })
68
104
  : displayName}
69
105
  </List.Description>
70
106
  </List.Content>
@@ -81,6 +117,7 @@ StructureItem.propTypes = {
81
117
  original_name: PropTypes.string,
82
118
  structuresAliasNameMode: PropTypes.bool,
83
119
  type: PropTypes.string,
120
+ hasCatalogViewProp: PropTypes.bool,
84
121
  };
85
122
 
86
123
  export const mapStateToProps = (
@@ -6,7 +6,6 @@ import { useHistory } from "react-router-dom";
6
6
  import PropTypes from "prop-types";
7
7
  import { List } from "semantic-ui-react";
8
8
  import { STRUCTURES, linkTo } from "@truedat/core/routes";
9
- import { isBucketFilter } from "../utils/bucketNav";
10
9
 
11
10
  const filterParams = _.flow(
12
11
  Object.entries,
@@ -19,11 +18,12 @@ const filterParams = _.flow(
19
18
 
20
19
  export const StructureItemRoot = ({ navFilter }) => {
21
20
  const history = useHistory();
22
- const url = isBucketFilter(navFilter)
23
- ? linkTo.BUCKETS_VIEW({
24
- propertyPath: filterParams(navFilter)?.propertyPath,
25
- })
26
- : STRUCTURES;
21
+ const url =
22
+ navFilter?.view === "BUCKET_VIEW"
23
+ ? linkTo.BUCKETS_VIEW({
24
+ propertyPath: filterParams(navFilter.filter)?.propertyPath,
25
+ })
26
+ : STRUCTURES;
27
27
  return (
28
28
  <List.Item onClick={() => history.push(url)}>
29
29
  <List.Icon name="angle double left" verticalAlign="middle" />
@@ -3,13 +3,16 @@ import PropTypes from "prop-types";
3
3
  import StructureItem from "./StructureItem";
4
4
 
5
5
  export const StructureItems = ({
6
- bucketChildStructures,
7
6
  structures,
8
7
  structuresAliasNameMode,
8
+ fnHasCatalogViewProp,
9
9
  }) => {
10
- return (bucketChildStructures || structures).map((structure, index) => (
10
+ return structures.map((structure, index) => (
11
11
  <StructureItem
12
12
  key={index}
13
+ hasCatalogViewProp={
14
+ fnHasCatalogViewProp ? fnHasCatalogViewProp(structure) : undefined
15
+ }
13
16
  structuresAliasNameMode={structuresAliasNameMode}
14
17
  {...structure}
15
18
  />
@@ -19,6 +22,7 @@ export const StructureItems = ({
19
22
  StructureItems.propTypes = {
20
23
  structures: PropTypes.array,
21
24
  structuresAliasNameMode: PropTypes.bool,
25
+ fnHasCatalogViewProp: PropTypes.func,
22
26
  };
23
27
 
24
28
  export default StructureItems;
@@ -2,6 +2,7 @@ import _ from "lodash/fp";
2
2
  import React from "react";
3
3
  import PropTypes from "prop-types";
4
4
  import { List, Divider } from "semantic-ui-react";
5
+ import { Loading } from "@truedat/core/components";
5
6
  import { useIntl } from "react-intl";
6
7
  import StructureItemRoot from "./StructureItemRoot";
7
8
  import StructureItem from "./StructureItem";
@@ -20,6 +21,8 @@ export const StructureNav = ({
20
21
  parentStructure,
21
22
  structuresAliasNameMode,
22
23
  totalChildStructures,
24
+ fnHasCatalogViewProp,
25
+ isLoading,
23
26
  }) => {
24
27
  const { formatMessage } = useIntl();
25
28
  const firstWithClasses = _.find(hasNavClass)(childStructures);
@@ -27,7 +30,9 @@ export const StructureNav = ({
27
30
  ? _.flow(navClasses, _.head)(firstWithClasses)
28
31
  : undefined;
29
32
 
30
- return (
33
+ return isLoading ? (
34
+ <Loading />
35
+ ) : (
31
36
  <>
32
37
  <List relaxed selection className="structure-nav">
33
38
  <StructureItemRoot />
@@ -42,11 +47,13 @@ export const StructureNav = ({
42
47
  <StructureNavClassified
43
48
  classifier={classifier}
44
49
  structures={childStructures}
50
+ fnHasCatalogViewProp={fnHasCatalogViewProp}
45
51
  />
46
52
  ) : (
47
53
  <StructureItems
48
54
  structures={childStructures}
49
55
  structuresAliasNameMode={structuresAliasNameMode}
56
+ fnHasCatalogViewProp={fnHasCatalogViewProp}
50
57
  />
51
58
  )}
52
59
  </List>
@@ -67,9 +74,11 @@ export const StructureNav = ({
67
74
 
68
75
  StructureNav.propTypes = {
69
76
  childStructures: PropTypes.array,
77
+ isLoading: PropTypes.bool,
70
78
  parentStructure: PropTypes.object,
71
79
  structuresAliasNameMode: PropTypes.bool,
72
80
  totalChildStructures: PropTypes.number,
81
+ fnHasCatalogViewProp: PropTypes.func,
73
82
  };
74
83
 
75
84
  export default StructureNav;
@@ -11,6 +11,7 @@ export const StructureNavClassified = ({
11
11
  classifier,
12
12
  structures,
13
13
  leafClasses,
14
+ fnHasCatalogViewProp,
14
15
  }) => {
15
16
  const { formatMessage } = useIntl();
16
17
 
@@ -43,7 +44,12 @@ export const StructureNavClassified = ({
43
44
  className: "noselect",
44
45
  }}
45
46
  />
46
- {expanded[label] ? <StructureItems structures={children} /> : null}
47
+ {expanded[label] ? (
48
+ <StructureItems
49
+ structures={children}
50
+ fnHasCatalogViewProp={fnHasCatalogViewProp}
51
+ />
52
+ ) : null}
47
53
  </React.Fragment>
48
54
  ));
49
55
  };
@@ -52,6 +58,7 @@ StructureNavClassified.propTypes = {
52
58
  classifier: PropTypes.string,
53
59
  structures: PropTypes.array,
54
60
  leafClasses: PropTypes.object,
61
+ fnHasCatalogViewProp: PropTypes.func,
55
62
  };
56
63
 
57
64
  export const mapStateToProps = (state) => ({
@@ -7,9 +7,10 @@ import { useIntl } from "react-intl";
7
7
  import { HistoryBackButton } from "@truedat/core/components";
8
8
  import { selectTemplate, selectDomains } from "@truedat/df/routines";
9
9
  import { applyTemplate, validateContent } from "@truedat/df/utils";
10
+ import { apiJson } from "@truedat/core/services/api";
11
+ import SuggestionsWidget from "@truedat/ai/components/suggestions/SuggestionsWidget";
10
12
  import { doStructureNoteAction } from "../routines";
11
13
  import { getLatestDfContent } from "../selectors/getSortedStructureNotes";
12
- import StructureNoteSuggestions from "./StructureNoteSuggestions";
13
14
 
14
15
  const DynamicForm = React.lazy(() =>
15
16
  import("@truedat/df/components/DynamicForm")
@@ -30,7 +31,9 @@ export const StructureNotesEdit = ({
30
31
  latestDfContent,
31
32
  }) => {
32
33
  const [content, setContent] = useState(latestDfContent);
33
- const aiSuggestionsAction = _.prop("_actions.ai_suggestions")(structureNotes);
34
+ const aiSuggestionsUrl = _.prop("_actions.ai_suggestions.href")(
35
+ structureNotes
36
+ );
34
37
 
35
38
  const isModification = !_.isEmpty(latestDfContent);
36
39
 
@@ -75,13 +78,18 @@ export const StructureNotesEdit = ({
75
78
  setContent({ ...content, ...suggestions });
76
79
  };
77
80
 
81
+ const requestAiSuggestion = (callback) => {
82
+ const url = `${aiSuggestionsUrl}?language=${locale}`;
83
+ apiJson(url).then(callback);
84
+ };
85
+
78
86
  return (
79
87
  <Segment attached="bottom">
80
88
  <TemplateLoader />
81
89
 
82
- {aiSuggestionsAction ? (
83
- <StructureNoteSuggestions
84
- aiSuggestionsAction={aiSuggestionsAction}
90
+ {aiSuggestionsUrl ? (
91
+ <SuggestionsWidget
92
+ requestAiSuggestion={requestAiSuggestion}
85
93
  template={template}
86
94
  applySuggestions={applySuggestions}
87
95
  isModification={isModification}
@@ -1,20 +1,29 @@
1
+ import _ from "lodash/fp";
1
2
  import React from "react";
2
3
  import PropTypes from "prop-types";
3
4
  import { useIntl } from "react-intl";
4
5
  import { Input } from "semantic-ui-react";
5
6
 
6
- export const StructureSearch = ({ onChange, loading = false }) => {
7
- const { formatMessage } = useIntl();
8
- const handleChange = (e) => {
9
- onChange(e.target.value);
7
+ // https://stackoverflow.com/a/37312154
8
+ const debounceEventHandler = (...args) => {
9
+ const debounced = _.debounce(...args);
10
+ return (e) => {
11
+ e.persist();
12
+ return debounced(e);
10
13
  };
14
+ };
15
+
16
+ export const StructureSearch = ({ onChange, isLoading = false }) => {
17
+ const { formatMessage } = useIntl();
18
+ const handleChange = (e) => onChange(e.target.value);
19
+
11
20
  return (
12
21
  <Input
13
- onChange={handleChange}
22
+ onChange={debounceEventHandler(500, handleChange)}
14
23
  icon={{ name: "search", link: true }}
15
24
  iconPosition="left"
16
25
  fluid
17
- loading={loading}
26
+ loading={isLoading}
18
27
  placeholder={formatMessage({ id: "structure.search.placeholder" })}
19
28
  />
20
29
  );
@@ -22,7 +31,7 @@ export const StructureSearch = ({ onChange, loading = false }) => {
22
31
 
23
32
  StructureSearch.propTypes = {
24
33
  onChange: PropTypes.func,
25
- loading: PropTypes.bool,
34
+ isLoading: PropTypes.bool,
26
35
  };
27
36
 
28
37
  export default StructureSearch;