@truedat/bg 4.38.7 → 4.38.8
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 +6 -0
- package/package.json +5 -5
- package/src/concepts/components/ConceptActions.js +1 -0
- package/src/concepts/components/ConceptHeader.js +5 -5
- package/src/concepts/components/ConceptManageDomain.js +87 -0
- package/src/concepts/components/ConceptManageDomainPopup.js +50 -0
- package/src/concepts/components/ConceptTaxonomy.js +11 -3
- package/src/concepts/components/__tests__/ConceptManageDomain.spec.js +43 -0
- package/src/concepts/components/__tests__/ConceptManageDomainPopup.spec.js +36 -0
- package/src/concepts/components/__tests__/__snapshots__/ConceptManageDomain.spec.js.snap +129 -0
- package/src/concepts/components/__tests__/__snapshots__/ConceptManageDomainPopup.spec.js.snap +171 -0
- package/src/concepts/components/__tests__/__snapshots__/ConceptTaxonomy.spec.js.snap +6 -0
- package/src/concepts/reducers/index.js +3 -1
- package/src/concepts/reducers/updatingDomain.js +16 -0
- package/src/messages/en.js +4 -0
- package/src/messages/es.js +4 -0
- package/src/taxonomy/components/DomainsConceptLoader.js +43 -0
- package/src/taxonomy/components/__tests__/DomainsConceptLoader.spec.js +38 -0
- package/src/taxonomy/reducers/__tests__/domainConceptLoading.spec.js +26 -0
- package/src/taxonomy/reducers/__tests__/domainsConcept.spec.js +30 -0
- package/src/taxonomy/reducers/domainsConcept.js +19 -0
- package/src/taxonomy/reducers/domainsConceptLoading.js +12 -0
- package/src/taxonomy/reducers/index.js +4 -0
- package/src/taxonomy/routines.js +2 -0
- package/src/taxonomy/sagas/__tests__/fetchDomainsConcept.spec.js +93 -0
- package/src/taxonomy/sagas/fetchDomainsConcept.js +37 -0
- package/src/taxonomy/sagas/index.js +3 -0
- package/src/taxonomy/selectors/getDomainsConcept.js +176 -0
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@truedat/bg",
|
|
3
|
-
"version": "4.38.
|
|
3
|
+
"version": "4.38.8",
|
|
4
4
|
"description": "Truedat Web Business Glossary",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"jsnext:main": "src/index.js",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"@testing-library/jest-dom": "^5.14.1",
|
|
35
35
|
"@testing-library/react": "^12.0.0",
|
|
36
36
|
"@testing-library/user-event": "^13.2.1",
|
|
37
|
-
"@truedat/test": "4.38.
|
|
37
|
+
"@truedat/test": "4.38.8",
|
|
38
38
|
"babel-jest": "^27.0.6",
|
|
39
39
|
"babel-plugin-dynamic-import-node": "^2.3.3",
|
|
40
40
|
"babel-plugin-lodash": "^3.3.4",
|
|
@@ -83,8 +83,8 @@
|
|
|
83
83
|
]
|
|
84
84
|
},
|
|
85
85
|
"dependencies": {
|
|
86
|
-
"@truedat/core": "4.38.
|
|
87
|
-
"@truedat/df": "4.38.
|
|
86
|
+
"@truedat/core": "4.38.8",
|
|
87
|
+
"@truedat/df": "4.38.8",
|
|
88
88
|
"file-saver": "^2.0.5",
|
|
89
89
|
"moment": "^2.24.0",
|
|
90
90
|
"path-to-regexp": "^1.7.0",
|
|
@@ -104,5 +104,5 @@
|
|
|
104
104
|
"react-dom": ">= 16.8.6 < 17",
|
|
105
105
|
"semantic-ui-react": ">= 0.88.2 < 2.1"
|
|
106
106
|
},
|
|
107
|
-
"gitHead": "
|
|
107
|
+
"gitHead": "66f56f3fe3e1b5eb0b18379f2d141b1433bf82c1"
|
|
108
108
|
}
|
|
@@ -16,12 +16,12 @@ export const ConceptHeader = ({
|
|
|
16
16
|
setConfidentialConcept,
|
|
17
17
|
share,
|
|
18
18
|
domain,
|
|
19
|
-
template
|
|
19
|
+
template,
|
|
20
20
|
}) => {
|
|
21
21
|
const { formatMessage } = useIntl();
|
|
22
22
|
const path = linkTo.CONCEPT_VERSION({
|
|
23
23
|
business_concept_id: _.prop("business_concept_id")(concept),
|
|
24
|
-
id: "current"
|
|
24
|
+
id: "current",
|
|
25
25
|
});
|
|
26
26
|
const name = concept?.name;
|
|
27
27
|
const description = concept?.description;
|
|
@@ -78,15 +78,15 @@ ConceptHeader.propTypes = {
|
|
|
78
78
|
share: PropTypes.bool,
|
|
79
79
|
concept: PropTypes.object,
|
|
80
80
|
domain: PropTypes.object,
|
|
81
|
-
template: PropTypes.object
|
|
81
|
+
template: PropTypes.object,
|
|
82
82
|
};
|
|
83
83
|
|
|
84
84
|
const mapStateToProps = ({ concept, conceptActions, conceptPermissions }) => ({
|
|
85
|
-
share: _.
|
|
85
|
+
share: _.prop("share")(conceptPermissions),
|
|
86
86
|
concept,
|
|
87
87
|
setConfidentialConcept: _.prop("set_confidential")(conceptActions),
|
|
88
88
|
domain: concept.domain || {},
|
|
89
|
-
template: concept.template || {}
|
|
89
|
+
template: concept.template || {},
|
|
90
90
|
});
|
|
91
91
|
|
|
92
92
|
export default connect(mapStateToProps)(ConceptHeader);
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import _ from "lodash/fp";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { Button, Container, Form, Header } from "semantic-ui-react";
|
|
4
|
+
import { connect } from "react-redux";
|
|
5
|
+
import { useForm, Controller } from "react-hook-form";
|
|
6
|
+
import { useIntl } from "react-intl";
|
|
7
|
+
import PropTypes from "prop-types";
|
|
8
|
+
import DomainDropdownSelector from "../../taxonomy/components/DomainDropdownSelector";
|
|
9
|
+
import { getDomainConceptSelectorOptions } from "../../taxonomy/selectors/getDomainsConcept";
|
|
10
|
+
import { conceptAction } from "../routines";
|
|
11
|
+
|
|
12
|
+
const actionKey = "update_domain";
|
|
13
|
+
|
|
14
|
+
export const ConceptManageDomain = ({
|
|
15
|
+
domainOptions,
|
|
16
|
+
conceptAction,
|
|
17
|
+
action,
|
|
18
|
+
setToDomain,
|
|
19
|
+
}) => {
|
|
20
|
+
const { formatMessage } = useIntl();
|
|
21
|
+
const { handleSubmit, control, formState } = useForm({
|
|
22
|
+
mode: "all",
|
|
23
|
+
defaultValues: {
|
|
24
|
+
setToDomain: _.prop("id")(setToDomain),
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
const { isDirty, isValid } = formState;
|
|
28
|
+
const onSubmit = ({ setToDomain }) => {
|
|
29
|
+
conceptAction({
|
|
30
|
+
action: actionKey,
|
|
31
|
+
...action,
|
|
32
|
+
domain_id: setToDomain,
|
|
33
|
+
});
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<Container style={{ width: "400px" }}>
|
|
38
|
+
<Header
|
|
39
|
+
as="h2"
|
|
40
|
+
content={formatMessage({
|
|
41
|
+
id: "concept.changeDomain.header",
|
|
42
|
+
})}
|
|
43
|
+
/>
|
|
44
|
+
<Form onSubmit={handleSubmit(onSubmit)}>
|
|
45
|
+
<Controller
|
|
46
|
+
control={control}
|
|
47
|
+
name="setToDomain"
|
|
48
|
+
render={({ onBlur, onChange, value }, { invalid }) => (
|
|
49
|
+
<DomainDropdownSelector
|
|
50
|
+
domainOptions={domainOptions}
|
|
51
|
+
name="setToDomain"
|
|
52
|
+
clearable={false}
|
|
53
|
+
error={invalid}
|
|
54
|
+
label={formatMessage({ id: "concept.changeDomain.label" })}
|
|
55
|
+
onBlur={onBlur}
|
|
56
|
+
required
|
|
57
|
+
onChange={(_e, { value }) => onChange(value)}
|
|
58
|
+
value={value}
|
|
59
|
+
/>
|
|
60
|
+
)}
|
|
61
|
+
/>
|
|
62
|
+
<div className="actions">
|
|
63
|
+
<Button
|
|
64
|
+
type="submit"
|
|
65
|
+
disabled={!isDirty || !isValid}
|
|
66
|
+
primary
|
|
67
|
+
content={formatMessage({ id: "conceptDomain.actions.update" })}
|
|
68
|
+
/>
|
|
69
|
+
</div>
|
|
70
|
+
</Form>
|
|
71
|
+
</Container>
|
|
72
|
+
);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
ConceptManageDomain.propTypes = {
|
|
76
|
+
domainOptions: PropTypes.array,
|
|
77
|
+
conceptAction: PropTypes.func,
|
|
78
|
+
setToDomain: PropTypes.array,
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const mapStateToProps = (state) => ({
|
|
82
|
+
domainOptions: getDomainConceptSelectorOptions(state),
|
|
83
|
+
action: _.prop(actionKey)(state.conceptActions),
|
|
84
|
+
setToDomain: state.concept.domain,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
export default connect(mapStateToProps, { conceptAction })(ConceptManageDomain);
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import _ from "lodash/fp";
|
|
2
|
+
import React, { useState, useEffect } from "react";
|
|
3
|
+
import { Button, Icon, Popup } from "semantic-ui-react";
|
|
4
|
+
import { connect } from "react-redux";
|
|
5
|
+
import PropTypes from "prop-types";
|
|
6
|
+
import ConceptManageDomain from "./ConceptManageDomain";
|
|
7
|
+
|
|
8
|
+
export const ConceptManageDomainPopup = ({ update_domain, saving }) => {
|
|
9
|
+
const [open, setOpen] = useState(false);
|
|
10
|
+
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
if (saving === false) {
|
|
13
|
+
setOpen(false);
|
|
14
|
+
}
|
|
15
|
+
}, [saving]);
|
|
16
|
+
|
|
17
|
+
return update_domain ? (
|
|
18
|
+
<Popup
|
|
19
|
+
on="click"
|
|
20
|
+
basic
|
|
21
|
+
flowing
|
|
22
|
+
content={<ConceptManageDomain />}
|
|
23
|
+
onOpen={() => setOpen(true)}
|
|
24
|
+
onClose={() => setOpen(false)}
|
|
25
|
+
open={open}
|
|
26
|
+
position="bottom right"
|
|
27
|
+
size="large"
|
|
28
|
+
positionFixed
|
|
29
|
+
trigger={
|
|
30
|
+
<Button
|
|
31
|
+
basic
|
|
32
|
+
icon={<Icon name={"pencil alternate"} />}
|
|
33
|
+
style={{ boxShadow: "none" }}
|
|
34
|
+
/>
|
|
35
|
+
}
|
|
36
|
+
/>
|
|
37
|
+
) : null;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
ConceptManageDomainPopup.propTypes = {
|
|
41
|
+
update_domain: PropTypes.bool,
|
|
42
|
+
saving: PropTypes.bool,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const mapStateToProps = ({
|
|
46
|
+
conceptActions,
|
|
47
|
+
updatingDomain: saving,
|
|
48
|
+
}) => ({ update_domain: _.has("update_domain")(conceptActions), saving });
|
|
49
|
+
|
|
50
|
+
export default connect(mapStateToProps)(ConceptManageDomainPopup);
|
|
@@ -6,6 +6,11 @@ import { List, Header, Segment } from "semantic-ui-react";
|
|
|
6
6
|
import { FormattedMessage } from "react-intl";
|
|
7
7
|
import { getConceptDomainPath } from "../selectors";
|
|
8
8
|
import DomainItem from "../../taxonomy/components/DomainItem";
|
|
9
|
+
import ConceptManageDomainPopup from "./ConceptManageDomainPopup";
|
|
10
|
+
|
|
11
|
+
const DomainsConceptLoader = React.lazy(() =>
|
|
12
|
+
import("../../taxonomy/components/DomainsConceptLoader")
|
|
13
|
+
);
|
|
9
14
|
|
|
10
15
|
export const ConceptTaxonomy = ({ domainPath }) =>
|
|
11
16
|
_.negate(_.isEmpty)(domainPath) && (
|
|
@@ -13,6 +18,9 @@ export const ConceptTaxonomy = ({ domainPath }) =>
|
|
|
13
18
|
<Header as="h3" dividing>
|
|
14
19
|
<FormattedMessage id="concepts.taxonomy" defaultMessage="Taxonomía" />
|
|
15
20
|
</Header>
|
|
21
|
+
<DomainsConceptLoader actions="manage_business_concepts_domain" />
|
|
22
|
+
<ConceptManageDomainPopup conceptAction="concepts.actions.edit" />
|
|
23
|
+
|
|
16
24
|
<List horizontal>
|
|
17
25
|
{_.map.convert({ cap: false })((domain, i) => (
|
|
18
26
|
<DomainItem
|
|
@@ -27,11 +35,11 @@ export const ConceptTaxonomy = ({ domainPath }) =>
|
|
|
27
35
|
);
|
|
28
36
|
|
|
29
37
|
ConceptTaxonomy.propTypes = {
|
|
30
|
-
domainPath: PropTypes.array
|
|
38
|
+
domainPath: PropTypes.array,
|
|
31
39
|
};
|
|
32
40
|
|
|
33
|
-
const mapStateToProps = state => ({
|
|
34
|
-
domainPath: getConceptDomainPath(state)
|
|
41
|
+
const mapStateToProps = (state) => ({
|
|
42
|
+
domainPath: getConceptDomainPath(state),
|
|
35
43
|
});
|
|
36
44
|
|
|
37
45
|
export default connect(mapStateToProps)(ConceptTaxonomy);
|
|
@@ -0,0 +1,43 @@
|
|
|
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 { ConceptManageDomain } from "../ConceptManageDomain";
|
|
6
|
+
|
|
7
|
+
describe("<ConceptManageDomain />", () => {
|
|
8
|
+
// const id = 5;
|
|
9
|
+
const domainOptions = [
|
|
10
|
+
{ id: 1, name: "foo", level: 0 },
|
|
11
|
+
{ id: 2, name: "bar", level: 0 },
|
|
12
|
+
{ id: 3, name: "baz", level: 0 },
|
|
13
|
+
];
|
|
14
|
+
const conceptAction = jest.fn();
|
|
15
|
+
// const saving = false;
|
|
16
|
+
const setToDomain = [{ id: 2, name: "bar" }];
|
|
17
|
+
const props = {
|
|
18
|
+
domainOptions,
|
|
19
|
+
// id,
|
|
20
|
+
conceptAction,
|
|
21
|
+
// saving,
|
|
22
|
+
setToDomain,
|
|
23
|
+
};
|
|
24
|
+
const renderOpts = {
|
|
25
|
+
messages: {
|
|
26
|
+
en: {
|
|
27
|
+
"concept.changeDomain.header": "edit domain",
|
|
28
|
+
"concept.changeDomain.label": "domain",
|
|
29
|
+
"conceptDomain.actions.update": "update",
|
|
30
|
+
"domain.selector.label": "domain",
|
|
31
|
+
"domain.selector.placeholder": "select a domain...",
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
it("matches the latest snapshot", () => {
|
|
37
|
+
const { container } = render(
|
|
38
|
+
<ConceptManageDomain {...props} />,
|
|
39
|
+
renderOpts
|
|
40
|
+
);
|
|
41
|
+
expect(container).toMatchSnapshot();
|
|
42
|
+
});
|
|
43
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { mount } from "enzyme";
|
|
3
|
+
import { intl } from "@truedat/test/intl-stub";
|
|
4
|
+
import { ConceptManageDomainPopup } from "../ConceptManageDomainPopup";
|
|
5
|
+
|
|
6
|
+
// workaround for enzyme issue with React.useContext
|
|
7
|
+
// see https://github.com/airbnb/enzyme/issues/2176#issuecomment-532361526
|
|
8
|
+
jest.spyOn(React, "useContext").mockImplementation(() => intl);
|
|
9
|
+
jest.mock("../ConceptManageDomain");
|
|
10
|
+
|
|
11
|
+
describe("<ConceptManageDomainPopup />", () => {
|
|
12
|
+
const props = {
|
|
13
|
+
saving: false,
|
|
14
|
+
update_domain: true,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
it("matches the latest snapshot", () => {
|
|
18
|
+
const wrapper = mount(<ConceptManageDomainPopup {...props} />);
|
|
19
|
+
expect(wrapper).toMatchSnapshot();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("manages open and close popup", () => {
|
|
23
|
+
const wrapper = mount(<ConceptManageDomainPopup {...props} />);
|
|
24
|
+
wrapper.setProps({ saving: true, update_domain: true });
|
|
25
|
+
expect(wrapper.find("Popup").prop("open")).toBeFalsy();
|
|
26
|
+
wrapper.find("Popup").find("Portal").find("Button").simulate("click");
|
|
27
|
+
expect(wrapper.find("Popup").prop("open")).toBeTruthy();
|
|
28
|
+
wrapper.find("Popup").find("Portal").find("Button").simulate("click");
|
|
29
|
+
expect(wrapper.find("Popup").prop("open")).toBeFalsy();
|
|
30
|
+
wrapper.find("Popup").find("Portal").find("Button").simulate("click");
|
|
31
|
+
expect(wrapper.find("Popup").prop("open")).toBeTruthy();
|
|
32
|
+
wrapper.setProps({ saving: false, update_domain: true });
|
|
33
|
+
wrapper.update();
|
|
34
|
+
expect(wrapper.find("Popup").prop("open")).toBeFalsy();
|
|
35
|
+
});
|
|
36
|
+
});
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
+
|
|
3
|
+
exports[`<ConceptManageDomain /> matches the latest snapshot 1`] = `
|
|
4
|
+
<div>
|
|
5
|
+
<div
|
|
6
|
+
class="ui container"
|
|
7
|
+
style="width: 400px;"
|
|
8
|
+
>
|
|
9
|
+
<h2
|
|
10
|
+
class="ui header"
|
|
11
|
+
>
|
|
12
|
+
edit domain
|
|
13
|
+
</h2>
|
|
14
|
+
<form
|
|
15
|
+
class="ui form"
|
|
16
|
+
>
|
|
17
|
+
<div
|
|
18
|
+
class="required field"
|
|
19
|
+
>
|
|
20
|
+
<label>
|
|
21
|
+
domain
|
|
22
|
+
</label>
|
|
23
|
+
<div
|
|
24
|
+
class="field"
|
|
25
|
+
>
|
|
26
|
+
<div
|
|
27
|
+
aria-expanded="false"
|
|
28
|
+
class="ui fluid search selection dropdown"
|
|
29
|
+
name="domain"
|
|
30
|
+
role="combobox"
|
|
31
|
+
>
|
|
32
|
+
<input
|
|
33
|
+
aria-autocomplete="list"
|
|
34
|
+
autocomplete="off"
|
|
35
|
+
class="search"
|
|
36
|
+
tabindex="0"
|
|
37
|
+
type="text"
|
|
38
|
+
value=""
|
|
39
|
+
/>
|
|
40
|
+
<div
|
|
41
|
+
aria-atomic="true"
|
|
42
|
+
aria-live="polite"
|
|
43
|
+
class="divider default text"
|
|
44
|
+
role="alert"
|
|
45
|
+
>
|
|
46
|
+
select a domain...
|
|
47
|
+
</div>
|
|
48
|
+
<i
|
|
49
|
+
aria-hidden="true"
|
|
50
|
+
class="dropdown icon"
|
|
51
|
+
/>
|
|
52
|
+
<div
|
|
53
|
+
class="menu transition"
|
|
54
|
+
role="listbox"
|
|
55
|
+
>
|
|
56
|
+
<div
|
|
57
|
+
aria-checked="false"
|
|
58
|
+
aria-selected="true"
|
|
59
|
+
class="selected item"
|
|
60
|
+
role="option"
|
|
61
|
+
style="pointer-events: all;"
|
|
62
|
+
>
|
|
63
|
+
<div
|
|
64
|
+
class="text"
|
|
65
|
+
style="margin-left: 0px;"
|
|
66
|
+
>
|
|
67
|
+
<i
|
|
68
|
+
aria-hidden="true"
|
|
69
|
+
class="icon"
|
|
70
|
+
/>
|
|
71
|
+
foo
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
<div
|
|
75
|
+
aria-checked="false"
|
|
76
|
+
aria-selected="false"
|
|
77
|
+
class="item"
|
|
78
|
+
role="option"
|
|
79
|
+
style="pointer-events: all;"
|
|
80
|
+
>
|
|
81
|
+
<div
|
|
82
|
+
class="text"
|
|
83
|
+
style="margin-left: 0px;"
|
|
84
|
+
>
|
|
85
|
+
<i
|
|
86
|
+
aria-hidden="true"
|
|
87
|
+
class="icon"
|
|
88
|
+
/>
|
|
89
|
+
bar
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
<div
|
|
93
|
+
aria-checked="false"
|
|
94
|
+
aria-selected="false"
|
|
95
|
+
class="item"
|
|
96
|
+
role="option"
|
|
97
|
+
style="pointer-events: all;"
|
|
98
|
+
>
|
|
99
|
+
<div
|
|
100
|
+
class="text"
|
|
101
|
+
style="margin-left: 0px;"
|
|
102
|
+
>
|
|
103
|
+
<i
|
|
104
|
+
aria-hidden="true"
|
|
105
|
+
class="icon"
|
|
106
|
+
/>
|
|
107
|
+
baz
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
<div
|
|
115
|
+
class="actions"
|
|
116
|
+
>
|
|
117
|
+
<button
|
|
118
|
+
class="ui primary disabled button"
|
|
119
|
+
disabled=""
|
|
120
|
+
tabindex="-1"
|
|
121
|
+
type="submit"
|
|
122
|
+
>
|
|
123
|
+
update
|
|
124
|
+
</button>
|
|
125
|
+
</div>
|
|
126
|
+
</form>
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
`;
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
+
|
|
3
|
+
exports[`<ConceptManageDomainPopup /> matches the latest snapshot 1`] = `
|
|
4
|
+
<ConceptManageDomainPopup
|
|
5
|
+
saving={false}
|
|
6
|
+
update_domain={true}
|
|
7
|
+
>
|
|
8
|
+
<Popup
|
|
9
|
+
basic={true}
|
|
10
|
+
content={<Memo(Connect(ConceptManageDomain)) />}
|
|
11
|
+
disabled={false}
|
|
12
|
+
eventsEnabled={true}
|
|
13
|
+
flowing={true}
|
|
14
|
+
on="click"
|
|
15
|
+
onClose={[Function]}
|
|
16
|
+
onOpen={[Function]}
|
|
17
|
+
open={false}
|
|
18
|
+
pinned={false}
|
|
19
|
+
popperModifiers={Array []}
|
|
20
|
+
position="bottom right"
|
|
21
|
+
positionFixed={true}
|
|
22
|
+
size="large"
|
|
23
|
+
trigger={
|
|
24
|
+
<Button
|
|
25
|
+
as="button"
|
|
26
|
+
basic={true}
|
|
27
|
+
icon={
|
|
28
|
+
<Icon
|
|
29
|
+
as="i"
|
|
30
|
+
name="pencil alternate"
|
|
31
|
+
/>
|
|
32
|
+
}
|
|
33
|
+
style={
|
|
34
|
+
Object {
|
|
35
|
+
"boxShadow": "none",
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/>
|
|
39
|
+
}
|
|
40
|
+
>
|
|
41
|
+
<Portal
|
|
42
|
+
closeOnDocumentClick={true}
|
|
43
|
+
closeOnEscape={true}
|
|
44
|
+
closeOnTriggerClick={true}
|
|
45
|
+
eventPool="default"
|
|
46
|
+
onClose={[Function]}
|
|
47
|
+
onMount={[Function]}
|
|
48
|
+
onOpen={[Function]}
|
|
49
|
+
onUnmount={[Function]}
|
|
50
|
+
open={false}
|
|
51
|
+
openOnTriggerClick={true}
|
|
52
|
+
trigger={
|
|
53
|
+
<Button
|
|
54
|
+
as="button"
|
|
55
|
+
basic={true}
|
|
56
|
+
icon={
|
|
57
|
+
<Icon
|
|
58
|
+
as="i"
|
|
59
|
+
name="pencil alternate"
|
|
60
|
+
/>
|
|
61
|
+
}
|
|
62
|
+
style={
|
|
63
|
+
Object {
|
|
64
|
+
"boxShadow": "none",
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/>
|
|
68
|
+
}
|
|
69
|
+
triggerRef={
|
|
70
|
+
Object {
|
|
71
|
+
"current": <button
|
|
72
|
+
class="ui basic icon button"
|
|
73
|
+
style="box-shadow: none;"
|
|
74
|
+
>
|
|
75
|
+
<i
|
|
76
|
+
aria-hidden="true"
|
|
77
|
+
class="pencil alternate icon"
|
|
78
|
+
/>
|
|
79
|
+
</button>,
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
>
|
|
83
|
+
<Ref
|
|
84
|
+
innerRef={[Function]}
|
|
85
|
+
>
|
|
86
|
+
<RefFindNode
|
|
87
|
+
innerRef={[Function]}
|
|
88
|
+
>
|
|
89
|
+
<Button
|
|
90
|
+
as="button"
|
|
91
|
+
basic={true}
|
|
92
|
+
icon={
|
|
93
|
+
<Icon
|
|
94
|
+
as="i"
|
|
95
|
+
name="pencil alternate"
|
|
96
|
+
/>
|
|
97
|
+
}
|
|
98
|
+
onBlur={[Function]}
|
|
99
|
+
onClick={[Function]}
|
|
100
|
+
onFocus={[Function]}
|
|
101
|
+
onMouseEnter={[Function]}
|
|
102
|
+
onMouseLeave={[Function]}
|
|
103
|
+
style={
|
|
104
|
+
Object {
|
|
105
|
+
"boxShadow": "none",
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
>
|
|
109
|
+
<Ref
|
|
110
|
+
innerRef={
|
|
111
|
+
Object {
|
|
112
|
+
"current": <button
|
|
113
|
+
class="ui basic icon button"
|
|
114
|
+
style="box-shadow: none;"
|
|
115
|
+
>
|
|
116
|
+
<i
|
|
117
|
+
aria-hidden="true"
|
|
118
|
+
class="pencil alternate icon"
|
|
119
|
+
/>
|
|
120
|
+
</button>,
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
>
|
|
124
|
+
<RefFindNode
|
|
125
|
+
innerRef={
|
|
126
|
+
Object {
|
|
127
|
+
"current": <button
|
|
128
|
+
class="ui basic icon button"
|
|
129
|
+
style="box-shadow: none;"
|
|
130
|
+
>
|
|
131
|
+
<i
|
|
132
|
+
aria-hidden="true"
|
|
133
|
+
class="pencil alternate icon"
|
|
134
|
+
/>
|
|
135
|
+
</button>,
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
>
|
|
139
|
+
<button
|
|
140
|
+
className="ui basic icon button"
|
|
141
|
+
onBlur={[Function]}
|
|
142
|
+
onClick={[Function]}
|
|
143
|
+
onFocus={[Function]}
|
|
144
|
+
onMouseEnter={[Function]}
|
|
145
|
+
onMouseLeave={[Function]}
|
|
146
|
+
style={
|
|
147
|
+
Object {
|
|
148
|
+
"boxShadow": "none",
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
>
|
|
152
|
+
<Icon
|
|
153
|
+
as="i"
|
|
154
|
+
name="pencil alternate"
|
|
155
|
+
>
|
|
156
|
+
<i
|
|
157
|
+
aria-hidden="true"
|
|
158
|
+
className="pencil alternate icon"
|
|
159
|
+
onClick={[Function]}
|
|
160
|
+
/>
|
|
161
|
+
</Icon>
|
|
162
|
+
</button>
|
|
163
|
+
</RefFindNode>
|
|
164
|
+
</Ref>
|
|
165
|
+
</Button>
|
|
166
|
+
</RefFindNode>
|
|
167
|
+
</Ref>
|
|
168
|
+
</Portal>
|
|
169
|
+
</Popup>
|
|
170
|
+
</ConceptManageDomainPopup>
|
|
171
|
+
`;
|
|
@@ -11,6 +11,12 @@ exports[`<ConceptTaxonomy /> matches the latest snapshot 1`] = `
|
|
|
11
11
|
id="concepts.taxonomy"
|
|
12
12
|
/>
|
|
13
13
|
</Header>
|
|
14
|
+
<lazy
|
|
15
|
+
actions="manage_business_concepts_domain"
|
|
16
|
+
/>
|
|
17
|
+
<Connect(ConceptManageDomainPopup)
|
|
18
|
+
conceptAction="concepts.actions.edit"
|
|
19
|
+
/>
|
|
14
20
|
<List
|
|
15
21
|
horizontal={true}
|
|
16
22
|
>
|
|
@@ -28,6 +28,7 @@ import { conceptUserFilters } from "./conceptUserFilters";
|
|
|
28
28
|
import { conceptSelectedUserFilter } from "./conceptSelectedUserFilter";
|
|
29
29
|
import { sharedToDomains } from "./sharedToDomains";
|
|
30
30
|
import { savingSharedTo } from "./savingSharedTo";
|
|
31
|
+
import { updatingDomain } from "./updatingDomain";
|
|
31
32
|
|
|
32
33
|
export {
|
|
33
34
|
bulkUpdateLoading,
|
|
@@ -59,7 +60,8 @@ export {
|
|
|
59
60
|
conceptUpdating,
|
|
60
61
|
conceptUserFilters,
|
|
61
62
|
sharedToDomains,
|
|
62
|
-
savingSharedTo
|
|
63
|
+
savingSharedTo,
|
|
64
|
+
updatingDomain,
|
|
63
65
|
};
|
|
64
66
|
|
|
65
67
|
export * from "../relations/reducers";
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { conceptAction } from "../routines";
|
|
2
|
+
|
|
3
|
+
const initialState = false;
|
|
4
|
+
|
|
5
|
+
export const updatingDomain = (state = initialState, { type }) => {
|
|
6
|
+
switch (type) {
|
|
7
|
+
case conceptAction.TRIGGER:
|
|
8
|
+
return true;
|
|
9
|
+
case conceptAction.FULFILL:
|
|
10
|
+
return false;
|
|
11
|
+
default:
|
|
12
|
+
return state;
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export default updatingDomain;
|
package/src/messages/en.js
CHANGED
|
@@ -13,6 +13,8 @@ export default {
|
|
|
13
13
|
"Repeated Concept name",
|
|
14
14
|
"bulkUpdate.no.concepts.body": "No concepts selected",
|
|
15
15
|
"bulkUpdate.no.concepts.header": "Empty Search",
|
|
16
|
+
business_concept_to_field_master: "Master Relation",
|
|
17
|
+
"conceptDomain.actions.update": "Update",
|
|
16
18
|
"conceptRelations.relationType.business_concept_to_field_master":
|
|
17
19
|
"Master Relation",
|
|
18
20
|
"concept.error.existing.business.concept":
|
|
@@ -54,6 +56,8 @@ export default {
|
|
|
54
56
|
"concept.sharedTo.dropdown.label": "Domain",
|
|
55
57
|
"concept.sharedTo.dropdown.placeholder": "Select domain",
|
|
56
58
|
"concept.sharedTo.header": "Share in",
|
|
59
|
+
"concept.changeDomain.header": "Edit Domain",
|
|
60
|
+
"concept.changeDomain.label": "Domain",
|
|
57
61
|
"conceptRelation.actions.create": "New Link",
|
|
58
62
|
"conceptRelation.actions.delete.confirmation.content":
|
|
59
63
|
"The Concept will be unlinked from the field. Are you sure?",
|
package/src/messages/es.js
CHANGED
|
@@ -15,6 +15,8 @@ export default {
|
|
|
15
15
|
"Nombre de concepto repetido",
|
|
16
16
|
"bulkUpdate.no.concepts.body": "Ningún concepto seleccionado",
|
|
17
17
|
"bulkUpdate.no.concepts.header": "Búsqueda vacía",
|
|
18
|
+
business_concept_to_field_master: "Relación master",
|
|
19
|
+
"conceptDomain.actions.update": "Actualizar",
|
|
18
20
|
"conceptRelations.relationType.business_concept_to_field_master":
|
|
19
21
|
"Relación master",
|
|
20
22
|
"concept.error.existing.business.concept":
|
|
@@ -56,6 +58,8 @@ export default {
|
|
|
56
58
|
"concept.sharedTo.dropdown.label": "Dominios",
|
|
57
59
|
"concept.sharedTo.dropdown.placeholder": "Seleccionar dominio",
|
|
58
60
|
"concept.sharedTo.header": "Compartir en",
|
|
61
|
+
"concept.changeDomain.label": "Dominios",
|
|
62
|
+
"concept.changeDomain.header": "Editar Dominio",
|
|
59
63
|
"conceptRelation.actions.create": "Crear vínculo",
|
|
60
64
|
"conceptRelation.actions.delete.confirmation.content":
|
|
61
65
|
"Se eliminará esta vinculación. ¿Estás seguro?",
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import PropTypes from "prop-types";
|
|
3
|
+
import { connect } from "react-redux";
|
|
4
|
+
import { Loading } from "@truedat/core/components";
|
|
5
|
+
import { clearDomainsConcept, fetchDomainsConcept } from "../routines";
|
|
6
|
+
|
|
7
|
+
export class DomainsConceptLoader extends React.Component {
|
|
8
|
+
static propTypes = {
|
|
9
|
+
actions: PropTypes.string,
|
|
10
|
+
clearDomainsConcept: PropTypes.func,
|
|
11
|
+
fetchDomainsConcept: PropTypes.func,
|
|
12
|
+
filter: PropTypes.string,
|
|
13
|
+
domainsConceptLoading: PropTypes.bool,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
componentDidMount() {
|
|
17
|
+
const { fetchDomainsConcept, actions, filter } = this.props;
|
|
18
|
+
fetchDomainsConcept({ actions, filter });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
componentWillUnmount() {
|
|
22
|
+
const { clearDomainsConcept } = this.props;
|
|
23
|
+
clearDomainsConcept();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
render() {
|
|
27
|
+
const { domainsConceptLoading } = this.props;
|
|
28
|
+
if (domainsConceptLoading) {
|
|
29
|
+
return <Loading />;
|
|
30
|
+
} else {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const mapStateToProps = ({ domainsConceptLoading }) => ({
|
|
37
|
+
domainsConceptLoading,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
export default connect(mapStateToProps, {
|
|
41
|
+
clearDomainsConcept,
|
|
42
|
+
fetchDomainsConcept,
|
|
43
|
+
})(DomainsConceptLoader);
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { mount } from "enzyme";
|
|
3
|
+
import { DomainsConceptLoader } from "../DomainsConceptLoader";
|
|
4
|
+
|
|
5
|
+
const getProps = () => ({
|
|
6
|
+
clearDomainsConcept: jest.fn(),
|
|
7
|
+
domainsConceptLoading: false,
|
|
8
|
+
fetchDomainsConcept: jest.fn(),
|
|
9
|
+
actions: "create_ingest",
|
|
10
|
+
filter: "all",
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
describe("<DomainsConceptLoader />", () => {
|
|
14
|
+
it("calls fetchDomainsConcept when component mounts but not when it unmounts", () => {
|
|
15
|
+
const props = getProps();
|
|
16
|
+
const wrapper = mount(<DomainsConceptLoader {...props} />);
|
|
17
|
+
expect(props.fetchDomainsConcept).toHaveBeenCalledTimes(1);
|
|
18
|
+
wrapper.unmount();
|
|
19
|
+
expect(props.fetchDomainsConcept).toHaveBeenCalledTimes(1);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("calls clearDomainsConcept and clearDomainDefaultFilters when component unmounts but not when it mounts", () => {
|
|
23
|
+
const props = getProps();
|
|
24
|
+
const wrapper = mount(<DomainsConceptLoader {...props} />);
|
|
25
|
+
expect(props.clearDomainsConcept).toHaveBeenCalledTimes(0);
|
|
26
|
+
wrapper.unmount();
|
|
27
|
+
expect(props.clearDomainsConcept).toHaveBeenCalledTimes(1);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("renders Loading when domainLoading is true", () => {
|
|
31
|
+
const props = getProps();
|
|
32
|
+
|
|
33
|
+
const wrapper = mount(
|
|
34
|
+
<DomainsConceptLoader {...{ ...props, domainsConceptLoading: true }} />
|
|
35
|
+
);
|
|
36
|
+
expect(wrapper.find("Loading").length).toBe(1);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { fetchDomainsConcept } from "../../routines";
|
|
2
|
+
import { domainsConceptLoading } from "..";
|
|
3
|
+
|
|
4
|
+
const fooState = { foo: "bar" };
|
|
5
|
+
|
|
6
|
+
describe("reducers: domainsConceptLoading", () => {
|
|
7
|
+
it("should provide the initial state", () => {
|
|
8
|
+
expect(domainsConceptLoading(undefined, {})).toBe(false);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it("should be true after receiving the fetchDomainsConcept.TRIGGER action", () => {
|
|
12
|
+
expect(
|
|
13
|
+
domainsConceptLoading(false, { type: fetchDomainsConcept.TRIGGER })
|
|
14
|
+
).toBe(true);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("should be false after receiving the fetchDomainsConcept.FULFILL action", () => {
|
|
18
|
+
expect(
|
|
19
|
+
domainsConceptLoading(true, { type: fetchDomainsConcept.FULFILL })
|
|
20
|
+
).toBe(false);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("should ignore unhandled actions", () => {
|
|
24
|
+
expect(domainsConceptLoading(fooState, { type: "FOO" })).toBe(fooState);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { fetchDomainsConcept } from "../../routines";
|
|
2
|
+
import { domainsConcept } from "..";
|
|
3
|
+
|
|
4
|
+
const fooState = { foo: "bar" };
|
|
5
|
+
|
|
6
|
+
describe("reducers: domainsConcept", () => {
|
|
7
|
+
const initialState = [];
|
|
8
|
+
|
|
9
|
+
it("should provide the initial state", () => {
|
|
10
|
+
expect(domainsConcept(undefined, {})).toEqual(initialState);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("should handle the SUCCESS action", () => {
|
|
14
|
+
const data = [
|
|
15
|
+
{ id: 1, name: "Domain 1", description: "Desc 1" },
|
|
16
|
+
{ id: 2, name: "Domain 2", description: "Desc 2", parent_id: 1 },
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
expect(
|
|
20
|
+
domainsConcept(fooState, {
|
|
21
|
+
type: fetchDomainsConcept.SUCCESS,
|
|
22
|
+
payload: { data: { data } },
|
|
23
|
+
})
|
|
24
|
+
).toMatchObject(data);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("should ignore unknown actions", () => {
|
|
28
|
+
expect(domainsConcept(fooState, { type: "FOO" })).toBe(fooState);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import _ from "lodash/fp";
|
|
2
|
+
import { fetchDomainsConcept, clearDomainsConcept } from "../routines";
|
|
3
|
+
|
|
4
|
+
const initialState = [];
|
|
5
|
+
|
|
6
|
+
const caseInsensitiveName = ({ name }) => _.toLower(name);
|
|
7
|
+
|
|
8
|
+
export const domainsConcept = (state = initialState, { type, payload }) => {
|
|
9
|
+
switch (type) {
|
|
10
|
+
case fetchDomainsConcept.SUCCESS:
|
|
11
|
+
const { data } = payload;
|
|
12
|
+
const collection = _.prop("data")(data);
|
|
13
|
+
return _.sortBy(caseInsensitiveName)(collection);
|
|
14
|
+
case clearDomainsConcept.TRIGGER:
|
|
15
|
+
return initialState;
|
|
16
|
+
default:
|
|
17
|
+
return state;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { fetchDomainsConcept } from "../routines";
|
|
2
|
+
|
|
3
|
+
export const domainsConceptLoading = (state = false, { type }) => {
|
|
4
|
+
switch (type) {
|
|
5
|
+
case fetchDomainsConcept.TRIGGER:
|
|
6
|
+
return true;
|
|
7
|
+
case fetchDomainsConcept.FULFILL:
|
|
8
|
+
return false;
|
|
9
|
+
default:
|
|
10
|
+
return state;
|
|
11
|
+
}
|
|
12
|
+
};
|
|
@@ -5,6 +5,8 @@ import { domainLoading } from "./domainLoading";
|
|
|
5
5
|
import { domainMemberDeleting } from "./domainMemberDeleting";
|
|
6
6
|
import { domainMemberSaving } from "./domainMemberSaving";
|
|
7
7
|
import { domainMembers } from "./domainMembers";
|
|
8
|
+
import { domainsConcept } from "./domainsConcept";
|
|
9
|
+
import { domainsConceptLoading } from "./domainsConceptLoading";
|
|
8
10
|
import { domainConceptChildren } from "./domainConceptChildren";
|
|
9
11
|
import { domainConceptChildrenLoading } from "./domainConceptChildrenLoading";
|
|
10
12
|
import { domainMembersActions } from "./domainMembersActions";
|
|
@@ -26,6 +28,8 @@ export {
|
|
|
26
28
|
domainMembersActions,
|
|
27
29
|
domainMembers,
|
|
28
30
|
domainMembersLoading,
|
|
31
|
+
domainsConcept,
|
|
32
|
+
domainsConceptLoading,
|
|
29
33
|
domainConceptChildren,
|
|
30
34
|
domainConceptChildrenLoading,
|
|
31
35
|
domainRedirect,
|
package/src/taxonomy/routines.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { createRoutine } from "redux-saga-routines";
|
|
2
2
|
|
|
3
3
|
export const fetchDomains = createRoutine("FETCH_DOMAINS");
|
|
4
|
+
export const fetchDomainsConcept = createRoutine("FETCH_CONCEPT_DOMAINS");
|
|
5
|
+
export const clearDomainsConcept = createRoutine("CLEAR_DOMAINS");
|
|
4
6
|
export const clearDomains = createRoutine("CLEAR_DOMAINS");
|
|
5
7
|
export const filterDomains = createRoutine("FILTER_DOMAINS");
|
|
6
8
|
export const clearDomainConceptChildren = createRoutine(
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { testSaga } from "redux-saga-test-plan";
|
|
2
|
+
import { apiJson, JSON_OPTS } from "@truedat/core/services/api";
|
|
3
|
+
import {
|
|
4
|
+
fetchDomainsConceptRequestSaga,
|
|
5
|
+
fetchDomainsConceptSaga,
|
|
6
|
+
} from "../fetchDomainsConcept";
|
|
7
|
+
import { fetchDomainsConcept } from "../../routines";
|
|
8
|
+
import { API_DOMAINS } from "../../api";
|
|
9
|
+
|
|
10
|
+
describe("sagas: fetchDomainsConceptRequestSaga", () => {
|
|
11
|
+
it("should invoke fetchDomainsConceptSaga on trigger", () => {
|
|
12
|
+
expect(() => {
|
|
13
|
+
testSaga(fetchDomainsConceptRequestSaga)
|
|
14
|
+
.next()
|
|
15
|
+
.takeLatest(fetchDomainsConcept.TRIGGER, fetchDomainsConceptSaga)
|
|
16
|
+
.finish()
|
|
17
|
+
.isDone();
|
|
18
|
+
}).not.toThrow();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("should throw exception if an unhandled action is received", () => {
|
|
22
|
+
expect(() => {
|
|
23
|
+
testSaga(fetchDomainsConceptRequestSaga)
|
|
24
|
+
.next()
|
|
25
|
+
.takeLatest("FOO", fetchDomainsConceptSaga);
|
|
26
|
+
}).toThrow();
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe("sagas: fetchDomainsConceptSaga", () => {
|
|
31
|
+
const data = {
|
|
32
|
+
collection: [
|
|
33
|
+
{ id: 1, name: "Top domain", description: "Desc" },
|
|
34
|
+
{ id: 2, name: "Child domain", description: "Desc2", parent_id: 2 },
|
|
35
|
+
],
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
it("should put a success action when a response is returned", () => {
|
|
39
|
+
const actions = undefined;
|
|
40
|
+
expect(() => {
|
|
41
|
+
testSaga(fetchDomainsConceptSaga, {})
|
|
42
|
+
.next()
|
|
43
|
+
.put(fetchDomainsConcept.request())
|
|
44
|
+
.next()
|
|
45
|
+
.call(apiJson, API_DOMAINS, JSON_OPTS)
|
|
46
|
+
.next({ data })
|
|
47
|
+
.put(fetchDomainsConcept.success({ data, actions }))
|
|
48
|
+
.next()
|
|
49
|
+
.put(fetchDomainsConcept.fulfill())
|
|
50
|
+
.next()
|
|
51
|
+
.isDone();
|
|
52
|
+
}).not.toThrow();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("should handle actions in the payload", () => {
|
|
56
|
+
const actions = "show";
|
|
57
|
+
const filter = "foo";
|
|
58
|
+
const payload = { actions, filter };
|
|
59
|
+
const json_opts = { params: { actions, filter }, ...JSON_OPTS };
|
|
60
|
+
expect(() => {
|
|
61
|
+
testSaga(fetchDomainsConceptSaga, { payload })
|
|
62
|
+
.next()
|
|
63
|
+
.put(fetchDomainsConcept.request(payload))
|
|
64
|
+
.next()
|
|
65
|
+
.call(apiJson, API_DOMAINS, json_opts)
|
|
66
|
+
.next({ data })
|
|
67
|
+
.put(fetchDomainsConcept.success({ data, actions }))
|
|
68
|
+
.next()
|
|
69
|
+
.put(fetchDomainsConcept.fulfill())
|
|
70
|
+
.next()
|
|
71
|
+
.isDone();
|
|
72
|
+
}).not.toThrow();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("should put a failure action when the call throws an error", () => {
|
|
76
|
+
const message = "Request failed";
|
|
77
|
+
const error = { message };
|
|
78
|
+
|
|
79
|
+
expect(() => {
|
|
80
|
+
testSaga(fetchDomainsConceptSaga, {})
|
|
81
|
+
.next()
|
|
82
|
+
.put(fetchDomainsConcept.request())
|
|
83
|
+
.next()
|
|
84
|
+
.call(apiJson, API_DOMAINS, JSON_OPTS)
|
|
85
|
+
.throw(error)
|
|
86
|
+
.put(fetchDomainsConcept.failure(message))
|
|
87
|
+
.next()
|
|
88
|
+
.put(fetchDomainsConcept.fulfill())
|
|
89
|
+
.next()
|
|
90
|
+
.isDone();
|
|
91
|
+
}).not.toThrow();
|
|
92
|
+
});
|
|
93
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import _ from "lodash/fp";
|
|
2
|
+
import { call, put, takeLatest } from "redux-saga/effects";
|
|
3
|
+
import { apiJson, JSON_OPTS } from "@truedat/core/services/api";
|
|
4
|
+
import { fetchDomainsConcept } from "../routines";
|
|
5
|
+
import { API_DOMAINS } from "../api";
|
|
6
|
+
|
|
7
|
+
export function* fetchDomainsConceptSaga({ payload }) {
|
|
8
|
+
try {
|
|
9
|
+
const { actions, filter } = payload || {};
|
|
10
|
+
const json_opts = actions
|
|
11
|
+
? {
|
|
12
|
+
...JSON_OPTS,
|
|
13
|
+
params: {
|
|
14
|
+
actions: _.flow(_.castArray, _.join(","))(actions),
|
|
15
|
+
filter,
|
|
16
|
+
},
|
|
17
|
+
}
|
|
18
|
+
: JSON_OPTS;
|
|
19
|
+
const url = API_DOMAINS;
|
|
20
|
+
yield put(fetchDomainsConcept.request(payload));
|
|
21
|
+
const { data } = yield call(apiJson, url, json_opts);
|
|
22
|
+
yield put(fetchDomainsConcept.success({ data, actions }));
|
|
23
|
+
} catch (error) {
|
|
24
|
+
if (error.response) {
|
|
25
|
+
const { status, data } = error.response;
|
|
26
|
+
yield put(fetchDomainsConcept.failure({ status, data }));
|
|
27
|
+
} else {
|
|
28
|
+
yield put(fetchDomainsConcept.failure(error.message));
|
|
29
|
+
}
|
|
30
|
+
} finally {
|
|
31
|
+
yield put(fetchDomainsConcept.fulfill());
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function* fetchDomainsConceptRequestSaga() {
|
|
36
|
+
yield takeLatest(fetchDomainsConcept.TRIGGER, fetchDomainsConceptSaga);
|
|
37
|
+
}
|
|
@@ -4,6 +4,7 @@ import { deleteDomainMemberRequestSaga } from "./deleteDomainMember";
|
|
|
4
4
|
import { updateDomainMemberRequestSaga } from "./updateDomainMember";
|
|
5
5
|
import { updateDomainRequestSaga } from "./updateDomain";
|
|
6
6
|
import { deleteDomainRequestSaga } from "./deleteDomain";
|
|
7
|
+
import { fetchDomainsConceptRequestSaga } from "./fetchDomainsConcept";
|
|
7
8
|
import { fetchDomainConceptChildrenRequestSaga } from "./fetchDomainConceptChildren";
|
|
8
9
|
import { fetchDomainMembersRequestSaga } from "./fetchDomainMembers";
|
|
9
10
|
import { fetchDomainRequestSaga } from "./fetchDomain";
|
|
@@ -16,6 +17,7 @@ export {
|
|
|
16
17
|
updateDomainMemberRequestSaga,
|
|
17
18
|
updateDomainRequestSaga,
|
|
18
19
|
deleteDomainRequestSaga,
|
|
20
|
+
fetchDomainsConceptRequestSaga,
|
|
19
21
|
fetchDomainConceptChildrenRequestSaga,
|
|
20
22
|
fetchDomainMembersRequestSaga,
|
|
21
23
|
fetchDomainRequestSaga,
|
|
@@ -29,6 +31,7 @@ export default [
|
|
|
29
31
|
updateDomainMemberRequestSaga(),
|
|
30
32
|
updateDomainRequestSaga(),
|
|
31
33
|
deleteDomainRequestSaga(),
|
|
34
|
+
fetchDomainsConceptRequestSaga(),
|
|
32
35
|
fetchDomainConceptChildrenRequestSaga(),
|
|
33
36
|
fetchDomainMembersRequestSaga(),
|
|
34
37
|
fetchDomainRequestSaga(),
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import _ from "lodash/fp";
|
|
2
|
+
import { createSelector } from "reselect";
|
|
3
|
+
import { accentInsensitivePathOrder } from "@truedat/core/services/sort";
|
|
4
|
+
import { getPrimaryActions, getSecondaryActions } from "./getDomainActions";
|
|
5
|
+
import { getDomainGroups } from "./getDomainGroups";
|
|
6
|
+
import { domainParentOptionsSelector } from "./domainParentOptionsSelector";
|
|
7
|
+
|
|
8
|
+
const getDomain = ({ domain }) => domain;
|
|
9
|
+
const getDomains = ({ domainsConcept }) => domainsConcept;
|
|
10
|
+
const getDomainsFilter = ({ domainsFilter }) => domainsFilter;
|
|
11
|
+
|
|
12
|
+
const isChildOf =
|
|
13
|
+
({ id }) =>
|
|
14
|
+
({ parent_id }) =>
|
|
15
|
+
parent_id === id;
|
|
16
|
+
const isParentOf =
|
|
17
|
+
({ parent_id }) =>
|
|
18
|
+
({ id }) =>
|
|
19
|
+
parent_id === id;
|
|
20
|
+
|
|
21
|
+
const hasNoParentIn = (domainsConcept) => (domain) =>
|
|
22
|
+
!_.some(isParentOf(domain))(domainsConcept);
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Creates a selector which returns the child domainsConcept of the currently selected domain.
|
|
26
|
+
* If no domain is currently selected, it returns the domainsConcept for which no parent is found.
|
|
27
|
+
*/
|
|
28
|
+
const getChildOrRootDomains = createSelector(
|
|
29
|
+
[getDomain, getDomains],
|
|
30
|
+
(domain, domainsConcept) => {
|
|
31
|
+
return _.isEmpty(domain)
|
|
32
|
+
? _.filter(hasNoParentIn(domainsConcept))(domainsConcept)
|
|
33
|
+
: _.filter(isChildOf(domain))(domainsConcept);
|
|
34
|
+
}
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
const findParentsIn = (domainsConcept) => (child) =>
|
|
38
|
+
_.filter(isParentOf(child))(domainsConcept);
|
|
39
|
+
const findChildrenIn = (domainsConcept) => (parent) =>
|
|
40
|
+
_.filter(isChildOf(parent))(domainsConcept);
|
|
41
|
+
|
|
42
|
+
const findDescendents = (parents) => (domainsConcept) => {
|
|
43
|
+
const children = _.flatMap(findChildrenIn(domainsConcept))(parents);
|
|
44
|
+
return _.isEmpty(children)
|
|
45
|
+
? children
|
|
46
|
+
: _.concat(children, findDescendents(children)(domainsConcept));
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* A selector to compute the descendents of the currently selected domain.
|
|
51
|
+
* If no domain is currently selected, all domainsConcept are returned.
|
|
52
|
+
*/
|
|
53
|
+
const getDescendents = createSelector(
|
|
54
|
+
[getDomain, getDomains],
|
|
55
|
+
(domain, domainsConcept) =>
|
|
56
|
+
_.isEmpty(domain)
|
|
57
|
+
? domainsConcept
|
|
58
|
+
: findDescendents([domain])(domainsConcept)
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
const toSearchable = _.flow(_.deburr, _.toLower, _.trim);
|
|
62
|
+
|
|
63
|
+
const isValidFilter = (filter) => !_.isEmpty(toSearchable(filter));
|
|
64
|
+
|
|
65
|
+
const matchesFilter = (filter) =>
|
|
66
|
+
_.flow(
|
|
67
|
+
_.at(["name", "description"]),
|
|
68
|
+
_.map(toSearchable),
|
|
69
|
+
_.some(_.includes(toSearchable(filter)))
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* A selector to compute the domainsConcept whose name or description matches a search string.
|
|
74
|
+
* If a domain is currently selected, the scope of the search will be limited to descendents
|
|
75
|
+
* of the currently selected domain. The search is case-insensitive and accent-insensitive.
|
|
76
|
+
*/
|
|
77
|
+
const getFilteredDomains = createSelector(
|
|
78
|
+
[getDescendents, getDomainsFilter],
|
|
79
|
+
(descendents, domainsFilter) =>
|
|
80
|
+
isValidFilter(domainsFilter)
|
|
81
|
+
? _.filter(matchesFilter(domainsFilter))(descendents)
|
|
82
|
+
: []
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
const _getVisibleDomains = createSelector(
|
|
86
|
+
[getDomainsFilter, getFilteredDomains, getChildOrRootDomains],
|
|
87
|
+
(filter, filteredDomains, childOrRootDomains) =>
|
|
88
|
+
_.isEmpty(filter) ? childOrRootDomains : filteredDomains
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* A selector to compute the currently visible domainsConcept. If a filter is present, the selector
|
|
93
|
+
* returns descendent domainsConcept matching the filter within the scope of the currently selected
|
|
94
|
+
* domain (or within the scope of all domainsConcept if no domain is currently selected).
|
|
95
|
+
* If no filter is present, the selector returns domainsConcept without parents.
|
|
96
|
+
* Enriches domainsConcept with child count.
|
|
97
|
+
*/
|
|
98
|
+
const getVisibleDomains = createSelector(
|
|
99
|
+
[_getVisibleDomains, getDomains],
|
|
100
|
+
(visibleDomains, domainsConcept) =>
|
|
101
|
+
visibleDomains
|
|
102
|
+
.map((d) => ({
|
|
103
|
+
...d,
|
|
104
|
+
children: _.filter(isChildOf(d))(domainsConcept),
|
|
105
|
+
}))
|
|
106
|
+
.map(({ children, ...d }) => ({ childCount: children.length, ...d }))
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* A selector to obtain unique domain types, sorted case-insensitively.
|
|
111
|
+
*/
|
|
112
|
+
const getDomainTypes = createSelector(
|
|
113
|
+
getDomains,
|
|
114
|
+
_.flow(_.map("type"), _.filter(_.isString), _.uniq, _.sortBy(_.toLower))
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
const findAncestorsIn = (domainsConcept) => (children) => {
|
|
118
|
+
const parents = _.flatMap(findParentsIn(domainsConcept))(children);
|
|
119
|
+
return _.isEmpty(parents)
|
|
120
|
+
? parents
|
|
121
|
+
: _.concat(findAncestorsIn(domainsConcept)(parents), parents);
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* A selector to obtain the parents of the currently selected domain.
|
|
126
|
+
*/
|
|
127
|
+
const getAncestorDomains = createSelector(
|
|
128
|
+
[getDomains, getDomain],
|
|
129
|
+
(domainsConcept, domain) =>
|
|
130
|
+
_.isEmpty(domain) ? [] : findAncestorsIn(domainsConcept)([domain])
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
const reduceTaxonomy = (domainsConcept) => (domainsInLevel, level) => {
|
|
134
|
+
if (domainsConcept)
|
|
135
|
+
return _.reduce((acc, domain) => {
|
|
136
|
+
const children = findChildrenIn(domainsConcept)(domain);
|
|
137
|
+
return [
|
|
138
|
+
...acc,
|
|
139
|
+
{
|
|
140
|
+
...domain,
|
|
141
|
+
ancestors: findAncestorsIn(domainsConcept)([domain]),
|
|
142
|
+
descendents: findDescendents([domain])(domainsConcept),
|
|
143
|
+
children,
|
|
144
|
+
level,
|
|
145
|
+
},
|
|
146
|
+
...reduceTaxonomy(domainsConcept)(children, level + 1),
|
|
147
|
+
];
|
|
148
|
+
}, [])(domainsInLevel);
|
|
149
|
+
return [];
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const getDomainConceptSelectorOptions = createSelector(
|
|
153
|
+
[getDomains],
|
|
154
|
+
(domainsConcept) => {
|
|
155
|
+
const roots = getChildOrRootDomains({
|
|
156
|
+
domainsConcept: _.sortBy(accentInsensitivePathOrder("name"))(
|
|
157
|
+
domainsConcept
|
|
158
|
+
),
|
|
159
|
+
});
|
|
160
|
+
return reduceTaxonomy(domainsConcept)(roots, 0);
|
|
161
|
+
}
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
export {
|
|
165
|
+
domainParentOptionsSelector,
|
|
166
|
+
getChildOrRootDomains,
|
|
167
|
+
getDescendents,
|
|
168
|
+
getDomainConceptSelectorOptions,
|
|
169
|
+
getFilteredDomains,
|
|
170
|
+
getVisibleDomains,
|
|
171
|
+
getDomainGroups,
|
|
172
|
+
getDomainTypes,
|
|
173
|
+
getAncestorDomains,
|
|
174
|
+
getPrimaryActions,
|
|
175
|
+
getSecondaryActions,
|
|
176
|
+
};
|