@truedat/lm 4.40.1 → 4.40.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.
Files changed (31) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/package.json +4 -4
  3. package/src/components/ConceptImplementationLink.js +25 -0
  4. package/src/components/ConfirmDeleteRelation.js +13 -5
  5. package/src/components/ImplementationLinkRow.js +24 -0
  6. package/src/components/ImplementationLinks.js +126 -0
  7. package/src/components/ImplementationRelationForm.js +147 -0
  8. package/src/components/LinksPane.js +13 -0
  9. package/src/components/RelationTagForm.js +17 -13
  10. package/src/components/__tests__/ConceptImplementationLink.spec.js +23 -0
  11. package/src/components/__tests__/ConfirmDeleteRelation.spec.js +27 -0
  12. package/src/components/__tests__/ImplementationLinkRow.spec.js +25 -0
  13. package/src/components/__tests__/ImplementationLinks.spec.js +51 -0
  14. package/src/components/__tests__/ImplementationRelationForm.spec.js +33 -0
  15. package/src/components/__tests__/LinksPane.spec.js +35 -0
  16. package/src/components/__tests__/RelationTagForm.spec.js +6 -5
  17. package/src/components/__tests__/__snapshots__/ConceptImplementationLink.spec.js.snap +11 -0
  18. package/src/components/__tests__/__snapshots__/ConfirmDeleteRelation.spec.js.snap +10 -0
  19. package/src/components/__tests__/__snapshots__/ImplementationLinkRow.spec.js.snap +21 -0
  20. package/src/components/__tests__/__snapshots__/ImplementationLinks.spec.js.snap +183 -0
  21. package/src/components/__tests__/__snapshots__/ImplementationRelationForm.spec.js.snap +70 -0
  22. package/src/components/__tests__/__snapshots__/LinksPane.spec.js.snap +52 -0
  23. package/src/components/__tests__/__snapshots__/RelationTagForm.spec.js.snap +13 -0
  24. package/src/components/index.js +3 -1
  25. package/src/messages/en.js +3 -2
  26. package/src/messages/es.js +4 -1
  27. package/src/sagas/__tests__/deleteRelation.spec.js +2 -2
  28. package/src/sagas/deleteRelation.js +2 -2
  29. package/src/selectors/__tests__/getImplementationToConceptLinks.js +11 -0
  30. package/src/selectors/getImplementationToConceptLinks.js +9 -0
  31. package/src/selectors/index.js +1 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## [4.40.4] 2022-03-15
4
+
5
+ ### Fixed
6
+
7
+ - [TD-4271] Remove linking implementations tab for users without permissions
8
+
9
+ ## [4.40.3] 2022-03-14
10
+
11
+ ### Added
12
+
13
+ - [TD-4271] Support for linking implementations with business_concepts
14
+
3
15
  ## [4.39.2] 2022-03-04
4
16
 
5
17
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@truedat/lm",
3
- "version": "4.40.1",
3
+ "version": "4.40.4",
4
4
  "description": "Truedat Link Manager",
5
5
  "sideEffects": false,
6
6
  "jsnext:main": "src/index.js",
@@ -33,7 +33,7 @@
33
33
  "@testing-library/jest-dom": "^5.14.1",
34
34
  "@testing-library/react": "^12.0.0",
35
35
  "@testing-library/user-event": "^13.2.1",
36
- "@truedat/test": "4.39.0",
36
+ "@truedat/test": "4.40.4",
37
37
  "babel-jest": "^27.0.6",
38
38
  "babel-plugin-dynamic-import-node": "^2.3.3",
39
39
  "babel-plugin-lodash": "^3.3.4",
@@ -83,7 +83,7 @@
83
83
  ]
84
84
  },
85
85
  "dependencies": {
86
- "@truedat/core": "4.40.1",
86
+ "@truedat/core": "4.40.4",
87
87
  "path-to-regexp": "^1.7.0",
88
88
  "prop-types": "^15.7.2",
89
89
  "react-graph-vis": "1.0.5",
@@ -100,5 +100,5 @@
100
100
  "react-dom": ">= 16.8.6 < 17",
101
101
  "semantic-ui-react": ">= 0.88.2 < 2.1"
102
102
  },
103
- "gitHead": "58b35b48833fbed1602d1070853e6c805bcc48f1"
103
+ "gitHead": "e9f62f91507d810f91963967a5f3afa20100c1b9"
104
104
  }
@@ -0,0 +1,25 @@
1
+ import React from "react";
2
+ import { Link } from "react-router-dom";
3
+ import PropTypes from "prop-types";
4
+ import { linkTo } from "@truedat/core/routes";
5
+
6
+ export const ConceptImplementationLink = ({
7
+ resource_id: business_concept_id,
8
+ name,
9
+ }) => (
10
+ <Link
11
+ to={linkTo.CONCEPT_LINKS_IMPLEMENTATIONS({
12
+ business_concept_id,
13
+ id: "current",
14
+ })}
15
+ >
16
+ {name}
17
+ </Link>
18
+ );
19
+
20
+ ConceptImplementationLink.propTypes = {
21
+ resource_id: PropTypes.number,
22
+ name: PropTypes.string,
23
+ };
24
+
25
+ export default ConceptImplementationLink;
@@ -6,15 +6,19 @@ import { FormattedMessage } from "react-intl";
6
6
  import { ConfirmModal } from "@truedat/core/components";
7
7
  import { deleteRelation } from "../routines";
8
8
 
9
- export const ConfirmDeleteRelation = ({ id, deleteRelation }) => (
9
+ export const ConfirmDeleteRelation = ({ id, deleteRelation, resourceType }) => (
10
10
  <ConfirmModal
11
11
  icon="trash"
12
- trigger={<Icon name="trash alternate outline" />}
12
+ trigger={<Icon name="trash alternate outline" color="red" />}
13
13
  header={
14
- <FormattedMessage id="relations.actions.data_field.delete.confirmation.header" />
14
+ <FormattedMessage
15
+ id={`relations.actions.${resourceType}.delete.confirmation.header`}
16
+ />
15
17
  }
16
18
  content={
17
- <FormattedMessage id="relations.actions.data_field.delete.confirmation.content" />
19
+ <FormattedMessage
20
+ id={`relations.actions.${resourceType}.delete.confirmation.content`}
21
+ />
18
22
  }
19
23
  onConfirm={() => deleteRelation({ id })}
20
24
  onOpen={(e) => e.stopPropagation()}
@@ -22,6 +26,10 @@ export const ConfirmDeleteRelation = ({ id, deleteRelation }) => (
22
26
  />
23
27
  );
24
28
 
25
- ConfirmDeleteRelation.propTypes = { id: PropTypes.string };
29
+ ConfirmDeleteRelation.propTypes = {
30
+ id: PropTypes.number,
31
+ deleteRelation: PropTypes.func,
32
+ resourceType: PropTypes.string,
33
+ };
26
34
 
27
35
  export default connect(null, { deleteRelation })(ConfirmDeleteRelation);
@@ -0,0 +1,24 @@
1
+ import _ from "lodash/fp";
2
+ import React from "react";
3
+ import PropTypes from "prop-types";
4
+ import { Table } from "semantic-ui-react";
5
+ import { columnDecorator } from "@truedat/core/services";
6
+
7
+ export const ImplementationLinkRow = ({ link, columns }) => (
8
+ <Table.Row>
9
+ {columns.map((column, i) => (
10
+ <Table.Cell
11
+ key={i}
12
+ textAlign={_.prop("textAlign")(column)}
13
+ content={columnDecorator(column)(link)}
14
+ />
15
+ ))}
16
+ </Table.Row>
17
+ );
18
+
19
+ ImplementationLinkRow.propTypes = {
20
+ link: PropTypes.object,
21
+ columns: PropTypes.array,
22
+ };
23
+
24
+ export default ImplementationLinkRow;
@@ -0,0 +1,126 @@
1
+ import _ from "lodash/fp";
2
+ import React from "react";
3
+ import PropTypes from "prop-types";
4
+ import { connect } from "react-redux";
5
+ import { Table, Button, Grid, Segment } from "semantic-ui-react";
6
+ import { FormattedMessage } from "react-intl";
7
+ import { Link } from "react-router-dom";
8
+ import { linkTo } from "@truedat/core/routes";
9
+ import { accentInsensitivePathOrder } from "@truedat/core/services/sort";
10
+ import { getImplementationToConceptLinks } from "../selectors/getImplementationToConceptLinks";
11
+ import ConceptImplementationLink from "./ConceptImplementationLink";
12
+ import ConceptLinkTags from "./ConceptLinkTags";
13
+ import ImplementationLinkRow from "./ImplementationLinkRow";
14
+ import ConfirmDeleteRelation from "./ConfirmDeleteRelation";
15
+
16
+ const ConfirmDeleteImplementationRelation = ({ id }) => (
17
+ <ConfirmDeleteRelation id={id} resourceType="implementation" />
18
+ );
19
+ ConfirmDeleteImplementationRelation.propTypes = {
20
+ id: PropTypes.number,
21
+ };
22
+
23
+ export const ImplementationLinks = ({
24
+ implementation,
25
+ implementationLinks,
26
+ canCreateLink,
27
+ }) => {
28
+ const deleteColumn = canCreateLink
29
+ ? [
30
+ {
31
+ fieldSelector: _.identity,
32
+ fieldDecorator: ConfirmDeleteImplementationRelation,
33
+ },
34
+ ]
35
+ : [];
36
+
37
+ const hasTags = _.flow(
38
+ _.map("tags"),
39
+ _.flatten,
40
+ _.isEmpty
41
+ )(implementationLinks);
42
+
43
+ const tagsColumn = !hasTags
44
+ ? [
45
+ {
46
+ header: "concepts.props.tags",
47
+ fieldSelector: _.identity,
48
+ fieldDecorator: ConceptLinkTags,
49
+ },
50
+ ]
51
+ : [];
52
+
53
+ const columns = [
54
+ {
55
+ header: "concepts.props.name",
56
+ fieldSelector: _.identity,
57
+ fieldDecorator: ConceptImplementationLink,
58
+ },
59
+ ...tagsColumn,
60
+ {
61
+ header: "concepts.props.domain",
62
+ fieldSelector: _.path("domain.name"),
63
+ },
64
+ ...deleteColumn,
65
+ ];
66
+
67
+ const headerRow = columns.map(({ header }, key) => (
68
+ <Table.HeaderCell
69
+ key={key}
70
+ content={header ? <FormattedMessage id={header} /> : null}
71
+ />
72
+ ));
73
+
74
+ const id = _.prop("rule_id")(implementation);
75
+ const implementation_id = _.prop("id")(implementation);
76
+
77
+ const renderBodyRow = (link, i) => (
78
+ <ImplementationLinkRow key={i} link={link} columns={columns} />
79
+ );
80
+
81
+ return (
82
+ <Segment attached="bottom">
83
+ <Grid>
84
+ {id && canCreateLink && (
85
+ <Grid.Column width={16}>
86
+ <Button
87
+ floated="right"
88
+ primary
89
+ as={Link}
90
+ to={linkTo.IMPLEMENTATION_CONCEPT_LINKS_NEW({
91
+ id: id,
92
+ implementation_id: implementation_id,
93
+ })}
94
+ content={<FormattedMessage id="links.actions.create" />}
95
+ />
96
+ </Grid.Column>
97
+ )}
98
+ {!_.isEmpty(implementationLinks) ? (
99
+ <Grid.Column width={16}>
100
+ <Table
101
+ headerRow={headerRow}
102
+ renderBodyRow={renderBodyRow}
103
+ tableData={implementationLinks}
104
+ />
105
+ </Grid.Column>
106
+ ) : null}
107
+ </Grid>
108
+ </Segment>
109
+ );
110
+ };
111
+
112
+ ImplementationLinks.propTypes = {
113
+ implementation: PropTypes.object,
114
+ canCreateLink: PropTypes.bool,
115
+ implementationLinks: PropTypes.array,
116
+ };
117
+
118
+ const mapStateToProps = (state) => ({
119
+ implementation: state.ruleImplementation,
120
+ implementationLinks: _.sortBy(accentInsensitivePathOrder("name"))(
121
+ getImplementationToConceptLinks(state)
122
+ ),
123
+ canCreateLink: _.propOr(false, "link_concept")(state.implementationActions),
124
+ });
125
+
126
+ export default connect(mapStateToProps)(ImplementationLinks);
@@ -0,0 +1,147 @@
1
+ import _ from "lodash/fp";
2
+ import React, { useState } from "react";
3
+ import PropTypes from "prop-types";
4
+ import { connect } from "react-redux";
5
+ import { useIntl } from "react-intl";
6
+ import {
7
+ Button,
8
+ Checkbox,
9
+ Form,
10
+ Grid,
11
+ Header,
12
+ Segment,
13
+ } from "semantic-ui-react";
14
+ import { HistoryBackButton } from "@truedat/core/components";
15
+ import { linkTo } from "@truedat/core/routes";
16
+ import { linkConcept } from "../routines";
17
+ import RelationTagsLoader from "./RelationTagsLoader";
18
+
19
+ const ConceptSelector = React.lazy(() =>
20
+ import("@truedat/bg/concepts/relations/components/ConceptSelector")
21
+ );
22
+
23
+ export const ImplementationRelationForm = ({
24
+ conceptLinking,
25
+ implementation,
26
+ tagOptions,
27
+ linkConcept,
28
+ }) => {
29
+ const { formatMessage } = useIntl();
30
+ const [selectedTag, setSelectedTag] = useState([]);
31
+ const [selectedConcept, setSelectedConcept] = useState(null);
32
+ const submitDisabled = conceptLinking || !selectedConcept;
33
+
34
+ const handleTagOnChange = (e, { value }) => {
35
+ const newSelectedTag = _.cond([
36
+ [_.includes(value), _.without([value])],
37
+ [_.stubTrue, _.concat(value)],
38
+ ])(selectedTag);
39
+ setSelectedTag(newSelectedTag);
40
+ };
41
+
42
+ const handleConceptSelected = (selectedConcept) =>
43
+ setSelectedConcept(selectedConcept);
44
+
45
+ const handleSubmit = () => {
46
+ const redirectUrl = linkTo.IMPLEMENTATION_CONCEPT_LINKS({
47
+ id: implementation.rule_id,
48
+ implementation_id: implementation.id,
49
+ });
50
+ const conceptLink = {
51
+ redirectUrl,
52
+ source_id: implementation.id,
53
+ source_type: "implementation",
54
+ target_id: selectedConcept.business_concept_id,
55
+ target_type: "business_concept",
56
+ tag_ids: selectedTag ? selectedTag : [],
57
+ context: {},
58
+ };
59
+ linkConcept(conceptLink);
60
+ };
61
+
62
+ return (
63
+ <Segment attached="bottom">
64
+ <Grid>
65
+ <Grid.Column with={16}>
66
+ <RelationTagsLoader />
67
+ <Header
68
+ as="h4"
69
+ content={formatMessage({
70
+ id: "implementations.relation.new.header",
71
+ })}
72
+ />
73
+
74
+ {!_.isEmpty(tagOptions) ? (
75
+ <Form.Field style={{ marginBottom: "10px" }}>
76
+ <label>
77
+ {formatMessage({ id: "conceptRelations.relationType" })}
78
+ </label>
79
+ {tagOptions.map((tagOption, key) => (
80
+ <div key={key} style={{ margin: "6px 0" }}>
81
+ <Checkbox
82
+ toggle
83
+ label={formatMessage({
84
+ id: `conceptRelations.relationType.${tagOption.text}`,
85
+ defaultMessage: tagOption.text,
86
+ })}
87
+ name={tagOption.text}
88
+ value={tagOption.id}
89
+ onChange={handleTagOnChange}
90
+ />
91
+ </div>
92
+ ))}
93
+ </Form.Field>
94
+ ) : null}
95
+ <ConceptSelector
96
+ selectedConcept={selectedConcept}
97
+ handleConceptSelected={handleConceptSelected}
98
+ />
99
+
100
+ <div className="actions">
101
+ <HistoryBackButton
102
+ content={formatMessage({ id: "actions.cancel" })}
103
+ disabled={conceptLinking}
104
+ />
105
+
106
+ <Button
107
+ primary
108
+ content={formatMessage({ id: "actions.create" })}
109
+ disabled={submitDisabled}
110
+ loading={conceptLinking}
111
+ onClick={handleSubmit}
112
+ />
113
+ </div>
114
+ </Grid.Column>
115
+ </Grid>
116
+ </Segment>
117
+ );
118
+ };
119
+
120
+ ImplementationRelationForm.propTypes = {
121
+ implementation: PropTypes.object,
122
+ tagOptions: PropTypes.array,
123
+ conceptLinking: PropTypes.bool,
124
+ linkConcept: PropTypes.func,
125
+ };
126
+
127
+ const tagsToOptions = _.map(({ id: id, value: { type } }) => ({
128
+ id,
129
+ text: type,
130
+ }));
131
+
132
+ const mapStateToProps = (state) => ({
133
+ conceptLinking: state.conceptLinking,
134
+ implementation: state.ruleImplementation,
135
+ tagOptions: _.flow(
136
+ _.filter(
137
+ (tag) =>
138
+ _.path("value.target_type")(tag) == "rule_implementations" ||
139
+ !_.path("value.target_type")
140
+ ),
141
+ tagsToOptions
142
+ )(state.relationTags),
143
+ });
144
+
145
+ export default connect(mapStateToProps, { linkConcept })(
146
+ ImplementationRelationForm
147
+ );
@@ -18,6 +18,11 @@ const Row = ({ columns, link }) => (
18
18
  </Table.Row>
19
19
  );
20
20
 
21
+ Row.propTypes = {
22
+ columns: PropTypes.array,
23
+ link: PropTypes.object,
24
+ };
25
+
21
26
  export const Links = ({ tag, sourceType, targetType, columns, links }) => {
22
27
  const headerRow = columns.map(({ name }, key) => (
23
28
  <Table.HeaderCell
@@ -50,6 +55,14 @@ export const Links = ({ tag, sourceType, targetType, columns, links }) => {
50
55
  );
51
56
  };
52
57
 
58
+ Links.propTypes = {
59
+ tag: PropTypes.string,
60
+ sourceType: PropTypes.string,
61
+ targetType: PropTypes.string,
62
+ columns: PropTypes.array,
63
+ links: PropTypes.array,
64
+ };
65
+
53
66
  const columnPredicate = (column) => {
54
67
  const { fieldSelector, name } = column || {};
55
68
  return _.isFunction(fieldSelector)
@@ -13,20 +13,24 @@ export const RelationTagForm = ({ relationTag, isSubmitting, onSubmit }) => {
13
13
  defaultValues: {
14
14
  type: "",
15
15
  target_type: "",
16
- ...relationTag
17
- }
16
+ ...relationTag,
17
+ },
18
18
  });
19
19
  const { isDirty, isValid } = formState;
20
20
  const targetTypeOptions = [
21
21
  {
22
22
  value: "business_concept",
23
- text: formatMessage({ id: "target_type.business_concept" })
23
+ text: formatMessage({ id: "target_type.business_concept" }),
24
24
  },
25
25
  { value: "ingest", text: formatMessage({ id: "target_type.ingest" }) },
26
+ {
27
+ value: "implementations",
28
+ text: formatMessage({ id: "target_type.implementations" }),
29
+ },
26
30
  {
27
31
  value: "data_field",
28
- text: formatMessage({ id: "target_type.data_field" })
29
- }
32
+ text: formatMessage({ id: "target_type.data_field" }),
33
+ },
30
34
  ];
31
35
  return (
32
36
  <Form onSubmit={handleSubmit(onSubmit)}>
@@ -37,7 +41,7 @@ export const RelationTagForm = ({ relationTag, isSubmitting, onSubmit }) => {
37
41
  required: formatMessage(
38
42
  { id: "form.validation.required" },
39
43
  { prop: formatMessage({ id: "relationTags.props.type" }) }
40
- )
44
+ ),
41
45
  }}
42
46
  render={({ onBlur, onChange, value }) => (
43
47
  <Form.Input
@@ -46,12 +50,12 @@ export const RelationTagForm = ({ relationTag, isSubmitting, onSubmit }) => {
46
50
  error={errors.type?.message}
47
51
  label={{
48
52
  children: formatMessage({ id: "relationTags.props.type" }),
49
- htmlFor: "type"
53
+ htmlFor: "type",
50
54
  }}
51
55
  onBlur={onBlur}
52
56
  onChange={(_e, { value }) => onChange(value)}
53
57
  placeholder={formatMessage({
54
- id: "relationTags.props.type.placeholder"
58
+ id: "relationTags.props.type.placeholder",
55
59
  })}
56
60
  value={value || ""}
57
61
  required
@@ -65,7 +69,7 @@ export const RelationTagForm = ({ relationTag, isSubmitting, onSubmit }) => {
65
69
  required: formatMessage(
66
70
  { id: "form.validation.required" },
67
71
  { prop: formatMessage({ id: "relationTags.props.target_type" }) }
68
- )
72
+ ),
69
73
  }}
70
74
  render={({ onBlur, onChange, value }) => (
71
75
  <Form.Dropdown
@@ -74,12 +78,12 @@ export const RelationTagForm = ({ relationTag, isSubmitting, onSubmit }) => {
74
78
  error={errors.target_type?.message}
75
79
  label={{
76
80
  children: formatMessage({ id: "relationTags.props.target_type" }),
77
- htmlFor: "target_type"
81
+ htmlFor: "target_type",
78
82
  }}
79
83
  onBlur={onBlur}
80
84
  onChange={(_e, { value }) => onChange(value)}
81
85
  placeholder={formatMessage({
82
- id: "relationTags.props.target_type.placeholder"
86
+ id: "relationTags.props.target_type.placeholder",
83
87
  })}
84
88
  value={value || ""}
85
89
  selection
@@ -109,11 +113,11 @@ export const RelationTagForm = ({ relationTag, isSubmitting, onSubmit }) => {
109
113
  RelationTagForm.propTypes = {
110
114
  relationTag: PropTypes.object,
111
115
  isSubmitting: PropTypes.bool,
112
- onSubmit: PropTypes.func
116
+ onSubmit: PropTypes.func,
113
117
  };
114
118
 
115
119
  const mapStateToProps = ({ relationTagCreating }) => ({
116
- isSubmitting: relationTagCreating
120
+ isSubmitting: relationTagCreating,
117
121
  });
118
122
 
119
123
  export default connect(mapStateToProps)(RelationTagForm);
@@ -0,0 +1,23 @@
1
+ import React from "react";
2
+ import { render } from "@truedat/test/render";
3
+ import { ConceptImplementationLink } from "../ConceptImplementationLink";
4
+
5
+ describe("<ConceptImplementationLink />", () => {
6
+ const renderOpts = {
7
+ messages: {
8
+ en: {},
9
+ },
10
+ };
11
+
12
+ const props = {
13
+ resource_id: 8,
14
+ name: "ConceptImplementationLink",
15
+ };
16
+ it("matches the latest snapshot", () => {
17
+ const { container } = render(
18
+ <ConceptImplementationLink {...props} />,
19
+ renderOpts
20
+ );
21
+ expect(container).toMatchSnapshot();
22
+ });
23
+ });
@@ -0,0 +1,27 @@
1
+ import React from "react";
2
+ import { render } from "@truedat/test/render";
3
+ import { ConfirmDeleteRelation } from "../ConfirmDeleteRelation";
4
+
5
+ describe("<ConfirmDeleteRelation />", () => {
6
+ const renderOpts = {
7
+ messages: {
8
+ en: {
9
+ "relations.actions.resourceType.delete.confirmation.header": "header",
10
+ "relations.actions.resourceType.delete.confirmation.content": "content",
11
+ },
12
+ },
13
+ };
14
+
15
+ const props = {
16
+ id: 10,
17
+ deleteRelation: () => {},
18
+ resourceType: "resourceType",
19
+ };
20
+ it("matches the latest snapshot", () => {
21
+ const { container } = render(
22
+ <ConfirmDeleteRelation {...props} />,
23
+ renderOpts
24
+ );
25
+ expect(container).toMatchSnapshot();
26
+ });
27
+ });
@@ -0,0 +1,25 @@
1
+ import React from "react";
2
+ import { render } from "@truedat/test/render";
3
+ import { Table } from "semantic-ui-react";
4
+ import { ImplementationLinkRow } from "../ImplementationLinkRow";
5
+
6
+ describe("<ImplementationLinkRow />", () => {
7
+ const renderOpts = {
8
+ messages: {
9
+ en: { "props.name": "name" },
10
+ },
11
+ };
12
+
13
+ it("matches the latest snapshot", () => {
14
+ const columns = [{ header: "props.name" }];
15
+ const renderBodyRow = (link, i) => (
16
+ <ImplementationLinkRow key={i} link={link} columns={columns} />
17
+ );
18
+
19
+ const { container } = render(
20
+ <Table renderBodyRow={renderBodyRow} tableData={[{ id: 1 }]} />,
21
+ renderOpts
22
+ );
23
+ expect(container).toMatchSnapshot();
24
+ });
25
+ });
@@ -0,0 +1,51 @@
1
+ import React from "react";
2
+ import { render } from "@truedat/test/render";
3
+ import { ImplementationLinks } from "../ImplementationLinks";
4
+
5
+ describe("<ImplementationLinks />", () => {
6
+ const renderOpts = {
7
+ messages: {
8
+ en: {
9
+ "links.actions.create": "create",
10
+ "concepts.props.name": "name",
11
+ "concepts.props.tags": "tags",
12
+ "concepts.props.domain": "domain",
13
+ },
14
+ },
15
+ };
16
+
17
+ it("matches the latest snapshot", () => {
18
+ const props = {
19
+ implementation: { rule_id: 2, id: 4 },
20
+ canCreateLink: true,
21
+ implementationLinks: [
22
+ {
23
+ name: "name",
24
+ tags: ["sometags"],
25
+ domain: { name: "domain" },
26
+ resource_id: 2,
27
+ },
28
+ ],
29
+ };
30
+ const { container } = render(
31
+ <ImplementationLinks {...props} />,
32
+ renderOpts
33
+ );
34
+ expect(container).toMatchSnapshot();
35
+ });
36
+
37
+ it("matches the latest snapshot when implementations have no tags", () => {
38
+ const props = {
39
+ implementation: { rule_id: 2, id: 4 },
40
+ canCreateLink: true,
41
+ implementationLinks: [
42
+ { name: "name", tags: [], domain: { name: "domain" }, resource_id: 2 },
43
+ ],
44
+ };
45
+ const { container } = render(
46
+ <ImplementationLinks {...props} />,
47
+ renderOpts
48
+ );
49
+ expect(container).toMatchSnapshot();
50
+ });
51
+ });
@@ -0,0 +1,33 @@
1
+ import React, { Suspense } from "react";
2
+ import { render } from "@truedat/test/render";
3
+ import { ImplementationRelationForm } from "../ImplementationRelationForm";
4
+
5
+ describe("<ImplementationRelationForm />", () => {
6
+ const renderOpts = {
7
+ messages: {
8
+ en: {
9
+ "implementations.relation.new.header": "header",
10
+ "conceptRelations.relationType": "relationType",
11
+ "actions.cancel": "cancel",
12
+ "actions.create": "create",
13
+ "conceptRelations.relationType.text": "text",
14
+ },
15
+ },
16
+ };
17
+
18
+ const props = {
19
+ implementation: { rule_id: 2, id: 4 },
20
+ tagOptions: [{ id: 2, text: "text" }],
21
+ conceptLinking: false,
22
+ linkConcept: () => {},
23
+ };
24
+ it("matches the latest snapshot", () => {
25
+ const { container } = render(
26
+ <Suspense fallback={null}>
27
+ <ImplementationRelationForm {...props} />
28
+ </Suspense>,
29
+ renderOpts
30
+ );
31
+ expect(container).toMatchSnapshot();
32
+ });
33
+ });
@@ -0,0 +1,35 @@
1
+ import React from "react";
2
+ import { render } from "@truedat/test/render";
3
+ import { LinksPane } from "../LinksPane";
4
+
5
+ describe("<LinksPane />", () => {
6
+ const renderOpts = {
7
+ messages: {
8
+ en: {
9
+ "column.header": "header",
10
+ "implementationDeletedRelation.table.title": "Delete title",
11
+ "implementationRelation.table.title": "Relation Title",
12
+ "implementationRelation.master.table.title": "Master title",
13
+ "concept.column": "column",
14
+ },
15
+ },
16
+ };
17
+
18
+ const columns = [
19
+ {
20
+ name: "column",
21
+ header: "column.header",
22
+ },
23
+ ];
24
+ const links = [{ id: 4 }];
25
+ const props = {
26
+ groups: [[columns, ["tag", links]]],
27
+ sourceType: "implementation",
28
+ targetType: "concept",
29
+ linksActions: <button>action</button>,
30
+ };
31
+ it("matches the latest snapshot", () => {
32
+ const { container } = render(<LinksPane {...props} />, renderOpts);
33
+ expect(container).toMatchSnapshot();
34
+ });
35
+ });
@@ -16,9 +16,10 @@ const renderOpts = {
16
16
  "relationTags.props.type.placeholder": "...",
17
17
  "target_type.business_concept": "concept",
18
18
  "target_type.data_field": "structure",
19
- "target_type.ingest": "ingest"
20
- }
21
- }
19
+ "target_type.ingest": "ingest",
20
+ "target_type.implementations": "implementations",
21
+ },
22
+ },
22
23
  };
23
24
 
24
25
  describe("<RelationTagForm />", () => {
@@ -32,7 +33,7 @@ describe("<RelationTagForm />", () => {
32
33
  const onSubmit = jest.fn();
33
34
 
34
35
  const { findByRole, getByRole, findByText } = render(
35
- <RelationTagForm onSubmit={props => onSubmit(props)} />,
36
+ <RelationTagForm onSubmit={(props) => onSubmit(props)} />,
36
37
  renderOpts
37
38
  );
38
39
 
@@ -59,7 +60,7 @@ describe("<RelationTagForm />", () => {
59
60
  await waitFor(() =>
60
61
  expect(onSubmit).toHaveBeenCalledWith({
61
62
  type: "foo",
62
- target_type: "business_concept"
63
+ target_type: "business_concept",
63
64
  })
64
65
  );
65
66
  });
@@ -0,0 +1,11 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`<ConceptImplementationLink /> matches the latest snapshot 1`] = `
4
+ <div>
5
+ <a
6
+ href="/concepts/8/versions/current/links/implementations"
7
+ >
8
+ ConceptImplementationLink
9
+ </a>
10
+ </div>
11
+ `;
@@ -0,0 +1,10 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`<ConfirmDeleteRelation /> matches the latest snapshot 1`] = `
4
+ <div>
5
+ <i
6
+ aria-hidden="true"
7
+ class="red trash alternate outline icon"
8
+ />
9
+ </div>
10
+ `;
@@ -0,0 +1,21 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`<ImplementationLinkRow /> matches the latest snapshot 1`] = `
4
+ <div>
5
+ <table
6
+ class="ui table"
7
+ >
8
+ <tbody
9
+ class=""
10
+ >
11
+ <tr
12
+ class=""
13
+ >
14
+ <td
15
+ class=""
16
+ />
17
+ </tr>
18
+ </tbody>
19
+ </table>
20
+ </div>
21
+ `;
@@ -0,0 +1,183 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`<ImplementationLinks /> matches the latest snapshot 1`] = `
4
+ <div>
5
+ <div
6
+ class="ui bottom attached segment"
7
+ >
8
+ <div
9
+ class="ui grid"
10
+ >
11
+ <div
12
+ class="sixteen wide column"
13
+ >
14
+ <a
15
+ class="ui primary right floated button"
16
+ href="/rules/2/implementations/4/links/new"
17
+ role="button"
18
+ >
19
+ create
20
+ </a>
21
+ </div>
22
+ <div
23
+ class="sixteen wide column"
24
+ >
25
+ <table
26
+ class="ui table"
27
+ >
28
+ <thead
29
+ class=""
30
+ >
31
+ <tr
32
+ class=""
33
+ >
34
+ <th
35
+ class=""
36
+ >
37
+ name
38
+ </th>
39
+ <th
40
+ class=""
41
+ >
42
+ tags
43
+ </th>
44
+ <th
45
+ class=""
46
+ >
47
+ domain
48
+ </th>
49
+ <th
50
+ class=""
51
+ />
52
+ </tr>
53
+ </thead>
54
+ <tbody
55
+ class=""
56
+ >
57
+ <tr
58
+ class=""
59
+ >
60
+ <td
61
+ class=""
62
+ >
63
+ <a
64
+ href="/concepts/2/versions/current/links/implementations"
65
+ >
66
+ name
67
+ </a>
68
+ </td>
69
+ <td
70
+ class=""
71
+ >
72
+ <ul
73
+ class="concept-link-tags"
74
+ >
75
+ <li>
76
+ sometags
77
+ </li>
78
+ </ul>
79
+ </td>
80
+ <td
81
+ class=""
82
+ >
83
+ domain
84
+ </td>
85
+ <td
86
+ class=""
87
+ >
88
+ <i
89
+ aria-hidden="true"
90
+ class="red trash alternate outline icon"
91
+ />
92
+ </td>
93
+ </tr>
94
+ </tbody>
95
+ </table>
96
+ </div>
97
+ </div>
98
+ </div>
99
+ </div>
100
+ `;
101
+
102
+ exports[`<ImplementationLinks /> matches the latest snapshot when implementations have no tags 1`] = `
103
+ <div>
104
+ <div
105
+ class="ui bottom attached segment"
106
+ >
107
+ <div
108
+ class="ui grid"
109
+ >
110
+ <div
111
+ class="sixteen wide column"
112
+ >
113
+ <a
114
+ class="ui primary right floated button"
115
+ href="/rules/2/implementations/4/links/new"
116
+ role="button"
117
+ >
118
+ create
119
+ </a>
120
+ </div>
121
+ <div
122
+ class="sixteen wide column"
123
+ >
124
+ <table
125
+ class="ui table"
126
+ >
127
+ <thead
128
+ class=""
129
+ >
130
+ <tr
131
+ class=""
132
+ >
133
+ <th
134
+ class=""
135
+ >
136
+ name
137
+ </th>
138
+ <th
139
+ class=""
140
+ >
141
+ domain
142
+ </th>
143
+ <th
144
+ class=""
145
+ />
146
+ </tr>
147
+ </thead>
148
+ <tbody
149
+ class=""
150
+ >
151
+ <tr
152
+ class=""
153
+ >
154
+ <td
155
+ class=""
156
+ >
157
+ <a
158
+ href="/concepts/2/versions/current/links/implementations"
159
+ >
160
+ name
161
+ </a>
162
+ </td>
163
+ <td
164
+ class=""
165
+ >
166
+ domain
167
+ </td>
168
+ <td
169
+ class=""
170
+ >
171
+ <i
172
+ aria-hidden="true"
173
+ class="red trash alternate outline icon"
174
+ />
175
+ </td>
176
+ </tr>
177
+ </tbody>
178
+ </table>
179
+ </div>
180
+ </div>
181
+ </div>
182
+ </div>
183
+ `;
@@ -0,0 +1,70 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`<ImplementationRelationForm /> matches the latest snapshot 1`] = `
4
+ <div>
5
+ <div
6
+ class="ui bottom attached segment"
7
+ style="display: none;"
8
+ >
9
+ <div
10
+ class="ui grid"
11
+ >
12
+ <div
13
+ class="column"
14
+ with="16"
15
+ >
16
+ <h4
17
+ class="ui header"
18
+ >
19
+ header
20
+ </h4>
21
+ <div
22
+ class="field"
23
+ style="margin-bottom: 10px;"
24
+ >
25
+ <label>
26
+ relationType
27
+ </label>
28
+ <div
29
+ style="margin: 6px 0px;"
30
+ >
31
+ <div
32
+ class="ui toggle checkbox"
33
+ >
34
+ <input
35
+ class="hidden"
36
+ name="text"
37
+ readonly=""
38
+ tabindex="0"
39
+ type="checkbox"
40
+ value="2"
41
+ />
42
+ <label>
43
+ text
44
+ </label>
45
+ </div>
46
+ </div>
47
+ </div>
48
+ <div
49
+ class="actions"
50
+ >
51
+ <a
52
+ class="ui secondary button"
53
+ href="/"
54
+ role="button"
55
+ >
56
+ cancel
57
+ </a>
58
+ <button
59
+ class="ui primary disabled button"
60
+ disabled=""
61
+ tabindex="-1"
62
+ >
63
+ create
64
+ </button>
65
+ </div>
66
+ </div>
67
+ </div>
68
+ </div>
69
+ </div>
70
+ `;
@@ -0,0 +1,52 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`<LinksPane /> matches the latest snapshot 1`] = `
4
+ <div>
5
+ <div
6
+ class="ui grid"
7
+ >
8
+ <div
9
+ class="row"
10
+ >
11
+ <div
12
+ class="column"
13
+ >
14
+ <button>
15
+ action
16
+ </button>
17
+ </div>
18
+ </div>
19
+ <div
20
+ class="row"
21
+ >
22
+ <div
23
+ class="column"
24
+ >
25
+ <h3
26
+ class="ui header"
27
+ >
28
+ Master title
29
+ </h3>
30
+ <table
31
+ class="ui table"
32
+ >
33
+ <thead
34
+ class=""
35
+ >
36
+ <tr
37
+ class=""
38
+ />
39
+ </thead>
40
+ <tbody
41
+ class=""
42
+ >
43
+ <tr
44
+ class=""
45
+ />
46
+ </tbody>
47
+ </table>
48
+ </div>
49
+ </div>
50
+ </div>
51
+ </div>
52
+ `;
@@ -84,6 +84,19 @@ exports[`<RelationTagForm /> matches the latest snapshot 1`] = `
84
84
  ingest
85
85
  </span>
86
86
  </div>
87
+ <div
88
+ aria-checked="false"
89
+ aria-selected="false"
90
+ class="item"
91
+ role="option"
92
+ style="pointer-events: all;"
93
+ >
94
+ <span
95
+ class="text"
96
+ >
97
+ implementations
98
+ </span>
99
+ </div>
87
100
  <div
88
101
  aria-checked="false"
89
102
  aria-selected="false"
@@ -7,6 +7,7 @@ import RelationTagsLoader from "./RelationTagsLoader";
7
7
  import Relations from "./Relations";
8
8
  import RelationsLoader from "./RelationsLoader";
9
9
  import RelationsGraphLoader from "./RelationsGraphLoader";
10
+ import ImplementationRelationForm from "./ImplementationRelationForm";
10
11
  import StructureLinks from "./StructureLinks";
11
12
  import StructureRelationForm from "./StructureRelationForm";
12
13
  import TagTypeSelector from "./TagTypeSelector";
@@ -26,6 +27,7 @@ export {
26
27
  Relations,
27
28
  RelationsLoader,
28
29
  RelationsGraphLoader,
30
+ ImplementationRelationForm,
29
31
  StructureLinks,
30
32
  StructureRelationForm,
31
33
  TagTypeSelector,
@@ -33,5 +35,5 @@ export {
33
35
  RelationRoutes,
34
36
  RelationTags,
35
37
  RelationTagCards,
36
- NewRelationTag
38
+ NewRelationTag,
37
39
  };
@@ -1,8 +1,8 @@
1
1
  export default {
2
+ "implementations.relation.new.header": "Link to Concept",
2
3
  "links.actions.create": "Add Link",
3
4
  "relations.actions.create": "Add relation",
4
5
  "relations.empty": "Not found relations",
5
-
6
6
  "relations.relationType": "Relation types",
7
7
  "relation.actions.delete.confirmation.header": "Delete Relation",
8
8
  "relation.actions.delete.confirmation.content":
@@ -19,6 +19,7 @@ export default {
19
19
  "relationTags.props.type.placeholder": "Type",
20
20
  "relationTags.props.target_type.placeholder": "Target type",
21
21
  "target_type.business_concept": "Concept",
22
+ "target_type.implementations": "Implementations",
22
23
  "target_type.ingest": "Ingest",
23
- "target_type.data_field": "Structure"
24
+ "target_type.data_field": "Structure",
24
25
  };
@@ -1,4 +1,6 @@
1
1
  export default {
2
+ "implementations.relation.new.header":
3
+ "Vas a vincular esta implementación a un término del glosario",
2
4
  "links.actions.create": "Añadir vinculación",
3
5
  "relations.actions.create": "Añadir relación",
4
6
  "relations.empty": "No se han encontrado relaciones",
@@ -19,6 +21,7 @@ export default {
19
21
  "relationTags.props.type.placeholder": "Código",
20
22
  "relationTags.props.target_type.placeholder": "Destino",
21
23
  "target_type.business_concept": "Concepto",
24
+ "target_type.implementations": "Implementaciones",
22
25
  "target_type.ingest": "Ingesta",
23
- "target_type.data_field": "Estructura"
26
+ "target_type.data_field": "Estructura",
24
27
  };
@@ -3,7 +3,7 @@ import { testSaga } from "redux-saga-test-plan";
3
3
  import { apiJsonDelete, JSON_OPTS } from "@truedat/core/services/api";
4
4
  import {
5
5
  deleteRelationSaga,
6
- deleteRelationRequestSaga
6
+ deleteRelationRequestSaga,
7
7
  } from "../deleteRelation";
8
8
  import { deleteRelation } from "../../routines";
9
9
  import { API_RELATIONS_ID } from "../../api";
@@ -41,7 +41,7 @@ describe("sagas: deleteRelationSaga", () => {
41
41
  .put({ meta, ...deleteRelation.request() })
42
42
  .next()
43
43
  .call(apiJsonDelete, url, JSON_OPTS)
44
- .next({ data: payload })
44
+ .next({ data: "" })
45
45
  .put({ meta, ...deleteRelation.success(payload) })
46
46
  .next()
47
47
  .put(deleteRelation.fulfill())
@@ -12,8 +12,8 @@ export function* deleteRelationSaga({ payload }) {
12
12
  const meta = { id };
13
13
  const url = toApiPath(payload);
14
14
  yield put({ meta, ...deleteRelation.request() });
15
- const { data } = yield call(apiJsonDelete, url, JSON_OPTS);
16
- yield put({ meta, ...deleteRelation.success(data) });
15
+ yield call(apiJsonDelete, url, JSON_OPTS);
16
+ yield put({ meta, ...deleteRelation.success(payload) });
17
17
  } catch (error) {
18
18
  if (error.response) {
19
19
  const { status, data } = error.response;
@@ -0,0 +1,11 @@
1
+ import { getImplementationToConceptLinks } from "..";
2
+
3
+ const conceptLink = { resource_type: "concept" };
4
+ describe("selectors: getGrantAvailableFilters", () => {
5
+ const implementationLinks = [conceptLink, { resource_type: "other" }];
6
+ const state = { implementationLinks };
7
+
8
+ it("should return implementation links with 'concept' resource_type", () => {
9
+ expect(getImplementationToConceptLinks(state)).toEqual([conceptLink]);
10
+ });
11
+ });
@@ -0,0 +1,9 @@
1
+ import _ from "lodash/fp";
2
+ import { createSelector } from "reselect";
3
+
4
+ const getImplementationLinks = (state) => state.implementationLinks;
5
+
6
+ export const getImplementationToConceptLinks = createSelector(
7
+ getImplementationLinks,
8
+ _.filter(_.propEq("resource_type", "concept"))
9
+ );
@@ -1 +1,2 @@
1
1
  export * from "./getStructureLinks";
2
+ export * from "./getImplementationToConceptLinks";