@truedat/bg 5.8.1 → 5.8.2

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.
@@ -1,49 +1,44 @@
1
1
  import _ from "lodash/fp";
2
- import React from "react";
2
+ import React, { useState } from "react";
3
3
  import PropTypes from "prop-types";
4
4
  import { connect } from "react-redux";
5
- import { injectIntl } from "react-intl";
6
- import { compose } from "redux";
7
- import { Button, Form, Checkbox } from "semantic-ui-react";
8
- import { FormattedMessage } from "react-intl";
5
+ import { FormattedMessage, useIntl } from "react-intl";
6
+ import { Button, Form } from "semantic-ui-react";
9
7
  import { HistoryBackButton } from "@truedat/core/components";
10
8
  import { linkTo } from "@truedat/core/routes";
11
9
  import { makeTagOptionsSelector } from "@truedat/core/selectors";
12
10
  import { linkConcept } from "../routines";
13
11
  import ConceptSelector from "./ConceptSelector";
14
12
 
13
+ const TagTypeDropdownSelector = React.lazy(() =>
14
+ import("@truedat/lm/components/TagTypeDropdownSelector")
15
+ );
16
+
15
17
  const filters = {
16
18
  current: [true],
17
19
  status: ["pending_approval", "draft", "rejected", "published"],
18
20
  };
19
21
 
20
- export class ConceptRelationForm extends React.Component {
21
- state = {
22
- selectedTag: [],
23
- selectedConcept: null,
24
- };
25
- handleTagOnChange = (e, { value }) => {
26
- const { selectedTag } = this.state;
22
+ export const pickConceptAttrs = _.pick([
23
+ "name",
24
+ "business_concept_id",
25
+ "id",
26
+ "version",
27
+ ]);
27
28
 
28
- const newSelectedTag = _.cond([
29
- [_.includes(value), _.without([value])],
30
- [_.stubTrue, _.concat(value)],
31
- ])(selectedTag);
29
+ export const ConceptRelationForm = ({
30
+ concept,
31
+ tagOptions,
32
+ linkConcept,
33
+ selectedRelationTags,
34
+ }) => {
35
+ const { formatMessage } = useIntl();
36
+ const [selectedConcept, setSelectedConcept] = useState(null);
32
37
 
33
- this.setState({ selectedTag: newSelectedTag });
34
- };
35
- handleConceptSelected = (selectedConcept) =>
36
- this.setState({ selectedConcept });
37
- handleSubmit = () => {
38
- const pickConceptAttrs = _.pick([
39
- "name",
40
- "business_concept_id",
41
- "id",
42
- "version",
43
- ]);
44
- const { selectedConcept, selectedTag } = this.state;
45
- const { concept } = this.props;
38
+ const handleConceptSelected = (selectedConcept) =>
39
+ setSelectedConcept(selectedConcept);
46
40
 
41
+ const handleSubmit = () => {
47
42
  const redirectUrl = linkTo.CONCEPT_LINKS_CONCEPTS(concept);
48
43
 
49
44
  const conceptLink = {
@@ -52,77 +47,46 @@ export class ConceptRelationForm extends React.Component {
52
47
  source_type: "business_concept",
53
48
  target_id: selectedConcept.business_concept_id,
54
49
  target_type: "business_concept",
55
- tag_ids: selectedTag ? selectedTag : [],
50
+ tag_ids: selectedRelationTags ? selectedRelationTags : [],
56
51
  context: {
57
52
  target: pickConceptAttrs(selectedConcept),
58
53
  source: pickConceptAttrs(concept),
59
54
  },
60
55
  };
61
- this.props.linkConcept(conceptLink);
56
+ linkConcept(conceptLink);
62
57
  };
63
58
 
64
- render() {
65
- const {
66
- tagOptions,
67
- concept: { business_concept_id },
68
- intl: { formatMessage },
69
- } = this.props;
70
-
71
- const { selectedConcept, selectedTag } = this.state;
72
- return (
73
- <>
74
- {!_.isEmpty(tagOptions) && (
75
- <Form.Field className="concept-relation-form">
76
- <label>
77
- <FormattedMessage id="conceptRelations.relationType" />
78
- </label>
79
-
80
- {tagOptions.map((tagOption, key) => (
81
- <div key={key} className="concept-relation-form__field">
82
- <Checkbox
83
- toggle
84
- label={formatMessage({
85
- id: `source.${tagOption.text}`,
86
- defaultMessage: formatMessage({
87
- id: tagOption.text,
88
- defaultMessage: tagOption.text,
89
- }),
90
- })}
91
- name={tagOption.text}
92
- value={tagOption.value}
93
- onChange={this.handleTagOnChange}
94
- />
95
- </div>
96
- ))}
97
- </Form.Field>
98
- )}
99
- <ConceptSelector
100
- selectedConcept={selectedConcept}
101
- handleConceptSelected={this.handleConceptSelected}
102
- businessConceptId={business_concept_id}
103
- defaultFilters={filters}
59
+ return (
60
+ <>
61
+ {!_.isEmpty(tagOptions) ? (
62
+ <Form.Field className="concept-relation-form">
63
+ <TagTypeDropdownSelector options={tagOptions} />
64
+ </Form.Field>
65
+ ) : null}
66
+ <ConceptSelector
67
+ selectedConcept={selectedConcept}
68
+ handleConceptSelected={handleConceptSelected}
69
+ businessConceptId={concept.business_concept_id}
70
+ defaultFilters={filters}
71
+ />
72
+ <Button.Group>
73
+ <Button
74
+ primary
75
+ content={<FormattedMessage id="actions.create" />}
76
+ disabled={!selectedConcept}
77
+ onClick={handleSubmit}
104
78
  />
105
- <Button.Group>
106
- <Button
107
- primary
108
- content={<FormattedMessage id="actions.create" />}
109
- disabled={!selectedConcept || _.isEmpty(selectedTag)}
110
- onClick={this.handleSubmit}
111
- />
112
- <HistoryBackButton
113
- content={formatMessage({ id: "actions.cancel" })}
114
- />
115
- </Button.Group>
116
- </>
117
- );
118
- }
119
- }
79
+ <HistoryBackButton content={formatMessage({ id: "actions.cancel" })} />
80
+ </Button.Group>
81
+ </>
82
+ );
83
+ };
120
84
 
121
85
  ConceptRelationForm.propTypes = {
122
86
  tagOptions: PropTypes.array,
123
87
  concept: PropTypes.object,
124
- intl: PropTypes.object,
125
88
  linkConcept: PropTypes.func,
89
+ selectedRelationTags: PropTypes.array,
126
90
  };
127
91
 
128
92
  const makeMapStateToProps = () => {
@@ -130,11 +94,11 @@ const makeMapStateToProps = () => {
130
94
  const mapStateToProps = (state) => ({
131
95
  tagOptions: getTagOptions(state),
132
96
  concept: state.concept,
97
+ selectedRelationTags: state.selectedRelationTags,
133
98
  });
134
99
  return mapStateToProps;
135
100
  };
136
101
 
137
- export default compose(
138
- injectIntl,
139
- connect(makeMapStateToProps, { linkConcept })
140
- )(ConceptRelationForm);
102
+ export default connect(makeMapStateToProps, { linkConcept })(
103
+ ConceptRelationForm
104
+ );
@@ -17,13 +17,32 @@ const filters = {
17
17
  status: ["pending_approval", "draft", "rejected", "published"],
18
18
  };
19
19
 
20
- const ConceptSelectorRow = ({ concept, active, onClick, disabled }) => {
20
+ const isPositive = (links, id, selectedId) => {
21
+ const link =
22
+ id === selectedId
23
+ ? false
24
+ : _.find(
25
+ (link) =>
26
+ _.propEq("target_id", id)(link) &&
27
+ _.propEq("target_type", "business_concept")(link)
28
+ )(links);
29
+ return link ? true : false;
30
+ };
31
+
32
+ const ConceptSelectorRow = ({
33
+ concept,
34
+ active,
35
+ onClick,
36
+ disabled,
37
+ positive,
38
+ }) => {
21
39
  const { name, status, domain } = concept;
22
40
  return (
23
41
  <Table.Row
24
42
  disabled={disabled}
25
43
  active={active}
26
44
  onClick={() => onClick && onClick(concept)}
45
+ positive={positive}
27
46
  >
28
47
  <Table.Cell content={name} />
29
48
  <Table.Cell content={domain?.name} />
@@ -48,6 +67,7 @@ ConceptSelectorRow.propTypes = {
48
67
  active: PropTypes.bool,
49
68
  onClick: PropTypes.func,
50
69
  disabled: PropTypes.bool,
70
+ positive: PropTypes.bool,
51
71
  };
52
72
 
53
73
  export const ConceptSelector = ({
@@ -59,6 +79,7 @@ export const ConceptSelector = ({
59
79
  pageSize = 7,
60
80
  selectedConcept,
61
81
  showTitle = true,
82
+ links,
62
83
  }) => {
63
84
  const selectedConceptIsFiltered = !_.reduce(
64
85
  (acc, c) => (selectedConcept && c.id == selectedConcept.id) || acc,
@@ -114,6 +135,11 @@ export const ConceptSelector = ({
114
135
  concept={s}
115
136
  active={selectedConcept && selectedConcept.id == s.id}
116
137
  onClick={handleConceptSelected}
138
+ positive={isPositive(
139
+ links,
140
+ s.business_concept_id,
141
+ businessConceptId
142
+ )}
117
143
  />
118
144
  ))}
119
145
  {selectedConcept && selectedConceptIsFiltered && (
@@ -141,6 +167,7 @@ ConceptSelector.propTypes = {
141
167
  pageSize: PropTypes.number,
142
168
  selectedConcept: PropTypes.object,
143
169
  showTitle: PropTypes.bool,
170
+ links: PropTypes.array,
144
171
  };
145
172
 
146
173
  const mapStateToProps = ({ concepts }) => ({ concepts });
@@ -1,90 +1,129 @@
1
1
  import React from "react";
2
- import { shallowWithIntl } from "@truedat/test/intl-stub";
2
+ import { render } from "@truedat/test/render";
3
+ import { waitFor } from "@testing-library/react";
4
+ import userEvent from "@testing-library/user-event";
3
5
  import { linkTo } from "@truedat/core/routes";
4
6
  import { ConceptRelationForm } from "../ConceptRelationForm";
7
+ import en from "../../../../messages/en";
5
8
 
6
- describe("<ConceptRelationForm />", () => {
7
- it("matches the latest snapshot", () => {
8
- const props = {
9
- tagOptions: [{ text: "-", value: 0 }],
10
- concept: {}
11
- };
12
- const wrapper = shallowWithIntl(<ConceptRelationForm {...props} />);
13
- expect(wrapper).toMatchSnapshot();
14
- });
9
+ const linkConcept = jest.fn();
15
10
 
16
- it("handles tag and concept selection", () => {
17
- const props = {
18
- tagOptions: [
19
- { text: "-", value: 0 },
20
- { text: "relates to", value: 1 }
21
- ],
22
- concept: {}
23
- };
24
- const wrapper = shallowWithIntl(<ConceptRelationForm {...props} />);
11
+ const domain = {
12
+ external_id: "foo domain",
13
+ id: 381,
14
+ name: "foo domain",
15
+ };
16
+
17
+ const concepts = [
18
+ {
19
+ business_concept_id: 2,
20
+ domain: domain,
21
+ id: 12355,
22
+ name: "foo concept",
23
+ status: "published",
24
+ },
25
+ {
26
+ business_concept_id: 3,
27
+ domain: domain,
28
+ id: 12290,
29
+ name: "bar concept",
30
+ status: "draft",
31
+ },
32
+ ];
33
+
34
+ const concept = {
35
+ id: 1,
36
+ business_concept_id: 543,
37
+ };
25
38
 
26
- const selectedTag = [1];
27
- const selectedConcept = { business_concept_id: 123, id: 2 };
28
- const expectedState = { selectedTag, selectedConcept };
39
+ const tagOptions = [
40
+ { value: 0, text: "-" },
41
+ { value: 1, text: "relates_to" },
42
+ ];
29
43
 
30
- wrapper
31
- .find("Checkbox")
32
- .at(1)
33
- .prop("onChange")(null, { value: selectedTag });
44
+ const props = {
45
+ tagOptions,
46
+ concept,
47
+ linkConcept,
48
+ selectedRelationTags: [1],
49
+ };
34
50
 
35
- wrapper
36
- .findWhere(n => n.prop("handleConceptSelected"))
37
- .prop("handleConceptSelected")(selectedConcept);
51
+ const renderOpts = {
52
+ messages: {
53
+ en: {
54
+ ...en,
55
+ "actions.create": "create",
56
+ "actions.cancel": "cancel",
57
+ "search.save_filters": "save filters",
58
+ "search.clear_filters": "clear filters",
59
+ "search.applied_filters": "applied filters",
60
+ "relations.relationType": "relation type",
61
+ },
62
+ },
63
+ state: {
64
+ conceptActiveFilters: { filter: "some" },
65
+ concepts,
66
+ tagOptions,
67
+ },
68
+ fallback: "lazy",
69
+ };
38
70
 
39
- expect(wrapper.state()).toEqual(expectedState);
71
+ describe("<ConceptRelationForm />", () => {
72
+ it("matches the latest snapshot", () => {
73
+ const { container } = render(
74
+ <ConceptRelationForm {...props} />,
75
+ renderOpts
76
+ );
77
+ expect(container).toMatchSnapshot();
40
78
  });
41
79
 
42
- it("submits the selected relation", () => {
43
- const linkConcept = jest.fn();
44
- const concept = { business_concept_id: 543, id: 3 };
45
- const props = {
46
- linkConcept,
47
- tagOptions: [
48
- { text: "-", value: 0 },
49
- { text: "relates to", value: 2 }
50
- ],
51
- concept
52
- };
53
- const wrapper = shallowWithIntl(<ConceptRelationForm {...props} />);
80
+ it("handles tag and concept selection", async () => {
81
+ const { findByText, findByRole, getByRole } = render(
82
+ <ConceptRelationForm {...props} />,
83
+ renderOpts
84
+ );
85
+ await waitFor(() => {
86
+ expect(getByRole("button", { name: "create" })).toBeDisabled();
87
+ });
54
88
 
55
- const selectedConcept = { business_concept_id: 123, id: 2 };
89
+ userEvent.click(await findByText("bar concept"));
90
+
91
+ userEvent.click(await findByRole("option", { name: "relates_to" }));
92
+
93
+ await waitFor(() => {
94
+ expect(getByRole("button", { name: "create" })).toBeEnabled();
95
+ });
96
+ });
56
97
 
57
- wrapper
58
- .find("Checkbox")
59
- .at(0)
60
- .prop("onChange")(null, { value: 0 });
98
+ it("submits the selected relation", async () => {
99
+ const { findByText, findByRole, getByRole } = render(
100
+ <ConceptRelationForm {...props} />,
101
+ renderOpts
102
+ );
103
+ userEvent.click(await findByText("bar concept"));
61
104
 
62
- wrapper
63
- .find("Checkbox")
64
- .at(1)
65
- .prop("onChange")(null, { value: 2 });
105
+ userEvent.click(await findByRole("option", { name: "relates_to" }));
66
106
 
67
- wrapper
68
- .findWhere(n => n.prop("handleConceptSelected"))
69
- .prop("handleConceptSelected")(selectedConcept);
107
+ await waitFor(() => {
108
+ expect(getByRole("button", { name: "create" })).toBeEnabled();
109
+ });
70
110
 
71
- wrapper
72
- .find("Button")
73
- .find({ primary: true })
74
- .prop("onClick")();
111
+ userEvent.click(await getByRole("button", { name: "create" }));
75
112
 
76
113
  const expectedRelation = {
77
114
  redirectUrl: linkTo.CONCEPT_LINKS_CONCEPTS(concept),
78
115
  source_id: 543,
79
116
  source_type: "business_concept",
80
- target_id: 123,
117
+ target_id: 3,
81
118
  target_type: "business_concept",
82
- tag_ids: [2, 0],
119
+ tag_ids: [1],
83
120
  context: {
84
- target: { business_concept_id: 123, id: 2 },
85
- source: { business_concept_id: 543, id: 3 }
86
- }
121
+ target: { business_concept_id: 3, id: 12290, name: "bar concept" },
122
+ source: { business_concept_id: 543, id: 1 },
123
+ },
87
124
  };
88
- expect(linkConcept.mock.calls[0][0]).toEqual(expectedRelation);
125
+ await waitFor(() =>
126
+ expect(linkConcept).toHaveBeenCalledWith(expectedRelation)
127
+ );
89
128
  });
90
129
  });