@truedat/bg 7.4.1 → 7.4.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@truedat/bg",
3
- "version": "7.4.1",
3
+ "version": "7.4.2",
4
4
  "description": "Truedat Web Business Glossary",
5
5
  "sideEffects": false,
6
6
  "jsnext:main": "src/index.js",
@@ -34,7 +34,7 @@
34
34
  "@testing-library/jest-dom": "^5.16.5",
35
35
  "@testing-library/react": "^12.0.0",
36
36
  "@testing-library/user-event": "^13.2.1",
37
- "@truedat/test": "7.4.1",
37
+ "@truedat/test": "7.4.2",
38
38
  "babel-jest": "^28.1.0",
39
39
  "babel-plugin-dynamic-import-node": "^2.3.3",
40
40
  "babel-plugin-lodash": "^3.3.4",
@@ -86,9 +86,9 @@
86
86
  ]
87
87
  },
88
88
  "dependencies": {
89
- "@truedat/core": "7.4.1",
90
- "@truedat/df": "7.4.1",
91
- "@truedat/lm": "7.4.1",
89
+ "@truedat/core": "7.4.2",
90
+ "@truedat/df": "7.4.2",
91
+ "@truedat/lm": "7.4.2",
92
92
  "decode-uri-component": "^0.2.2",
93
93
  "file-saver": "^2.0.5",
94
94
  "moment": "^2.29.4",
@@ -111,5 +111,5 @@
111
111
  "react-dom": ">= 16.8.6 < 17",
112
112
  "semantic-ui-react": ">= 2.0.3 < 2.2"
113
113
  },
114
- "gitHead": "3f0fb3cef1d8e517ab71b741d8e4617319960f58"
114
+ "gitHead": "bfc4801642aec7b29767605d30866b52f67aacce"
115
115
  }
@@ -4,6 +4,7 @@ import PropTypes from "prop-types";
4
4
  import { connect } from "react-redux";
5
5
  import { Route } from "react-router-dom";
6
6
  import { Grid, Segment } from "semantic-ui-react";
7
+ import StructureGrantCart from "@truedat/dd/components/StructureGrantCart";
7
8
  import { Comments, CommentsLoader } from "@truedat/core/components";
8
9
  import { CONCEPT_VERSION } from "@truedat/core/routes";
9
10
  import { useIntl } from "react-intl";
@@ -19,6 +20,7 @@ import ConceptTabPane from "./ConceptTabPane";
19
20
  import ConceptSummary from "./ConceptSummary";
20
21
  import ConceptTaxonomy from "./ConceptTaxonomy";
21
22
  import SharedToDomains from "./SharedToDomains";
23
+ import ConceptContextProvider, { useConceptContext } from "./ConceptContext";
22
24
 
23
25
  const EventsLoader = React.lazy(() =>
24
26
  import("@truedat/audit/components/EventsLoader")
@@ -35,6 +37,9 @@ const ConceptRulesLoader = React.lazy(() =>
35
37
  export const Concept = ({ id, concept }) => {
36
38
  const { locales } = useLanguage();
37
39
  const { locale } = useIntl();
40
+ const {
41
+ conceptContext: { grantView },
42
+ } = useConceptContext();
38
43
 
39
44
  const defaultLang = _.flow(
40
45
  _.find({ is_default: true }),
@@ -128,11 +133,17 @@ export const Concept = ({ id, concept }) => {
128
133
  />
129
134
  </Grid.Column>
130
135
  <Grid.Column width={4}>
131
- <ConceptSummary />
132
- <ConceptRelationsSummary />
133
- <ConceptTaxonomy />
134
- <SharedToDomains />
135
- <ConceptCompleteness />
136
+ {grantView ? (
137
+ <StructureGrantCart allowEmptyCart />
138
+ ) : (
139
+ <>
140
+ <ConceptSummary />
141
+ <ConceptRelationsSummary />
142
+ <ConceptTaxonomy />
143
+ <SharedToDomains />
144
+ <ConceptCompleteness />
145
+ </>
146
+ )}
136
147
  </Grid.Column>
137
148
  </Grid>
138
149
  </>
@@ -144,9 +155,20 @@ Concept.propTypes = {
144
155
  concept: PropTypes.object,
145
156
  };
146
157
 
158
+ const ConceptWrapper = ({ id, concept }) => (
159
+ <ConceptContextProvider>
160
+ <Concept id={id} concept={concept} />
161
+ </ConceptContextProvider>
162
+ );
163
+
164
+ ConceptWrapper.propTypes = {
165
+ id: PropTypes.number.isRequired,
166
+ concept: PropTypes.object,
167
+ };
168
+
147
169
  const mapStateToProps = ({ concept }) => ({
148
170
  id: _.prop("business_concept_id")(concept),
149
171
  concept: concept,
150
172
  });
151
173
 
152
- export default connect(mapStateToProps)(Concept);
174
+ export default connect(mapStateToProps)(ConceptWrapper);
@@ -0,0 +1,37 @@
1
+ import React, { createContext, useState, useContext } from "react";
2
+ import PropTypes from "prop-types";
3
+
4
+ export const ConceptContext = createContext("conceptContext");
5
+
6
+ const initialContext = {
7
+ grantView: false,
8
+ };
9
+
10
+ export const ConceptContextProvider = ({ children }) => {
11
+ const [context, setContext] = useState(initialContext);
12
+
13
+ const setProperty = (value) =>
14
+ setContext({
15
+ ...context,
16
+ ...value,
17
+ });
18
+
19
+ return (
20
+ <ConceptContext.Provider
21
+ value={{
22
+ conceptContext: context,
23
+ setConceptContextProperty: setProperty,
24
+ }}
25
+ >
26
+ {children}
27
+ </ConceptContext.Provider>
28
+ );
29
+ };
30
+
31
+ ConceptContextProvider.propTypes = {
32
+ children: PropTypes.node,
33
+ };
34
+
35
+ export const useConceptContext = () => useContext(ConceptContext);
36
+
37
+ export default ConceptContextProvider;
@@ -3,6 +3,31 @@ import { render } from "@truedat/test/render";
3
3
  import { waitFor } from "@testing-library/react";
4
4
  import { LangProviderWrapper } from "@truedat/core/i18n";
5
5
  import { Concept } from "../Concept";
6
+ import ConceptContextProvider, { useConceptContext } from "../ConceptContext";
7
+
8
+ jest.mock("../ConceptContext", () => {
9
+ const originalModule = jest.requireActual("../ConceptContext");
10
+
11
+ return {
12
+ __esModule: true,
13
+ ...originalModule,
14
+ useConceptContext: jest.fn(),
15
+ };
16
+ });
17
+
18
+ jest.mock("@truedat/core/hooks", () => {
19
+ const originalModule = jest.requireActual("@truedat/core/hooks");
20
+
21
+ return {
22
+ __esModule: true,
23
+ ...originalModule,
24
+ useOperators: () => ({
25
+ data: {},
26
+ error: false,
27
+ loading: false,
28
+ }),
29
+ };
30
+ });
6
31
 
7
32
  beforeAll(() => {
8
33
  jest.useFakeTimers();
@@ -116,9 +141,33 @@ describe("<Concept />", () => {
116
141
  };
117
142
 
118
143
  it("matches the latest snapshot", async () => {
144
+ useConceptContext.mockReturnValue({
145
+ conceptContext: { grantView: false },
146
+ });
147
+ const { container, queryByText } = render(
148
+ <LangProviderWrapper langs={["es", "en"]}>
149
+ <ConceptContextProvider>
150
+ <Concept {...props} />
151
+ </ConceptContextProvider>
152
+ </LangProviderWrapper>,
153
+ renderOpts
154
+ );
155
+ await waitFor(() => {
156
+ expect(queryByText(/lazy/i)).not.toBeInTheDocument();
157
+ expect(container.querySelector(".loading")).not.toBeInTheDocument();
158
+ });
159
+ expect(container).toMatchSnapshot();
160
+ });
161
+
162
+ it("matches the latest snapshot with grantView activated", async () => {
163
+ useConceptContext.mockReturnValue({
164
+ conceptContext: { grantView: true },
165
+ });
119
166
  const { container, queryByText } = render(
120
167
  <LangProviderWrapper langs={["es", "en"]}>
121
- <Concept {...props} />
168
+ <ConceptContextProvider>
169
+ <Concept {...props} />
170
+ </ConceptContextProvider>
122
171
  </LangProviderWrapper>,
123
172
  renderOpts
124
173
  );
@@ -0,0 +1,47 @@
1
+ import React, { useEffect } from "react";
2
+ import { render } from "@truedat/test/render";
3
+ import { renderHook, act } from "@testing-library/react-hooks";
4
+ import ConceptContext, { useConceptContext } from "../ConceptContext";
5
+
6
+ describe("<ConceptContext />", () => {
7
+ it("matches the latest snapshot", async () => {
8
+ const { container } = render(
9
+ <ConceptContext>
10
+ <div>Hello world</div>
11
+ </ConceptContext>
12
+ );
13
+ expect(container).toMatchSnapshot();
14
+ });
15
+
16
+ it("should update context with new values when setProperty is called", () => {
17
+ const { result } = renderHook(() => useConceptContext(), {
18
+ wrapper: ({ children }) => <ConceptContext>{children}</ConceptContext>,
19
+ });
20
+
21
+ act(() => {
22
+ result.current.setConceptContextProperty({ newKey: "newValue" });
23
+ });
24
+
25
+ expect(result.current.conceptContext).toEqual(
26
+ expect.objectContaining({ newKey: "newValue" })
27
+ );
28
+ });
29
+
30
+ it("should provide context values to child components", () => {
31
+ const TestComponent = () => {
32
+ const { conceptContext, setConceptContextProperty } = useConceptContext();
33
+ useEffect(() => {
34
+ setConceptContextProperty({ testKey: "testValue" });
35
+ }, []);
36
+ return <div>{conceptContext.testKey}</div>;
37
+ };
38
+
39
+ const { getByText } = render(
40
+ <ConceptContext>
41
+ <TestComponent />
42
+ </ConceptContext>
43
+ );
44
+
45
+ expect(getByText("testValue")).toBeInTheDocument();
46
+ });
47
+ });
@@ -307,3 +307,184 @@ exports[`<Concept /> matches the latest snapshot 1`] = `
307
307
  </div>
308
308
  </div>
309
309
  `;
310
+
311
+ exports[`<Concept /> matches the latest snapshot with grantView activated 1`] = `
312
+ <div>
313
+ <div
314
+ class="ui breadcrumb"
315
+ >
316
+ <a
317
+ class="section"
318
+ href="/concepts"
319
+ >
320
+ Business Glossary
321
+ </a>
322
+ <i
323
+ aria-hidden="true"
324
+ class="right angle icon divider"
325
+ />
326
+ <a
327
+ class="section"
328
+ href="/concepts/pending"
329
+ >
330
+ Draft
331
+ </a>
332
+ <i
333
+ aria-hidden="true"
334
+ class="right angle icon divider"
335
+ />
336
+ <span
337
+ class="active section"
338
+ style="cursor: default;"
339
+ >
340
+ Concept_en
341
+ </span>
342
+ </div>
343
+ <div
344
+ class="ui equal width grid"
345
+ style="margin-top: 0px;"
346
+ >
347
+ <div
348
+ class="twelve wide column"
349
+ >
350
+ <div
351
+ class="ui segment"
352
+ >
353
+ <div
354
+ class="ui grid"
355
+ >
356
+ <div
357
+ class="row"
358
+ >
359
+ <div
360
+ class="eight wide column"
361
+ >
362
+ <h2
363
+ class="ui header"
364
+ >
365
+ <i
366
+ aria-hidden="true"
367
+ class="book circular icon"
368
+ />
369
+ <div
370
+ class="content"
371
+ >
372
+ Concept_es
373
+ <div
374
+ class="sub header"
375
+ >
376
+ templates.undefined
377
+ </div>
378
+ </div>
379
+ </h2>
380
+ </div>
381
+ <div
382
+ class="right aligned eight wide column"
383
+ >
384
+ <button
385
+ class="ui basic icon button button icon group-actions"
386
+ data-tooltip="Sharing"
387
+ >
388
+ <i
389
+ aria-hidden="true"
390
+ class="share alternate icon"
391
+ />
392
+ </button>
393
+ <button
394
+ class="ui basic icon button button icon group-actions structureButton"
395
+ >
396
+ <i
397
+ aria-hidden="true"
398
+ class="eye icon"
399
+ />
400
+ </button>
401
+ </div>
402
+ </div>
403
+ <div
404
+ class="row"
405
+ >
406
+ <div
407
+ class="eight wide column"
408
+ >
409
+ <div
410
+ class="ui left floated buttons"
411
+ >
412
+ <button
413
+ class="ui mini active button"
414
+ >
415
+ Spanish
416
+ </button>
417
+ <button
418
+ class="ui mini button"
419
+ >
420
+ English
421
+ </button>
422
+ </div>
423
+ </div>
424
+ <div
425
+ class="right aligned eight wide column"
426
+ />
427
+ </div>
428
+ </div>
429
+ <div
430
+ class="ui pointing secondary top attached tabular menu"
431
+ >
432
+ <a
433
+ class="item"
434
+ href="/concepts/1/versions/1"
435
+ >
436
+ Concept
437
+ </a>
438
+ <a
439
+ class="item"
440
+ href="/concepts/1/versions/1/links/structures"
441
+ >
442
+ Linkage
443
+ </a>
444
+ <a
445
+ class="item"
446
+ href="/concepts/1/versions/1/rules"
447
+ >
448
+ Quality Rules
449
+ </a>
450
+ <a
451
+ class="item"
452
+ href="/concepts/1/versions/1/archive"
453
+ >
454
+ History
455
+ </a>
456
+ <a
457
+ class="item"
458
+ href="/concepts/1/versions/1/events"
459
+ >
460
+ Audit
461
+ </a>
462
+ </div>
463
+ </div>
464
+ </div>
465
+ <div
466
+ class="four wide column"
467
+ >
468
+ <div
469
+ class="ui segment structure-grant-cart"
470
+ >
471
+ <div
472
+ class="ui header"
473
+ title="structure.grant.cart"
474
+ >
475
+ structure.grant.cart
476
+ </div>
477
+ <div
478
+ class="ui divider"
479
+ />
480
+ <div
481
+ class="list"
482
+ />
483
+ <p>
484
+ structure.grant.cart.empty
485
+ </p>
486
+ </div>
487
+ </div>
488
+ </div>
489
+ </div>
490
+ `;
@@ -0,0 +1,9 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`<ConceptContext /> matches the latest snapshot 1`] = `
4
+ <div>
5
+ <div>
6
+ Hello world
7
+ </div>
8
+ </div>
9
+ `;
@@ -32,6 +32,7 @@ const pickFields = _.pick([
32
32
  "rule_count",
33
33
  "concept_count",
34
34
  "i18n_content",
35
+ "actions",
35
36
  ]);
36
37
 
37
38
  export const concept = (state = initialState, { type, payload, meta }) => {
@@ -1,29 +1,55 @@
1
1
  import _ from "lodash/fp";
2
- import React from "react";
2
+ import React, { useState } from "react";
3
+ import { PropTypes } from "prop-types";
3
4
  import { connect } from "react-redux";
4
- import { Icon } from "semantic-ui-react";
5
+ import { Button, Icon } from "semantic-ui-react";
5
6
  import { FormattedMessage } from "react-intl";
6
7
  import { ConfirmModal } from "@truedat/core/components";
7
8
  import { conceptLinkAction } from "../routines";
8
9
 
9
- export const DeleteLink = ({ conceptLinkAction, action, loading }) =>
10
- loading ? (
10
+ export const DeleteLink = ({ conceptLinkAction, action, loading }) => {
11
+ const [openModal, setOpenModal] = useState(false);
12
+
13
+ return loading ? (
11
14
  <Icon loading name="circle notch" color="red" />
12
15
  ) : (
13
- <ConfirmModal
14
- icon="trash"
15
- trigger={<Icon name="trash alternate outline" color="red" />}
16
- header={
17
- <FormattedMessage id="relations.actions.data_field.delete.confirmation.header" />
18
- }
19
- content={
20
- <FormattedMessage id="relations.actions.data_field.delete.confirmation.content" />
21
- }
22
- onConfirm={() => conceptLinkAction(action)}
23
- onOpen={(e) => e.stopPropagation()}
24
- onClose={(e) => e.stopPropagation()}
25
- />
16
+ <>
17
+ <ConfirmModal
18
+ open={openModal}
19
+ trigger={
20
+ <Button
21
+ basic
22
+ color="red"
23
+ icon
24
+ onClick={() => setOpenModal(true)}
25
+ size="mini"
26
+ >
27
+ <Icon name="trash alternate outline" color="red" />
28
+ </Button>
29
+ }
30
+ header={
31
+ <FormattedMessage id="relations.actions.data_field.delete.confirmation.header" />
32
+ }
33
+ content={
34
+ <FormattedMessage id="relations.actions.data_field.delete.confirmation.content" />
35
+ }
36
+ onConfirm={() => conceptLinkAction(action)}
37
+ onOpen={() => {
38
+ setOpenModal(true);
39
+ }}
40
+ onClose={() => {
41
+ setOpenModal(false);
42
+ }}
43
+ />
44
+ </>
26
45
  );
46
+ };
47
+
48
+ DeleteLink.propTypes = {
49
+ conceptLinkAction: PropTypes.func,
50
+ action: PropTypes.object,
51
+ loading: PropTypes.bool,
52
+ };
27
53
 
28
54
  const mapStateToProps = ({ conceptLinkActionLoading }, ownProps) => ({
29
55
  loading: _.pathEq("action.href")(conceptLinkActionLoading)(ownProps),
@@ -36,4 +62,8 @@ export const DeleteLinkConnected = connect(mapStateToProps, {
36
62
  export const LinkActions = ({ delete: deleteAction }) =>
37
63
  deleteAction ? <DeleteLinkConnected action={deleteAction} /> : null;
38
64
 
65
+ LinkActions.propTypes = {
66
+ delete: PropTypes.object,
67
+ };
68
+
39
69
  export default LinkActions;
@@ -1,37 +1,64 @@
1
1
  import _ from "lodash/fp";
2
2
  import React from "react";
3
3
  import PropTypes from "prop-types";
4
+ import { useIntl } from "react-intl";
4
5
  import { connect } from "react-redux";
5
6
  import { Button } from "semantic-ui-react";
6
7
  import { Link } from "react-router-dom";
7
- import { FormattedMessage } from "react-intl";
8
8
  import { linkTo } from "@truedat/core/routes";
9
+ import { Checkbox } from "semantic-ui-react";
9
10
 
10
- export const ConceptLinksActions = ({ createLinkUrl }) =>
11
- createLinkUrl ? (
12
- <Button
13
- primary
14
- floated="right"
15
- as={Link}
16
- to={createLinkUrl}
17
- content={<FormattedMessage id="links.actions.create" />}
18
- />
11
+ import ConceptLinkstRequestGrantButton from "./ConceptLinkstRequestGrantButton";
12
+
13
+ export const ConceptLinksActions = ({
14
+ canManageGrantRequests,
15
+ createLinkUrl,
16
+ grantView,
17
+ onGrantViewChange,
18
+ }) => {
19
+ const { formatMessage } = useIntl();
20
+
21
+ return createLinkUrl ? (
22
+ <div style={{ float: "right" }}>
23
+ {canManageGrantRequests ? (
24
+ <React.Fragment>
25
+ <Checkbox
26
+ id="execute_checkbox"
27
+ className="bgOrange"
28
+ toggle
29
+ checked={grantView}
30
+ onChange={() => onGrantViewChange(!grantView)}
31
+ style={{ top: "6px", marginRight: "7.5px" }}
32
+ />
33
+ <ConceptLinkstRequestGrantButton disabled={!grantView} />
34
+ </React.Fragment>
35
+ ) : null}
36
+ <Button
37
+ primary
38
+ as={Link}
39
+ to={createLinkUrl}
40
+ content={formatMessage({ id: "links.actions.create" })}
41
+ />
42
+ </div>
19
43
  ) : null;
44
+ };
20
45
 
21
46
  ConceptLinksActions.propTypes = {
22
- createLinkUrl: PropTypes.string
47
+ canManageGrantRequests: PropTypes.bool,
48
+ createLinkUrl: PropTypes.string,
49
+ grantView: PropTypes.bool,
50
+ onGrantViewChange: PropTypes.func,
23
51
  };
24
52
 
25
- const mapStateToProps = ({
26
- conceptActions,
27
- conceptActionLoading,
28
- concept
29
- }) => ({
30
- createLinkUrl:
31
- _.has("create_structure_link")(conceptActions) && _.has("id")(concept)
32
- ? linkTo.CONCEPT_LINKS_STRUCTURES_NEW(concept)
33
- : null,
34
- conceptActionLoading
35
- });
53
+ const mapStateToProps = ({ conceptActions, conceptActionLoading, concept }) => {
54
+ return {
55
+ createLinkUrl:
56
+ _.has("create_structure_link")(conceptActions) && _.has("id")(concept)
57
+ ? linkTo.CONCEPT_LINKS_STRUCTURES_NEW(concept)
58
+ : null,
59
+ conceptActionLoading,
60
+ canManageGrantRequests: concept.actions?.manage_grant_requests,
61
+ };
62
+ };
36
63
 
37
64
  export default connect(mapStateToProps)(ConceptLinksActions);
@@ -0,0 +1,65 @@
1
+ import _ from "lodash/fp";
2
+ import React from "react";
3
+ import PropTypes from "prop-types";
4
+ import { useIntl } from "react-intl";
5
+ import { connect } from "react-redux";
6
+ import { Button } from "semantic-ui-react";
7
+ import { useSearchContext } from "@truedat/core/search/SearchContext";
8
+ import { addGrantRequestToCart } from "@truedat/dd/routines";
9
+
10
+ export const ConceptLinkstRequestGrantButton = ({
11
+ disabled,
12
+ addGrantRequestToCart,
13
+ requested,
14
+ }) => {
15
+ const { formatMessage } = useIntl();
16
+ const { searchData } = useSearchContext();
17
+ const structures = searchData?.data;
18
+
19
+ const requestAllStructures = () => {
20
+ _.forEach((structure) => {
21
+ const authorizedRequest = _.pathOr(
22
+ false,
23
+ "request_grant"
24
+ )(structure.user_permissions);
25
+
26
+ const isRequested = _.flow(
27
+ _.find({ id: `${structure.id}` }),
28
+ _.negate(_.isUndefined)
29
+ )(requested);
30
+
31
+ const granted = _.has("my_grant_request")(structure);
32
+
33
+ if (authorizedRequest && !isRequested && !granted)
34
+ addGrantRequestToCart({
35
+ ...structure,
36
+ id: `${structure.id}`,
37
+ });
38
+ })(structures);
39
+ };
40
+
41
+ return (
42
+ <Button
43
+ secondary
44
+ icon="shield"
45
+ title={formatMessage({ id: "links.actions.grant_request.tooltip" })}
46
+ disabled={disabled}
47
+ content={formatMessage({ id: "links.actions.grant_request" })}
48
+ onClick={requestAllStructures}
49
+ />
50
+ );
51
+ };
52
+
53
+ ConceptLinkstRequestGrantButton.propTypes = {
54
+ disabled: PropTypes.bool,
55
+ addGrantRequestToCart: PropTypes.func,
56
+ requested: PropTypes.arrayOf(PropTypes.object),
57
+ };
58
+
59
+ const mapStateToProps = ({ grantRequestsCart }) => ({
60
+ requested: _.pathOr([], "structures")(grantRequestsCart),
61
+ });
62
+
63
+ export default connect(mapStateToProps, { addGrantRequestToCart })(
64
+ ConceptLinkstRequestGrantButton
65
+ );
@@ -2,26 +2,109 @@ import _ from "lodash/fp";
2
2
  import React from "react";
3
3
  import PropTypes from "prop-types";
4
4
  import { connect } from "react-redux";
5
- import { Segment } from "semantic-ui-react";
5
+ import { Segment, Grid } from "semantic-ui-react";
6
+ import {
7
+ useDataStructureFilters,
8
+ useDataStructureSearch,
9
+ } from "@truedat/dd/hooks/useStructures";
10
+ import translations from "@truedat/dd/utils/structureCustomTranslations";
11
+ import StructuresSearchResults from "@truedat/dd/components/StructuresSearchResults";
12
+ import {
13
+ SearchContextProvider,
14
+ useSearchContext,
15
+ } from "@truedat/core/search/SearchContext";
6
16
  import { getConceptLinks } from "../selectors";
17
+ import { useConceptContext } from "../../components/ConceptContext";
7
18
  import ConceptLinksActions from "./ConceptLinksActions";
8
19
 
9
20
  const LinksPane = React.lazy(() => import("@truedat/lm/components/LinksPane"));
10
21
 
11
- export const ConceptStructureLinksPane = ({ groups, visible }) => {
12
- const LinksActions = visible ? <ConceptLinksActions /> : null;
22
+ const ConceptStructureLinksSegment = ({ groups, visible }) => {
23
+ const { conceptContext, setConceptContextProperty } = useConceptContext();
24
+ const { searchData, loading } = useSearchContext();
25
+ const structures = searchData?.data;
26
+
27
+ const { grantView } = conceptContext;
28
+
29
+ const onGrantViewChange = (grantViewNewState) =>
30
+ setConceptContextProperty({ grantView: grantViewNewState });
31
+
32
+ const LinksActions = visible ? (
33
+ <ConceptLinksActions
34
+ grantView={grantView}
35
+ onGrantViewChange={onGrantViewChange}
36
+ />
37
+ ) : null;
38
+
13
39
  return (
14
40
  <Segment attached="bottom">
15
- <LinksPane
16
- groups={groups}
17
- sourceType="concept"
18
- targetType="structure"
19
- linksActions={LinksActions}
20
- />
41
+ {grantView ? (
42
+ <Grid>
43
+ <Grid.Row>
44
+ <Grid.Column>{LinksActions}</Grid.Column>
45
+ </Grid.Row>
46
+ <Grid.Row>
47
+ <Grid.Column>
48
+ <StructuresSearchResults
49
+ structures={structures}
50
+ loading={loading}
51
+ grantable
52
+ />
53
+ </Grid.Column>
54
+ </Grid.Row>
55
+ </Grid>
56
+ ) : (
57
+ <LinksPane
58
+ groups={groups}
59
+ sourceType="concept"
60
+ targetType="structure"
61
+ linksActions={LinksActions}
62
+ />
63
+ )}
21
64
  </Segment>
22
65
  );
23
66
  };
24
67
 
68
+ ConceptStructureLinksSegment.propTypes = {
69
+ groups: PropTypes.array,
70
+ visible: PropTypes.bool,
71
+ };
72
+
73
+ export const ConceptStructureLinksPane = ({ groups, visible }) => {
74
+ const structures_ids = _.flow(
75
+ _.flatMap(([_columns, [_tag, links]]) => links),
76
+ _.map(({ resource_id }) => parseInt(resource_id))
77
+ )(groups);
78
+
79
+ const enrichSearchPayload = {
80
+ my_grant_requests: true,
81
+ with_data_fields: false,
82
+ };
83
+
84
+ const searchProps = {
85
+ initialSortColumn: "name.raw",
86
+ initialSortDirection: "ascending",
87
+ useSearch: useDataStructureSearch,
88
+ useFilters: useDataStructureFilters,
89
+ pageSize: 20,
90
+ userFiltersType: "user_search_filters",
91
+ userFilterScope: "data_structure",
92
+ translations,
93
+ filtersGroup: [],
94
+ defaultFilters: {
95
+ exists: { field: "classes._grantable" },
96
+ ids: structures_ids,
97
+ },
98
+ enrichSearchPayload,
99
+ };
100
+
101
+ return (
102
+ <SearchContextProvider {...searchProps}>
103
+ <ConceptStructureLinksSegment groups={groups} visible={visible} />
104
+ </SearchContextProvider>
105
+ );
106
+ };
107
+
25
108
  ConceptStructureLinksPane.propTypes = {
26
109
  groups: PropTypes.array,
27
110
  visible: PropTypes.bool,
@@ -0,0 +1,65 @@
1
+ import React from "react";
2
+ import { render } from "@truedat/test/render";
3
+ import { useSearchContext } from "@truedat/core/search/SearchContext";
4
+ import ConceptLinksActions from "../ConceptLinksActions";
5
+
6
+ jest.mock("@truedat/core/search/SearchContext", () => {
7
+ const originalModule = jest.requireActual(
8
+ "@truedat/core/search/SearchContext"
9
+ );
10
+
11
+ return {
12
+ __esModule: true,
13
+ ...originalModule,
14
+ useSearchContext: jest.fn(),
15
+ };
16
+ });
17
+
18
+ const renderOpts = {
19
+ state: {
20
+ conceptActions: {
21
+ create_structure_link: true,
22
+ },
23
+ concept: {
24
+ id: 1,
25
+ business_concept_id: 11,
26
+ },
27
+ },
28
+ };
29
+
30
+ describe("<ConceptLinksActions />", () => {
31
+ it("matches the latest snapshot with Grant Requests permission", () => {
32
+ useSearchContext.mockReturnValue({ searchData: [] });
33
+
34
+ const grRenderOpts = {
35
+ ...renderOpts,
36
+ state: {
37
+ ...renderOpts.state,
38
+ concept: {
39
+ ...renderOpts.state.concept,
40
+ actions: {
41
+ manage_grant_requests: {},
42
+ },
43
+ },
44
+ },
45
+ };
46
+
47
+ const { container } = render(
48
+ <ConceptLinksActions onGrantViewChange={jest.fn()} />,
49
+ grRenderOpts
50
+ );
51
+
52
+ expect(container).toMatchSnapshot();
53
+ });
54
+
55
+ it("matches the latest snapshot without Grant Requests permission", () => {
56
+ useSearchContext.mockReturnValue({ searchData: [] });
57
+
58
+ const { container } = render(
59
+ <ConceptLinksActions onGrantViewChange={jest.fn()} />,
60
+ renderOpts
61
+ );
62
+
63
+ expect(container).toMatchSnapshot();
64
+ });
65
+ });
@@ -0,0 +1,37 @@
1
+ import React from "react";
2
+ import { render } from "@truedat/test/render";
3
+ import { useSearchContext } from "@truedat/core/search/SearchContext";
4
+ import ConceptLinkstRequestGrantButton from "../ConceptLinkstRequestGrantButton";
5
+
6
+ jest.mock("@truedat/core/search/SearchContext", () => {
7
+ const originalModule = jest.requireActual(
8
+ "@truedat/core/search/SearchContext"
9
+ );
10
+
11
+ return {
12
+ __esModule: true,
13
+ ...originalModule,
14
+ useSearchContext: jest.fn(),
15
+ };
16
+ });
17
+
18
+ const renderOpts = {
19
+ state: {
20
+ grantRequestsCart: {
21
+ structures: [],
22
+ },
23
+ },
24
+ };
25
+
26
+ describe("<ConceptLinkstRequestGrantButton />", () => {
27
+ it("matches the latest snapshot", () => {
28
+ useSearchContext.mockReturnValue({ searchData: [] });
29
+
30
+ const { container } = render(
31
+ <ConceptLinkstRequestGrantButton onGrantViewChange={jest.fn()} />,
32
+ renderOpts
33
+ );
34
+
35
+ expect(container).toMatchSnapshot();
36
+ });
37
+ });
@@ -1,15 +1,91 @@
1
1
  import _ from "lodash/fp";
2
- import React from "react";
3
- import { shallow } from "enzyme";
2
+ import React, { Suspense } from "react";
3
+ import { render } from "@truedat/test/render";
4
+ import { useSearchContext } from "@truedat/core/search/SearchContext";
4
5
  import { ConceptStructureLinksPane } from "../ConceptStructureLinks";
5
6
 
7
+ import { useConceptContext } from "../../../components/ConceptContext";
8
+
9
+ jest.mock("../../../components/ConceptContext", () => {
10
+ const originalModule = jest.requireActual(
11
+ "../../../components/ConceptContext"
12
+ );
13
+
14
+ return {
15
+ __esModule: true,
16
+ ...originalModule,
17
+ useConceptContext: jest.fn(),
18
+ };
19
+ });
20
+
21
+ jest.mock("@truedat/core/search/SearchContext", () => {
22
+ const originalModule = jest.requireActual(
23
+ "@truedat/core/search/SearchContext"
24
+ );
25
+
26
+ return {
27
+ __esModule: true,
28
+ ...originalModule,
29
+ useSearchContext: jest.fn(),
30
+ };
31
+ });
32
+
33
+ beforeAll(() => {
34
+ jest.useFakeTimers();
35
+ jest.setSystemTime(new Date("2025-01-01T10:00:00Z"));
36
+ });
37
+
38
+ afterAll(() => {
39
+ jest.useRealTimers();
40
+ });
41
+
6
42
  describe("<ConceptStructureLinksPane />", () => {
7
43
  const props = {
8
- groups: [[[{ name: "name" }], ["group", [{ name: "foo" }]]]]
44
+ groups: [[[{ name: "name" }], ["group", [{ name: "foo" }]]]],
9
45
  };
10
46
 
11
- it("matches the latest snapshot", () => {
12
- const wrapper = shallow(<ConceptStructureLinksPane {...props} />);
13
- expect(wrapper).toMatchSnapshot();
47
+ it("matches the latest snapshot", async () => {
48
+ useConceptContext.mockReturnValue({
49
+ conceptContext: { grantView: false },
50
+ });
51
+ useSearchContext.mockReturnValue({
52
+ searchData: [],
53
+ });
54
+
55
+ const { container, findByText } = render(
56
+ <Suspense fallback={null}>
57
+ <ConceptStructureLinksPane {...props} />
58
+ </Suspense>
59
+ );
60
+
61
+ await findByText("Links to Structures");
62
+
63
+ expect(container).toMatchSnapshot();
64
+ });
65
+
66
+ it("matches the latest snapshot with grantView", async () => {
67
+ useConceptContext.mockReturnValue({
68
+ conceptContext: { grantView: true },
69
+ });
70
+ useSearchContext.mockReturnValue({
71
+ searchData: {
72
+ data: [
73
+ {
74
+ id: 1,
75
+ name: "test structure",
76
+ type: "Data Base",
77
+ group: "test",
78
+ system: { name: "System_Test" },
79
+ path: ["path", "to", "structure"],
80
+ updated_at: "2024-02-26 10:30:29.909476Z",
81
+ },
82
+ ],
83
+ },
84
+ loading: false,
85
+ });
86
+
87
+ const { container } = render(<ConceptStructureLinksPane {...props} />);
88
+
89
+ expect(container).toMatchSnapshot();
14
90
  });
15
91
  });
@@ -0,0 +1,61 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`<ConceptLinksActions /> matches the latest snapshot with Grant Requests permission 1`] = `
4
+ <div>
5
+ <div
6
+ style="float: right;"
7
+ >
8
+ <div
9
+ class="ui fitted toggle checkbox bgOrange"
10
+ style="top: 6px; margin-right: 7.5px;"
11
+ >
12
+ <input
13
+ class="hidden"
14
+ id="execute_checkbox"
15
+ readonly=""
16
+ tabindex="0"
17
+ type="checkbox"
18
+ value=""
19
+ />
20
+ <label
21
+ for="execute_checkbox"
22
+ />
23
+ </div>
24
+ <button
25
+ class="ui secondary disabled button"
26
+ disabled=""
27
+ tabindex="-1"
28
+ title="links.actions.grant_request.tooltip"
29
+ >
30
+ <i
31
+ aria-hidden="true"
32
+ class="shield icon"
33
+ />
34
+ links.actions.grant_request
35
+ </button>
36
+ <a
37
+ class="ui primary button"
38
+ href="/concepts/11/versions/1/links/structures/new"
39
+ role="button"
40
+ >
41
+ Add Link
42
+ </a>
43
+ </div>
44
+ </div>
45
+ `;
46
+
47
+ exports[`<ConceptLinksActions /> matches the latest snapshot without Grant Requests permission 1`] = `
48
+ <div>
49
+ <div
50
+ style="float: right;"
51
+ >
52
+ <a
53
+ class="ui primary button"
54
+ href="/concepts/11/versions/1/links/structures/new"
55
+ role="button"
56
+ >
57
+ Add Link
58
+ </a>
59
+ </div>
60
+ </div>
61
+ `;
@@ -0,0 +1,16 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`<ConceptLinkstRequestGrantButton /> matches the latest snapshot 1`] = `
4
+ <div>
5
+ <button
6
+ class="ui secondary button"
7
+ title="links.actions.grant_request.tooltip"
8
+ >
9
+ <i
10
+ aria-hidden="true"
11
+ class="shield icon"
12
+ />
13
+ links.actions.grant_request
14
+ </button>
15
+ </div>
16
+ `;
@@ -1,32 +1,197 @@
1
1
  // Jest Snapshot v1, https://goo.gl/fbAQLP
2
2
 
3
3
  exports[`<ConceptStructureLinksPane /> matches the latest snapshot 1`] = `
4
- <Segment
5
- attached="bottom"
6
- >
7
- <lazy
8
- groups={
9
- [
10
- [
11
- [
12
- {
13
- "name": "name",
14
- },
15
- ],
16
- [
17
- "group",
18
- [
19
- {
20
- "name": "foo",
21
- },
22
- ],
23
- ],
24
- ],
25
- ]
26
- }
27
- linksActions={null}
28
- sourceType="concept"
29
- targetType="structure"
30
- />
31
- </Segment>
4
+ <div>
5
+ <div
6
+ class="ui bottom attached segment"
7
+ style=""
8
+ >
9
+ <div
10
+ class="ui grid"
11
+ >
12
+ <div
13
+ class="row"
14
+ >
15
+ <div
16
+ class="column"
17
+ />
18
+ </div>
19
+ <div
20
+ class="row"
21
+ >
22
+ <div
23
+ class="column"
24
+ >
25
+ <h3
26
+ class="ui header"
27
+ >
28
+ Links to Structures
29
+ </h3>
30
+ <table
31
+ class="ui table"
32
+ >
33
+ <thead
34
+ class=""
35
+ >
36
+ <tr
37
+ class=""
38
+ >
39
+ <th
40
+ class=""
41
+ >
42
+ Structure
43
+ </th>
44
+ </tr>
45
+ </thead>
46
+ <tbody
47
+ class=""
48
+ >
49
+ <tr
50
+ class=""
51
+ >
52
+ <td
53
+ class=""
54
+ >
55
+ foo
56
+ </td>
57
+ </tr>
58
+ </tbody>
59
+ </table>
60
+ </div>
61
+ </div>
62
+ </div>
63
+ </div>
64
+ </div>
65
+ `;
66
+
67
+ exports[`<ConceptStructureLinksPane /> matches the latest snapshot with grantView 1`] = `
68
+ <div>
69
+ <div
70
+ class="ui bottom attached segment"
71
+ >
72
+ <div
73
+ class="ui grid"
74
+ >
75
+ <div
76
+ class="row"
77
+ >
78
+ <div
79
+ class="column"
80
+ />
81
+ </div>
82
+ <div
83
+ class="row"
84
+ >
85
+ <div
86
+ class="column"
87
+ >
88
+ <div
89
+ class="dimmable structure-table-overflow"
90
+ >
91
+ <div
92
+ class="ui label structures-label-results"
93
+ >
94
+ NaN structures found
95
+ </div>
96
+ <table
97
+ class="ui sortable table"
98
+ >
99
+ <thead
100
+ class=""
101
+ >
102
+ <tr
103
+ class=""
104
+ >
105
+ <th
106
+ class="two wide"
107
+ >
108
+ Structure
109
+ </th>
110
+ <th
111
+ class="one wide disabled"
112
+ >
113
+ Type
114
+ </th>
115
+ <th
116
+ class="one wide"
117
+ >
118
+ System
119
+ </th>
120
+ <th
121
+ class="one wide disabled"
122
+ >
123
+ Relation Type
124
+ </th>
125
+ <th
126
+ class="one wide disabled"
127
+ >
128
+ Group
129
+ </th>
130
+ <th
131
+ class="one wide"
132
+ >
133
+ structure.last_change_at
134
+ </th>
135
+ <th
136
+ class="center aligned"
137
+ >
138
+ Request grant
139
+ </th>
140
+ </tr>
141
+ </thead>
142
+ <tbody
143
+ class=""
144
+ >
145
+ <tr
146
+ class=""
147
+ >
148
+ <td
149
+ class="structure-cell-overflow"
150
+ >
151
+ <a
152
+ href="/structures/1"
153
+ title="test structure"
154
+ >
155
+ test structure
156
+ </a>
157
+ </td>
158
+ <td
159
+ class=""
160
+ >
161
+ Data Base
162
+ </td>
163
+ <td
164
+ class=""
165
+ >
166
+ System_Test
167
+ </td>
168
+ <td
169
+ class=""
170
+ />
171
+ <td
172
+ class=""
173
+ >
174
+ test
175
+ </td>
176
+ <td
177
+ class=""
178
+ >
179
+ <time
180
+ datetime="1735725600100"
181
+ >
182
+ 2025-01-01 10:00
183
+ </time>
184
+ </td>
185
+ <td
186
+ class="collapsing center aligned one wide"
187
+ />
188
+ </tr>
189
+ </tbody>
190
+ </table>
191
+ </div>
192
+ </div>
193
+ </div>
194
+ </div>
195
+ </div>
196
+ </div>
32
197
  `;