@truedat/core 4.43.4 → 4.44.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
+ ## [4.44.0] 2022-05-10
4
+
5
+ ### Added
6
+
7
+ - [TD-4723] New `DomainSelector` which loads domains using GraphQL query
8
+
9
+ ## [4.43.6] 2022-05-05
10
+
11
+ ### Changed
12
+
13
+ - [TD-4586] Simplify accepted CSV message and include link to 'my loads'.
14
+
3
15
  ## [4.43.4] 2022-05-05
4
16
 
5
17
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@truedat/core",
3
- "version": "4.43.4",
3
+ "version": "4.44.0",
4
4
  "description": "Truedat Web Core",
5
5
  "sideEffects": false,
6
6
  "jsnext:main": "src/index.js",
@@ -32,7 +32,7 @@
32
32
  "@babel/plugin-transform-modules-commonjs": "^7.15.0",
33
33
  "@babel/preset-env": "^7.15.0",
34
34
  "@babel/preset-react": "^7.14.5",
35
- "@truedat/test": "4.43.4",
35
+ "@truedat/test": "4.44.0",
36
36
  "babel-jest": "^27.0.6",
37
37
  "babel-plugin-dynamic-import-node": "^2.3.3",
38
38
  "babel-plugin-lodash": "^3.3.4",
@@ -106,5 +106,5 @@
106
106
  "react-dom": ">= 16.8.6 < 17",
107
107
  "semantic-ui-react": ">= 0.88.2 < 2.1"
108
108
  },
109
- "gitHead": "7f0463a65a0b8137f1598487bf26fefa52e5fa59"
109
+ "gitHead": "55fd4a8be70ca0d1bd4f3cb7b5eaf073b0350029"
110
110
  }
@@ -0,0 +1,11 @@
1
+ import { gql } from "@apollo/client";
2
+
3
+ export const DOMAINS_QUERY = gql`
4
+ query DomainsQuery($action: String!) {
5
+ domains(action: $action) {
6
+ id
7
+ name
8
+ parentId
9
+ }
10
+ }
11
+ `;
@@ -45,6 +45,7 @@ export const Alert = ({
45
45
  messages,
46
46
  downloadable = false,
47
47
  initialMaxListItems = Infinity,
48
+ anchor,
48
49
  },
49
50
  dismissAlert,
50
51
  }) => {
@@ -81,7 +82,16 @@ export const Alert = ({
81
82
  <Message
82
83
  error={error}
83
84
  header={formatMessage({ id: header, defaultMessage: header }, fields)}
84
- content={message_content}
85
+ content={
86
+ <>
87
+ <p>{message_content}</p>
88
+ {!anchor ? null : (
89
+ <a href={anchor.reference}>
90
+ {formatMessage({ id: "sidemenu.structures_upload_events" })}
91
+ </a>
92
+ )}
93
+ </>
94
+ }
85
95
  list={list.length >= maxListItems ? list.slice(0, maxListItems) : list}
86
96
  icon={icon}
87
97
  color={color}
@@ -14,8 +14,8 @@ import Submenu from "./Submenu";
14
14
  const items = [{ name: "structures", routes: [STRUCTURES, SYSTEMS] }];
15
15
 
16
16
  const adminItems = [
17
- { name: "structure_types", routes: [STRUCTURE_TYPES] },
18
- { name: "structure_tags", routes: [STRUCTURE_TAGS] },
17
+ { name: "structureTypes", routes: [STRUCTURE_TYPES] },
18
+ { name: "structureTags", routes: [STRUCTURE_TAGS] },
19
19
  ];
20
20
 
21
21
  const structureNoteItems = [
@@ -0,0 +1,42 @@
1
+ import _ from "lodash/fp";
2
+ import React from "react";
3
+ import PropTypes from "prop-types";
4
+ import { useIntl } from "react-intl";
5
+ import { useQuery } from "@apollo/client";
6
+ import { accentInsensitivePathOrder } from "../services/sort";
7
+ import { stratify, flatten } from "../services/tree";
8
+ import { DOMAINS_QUERY } from "../api/queries";
9
+ import TreeSelector from "./TreeSelector";
10
+
11
+ export const DomainSelector = ({ action, onLoad, value, ...props }) => {
12
+ const { formatMessage } = useIntl();
13
+ const { loading, error, data } = useQuery(DOMAINS_QUERY, {
14
+ variables: { action },
15
+ onCompleted: onLoad,
16
+ });
17
+ if (error) return null;
18
+ if (loading) return null;
19
+ const options = _.flow(
20
+ _.propOr([], "domains"),
21
+ _.sortBy(accentInsensitivePathOrder("name")),
22
+ stratify({}),
23
+ flatten
24
+ )(data);
25
+
26
+ return (
27
+ <TreeSelector
28
+ options={options}
29
+ placeholder={formatMessage({ id: "domain.multiple.placeholder" })}
30
+ value={_.map(_.toString)(value)}
31
+ {...props}
32
+ />
33
+ );
34
+ };
35
+
36
+ DomainSelector.propTypes = {
37
+ action: PropTypes.string,
38
+ onLoad: PropTypes.func,
39
+ value: PropTypes.array,
40
+ };
41
+
42
+ export default DomainSelector;
@@ -43,7 +43,7 @@ export const DropdownMenuItem = ({
43
43
  };
44
44
 
45
45
  DropdownMenuItem.propTypes = {
46
- id: PropTypes.number,
46
+ id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
47
47
  canOpen: PropTypes.bool,
48
48
  check: PropTypes.bool,
49
49
  handleOpen: PropTypes.func,
@@ -0,0 +1,161 @@
1
+ import _ from "lodash/fp";
2
+ import React, { useState, useEffect } from "react";
3
+ import { Dropdown, Form, Icon, Input, Label } from "semantic-ui-react";
4
+ import PropTypes from "prop-types";
5
+ import { lowerDeburr } from "../services/sort";
6
+ import { recursiveMatch, descendents, childIds } from "../services/tree";
7
+ import DropdownMenuItem from "./DropdownMenuItem";
8
+
9
+ export const match =
10
+ (query) =>
11
+ ({ name }) =>
12
+ _.contains(query)(lowerDeburr(name));
13
+ export const matchAny = recursiveMatch(match);
14
+
15
+ export const TreeSelector = ({
16
+ options,
17
+ error,
18
+ label,
19
+ name,
20
+ onBlur,
21
+ onChange,
22
+ required = false,
23
+ placeholder,
24
+ value: initialValue = [],
25
+ }) => {
26
+ const [value, setValue] = useState(initialValue);
27
+ const [query, setQuery] = useState();
28
+ const [open, setOpen] = useState([]);
29
+ const [displayed, setDisplayed] = useState([]);
30
+
31
+ useEffect(() => {
32
+ const ids = childIds(open)(options);
33
+ setDisplayed((displayed) => _.union(displayed)(ids));
34
+ }, [options, open, value]);
35
+
36
+ const handleOpen = (id) => {
37
+ const option = _.find({ id })(options);
38
+ const isOpen = _.contains(id)(open);
39
+ const childIds = _.map("id")(option.children);
40
+ const descendentIds = descendents(option);
41
+
42
+ if (isOpen) {
43
+ setOpen(_.without([id, ...descendentIds])(open));
44
+ setDisplayed(_.without(descendentIds)(displayed));
45
+ } else {
46
+ setOpen(_.union([id])(open));
47
+ setDisplayed(_.union(childIds)(displayed));
48
+ }
49
+ };
50
+
51
+ const displayAll = () => {
52
+ const ids = _.map("id")(options);
53
+ setOpen(ids);
54
+ setDisplayed(ids);
55
+ };
56
+
57
+ const handleSearch = (e, { value }) => {
58
+ e.preventDefault();
59
+ setQuery(lowerDeburr(value));
60
+ if (!_.isEmpty(value)) {
61
+ displayAll();
62
+ }
63
+ };
64
+
65
+ const handleClick = (e, id) => {
66
+ const ids = _.includes(id)(value)
67
+ ? _.without([id])(value)
68
+ : _.union([id])(value);
69
+ setValue(ids);
70
+ onChange && onChange(e, { value: ids });
71
+ };
72
+
73
+ const filterSearch = query ? _.filter(matchAny(query)) : _.identity;
74
+
75
+ const filterDisplayed = _.filter(
76
+ ({ id, level }) => level === 0 || _.includes(id)(displayed)
77
+ );
78
+
79
+ const trigger = _.isEmpty(value) ? (
80
+ <label>{placeholder}</label>
81
+ ) : (
82
+ _.map((id) => (
83
+ <Label key={id}>
84
+ {_.flow(_.find({ id }), _.prop("name"))(options)}
85
+ <Icon
86
+ name="delete"
87
+ onClick={(e) => {
88
+ e.preventDefault();
89
+ e.stopPropagation();
90
+ handleClick(e, id);
91
+ }}
92
+ />
93
+ </Label>
94
+ ))(value)
95
+ );
96
+
97
+ const items = _.flow(
98
+ filterSearch,
99
+ filterDisplayed,
100
+ _.map((option) => (
101
+ <DropdownMenuItem
102
+ key={option?.id}
103
+ check={false}
104
+ handleOpen={handleOpen}
105
+ handleClick={handleClick}
106
+ open={_.contains(option.id)(open)}
107
+ canOpen={!_.isEmpty(option.children)}
108
+ selected={_.contains(option.id)(value)}
109
+ {...option}
110
+ />
111
+ ))
112
+ )(options);
113
+
114
+ return (
115
+ <Form.Dropdown
116
+ error={error}
117
+ floating
118
+ label={label}
119
+ name={name}
120
+ onBlur={onBlur}
121
+ upward={false}
122
+ required={required}
123
+ trigger={trigger}
124
+ multiple
125
+ value={value}
126
+ >
127
+ <Dropdown.Menu>
128
+ <Input
129
+ icon="search"
130
+ iconPosition="left"
131
+ className="search"
132
+ onKeyDown={(e) => {
133
+ if (e.key === " ") {
134
+ e.stopPropagation();
135
+ }
136
+ }}
137
+ onChange={handleSearch}
138
+ onClick={(e) => {
139
+ e.preventDefault();
140
+ e.stopPropagation();
141
+ }}
142
+ />
143
+ <Dropdown.Menu scrolling>{items}</Dropdown.Menu>
144
+ </Dropdown.Menu>
145
+ </Form.Dropdown>
146
+ );
147
+ };
148
+
149
+ TreeSelector.propTypes = {
150
+ error: PropTypes.bool,
151
+ label: PropTypes.string,
152
+ name: PropTypes.string,
153
+ onBlur: PropTypes.func,
154
+ onChange: PropTypes.func,
155
+ options: PropTypes.array,
156
+ placeholder: PropTypes.string,
157
+ required: PropTypes.bool,
158
+ value: PropTypes.array,
159
+ };
160
+
161
+ export default TreeSelector;
@@ -27,7 +27,7 @@ describe("<Alert />", () => {
27
27
  const wrapper = shallow(<Alert {...props} />);
28
28
  const message = wrapper.find("Message");
29
29
  expect(message).toHaveLength(1);
30
- expect(message.prop("content")).toEqual("content.message.id");
30
+ expect(wrapper.text().includes(message.prop("content")));
31
31
  expect(message.prop("header")).toEqual("header.message.id");
32
32
  });
33
33
 
@@ -1,15 +1,21 @@
1
1
  import React from "react";
2
- import { shallow } from "enzyme";
2
+ import { render } from "@truedat/test/render";
3
3
  import { CatalogMenu } from "../CatalogMenu";
4
4
 
5
5
  jest.mock("../../hooks", () => ({
6
- useActiveRoutes: jest.fn(() => true),
6
+ ...jest.requireActual("../../hooks"),
7
7
  useAuthorized: jest.fn(() => true),
8
8
  }));
9
9
 
10
+ const renderOpts = {
11
+ messages: {
12
+ en: {},
13
+ },
14
+ };
15
+
10
16
  describe("<CatalogMenu />", () => {
11
17
  it("matches the latest snapshot", () => {
12
- const wrapper = shallow(<CatalogMenu />);
13
- expect(wrapper).toMatchSnapshot();
18
+ const { container } = render(<CatalogMenu />, renderOpts);
19
+ expect(container).toMatchSnapshot();
14
20
  });
15
21
  });
@@ -0,0 +1,59 @@
1
+ import React from "react";
2
+ import { waitFor } from "@testing-library/react";
3
+ import userEvent from "@testing-library/user-event";
4
+ import { render } from "@truedat/test/render";
5
+ import { DOMAINS_QUERY } from "../../api/queries";
6
+ import DomainSelector from "../DomainSelector";
7
+
8
+ const action = "manageStructureTags";
9
+
10
+ const domainsMock = {
11
+ request: { query: DOMAINS_QUERY, variables: { action } },
12
+ result: {
13
+ data: {
14
+ domains: [
15
+ { __typename: "Domain", id: "1", name: "foo", parentId: null },
16
+ { __typename: "Domain", id: "2", name: "bar", parentId: "1" },
17
+ { __typename: "Domain", id: "3", name: "baz", parentId: "2" },
18
+ { __typename: "Domain", id: "4", name: "xyzzy", parentId: "99" },
19
+ ],
20
+ },
21
+ },
22
+ };
23
+ const messages = {
24
+ en: {
25
+ "domain.multiple.placeholder": "Select domains",
26
+ },
27
+ };
28
+
29
+ const renderOpts = { mocks: [domainsMock], messages };
30
+
31
+ describe("<DomainSelector />", () => {
32
+ it("matches latest snapshot", () => {
33
+ const props = { action, onChange: jest.fn() };
34
+ const { container } = render(<DomainSelector {...props} />, renderOpts);
35
+ expect(container).toMatchSnapshot();
36
+ });
37
+
38
+ it("calls onChange with selected values", async () => {
39
+ const props = { action, onChange: jest.fn() };
40
+ const { getByText, getByRole } = render(
41
+ <DomainSelector {...props} />,
42
+ renderOpts
43
+ );
44
+
45
+ await waitFor(() => {
46
+ expect(getByText("Select domains")).toBeTruthy();
47
+ });
48
+
49
+ userEvent.click(getByText("Select domains"));
50
+
51
+ await waitFor(() => {
52
+ expect(getByRole("option", { name: /foo/i })).toBeTruthy();
53
+ });
54
+
55
+ userEvent.click(getByRole("option", { name: /foo/i }));
56
+ expect(props.onChange.mock.calls.length).toBe(1);
57
+ expect(props.onChange.mock.calls[0][1]).toEqual({ value: ["1"] });
58
+ });
59
+ });
@@ -0,0 +1,38 @@
1
+ import React from "react";
2
+ import { waitFor } from "@testing-library/react";
3
+ import userEvent from "@testing-library/user-event";
4
+ import { render } from "@truedat/test/render";
5
+ import TreeSelector from "../TreeSelector";
6
+
7
+ const baz = { id: "3", level: 2, name: "baz", children: [] };
8
+ const bar = { id: "2", level: 1, name: "bar", children: [baz] };
9
+ const foo = { id: "1", level: 0, name: "foo", children: [bar] };
10
+ const options = [foo, bar, baz];
11
+
12
+ const renderOpts = {};
13
+ const onChange = jest.fn();
14
+ const props = { options, placeholder: "Select a domain", onChange };
15
+
16
+ describe("<TreeSelector />", () => {
17
+ it("matches latest snapshot", () => {
18
+ const { container } = render(<TreeSelector {...props} />, renderOpts);
19
+ expect(container).toMatchSnapshot();
20
+ });
21
+
22
+ it("calls onChange with selected values", async () => {
23
+ const { getByText, getByRole } = render(
24
+ <TreeSelector {...props} />,
25
+ renderOpts
26
+ );
27
+
28
+ userEvent.click(getByText("Select a domain"));
29
+
30
+ await waitFor(() => {
31
+ expect(getByRole("option", { name: /foo/i })).toBeTruthy();
32
+ });
33
+
34
+ userEvent.click(getByRole("option", { name: /foo/i }));
35
+ expect(onChange.mock.calls.length).toBe(1);
36
+ expect(onChange.mock.calls[0][1]).toEqual({ value: ["1"] });
37
+ });
38
+ });
@@ -3,7 +3,13 @@
3
3
  exports[`<Alert /> matches the latest snapshot 1`] = `
4
4
  <Fragment>
5
5
  <Message
6
- content="content.message.id"
6
+ content={
7
+ <React.Fragment>
8
+ <p>
9
+ content.message.id
10
+ </p>
11
+ </React.Fragment>
12
+ }
7
13
  error={true}
8
14
  header="header.message.id"
9
15
  icon="warning"
@@ -1,43 +1,101 @@
1
1
  // Jest Snapshot v1, https://goo.gl/fbAQLP
2
2
 
3
3
  exports[`<CatalogMenu /> matches the latest snapshot 1`] = `
4
- <Submenu
5
- icon="block layout"
6
- items={
7
- Array [
8
- Object {
9
- "name": "structures",
10
- "routes": Array [
11
- "/structures",
12
- "/systems",
13
- ],
14
- },
15
- Object {
16
- "name": "structure_types",
17
- "routes": Array [
18
- "/structure_types",
19
- ],
20
- },
21
- Object {
22
- "name": "structure_tags",
23
- "routes": Array [
24
- "/structure_tags",
25
- ],
26
- },
27
- Object {
28
- "name": "pending_structure_notes",
29
- "routes": Array [
30
- "/structure_notes",
31
- ],
32
- },
33
- Object {
34
- "name": "structures_upload_events",
35
- "routes": Array [
36
- "/bulk_update_template_content_events",
37
- ],
38
- },
39
- ]
40
- }
41
- name="catalog"
42
- />
4
+ <div>
5
+ <div>
6
+ <div
7
+ aria-expanded="false"
8
+ class="ui item dropdown"
9
+ role="listbox"
10
+ tabindex="0"
11
+ >
12
+ <a
13
+ class="ui"
14
+ href="/structures"
15
+ >
16
+ <i
17
+ aria-hidden="true"
18
+ class="block layout large icon"
19
+ />
20
+ </a>
21
+ <div
22
+ class="menu transition"
23
+ >
24
+ <div
25
+ class="header selectable"
26
+ >
27
+ catalog
28
+ </div>
29
+ <div
30
+ class="divider"
31
+ />
32
+ <a
33
+ aria-checked="false"
34
+ class="item"
35
+ href="/structures"
36
+ name="structures"
37
+ role="option"
38
+ >
39
+ <span
40
+ class="text"
41
+ >
42
+ structures
43
+ </span>
44
+ </a>
45
+ <a
46
+ aria-checked="false"
47
+ class="item"
48
+ href="/structureTypes"
49
+ name="structureTypes"
50
+ role="option"
51
+ >
52
+ <span
53
+ class="text"
54
+ >
55
+ structureTypes
56
+ </span>
57
+ </a>
58
+ <a
59
+ aria-checked="false"
60
+ class="item"
61
+ href="/structureTags"
62
+ name="structureTags"
63
+ role="option"
64
+ >
65
+ <span
66
+ class="text"
67
+ >
68
+ structureTags
69
+ </span>
70
+ </a>
71
+ <a
72
+ aria-checked="false"
73
+ class="item"
74
+ href="/structureNotes"
75
+ name="pending_structure_notes"
76
+ role="option"
77
+ >
78
+ <span
79
+ class="text"
80
+ >
81
+ pending_structure_notes
82
+ </span>
83
+ </a>
84
+ <a
85
+ aria-checked="false"
86
+ class="item"
87
+ href="/bulk_update_template_content_events"
88
+ name="structures_upload_events"
89
+ role="option"
90
+ >
91
+ <span
92
+ class="text"
93
+ >
94
+ structures_upload_events
95
+ </span>
96
+ </a>
97
+ </div>
98
+ </div>
99
+ </div>
100
+ </div>
43
101
  `;
@@ -0,0 +1,3 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`<DomainSelector /> matches latest snapshot 1`] = `<div />`;
@@ -0,0 +1,59 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`<TreeSelector /> matches latest snapshot 1`] = `
4
+ <div>
5
+ <div
6
+ class="field"
7
+ >
8
+ <div
9
+ aria-expanded="false"
10
+ aria-multiselectable="true"
11
+ class="ui floating multiple dropdown"
12
+ role="listbox"
13
+ tabindex="0"
14
+ >
15
+ <label>
16
+ Select a domain
17
+ </label>
18
+ <i
19
+ aria-hidden="true"
20
+ class="dropdown icon"
21
+ />
22
+ <div
23
+ class="menu transition"
24
+ >
25
+ <div
26
+ class="ui left icon input search"
27
+ >
28
+ <input
29
+ type="text"
30
+ />
31
+ <i
32
+ aria-hidden="true"
33
+ class="search icon"
34
+ />
35
+ </div>
36
+ <div
37
+ class="scrolling menu transition"
38
+ >
39
+ <div
40
+ aria-selected="false"
41
+ class="item"
42
+ role="option"
43
+ >
44
+ <div
45
+ style="margin-left: 0px;"
46
+ >
47
+ <i
48
+ aria-hidden="true"
49
+ class="plus icon"
50
+ />
51
+ foo
52
+ </div>
53
+ </div>
54
+ </div>
55
+ </div>
56
+ </div>
57
+ </div>
58
+ </div>
59
+ `;
@@ -12,6 +12,7 @@ import DashboardMenu from "./DashboardMenu";
12
12
  import DateFilter from "./DateFilter";
13
13
  import DateRangeFilter from "./DateRangeFilter";
14
14
  import DateTime from "./DateTime";
15
+ import DomainSelector from "./DomainSelector";
15
16
  import DropdownMenuItem from "./DropdownMenuItem";
16
17
  import ErrorBoundary from "./ErrorBoundary";
17
18
  import FiltersLoader from "./FiltersLoader";
@@ -36,6 +37,7 @@ import SidebarToggle from "./SidebarToggle";
36
37
  import SideMenu from "./SideMenu";
37
38
  import Submenu from "./Submenu";
38
39
  import TaxonomyMenu from "./TaxonomyMenu";
40
+ import TreeSelector from "./TreeSelector";
39
41
  import Unauthorized from "./Unauthorized";
40
42
  import UploadModal from "./UploadModal";
41
43
 
@@ -54,6 +56,7 @@ export {
54
56
  DateFilter,
55
57
  DateRangeFilter,
56
58
  DateTime,
59
+ DomainSelector,
57
60
  DropdownMenuItem,
58
61
  ErrorBoundary,
59
62
  FiltersLoader,
@@ -78,6 +81,7 @@ export {
78
81
  SideMenu,
79
82
  Submenu,
80
83
  TaxonomyMenu,
84
+ TreeSelector,
81
85
  Unauthorized,
82
- UploadModal
86
+ UploadModal,
83
87
  };
@@ -92,8 +92,8 @@ export default {
92
92
  "sidemenu.rules": "Quality Rules",
93
93
  "sidemenu.search": "Search",
94
94
  "sidemenu.sources": "Sources",
95
- "sidemenu.structure_tags": "Structure Tags",
96
- "sidemenu.structure_types": "Structure Types",
95
+ "sidemenu.structureTags": "Structure Tags",
96
+ "sidemenu.structureTypes": "Structure Types",
97
97
  "sidemenu.structures": "Structures",
98
98
  "sidemenu.structures_upload_events": "My loads",
99
99
  "sidemenu.subscriptions": "Subscriptions",
@@ -95,8 +95,8 @@ export default {
95
95
  "sidemenu.rules": "Reglas",
96
96
  "sidemenu.search": "Búsqueda",
97
97
  "sidemenu.sources": "Orígenes",
98
- "sidemenu.structure_tags": "Etiquetas de estructura",
99
- "sidemenu.structure_types": "Tipos de estructura",
98
+ "sidemenu.structureTags": "Etiquetas de estructura",
99
+ "sidemenu.structureTypes": "Tipos de estructura",
100
100
  "sidemenu.structures": "Estructuras",
101
101
  "sidemenu.structures_upload_events": "Mis cargas",
102
102
  "sidemenu.subscriptions": "Suscripciones",
package/src/routes.js CHANGED
@@ -1,9 +1,7 @@
1
1
  import _ from "lodash/fp";
2
2
  import { compile } from "path-to-regexp";
3
3
 
4
- export const GRANT_REQUEST_APPROVALS = "/grant_request_approvals";
5
4
  export const CALLBACK = "/callback";
6
- export const CONCEPT_VERSION = "/concepts/:business_concept_id/versions/:id";
7
5
  export const CONCEPTS = "/concepts";
8
6
  export const CONCEPTS_BULK_UPDATE = "/concepts/bulk_update";
9
7
  export const CONCEPTS_NEW = "/concepts/new";
@@ -17,17 +15,17 @@ export const CONCEPT_LINKS_CONCEPTS =
17
15
  "/concepts/:business_concept_id/versions/:id/links/concepts";
18
16
  export const CONCEPT_LINKS_CONCEPTS_NEW =
19
17
  "/concepts/:business_concept_id/versions/:id/links/concepts/new";
18
+ export const CONCEPT_LINKS_IMPLEMENTATIONS =
19
+ "/concepts/:business_concept_id/versions/:id/links/implementations";
20
20
  export const CONCEPT_LINKS_STRUCTURES =
21
21
  "/concepts/:business_concept_id/versions/:id/links/structures";
22
22
  export const CONCEPT_LINKS_STRUCTURES_NEW =
23
23
  "/concepts/:business_concept_id/versions/:id/links/structures/new";
24
-
25
- export const CONCEPT_LINKS_IMPLEMENTATIONS =
26
- "/concepts/:business_concept_id/versions/:id/links/implementations";
27
24
  export const CONCEPT_RULES =
28
25
  "/concepts/:business_concept_id/versions/:id/rules";
29
26
  export const CONCEPT_RULES_NEW =
30
27
  "/concepts/:business_concept_id/versions/:id/rules/new";
28
+ export const CONCEPT_VERSION = "/concepts/:business_concept_id/versions/:id";
31
29
  export const CONFIGURATION = "/configurations/:external_id";
32
30
  export const CONFIGURATIONS = "/configurations";
33
31
  export const CONFIGURATION_CREATE = "/configurations/new";
@@ -44,11 +42,11 @@ export const DOMAIN_MEMBERS = "/domains/:id/members";
44
42
  export const DOMAIN_MEMBERS_NEW = "/domains/:id/members/new";
45
43
  export const DOMAIN_NEW = "/domains/:id/new";
46
44
  export const EXECUTION_GROUP = "/executionGroups/:id";
47
- export const GRANT_REQUESTS = "/grant_requests";
48
- export const GRANT_REQUEST = "/grant_requests/:id";
49
-
50
45
  export const GRANTS = "/grants";
51
46
  export const GRANTS_REQUESTS_CHECKOUT = "/grants_requests/checkout";
47
+ export const GRANT_REQUEST = "/grant_requests/:id";
48
+ export const GRANT_REQUESTS = "/grant_requests";
49
+ export const GRANT_REQUEST_APPROVALS = "/grant_request_approvals";
52
50
  export const GRAPH = "/graphs/:id";
53
51
  export const GRAPHS = "/graphs";
54
52
  export const GROUP = "/groups/:id";
@@ -83,20 +81,19 @@ export const JOB = "/jobs/:id";
83
81
  export const JOBS = "/jobs";
84
82
  export const LINEAGE_EVENTS = "/lineage_events";
85
83
  export const LOGIN = "/login";
86
- export const MY_GRANT_REQUESTS = "/my_grant_requests";
87
84
  export const MY_GRANTS = "/my_grants";
85
+ export const MY_GRANT_REQUESTS = "/my_grant_requests";
88
86
  export const PASSWORD = "/password";
89
- export const PENDING_STRUCTURE_NOTES = "/structure_notes";
87
+ export const PENDING_STRUCTURE_NOTES = "/structureNotes";
90
88
  export const PROFILE_EXECUTION =
91
89
  "/profileGroups/:group_id/profileExecutions/:id";
92
90
  export const PROFILE_GROUP = "/profileGroups/:id";
93
91
  export const QUALITY_DASHBOARD = "/quality_dashboard";
94
-
95
- export const REMEDIATION_PLAN = "/rule_results/:rule_result_id/remediation";
96
92
  export const REMEDIATION_EDIT =
97
- "/rules/:id/implementations/:implementation_id/ruleResults/:rule_result_id/remediation/edit";
93
+ "/rules/:id/implementations/:implementation_id/results/:rule_result_id/remediation/edit";
98
94
  export const REMEDIATION_NEW =
99
- "/rules/:id/implementations/:implementation_id/ruleResults/:rule_result_id/remediation/new";
95
+ "/rules/:id/implementations/:implementation_id/results/:rule_result_id/remediation/new";
96
+ export const REMEDIATION_PLAN = "/rule_results/:rule_result_id/remediation";
100
97
  export const ROLE = "/roles/:id";
101
98
  export const ROLES = "/roles";
102
99
  export const ROLES_NEW = "/roles/new";
@@ -107,20 +104,22 @@ export const RULE_EVENTS = "/rules/:id/events";
107
104
  export const RULE_IMPLEMENTATION =
108
105
  "/rules/:id/implementations/:implementation_id(\\d+)";
109
106
  export const RULE_IMPLEMENTATIONS = "/rules/:id/implementations";
110
- export const RULE_IMPLEMENTATION_EVENTS =
111
- "/rules/:id/implementations/:implementation_id/events";
112
- export const RULE_IMPLEMENTATION_NEW = "/rules/:id/implementations/new";
113
- export const RULE_IMPLEMENTATION_EDIT =
114
- "/rules/:id/implementations/:implementation_id/edit";
115
107
  export const RULE_IMPLEMENTATION_CLONE =
116
108
  "/rules/:id/implementations/:implementation_id/clone";
109
+ export const RULE_IMPLEMENTATION_EDIT =
110
+ "/rules/:id/implementations/:implementation_id/edit";
111
+ export const RULE_IMPLEMENTATION_EVENTS =
112
+ "/rules/:id/implementations/:implementation_id/events";
117
113
  export const RULE_IMPLEMENTATION_MOVE =
118
114
  "/rules/:id/implementations/:implementation_id/move";
115
+ export const RULE_IMPLEMENTATION_NEW = "/rules/:id/implementations/new";
119
116
  export const RULE_IMPLEMENTATION_RESULT_DETAILS =
120
117
  "/rules/:id/implementations/:implementation_id/results/:rule_result_id";
118
+ export const RULE_IMPLEMENTATION_RESULT_DETAILS_REMEDIATION_PLAN =
119
+ "/rules/:id/implementations/:implementation_id/results/:rule_result_id/remediation_plan";
121
120
  export const RULE_IMPLEMENTATION_RESULT_SEGMENTS_RESULTS =
122
121
  "/rules/:id/implementations/:implementation_id/results/:rule_result_id/segment_results";
123
- export const RULE_IMPLEMENTATION_RESULT_DETAILS_REMEDIATION_PLAN =
122
+ export const RULE_IMPLEMENTATION_RESULT_REMEDIATION_PLAN =
124
123
  "/rules/:id/implementations/:implementation_id/results/:rule_result_id/remediation_plan";
125
124
  export const RULE_IMPLEMENTATION_RESULTS =
126
125
  "/rules/:id/implementations/:implementation_id/results";
@@ -157,11 +156,11 @@ export const STRUCTURE_PARENTS = "/structures/:id/parents";
157
156
  export const STRUCTURE_PROFILE = "/structures/:id/profile";
158
157
  export const STRUCTURE_RULES_IMPLEMENTATIONS =
159
158
  "/structures/:id/rules_implementations";
160
- export const STRUCTURE_TAGS = "/structure_tags";
161
- export const STRUCTURE_TAGS_NEW = "/structure_tags/new";
162
- export const STRUCTURE_TAGS_EDIT = "/structure_tags/:id/edit";
163
- export const STRUCTURE_TYPES = "/structure_types";
164
- export const STRUCTURE_TYPES_EDIT = "/structure_types/:id/edit";
159
+ export const STRUCTURE_TAGS = "/structureTags";
160
+ export const STRUCTURE_TAGS_EDIT = "/structureTags/:id/edit";
161
+ export const STRUCTURE_TAGS_NEW = "/structureTags/new";
162
+ export const STRUCTURE_TYPES = "/structureTypes";
163
+ export const STRUCTURE_TYPES_EDIT = "/structureTypes/:id/edit";
165
164
  export const STRUCTURE_VERSION = "/structures/:id/versions/:version";
166
165
  export const STRUCTURE_VERSIONS = "/structures/:id/versions";
167
166
  export const STRUCTURE_VERSION_VERSIONS =
@@ -169,8 +168,8 @@ export const STRUCTURE_VERSION_VERSIONS =
169
168
  export const STRUCTURES_UPLOAD_EVENTS = "/bulk_update_template_content_events";
170
169
  export const SUBSCRIPTION = "/subscriptions/:id";
171
170
  export const SUBSCRIPTIONS = "/subscriptions";
172
- export const SUBSCRIPTION_NEW = "/subscriptions/new";
173
171
  export const SUBSCRIPTION_EDIT = "/subscriptions/:id/edit";
172
+ export const SUBSCRIPTION_NEW = "/subscriptions/new";
174
173
  export const SYSTEMS = "/systems";
175
174
  export const SYSTEM_EDIT = "/systems/:id/edit";
176
175
  export const SYSTEM_NEW = "/systems/new";
@@ -190,133 +189,142 @@ export const USER_EDIT = "/users/:id/edit";
190
189
  export const USER_EDIT_PASSWORD = "/users/:id/password";
191
190
 
192
191
  const routes = {
193
- GRANT_REQUEST_APPROVALS,
194
192
  CALLBACK,
193
+ CONCEPTS,
194
+ CONCEPTS_BULK_UPDATE,
195
+ CONCEPTS_NEW,
196
+ CONCEPTS_PENDING,
195
197
  CONCEPT_ARCHIVE,
196
198
  CONCEPT_EDIT,
197
199
  CONCEPT_EVENTS,
198
- CONCEPT_LINKS_CONCEPTS_NEW,
199
200
  CONCEPT_LINKS_CONCEPTS,
200
- CONCEPT_LINKS_STRUCTURES_NEW,
201
- CONCEPT_LINKS_STRUCTURES,
201
+ CONCEPT_LINKS_CONCEPTS_NEW,
202
202
  CONCEPT_LINKS_IMPLEMENTATIONS,
203
- CONCEPT_RULES_NEW,
203
+ CONCEPT_LINKS_STRUCTURES,
204
+ CONCEPT_LINKS_STRUCTURES_NEW,
204
205
  CONCEPT_RULES,
206
+ CONCEPT_RULES_NEW,
205
207
  CONCEPT_VERSION,
206
- CONCEPTS_BULK_UPDATE,
207
- CONCEPTS_NEW,
208
- CONCEPTS_PENDING,
209
- CONCEPTS,
210
- CONFIGURATION_EDIT,
211
208
  CONFIGURATION,
209
+ CONFIGURATION_EDIT,
212
210
  DASHBOARD,
213
- DOMAIN_ACTION,
214
- DOMAIN_EDIT,
215
- DOMAIN_MEMBERS_NEW,
216
- DOMAIN_MEMBERS,
217
- DOMAIN_NEW,
218
211
  DOMAIN,
212
+ DOMAINS,
219
213
  DOMAINS_ACTIONS,
220
214
  DOMAINS_NEW,
221
215
  DOMAINS_SEARCH,
222
- DOMAINS,
216
+ DOMAIN_ACTION,
217
+ DOMAIN_EDIT,
218
+ DOMAIN_MEMBERS,
219
+ DOMAIN_MEMBERS_NEW,
220
+ DOMAIN_NEW,
223
221
  EXECUTION_GROUP,
224
- GRANT_REQUEST,
225
- GRANT_REQUESTS,
226
222
  GRANTS,
227
223
  GRANTS_REQUESTS_CHECKOUT,
224
+ GRANT_REQUEST,
225
+ GRANT_REQUESTS,
226
+ GRANT_REQUEST_APPROVALS,
228
227
  GRAPH,
229
228
  GRAPHS,
230
- GROUP_CREATE,
231
- GROUP_EDIT,
232
229
  GROUP,
233
230
  GROUPS,
231
+ GROUP_CREATE,
232
+ GROUP_EDIT,
234
233
  IMPLEMENTATIONS,
234
+ IMPLEMENTATION_CONCEPT_LINKS,
235
+ IMPLEMENTATION_CONCEPT_LINKS_NEW,
236
+ IMPLEMENTATION_STRUCTURES,
237
+ IMPLEMENTATION_STRUCTURES_NEW,
238
+ INGEST,
239
+ INGESTS,
240
+ INGESTS_NEW,
241
+ INGESTS_PENDING,
235
242
  INGEST_ARCHIVE,
236
243
  INGEST_DUPLICATE,
237
244
  INGEST_EDIT,
238
245
  INGEST_EVENTS,
239
246
  INGEST_EXECUTIONS,
240
- INGEST_RELATIONS_INGESTS_NEW,
241
247
  INGEST_RELATIONS_INGESTS,
242
- INGEST_RELATIONS_STRUCTURES_NEW,
248
+ INGEST_RELATIONS_INGESTS_NEW,
243
249
  INGEST_RELATIONS_STRUCTURES,
244
- INGEST,
245
- INGESTS_NEW,
246
- INGESTS_PENDING,
247
- INGESTS,
250
+ INGEST_RELATIONS_STRUCTURES_NEW,
248
251
  JOB,
249
252
  JOBS,
250
253
  LINEAGE_EVENTS,
251
254
  LOGIN,
252
- MY_GRANT_REQUESTS,
253
255
  MY_GRANTS,
256
+ MY_GRANT_REQUESTS,
254
257
  PASSWORD,
255
258
  PENDING_STRUCTURE_NOTES,
256
259
  PROFILE_EXECUTION,
257
260
  PROFILE_GROUP,
258
261
  QUALITY_DASHBOARD,
259
- REMEDIATION_PLAN,
260
262
  REMEDIATION_EDIT,
261
263
  REMEDIATION_NEW,
264
+ REMEDIATION_PLAN,
262
265
  ROLE,
263
- ROLES_NEW,
264
266
  ROLES,
267
+ ROLES_NEW,
268
+ RULE,
269
+ RULES,
265
270
  RULE_EDIT,
266
271
  RULE_EVENTS,
272
+ RULE_IMPLEMENTATION,
273
+ RULE_IMPLEMENTATIONS,
267
274
  RULE_IMPLEMENTATION_CLONE,
268
275
  RULE_IMPLEMENTATION_EDIT,
269
276
  RULE_IMPLEMENTATION_EVENTS,
270
- IMPLEMENTATION_CONCEPT_LINKS,
271
- IMPLEMENTATION_CONCEPT_LINKS_NEW,
272
- IMPLEMENTATION_STRUCTURES,
273
- IMPLEMENTATION_STRUCTURES_NEW,
274
277
  RULE_IMPLEMENTATION_MOVE,
275
278
  RULE_IMPLEMENTATION_NEW,
279
+ RULE_IMPLEMENTATION_RESULTS,
280
+ RULE_IMPLEMENTATION_RESULTS_DETAILS,
276
281
  RULE_IMPLEMENTATION_RESULT_DETAILS,
277
- RULE_IMPLEMENTATION_RESULT_SEGMENTS_RESULTS,
278
282
  RULE_IMPLEMENTATION_RESULT_DETAILS_REMEDIATION_PLAN,
283
+ RULE_IMPLEMENTATION_RESULT_SEGMENTS_RESULTS,
284
+ RULE_IMPLEMENTATION_RESULT_SEGMENTS_RESULTS,
285
+ RULE_IMPLEMENTATION_RESULT_REMEDIATION_PLAN,
279
286
  RULE_IMPLEMENTATION_RESULTS_DETAILS,
280
287
  RULE_IMPLEMENTATION_RESULTS,
281
288
  RULE_IMPLEMENTATION,
282
289
  RULE_IMPLEMENTATIONS,
283
290
  RULE_NEW,
284
- RULE,
285
- RULES,
286
291
  SAMPLE,
292
+ SEARCH,
287
293
  SEARCH_CONCEPTS,
288
294
  SEARCH_INGESTS,
289
295
  SEARCH_RESULTS,
290
296
  SEARCH_STRUCTURES,
291
- SEARCH,
297
+ SOURCE,
298
+ SOURCES,
292
299
  SOURCES_NEW,
293
300
  SOURCE_EDIT,
294
301
  SOURCE_JOB,
295
- SOURCE_JOBS_NEW,
296
302
  SOURCE_JOBS,
297
- SOURCE,
298
- SOURCES,
303
+ SOURCE_JOBS_NEW,
304
+ STRUCTURE,
305
+ STRUCTURES,
306
+ STRUCTURES_BULK_UPDATE,
299
307
  STRUCTURE_CHILDREN,
300
308
  STRUCTURE_EVENTS,
301
309
  STRUCTURE_GRANTS,
302
310
  STRUCTURE_IMPACT,
303
311
  STRUCTURE_LINEAGE,
304
- STRUCTURE_LINKS_NEW,
305
312
  STRUCTURE_LINKS,
313
+ STRUCTURE_LINKS_NEW,
306
314
  STRUCTURE_METADATA,
307
- STRUCTURE_NOTES_EDIT,
308
315
  STRUCTURE_NOTES,
316
+ STRUCTURE_NOTES_EDIT,
309
317
  STRUCTURE_PARENTS,
310
318
  STRUCTURE_PROFILE,
311
319
  STRUCTURE_RULES_IMPLEMENTATIONS,
320
+ STRUCTURE_TAGS,
312
321
  STRUCTURE_TAGS_EDIT,
313
322
  STRUCTURE_TAGS_NEW,
314
- STRUCTURE_TAGS,
315
- STRUCTURE_TYPES_EDIT,
316
323
  STRUCTURE_TYPES,
317
- STRUCTURE_VERSION_VERSIONS,
324
+ STRUCTURE_TYPES_EDIT,
318
325
  STRUCTURE_VERSION,
319
326
  STRUCTURE_VERSIONS,
327
+ STRUCTURE_VERSION_VERSIONS,
320
328
  STRUCTURE,
321
329
  STRUCTURES_BULK_UPDATE,
322
330
  STRUCTURES,
@@ -325,23 +333,25 @@ const routes = {
325
333
  SUBSCRIPTION_NEW,
326
334
  SUBSCRIPTION,
327
335
  SUBSCRIPTIONS,
336
+ SUBSCRIPTION_EDIT,
337
+ SUBSCRIPTION_NEW,
338
+ SYSTEMS,
328
339
  SYSTEM_EDIT,
329
340
  SYSTEM_NEW,
330
341
  SYSTEM_STRUCTURES,
331
- SYSTEMS,
332
- TAGS_NEW,
333
342
  TAGS,
334
- TEMPLATE_EDIT,
335
- TEMPLATE_SCOPE,
343
+ TAGS_NEW,
336
344
  TEMPLATE,
337
- TEMPLATES_NEW,
338
345
  TEMPLATES,
346
+ TEMPLATES_NEW,
347
+ TEMPLATE_EDIT,
348
+ TEMPLATE_SCOPE,
339
349
  UNAUTHORIZED,
340
- USER_EDIT_PASSWORD,
341
- USER_EDIT,
342
350
  USER,
343
- USERS_NEW,
344
351
  USERS,
352
+ USERS_NEW,
353
+ USER_EDIT,
354
+ USER_EDIT_PASSWORD,
345
355
  };
346
356
 
347
357
  export const linkTo = _.mapValues(compile)(routes);
@@ -0,0 +1,68 @@
1
+ import _ from "lodash/fp";
2
+ import {
3
+ flatten,
4
+ childIds,
5
+ descendents,
6
+ recursiveMatch,
7
+ stratify,
8
+ } from "../tree";
9
+
10
+ const baz = { id: "3", name: "baz", children: [] };
11
+ const bar = { id: "2", name: "bar", children: [baz] };
12
+ const foo = { id: "1", name: "foo", children: [bar] };
13
+ const options = [foo];
14
+
15
+ describe("stratify", () => {
16
+ it("stratifies options", () => {
17
+ const foo = { id: 1, name: "foo" };
18
+ const bar = { id: 2, name: "bar", parentId: 1 };
19
+ const baz = { id: 3, name: "baz", parentId: 2 };
20
+ const xyzzy = { id: 4, name: "xyzzy", parentId: 99 };
21
+ const options = [foo, bar, baz, xyzzy];
22
+ const res = stratify({})(options);
23
+ expect(res).toEqual([
24
+ {
25
+ ...foo,
26
+ level: 0,
27
+ children: [{ ...bar, children: [{ ...baz, children: [] }] }],
28
+ },
29
+ { ...xyzzy, level: 0, children: [] },
30
+ ]);
31
+ });
32
+ });
33
+
34
+ describe("flatten", () => {
35
+ it("flattens stratified options and assigns level", () => {
36
+ const res = flatten(options);
37
+ expect(res).toEqual([
38
+ { ...foo, level: 0 },
39
+ { ...bar, level: 1 },
40
+ { ...baz, level: 2 },
41
+ ]);
42
+ });
43
+ });
44
+
45
+ describe("childIds", () => {
46
+ it("returns ids of children", () => {
47
+ const res = childIds("1")(options);
48
+ expect(res).toEqual(["2"]);
49
+ });
50
+ });
51
+
52
+ describe("descendents", () => {
53
+ it("returns descendent ids", () => {
54
+ const res = _.flatMap(descendents)(options);
55
+ expect(res).toEqual(["1", "2", "3"]);
56
+ });
57
+ });
58
+
59
+ describe("recursiveMatch", () => {
60
+ it("matches recursively", () => {
61
+ const match =
62
+ (query) =>
63
+ ({ name }) =>
64
+ _.contains(query)(name);
65
+ const res = _.filter(recursiveMatch(match)("baz"))(options);
66
+ expect(res).toEqual([foo]);
67
+ });
68
+ });
@@ -0,0 +1,44 @@
1
+ import _ from "lodash/fp";
2
+
3
+ const idFn = (d) => d.id;
4
+ const parentFn = (d) => d.parentId;
5
+ const nodeFn = ({ id, name, ...props }) => ({
6
+ id,
7
+ name,
8
+ children: [],
9
+ ...props,
10
+ });
11
+
12
+ export const stratify = (options) => (data) => {
13
+ const { node = nodeFn, id = idFn, parent = parentFn } = options || {};
14
+ const root = { children: [] };
15
+ const nodes = data.reduce((acc, d) => ({ ...acc, [id(d)]: node(d) }), {
16
+ root,
17
+ });
18
+ for (const d of data) {
19
+ const n = nodes[id(d)];
20
+ const p = nodes[parent(d)] || root;
21
+ p.children.push(n);
22
+ }
23
+ return root.children.map((c) => ({ ...c, level: 0 }));
24
+ };
25
+
26
+ export const flatten = (data, level = 0) =>
27
+ data.flatMap((d) =>
28
+ d.children?.length
29
+ ? [{ ...d, level }, ...flatten(d.children, level + 1)]
30
+ : [{ ...d, level }]
31
+ );
32
+
33
+ export const recursiveMatch = (match) => (query) => (d) =>
34
+ match(query)(d) || _.some(recursiveMatch(match)(query))(d?.children);
35
+
36
+ export const descendents = ({ id, children }) =>
37
+ _.isArray(children) ? [id, ...children.flatMap(descendents)] : [id];
38
+
39
+ export const childIds = (ids) =>
40
+ _.flow(
41
+ _.filter(({ id }) => _.contains(id)(ids)),
42
+ _.flatMap("children"),
43
+ _.map("id")
44
+ );