@truedat/bg 7.10.4 → 7.11.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.
Files changed (25) hide show
  1. package/package.json +3 -3
  2. package/src/concepts/components/ConceptLinksUploadButton.js +0 -4
  3. package/src/concepts/components/ConceptRoutes.js +28 -46
  4. package/src/concepts/components/__tests__/ConceptRoutes.spec.js +201 -0
  5. package/src/concepts/components/__tests__/__snapshots__/ConceptRoutes.spec.js.snap +165 -0
  6. package/src/concepts/relations/api.js +9 -1
  7. package/src/concepts/relations/components/ConceptLinksApprovalResults.js +91 -0
  8. package/src/concepts/relations/components/ConceptLinksApprovals.js +225 -0
  9. package/src/concepts/relations/components/ConceptLinksApprovalsLabelResults.js +42 -0
  10. package/src/concepts/relations/components/ConceptLinksApprovalsRow.js +43 -0
  11. package/src/concepts/relations/components/ConceptLinksApprovalsTable.js +104 -0
  12. package/src/concepts/relations/components/__tests__/ConceptLinksApprovalResults.spec.js +123 -0
  13. package/src/concepts/relations/components/__tests__/ConceptLinksApprovals.spec.js +77 -0
  14. package/src/concepts/relations/components/__tests__/ConceptLinksApprovalsLabelResults.spec.js +47 -0
  15. package/src/concepts/relations/components/__tests__/ConceptLinksApprovalsRow.spec.js +68 -0
  16. package/src/concepts/relations/components/__tests__/ConceptLinksApprovalsTable.spec.js +107 -0
  17. package/src/concepts/relations/components/__tests__/__snapshots__/ConceptLinksApprovalResults.spec.js.snap +217 -0
  18. package/src/concepts/relations/components/__tests__/__snapshots__/ConceptLinksApprovals.spec.js.snap +559 -0
  19. package/src/concepts/relations/components/__tests__/__snapshots__/ConceptLinksApprovalsLabelResults.spec.js.snap +34 -0
  20. package/src/concepts/relations/components/__tests__/__snapshots__/ConceptLinksApprovalsRow.spec.js.snap +47 -0
  21. package/src/concepts/relations/components/__tests__/__snapshots__/ConceptLinksApprovalsTable.spec.js.snap +132 -0
  22. package/src/concepts/relations/hooks/useLinks.js +25 -0
  23. package/src/concepts/relations/selectors/getLinksSearchColumns.js +106 -0
  24. package/src/concepts/relations/selectors/index.js +2 -0
  25. package/src/concepts/relations/styles/ConceptLinksApprovals.less +15 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@truedat/bg",
3
- "version": "7.10.4",
3
+ "version": "7.11.0",
4
4
  "description": "Truedat Web Business Glossary",
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.4",
51
+ "@truedat/test": "7.11.0",
52
52
  "identity-obj-proxy": "^3.0.0",
53
53
  "jest": "^29.7.0",
54
54
  "redux-saga-test-plan": "^4.0.6"
@@ -81,5 +81,5 @@
81
81
  "semantic-ui-react": "^3.0.0-beta.2",
82
82
  "swr": "^2.3.3"
83
83
  },
84
- "gitHead": "0dcedbfaed3964ee02a3d2c175e0f96e2fbc9316"
84
+ "gitHead": "c8c0d3e62835df86440869c7b797bbe2fc12f3a6"
85
85
  }
@@ -38,18 +38,14 @@ export const ConceptLinksUploadButton = () => {
38
38
  triggerUpload(data)
39
39
  .then(({ status, data }) => {
40
40
  const alertMessage = getUploadAlertMessage({ status, data });
41
- console.log("alertMessage --->", alertMessage);
42
41
  setAlertMessage(alertMessage);
43
42
  })
44
43
  .catch((error) => {
45
- console.log("error --->", error);
46
44
  if (error.response) {
47
45
  const { status, data } = error.response;
48
46
  const alertMessage = getUploadAlertMessage({ status, data });
49
- console.log("error response alertMessage --->", alertMessage);
50
47
  setAlertMessage(alertMessage);
51
48
  } else {
52
- console.log("error.message --->", error.message);
53
49
  setAlertMessage(error.message);
54
50
  }
55
51
  });
@@ -4,8 +4,7 @@ import PropTypes from "prop-types";
4
4
  import { Route, useParams, Routes } from "react-router";
5
5
  import { connect } from "react-redux";
6
6
  import { ProtectedRoute } from "@truedat/core/router";
7
- // import { Unauthorized } from "@truedat/core/components";
8
- // import { useAuthorized } from "@truedat/core/hooks";
7
+
9
8
  import {
10
9
  CONCEPT_EDIT,
11
10
  CONCEPT_LINKS_MANAGEMENT,
@@ -25,6 +24,8 @@ import {
25
24
  } from "@truedat/core/routes";
26
25
  import { useIntl } from "react-intl";
27
26
  import { getSidemenuGlossarySubscopes } from "@truedat/core/selectors/getSidemenuGlossarySubscopes";
27
+ import { SearchContextProvider } from "@truedat/core/search/SearchContext";
28
+ import { useLinksSearch, useLinksFilters } from "../relations/hooks/useLinks";
28
29
  import Concept from "./Concept";
29
30
  import ConceptCrumbs from "./ConceptCrumbs";
30
31
  import ConceptEdit from "./ConceptEdit";
@@ -36,6 +37,8 @@ import ConceptsBulkUpdate from "./ConceptsBulkUpdate";
36
37
 
37
38
  import ConceptSubscriptionLoader from "./ConceptSubscriptionLoader";
38
39
  import ConceptsUploadEvents from "./ConceptsUploadEvents";
40
+ import ConceptLinksApprovals from "../relations/components/ConceptLinksApprovals";
41
+ import ConceptLinksApprovalResults from "../relations/components/ConceptLinksApprovalResults";
39
42
 
40
43
  const RelationTagsLoader = React.lazy(
41
44
  () => import("@truedat/lm/components/RelationTagsLoader")
@@ -85,13 +88,6 @@ const ConceptRoutes = ({
85
88
  sidemenuGlossarySubscopes,
86
89
  }) => {
87
90
  const { formatMessage } = useIntl();
88
- // const authorized = useAuthorized([
89
- // "business_glossary_view",
90
- // "business_glossary_management",
91
- // ]);
92
- // if (!authorized) {
93
- // return <Unauthorized />;
94
- // }
95
91
 
96
92
  return (
97
93
  <Routes>
@@ -181,6 +177,29 @@ const ConceptRoutes = ({
181
177
  element={<ConceptsUploadEvents />}
182
178
  />
183
179
 
180
+ <Route
181
+ path="approvals/results"
182
+ element={<ConceptLinksApprovalResults />}
183
+ />
184
+
185
+ <Route
186
+ path="approvals"
187
+ element={
188
+ <>
189
+ <SearchContextProvider
190
+ initialSortColumn="updated_at"
191
+ initialSortDirection="ascending"
192
+ useSearch={useLinksSearch}
193
+ useFilters={useLinksFilters}
194
+ pageSize={20}
195
+ defaultFilters={{ status: ["pending"] }}
196
+ >
197
+ <ConceptLinksApprovals />
198
+ </SearchContextProvider>
199
+ </>
200
+ }
201
+ />
202
+
184
203
  <Route
185
204
  // CONCEPT_LINKS_MANAGEMENT = "/concepts/management/links";
186
205
  path="links"
@@ -251,43 +270,6 @@ const ConceptRoutes = ({
251
270
  />
252
271
  </Route>
253
272
 
254
- {/* <Route
255
- path={CONCEPTS_NEW}
256
- element={
257
- <>
258
- <ConceptCrumbs conceptAction="concepts.actions.create" />
259
- <ConceptCreate />
260
- </>
261
- }
262
- /> */}
263
-
264
- <Route path="glossaryManagement">
265
- <Route
266
- // CONCEPTS_SIDEMENU_MANAGEMENT_UPLOADS = "/glossaryManagement/uploads";
267
- path="uploads"
268
- element={<ConceptsUploadEvents />}
269
- />
270
- <Route
271
- // CONCEPTS_SIDEMENU_MANAGEMENT_LINKS = "/glossaryManagement/links";
272
- path="links"
273
- element={
274
- <>
275
- <RelationTagsLoader />
276
- <ConceptsLinksManagement
277
- header={formatMessage({ id: "concepts.header.linksManager" })}
278
- subheader={formatMessage({
279
- id: "concepts.subheader.linksManager",
280
- })}
281
- icon={formatMessage({
282
- id: "concepts.header.linksManager.icon",
283
- defaultMessage: "linkify",
284
- })}
285
- />
286
- </>
287
- }
288
- />
289
- </Route>
290
-
291
273
  <Route path="glossarySubscope">
292
274
  <Route
293
275
  // CONCEPTS_SIDEMENU_SUBSCOPE = "/glossarySubscope/:subscope/published";
@@ -0,0 +1,201 @@
1
+ import React from "react";
2
+ import { render, waitForLoad } from "@truedat/test/render";
3
+ import { useAuthorized } from "@truedat/core/hooks/useAuthorized";
4
+ import {
5
+ CONCEPTS,
6
+ CONCEPTS_PENDING,
7
+ CONCEPTS_DEPRECATED,
8
+ CONCEPTS_NEW,
9
+ CONCEPTS_BULK_UPDATE,
10
+ CONCEPTS_BULK_UPLOAD_EVENTS,
11
+ CONCEPT_LINKS_MANAGEMENT,
12
+ CONCEPT_VERSION,
13
+ CONCEPT_EDIT,
14
+ CONCEPTS_SUBSCOPE,
15
+ CONCEPTS_SIDEMENU_SUBSCOPE,
16
+ CONCEPTS_SIDEMENU_SUBSCOPE_PENDING,
17
+ CONCEPTS_SIDEMENU_SUBSCOPE_DEPRECATED,
18
+ } from "@truedat/core/routes";
19
+ import ConceptRoutes from "../ConceptRoutes";
20
+
21
+ jest.mock("@truedat/core/hooks/useAuthorized", () => ({
22
+ useAuthorized: jest.fn(() => true),
23
+ }));
24
+
25
+ jest.mock("../Concept", () => () => <div>Concept</div>);
26
+ jest.mock("../ConceptCrumbs", () => () => <div>ConceptCrumbs</div>);
27
+ jest.mock("../ConceptEdit", () => () => <div>ConceptEdit</div>);
28
+ jest.mock("../ConceptCreate", () => () => <div>ConceptCreate</div>);
29
+ jest.mock("../ConceptsLinksManagement", () => () => (
30
+ <div>ConceptsLinksManagement</div>
31
+ ));
32
+ jest.mock("../ConceptLoader", () => () => <div>ConceptLoader</div>);
33
+ jest.mock("../Concepts", () => () => <div>Concepts</div>);
34
+ jest.mock("../ConceptsBulkUpdate", () => () => <div>ConceptsBulkUpdate</div>);
35
+ jest.mock("../ConceptSubscriptionLoader", () => () => (
36
+ <div>ConceptSubscriptionLoader</div>
37
+ ));
38
+ jest.mock("../ConceptsUploadEvents", () => () => (
39
+ <div>ConceptsUploadEvents</div>
40
+ ));
41
+ jest.mock("../../relations/components/ConceptLinksApprovals", () => () => (
42
+ <div>ConceptLinksApprovals</div>
43
+ ));
44
+ jest.mock(
45
+ "../../relations/components/ConceptLinksApprovalResults",
46
+ () => () => <div>ConceptsLinksApprovalsResults</div>
47
+ );
48
+ jest.mock("@truedat/lm/components/RelationTagsLoader", () => () => (
49
+ <div>RelationTagsLoader</div>
50
+ ));
51
+ jest.mock("@truedat/core/components/TemplatesLoader", () => () => (
52
+ <div>TemplatesLoader</div>
53
+ ));
54
+ jest.mock("@truedat/lm/components/RelationsGraphLoader", () => () => (
55
+ <div>RelationsGraphLoader</div>
56
+ ));
57
+
58
+ describe("<ConceptRoutes />", () => {
59
+ it("renders correctly with default route", async () => {
60
+ const rendered = render(<ConceptRoutes />);
61
+ await waitForLoad(rendered);
62
+ expect(rendered.container).toMatchSnapshot();
63
+ });
64
+
65
+ it("renders correctly with concepts route", async () => {
66
+ const rendered = render(<ConceptRoutes />, {
67
+ routes: [CONCEPTS],
68
+ });
69
+ await waitForLoad(rendered);
70
+ expect(rendered.container).toMatchSnapshot();
71
+ });
72
+
73
+ it("renders correctly with pending concepts route", async () => {
74
+ const rendered = render(<ConceptRoutes />, {
75
+ routes: [CONCEPTS_PENDING],
76
+ });
77
+ await waitForLoad(rendered);
78
+ expect(rendered.container).toMatchSnapshot();
79
+ });
80
+
81
+ it("renders correctly with deprecated concepts route", async () => {
82
+ const rendered = render(<ConceptRoutes />, {
83
+ routes: [CONCEPTS_DEPRECATED],
84
+ });
85
+ await waitForLoad(rendered);
86
+ expect(rendered.container).toMatchSnapshot();
87
+ });
88
+
89
+ it("renders correctly with new concept route", async () => {
90
+ const rendered = render(<ConceptRoutes />, {
91
+ routes: [CONCEPTS_NEW],
92
+ });
93
+ await waitForLoad(rendered);
94
+ expect(rendered.container).toMatchSnapshot();
95
+ });
96
+
97
+ it("renders correctly with bulk update route", async () => {
98
+ const rendered = render(<ConceptRoutes />, {
99
+ routes: [CONCEPTS_BULK_UPDATE],
100
+ state: {
101
+ templatesLoading: false,
102
+ templates: { some: "template" },
103
+ },
104
+ });
105
+ await waitForLoad(rendered);
106
+ expect(rendered.container).toMatchSnapshot();
107
+ });
108
+
109
+ it("renders correctly with bulk upload events route", async () => {
110
+ const rendered = render(<ConceptRoutes />, {
111
+ routes: [CONCEPTS_BULK_UPLOAD_EVENTS],
112
+ });
113
+ await waitForLoad(rendered);
114
+ expect(rendered.container).toMatchSnapshot();
115
+ });
116
+
117
+ it("renders correctly with links management route", async () => {
118
+ const rendered = render(<ConceptRoutes />, {
119
+ routes: [CONCEPT_LINKS_MANAGEMENT],
120
+ });
121
+ await waitForLoad(rendered);
122
+ expect(rendered.container).toMatchSnapshot();
123
+ });
124
+
125
+ it("renders correctly with concept version route", async () => {
126
+ const rendered = render(<ConceptRoutes />, {
127
+ routes: [
128
+ CONCEPT_VERSION.replace(":business_concept_id", "123").replace(
129
+ ":id",
130
+ "456"
131
+ ),
132
+ ],
133
+ state: {
134
+ concept: { business_concept_id: "123" },
135
+ },
136
+ });
137
+ await waitForLoad(rendered);
138
+ expect(rendered.container).toMatchSnapshot();
139
+ });
140
+
141
+ it("renders correctly with concept edit route", async () => {
142
+ const rendered = render(<ConceptRoutes />, {
143
+ routes: [
144
+ CONCEPT_EDIT.replace(":business_concept_id", "123").replace(
145
+ ":id",
146
+ "456"
147
+ ),
148
+ ],
149
+ state: {
150
+ concept: { business_concept_id: "123" },
151
+ templatesLoading: false,
152
+ templates: { some: "template" },
153
+ },
154
+ });
155
+ await waitForLoad(rendered);
156
+ expect(rendered.container).toMatchSnapshot();
157
+ });
158
+
159
+ it("renders correctly with subscope route", async () => {
160
+ const rendered = render(<ConceptRoutes />, {
161
+ routes: [CONCEPTS_SUBSCOPE.replace(":subscope", "test")],
162
+ });
163
+ await waitForLoad(rendered);
164
+ expect(rendered.container).toMatchSnapshot();
165
+ });
166
+
167
+ it("renders correctly with sidemenu subscope route", async () => {
168
+ const rendered = render(<ConceptRoutes />, {
169
+ routes: [CONCEPTS_SIDEMENU_SUBSCOPE.replace(":subscope", "test")],
170
+ });
171
+ await waitForLoad(rendered);
172
+ expect(rendered.container).toMatchSnapshot();
173
+ });
174
+
175
+ it("renders correctly with sidemenu subscope pending route", async () => {
176
+ const rendered = render(<ConceptRoutes />, {
177
+ routes: [CONCEPTS_SIDEMENU_SUBSCOPE_PENDING.replace(":subscope", "test")],
178
+ });
179
+ await waitForLoad(rendered);
180
+ expect(rendered.container).toMatchSnapshot();
181
+ });
182
+
183
+ it("renders correctly with sidemenu subscope deprecated route", async () => {
184
+ const rendered = render(<ConceptRoutes />, {
185
+ routes: [
186
+ CONCEPTS_SIDEMENU_SUBSCOPE_DEPRECATED.replace(":subscope", "test"),
187
+ ],
188
+ });
189
+ await waitForLoad(rendered);
190
+ expect(rendered.container).toMatchSnapshot();
191
+ });
192
+
193
+ it("renders unauthorized component when not authorized", async () => {
194
+ useAuthorized.mockReturnValueOnce(false);
195
+ const rendered = render(<ConceptRoutes />, {
196
+ routes: [CONCEPTS],
197
+ });
198
+ await waitForLoad(rendered);
199
+ expect(rendered.container).toMatchSnapshot();
200
+ });
201
+ });
@@ -0,0 +1,165 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`<ConceptRoutes /> renders correctly with bulk update route 1`] = `
4
+ <div>
5
+ <div>
6
+ ConceptCrumbs
7
+ </div>
8
+ <div>
9
+ TemplatesLoader
10
+ </div>
11
+ <div>
12
+ ConceptsBulkUpdate
13
+ </div>
14
+ </div>
15
+ `;
16
+
17
+ exports[`<ConceptRoutes /> renders correctly with bulk upload events route 1`] = `
18
+ <div>
19
+ <div>
20
+ ConceptsUploadEvents
21
+ </div>
22
+ </div>
23
+ `;
24
+
25
+ exports[`<ConceptRoutes /> renders correctly with concept edit route 1`] = `
26
+ <div>
27
+ <div>
28
+ ConceptCrumbs
29
+ </div>
30
+ <div>
31
+ TemplatesLoader
32
+ </div>
33
+ <div>
34
+ ConceptLoader
35
+ </div>
36
+ <div>
37
+ ConceptEdit
38
+ </div>
39
+ </div>
40
+ `;
41
+
42
+ exports[`<ConceptRoutes /> renders correctly with concept version route 1`] = `
43
+ <div>
44
+ <div>
45
+ ConceptLoader
46
+ </div>
47
+ <div>
48
+ RelationTagsLoader
49
+ </div>
50
+ <div>
51
+ ConceptSubscriptionLoader
52
+ </div>
53
+ <div>
54
+ RelationsGraphLoader
55
+ </div>
56
+ <div>
57
+ Concept
58
+ </div>
59
+ </div>
60
+ `;
61
+
62
+ exports[`<ConceptRoutes /> renders correctly with concepts route 1`] = `
63
+ <div>
64
+ <div>
65
+ Concepts
66
+ </div>
67
+ </div>
68
+ `;
69
+
70
+ exports[`<ConceptRoutes /> renders correctly with default route 1`] = `<div />`;
71
+
72
+ exports[`<ConceptRoutes /> renders correctly with deprecated concepts route 1`] = `
73
+ <div>
74
+ <div>
75
+ Concepts
76
+ </div>
77
+ </div>
78
+ `;
79
+
80
+ exports[`<ConceptRoutes /> renders correctly with links management route 1`] = `
81
+ <div>
82
+ <div>
83
+ RelationTagsLoader
84
+ </div>
85
+ <div>
86
+ ConceptsLinksManagement
87
+ </div>
88
+ </div>
89
+ `;
90
+
91
+ exports[`<ConceptRoutes /> renders correctly with new concept route 1`] = `
92
+ <div>
93
+ <div>
94
+ ConceptCrumbs
95
+ </div>
96
+ <div>
97
+ ConceptCreate
98
+ </div>
99
+ </div>
100
+ `;
101
+
102
+ exports[`<ConceptRoutes /> renders correctly with pending concepts route 1`] = `
103
+ <div>
104
+ <div>
105
+ Concepts
106
+ </div>
107
+ </div>
108
+ `;
109
+
110
+ exports[`<ConceptRoutes /> renders correctly with sidemenu subscope deprecated route 1`] = `
111
+ <div>
112
+ <div>
113
+ Concepts
114
+ </div>
115
+ </div>
116
+ `;
117
+
118
+ exports[`<ConceptRoutes /> renders correctly with sidemenu subscope pending route 1`] = `
119
+ <div>
120
+ <div>
121
+ Concepts
122
+ </div>
123
+ </div>
124
+ `;
125
+
126
+ exports[`<ConceptRoutes /> renders correctly with sidemenu subscope route 1`] = `
127
+ <div>
128
+ <div>
129
+ Concepts
130
+ </div>
131
+ </div>
132
+ `;
133
+
134
+ exports[`<ConceptRoutes /> renders correctly with subscope route 1`] = `
135
+ <div>
136
+ <div>
137
+ Concepts
138
+ </div>
139
+ </div>
140
+ `;
141
+
142
+ exports[`<ConceptRoutes /> renders unauthorized component when not authorized 1`] = `
143
+ <div>
144
+ <div
145
+ class="ui icon visible bottom attached message center"
146
+ >
147
+ <i
148
+ aria-hidden="true"
149
+ class="warning sign icon"
150
+ />
151
+ <div
152
+ class="content"
153
+ >
154
+ <div
155
+ class="header"
156
+ >
157
+ view.unauthorized.head
158
+ </div>
159
+ <p>
160
+ view.unauthorized.content
161
+ </p>
162
+ </div>
163
+ </div>
164
+ </div>
165
+ `;
@@ -1,3 +1,11 @@
1
1
  const API_RELATIONS = "/api/relations";
2
+ const API_RELATIONS_SEARCH = "/api/relations/index_search";
3
+ const API_RELATIONS_FILTERS = "/api/relations/filters";
4
+ const API_RELATIONS_BULK_UPDATE_STATUS = "/api/relations/status";
2
5
 
3
- export { API_RELATIONS };
6
+ export {
7
+ API_RELATIONS,
8
+ API_RELATIONS_SEARCH,
9
+ API_RELATIONS_FILTERS,
10
+ API_RELATIONS_BULK_UPDATE_STATUS,
11
+ };
@@ -0,0 +1,91 @@
1
+ import _ from "lodash/fp";
2
+ import { useIntl, FormattedMessage } from "react-intl";
3
+ import { Breadcrumb, Message } from "semantic-ui-react";
4
+ import { useLocation, Link } from "react-router";
5
+ import { linkTo } from "@truedat/core/routes";
6
+ import { CONCEPT_LINKS_APPROVALS } from "@truedat/core/routes";
7
+
8
+ export default function LinksApprovalResults() {
9
+ const { state } = useLocation();
10
+
11
+ if (!state) {
12
+ window.location.replace(CONCEPT_LINKS_APPROVALS);
13
+ return null;
14
+ }
15
+
16
+ const { formatMessage } = useIntl();
17
+ const relations = state?.relations || [];
18
+ const isRejection = state.status === "rejected";
19
+
20
+ const relationDecorator = (relation) => (
21
+ <Message.Item key={relation.id}>
22
+ <FormattedMessage id="conceptRelations.approvals.results.message.source_name" />
23
+ <Link
24
+ to={linkTo.CONCEPT_VERSION({
25
+ business_concept_id: relation.source_id,
26
+ id: "current",
27
+ })}
28
+ target="_blank"
29
+ >
30
+ {relation.source_name}
31
+ </Link>
32
+ {relation.tag_type ? (
33
+ <FormattedMessage
34
+ id="conceptRelations.approvals.results.message.tag_type"
35
+ values={{ tag_type: relation.tag_type }}
36
+ />
37
+ ) : null}
38
+ <FormattedMessage id="conceptRelations.approvals.results.message.target_name" />
39
+ <Link
40
+ to={linkTo.STRUCTURE({
41
+ id: relation.target_id,
42
+ })}
43
+ target="_blank"
44
+ >
45
+ {relation.target_name}
46
+ </Link>
47
+ </Message.Item>
48
+ );
49
+
50
+ return (
51
+ <>
52
+ <Breadcrumb>
53
+ <Breadcrumb.Section
54
+ as={Link}
55
+ to={CONCEPT_LINKS_APPROVALS}
56
+ active={false}
57
+ >
58
+ {formatMessage({ id: "conceptRelations.approvals.header" })}
59
+ </Breadcrumb.Section>
60
+ <Breadcrumb.Divider icon="right angle" />
61
+ <Breadcrumb.Section active>
62
+ {formatMessage({ id: "conceptRelations.approvals.results.header" })}
63
+ </Breadcrumb.Section>
64
+ </Breadcrumb>
65
+
66
+ {_.isEmpty(relations) ? null : (
67
+ <Message positive={!isRejection} warning={isRejection}>
68
+ <Message.Header>
69
+ {formatMessage({
70
+ id: `conceptRelations.approvals.results.${state.status}.header`,
71
+ })}
72
+ </Message.Header>
73
+ {_.map((relation) => relationDecorator(relation))(state.relations)}
74
+ </Message>
75
+ )}
76
+
77
+ {_.map((errorGroup) => (
78
+ <Message error key={errorGroup.reason}>
79
+ <Message.Header>
80
+ {formatMessage({
81
+ id: `conceptRelations.approvals.results.errors.${errorGroup.reason}.header`,
82
+ })}
83
+ </Message.Header>
84
+ {_.map((relation) => relationDecorator(relation))(
85
+ errorGroup.relations
86
+ )}
87
+ </Message>
88
+ ))(state.errors)}
89
+ </>
90
+ );
91
+ }