@truedat/core 4.36.4 → 4.36.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,23 @@
1
1
  # Changelog
2
2
 
3
+ ## [4.36.8] 2022-01-24
4
+
5
+ ### Changed
6
+
7
+ - [TD-4125] Renamed `ConfirmModal` prop `handleSubmit` to `onConfirm`
8
+
9
+ ## [4.36.6] 2022-01-07
10
+
11
+ ### Fixed
12
+
13
+ - [TD-4100] Move dependency from devDepencies to dependencies
14
+
15
+ ## [4.36.5] 2022-01-07
16
+
17
+ ### Added
18
+
19
+ - [TD-4100] Long Alert error message CSV download link
20
+
3
21
  ## [4.35.8] 2022-01-07
4
22
 
5
23
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@truedat/core",
3
- "version": "4.36.4",
3
+ "version": "4.36.8",
4
4
  "description": "Truedat Web Core",
5
5
  "sideEffects": false,
6
6
  "jsnext:main": "src/index.js",
@@ -44,7 +44,7 @@
44
44
  "jest-localstorage-mock": "^2.4.14",
45
45
  "react": "^16.14.0",
46
46
  "react-dom": "^16.14.0",
47
- "redux-saga-test-plan": "^4.0.1",
47
+ "redux-saga-test-plan": "^4.0.4",
48
48
  "rimraf": "^3.0.2",
49
49
  "semantic-ui-react": "^2.0.3"
50
50
  },
@@ -86,6 +86,7 @@
86
86
  "is-hotkey": "^0.1.6",
87
87
  "is-url": "^1.2.4",
88
88
  "prop-types": "^15.7.2",
89
+ "react-csv": "^2.2.2",
89
90
  "react-dropzone": "^4.2.13",
90
91
  "react-intl": "^5.20.10",
91
92
  "react-moment": "^0.9.7",
@@ -103,5 +104,5 @@
103
104
  "react-dom": ">= 16.8.6 < 17",
104
105
  "semantic-ui-react": ">= 0.88.2 < 2.1"
105
106
  },
106
- "gitHead": "4dbf4f9e5188b82b17d053b65e76506e60aa6722"
107
+ "gitHead": "b9b8ee80998b230697593b5d207d187971bc0724"
107
108
  }
@@ -1,11 +1,12 @@
1
1
  import _ from "lodash/fp";
2
- import React from "react";
2
+ import React, { useState } from "react";
3
3
  import PropTypes from "prop-types";
4
4
  import { useIntl } from "react-intl";
5
5
  import { connect } from "react-redux";
6
6
  import { Message } from "semantic-ui-react";
7
7
  import { dismissAlert } from "../routines";
8
8
  import { getMessage } from "../selectors";
9
+ import { AlertExporter } from "./AlertExporter";
9
10
 
10
11
  const existingList = (list) => list && !_.isEmpty(list);
11
12
 
@@ -40,6 +41,8 @@ export const Alert = ({
40
41
  color,
41
42
  fields,
42
43
  messages,
44
+ downloadable = false,
45
+ initialMaxListItems = Infinity,
43
46
  },
44
47
  dismissAlert,
45
48
  }) => {
@@ -48,6 +51,9 @@ export const Alert = ({
48
51
  const formattedMessages = _.map((e) => formatMessages(e, formatMessage))(
49
52
  messages
50
53
  );
54
+ const list = _.isEmpty(errors) ? formattedMessages : errors;
55
+ const [maxListItems, setMaxListItems] = useState(initialMaxListItems);
56
+
51
57
  const message_content =
52
58
  content && !existingList(errors) && !existingList(formattedMessages)
53
59
  ? formatMessage(
@@ -55,18 +61,28 @@ export const Alert = ({
55
61
  { text, ...fields }
56
62
  )
57
63
  : undefined;
58
- const list = _.isEmpty(errors) ? formattedMessages : errors;
59
64
  return content || header ? (
60
- <Message
61
- error={error}
62
- header={formatMessage({ id: header, defaultMessage: header }, fields)}
63
- content={message_content}
64
- list={list}
65
- icon={icon}
66
- color={color}
67
- onDismiss={dismissAlert}
68
- visible
69
- />
65
+ <>
66
+ {list.length > maxListItems ? (
67
+ <a
68
+ style={{ cursor: "pointer" }}
69
+ onClick={() => setMaxListItems(maxListItems + initialMaxListItems)}
70
+ >
71
+ {formatMessage({ id: "alert.show.more" })}
72
+ </a>
73
+ ) : null}
74
+ {downloadable ? AlertExporter({ list, messages }) : null}
75
+ <Message
76
+ error={error}
77
+ header={formatMessage({ id: header, defaultMessage: header }, fields)}
78
+ content={message_content}
79
+ list={list.length >= maxListItems ? list.slice(0, maxListItems) : list}
80
+ icon={icon}
81
+ color={color}
82
+ onDismiss={dismissAlert}
83
+ visible
84
+ />
85
+ </>
70
86
  ) : null;
71
87
  };
72
88
 
@@ -0,0 +1,73 @@
1
+ /* eslint-disable fp/no-mutation */
2
+ /* eslint-disable fp/no-let */
3
+ import _ from "lodash/fp";
4
+ import React from "react";
5
+ import PropTypes from "prop-types";
6
+ import { useIntl } from "react-intl";
7
+ import { CSVLink } from "react-csv";
8
+
9
+ export const AlertExporter = ({ list, messages }) => {
10
+ const fullTranslations = joinTranslations({ list, messages });
11
+ const { formatMessage } = useIntl();
12
+ return (
13
+ <CSVLink
14
+ style={{ float: "right" }}
15
+ headers={generateCsvHeaders(fullTranslations)}
16
+ data={fullTranslations}
17
+ filename="errors.csv"
18
+ target="_blank"
19
+ >
20
+ {formatMessage({ id: "alert.export.csv" })}
21
+ </CSVLink>
22
+ );
23
+ };
24
+
25
+ AlertExporter.propTypes = {
26
+ list: PropTypes.array,
27
+ messages: PropTypes.array,
28
+ };
29
+
30
+ const joinTranslations = ({ list, messages }) => {
31
+ const renderedMessages = _.map((rm) => ({
32
+ renderedMessage: rm,
33
+ }))(list);
34
+ const full_messages = _.flow(
35
+ _.zip(renderedMessages),
36
+ _.map(
37
+ _.reduce(function (acc, ob) {
38
+ return _.assign(acc)(ob);
39
+ }, {})
40
+ )
41
+ )(messages);
42
+ return full_messages;
43
+ };
44
+
45
+ const flattenObj = (ob) => {
46
+ let result = {};
47
+ for (const i in ob) {
48
+ if (typeof ob[i] === "object" && !Array.isArray(ob[i])) {
49
+ const temp = flattenObj(ob[i]);
50
+ for (const j in temp) {
51
+ result[i + "." + j] = temp[j];
52
+ }
53
+ } else {
54
+ result[i] = ob[i];
55
+ }
56
+ }
57
+ return result;
58
+ };
59
+
60
+ const getAllUniqueKeys = _.flow(
61
+ _.map(flattenObj),
62
+ _.map(Object.keys),
63
+ _.flattenDeep,
64
+ _.uniq
65
+ );
66
+
67
+ const generateCsvHeaders = _.flow(
68
+ getAllUniqueKeys,
69
+ _.map((key) => ({
70
+ label: key.toUpperCase(),
71
+ key,
72
+ }))
73
+ );
@@ -3,18 +3,18 @@ import PropTypes from "prop-types";
3
3
  import { Modal } from "semantic-ui-react";
4
4
  import { FormattedMessage } from "react-intl";
5
5
 
6
- const predefined_actions = handleSubmit => [
6
+ const defaultActions = (onConfirm) => [
7
7
  {
8
8
  key: "no",
9
9
  secondary: true,
10
- content: <FormattedMessage id="confirmation.no" />
10
+ content: <FormattedMessage id="confirmation.no" />,
11
11
  },
12
12
  {
13
13
  key: "yes",
14
14
  negative: true,
15
15
  content: <FormattedMessage id="confirmation.yes" />,
16
- onClick: handleSubmit
17
- }
16
+ onClick: onConfirm,
17
+ },
18
18
  ];
19
19
 
20
20
  const CoolHeader = ({ header, subHeader }) => (
@@ -25,7 +25,7 @@ const CoolHeader = ({ header, subHeader }) => (
25
25
  );
26
26
  CoolHeader.propTypes = {
27
27
  header: PropTypes.any,
28
- subHeader: PropTypes.any
28
+ subHeader: PropTypes.any,
29
29
  };
30
30
 
31
31
  export const ConfirmModal = ({
@@ -37,22 +37,22 @@ export const ConfirmModal = ({
37
37
  subHeader,
38
38
  content,
39
39
  onClose,
40
- handleSubmit,
40
+ onConfirm,
41
41
  trigger,
42
- size
42
+ size,
43
43
  }) => (
44
44
  <Modal
45
45
  icon={icon}
46
46
  open={open}
47
47
  onOpen={onOpen}
48
48
  onClose={onClose}
49
- actions={actions || predefined_actions(handleSubmit)}
49
+ actions={actions || defaultActions(onConfirm)}
50
50
  trigger={trigger}
51
51
  closeIcon
52
52
  size={size || "small"}
53
53
  header={<CoolHeader header={header} subHeader={subHeader} />}
54
54
  content={
55
- content.type === "string" ? (
55
+ content?.type === "string" ? (
56
56
  content
57
57
  ) : (
58
58
  <Modal.Content>{content}</Modal.Content>
@@ -69,10 +69,10 @@ ConfirmModal.propTypes = {
69
69
  header: PropTypes.any,
70
70
  subHeader: PropTypes.any,
71
71
  content: PropTypes.any,
72
- handleSubmit: PropTypes.func,
72
+ onConfirm: PropTypes.func,
73
73
  onClose: PropTypes.func,
74
74
  trigger: PropTypes.element,
75
- size: PropTypes.string
75
+ size: PropTypes.string,
76
76
  };
77
77
 
78
78
  export default ConfirmModal;
@@ -6,7 +6,7 @@ import { FormattedMessage } from "react-intl";
6
6
  import { ConfirmModal } from "./ConfirmModal";
7
7
 
8
8
  export const SaveFilterForm = ({ setFilterName }) => {
9
- const onChange = value => {
9
+ const onChange = (value) => {
10
10
  setFilterName(value);
11
11
  };
12
12
 
@@ -26,32 +26,32 @@ export const SaveFilterForm = ({ setFilterName }) => {
26
26
  };
27
27
 
28
28
  SaveFilterForm.propTypes = {
29
- setFilterName: PropTypes.func
29
+ setFilterName: PropTypes.func,
30
30
  };
31
31
 
32
32
  export const ModalSaveFilter = ({ saveFilters, activeFilters }) => {
33
33
  const [filterName, setFilterName] = useState("");
34
34
 
35
- const actions = handleSubmit => [
35
+ const actions = (handleSubmit) => [
36
36
  {
37
37
  key: "no",
38
38
  secondary: true,
39
- content: <FormattedMessage id="search.save_filter.cancel" />
39
+ content: <FormattedMessage id="search.save_filter.cancel" />,
40
40
  },
41
41
  {
42
42
  key: "yes",
43
43
  primary: true,
44
44
  disabled: _.isEmpty(filterName),
45
45
  content: <FormattedMessage id="search.save_filter.save" />,
46
- onClick: handleSubmit
47
- }
46
+ onClick: handleSubmit,
47
+ },
48
48
  ];
49
49
 
50
- const handleSubmit = e => {
50
+ const handleSubmit = (e) => {
51
51
  e.preventDefault();
52
52
  saveFilters({
53
53
  filters: activeFilters,
54
- filterName: filterName
54
+ filterName: filterName,
55
55
  });
56
56
  };
57
57
 
@@ -70,14 +70,14 @@ export const ModalSaveFilter = ({ saveFilters, activeFilters }) => {
70
70
  content={
71
71
  <SaveFilterForm setFilterName={setFilterName} filterName={filterName} />
72
72
  }
73
- handleSubmit={handleSubmit}
73
+ onConfirm={handleSubmit}
74
74
  />
75
75
  );
76
76
  };
77
77
 
78
78
  ModalSaveFilter.propTypes = {
79
79
  saveFilters: PropTypes.func,
80
- activeFilters: PropTypes.object
80
+ activeFilters: PropTypes.object,
81
81
  };
82
82
 
83
83
  export default ModalSaveFilter;
@@ -10,7 +10,7 @@ export const UserFilters = ({
10
10
  deleteUserFilter,
11
11
  resetFilters,
12
12
  selectedUserFilter,
13
- userFilters
13
+ userFilters,
14
14
  }) => {
15
15
  return (
16
16
  <>
@@ -23,7 +23,7 @@ export const UserFilters = ({
23
23
  key={`label${userFilter.id}.${i}`}
24
24
  >
25
25
  <span
26
- onClick={e => {
26
+ onClick={(e) => {
27
27
  e.preventDefault();
28
28
  e.stopPropagation();
29
29
  if (selectedUserFilter == userFilter.name) {
@@ -58,13 +58,11 @@ export const UserFilters = ({
58
58
  <b>
59
59
  <i>{userFilter.name}</i>
60
60
  </b>
61
- )
61
+ ),
62
62
  }}
63
63
  />
64
64
  }
65
- handleSubmit={() => {
66
- deleteUserFilter({ id: userFilter.id });
67
- }}
65
+ onConfirm={() => deleteUserFilter({ id: userFilter.id })}
68
66
  />
69
67
  </Label>
70
68
  ))}
@@ -79,7 +77,7 @@ UserFilters.propTypes = {
79
77
  deleteUserFilter: PropTypes.func,
80
78
  resetFilters: PropTypes.func,
81
79
  selectedUserFilter: PropTypes.string,
82
- userFilters: PropTypes.array
80
+ userFilters: PropTypes.array,
83
81
  };
84
82
 
85
83
  export default UserFilters;
@@ -14,7 +14,7 @@ describe("<Alert />", () => {
14
14
  content: "content.message.id",
15
15
  header: "header.message.id",
16
16
  text: "some text",
17
- icon: "warning"
17
+ icon: "warning",
18
18
  };
19
19
  const props = { message, dismissAlert };
20
20
 
@@ -25,9 +25,10 @@ describe("<Alert />", () => {
25
25
 
26
26
  it("contains a formatted message with properties", () => {
27
27
  const wrapper = shallow(<Alert {...props} />);
28
- expect(wrapper.find("Message")).toHaveLength(1);
29
- expect(wrapper.prop("content")).toEqual("content.message.id");
30
- expect(wrapper.prop("header")).toEqual("header.message.id");
28
+ const message = wrapper.find("Message");
29
+ expect(message).toHaveLength(1);
30
+ expect(message.prop("content")).toEqual("content.message.id");
31
+ expect(message.prop("header")).toEqual("header.message.id");
31
32
  });
32
33
 
33
34
  it("contains a list of messages", () => {
@@ -39,13 +40,13 @@ describe("<Alert />", () => {
39
40
  text: "",
40
41
  messages: [
41
42
  {
42
- id: "message.0.id"
43
+ id: "message.0.id",
43
44
  },
44
45
  {
45
46
  id: "message.1.id",
46
- fields: { count: 1 }
47
- }
48
- ]
47
+ fields: { count: 1 },
48
+ },
49
+ ],
49
50
  };
50
51
  const props = { message, dismissAlert };
51
52
  const wrapper = shallow(<Alert {...props} />);
@@ -1,13 +1,15 @@
1
1
  // Jest Snapshot v1, https://goo.gl/fbAQLP
2
2
 
3
3
  exports[`<Alert /> matches the latest snapshot 1`] = `
4
- <Message
5
- content="content.message.id"
6
- error={true}
7
- header="header.message.id"
8
- icon="warning"
9
- list={Array []}
10
- onDismiss={[MockFunction]}
11
- visible={true}
12
- />
4
+ <Fragment>
5
+ <Message
6
+ content="content.message.id"
7
+ error={true}
8
+ header="header.message.id"
9
+ icon="warning"
10
+ list={Array []}
11
+ onDismiss={[MockFunction]}
12
+ visible={true}
13
+ />
14
+ </Fragment>
13
15
  `;
@@ -28,13 +28,13 @@ exports[`<ModalSaveFilter /> matches the latest snapshot 1`] = `
28
28
  setFilterName={[Function]}
29
29
  />
30
30
  }
31
- handleSubmit={[Function]}
32
31
  header={
33
32
  <Memo(MemoizedFormattedMessage)
34
33
  id="search.filters.actions.save.confirmation.header"
35
34
  />
36
35
  }
37
36
  icon="save"
37
+ onConfirm={[Function]}
38
38
  trigger={
39
39
  <a
40
40
  className="resetFilters"
@@ -31,13 +31,13 @@ exports[`<UserFilters /> matches the latest snapshot 1`] = `
31
31
  }
32
32
  />
33
33
  }
34
- handleSubmit={[Function]}
35
34
  header={
36
35
  <Memo(MemoizedFormattedMessage)
37
36
  id="search.filters.actions.delete.confirmation.header"
38
37
  />
39
38
  }
40
39
  icon="trash"
40
+ onConfirm={[Function]}
41
41
  trigger={
42
42
  <Icon
43
43
  as="i"
@@ -75,13 +75,13 @@ exports[`<UserFilters /> matches the latest snapshot 1`] = `
75
75
  }
76
76
  />
77
77
  }
78
- handleSubmit={[Function]}
79
78
  header={
80
79
  <Memo(MemoizedFormattedMessage)
81
80
  id="search.filters.actions.delete.confirmation.header"
82
81
  />
83
82
  }
84
83
  icon="trash"
84
+ onConfirm={[Function]}
85
85
  trigger={
86
86
  <Icon
87
87
  as="i"
package/src/routes.js CHANGED
@@ -112,7 +112,7 @@ export const SEARCH_RESULTS = "/search/results";
112
112
  export const SEARCH_STRUCTURES = "/search/structures";
113
113
  export const SOURCE = "/sources/:external_id";
114
114
  export const SOURCES = "/sources";
115
- export const SOURCE_CREATE = "/sources/new";
115
+ export const SOURCES_NEW = "/sources/new";
116
116
  export const SOURCE_EDIT = "/sources/:external_id/edit";
117
117
  export const SOURCE_JOBS = "/sources/:external_id/jobs";
118
118
  export const SOURCE_JOBS_NEW = "/sources/:external_id/jobs/new";
@@ -252,7 +252,7 @@ const routes = {
252
252
  SEARCH_RESULTS,
253
253
  SEARCH_STRUCTURES,
254
254
  SEARCH,
255
- SOURCE_CREATE,
255
+ SOURCES_NEW,
256
256
  SOURCE_EDIT,
257
257
  SOURCE_JOBS_NEW,
258
258
  SOURCE_JOBS,