@truedat/core 5.0.0 → 5.0.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/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## [5.0.2] 2023-01-26
4
+
5
+ ### Added
6
+
7
+ - [TD-3805]
8
+ - SwrMiddleware for error handling
9
+ - CSVFileModal and descriptionInput components used for hierarchy functionality
10
+
3
11
  ## [5.0.0] 2023-01-24
4
12
 
5
13
  ### Changed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@truedat/core",
3
- "version": "5.0.0",
3
+ "version": "5.0.2",
4
4
  "description": "Truedat Web Core",
5
5
  "sideEffects": false,
6
6
  "jsnext:main": "src/index.js",
@@ -35,7 +35,7 @@
35
35
  "@testing-library/jest-dom": "^5.16.5",
36
36
  "@testing-library/react": "^12.0.0",
37
37
  "@testing-library/user-event": "^13.2.1",
38
- "@truedat/test": "5.0.0",
38
+ "@truedat/test": "5.0.2",
39
39
  "babel-jest": "^28.1.0",
40
40
  "babel-plugin-dynamic-import-node": "^2.3.3",
41
41
  "babel-plugin-lodash": "^3.3.4",
@@ -117,5 +117,5 @@
117
117
  "react-dom": ">= 16.8.6 < 17",
118
118
  "semantic-ui-react": ">= 2.0.3 < 2.2"
119
119
  },
120
- "gitHead": "688cd20866f3c91f8390588f65d8c75402ad06e4"
120
+ "gitHead": "627767d6f31541ef09a077d7e535fb488a05700c"
121
121
  }
@@ -2,8 +2,9 @@ import React from "react";
2
2
  import { useAuthorized } from "../hooks";
3
3
  import {
4
4
  CONFIGURATIONS,
5
- JOBS,
5
+ HIERARCHIES,
6
6
  I18N_MESSAGES,
7
+ JOBS,
7
8
  RELATION_TAGS,
8
9
  SOURCES,
9
10
  SUBSCRIPTIONS,
@@ -13,6 +14,7 @@ import Submenu from "./Submenu";
13
14
 
14
15
  const items = [
15
16
  { name: "templates", routes: [TEMPLATES] },
17
+ { name: "hierarchies", routes: [HIERARCHIES] },
16
18
  { name: "relations", routes: [RELATION_TAGS] },
17
19
  { name: "subscriptions", routes: [SUBSCRIPTIONS] },
18
20
  { name: "sources", routes: [SOURCES] },
@@ -0,0 +1,63 @@
1
+ import _ from "lodash/fp";
2
+ import React, { useState } from "react";
3
+ import PropTypes from "prop-types";
4
+ import { Button } from "semantic-ui-react";
5
+ import { useIntl } from "react-intl";
6
+ import { UploadModal } from "./UploadModal";
7
+
8
+ const CSVFileModal = ({ onSubmit, param }) => {
9
+ const { formatMessage } = useIntl();
10
+ const [processing, setProcessing] = useState(false);
11
+
12
+ const processCSV = (str, delim = ",") => {
13
+ const breakLine = "\n";
14
+ const lines = _.split(breakLine)(str);
15
+ const headers = _.flow(_.head, _.split(delim))(lines);
16
+ const rows = _.tail(lines);
17
+
18
+ const csvMap = rows.map((row) => {
19
+ const values = _.split(delim)(row);
20
+ return headers.reduce(
21
+ (map, header, pos) => _.set(header, values[pos])(map),
22
+ {}
23
+ );
24
+ });
25
+
26
+ return csvMap;
27
+ };
28
+
29
+ const onload = _.flow(_.pathOr("", "target.result"), processCSV, onSubmit);
30
+
31
+ const reader = new FileReader();
32
+ // eslint-disable-next-line fp/no-mutation
33
+ reader.onload = onload;
34
+
35
+ const process = (data) => {
36
+ setProcessing(true);
37
+ const file = data.get(param);
38
+ reader.readAsText(file);
39
+ setProcessing(false);
40
+ };
41
+
42
+ return (
43
+ <UploadModal
44
+ icon="upload"
45
+ trigger={
46
+ <Button secondary floated="right" icon="upload" loading={processing} />
47
+ }
48
+ header={formatMessage({ id: "uploadModal.actions.upload" })}
49
+ content={formatMessage({
50
+ id: "uploadModal.actions.upload.confirmation.content",
51
+ })}
52
+ param={param}
53
+ handleSubmit={process}
54
+ />
55
+ );
56
+ };
57
+
58
+ CSVFileModal.propTypes = {
59
+ onSubmit: PropTypes.func,
60
+ param: PropTypes.object,
61
+ };
62
+
63
+ export default CSVFileModal;
@@ -0,0 +1,39 @@
1
+ import React, { useState } from "react";
2
+ import PropTypes from "prop-types";
3
+ import { useIntl } from "react-intl";
4
+ import { Input } from "semantic-ui-react";
5
+
6
+ const DescriptionInput = ({ value, onChange, disabled = false }) => {
7
+ const { formatMessage } = useIntl();
8
+ const [editionMode, setEditMode] = useState();
9
+ return !editionMode ? (
10
+ <div
11
+ onClick={() => setEditMode(true && !disabled)}
12
+ onFocus={() => setEditMode(true && !disabled)}
13
+ className={disabled ? "description disabled" : "description"}
14
+ >
15
+ {!value || typeof value !== "string"
16
+ ? disabled
17
+ ? ""
18
+ : formatMessage({ id: "hierarchy.add_description" })
19
+ : value}
20
+ </div>
21
+ ) : (
22
+ <Input
23
+ style={{ width: "100%" }}
24
+ onBlur={() => setEditMode(false)}
25
+ placeholder={formatMessage({ id: "hierarchy.add_description" })}
26
+ onChange={onChange}
27
+ value={value == null ? "" : value}
28
+ autoFocus
29
+ />
30
+ );
31
+ };
32
+
33
+ DescriptionInput.propTypes = {
34
+ value: PropTypes.string,
35
+ onChange: PropTypes.func,
36
+ disabled: PropTypes.bool,
37
+ };
38
+
39
+ export default DescriptionInput;
@@ -43,6 +43,19 @@ exports[`<AdminMenu /> matches the latest snapshot 1`] = `
43
43
  Templates
44
44
  </span>
45
45
  </a>
46
+ <a
47
+ aria-checked="false"
48
+ class="item"
49
+ href="/hierarchies"
50
+ name="hierarchies"
51
+ role="option"
52
+ >
53
+ <span
54
+ class="text"
55
+ >
56
+ Hierarchies
57
+ </span>
58
+ </a>
46
59
  <a
47
60
  aria-checked="false"
48
61
  class="item"
@@ -1,6 +1,5 @@
1
1
  import ActiveRoute from "./ActiveRoute";
2
2
  import AdminMenu from "./AdminMenu";
3
- import MembersMenu from "./MembersMenu";
4
3
  import Alert from "./Alert";
5
4
  import Authorized from "./Authorized";
6
5
  import AvailableFilters from "./AvailableFilters";
@@ -9,12 +8,14 @@ import CatalogMenu from "./CatalogMenu";
9
8
  import Comments from "./Comments";
10
9
  import CommentsLoader from "./CommentsLoader";
11
10
  import ConfirmModal from "./ConfirmModal";
11
+ import CSVFileModal from "./CSVFileModal";
12
12
  import CursorPagination from "./CursorPagination";
13
13
  import DashboardMenu from "./DashboardMenu";
14
14
  import Date from "./Date";
15
15
  import DateFilter from "./DateFilter";
16
16
  import DateRangeFilter from "./DateRangeFilter";
17
17
  import DateTime from "./DateTime";
18
+ import DescriptionInput from "./DescriptionInput";
18
19
  import DomainSelector from "./DomainSelector";
19
20
  import DropdownMenuItem from "./DropdownMenuItem";
20
21
  import ErrorBoundary from "./ErrorBoundary";
@@ -26,6 +27,7 @@ import HistoryBackButton from "./HistoryBackButton";
26
27
  import IngestMenu from "./IngestMenu";
27
28
  import LineageMenu from "./LineageMenu";
28
29
  import Loading from "./Loading";
30
+ import MembersMenu from "./MembersMenu";
29
31
  import OptionGroup from "./OptionGroup";
30
32
  import OptionModal from "./OptionModal";
31
33
  import Pagination from "./Pagination";
@@ -50,7 +52,6 @@ import UploadModal from "./UploadModal";
50
52
  export {
51
53
  ActiveRoute,
52
54
  AdminMenu,
53
- MembersMenu,
54
55
  Alert,
55
56
  Authorized,
56
57
  AvailableFilters,
@@ -59,12 +60,14 @@ export {
59
60
  Comments,
60
61
  CommentsLoader,
61
62
  ConfirmModal,
63
+ CSVFileModal,
62
64
  CursorPagination,
63
65
  DashboardMenu,
64
66
  Date,
65
67
  DateFilter,
66
68
  DateRangeFilter,
67
69
  DateTime,
70
+ DescriptionInput,
68
71
  DomainSelector,
69
72
  DropdownMenuItem,
70
73
  ErrorBoundary,
@@ -76,6 +79,7 @@ export {
76
79
  IngestMenu,
77
80
  LineageMenu,
78
81
  Loading,
82
+ MembersMenu,
79
83
  OptionGroup,
80
84
  OptionModal,
81
85
  Pagination,
@@ -107,6 +107,7 @@ export default {
107
107
  "sidemenu.grant_request_approvals": "Approve Grant Requests",
108
108
  "sidemenu.grant_requests": "Grant Requests",
109
109
  "sidemenu.grants": "Grants",
110
+ "sidemenu.hierarchies": "Hierarchies",
110
111
  "sidemenu.hide": "Collapse sidebar",
111
112
  "sidemenu.i18nMessages": "Translations",
112
113
  "sidemenu.implementations": "Implementations",
@@ -110,6 +110,7 @@ export default {
110
110
  "sidemenu.grant_request_approvals": "Aprobar Peticiones de Accesos",
111
111
  "sidemenu.grant_requests": "Peticiones de Accesos",
112
112
  "sidemenu.grants": "Accesos",
113
+ "sidemenu.hierarchies": "Jerarquías",
113
114
  "sidemenu.hide": "Ocultar",
114
115
  "sidemenu.i18nMessages": "Traducciones",
115
116
  "sidemenu.implementations": "Implementaciones",
package/src/routes.js CHANGED
@@ -62,6 +62,10 @@ export const GROUP = "/groups/:id";
62
62
  export const GROUPS = "/groups";
63
63
  export const GROUP_CREATE = "/groups/new";
64
64
  export const GROUP_EDIT = "/groups/:id/edit";
65
+ export const HIERARCHIES = "/hierarchies";
66
+ export const HIERARCHY = "/hierarchies/:hierarchyId";
67
+ export const HIERARCHY_EDIT = "/hierarchies/:hierarchyId/edit";
68
+ export const HIERARCHY_CREATE = "/hierarchies/new";
65
69
  export const I18N = "/i18n";
66
70
  export const I18N_MESSAGES = "/i18n/messages";
67
71
  export const I18N_MESSAGES_NEW = "/i18n/messages/new";
@@ -261,6 +265,10 @@ const routes = {
261
265
  GROUPS,
262
266
  GROUP_CREATE,
263
267
  GROUP_EDIT,
268
+ HIERARCHIES,
269
+ HIERARCHY,
270
+ HIERARCHY_EDIT,
271
+ HIERARCHY_CREATE,
264
272
  I18N,
265
273
  I18N_MESSAGES,
266
274
  I18N_MESSAGES_NEW,
@@ -14,6 +14,9 @@ export const defaultMessage = (state, type, payload) => {
14
14
  if (type === "LOG_ERROR/TRIGGER" && _.has("graphQLErrors")(payload))
15
15
  return graphQLErrors(payload);
16
16
 
17
+ if (type === "LOG_ERROR/TRIGGER" && _.has("swrErrors")(payload))
18
+ return swrErrors(payload);
19
+
17
20
  if (_.endsWith("/TRIGGER")(type)) return state;
18
21
 
19
22
  if (_.endsWith("/FAILURE")(type)) {
@@ -27,6 +30,24 @@ export const defaultMessage = (state, type, payload) => {
27
30
  return state;
28
31
  };
29
32
 
33
+ const swrErrors = ({ swrErrors, prefix }) => {
34
+ const errors = _.flow(
35
+ _.toPairs,
36
+ _.flatMap(([field, errors]) =>
37
+ _.map((v) => `${prefix}.${field}.${v}`)(errors)
38
+ ),
39
+ _.map((name) => ({ name }))
40
+ )(swrErrors);
41
+
42
+ return {
43
+ error: true,
44
+ header: "Error",
45
+ icon: "attention",
46
+ errorList: errors,
47
+ text: "List of errors",
48
+ };
49
+ };
50
+
30
51
  export const graphQLErrors = (payload) => {
31
52
  const errorList = _.map(_.pick(["path", "field", "message"]))(
32
53
  payload.graphQLErrors
@@ -0,0 +1,43 @@
1
+ import _ from "lodash/fp";
2
+ import { unauthorized, logError } from "../routines";
3
+
4
+ function isNumeric(str) {
5
+ if (typeof str != "string") return false;
6
+ return !isNaN(str) && !isNaN(parseInt(str));
7
+ }
8
+
9
+ const getPrefix = _.flow(
10
+ _.replace("/api/", ""),
11
+ _.split("/"),
12
+ _.reject(isNumeric),
13
+ _.join(".")
14
+ );
15
+
16
+ const createErrorMiddleware = ({ dispatch }) => {
17
+ return (useSWRNext) => {
18
+ return (key, fetcher, config) => {
19
+ return useSWRNext(key, fetcher, {
20
+ ...config,
21
+ onError: (error) => {
22
+ config.onError(error);
23
+ const statusCode = _.pathOr(null, "response.status")(error);
24
+ const swrErrors = _.pathOr(null, "response.data.errors")(error);
25
+
26
+ if (statusCode === 401) {
27
+ dispatch(unauthorized());
28
+ } else if (swrErrors !== null) {
29
+ const prefix = getPrefix(key);
30
+ dispatch(logError({ swrErrors, prefix }));
31
+ }
32
+ },
33
+ });
34
+ };
35
+ };
36
+ };
37
+
38
+ export const createSwrConfig = (store) => {
39
+ const errorMiddleware = createErrorMiddleware(store);
40
+ return {
41
+ use: [errorMiddleware],
42
+ };
43
+ };