@truedat/dd 4.45.7 → 4.45.8

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## [4.45.8] 2022-06-09
4
+
5
+ ### Added
6
+
7
+ - [TD-4431] Support for requesting Grant removal
8
+
3
9
  ## [4.45.5] 2022-06-06
4
10
 
5
11
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@truedat/dd",
3
- "version": "4.45.7",
3
+ "version": "4.45.8",
4
4
  "description": "Truedat Web Data Dictionary",
5
5
  "sideEffects": false,
6
6
  "jsnext:main": "src/index.js",
@@ -34,7 +34,7 @@
34
34
  "@testing-library/jest-dom": "^5.16.4",
35
35
  "@testing-library/react": "^12.0.0",
36
36
  "@testing-library/user-event": "^13.2.1",
37
- "@truedat/test": "4.45.7",
37
+ "@truedat/test": "4.45.8",
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",
@@ -88,9 +88,9 @@
88
88
  },
89
89
  "dependencies": {
90
90
  "@apollo/client": "^3.6.4",
91
- "@truedat/auth": "4.45.7",
92
- "@truedat/core": "4.45.7",
93
- "@truedat/df": "4.45.7",
91
+ "@truedat/auth": "4.45.8",
92
+ "@truedat/core": "4.45.8",
93
+ "@truedat/df": "4.45.8",
94
94
  "lodash": "^4.17.21",
95
95
  "path-to-regexp": "^1.7.0",
96
96
  "prop-types": "^15.8.1",
@@ -111,5 +111,5 @@
111
111
  "react-dom": ">= 16.8.6 < 17",
112
112
  "semantic-ui-react": ">= 0.88.2 < 2.1"
113
113
  },
114
- "gitHead": "d3c6c144cc850972e0e602967e23259a1e176a2c"
114
+ "gitHead": "1b5acb1dc1233181e5a47353fd8a5da1424e7a47"
115
115
  }
package/src/api.js CHANGED
@@ -12,6 +12,7 @@ const API_GRANT_REQUEST = "/api/grant_requests/:id";
12
12
  const API_GRANT_REQUEST_APPROVALS = "/api/grant_requests/:id/approvals";
13
13
  const API_GRANT_REQUEST_GROUP = "/api/grant_request_groups";
14
14
  const API_GRANT_REQUESTS = "/api/grant_requests";
15
+ const API_GRANT = "/api/grants/:id";
15
16
  const API_GRANTS_CSV = "/api/grants/csv";
16
17
  const API_GRANTS_SEARCH = "/api/grants/search";
17
18
  const API_GRAPH = "/api/graphs/:id";
@@ -54,6 +55,7 @@ export {
54
55
  API_DATA_STRUCTURE_FILTERS_SEARCH,
55
56
  API_DATA_STRUCTURE_VERSION,
56
57
  API_PROFILE_EXECUTION,
58
+ API_GRANT,
57
59
  API_GRANT_FILTERS_SEARCH,
58
60
  API_GRANT_REQUEST,
59
61
  API_GRANT_REQUEST_APPROVALS,
@@ -0,0 +1,44 @@
1
+ import React from "react";
2
+ import PropTypes from "prop-types";
3
+ import { connect } from "react-redux";
4
+ import { useIntl } from "react-intl";
5
+ import { ConfirmModal } from "@truedat/core/components";
6
+ import { requestGrantRemoval } from "../routines";
7
+
8
+ export const GrantRemoval = ({ grant, requestGrantRemoval, children }) => {
9
+ const { formatMessage } = useIntl();
10
+
11
+ if (!grant) return;
12
+
13
+ const action = grant.pending_removal ? "cancel_removal" : "request_removal";
14
+
15
+ return (
16
+ <ConfirmModal
17
+ trigger={children}
18
+ header={formatMessage({
19
+ id: `grant.actions.${action}.confirmation.header`,
20
+ })}
21
+ content={formatMessage({
22
+ id: `grant.actions.${action}.confirmation.content`,
23
+ })}
24
+ onConfirm={() =>
25
+ requestGrantRemoval({
26
+ id: grant.id,
27
+ action,
28
+ data_structure_id: grant.data_structure.id,
29
+ version: grant.data_structure_version.id,
30
+ })
31
+ }
32
+ onOpen={(e) => e.stopPropagation()}
33
+ onClose={(e) => e.stopPropagation()}
34
+ />
35
+ );
36
+ };
37
+
38
+ GrantRemoval.propTypes = {
39
+ grant: PropTypes.object,
40
+ requestGrantRemoval: PropTypes.func,
41
+ children: PropTypes.node,
42
+ };
43
+
44
+ export default connect(null, { requestGrantRemoval })(GrantRemoval);
@@ -25,7 +25,7 @@ export const StructureDeleteButton = ({ structure, deleteStructure }) => {
25
25
  })}
26
26
  onConfirm={() => deleteStructure({ id: structure.id })}
27
27
  onOpen={(e) => e.stopPropagation()}
28
- onClone={(e) => e.stopPropagation()}
28
+ onClose={(e) => e.stopPropagation()}
29
29
  />
30
30
  );
31
31
  };
@@ -0,0 +1,63 @@
1
+ import _ from "lodash/fp";
2
+ import React from "react";
3
+ import PropTypes from "prop-types";
4
+ import { Icon, Label, Popup } from "semantic-ui-react";
5
+ import { connect } from "react-redux";
6
+ import { useIntl } from "react-intl";
7
+ import { DateTime } from "@truedat/core/components";
8
+ import GrantRemoval from "./GrantRemoval";
9
+ import StructureGrantRequestButton from "./StructureGrantRequestButton";
10
+
11
+ const isNilOrEmpty = (value) => _.isEmpty(value) || _.isNil(value);
12
+ export const StructureGrantButton = ({ structure, structureGrantPopup }) => {
13
+ const { formatMessage } = useIntl();
14
+ const { grant } = structure;
15
+
16
+ const popupContent = _.flow(
17
+ _.reject(({ field }) => isNilOrEmpty(_.get(field)(grant))),
18
+ _.map(({ field, type }) => {
19
+ switch (type) {
20
+ case "datetime":
21
+ return <DateTime value={_.get(field)(grant)} key={field} />;
22
+ default:
23
+ return <div key={field}>{_.get(field)(grant)}</div>;
24
+ }
25
+ })
26
+ )(structureGrantPopup);
27
+
28
+ const label = grant ? (
29
+ grant.pending_removal ? (
30
+ <Label color="orange">
31
+ <Icon name="shield" />
32
+ {formatMessage({ id: "structure.grant.has_grant_pending_removal" })}
33
+ <GrantRemoval grant={grant}>
34
+ <Icon name="undo" />
35
+ </GrantRemoval>
36
+ </Label>
37
+ ) : (
38
+ <Label color="green">
39
+ <Icon name="shield" />
40
+ {formatMessage({ id: "structure.grant.has_grant" })}
41
+ <GrantRemoval grant={grant}>
42
+ <Icon name="delete" />
43
+ </GrantRemoval>
44
+ </Label>
45
+ )
46
+ ) : (
47
+ <StructureGrantRequestButton />
48
+ );
49
+ return _.isEmpty(popupContent) ? (
50
+ label
51
+ ) : (
52
+ <Popup content={popupContent} trigger={label} />
53
+ );
54
+ };
55
+
56
+ StructureGrantButton.propTypes = {
57
+ structure: PropTypes.object,
58
+ structureGrantPopup: PropTypes.array,
59
+ };
60
+
61
+ const mapStateToProps = _.pick(["structure", "structureGrantPopup"]);
62
+
63
+ export default connect(mapStateToProps)(StructureGrantButton);
@@ -2,16 +2,43 @@ import _ from "lodash/fp";
2
2
  import React from "react";
3
3
  import PropTypes from "prop-types";
4
4
  import { useIntl } from "react-intl";
5
- import { Table } from "semantic-ui-react";
5
+ import { Table, Button } from "semantic-ui-react";
6
6
  import { connect } from "react-redux";
7
7
  import { columnDecorator } from "@truedat/core/services";
8
8
  import { getGrantsColumns } from "../selectors";
9
+ import GrantRemoval from "./GrantRemoval";
9
10
 
10
- export const StructureGrants = ({ columns, grants }) => {
11
+ export const RemovalButtonDecorator = (grant) => (
12
+ <GrantRemoval grant={grant}>
13
+ {grant.pending_removal ? (
14
+ <Button size="mini" color="olive" icon="undo" />
15
+ ) : (
16
+ <Button size="mini" color="red" icon="delete" />
17
+ )}
18
+ </GrantRemoval>
19
+ );
20
+
21
+ export const StructureGrants = ({ columns, grants, userPermissions }) => {
11
22
  const { formatMessage } = useIntl();
12
23
  const filteredColumns = _.filter((c) => _.any(_.prop(c.name))(grants))(
13
24
  columns
14
25
  );
26
+ const canUpdateRemoval = _.getOr(
27
+ false,
28
+ "update_grant_removal"
29
+ )(userPermissions);
30
+
31
+ const columnsWithActions = [
32
+ ...columns,
33
+ ...(canUpdateRemoval
34
+ ? [
35
+ {
36
+ fieldSelector: _.identity,
37
+ fieldDecorator: RemovalButtonDecorator,
38
+ },
39
+ ]
40
+ : []),
41
+ ];
15
42
 
16
43
  return (
17
44
  grants && (
@@ -33,14 +60,13 @@ export const StructureGrants = ({ columns, grants }) => {
33
60
  <Table.Body>
34
61
  {grants.map((grant, i) => (
35
62
  <Table.Row key={i}>
36
- {columns &&
37
- columns.map((column, key) => (
38
- <Table.Cell
39
- key={key}
40
- textAlign={column.textAlign}
41
- content={columnDecorator(column)(grant)}
42
- />
43
- ))}
63
+ {columnsWithActions.map((column, key) => (
64
+ <Table.Cell
65
+ key={key}
66
+ textAlign={column.textAlign}
67
+ content={columnDecorator(column)(grant)}
68
+ />
69
+ ))}
44
70
  </Table.Row>
45
71
  ))}
46
72
  </Table.Body>
@@ -52,11 +78,13 @@ export const StructureGrants = ({ columns, grants }) => {
52
78
  StructureGrants.propTypes = {
53
79
  columns: PropTypes.array,
54
80
  grants: PropTypes.array,
81
+ userPermissions: PropTypes.object,
55
82
  };
56
83
 
57
84
  const mapStateToProps = (state) => ({
58
85
  grants: state.structure.grants,
59
86
  columns: getGrantsColumns(state),
87
+ userPermissions: state.userPermissions,
60
88
  });
61
89
 
62
90
  export default connect(mapStateToProps)(StructureGrants);
@@ -10,7 +10,7 @@ import { getStructureUpdatedAt } from "../selectors";
10
10
  import StructureProperties from "./StructureProperties";
11
11
  import StructureConfidentialButton from "./StructureConfidentialButton";
12
12
  import StructureDeleteButton from "./StructureDeleteButton";
13
- import StructureGrantRequestButton from "./StructureGrantRequestButton";
13
+ import StructureGrantButton from "./StructureGrantButton";
14
14
  import StructureProfileButton from "./StructureProfileButton";
15
15
  import StructuresProfileButton from "./StructuresProfileButton";
16
16
  import TagsHeader from "./TagsHeader";
@@ -58,14 +58,10 @@ export const StructureSummary = ({
58
58
  name,
59
59
  published_note,
60
60
  description,
61
- type,
62
61
  userPermissions,
63
62
  updatedAt,
64
63
  deleted_at,
65
- data_structure_type,
66
- grant,
67
64
  linkedStructureTagsLoading,
68
- structureGrant,
69
65
  isAdmin,
70
66
  }) => {
71
67
  const { formatMessage } = useIntl();
@@ -83,18 +79,7 @@ export const StructureSummary = ({
83
79
  {_.isEmpty(published_note?.alias)
84
80
  ? name
85
81
  : published_note.alias}
86
- {grant ? (
87
- structureGrant ? (
88
- structureGrant(grant)
89
- ) : (
90
- <Label color="green">
91
- <Icon name="shield" />
92
- {formatMessage({ id: "structure.grant.has_grant" })}
93
- </Label>
94
- )
95
- ) : (
96
- <StructureGrantRequestButton />
97
- )}
82
+ <StructureGrantButton />
98
83
  {!_.isEmpty(published_note?.alias) && (
99
84
  <Header.Subheader>{name}</Header.Subheader>
100
85
  )}
@@ -135,15 +120,10 @@ StructureSummary.propTypes = {
135
120
  name: PropTypes.string,
136
121
  published_note: PropTypes.object,
137
122
  description: PropTypes.string,
138
- type: PropTypes.string,
139
123
  userPermissions: PropTypes.object,
140
124
  updatedAt: PropTypes.string,
141
125
  deleted_at: PropTypes.string,
142
- grant: PropTypes.object,
143
- data_structure_type: PropTypes.object,
144
126
  linkedStructureTagsLoading: PropTypes.bool,
145
- structureGrant: PropTypes.func,
146
- structureTags: PropTypes.array,
147
127
  isAdmin: PropTypes.bool,
148
128
  };
149
129
 
@@ -151,8 +131,6 @@ const mapStateToProps = (state) => ({
151
131
  userPermissions: state.userPermissions,
152
132
  linkedStructureTagsLoading: state.linkedStructureTagsLoading,
153
133
  updatedAt: getStructureUpdatedAt(state),
154
- structureTags: state.editableStructureTags,
155
- structureGrant: state.structureGrant,
156
134
  ...state.structure,
157
135
  isAdmin: state.authentication.role === "admin",
158
136
  });
@@ -0,0 +1,47 @@
1
+ import React from "react";
2
+ import { fireEvent } from "@testing-library/react";
3
+ import { render } from "@truedat/test/render";
4
+ import { requestGrantRemoval } from "../../routines";
5
+ import GrantRemoval from "../GrantRemoval";
6
+
7
+ describe("<GrantRemoval />", () => {
8
+ const grant = {
9
+ id: 1,
10
+ detail: { foo: "bar" },
11
+ data_structure: { id: 2 },
12
+ data_structure_version: { id: 3 },
13
+ };
14
+ it("matches the latest snapshot", () => {
15
+ const { container } = render(
16
+ <GrantRemoval grant={grant}>
17
+ <p>child</p>
18
+ </GrantRemoval>
19
+ );
20
+ expect(container).toMatchSnapshot();
21
+ });
22
+
23
+ it("on click calls requestGrantRemoval routine", async () => {
24
+ const dispatch = jest.fn();
25
+ const { findByText } = render(
26
+ <GrantRemoval grant={grant}>
27
+ <p>child</p>
28
+ </GrantRemoval>,
29
+ { dispatch }
30
+ );
31
+
32
+ fireEvent.click(await findByText("child"));
33
+ expect(await findByText("Request grant removal")).toBeInTheDocument();
34
+
35
+ fireEvent.click(await findByText("Yes"));
36
+
37
+ expect(dispatch).toHaveBeenLastCalledWith({
38
+ ...requestGrantRemoval(),
39
+ payload: {
40
+ action: "request_removal",
41
+ data_structure_id: 2,
42
+ id: 1,
43
+ version: 3,
44
+ },
45
+ });
46
+ });
47
+ });
@@ -0,0 +1,52 @@
1
+ import _ from "lodash/fp";
2
+ import React from "react";
3
+ import { fireEvent } from "@testing-library/react";
4
+ import { render } from "@truedat/test/render";
5
+ import StructureGrantButton from "../StructureGrantButton";
6
+
7
+ describe("<StructureGrantButton />", () => {
8
+ const state = {
9
+ structure: {
10
+ grant: {
11
+ detail: { foo: "bar" },
12
+ pending_removal: false,
13
+ },
14
+ },
15
+ structureGrantPopup: [],
16
+ };
17
+
18
+ it("matches the latest snapshot", async () => {
19
+ const { findByText, container } = render(<StructureGrantButton />, {
20
+ state,
21
+ });
22
+ expect(container).toMatchSnapshot();
23
+ expect(
24
+ await findByText("You have granted access on this structure")
25
+ ).toBeTruthy();
26
+ });
27
+
28
+ it("matches the latest snapshot pending_removal", async () => {
29
+ const customState = _.set("structure.grant.pending_removal", true)(state);
30
+ const { findByText, container } = render(<StructureGrantButton />, {
31
+ state: customState,
32
+ });
33
+ expect(container).toMatchSnapshot();
34
+ expect(
35
+ await findByText("You have granted access pending removal")
36
+ ).toBeTruthy();
37
+ });
38
+
39
+ it("matches the latest snapshot with popup", async () => {
40
+ const customState = _.set("structureGrantPopup", [{ field: "detail.foo" }])(
41
+ state
42
+ );
43
+ const { findByText } = render(<StructureGrantButton />, {
44
+ state: customState,
45
+ });
46
+
47
+ fireEvent.mouseOver(
48
+ await findByText("You have granted access on this structure")
49
+ );
50
+ expect(await findByText("bar")).toBeInTheDocument();
51
+ });
52
+ });
@@ -1,24 +1,14 @@
1
+ import _ from "lodash/fp";
1
2
  import React from "react";
2
- import { shallow } from "enzyme";
3
- import { intl } from "@truedat/test/intl-stub";
4
3
  import { render } from "@truedat/test/render";
5
- import { createStore } from "redux";
6
- import { Provider } from "react-redux";
7
- import { StructureSummary } from "../StructureSummary";
8
- import { defaultStructureProperties } from "../../selectors/getStructureProperties";
9
-
10
- // workaround for enzyme issue with React.useContext
11
- // see https://github.com/airbnb/enzyme/issues/2176#issuecomment-532361526
12
- jest.spyOn(React, "useContext").mockImplementation(() => intl);
4
+ import StructureSummary from "../StructureSummary";
13
5
 
14
6
  describe("<StructureSummary />", () => {
15
- const system = { name: "system" };
16
7
  const structure = {
17
8
  id: 1,
18
9
  name: "s1",
19
10
  description: "dd",
20
11
  group: "gp",
21
- system: "sys",
22
12
  userPermissions: {
23
13
  update: true,
24
14
  confidential: true,
@@ -26,110 +16,47 @@ describe("<StructureSummary />", () => {
26
16
  },
27
17
  class: "table",
28
18
  type: "foo",
29
- system,
19
+ system: { name: "system" },
20
+ };
21
+
22
+ const state = {
23
+ userPermissions: {
24
+ profile_permission: true,
25
+ confidential: true,
26
+ },
27
+ linkedStructureTagsLoading: false,
28
+ structureVersions: [{ updated_at: "2019-01-02T03:04:05Z" }],
29
+ structure,
30
+ authentication: { role: "admin" },
30
31
  };
31
- const updatedAt = "2019-01-02T03:04:05Z";
32
- const deleted_at = "2019-01-02T03:04:05Z";
33
- const structureFields = [{ external_id: "external_id " }];
34
32
 
35
33
  it("matches the latest snapshot", () => {
36
- const props = {
37
- ...structure,
38
- updatedAt,
39
- deleted_at,
40
- structureFields,
41
- linkedStructureTagsLoading: false,
42
- };
43
- const wrapper = shallow(<StructureSummary {...props} />);
44
- expect(wrapper).toMatchSnapshot();
34
+ const { container } = render(<StructureSummary />, { state });
35
+ expect(container).toMatchSnapshot();
45
36
  });
46
37
 
47
38
  it("matches the latest snapshot with alias", () => {
48
- const props = {
49
- ...structure,
50
- published_note: { alias: "some alias" },
51
- updatedAt,
52
- deleted_at,
53
- structureFields,
54
- linkedStructureTagsLoading: false,
55
- };
56
- const wrapper = shallow(<StructureSummary {...props} />);
57
- expect(wrapper).toMatchSnapshot();
39
+ const customState = _.set("structure.published_note", {
40
+ alias: "some alias",
41
+ })(state);
42
+
43
+ const { container } = render(<StructureSummary />, { state: customState });
44
+ expect(container).toMatchSnapshot();
58
45
  });
59
46
 
60
47
  it("renders loading when linkedStructureTagsLoading is true", () => {
61
- const props = {
62
- ...structure,
63
- updatedAt,
64
- deleted_at,
65
- structureFields,
66
- linkedStructureTagsLoading: true,
67
- };
68
- const wrapper = shallow(<StructureSummary {...props} />);
69
- expect(wrapper.find("Loading")).toBeTruthy();
48
+ const customState = _.set("linkedStructureTagsLoading", true)(state);
49
+ const { findByText } = render(<StructureSummary />, { state: customState });
50
+ expect(findByText("Loading")).toBeTruthy();
70
51
  });
71
52
 
72
- const messages = {
73
- en: {
74
- "structure.grant.has_grant": "Grant",
75
- "structure.share.title": "Share",
76
- "structure.type.foo.text": "Foo",
77
- "structure.domain": "Domain",
78
- "structure.group": "Group",
79
- },
80
- };
81
-
82
53
  it("renders label when grant provided", async () => {
83
- const storeProps = {
84
- structure,
85
- properties: defaultStructureProperties,
86
- userPermissions: { update: true, update_domain: true },
87
- };
88
- const store = createStore(() => ({ ...storeProps }));
89
-
90
- const props = {
91
- ...structure,
92
- updatedAt,
93
- deleted_at,
94
- structureFields,
95
- grant: { detail: { foo: "bar" }, end_date: "2022-01-02T03:04:05Z" },
96
- };
97
- const { container } = render(
98
- <Provider store={store}>
99
- <StructureSummary {...props} />
100
- </Provider>,
101
- {
102
- messages,
103
- }
54
+ const customState = _.set("structure.grant", { detail: { foo: "bar" } })(
55
+ state
104
56
  );
105
- expect(container).toMatchSnapshot();
106
- });
107
-
108
- it("renders custom grant label when grant provided", async () => {
109
- const grant = (grant) => <p>{grant.detail.foo}</p>;
110
- const storeProps = {
111
- structure,
112
- properties: defaultStructureProperties,
113
- userPermissions: { update: true, update_domain: true },
114
- };
115
- const store = createStore(() => ({ ...storeProps }));
116
-
117
- const props = {
118
- ...structure,
119
- updatedAt,
120
- deleted_at,
121
- structureFields,
122
- grant: { detail: { foo: "bar" }, end_date: "2022-01-02T03:04:05Z" },
123
- structureGrant: grant,
124
- };
125
- const { container } = render(
126
- <Provider store={store}>
127
- <StructureSummary {...props} />
128
- </Provider>,
129
- {
130
- messages,
131
- }
132
- );
133
- expect(container).toMatchSnapshot();
57
+ const { findByText } = render(<StructureSummary />, { state: customState });
58
+ expect(
59
+ findByText("You have granted access on this structure")
60
+ ).toBeTruthy();
134
61
  });
135
62
  });
@@ -0,0 +1,9 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`<GrantRemoval /> matches the latest snapshot 1`] = `
4
+ <div>
5
+ <p>
6
+ child
7
+ </p>
8
+ </div>
9
+ `;
@@ -0,0 +1,37 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`<StructureGrantButton /> matches the latest snapshot 1`] = `
4
+ <div>
5
+ <div
6
+ class="ui green label"
7
+ >
8
+ <i
9
+ aria-hidden="true"
10
+ class="shield icon"
11
+ />
12
+ You have granted access on this structure
13
+ <i
14
+ aria-hidden="true"
15
+ class="delete icon"
16
+ />
17
+ </div>
18
+ </div>
19
+ `;
20
+
21
+ exports[`<StructureGrantButton /> matches the latest snapshot pending_removal 1`] = `
22
+ <div>
23
+ <div
24
+ class="ui orange label"
25
+ >
26
+ <i
27
+ aria-hidden="true"
28
+ class="shield icon"
29
+ />
30
+ You have granted access pending removal
31
+ <i
32
+ aria-hidden="true"
33
+ class="undo icon"
34
+ />
35
+ </div>
36
+ </div>
37
+ `;
@@ -1,123 +1,6 @@
1
1
  // Jest Snapshot v1, https://goo.gl/fbAQLP
2
2
 
3
3
  exports[`<StructureSummary /> matches the latest snapshot 1`] = `
4
- <Fragment>
5
- <Grid>
6
- <GridRow>
7
- <GridColumn
8
- width={10}
9
- >
10
- <DimmerDimmable
11
- dimmed={false}
12
- >
13
- <Header
14
- as="h2"
15
- >
16
- <HeaderContent>
17
- s1
18
- <Connect(StructureGrantRequestButton) />
19
- </HeaderContent>
20
- </Header>
21
- <Dimmer
22
- active={false}
23
- inverted={true}
24
- >
25
- <Loader />
26
- </Dimmer>
27
- </DimmerDimmable>
28
- </GridColumn>
29
- <GridColumn
30
- textAlign="right"
31
- width={6}
32
- >
33
- <Date
34
- className="alert warning"
35
- date="2019-01-02T03:04:05Z"
36
- icon={
37
- <Icon
38
- as="i"
39
- color="red"
40
- name="warning circle"
41
- />
42
- }
43
- label="deleted_at"
44
- />
45
- <Connect(ShareLinkPopup)
46
- description="dd"
47
- name="s1"
48
- title="structure.share.title"
49
- url="http://localhost/"
50
- />
51
- <injectIntl(Connect(StructureConfidentialButton)) />
52
- </GridColumn>
53
- </GridRow>
54
- </Grid>
55
- <Connect(TagsHeader) />
56
- <Connect(StructureProperties) />
57
- </Fragment>
58
- `;
59
-
60
- exports[`<StructureSummary /> matches the latest snapshot with alias 1`] = `
61
- <Fragment>
62
- <Grid>
63
- <GridRow>
64
- <GridColumn
65
- width={10}
66
- >
67
- <DimmerDimmable
68
- dimmed={false}
69
- >
70
- <Header
71
- as="h2"
72
- >
73
- <HeaderContent>
74
- some alias
75
- <Connect(StructureGrantRequestButton) />
76
- <HeaderSubheader>
77
- s1
78
- </HeaderSubheader>
79
- </HeaderContent>
80
- </Header>
81
- <Dimmer
82
- active={false}
83
- inverted={true}
84
- >
85
- <Loader />
86
- </Dimmer>
87
- </DimmerDimmable>
88
- </GridColumn>
89
- <GridColumn
90
- textAlign="right"
91
- width={6}
92
- >
93
- <Date
94
- className="alert warning"
95
- date="2019-01-02T03:04:05Z"
96
- icon={
97
- <Icon
98
- as="i"
99
- color="red"
100
- name="warning circle"
101
- />
102
- }
103
- label="deleted_at"
104
- />
105
- <Connect(ShareLinkPopup)
106
- description="dd"
107
- name="s1"
108
- title="structure.share.title"
109
- url="http://localhost/"
110
- />
111
- <injectIntl(Connect(StructureConfidentialButton)) />
112
- </GridColumn>
113
- </GridRow>
114
- </Grid>
115
- <Connect(TagsHeader) />
116
- <Connect(StructureProperties) />
117
- </Fragment>
118
- `;
119
-
120
- exports[`<StructureSummary /> renders custom grant label when grant provided 1`] = `
121
4
  <div>
122
5
  <div
123
6
  class="ui grid"
@@ -138,9 +21,6 @@ exports[`<StructureSummary /> renders custom grant label when grant provided 1`]
138
21
  class="content"
139
22
  >
140
23
  s1
141
- <p>
142
- bar
143
- </p>
144
24
  </div>
145
25
  </h2>
146
26
  <div
@@ -160,14 +40,14 @@ exports[`<StructureSummary /> renders custom grant label when grant provided 1`]
160
40
  class="right aligned six wide column"
161
41
  >
162
42
  <div
163
- class="ui label alert warning"
164
- title="structure.deleted_at 2019-01-02T03:04:05Z"
43
+ class="ui label basic"
44
+ title="Last changed 2019-01-02T03:04:05Z"
165
45
  >
166
46
  <i
167
47
  aria-hidden="true"
168
- class="red warning circle icon"
48
+ class="sync alternate icon"
169
49
  />
170
- structure.deleted_at
50
+ Last changed
171
51
  :
172
52
  <time
173
53
  datetime="1546398245000"
@@ -183,6 +63,14 @@ exports[`<StructureSummary /> renders custom grant label when grant provided 1`]
183
63
  class="share alternate icon"
184
64
  />
185
65
  </button>
66
+ <button
67
+ class="ui icon button basic"
68
+ >
69
+ <i
70
+ aria-hidden="true"
71
+ class="red trash alternate icon"
72
+ />
73
+ </button>
186
74
  </div>
187
75
  </div>
188
76
  </div>
@@ -209,21 +97,12 @@ exports[`<StructureSummary /> renders custom grant label when grant provided 1`]
209
97
  <h5
210
98
  class="header"
211
99
  >
212
- structure.domain
100
+ Domain
213
101
  </h5>
214
102
  <div
215
103
  class="description"
216
104
  >
217
105
 
218
- <button
219
- class="ui basic icon button"
220
- style="box-shadow: none;"
221
- >
222
- <i
223
- aria-hidden="true"
224
- class="pencil alternate icon"
225
- />
226
- </button>
227
106
  </div>
228
107
  </div>
229
108
  </div>
@@ -240,7 +119,7 @@ exports[`<StructureSummary /> renders custom grant label when grant provided 1`]
240
119
  <h5
241
120
  class="header"
242
121
  >
243
- structure.group
122
+ Group
244
123
  </h5>
245
124
  <div
246
125
  class="description"
@@ -262,7 +141,7 @@ exports[`<StructureSummary /> renders custom grant label when grant provided 1`]
262
141
  <h5
263
142
  class="header"
264
143
  >
265
- structure.type
144
+ Type
266
145
  </h5>
267
146
  <div
268
147
  class="description"
@@ -277,7 +156,7 @@ exports[`<StructureSummary /> renders custom grant label when grant provided 1`]
277
156
  </div>
278
157
  `;
279
158
 
280
- exports[`<StructureSummary /> renders label when grant provided 1`] = `
159
+ exports[`<StructureSummary /> matches the latest snapshot with alias 1`] = `
281
160
  <div>
282
161
  <div
283
162
  class="ui grid"
@@ -297,15 +176,11 @@ exports[`<StructureSummary /> renders label when grant provided 1`] = `
297
176
  <div
298
177
  class="content"
299
178
  >
300
- s1
179
+ some alias
301
180
  <div
302
- class="ui green label"
181
+ class="sub header"
303
182
  >
304
- <i
305
- aria-hidden="true"
306
- class="shield icon"
307
- />
308
- structure.grant.has_grant
183
+ s1
309
184
  </div>
310
185
  </div>
311
186
  </h2>
@@ -326,14 +201,14 @@ exports[`<StructureSummary /> renders label when grant provided 1`] = `
326
201
  class="right aligned six wide column"
327
202
  >
328
203
  <div
329
- class="ui label alert warning"
330
- title="structure.deleted_at 2019-01-02T03:04:05Z"
204
+ class="ui label basic"
205
+ title="Last changed 2019-01-02T03:04:05Z"
331
206
  >
332
207
  <i
333
208
  aria-hidden="true"
334
- class="red warning circle icon"
209
+ class="sync alternate icon"
335
210
  />
336
- structure.deleted_at
211
+ Last changed
337
212
  :
338
213
  <time
339
214
  datetime="1546398245000"
@@ -349,6 +224,14 @@ exports[`<StructureSummary /> renders label when grant provided 1`] = `
349
224
  class="share alternate icon"
350
225
  />
351
226
  </button>
227
+ <button
228
+ class="ui icon button basic"
229
+ >
230
+ <i
231
+ aria-hidden="true"
232
+ class="red trash alternate icon"
233
+ />
234
+ </button>
352
235
  </div>
353
236
  </div>
354
237
  </div>
@@ -375,21 +258,12 @@ exports[`<StructureSummary /> renders label when grant provided 1`] = `
375
258
  <h5
376
259
  class="header"
377
260
  >
378
- structure.domain
261
+ Domain
379
262
  </h5>
380
263
  <div
381
264
  class="description"
382
265
  >
383
266
 
384
- <button
385
- class="ui basic icon button"
386
- style="box-shadow: none;"
387
- >
388
- <i
389
- aria-hidden="true"
390
- class="pencil alternate icon"
391
- />
392
- </button>
393
267
  </div>
394
268
  </div>
395
269
  </div>
@@ -406,7 +280,7 @@ exports[`<StructureSummary /> renders label when grant provided 1`] = `
406
280
  <h5
407
281
  class="header"
408
282
  >
409
- structure.group
283
+ Group
410
284
  </h5>
411
285
  <div
412
286
  class="description"
@@ -428,7 +302,7 @@ exports[`<StructureSummary /> renders label when grant provided 1`] = `
428
302
  <h5
429
303
  class="header"
430
304
  >
431
- structure.type
305
+ Type
432
306
  </h5>
433
307
  <div
434
308
  class="description"
@@ -29,6 +29,9 @@ export default {
29
29
  "filters.system.name.raw": "System",
30
30
  "filters.tags.raw": "Tags",
31
31
  "filters.type.raw": "Type",
32
+ "filters.pending_removal.raw": "Pending Removal",
33
+ "filters.pending_removal.raw.true": "Pending removal",
34
+ "filters.pending_removal.raw.false": "Not pending removal",
32
35
  "filters.system_external_id": "System",
33
36
  "filters.user_name": "User",
34
37
  "filters.with_content.raw": "Extra Info",
@@ -36,8 +39,15 @@ export default {
36
39
  "filters.with_profiling.raw": "Profile",
37
40
  "filters.with_profiling.raw.false": "Without profile",
38
41
  "filters.with_profiling.raw.true": "With profile",
42
+ "grant.actions.cancel_removal.confirmation.content":
43
+ "Cancelling grant removal. Are you sure?",
44
+ "grant.actions.cancel_removal.confirmation.header": "Cancel grant removal",
45
+ "grant.actions.request_removal.confirmation.content":
46
+ "Requesting this grant to be revoked. Are you sure?",
47
+ "grant.actions.request_removal.confirmation.header": "Request grant removal",
39
48
  "grant.cell.direct_access": "Direct access",
40
49
  "grant.cell.end_date.null": "No expiration set",
50
+ "grant.cell.end_date.pending_removal": "Grant pending removal",
41
51
  "grant.cell.end_date.same_user": "It's you",
42
52
  "grant.header.user": "Account",
43
53
  "grant.header.data_structure_version": "Structure",
@@ -231,6 +241,8 @@ export default {
231
241
  "structure.fields.empty": "No field information available for this structure",
232
242
  "structure.filter.empty": "Empty",
233
243
  "structure.grant.has_grant": "You have granted access on this structure",
244
+ "structure.grant.has_grant_pending_removal":
245
+ "You have granted access pending removal",
234
246
  "structure.grant.request_grant": "Request grant",
235
247
  "structure.grant.requested_grant": "Pending request",
236
248
  "structure.grant.cart": "Requested grants",
@@ -542,7 +554,6 @@ export default {
542
554
  "A Structure Tag with this name already exists",
543
555
  "updateStructureType.success.header": "Structure type updated",
544
556
  "updateSystem.error.df_content.invalid content": "Invalid template content",
545
- "structure.grant.has_grant": "You have granted access on this structure",
546
557
  "visualization.title.impact": "Impact Analysis",
547
558
  "visualization.title.lineage": "Lineage Analysis",
548
559
  };
@@ -34,13 +34,25 @@ export default {
34
34
  "filters.type.raw": "Tipo",
35
35
  "filters.system_external_id": "Sistema",
36
36
  "filters.user_name": "Usuario",
37
+ "filters.pending_removal.raw": "Pendiente de eliminar",
38
+ "filters.pending_removal.raw.true": "Pendiente de eliminar",
39
+ "filters.pending_removal.raw.false": "No pendiente de eliminar",
37
40
  "filters.with_content.raw": "Extra Info",
38
41
  "filters.with_content.raw.false": "Sin información extra",
39
42
  "filters.with_profiling.raw": "Perfilado",
40
43
  "filters.with_profiling.raw.false": "Sin perfilado",
41
44
  "filters.with_profiling.raw.true": "Contiene perfilado",
45
+ "grant.actions.cancel_removal.confirmation.content":
46
+ "Cancelar solicitud de eliminar acceso. ¿Estás seguro?",
47
+ "grant.actions.cancel_removal.confirmation.header":
48
+ "Cancelar retirada de acceso",
49
+ "grant.actions.request_removal.confirmation.content":
50
+ "El acceso será eliminado. ¿Estás seguro?",
51
+ "grant.actions.request_removal.confirmation.header":
52
+ "Solicitar eliminación de acceso",
42
53
  "grant.cell.direct_access": "Acceso Directo",
43
54
  "grant.cell.end_date.null": "Sin fecha de expiración",
55
+ "grant.cell.end_date.pending_removal": "Solicitada expiración del acceso",
44
56
  "grant.cell.end_date.same_user": "Eres tú",
45
57
  "grant.header.data_structure_version": "Estructura",
46
58
  "grant.header.end_date": "Expira",
@@ -238,6 +250,8 @@ export default {
238
250
  "No se dispone de información sobre los campos de esta estructura",
239
251
  "structure.filter.empty": "Vacío",
240
252
  "structure.grant.has_grant": "Tienes acceso a la estructura",
253
+ "structure.grant.has_grant_pending_removal":
254
+ "Tienes acceso pendiente de eliminarse",
241
255
  "structure.grant.request_grant": "Solicitar acceso",
242
256
  "structure.grant.requested_grant": "Solicitud pendiente",
243
257
  "structure.grant.cart": "Accesos a solicitar",
@@ -558,7 +572,6 @@ export default {
558
572
  "updateStructureType.success.header": "Tipo actualizado correctamente",
559
573
  "updateSystem.error.df_content.invalid content":
560
574
  "Contenido de plantilla inválido",
561
- "structure.grant.has_grant": "Tienes acceso a la estructura",
562
575
  "visualization.title.impact": "Análisis de Impacto",
563
576
  "visualization.title.lineage": "Análisis de Linaje",
564
577
  };
package/src/routines.js CHANGED
@@ -90,6 +90,7 @@ export const linkStructureTag = createRoutine("LINK_STRUCTURE_TAG");
90
90
  export const loadLineageEvents = createRoutine("LOAD_LINEAGE_EVENTS");
91
91
  export const openGrantFilter = createRoutine("OPEN_GRANT_FILTER");
92
92
  export const openStructureFilter = createRoutine("OPEN_STRUCTURE_FILTER");
93
+ export const requestGrantRemoval = createRoutine("REQUEST_GRANT_REMOVAL");
93
94
  export const removeGrantFilter = createRoutine("REMOVE_GRANT_FILTER");
94
95
  export const removeGrantRequestFromCart = createRoutine("REMOVE_GRANT_REQUEST_FROM_CART");
95
96
  export const removeGrantRequestsQuery = createRoutine("REMOVE_GRANT_REQUESTS_QUERY");
@@ -42,6 +42,7 @@ import { fetchUnitDomainsRequestSaga } from "./fetchUnitDomains";
42
42
  import { fetchUserSearchFiltersRequestSaga } from "./fetchUserSearchFilters";
43
43
  import { linkStructureTagRequestSaga } from "./linkStructureTag";
44
44
  import { operateGrantRequestToCartRequestSaga } from "./operateGrantRequestCart";
45
+ import { requestGrantRemovalRequestSaga } from "./requestGrantRemoval";
45
46
  import { saveUserSearchFiltersRequestSaga } from "./saveUserSearchFilters";
46
47
  import { updateReferenceDatasetRequestSaga } from "./updateReferenceDataset";
47
48
  import { updateStructureRequestSaga } from "./updateStructure";
@@ -96,6 +97,7 @@ export {
96
97
  fetchUserSearchFiltersRequestSaga,
97
98
  linkStructureTagRequestSaga,
98
99
  operateGrantRequestToCartRequestSaga,
100
+ requestGrantRemovalRequestSaga,
99
101
  saveUserSearchFiltersRequestSaga,
100
102
  updateReferenceDatasetRequestSaga,
101
103
  updateStructureRequestSaga,
@@ -151,6 +153,7 @@ export default [
151
153
  fetchUserSearchFiltersRequestSaga(),
152
154
  linkStructureTagRequestSaga(),
153
155
  operateGrantRequestToCartRequestSaga(),
156
+ requestGrantRemovalRequestSaga(),
154
157
  saveUserSearchFiltersRequestSaga(),
155
158
  updateReferenceDatasetRequestSaga(),
156
159
  updateStructureRequestSaga(),
@@ -0,0 +1,31 @@
1
+ import { compile } from "path-to-regexp";
2
+ import { call, put, takeLatest } from "redux-saga/effects";
3
+ import { apiJsonPatch, JSON_OPTS } from "@truedat/core/services/api";
4
+ import { requestGrantRemoval, fetchStructure } from "../routines";
5
+ import { API_GRANT } from "../api";
6
+
7
+ export function* requestGrantRemovalSaga({ payload }) {
8
+ try {
9
+ const { id, action, data_structure_id, version } = payload;
10
+ const url = compile(API_GRANT)({ id });
11
+ const requestData = { action };
12
+ yield put(requestGrantRemoval.request());
13
+ const { data } = yield call(apiJsonPatch, url, requestData, JSON_OPTS);
14
+ yield put(requestGrantRemoval.success(data));
15
+ yield put(fetchStructure.trigger({ id: data_structure_id, version }));
16
+ } catch (error) {
17
+ console.error("error", error);
18
+ if (error.response) {
19
+ const { status, data } = error.response;
20
+ yield put(requestGrantRemoval.failure({ status, data }));
21
+ } else {
22
+ yield put(requestGrantRemoval.failure(error.message));
23
+ }
24
+ } finally {
25
+ yield put(requestGrantRemoval.fulfill());
26
+ }
27
+ }
28
+
29
+ export function* requestGrantRemovalRequestSaga() {
30
+ yield takeLatest(requestGrantRemoval.TRIGGER, requestGrantRemovalSaga);
31
+ }
@@ -6,6 +6,7 @@ import { Link } from "react-router-dom";
6
6
  import { linkTo } from "@truedat/core/routes";
7
7
  import { useIntl } from "react-intl";
8
8
  import Moment from "react-moment";
9
+ import moment from "moment";
9
10
  import PropTypes from "prop-types";
10
11
 
11
12
  export const GrantUserDecorator = ({ authentication, user }) => {
@@ -124,19 +125,25 @@ StartDateDecorator.propTypes = {
124
125
  start_date: PropTypes.string,
125
126
  };
126
127
 
127
- export const EndDateDecorator = ({ end_date }) => {
128
+ export const EndDateDecorator = ({ end_date, pending_removal }) => {
128
129
  const { locale, formatMessage } = useIntl();
129
- return end_date ? (
130
+ return pending_removal ? (
131
+ <Header>
132
+ <Header.Subheader>
133
+ {formatMessage({ id: "grant.cell.end_date.pending_removal" })}
134
+ </Header.Subheader>
135
+ </Header>
136
+ ) : end_date ? (
130
137
  <Popup
131
- content={
132
- <Moment locale={locale} date={end_date} format="YYYY-MM-DD HH:mm" />
133
- }
138
+ content={<Moment locale={locale} date={end_date} format="YYYY-MM-DD" />}
134
139
  trigger={
135
140
  <Header>
136
141
  <Header.Subheader>
137
- <Moment locale={locale} fromNow>
138
- {end_date}
139
- </Moment>
142
+ <Moment
143
+ locale={locale}
144
+ date={end_date}
145
+ from={moment().format("L")}
146
+ />
140
147
  </Header.Subheader>
141
148
  </Header>
142
149
  }
@@ -152,6 +159,7 @@ export const EndDateDecorator = ({ end_date }) => {
152
159
 
153
160
  EndDateDecorator.propTypes = {
154
161
  end_date: PropTypes.string,
162
+ pending_removal: PropTypes.bool,
155
163
  };
156
164
 
157
165
  export const defaultGrantsColumns = [
@@ -172,7 +180,7 @@ export const defaultGrantsColumns = [
172
180
  },
173
181
  {
174
182
  name: "end_date",
175
- fieldSelector: _.pick(["end_date"]),
183
+ fieldSelector: _.pick(["end_date", "pending_removal"]),
176
184
  fieldDecorator: EndDateDecorator,
177
185
  },
178
186
  ];
@@ -214,7 +222,7 @@ export const defaultGrantsTableColumns = [
214
222
  {
215
223
  name: "end_date",
216
224
  sort: { name: "end_date" },
217
- fieldSelector: _.pick(["end_date"]),
225
+ fieldSelector: _.pick(["end_date", "pending_removal"]),
218
226
  fieldDecorator: EndDateDecorator,
219
227
  },
220
228
  {