@truedat/qx 5.17.2 → 5.17.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.
Files changed (43) hide show
  1. package/package.json +3 -3
  2. package/src/api.js +24 -1
  3. package/src/components/QxRoutes.js +12 -5
  4. package/src/components/common/ClauseViewer.js +129 -0
  5. package/src/components/common/ResourceSelector.js +7 -2
  6. package/src/components/common/expressions/Clauses.js +15 -3
  7. package/src/components/common/expressions/Condition.js +8 -6
  8. package/src/components/common/expressions/ShapeSelector.js +18 -8
  9. package/src/components/common/expressions/__tests__/ShapeSelector.spec.js +2 -2
  10. package/src/components/dataViews/DataViewEditor.js +4 -3
  11. package/src/components/dataViews/__tests__/__snapshots__/DataViewEditor.spec.js.snap +2 -2
  12. package/src/components/dataViews/queryableFunctions.js +15 -9
  13. package/src/components/functions/FunctionEditor.js +3 -2
  14. package/src/components/functions/__tests__/__snapshots__/FunctionEditor.spec.js.snap +4 -4
  15. package/src/components/qualityControls/EditQualityControl.js +73 -0
  16. package/src/components/qualityControls/NewDraftQualityControl.js +77 -0
  17. package/src/components/qualityControls/NewQualityControl.js +81 -0
  18. package/src/components/qualityControls/QualityControl.js +93 -0
  19. package/src/components/qualityControls/QualityControlActions.js +67 -0
  20. package/src/components/qualityControls/QualityControlCrumbs.js +23 -0
  21. package/src/components/qualityControls/QualityControlEditor.js +271 -0
  22. package/src/components/qualityControls/QualityControlHeader.js +64 -0
  23. package/src/components/qualityControls/QualityControlHistory.js +81 -0
  24. package/src/components/qualityControls/QualityControlRoutes.js +84 -0
  25. package/src/components/qualityControls/QualityControlRow.js +24 -0
  26. package/src/components/qualityControls/QualityControlTabs.js +34 -0
  27. package/src/components/qualityControls/QualityControls.js +67 -0
  28. package/src/components/qualityControls/QualityControlsTable.js +139 -0
  29. package/src/components/qualityControls/ResultCriteria.js +120 -0
  30. package/src/components/qualityControls/ResultType.js +57 -0
  31. package/src/components/qualityControls/resultCriterias/Deviation.js +89 -0
  32. package/src/components/qualityControls/resultCriterias/ErrorsNumber.js +88 -0
  33. package/src/components/qualityControls/resultCriterias/Percentage.js +89 -0
  34. package/src/components/search/FilterDropdown.js +76 -0
  35. package/src/components/search/FilterItem.js +49 -0
  36. package/src/components/search/FilterMultilevelDropdown.js +200 -0
  37. package/src/components/search/HierarchyFilterDropdown.js +116 -0
  38. package/src/components/search/QualityControlFilters.js +60 -0
  39. package/src/components/search/QualityControlSelectedFilters.js +56 -0
  40. package/src/components/search/QualityControlsSearch.js +30 -0
  41. package/src/components/search/SearchContext.js +180 -0
  42. package/src/hooks/useQualityControls.js +74 -0
  43. package/src/styles/Expression.less +39 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@truedat/qx",
3
- "version": "5.17.2",
3
+ "version": "5.17.3",
4
4
  "description": "Truedat Web Quality Experience package",
5
5
  "sideEffects": false,
6
6
  "jsnext:main": "src/index.js",
@@ -84,7 +84,7 @@
84
84
  ]
85
85
  },
86
86
  "dependencies": {
87
- "@truedat/core": "5.17.2",
87
+ "@truedat/core": "5.17.3",
88
88
  "prop-types": "^15.8.1",
89
89
  "react-hook-form": "^7.45.4",
90
90
  "react-intl": "^5.20.10",
@@ -97,5 +97,5 @@
97
97
  "react-dom": ">= 16.8.6 < 17",
98
98
  "semantic-ui-react": ">= 2.0.3 < 2.2"
99
99
  },
100
- "gitHead": "1120a6efa4dfec423696957c5e72d752c9db284f"
100
+ "gitHead": "ec07aeeabffbb6e46d2d22ade283de203dd94c88"
101
101
  }
package/src/api.js CHANGED
@@ -2,5 +2,28 @@ const API_DATA_VIEWS = "/api/data_views";
2
2
  const API_DATA_VIEW = "/api/data_views/:id";
3
3
  const API_FUNCTIONS = "/api/quality_functions";
4
4
  const API_FUNCTION = "/api/quality_functions/:id";
5
+ const API_QUALITY_CONTROLS = "/api/quality_controls";
6
+ const API_QUALITY_CONTROL = "/api/quality_controls/:id";
7
+ const API_QUALITY_CONTROL_VERSIONS = "/api/quality_controls/:id/versions";
8
+ const API_QUALITY_CONTROL_PUBLISHED = "/api/quality_controls/:id/published";
9
+ const API_QUALITY_CONTROL_DRAFT = "/api/quality_controls/:id/draft";
10
+ const API_QUALITY_CONTROL_STATUS = "/api/quality_controls/:id/status";
11
+ const API_QUALITY_CONTROL_DOMAINS = "/api/quality_controls/:id/domains";
12
+ const API_QUALITY_CONTROL_SEARCH = "/api/quality_controls/search";
13
+ const API_QUALITY_CONTROL_FILTERS = "/api/quality_controls/filters";
5
14
 
6
- export { API_DATA_VIEWS, API_DATA_VIEW, API_FUNCTIONS, API_FUNCTION };
15
+ export {
16
+ API_DATA_VIEWS,
17
+ API_DATA_VIEW,
18
+ API_FUNCTIONS,
19
+ API_FUNCTION,
20
+ API_QUALITY_CONTROLS,
21
+ API_QUALITY_CONTROL,
22
+ API_QUALITY_CONTROL_VERSIONS,
23
+ API_QUALITY_CONTROL_PUBLISHED,
24
+ API_QUALITY_CONTROL_DRAFT,
25
+ API_QUALITY_CONTROL_STATUS,
26
+ API_QUALITY_CONTROL_DOMAINS,
27
+ API_QUALITY_CONTROL_SEARCH,
28
+ API_QUALITY_CONTROL_FILTERS,
29
+ };
@@ -1,16 +1,17 @@
1
1
  import React from "react";
2
- import { Route } from "react-router-dom";
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 { DATA_VIEWS, FUNCTIONS } from "@truedat/core/routes";
5
+ import { DATA_VIEWS, FUNCTIONS, QUALITY_CONTROLS } from "@truedat/core/routes";
6
6
  import DataViews from "./dataViews/DataViews";
7
7
  import Functions from "./functions/Functions";
8
+ import QualityControlRoutes from "./qualityControls/QualityControlRoutes";
8
9
 
9
10
  export default function QxRoutes() {
10
- const authorized = useAuthorized();
11
+ const authorized = useAuthorized("quality_control");
11
12
 
12
13
  return (
13
- <>
14
+ <Switch>
14
15
  <Route
15
16
  path={DATA_VIEWS}
16
17
  render={() => (authorized ? <DataViews /> : <Unauthorized />)}
@@ -19,6 +20,12 @@ export default function QxRoutes() {
19
20
  path={FUNCTIONS}
20
21
  render={() => (authorized ? <Functions /> : <Unauthorized />)}
21
22
  />
22
- </>
23
+ <Route
24
+ path={QUALITY_CONTROLS}
25
+ render={() =>
26
+ authorized ? <QualityControlRoutes /> : <Unauthorized />
27
+ }
28
+ />
29
+ </Switch>
23
30
  );
24
31
  }
@@ -0,0 +1,129 @@
1
+ import _ from "lodash/fp";
2
+ import React, { useContext } from "react";
3
+ import PropTypes from "prop-types";
4
+ import { Divider, Label, Segment } from "semantic-ui-react";
5
+ import { useFunctions } from "@truedat/qx/hooks/useFunctions";
6
+ import QxContext from "@truedat/qx/components/QxContext";
7
+ import { isConditionFunction } from "@truedat/qx/components/common/expressions/Condition";
8
+
9
+ export default function ClauseViewer({ clause }) {
10
+ const { data, loading } = useFunctions();
11
+ const functions = data?.data;
12
+
13
+ const _map = _.map.convert({ cap: false });
14
+ return (
15
+ <Segment loading={loading} compact basic>
16
+ {loading
17
+ ? null
18
+ : _map(({ expressions }, idx) => (
19
+ <QxContext.Provider value={{ functions }} key={idx}>
20
+ <Segment textAlign="center" className="no-margin">
21
+ {_map((expression, idx) => (
22
+ <React.Fragment key={idx}>
23
+ <ExpressionViewer expression={expression} />
24
+ {idx == _.size(expressions) - 1 ? null : (
25
+ <Divider horizontal className="divider-compact">
26
+ OR
27
+ </Divider>
28
+ )}
29
+ </React.Fragment>
30
+ ))(expressions)}
31
+ </Segment>
32
+ {idx == _.size(clause) - 1 ? null : (
33
+ <Divider horizontal className="divider-compact">
34
+ AND
35
+ </Divider>
36
+ )}
37
+ </QxContext.Provider>
38
+ ))(clause)}
39
+ </Segment>
40
+ );
41
+ }
42
+
43
+ ClauseViewer.propTypes = {
44
+ clause: PropTypes.array,
45
+ };
46
+
47
+ function ExpressionViewer({ expression }) {
48
+ const componentForShape = {
49
+ function: (value) => <ExpressionFunctionViewer value={value} />,
50
+ constant: (value) => <ExpressionConstantViewer value={value} />,
51
+ field: (value) => <ExpressionFieldViewer value={value} />,
52
+ };
53
+ return componentForShape[expression.shape](expression.value);
54
+ }
55
+
56
+ ExpressionViewer.propTypes = {
57
+ expression: PropTypes.object,
58
+ };
59
+
60
+ function ExpressionFunctionViewer({ value }) {
61
+ const { functions } = useContext(QxContext);
62
+ const { name, type } = value;
63
+ const func = _.find({ name, type })(functions);
64
+ return isConditionFunction(func) ? (
65
+ <ConditionExpressionFunctionViewer
66
+ value={value}
67
+ operator={func?.operator}
68
+ />
69
+ ) : (
70
+ <GenericExpressionFunctionViewer value={value} />
71
+ );
72
+ }
73
+ ExpressionFunctionViewer.propTypes = {
74
+ value: PropTypes.object,
75
+ };
76
+
77
+ function GenericExpressionFunctionViewer({ value }) {
78
+ return (
79
+ <div className="text-align-left">
80
+ <Label color="teal" horizontal size="tiny">
81
+ {value.name}
82
+ </Label>
83
+ (
84
+ <div className="clause-viewer-function">
85
+ {_.flow(
86
+ _.prop("args"),
87
+ _.toPairs,
88
+ _.map(([key, expression]) => (
89
+ <div key={key} className="display-flex">
90
+ <ExpressionViewer expression={expression} />
91
+ </div>
92
+ ))
93
+ )(value)}
94
+ </div>
95
+ )
96
+ </div>
97
+ );
98
+ }
99
+
100
+ GenericExpressionFunctionViewer.propTypes = {
101
+ value: PropTypes.object,
102
+ };
103
+ function ConditionExpressionFunctionViewer({ value, operator }) {
104
+ return (
105
+ <div className="condition-function-viewer">
106
+ <ExpressionViewer expression={value.args.arg1} />
107
+ <b className="font-big">{operator}</b>
108
+ <ExpressionViewer expression={value.args.arg2} />
109
+ </div>
110
+ );
111
+ }
112
+ ConditionExpressionFunctionViewer.propTypes = {
113
+ value: PropTypes.object,
114
+ operator: PropTypes.string,
115
+ };
116
+
117
+ function ExpressionConstantViewer({ value }) {
118
+ return <Label color="purple">{value.value}</Label>;
119
+ }
120
+ ExpressionConstantViewer.propTypes = {
121
+ value: PropTypes.object,
122
+ };
123
+
124
+ function ExpressionFieldViewer({ value }) {
125
+ return <Label color="blue">{value.name}</Label>;
126
+ }
127
+ ExpressionFieldViewer.propTypes = {
128
+ value: PropTypes.object,
129
+ };
@@ -11,7 +11,7 @@ import {
11
11
  ReferenceDatasetSelector,
12
12
  } from "./resourceSelectors";
13
13
 
14
- export default function ResourceSelector({ required }) {
14
+ export default function ResourceSelector({ required, labelId }) {
15
15
  const { formatMessage } = useIntl();
16
16
  const { field } = useContext(QxContext);
17
17
  const { control, setValue, watch } = useFormContext();
@@ -34,9 +34,12 @@ export default function ResourceSelector({ required }) {
34
34
  <Controller
35
35
  name={`${field}.type`}
36
36
  control={control}
37
+ rules={{ required }}
37
38
  render={({ field: { onBlur, onChange } }) => (
38
39
  <Form.Field required={required}>
39
- <label>{formatMessage({ id: "queryables.form.resource" })}</label>
40
+ <label>
41
+ {formatMessage({ id: labelId || "queryables.form.resource" })}
42
+ </label>
40
43
  <Dropdown
41
44
  selection
42
45
  placeholder={formatMessage({ id: "queryables.resource.type" })}
@@ -56,6 +59,7 @@ export default function ResourceSelector({ required }) {
56
59
  <Controller
57
60
  name={`${field}.id`}
58
61
  control={control}
62
+ rules={{ required }}
59
63
  render={({ field: { onBlur, onChange, value } }) => {
60
64
  const ResourceSelectorForType = resourceSelectorForType[type];
61
65
  return (
@@ -77,4 +81,5 @@ export default function ResourceSelector({ required }) {
77
81
 
78
82
  ResourceSelector.propTypes = {
79
83
  required: PropTypes.bool,
84
+ labelId: PropTypes.string,
80
85
  };
@@ -1,6 +1,7 @@
1
1
  import _ from "lodash/fp";
2
- import { useIntl } from "react-intl";
3
2
  import React, { useState, useContext } from "react";
3
+ import PropTypes from "prop-types";
4
+ import { useIntl } from "react-intl";
4
5
  import { useFieldArray } from "react-hook-form";
5
6
  import { Segment, Divider, Button, Form } from "semantic-ui-react";
6
7
  import QxContext from "@truedat/qx/components/QxContext";
@@ -8,7 +9,7 @@ import Condition from "./Condition";
8
9
 
9
10
  const newExpression = () => ({ shape: "function", value: null });
10
11
 
11
- export default function Clauses() {
12
+ export default function Clauses({ labelId }) {
12
13
  const { formatMessage } = useIntl();
13
14
  const context = useContext(QxContext);
14
15
  const { field } = context;
@@ -16,7 +17,9 @@ export default function Clauses() {
16
17
 
17
18
  return (
18
19
  <Form.Field>
19
- <label>{formatMessage({ id: "expression.form.clause" })}</label>
20
+ <label>
21
+ {formatMessage({ id: labelId || "expression.form.clause" })}
22
+ </label>
20
23
  {fields.map((_clauseGroup, groupIndex) => (
21
24
  <ClauseExpression
22
25
  key={`clauseGroup-${groupIndex}`}
@@ -38,6 +41,10 @@ export default function Clauses() {
38
41
  );
39
42
  }
40
43
 
44
+ Clauses.propTypes = {
45
+ labelId: PropTypes.string,
46
+ };
47
+
41
48
  const ClauseExpression = ({ groupIndex, removeGroup, lastGroup }) => {
42
49
  const { formatMessage } = useIntl();
43
50
  const context = useContext(QxContext);
@@ -98,3 +105,8 @@ const ClauseExpression = ({ groupIndex, removeGroup, lastGroup }) => {
98
105
  </div>
99
106
  );
100
107
  };
108
+ ClauseExpression.propTypes = {
109
+ groupIndex: PropTypes.number,
110
+ removeGroup: PropTypes.func,
111
+ lastGroup: PropTypes.bool,
112
+ };
@@ -1,16 +1,14 @@
1
1
  import _ from "lodash/fp";
2
2
  import { useIntl } from "react-intl";
3
3
  import React, { useState, useContext } from "react";
4
+ import PropTypes from "prop-types";
4
5
  import { Dropdown, Button, Grid } from "semantic-ui-react";
5
6
  import { Controller, useFormContext } from "react-hook-form";
6
7
  import QxContext from "@truedat/qx/components/QxContext";
7
8
  import Expression from "./Expression";
8
9
 
9
- const notEmptyExpression = _.conforms({
10
- name: _.negate(_.isNil),
11
- type: _.negate(_.isNil),
12
- });
13
- const isConditionFunction = _.conforms({
10
+ const hasTypeKey = _.flow(_.keys, _.contains("type"));
11
+ export const isConditionFunction = _.conforms({
14
12
  type: _.isEqual("boolean"),
15
13
  params: _.conforms([
16
14
  _.conforms({ name: _.isEqual("arg1"), type: _.isEqual("any") }),
@@ -22,7 +20,7 @@ const isConditionExpression = (expression, functions) => {
22
20
  const func = _.find({ name: value?.name, type: value?.type })(functions);
23
21
  return _.has("isCondition")(value)
24
22
  ? value?.isCondition
25
- : isConditionFunction(func) || !notEmptyExpression(value);
23
+ : isConditionFunction(func) || _.isNil(value) || !hasTypeKey(value);
26
24
  };
27
25
 
28
26
  export default function Condition({ onDelete }) {
@@ -176,3 +174,7 @@ export default function Condition({ onDelete }) {
176
174
  </div>
177
175
  );
178
176
  }
177
+
178
+ Condition.propTypes = {
179
+ onDelete: PropTypes.func,
180
+ };
@@ -1,5 +1,5 @@
1
1
  import _ from "lodash/fp";
2
- import React, { useEffect, useContext } from "react";
2
+ import React, { useEffect, useContext, useMemo } from "react";
3
3
  import { useIntl } from "react-intl";
4
4
  import { Controller, useFormContext } from "react-hook-form";
5
5
  import { Dropdown } from "semantic-ui-react";
@@ -23,6 +23,8 @@ export default function ShapeSelector() {
23
23
  const { control, watch, setValue } = useFormContext();
24
24
 
25
25
  const params = watch("params");
26
+ const value = watch(`${field}.value`);
27
+ const isCondition = _.prop("isCondition")(value);
26
28
 
27
29
  const emptyTypeParams = _.flow(
28
30
  _.filter((param) => type === "any" || param.type == type),
@@ -33,11 +35,15 @@ export default function ShapeSelector() {
33
35
  _.isEmpty
34
36
  )(fields);
35
37
 
36
- const shapes = _.reject(
37
- (shape) =>
38
- (shape === "param" && emptyTypeParams) ||
39
- (shape === "field" && emptyTypeFields)
40
- )(["constant", "function", "param", "field"]);
38
+ const shapes = useMemo(
39
+ () =>
40
+ _.reject(
41
+ (shape) =>
42
+ (shape === "param" && emptyTypeParams) ||
43
+ (shape === "field" && emptyTypeFields)
44
+ )(["constant", "function", "param", "field"]),
45
+ [emptyTypeParams, emptyTypeFields]
46
+ );
41
47
 
42
48
  const shape = watch(`${field}.shape`);
43
49
 
@@ -48,7 +54,7 @@ export default function ShapeSelector() {
48
54
  : "function";
49
55
  setValue(`${field}.shape`, initialShape, { shouldValidate: true });
50
56
  }
51
- }, [shape]);
57
+ }, [shape, defaultShape, field, shapes, setValue]);
52
58
 
53
59
  const shapeDropdownOptions = _.map((v) => ({
54
60
  key: v,
@@ -75,7 +81,11 @@ export default function ShapeSelector() {
75
81
  options={shapeDropdownOptions}
76
82
  onChange={(_e, { value }) => {
77
83
  onChange(value);
78
- setValue(`${field}.value`, undefined);
84
+ setValue(
85
+ `${field}.value`,
86
+ { isCondition },
87
+ { shouldValidate: true }
88
+ );
79
89
  }}
80
90
  trigger={
81
91
  <div className="shape-selector-trigger">{shapeToSymbol[shape]}</div>
@@ -59,7 +59,7 @@ describe("<ShapeSelector />", () => {
59
59
  type: "boolean",
60
60
  },
61
61
  ],
62
- test: { shape: "param" },
62
+ test: { shape: "param", value: { isCondition: undefined } },
63
63
  });
64
64
 
65
65
  expect(container).toMatchSnapshot();
@@ -79,7 +79,7 @@ describe("<ShapeSelector />", () => {
79
79
  userEvent.click(await getByRole("option", { name: /constant/i }));
80
80
 
81
81
  expect(watcher).lastCalledWith({
82
- test: { shape: "constant" },
82
+ test: { shape: "constant", value: { isCondition: undefined } },
83
83
  });
84
84
 
85
85
  expect(container).toMatchSnapshot();
@@ -3,6 +3,7 @@ import PropTypes from "prop-types";
3
3
  import { useIntl } from "react-intl";
4
4
  import {
5
5
  Button,
6
+ Container,
6
7
  Divider,
7
8
  Grid,
8
9
  GridColumn,
@@ -99,7 +100,7 @@ export default function DataViewEditor({
99
100
  </QxContext.Provider>
100
101
 
101
102
  <Divider hidden />
102
- <div className="actions">
103
+ <Container textAlign="right">
103
104
  <Button
104
105
  onClick={handleSubmit((data) => onSubmit(data))}
105
106
  primary
@@ -152,7 +153,7 @@ export default function DataViewEditor({
152
153
  onClose={(e) => e.stopPropagation()}
153
154
  />
154
155
  ) : null}
155
- </div>
156
+ </Container>
156
157
  </Form>
157
158
  </FormProvider>
158
159
  </Fragment>
@@ -161,7 +162,7 @@ export default function DataViewEditor({
161
162
 
162
163
  DataViewEditor.propTypes = {
163
164
  selectedDataView: PropTypes.object,
164
- dataViews: PropTypes.array,
165
+ context: PropTypes.object,
165
166
  onSubmit: PropTypes.func,
166
167
  onCancel: PropTypes.func,
167
168
  onDelete: PropTypes.func,
@@ -490,7 +490,7 @@ exports[`<DataViewEditor /> handles user interaction 1`] = `
490
490
  class="ui hidden divider"
491
491
  />
492
492
  <div
493
- class="actions"
493
+ class="ui right aligned container"
494
494
  >
495
495
  <button
496
496
  class="ui primary button"
@@ -975,7 +975,7 @@ exports[`<DataViewEditor /> matches the latest snapshot with content 1`] = `
975
975
  class="ui hidden divider"
976
976
  />
977
977
  <div
978
- class="actions"
978
+ class="ui right aligned container"
979
979
  >
980
980
  <button
981
981
  class="ui primary disabled button"
@@ -11,6 +11,17 @@ export const calculateGroupByFieldId = (id, type) => {
11
11
  }
12
12
  };
13
13
 
14
+ export const fieldsFromResource = (alias, parent_id) =>
15
+ _.flow(
16
+ _.propOr([], "resource.embedded.fields"),
17
+ _.map((field) => ({
18
+ ...field,
19
+ alias,
20
+ parent_id,
21
+ color: getColorById(parent_id),
22
+ }))
23
+ );
24
+
14
25
  const queryableToFields =
15
26
  (formatMessage) =>
16
27
  ({ type, properties, alias, id: parent_id }) => {
@@ -25,15 +36,7 @@ const queryableToFields =
25
36
  switch (type) {
26
37
  case "from":
27
38
  case "join":
28
- return _.flow(
29
- _.propOr([], "resource.embedded.fields"),
30
- _.map((field) => ({
31
- ...field,
32
- alias,
33
- parent_id,
34
- color: getColorById(parent_id),
35
- }))
36
- )(properties);
39
+ return fieldsFromResource(alias, parent_id)(properties);
37
40
  case "select":
38
41
  return _.flow(_.propOr([], "fields"), selectFieldsMap)(properties);
39
42
  case "group_by":
@@ -46,10 +49,13 @@ const queryableToFields =
46
49
  selectFieldsMap
47
50
  )(properties);
48
51
  return _.concat(groupFields, aggregateFields);
52
+ default:
53
+ return [];
49
54
  }
50
55
  };
51
56
 
52
57
  export const reduceQueryableFields = (formatMessage) => (queryables) => {
58
+ // eslint-disable-next-line no-unused-vars
53
59
  const [_halt, fields] = _.flow(
54
60
  _.reverse,
55
61
  _.reduce(
@@ -3,6 +3,7 @@ import PropTypes from "prop-types";
3
3
  import { useIntl } from "react-intl";
4
4
  import {
5
5
  Button,
6
+ Container,
6
7
  Divider,
7
8
  Grid,
8
9
  GridColumn,
@@ -130,7 +131,7 @@ export default function FunctionEditor({
130
131
  <Expression />
131
132
  </QxContext.Provider>
132
133
  <Divider hidden />
133
- <div className="actions">
134
+ <Container textAlign="right">
134
135
  <Button
135
136
  onClick={handleSubmit(onSubmit)}
136
137
  primary
@@ -183,7 +184,7 @@ export default function FunctionEditor({
183
184
  onClose={(e) => e.stopPropagation()}
184
185
  />
185
186
  ) : null}
186
- </div>
187
+ </Container>
187
188
  </Form>
188
189
  </FormProvider>
189
190
  </Fragment>
@@ -385,7 +385,7 @@ exports[`<FunctionEditor /> matches snapshot without onDelete 1`] = `
385
385
  class="ui hidden divider"
386
386
  />
387
387
  <div
388
- class="actions"
388
+ class="ui right aligned container"
389
389
  >
390
390
  <button
391
391
  class="ui primary disabled button"
@@ -791,7 +791,7 @@ exports[`<FunctionEditor /> matches the latest snapshot 1`] = `
791
791
  class="ui hidden divider"
792
792
  />
793
793
  <div
794
- class="actions"
794
+ class="ui right aligned container"
795
795
  >
796
796
  <button
797
797
  class="ui primary disabled button"
@@ -1155,7 +1155,7 @@ exports[`<FunctionEditor /> test cancel button with confirm 1`] = `
1155
1155
  class="ui hidden divider"
1156
1156
  />
1157
1157
  <div
1158
- class="actions"
1158
+ class="ui right aligned container"
1159
1159
  >
1160
1160
  <button
1161
1161
  class="ui primary button"
@@ -1562,7 +1562,7 @@ exports[`<FunctionEditor /> test delete button 1`] = `
1562
1562
  class="ui hidden divider"
1563
1563
  />
1564
1564
  <div
1565
- class="actions"
1565
+ class="ui right aligned container"
1566
1566
  >
1567
1567
  <button
1568
1568
  class="ui primary disabled button"
@@ -0,0 +1,73 @@
1
+ import _ from "lodash/fp";
2
+ import React from "react";
3
+ import { FormattedMessage } from "react-intl";
4
+ import { useParams, useHistory } from "react-router-dom";
5
+ import { Container, Header, Icon, Segment } from "semantic-ui-react";
6
+ import { QUALITY_CONTROLS, linkTo } from "@truedat/core/routes";
7
+ import {
8
+ useQualityControlUpdateDraft,
9
+ useQualityControl,
10
+ } from "../../hooks/useQualityControls";
11
+ import QualityControlEditor from "./QualityControlEditor";
12
+
13
+ export default function EditQualityControl() {
14
+ const { id } = useParams();
15
+ const history = useHistory();
16
+
17
+ const { trigger, isMutating } = useQualityControlUpdateDraft(id);
18
+ const { data, loading } = useQualityControl(id);
19
+ const qualityControl = data?.data;
20
+
21
+ const handleSubmit = (qualityControl) => {
22
+ const resource = _.prop("resource")(qualityControl);
23
+ const validation = _.prop("validation")(qualityControl);
24
+ trigger({
25
+ quality_control: {
26
+ ...qualityControl,
27
+ status: "draft",
28
+ resource:
29
+ !resource || _.conforms({ type: _.isNil })(resource)
30
+ ? null
31
+ : resource,
32
+ validation:
33
+ !validation ||
34
+ _.isEqual([
35
+ {
36
+ expressions: [
37
+ { shape: "function", value: { isCondition: true } },
38
+ ],
39
+ },
40
+ ])(validation)
41
+ ? []
42
+ : validation,
43
+ },
44
+ }).then((data) => {
45
+ const id = _.prop("data.data.id")(data);
46
+ history.push(linkTo.QUALITY_CONTROL({ id }));
47
+ });
48
+ };
49
+ return (
50
+ <Segment floated="left" loading={isMutating || loading}>
51
+ <Container text>
52
+ <Header as="h2">
53
+ <Icon circular name="archive" />
54
+ <Header.Content>
55
+ <FormattedMessage id="quality_controls.new.header" />
56
+ <Header.Subheader>
57
+ <FormattedMessage id="quality_controls.new.subheader" />
58
+ </Header.Subheader>
59
+ </Header.Content>
60
+ </Header>
61
+ {qualityControl ? (
62
+ <QualityControlEditor
63
+ isModification
64
+ value={qualityControl}
65
+ onSave={handleSubmit}
66
+ onCancel={() => history.push(QUALITY_CONTROLS)}
67
+ isSubmitting={false}
68
+ />
69
+ ) : null}
70
+ </Container>
71
+ </Segment>
72
+ );
73
+ }