@truedat/bg 4.38.7 → 4.40.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 +12 -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/__tests__/getDomainSelectorOptions.spec.js +66 -0
- package/src/taxonomy/selectors/getDomainsConcept.js +176 -0
- package/src/taxonomy/selectors/index.js +4 -3
|
@@ -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,66 @@
|
|
|
1
|
+
import { getDomainSelectorOptions } from "..";
|
|
2
|
+
|
|
3
|
+
describe("selectors: getDomainSelectorOptions", () => {
|
|
4
|
+
const domains = [
|
|
5
|
+
{ id: 1, name: "domain1" },
|
|
6
|
+
{ id: 2, name: "domain2", parent_id: 1 },
|
|
7
|
+
{ id: 3, name: "domain3" },
|
|
8
|
+
{ id: 4, name: "domain4", parent_id: 0 },
|
|
9
|
+
{ id: 5, name: "domain2", parent_id: 2 },
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
it("foo", () => {
|
|
13
|
+
const options = getDomainSelectorOptions({ domains });
|
|
14
|
+
expect(options).toEqual([
|
|
15
|
+
{
|
|
16
|
+
ancestors: [],
|
|
17
|
+
children: [{ id: 2, name: "domain2", parent_id: 1 }],
|
|
18
|
+
descendents: [
|
|
19
|
+
{ id: 2, name: "domain2", parent_id: 1 },
|
|
20
|
+
{ id: 5, name: "domain2", parent_id: 2 },
|
|
21
|
+
],
|
|
22
|
+
id: 1,
|
|
23
|
+
level: 0,
|
|
24
|
+
name: "domain1",
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
ancestors: [{ id: 1, name: "domain1" }],
|
|
28
|
+
children: [{ id: 5, name: "domain2", parent_id: 2 }],
|
|
29
|
+
descendents: [{ id: 5, name: "domain2", parent_id: 2 }],
|
|
30
|
+
id: 2,
|
|
31
|
+
level: 1,
|
|
32
|
+
name: "domain2",
|
|
33
|
+
parent_id: 1,
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
ancestors: [
|
|
37
|
+
{ id: 1, name: "domain1" },
|
|
38
|
+
{ id: 2, name: "domain2", parent_id: 1 },
|
|
39
|
+
],
|
|
40
|
+
children: [],
|
|
41
|
+
descendents: [],
|
|
42
|
+
id: 5,
|
|
43
|
+
level: 2,
|
|
44
|
+
name: "domain2",
|
|
45
|
+
parent_id: 2,
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
ancestors: [],
|
|
49
|
+
children: [],
|
|
50
|
+
descendents: [],
|
|
51
|
+
id: 3,
|
|
52
|
+
level: 0,
|
|
53
|
+
name: "domain3",
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
ancestors: [],
|
|
57
|
+
children: [],
|
|
58
|
+
descendents: [],
|
|
59
|
+
id: 4,
|
|
60
|
+
level: 0,
|
|
61
|
+
name: "domain4",
|
|
62
|
+
parent_id: 0,
|
|
63
|
+
},
|
|
64
|
+
]);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
@@ -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
|
+
};
|
|
@@ -148,9 +148,10 @@ const reduceTaxonomy = (domains) => (domainsInLevel, level) => {
|
|
|
148
148
|
};
|
|
149
149
|
|
|
150
150
|
const getDomainSelectorOptions = createSelector([getDomains], (domains) => {
|
|
151
|
-
const roots =
|
|
152
|
-
|
|
153
|
-
|
|
151
|
+
const roots = _.flow(
|
|
152
|
+
_.filter(hasNoParentIn(domains)),
|
|
153
|
+
_.sortBy(accentInsensitivePathOrder("name"))
|
|
154
|
+
)(domains);
|
|
154
155
|
return reduceTaxonomy(domains)(roots, 0);
|
|
155
156
|
});
|
|
156
157
|
|