@truedat/lm 7.10.2 → 7.10.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@truedat/lm",
3
- "version": "7.10.2",
3
+ "version": "7.10.4",
4
4
  "description": "Truedat Link Manager",
5
5
  "sideEffects": false,
6
6
  "module": "src/index.js",
@@ -48,7 +48,7 @@
48
48
  "@testing-library/jest-dom": "^6.6.3",
49
49
  "@testing-library/react": "^16.3.0",
50
50
  "@testing-library/user-event": "^14.6.1",
51
- "@truedat/test": "7.10.2",
51
+ "@truedat/test": "7.10.4",
52
52
  "identity-obj-proxy": "^3.0.0",
53
53
  "jest": "^29.7.0",
54
54
  "redux-saga-test-plan": "^4.0.6"
@@ -82,5 +82,5 @@
82
82
  "semantic-ui-react": "^3.0.0-beta.2",
83
83
  "swr": "^2.3.3"
84
84
  },
85
- "gitHead": "913c4f16345d35e38b0ed09c58d73b88955356f0"
85
+ "gitHead": "0dcedbfaed3964ee02a3d2c175e0f96e2fbc9316"
86
86
  }
@@ -11,6 +11,7 @@ import { Button } from "semantic-ui-react";
11
11
 
12
12
  import { createRelation, clearSelectedRelationTags } from "../routines";
13
13
  import TagTypeDropdownSelector from "./TagTypeDropdownSelector";
14
+ import RelationTagsLoader from "./RelationTagsLoader";
14
15
 
15
16
  const StructureSuggestions = React.lazy(
16
17
  () => import("@truedat/dd/components/StructureSuggestions")
@@ -55,6 +56,7 @@ export const ConceptSuggestionLinkForm = () => {
55
56
  return (
56
57
  <>
57
58
  <Divider hidden />
59
+ <RelationTagsLoader />
58
60
  {!_.isEmpty(tagOptions) && (
59
61
  <>
60
62
  <TagTypeDropdownSelector options={tagOptions} />
@@ -15,6 +15,7 @@ export const StructureLinks = ({
15
15
  structure,
16
16
  structureLinks,
17
17
  canCreateLink,
18
+ canSuggestConceptLink,
18
19
  }) => {
19
20
  const structureLinkColumnsWithTags = [
20
21
  {
@@ -75,6 +76,18 @@ export const StructureLinks = ({
75
76
  to={linkTo.STRUCTURE_LINKS_NEW({ id })}
76
77
  content={<FormattedMessage id="links.actions.create" />}
77
78
  />
79
+ {canSuggestConceptLink && (
80
+ <Grid.Column width={16}>
81
+ <Button
82
+ floated="right"
83
+ icon="lightbulb outline"
84
+ secondary
85
+ as={Link}
86
+ to={linkTo.STRUCTURE_LINKS_CONCEPTS_SUGGEST({ id })}
87
+ content={<FormattedMessage id="links.actions.suggest" />}
88
+ />
89
+ </Grid.Column>
90
+ )}
78
91
  </Grid.Column>
79
92
  )}
80
93
  {!_.isEmpty(structureLinks) && (
@@ -94,6 +107,7 @@ StructureLinks.propTypes = {
94
107
  structure: PropTypes.object,
95
108
  canCreateLink: PropTypes.bool,
96
109
  structureLinks: PropTypes.array,
110
+ canSuggestConceptLink: PropTypes.bool,
97
111
  };
98
112
 
99
113
  const mapStateToProps = (state) => ({
@@ -102,6 +116,7 @@ const mapStateToProps = (state) => ({
102
116
  getStructureToConceptLinks(state)
103
117
  ),
104
118
  canCreateLink: _.has("create_link")(state.structureActions),
119
+ canSuggestConceptLink: _.has("suggest_concept_link")(state.structureActions),
105
120
  });
106
121
 
107
122
  export default connect(mapStateToProps)(StructureLinks);
@@ -0,0 +1,79 @@
1
+ import _ from "lodash/fp";
2
+ import React, { useState, useEffect } from "react";
3
+ import { Divider } from "semantic-ui-react";
4
+ import { useParams } from "react-router";
5
+ import { useDispatch, useSelector } from "react-redux";
6
+ import { useIntl } from "react-intl";
7
+ import { makeTagOptionsSelector } from "@truedat/core/selectors";
8
+ import { HistoryBackButton } from "@truedat/core/components";
9
+ import { linkTo } from "@truedat/core/routes";
10
+ import { Button } from "semantic-ui-react";
11
+
12
+ import { linkConcept, clearSelectedRelationTags } from "../routines";
13
+ import TagTypeDropdownSelector from "./TagTypeDropdownSelector";
14
+ import RelationTagsLoader from "./RelationTagsLoader";
15
+
16
+ const ConceptSuggestions = React.lazy(() => import("@truedat/bg/concepts/components/ConceptSuggestions"));
17
+
18
+ const selectTagOptions = makeTagOptionsSelector("data_field");
19
+
20
+ export const StructureSuggestionLinkForm = () => {
21
+ const { id: targetId } = useParams();
22
+ const selectedRelationTags = useSelector((state) => state?.selectedRelationTags);
23
+ const tagOptions = useSelector(selectTagOptions);
24
+ const conceptLinking = useSelector((state) => state?.conceptLinking)
25
+ const dispatch = useDispatch();
26
+ const [selectedConcept, setSelectedConcept] = useState(null);
27
+ const { formatMessage } = useIntl();
28
+
29
+ useEffect(() => {
30
+ return () => {
31
+ dispatch(clearSelectedRelationTags.trigger());
32
+ };
33
+ }, [clearSelectedRelationTags, dispatch]);
34
+
35
+ const handleSubmit = () => {
36
+ const link = {
37
+ redirectUrl: linkTo.STRUCTURE_LINKS({ id: targetId }),
38
+ source_id: selectedConcept?.business_concept_id,
39
+ source_type: "business_concept",
40
+ target_id: targetId,
41
+ target_type: "data_structure",
42
+ tag_ids: selectedRelationTags ? selectedRelationTags : [],
43
+ origin: "suggested",
44
+ };
45
+ dispatch(linkConcept.trigger(link));
46
+ };
47
+
48
+ const disabled = !(selectedConcept && selectedRelationTags);
49
+
50
+ return (
51
+ <>
52
+ <Divider hidden />
53
+ <RelationTagsLoader />
54
+ {!_.isEmpty(tagOptions) && (
55
+ <>
56
+ <TagTypeDropdownSelector options={tagOptions} />
57
+ <Divider hidden />
58
+ </>
59
+ )}
60
+ <ConceptSuggestions
61
+ handleConceptSelected={setSelectedConcept}
62
+ selectedConcept={selectedConcept}
63
+ />
64
+ <Divider hidden />
65
+ <Button.Group>
66
+ <Button
67
+ primary
68
+ loading={conceptLinking}
69
+ content={formatMessage({ id: "actions.create" })}
70
+ disabled={disabled}
71
+ onClick={handleSubmit}
72
+ />
73
+ <HistoryBackButton content={formatMessage({ id: "actions.cancel" })} />
74
+ </Button.Group>
75
+ </>
76
+ );
77
+ };
78
+
79
+ export default StructureSuggestionLinkForm;
@@ -57,6 +57,11 @@ jest.mock("@truedat/core/routes", () => ({
57
57
  },
58
58
  }));
59
59
 
60
+ jest.mock("../RelationTagsLoader", () =>
61
+ jest.fn(() => <div>RelationTagsLoader</div>)
62
+ );
63
+
64
+
60
65
  describe("ConceptSuggestionLinkForm", () => {
61
66
  const mockDispatch = jest.fn();
62
67
 
@@ -0,0 +1,127 @@
1
+ import { fireEvent, screen, waitFor } from "@testing-library/react";
2
+ import { render } from "@truedat/test/render";
3
+ import { StructureSuggestionLinkForm } from "../StructureSuggestionLinkForm";
4
+ import { useDispatch } from "react-redux";
5
+ import { useParams } from "react-router";
6
+ import { linkConcept, clearSelectedRelationTags } from "../../routines";
7
+
8
+ jest.mock("react-redux", () => ({
9
+ ...jest.requireActual("react-redux"),
10
+ useDispatch: jest.fn(),
11
+ }));
12
+
13
+ jest.mock("react-router", () => ({
14
+ ...jest.requireActual("react-router"),
15
+ useParams: jest.fn(),
16
+ }));
17
+
18
+
19
+ jest.mock("react-intl", () => ({
20
+ ...jest.requireActual("react-intl"),
21
+ useIntl: () => ({
22
+ formatMessage: ({ id }) => id,
23
+ }),
24
+ }));
25
+
26
+
27
+ // Mock components
28
+ jest.mock("@truedat/bg/concepts/components/ConceptSuggestions", () =>
29
+ jest.fn((props) => {
30
+ return (
31
+ <div>
32
+ <div>MockConceptSuggestions</div>
33
+ <button
34
+ onClick={() => props.handleConceptSelected({ business_concept_id: "concept123" })}
35
+ >
36
+ Select Concept
37
+ </button>
38
+ </div>
39
+ );
40
+ })
41
+ );
42
+
43
+ jest.mock("../TagTypeDropdownSelector", () =>
44
+ jest.fn(() => <div>MockTagTypeDropdownSelector</div>)
45
+ );
46
+
47
+ jest.mock("../RelationTagsLoader", () =>
48
+ jest.fn(() => <div>RelationTagsLoader</div>)
49
+ );
50
+
51
+ jest.mock("@truedat/core/components", () => ({
52
+ HistoryBackButton: (props) => <button>{props.content}</button>,
53
+ }));
54
+
55
+ jest.mock("@truedat/core/routes", () => ({
56
+ linkTo: { STRUCTURE_LINKS: jest.fn(() => "/mocked/redirect/url"), },
57
+ }));
58
+
59
+ describe("StructureSuggestionLinkForm", () => {
60
+ const mockDispatch = jest.fn();
61
+ const selectedRelationTags = ["tag1"];
62
+ const relationTags = [{
63
+ id: "tag1",
64
+ value: {
65
+ type: "tag1",
66
+ label: "tag1",
67
+ target_type: "data_field",
68
+ },
69
+ }]
70
+ const conceptLinking = false;
71
+ const state = { selectedRelationTags, relationTags, conceptLinking };
72
+
73
+ beforeEach(() => {
74
+ jest.clearAllMocks();
75
+
76
+ useParams.mockReturnValue({ id: "s1" });
77
+ useDispatch.mockReturnValue(mockDispatch);
78
+
79
+ clearSelectedRelationTags.trigger = jest.fn(() => ({ type: "CLEAR_TAGS" }));
80
+ linkConcept.trigger = jest.fn((payload) => ({
81
+ type: "LINK_CONCEPT",
82
+ payload,
83
+ }));
84
+ });
85
+
86
+ it("renders concepet suggestions and tag dropdown", async () => {
87
+ render(<StructureSuggestionLinkForm />, { state });
88
+
89
+ await waitFor(() => {
90
+ expect(screen.getByText("MockConceptSuggestions")).toBeInTheDocument();
91
+ expect(screen.getByText("actions.create")).toBeDisabled();
92
+ });
93
+ });
94
+
95
+ it("dispatches linkConcept on submit", async () => {
96
+ render(<StructureSuggestionLinkForm />, { state });
97
+
98
+ fireEvent.click(screen.getByText("Select Concept"));
99
+
100
+ const createBtn = screen.getByText("actions.create");
101
+ expect(createBtn).not.toBeDisabled();
102
+
103
+ fireEvent.click(createBtn);
104
+
105
+ expect(linkConcept.trigger).toHaveBeenCalledWith({
106
+ redirectUrl: "/mocked/redirect/url",
107
+ source_id: "concept123",
108
+ source_type: "business_concept",
109
+ target_id: "s1",
110
+ target_type: "data_structure",
111
+ tag_ids: ["tag1"],
112
+ origin: "suggested",
113
+ });
114
+
115
+ expect(mockDispatch).toHaveBeenCalledWith(
116
+ expect.objectContaining({ type: "LINK_CONCEPT" })
117
+ );
118
+ });
119
+
120
+ it("dispatches clearSelectedRelationTags on unmount", () => {
121
+ const { unmount } = render(<StructureSuggestionLinkForm />, { state });
122
+ unmount();
123
+
124
+ expect(clearSelectedRelationTags.trigger).toHaveBeenCalled();
125
+ expect(mockDispatch).toHaveBeenCalledWith({ type: "CLEAR_TAGS" });
126
+ });
127
+ });