@truedat/df 5.17.2 → 5.18.0

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.
Files changed (25) hide show
  1. package/package.json +4 -4
  2. package/src/components/DynamicFormViewer.js +39 -2
  3. package/src/messages/en.js +1 -0
  4. package/src/messages/es.js +1 -0
  5. package/src/templates/components/Template.js +64 -16
  6. package/src/templates/components/TemplateFilters.js +56 -0
  7. package/src/templates/components/TemplateRoutes.js +17 -11
  8. package/src/templates/components/Templates.js +45 -21
  9. package/src/templates/components/TemplatesContext.js +66 -0
  10. package/src/templates/components/TemplatesTable.js +113 -0
  11. package/src/templates/components/__tests__/TemplatesTable.spec.js +29 -0
  12. package/src/templates/components/__tests__/__snapshots__/Template.spec.js.snap +54 -16
  13. package/src/templates/components/__tests__/__snapshots__/TemplatesTable.spec.js.snap +3 -0
  14. package/src/templates/components/templateForm/FieldForm.js +2 -0
  15. package/src/templates/components/templateForm/TemplateForm.js +14 -9
  16. package/src/templates/components/templateForm/TemplateFormActions.js +21 -38
  17. package/src/templates/components/templateForm/__tests__/TemplateForm.spec.js +2 -1
  18. package/src/templates/components/templateForm/__tests__/TemplateFormActions.spec.js +0 -2
  19. package/src/templates/components/templateForm/__tests__/__snapshots__/TemplateForm.spec.js.snap +38 -12
  20. package/src/templates/components/templateForm/__tests__/__snapshots__/TemplateFormActions.spec.js.snap +34 -109
  21. package/src/templates/components/TemplateCards.js +0 -57
  22. package/src/templates/components/TemplateTabs.js +0 -40
  23. package/src/templates/components/__tests__/TemplateCards.spec.js +0 -34
  24. package/src/templates/components/__tests__/__snapshots__/TemplateCards.spec.js.snap +0 -62
  25. package/src/templates/components/scopes.js +0 -8
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@truedat/df",
3
- "version": "5.17.2",
3
+ "version": "5.18.0",
4
4
  "description": "Truedat Web Data Quality Module",
5
5
  "sideEffects": false,
6
6
  "jsnext:main": "src/index.js",
@@ -87,8 +87,8 @@
87
87
  },
88
88
  "dependencies": {
89
89
  "@apollo/client": "^3.7.1",
90
- "@truedat/auth": "5.17.2",
91
- "@truedat/core": "5.17.2",
90
+ "@truedat/auth": "5.18.0",
91
+ "@truedat/core": "5.18.0",
92
92
  "decode-uri-component": "^0.2.2",
93
93
  "path-to-regexp": "^1.7.0",
94
94
  "prop-types": "^15.8.1",
@@ -109,5 +109,5 @@
109
109
  "react-dom": ">= 16.8.6 < 17",
110
110
  "semantic-ui-react": ">= 2.0.3 < 2.2"
111
111
  },
112
- "gitHead": "1120a6efa4dfec423696957c5e72d752c9db284f"
112
+ "gitHead": "2cd5914eafeae16400d4ccccfabc00cd2c84c80f"
113
113
  }
@@ -1,7 +1,10 @@
1
+ import _ from "lodash/fp";
1
2
  import React from "react";
2
3
  import PropTypes from "prop-types";
3
4
  import { useIntl } from "react-intl";
4
- import { Message } from "semantic-ui-react";
5
+ import { Message, Segment } from "semantic-ui-react";
6
+ import { useTemplate } from "@truedat/core/hooks";
7
+ import Loading from "@truedat/core/components/Loading";
5
8
  import { parseGroups } from "../utils";
6
9
  import FieldGroupDetail from "./FieldGroupDetail";
7
10
 
@@ -50,4 +53,38 @@ DynamicFormViewer.propTypes = {
50
53
  editFunctions: PropTypes.func,
51
54
  };
52
55
 
53
- export default DynamicFormViewer;
56
+ const DynamicFormViewerWithHook = (props) => {
57
+ const { data, loading } = useTemplate({
58
+ name: props.template,
59
+ domainIds: props.domainIds,
60
+ });
61
+ const newProps = {
62
+ ...props,
63
+ template: data?.template,
64
+ };
65
+ return loading ? (
66
+ <Segment>
67
+ <Loading />
68
+ </Segment>
69
+ ) : (
70
+ <DynamicFormViewer {...newProps} />
71
+ );
72
+ };
73
+
74
+ DynamicFormViewerWithHook.propTypes = {
75
+ template: PropTypes.string,
76
+ domainIds: PropTypes.arrayOf(PropTypes.number),
77
+ };
78
+
79
+ const DynamicFormViewerFetcher = (props) =>
80
+ _.isString(props?.template) ? (
81
+ <DynamicFormViewerWithHook {...props} />
82
+ ) : (
83
+ <DynamicFormViewer {...props} />
84
+ );
85
+
86
+ DynamicFormViewerFetcher.propTypes = {
87
+ template: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
88
+ };
89
+
90
+ export default DynamicFormViewerFetcher;
@@ -162,6 +162,7 @@ export default {
162
162
  "template.scope.qe": "Quality Executions",
163
163
  "template.scope.remediation": "Remediation plan",
164
164
  "template.scope.ri": "Quality Implementation",
165
+ "template.scope.quality_control": "Quality Control",
165
166
  "template.widget.copy.char.separator.format": "Char separation format",
166
167
  "template.widget.copy.fixed.column": "Fixed width column format",
167
168
  "template.widget.copy.without.fixed.column": "Format without char separation",
@@ -163,6 +163,7 @@ export default {
163
163
  "template.scope.qe": "Ejecuciones de calidad",
164
164
  "template.scope.remediation": "Plan de remediación",
165
165
  "template.scope.ri": "Implementación de calidad",
166
+ "template.scope.quality_control": "Control de calidad",
166
167
  "template.widget.copy.char.separator.format":
167
168
  "Formato de separación por caracter",
168
169
  "template.widget.copy.fixed.column": "Formato de columna de ancho fijo",
@@ -1,30 +1,72 @@
1
1
  import _ from "lodash/fp";
2
2
  import React from "react";
3
3
  import PropTypes from "prop-types";
4
- import { Header, Icon, Segment } from "semantic-ui-react";
4
+ import { FormattedMessage } from "react-intl";
5
+ import {
6
+ Button,
7
+ Header,
8
+ Icon,
9
+ Segment,
10
+ Grid,
11
+ Container,
12
+ } from "semantic-ui-react";
5
13
  import { connect } from "react-redux";
14
+ import { ConfirmModal } from "@truedat/core/components";
6
15
  import { deleteTemplate, updateTemplate } from "../routines";
7
16
  import TemplateCrumbs from "./TemplateCrumbs";
8
17
  import TemplateForm from "./templateForm/TemplateForm";
9
18
 
10
- export const Template = ({ template, updateTemplate, deleteTemplate }) =>
19
+ export const Template = ({
20
+ template,
21
+ updateTemplate,
22
+ deleteTemplate,
23
+ templateDeleting,
24
+ templateSaving,
25
+ }) =>
11
26
  _.isUndefined(template.id) ? null : (
12
27
  <>
13
28
  <TemplateCrumbs name={template?.label} />
14
29
  <Segment>
15
- <Header as="h2">
16
- <Icon name="file code outline" circular />
17
- <Header.Content>
18
- {template?.label}
19
- <Header.Subheader>{template?.name}</Header.Subheader>
20
- </Header.Content>
21
- </Header>
22
- <TemplateForm
23
- template={template}
24
- onDelete={deleteTemplate}
25
- onSubmit={updateTemplate}
26
- editMode
27
- />
30
+ <Grid>
31
+ <Grid.Column width={8}>
32
+ <Header as="h2">
33
+ <Icon name="file code outline" circular />
34
+ <Header.Content>
35
+ {template?.label}
36
+ <Header.Subheader>{template?.name}</Header.Subheader>
37
+ </Header.Content>
38
+ </Header>
39
+ </Grid.Column>
40
+ <Grid.Column width={8}>
41
+ <Container textAlign="right">
42
+ {template.id && (
43
+ <ConfirmModal
44
+ icon="trash"
45
+ trigger={
46
+ <Button
47
+ negative
48
+ icon="trash"
49
+ loading={templateDeleting}
50
+ disabled={templateSaving || templateDeleting}
51
+ />
52
+ }
53
+ header={
54
+ <FormattedMessage id="template.actions.delete.confirmation.header" />
55
+ }
56
+ content={
57
+ <FormattedMessage
58
+ id="template.actions.delete.confirmation.content"
59
+ values={{ name: <i>{template.name}</i> }}
60
+ />
61
+ }
62
+ onConfirm={() => deleteTemplate(template)}
63
+ />
64
+ )}
65
+ </Container>
66
+ </Grid.Column>
67
+ </Grid>
68
+
69
+ <TemplateForm template={template} onSubmit={updateTemplate} editMode />
28
70
  </Segment>
29
71
  </>
30
72
  );
@@ -33,9 +75,15 @@ Template.propTypes = {
33
75
  deleteTemplate: PropTypes.func,
34
76
  template: PropTypes.object,
35
77
  updateTemplate: PropTypes.func,
78
+ templateDeleting: PropTypes.bool,
79
+ templateSaving: PropTypes.bool,
36
80
  };
37
81
 
38
- const mapStateToProps = ({ template }) => ({ template });
82
+ const mapStateToProps = ({ template, templateDeleting, templateSaving }) => ({
83
+ template,
84
+ templateDeleting,
85
+ templateSaving,
86
+ });
39
87
 
40
88
  export default connect(mapStateToProps, { updateTemplate, deleteTemplate })(
41
89
  Template
@@ -0,0 +1,56 @@
1
+ import _ from "lodash/fp";
2
+ import React from "react";
3
+ import { Input, Dropdown } from "semantic-ui-react";
4
+ import { useIntl, FormattedMessage } from "react-intl";
5
+ import { lowerDeburrTrim } from "@truedat/core/services/sort";
6
+ import { useTemplatesContext } from "./TemplatesContext";
7
+
8
+ export default function TemplateFilters() {
9
+ const { formatMessage } = useIntl();
10
+ const { query, setQuery, scope, scopes, setScope } = useTemplatesContext();
11
+ return (
12
+ <Input
13
+ value={query}
14
+ onChange={(_e, data) => setQuery(lowerDeburrTrim(data.value))}
15
+ icon={{ name: "search", link: true }}
16
+ iconPosition="left"
17
+ action={
18
+ <Dropdown
19
+ button
20
+ className="icon"
21
+ floating
22
+ icon="filter"
23
+ labeled
24
+ scrolling
25
+ text={formatMessage({
26
+ id: scope ? `template.scope.${scope}` : "templates.all_scopes",
27
+ })}
28
+ upward={false}
29
+ >
30
+ <Dropdown.Menu>
31
+ <Dropdown.Item onClick={() => setScope()}>
32
+ <em>
33
+ <FormattedMessage id="templates.all_scopes" />
34
+ </em>
35
+ </Dropdown.Item>
36
+ {_.flow(
37
+ _.defaultTo([]),
38
+ _.map((scope) => (
39
+ <Dropdown.Item
40
+ key={scope}
41
+ text={formatMessage({
42
+ id: `template.scope.${scope}`,
43
+ })}
44
+ onClick={() => setScope(scope)}
45
+ />
46
+ ))
47
+ )(scopes)}
48
+ </Dropdown.Menu>
49
+ </Dropdown>
50
+ }
51
+ placeholder={formatMessage({
52
+ id: "templates.search.placeholder",
53
+ })}
54
+ />
55
+ );
56
+ }
@@ -2,17 +2,13 @@ import React from "react";
2
2
  import { Route, Switch } from "react-router-dom";
3
3
  import { Unauthorized } from "@truedat/core/components";
4
4
  import { useAuthorized } from "@truedat/core/hooks";
5
- import {
6
- TEMPLATE,
7
- TEMPLATES,
8
- TEMPLATES_NEW,
9
- TEMPLATE_SCOPE,
10
- } from "@truedat/core/routes";
5
+ import { TEMPLATE, TEMPLATES, TEMPLATES_NEW } from "@truedat/core/routes";
11
6
  import NewTemplate from "./NewTemplate";
12
7
  import Templates from "./Templates";
13
8
  import Template from "./Template";
14
9
  import TemplatesLoader from "./TemplatesLoader";
15
10
  import TemplateLoader from "./TemplateLoader";
11
+ import TemplatesContextProvider from "./TemplatesContext";
16
12
 
17
13
  export const TemplateRoutes = () => {
18
14
  const authorized = useAuthorized();
@@ -21,11 +17,21 @@ export const TemplateRoutes = () => {
21
17
  path={TEMPLATES}
22
18
  render={() =>
23
19
  authorized ? (
24
- <>
25
- <Route path={TEMPLATES} component={TemplatesLoader} exact />
26
- <Route path={TEMPLATES} component={Templates} exact />
27
- <Route path={TEMPLATE_SCOPE} component={Templates} exact />
20
+ <TemplatesContextProvider
21
+ initialSortColumn="updated_at"
22
+ initialSortDirection="descending"
23
+ >
28
24
  <Switch>
25
+ <Route
26
+ path={TEMPLATES}
27
+ render={() => (
28
+ <>
29
+ <TemplatesLoader />
30
+ <Templates />
31
+ </>
32
+ )}
33
+ exact
34
+ />
29
35
  <Route
30
36
  path={TEMPLATES_NEW}
31
37
  render={() => <NewTemplate />}
@@ -41,7 +47,7 @@ export const TemplateRoutes = () => {
41
47
  )}
42
48
  />
43
49
  </Switch>
44
- </>
50
+ </TemplatesContextProvider>
45
51
  ) : (
46
52
  <Unauthorized />
47
53
  )
@@ -1,25 +1,49 @@
1
1
  import React from "react";
2
- import { Header, Icon, Segment } from "semantic-ui-react";
3
2
  import { FormattedMessage } from "react-intl";
4
- import TemplateTabs from "./TemplateTabs";
5
- import TemplateCards from "./TemplateCards";
3
+ import {
4
+ Header,
5
+ Icon,
6
+ Segment,
7
+ Grid,
8
+ Container,
9
+ Button,
10
+ } from "semantic-ui-react";
11
+ import { Link } from "react-router-dom";
12
+ import { TEMPLATES_NEW } from "@truedat/core/routes";
13
+ import TemplateFilters from "./TemplateFilters";
14
+ import TemplatesTable from "./TemplatesTable";
6
15
 
7
- const Templates = () => (
8
- <Segment>
9
- <Header as="h2">
10
- <Icon name="file code outline" circular />
11
- <Header.Content>
12
- <FormattedMessage id="templates.header" />
13
- <Header.Subheader>
14
- <FormattedMessage id="templates.subheader" />
15
- </Header.Subheader>
16
- </Header.Content>
17
- </Header>
18
- <TemplateTabs />
19
- <Segment attached="bottom">
20
- <TemplateCards />
21
- </Segment>
22
- </Segment>
23
- );
16
+ export default function Templates() {
17
+ return (
18
+ <Segment>
19
+ <Header as="h2">
20
+ <Icon name="file code outline" circular />
21
+ <Header.Content>
22
+ <FormattedMessage id="templates.header" />
23
+ <Header.Subheader>
24
+ <FormattedMessage id="templates.subheader" />
25
+ </Header.Subheader>
26
+ </Header.Content>
27
+ </Header>
28
+
29
+ <Grid>
30
+ <Grid.Column width={8}>
31
+ <TemplateFilters />
32
+ </Grid.Column>
24
33
 
25
- export default Templates;
34
+ <Grid.Column width={8}>
35
+ <Container textAlign="right">
36
+ <Button
37
+ primary
38
+ as={Link}
39
+ to={TEMPLATES_NEW}
40
+ content={<FormattedMessage id="templates.actions.create" />}
41
+ />
42
+ </Container>
43
+ </Grid.Column>
44
+ </Grid>
45
+
46
+ <TemplatesTable />
47
+ </Segment>
48
+ );
49
+ }
@@ -0,0 +1,66 @@
1
+ import _ from "lodash/fp";
2
+ import React, { useState, useContext, createContext } from "react";
3
+ import { useSelector } from "react-redux";
4
+ import { matchSorter } from "match-sorter";
5
+
6
+ export const TemplatesContext = createContext();
7
+ export const useTemplatesContext = () => useContext(TemplatesContext);
8
+
9
+ const HIDDEN_SCOPES = ["cx", "ca"];
10
+ const isHiddenScope = (scope) => _.includes(scope)(HIDDEN_SCOPES);
11
+
12
+ export default function TemplatesContextProvider(props) {
13
+ const children = _.prop("children")(props);
14
+ const initialSortColumn = _.prop("initialSortColumn")(props);
15
+ const initialSortDirection = _.prop("initialSortDirection")(props);
16
+
17
+ const { templates, templatesLoading: loading } = useSelector(
18
+ _.pick(["templates", "templatesLoading"])
19
+ );
20
+
21
+ const [query, setQuery] = useState("");
22
+ const [scope, setScope] = useState();
23
+
24
+ const [sortColumn, setSortColumn] = useState(initialSortColumn);
25
+ const [sortDirection, setSortDirection] = useState(initialSortDirection);
26
+
27
+ const parsedTemplates = _.flow(
28
+ _.reject(({ scope }) => isHiddenScope(scope)),
29
+ _.filter((template) => !scope || scope == template.scope),
30
+ (items) =>
31
+ matchSorter(items, query, {
32
+ keys: ["name", "label"],
33
+ threshold: matchSorter.rankings.CONTAINS,
34
+ }),
35
+ _.orderBy([sortColumn], [sortDirection == "ascending" ? "asc" : "desc"])
36
+ )(templates);
37
+
38
+ const scopes = _.flow(
39
+ _.map("scope"),
40
+ _.uniq,
41
+ _.reject(isHiddenScope)
42
+ )(templates);
43
+
44
+ const context = {
45
+ loading,
46
+ templates: parsedTemplates,
47
+
48
+ query,
49
+ setQuery,
50
+
51
+ scope,
52
+ setScope,
53
+ scopes,
54
+
55
+ sortColumn,
56
+ sortDirection,
57
+ setSortColumn,
58
+ setSortDirection,
59
+ };
60
+
61
+ return (
62
+ <TemplatesContext.Provider value={context}>
63
+ {children}
64
+ </TemplatesContext.Provider>
65
+ );
66
+ }
@@ -0,0 +1,113 @@
1
+ import _ from "lodash/fp";
2
+ import React from "react";
3
+ import { FormattedMessage } from "react-intl";
4
+ import { Link } from "react-router-dom";
5
+ import { Table, Header, Icon } from "semantic-ui-react";
6
+ import { linkTo } from "@truedat/core/routes";
7
+ import { sortColumn as sortHandler } from "@truedat/core/services/sort";
8
+ import { columnDecorator } from "@truedat/core/services";
9
+ import {
10
+ TranslateDecorator,
11
+ DateDecorator,
12
+ } from "@truedat/core/services/columnDecorators";
13
+
14
+ import { useTemplatesContext } from "./TemplatesContext";
15
+
16
+ export default function TemplatesTable() {
17
+ const {
18
+ templates,
19
+ sortColumn,
20
+ sortDirection,
21
+ setSortColumn,
22
+ setSortDirection,
23
+ } = useTemplatesContext();
24
+
25
+ const columns = [
26
+ {
27
+ name: "name",
28
+ sort: { name: "name" },
29
+ fieldSelector: _.pick(["id", "name"]),
30
+ fieldDecorator: ({ id, name }) => (
31
+ <Link to={linkTo.TEMPLATE({ templateId: id })}>{name}</Link>
32
+ ),
33
+ },
34
+ {
35
+ name: "label",
36
+ sort: { name: "label" },
37
+ },
38
+ {
39
+ name: "scope",
40
+ sort: { name: "scope" },
41
+ fieldSelector: ({ scope }) => ({ id: `template.scope.${scope}` }),
42
+ fieldDecorator: TranslateDecorator,
43
+ width: 2,
44
+ },
45
+ {
46
+ name: "updated_at",
47
+ sort: { name: "updated_at" },
48
+ fieldSelector: ({ updated_at }) => ({
49
+ date: updated_at,
50
+ }),
51
+ width: 2,
52
+ fieldDecorator: DateDecorator,
53
+ textAlign: "center",
54
+ },
55
+ ];
56
+
57
+ return _.isEmpty(templates) ? (
58
+ <Header as="h4">
59
+ <Icon name="search" />
60
+ <Header.Content>
61
+ <FormattedMessage id="templates.search.results.empty" />
62
+ </Header.Content>
63
+ </Header>
64
+ ) : (
65
+ <Table sortable>
66
+ <Table.Header>
67
+ <Table.Row>
68
+ {columns.map((column, key) => (
69
+ <Table.HeaderCell
70
+ key={key}
71
+ width={column.width}
72
+ content={
73
+ <FormattedMessage
74
+ id={`template.form.${column.header || column.name}`}
75
+ defaultMessage={column.name}
76
+ />
77
+ }
78
+ sorted={
79
+ _.path("sort.name")(column) === sortColumn
80
+ ? sortDirection
81
+ : null
82
+ }
83
+ className={_.path("sort.name")(column) ? "" : "disabled"}
84
+ onClick={() =>
85
+ sortHandler(
86
+ column,
87
+ () => {},
88
+ setSortDirection,
89
+ setSortColumn,
90
+ sortDirection,
91
+ sortColumn
92
+ )
93
+ }
94
+ />
95
+ ))}
96
+ </Table.Row>
97
+ </Table.Header>
98
+ <Table.Body>
99
+ {templates.map((t, i) => (
100
+ <Table.Row key={i}>
101
+ {columns.map((column, i) => (
102
+ <Table.Cell
103
+ key={i}
104
+ textAlign={column.textAlign}
105
+ content={columnDecorator(column)(t)}
106
+ />
107
+ ))}
108
+ </Table.Row>
109
+ ))}
110
+ </Table.Body>
111
+ </Table>
112
+ );
113
+ }
@@ -0,0 +1,29 @@
1
+ import React from "react";
2
+ import { shallowWithIntl } from "@truedat/test/intl-stub";
3
+ import TemplatesTable from "../TemplatesTable";
4
+ import { TemplatesContext } from "../TemplatesContext";
5
+
6
+ describe("<TemplatesTable />", () => {
7
+ const templates = [
8
+ {
9
+ scope: "bg",
10
+ name: "testbg",
11
+ label: "testbg",
12
+ },
13
+ {
14
+ scope: "dq",
15
+ name: "testdq",
16
+ label: "testdq",
17
+ },
18
+ ];
19
+ const context = { templates };
20
+
21
+ it("matches the latest snapshot", () => {
22
+ const wrapper = shallowWithIntl(
23
+ <TemplatesContext.Provider values={context}>
24
+ <TemplatesTable />
25
+ </TemplatesContext.Provider>
26
+ );
27
+ expect(wrapper).toMatchSnapshot();
28
+ });
29
+ });