@truedat/dd 5.0.5 → 5.1.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## [5.1.0] 2023-02-09
4
+
5
+ ### Changed
6
+
7
+ - [TD-5444] Search system root structures
8
+
9
+ ## [5.0.7] 2023-02-07
10
+
11
+ ### Added
12
+
13
+ - [TD-5479] Show external_ref in grants
14
+
3
15
  ## [5.0.0] 2023-01-24
4
16
 
5
17
  ### Changed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@truedat/dd",
3
- "version": "5.0.5",
3
+ "version": "5.1.0",
4
4
  "description": "Truedat Web Data Dictionary",
5
5
  "sideEffects": false,
6
6
  "jsnext:main": "src/index.js",
@@ -114,5 +114,5 @@
114
114
  "react-dom": ">= 16.8.6 < 17",
115
115
  "semantic-ui-react": ">= 2.0.3 < 2.2"
116
116
  },
117
- "gitHead": "a90adc0fcf336af276a585669065b661e28a8739"
117
+ "gitHead": "0875313aacca475224716822c465a29973c285f3"
118
118
  }
@@ -171,6 +171,14 @@ export const Grant = ({ grant, actions, loading, onStatusChange }) => {
171
171
  </List.Description>
172
172
  </List.Item>
173
173
  )}
174
+ {grant?.external_ref && (
175
+ <List.Item>
176
+ <List.Header>
177
+ {formatMessage({ id: "grant.header.external_ref" })}
178
+ </List.Header>
179
+ <List.Description>{grant?.external_ref}</List.Description>
180
+ </List.Item>
181
+ )}
174
182
  <List.Item>
175
183
  <List.Header>
176
184
  {formatMessage({ id: "grant.header.created_at" })}
@@ -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 { useIntl } from "react-intl";
5
6
  import StructureItemRoot from "./StructureItemRoot";
6
7
  import StructureItem from "./StructureItem";
7
8
  import StructureItems from "./StructureItems";
@@ -14,37 +15,56 @@ export const navClasses = _.flow(
14
15
  );
15
16
  export const hasNavClass = _.flow(navClasses, _.negate(_.isEmpty));
16
17
 
17
- export const StructureNav = ({ childStructures, parentStructure }) => {
18
+ export const StructureNav = ({
19
+ childStructures,
20
+ parentStructure,
21
+ totalChildStructures,
22
+ }) => {
23
+ const { formatMessage } = useIntl();
18
24
  const firstWithClasses = _.find(hasNavClass)(childStructures);
19
25
  const classifier = firstWithClasses
20
26
  ? _.flow(navClasses, _.head)(firstWithClasses)
21
27
  : undefined;
22
28
 
23
29
  return (
24
- <List relaxed selection className="structure-nav">
25
- <StructureItemRoot />
26
- {_.isEmpty(parentStructure) ? null : (
27
- <>
28
- <Divider />
29
- <StructureItem {...parentStructure} />
30
- <Divider />
31
- </>
32
- )}
33
- {classifier ? (
34
- <StructureNavClassified
35
- classifier={classifier}
36
- structures={childStructures}
37
- />
38
- ) : (
39
- <StructureItems structures={childStructures} />
40
- )}
41
- </List>
30
+ <>
31
+ <List relaxed selection className="structure-nav">
32
+ <StructureItemRoot />
33
+ {_.isEmpty(parentStructure) ? null : (
34
+ <>
35
+ <Divider />
36
+ <StructureItem {...parentStructure} />
37
+ <Divider />
38
+ </>
39
+ )}
40
+ {classifier ? (
41
+ <StructureNavClassified
42
+ classifier={classifier}
43
+ structures={childStructures}
44
+ />
45
+ ) : (
46
+ <StructureItems structures={childStructures} />
47
+ )}
48
+ </List>
49
+ {totalChildStructures && totalChildStructures > childStructures.length ? (
50
+ <List>
51
+ <List.Item disabled active={false}>
52
+ <span>
53
+ {`${childStructures.length} ${formatMessage({
54
+ id: "structure.navigation.showing",
55
+ })} ${totalChildStructures}`}
56
+ </span>
57
+ </List.Item>
58
+ </List>
59
+ ) : null}
60
+ </>
42
61
  );
43
62
  };
44
63
 
45
64
  StructureNav.propTypes = {
46
65
  childStructures: PropTypes.array,
47
- parentStructure: PropTypes.object,
66
+ parentStructure: PropTypes.array,
67
+ totalChildStructures: PropTypes.number,
48
68
  };
49
69
 
50
70
  export default StructureNav;
@@ -3,9 +3,9 @@ import PropTypes from "prop-types";
3
3
  import { useIntl } from "react-intl";
4
4
  import { Input } from "semantic-ui-react";
5
5
 
6
- export const StructureSearch = ({ onChange }) => {
6
+ export const StructureSearch = ({ onChange, loading = false }) => {
7
7
  const { formatMessage } = useIntl();
8
- const handleChange = e => {
8
+ const handleChange = (e) => {
9
9
  onChange(e.target.value);
10
10
  };
11
11
  return (
@@ -14,13 +14,15 @@ export const StructureSearch = ({ onChange }) => {
14
14
  icon={{ name: "search", link: true }}
15
15
  iconPosition="left"
16
16
  fluid
17
+ loading={loading}
17
18
  placeholder={formatMessage({ id: "structure.search.placeholder" })}
18
19
  />
19
20
  );
20
21
  };
21
22
 
22
23
  StructureSearch.propTypes = {
23
- onChange: PropTypes.func
24
+ onChange: PropTypes.func,
25
+ loading: PropTypes.bool,
24
26
  };
25
27
 
26
28
  export default StructureSearch;
@@ -0,0 +1,32 @@
1
+ import _ from "lodash/fp";
2
+ import React, { useState } from "react";
3
+ import PropTypes from "prop-types";
4
+ import { useParams } from "react-router-dom";
5
+ import { useStructures } from "../hooks/useStructures";
6
+ import StructureNav from "./StructureNav";
7
+ import StructureSearch from "./StructureSearch";
8
+
9
+ const SystemFilteredNav = ({ delay = 500 }) => {
10
+ const { id: systemId } = useParams();
11
+ const [query, setQuery] = useState();
12
+ const { data, isLoading, count } = useStructures({ systemId, query });
13
+
14
+ return (
15
+ <>
16
+ <StructureSearch
17
+ onChange={_.debounce(delay)(setQuery)}
18
+ loading={isLoading}
19
+ />
20
+ <StructureNav
21
+ childStructures={data || []}
22
+ parentStructure={[]}
23
+ totalChildStructures={count}
24
+ />
25
+ </>
26
+ );
27
+ };
28
+ SystemFilteredNav.propTypes = {
29
+ delay: PropTypes.number,
30
+ };
31
+
32
+ export default SystemFilteredNav;
@@ -4,14 +4,14 @@ import PropTypes from "prop-types";
4
4
  import { connect } from "react-redux";
5
5
  import { Grid, Segment } from "semantic-ui-react";
6
6
  import { getSystem, getSystemTemplate } from "../selectors";
7
- import FilteredNav from "./FilteredNav";
7
+ import SystemFilteredNav from "./SystemFilteredNav";
8
8
  import SystemCrumbs from "./SystemCrumbs";
9
9
  import SystemView from "./SystemView";
10
10
 
11
11
  export const SystemStructures = ({
12
12
  system,
13
13
  systemStructuresLoading,
14
- systemTemplate
14
+ systemTemplate,
15
15
  }) =>
16
16
  !systemStructuresLoading && (
17
17
  <Grid>
@@ -23,7 +23,7 @@ export const SystemStructures = ({
23
23
  <Grid.Row stretched>
24
24
  <Grid.Column width={4}>
25
25
  <Segment>
26
- <FilteredNav parentStructures={[]} />
26
+ <SystemFilteredNav />
27
27
  </Segment>
28
28
  </Grid.Column>
29
29
  <Grid.Column width={12}>
@@ -36,13 +36,13 @@ export const SystemStructures = ({
36
36
  SystemStructures.propTypes = {
37
37
  system: PropTypes.object,
38
38
  systemStructuresLoading: PropTypes.bool,
39
- systemTemplate: PropTypes.object
39
+ systemTemplate: PropTypes.object,
40
40
  };
41
41
 
42
- const mapStateToProps = state => ({
42
+ const mapStateToProps = (state) => ({
43
43
  system: getSystem(state),
44
44
  systemStructuresLoading: _.prop("systemStructuresLoading")(state),
45
- systemTemplate: getSystemTemplate(state)
45
+ systemTemplate: getSystemTemplate(state),
46
46
  });
47
47
 
48
48
  export default connect(mapStateToProps)(SystemStructures);
@@ -38,6 +38,7 @@ describe("<Grant />", () => {
38
38
  end_date: "2019-12-31",
39
39
  inserted_at: "2022-12-12T12:49:09.789722Z",
40
40
  pending_removal: false,
41
+ external_ref: "foo",
41
42
  user: {
42
43
  email: "foo@gmail.com",
43
44
  full_name: "foo user",
@@ -4,7 +4,10 @@ import { StructureNav, hasNavClass, navClasses } from "../StructureNav";
4
4
 
5
5
  const renderOpts = {
6
6
  messages: {
7
- en: { "structure.navigation.home": "Home" },
7
+ en: {
8
+ "structure.navigation.home": "Home",
9
+ "structure.navigation.showing": "from a total of",
10
+ },
8
11
  },
9
12
  };
10
13
 
@@ -24,6 +27,15 @@ describe("<StructureNav />", () => {
24
27
  const { container } = render(<StructureNav {...props} />, renderOpts);
25
28
  expect(container).toMatchSnapshot();
26
29
  });
30
+
31
+ it("matches the latest snapshot with total greater than showed structures", () => {
32
+ const childStructures = [{ id: 1, classes: { foo: "bar" } }];
33
+ const totalChildStructures = 10;
34
+ const props = { childStructures, totalChildStructures };
35
+
36
+ const { container } = render(<StructureNav {...props} />, renderOpts);
37
+ expect(container).toMatchSnapshot();
38
+ });
27
39
  });
28
40
 
29
41
  describe("navClasses", () => {
@@ -0,0 +1,26 @@
1
+ import React from "react";
2
+ import { render } from "@truedat/test/render";
3
+ import SystemFilteredNav from "../SystemFilteredNav";
4
+
5
+ jest.mock("../../hooks/useStructures.js", () => {
6
+ const originalModule = jest.requireActual("../../hooks/useStructures.js");
7
+
8
+ return {
9
+ __esModule: true,
10
+ ...originalModule,
11
+ useStructures: jest.fn(() => ({
12
+ data: [
13
+ { id: 1, name: "bank_capital" },
14
+ { id: 2, name: "consumorenovables" },
15
+ ],
16
+ count: 2,
17
+ })),
18
+ };
19
+ });
20
+
21
+ describe("<SystemFilteredNav />", () => {
22
+ it("matches the latest snapshot", () => {
23
+ const { container } = render(<SystemFilteredNav />, {});
24
+ expect(container).toMatchSnapshot();
25
+ });
26
+ });
@@ -124,6 +124,21 @@ exports[`<Grant /> matches the latest snapshot 1`] = `
124
124
  </h4>
125
125
  </div>
126
126
  </div>
127
+ <div
128
+ class="item"
129
+ role="listitem"
130
+ >
131
+ <div
132
+ class="header"
133
+ >
134
+ External reference
135
+ </div>
136
+ <div
137
+ class="description"
138
+ >
139
+ foo
140
+ </div>
141
+ </div>
127
142
  <div
128
143
  class="item"
129
144
  role="listitem"
@@ -330,6 +345,21 @@ exports[`<Grant /> matches the latest snapshot with cancel_removal action 1`] =
330
345
  </h4>
331
346
  </div>
332
347
  </div>
348
+ <div
349
+ class="item"
350
+ role="listitem"
351
+ >
352
+ <div
353
+ class="header"
354
+ >
355
+ External reference
356
+ </div>
357
+ <div
358
+ class="description"
359
+ >
360
+ foo
361
+ </div>
362
+ </div>
333
363
  <div
334
364
  class="item"
335
365
  role="listitem"
@@ -588,6 +618,21 @@ exports[`<Grant /> matches the latest snapshot with update action 1`] = `
588
618
  </h4>
589
619
  </div>
590
620
  </div>
621
+ <div
622
+ class="item"
623
+ role="listitem"
624
+ >
625
+ <div
626
+ class="header"
627
+ >
628
+ External reference
629
+ </div>
630
+ <div
631
+ class="description"
632
+ >
633
+ foo
634
+ </div>
635
+ </div>
591
636
  <div
592
637
  class="item"
593
638
  role="listitem"
@@ -96,3 +96,65 @@ exports[`<StructureNav /> matches the latest snapshot with classified structures
96
96
  </div>
97
97
  </div>
98
98
  `;
99
+
100
+ exports[`<StructureNav /> matches the latest snapshot with total greater than showed structures 1`] = `
101
+ <div>
102
+ <div
103
+ class="ui selection relaxed list structure-nav"
104
+ role="list"
105
+ >
106
+ <div
107
+ class="item"
108
+ role="listitem"
109
+ >
110
+ <i
111
+ aria-hidden="true"
112
+ class="angle double left icon middle aligned"
113
+ />
114
+ <div
115
+ class="content"
116
+ >
117
+ Home
118
+ </div>
119
+ <div
120
+ class="content"
121
+ >
122
+ <a
123
+ class="description"
124
+ />
125
+ </div>
126
+ </div>
127
+ <div
128
+ class="item"
129
+ role="listitem"
130
+ >
131
+ <i
132
+ aria-hidden="true"
133
+ class="right triangle icon"
134
+ />
135
+ <div
136
+ class="content"
137
+ >
138
+ <div
139
+ class="header noselect"
140
+ >
141
+ bar
142
+ </div>
143
+ </div>
144
+ </div>
145
+ </div>
146
+ <div
147
+ class="ui list"
148
+ role="list"
149
+ >
150
+ <div
151
+ class="disabled item"
152
+ role="listitem"
153
+ >
154
+ <span>
155
+ 1 from a total of 10
156
+ </span>
157
+ </div>
158
+ </div>
159
+ </div>
160
+ `;
@@ -10,6 +10,7 @@ exports[`<StructureSearch/> matches the latest snapshot 1`] = `
10
10
  }
11
11
  }
12
12
  iconPosition="left"
13
+ loading={false}
13
14
  onChange={[Function]}
14
15
  placeholder="structure.search.placeholder"
15
16
  type="text"
@@ -0,0 +1,84 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`<SystemFilteredNav /> matches the latest snapshot 1`] = `
4
+ <div>
5
+ <div
6
+ class="ui fluid left icon input"
7
+ >
8
+ <input
9
+ placeholder="Enter a search..."
10
+ type="text"
11
+ />
12
+ <i
13
+ aria-hidden="true"
14
+ class="search link icon"
15
+ />
16
+ </div>
17
+ <div
18
+ class="ui selection relaxed list structure-nav"
19
+ role="list"
20
+ >
21
+ <div
22
+ class="item"
23
+ role="listitem"
24
+ >
25
+ <i
26
+ aria-hidden="true"
27
+ class="angle double left icon middle aligned"
28
+ />
29
+ <div
30
+ class="content"
31
+ >
32
+ Home
33
+ </div>
34
+ <div
35
+ class="content"
36
+ >
37
+ <a
38
+ class="description"
39
+ />
40
+ </div>
41
+ </div>
42
+ <a
43
+ class="item"
44
+ href="/structures/1"
45
+ role="listitem"
46
+ title="bank_capital"
47
+ >
48
+ <i
49
+ aria-hidden="true"
50
+ class="question circle outline icon middle aligned"
51
+ />
52
+ <div
53
+ class="content"
54
+ >
55
+ <div
56
+ class="description"
57
+ >
58
+ bank_capital
59
+ </div>
60
+ </div>
61
+ </a>
62
+ <a
63
+ class="item"
64
+ href="/structures/2"
65
+ role="listitem"
66
+ title="consumorenovables"
67
+ >
68
+ <i
69
+ aria-hidden="true"
70
+ class="question circle outline icon middle aligned"
71
+ />
72
+ <div
73
+ class="content"
74
+ >
75
+ <div
76
+ class="description"
77
+ >
78
+ consumorenovables
79
+ </div>
80
+ </div>
81
+ </a>
82
+ </div>
83
+ </div>
84
+ `;
@@ -16,9 +16,7 @@ exports[`<SystemStructures /> matches the latest snapshot 1`] = `
16
16
  width={4}
17
17
  >
18
18
  <Segment>
19
- <Connect(FilteredNav)
20
- parentStructures={Array []}
21
- />
19
+ <SystemFilteredNav />
22
20
  </Segment>
23
21
  </GridColumn>
24
22
  <GridColumn
@@ -0,0 +1,41 @@
1
+ import _ from "lodash/fp";
2
+ import { compile } from "path-to-regexp";
3
+ import useSWR from "swr";
4
+ import { apiJson } from "@truedat/core/services/api";
5
+ import { API_SYSTEM_STRUCTURES } from "../api";
6
+
7
+ const toApiPath = compile(API_SYSTEM_STRUCTURES);
8
+
9
+ const allowedQueryParams = ["query"];
10
+
11
+ const asQueryParams = (queryParams) => {
12
+ const stringifyQueryParams = _.flow(
13
+ _.pick(allowedQueryParams),
14
+ _.entries,
15
+ _.map(([key, value]) =>
16
+ _.isEmpty(value) ? "" : `${encodeURI(key)}=${encodeURI(value)}`
17
+ ),
18
+ _.join("&")
19
+ )(queryParams);
20
+ return _.isEmpty(stringifyQueryParams) ? "" : `?${stringifyQueryParams}`;
21
+ };
22
+
23
+ export const useStructures = (args) => {
24
+ console.log(args);
25
+ const url = toApiPath({ id: args.systemId });
26
+ const requestData = () => apiJson(`${url}${asQueryParams(args)}`);
27
+ const options = { keepPreviousData: true };
28
+
29
+ const { data, isLoading, error } = useSWR(
30
+ { url, args },
31
+ requestData,
32
+ options
33
+ );
34
+
35
+ return {
36
+ data: data?.data?.data,
37
+ isLoading,
38
+ error,
39
+ count: parseInt(data?.headers["x-total-count"]),
40
+ };
41
+ };
@@ -52,15 +52,16 @@ export default {
52
52
  "grant.cell.end_date.null": "No expiration set",
53
53
  "grant.cell.end_date.pending_removal": "Grant pending removal",
54
54
  "grant.cell.end_date.same_user": "It's you",
55
- "grant.header": "Grants",
55
+ "grant.header.created_at": "Grant created",
56
56
  "grant.header.data_structure": "Structure",
57
+ "grant.header.details": "Details",
58
+ "grant.header.end_date": "Expires",
59
+ "grant.header.external_ref": "External reference",
57
60
  "grant.header.id": "Id",
58
- "grant.header.user": "Account",
59
- "grant.header.user_name": "User name",
60
- "grant.header.created_at": "Grant created",
61
61
  "grant.header.start_date": "Access granted",
62
- "grant.header.end_date": "Expires",
63
- "grant.header.details": "Details",
62
+ "grant.header.user_name": "User name",
63
+ "grant.header.user": "Account",
64
+ "grant.header": "Grants",
64
65
  "grant.updated_at": "Last changed",
65
66
  "grantApprovalRule.action": "Action",
66
67
  "grantApprovalRule.action.approve": "Approve",
@@ -329,6 +330,7 @@ export default {
329
330
  "structure.metadata._protected": "Protected metadata",
330
331
  "structure.name": "Structure",
331
332
  "structure.navigation.home": "Home",
333
+ "structure.navigation.showing": "from a total of",
332
334
  "structure.no_description": "No definition",
333
335
  "structure.notes.actions.deleted": "Delete",
334
336
  "structure.notes.actions.deleted.confirmation.content":
@@ -56,15 +56,16 @@ export default {
56
56
  "grant.cell.end_date.null": "Sin fecha de expiración",
57
57
  "grant.cell.end_date.pending_removal": "Solicitada expiración del acceso",
58
58
  "grant.cell.end_date.same_user": "Eres tú",
59
- "grant.header": "Acceso",
59
+ "grant.header.created_at": "Acceso creado",
60
60
  "grant.header.data_structure": "Estructura",
61
+ "grant.header.details": "Detalles",
62
+ "grant.header.end_date": "Expira",
63
+ "grant.header.external_ref": "Referencia externa",
61
64
  "grant.header.id": "Id",
62
- "grant.header.user": "Cuenta",
63
- "grant.header.user_name": "Usuario",
64
- "grant.header.created_at": "Acceso creado",
65
65
  "grant.header.start_date": "Acceso desde",
66
- "grant.header.end_date": "Expira",
67
- "grant.header.details": "Detalles",
66
+ "grant.header.user_name": "Usuario",
67
+ "grant.header.user": "Cuenta",
68
+ "grant.header": "Acceso",
68
69
  "grant.updated_at": "Actualizada",
69
70
  "grantApprovalRules.header": "Reglas de aprobación",
70
71
  "grantApprovalRules.subheader":
@@ -338,6 +339,7 @@ export default {
338
339
  "structure.metadata._protected": "Metadata protegida",
339
340
  "structure.name": "Estructura",
340
341
  "structure.navigation.home": "Inicio",
342
+ "structure.navigation.showing": "mostrados de",
341
343
  "structure.no_description": "Sin descripción",
342
344
  "structure.notes.actions.deleted": "Eliminar",
343
345
  "structure.notes.actions.deleted.confirmation.content":