@truedat/core 5.0.1 → 5.0.3

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.1",
3
+ "version": "5.0.3",
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.1",
38
+ "@truedat/test": "5.0.3",
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": "7eda377af42b503fda5e1f0e8cebcaf4d38dec32"
120
+ "gitHead": "ee802e3e32b52638cb4723fcab3cdeca266a666c"
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";
@@ -75,6 +79,10 @@ export const IMPLEMENTATION_CONCEPT_LINKS =
75
79
  export const IMPLEMENTATION_CONCEPT_LINKS_NEW =
76
80
  "/implementations/:implementation_id/links/concepts/new";
77
81
  export const IMPLEMENTATION_EDIT = "/implementations/:implementation_id/edit";
82
+ export const IMPLEMENTATION_EDIT_AS_DEFAULT =
83
+ "/implementations/:implementation_id/edit_as_default";
84
+ export const IMPLEMENTATION_EDIT_AS_RAW =
85
+ "/implementations/:implementation_id/edit_as_raw";
78
86
  export const IMPLEMENTATION_EVENTS =
79
87
  "/implementations/:implementation_id/events";
80
88
  export const IMPLEMENTATION_EXECUTIONS =
@@ -261,6 +269,10 @@ const routes = {
261
269
  GROUPS,
262
270
  GROUP_CREATE,
263
271
  GROUP_EDIT,
272
+ HIERARCHIES,
273
+ HIERARCHY,
274
+ HIERARCHY_EDIT,
275
+ HIERARCHY_CREATE,
264
276
  I18N,
265
277
  I18N_MESSAGES,
266
278
  I18N_MESSAGES_NEW,
@@ -272,6 +284,8 @@ const routes = {
272
284
  IMPLEMENTATION_CONCEPT_LINKS,
273
285
  IMPLEMENTATION_CONCEPT_LINKS_NEW,
274
286
  IMPLEMENTATION_EDIT,
287
+ IMPLEMENTATION_EDIT_AS_DEFAULT,
288
+ IMPLEMENTATION_EDIT_AS_RAW,
275
289
  IMPLEMENTATION_EVENTS,
276
290
  IMPLEMENTATION_EXECUTIONS,
277
291
  IMPLEMENTATION_HISTORY,
@@ -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
+ };