@truedat/qx 5.17.1 → 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.
- package/package.json +3 -3
- package/src/api.js +24 -1
- package/src/components/QxRoutes.js +12 -5
- package/src/components/common/ClauseViewer.js +129 -0
- package/src/components/common/ResourceSelector.js +7 -2
- package/src/components/common/expressions/Clauses.js +15 -3
- package/src/components/common/expressions/Condition.js +8 -6
- package/src/components/common/expressions/ShapeSelector.js +18 -8
- package/src/components/common/expressions/__tests__/ShapeSelector.spec.js +2 -2
- package/src/components/dataViews/DataViewEditor.js +4 -3
- package/src/components/dataViews/__tests__/__snapshots__/DataViewEditor.spec.js.snap +2 -2
- package/src/components/dataViews/queryableFunctions.js +15 -9
- package/src/components/functions/FunctionEditor.js +3 -2
- package/src/components/functions/__tests__/__snapshots__/FunctionEditor.spec.js.snap +4 -4
- package/src/components/qualityControls/EditQualityControl.js +73 -0
- package/src/components/qualityControls/NewDraftQualityControl.js +77 -0
- package/src/components/qualityControls/NewQualityControl.js +81 -0
- package/src/components/qualityControls/QualityControl.js +93 -0
- package/src/components/qualityControls/QualityControlActions.js +67 -0
- package/src/components/qualityControls/QualityControlCrumbs.js +23 -0
- package/src/components/qualityControls/QualityControlEditor.js +271 -0
- package/src/components/qualityControls/QualityControlHeader.js +64 -0
- package/src/components/qualityControls/QualityControlHistory.js +81 -0
- package/src/components/qualityControls/QualityControlRoutes.js +84 -0
- package/src/components/qualityControls/QualityControlRow.js +24 -0
- package/src/components/qualityControls/QualityControlTabs.js +34 -0
- package/src/components/qualityControls/QualityControls.js +67 -0
- package/src/components/qualityControls/QualityControlsTable.js +139 -0
- package/src/components/qualityControls/ResultCriteria.js +120 -0
- package/src/components/qualityControls/ResultType.js +57 -0
- package/src/components/qualityControls/resultCriterias/Deviation.js +89 -0
- package/src/components/qualityControls/resultCriterias/ErrorsNumber.js +88 -0
- package/src/components/qualityControls/resultCriterias/Percentage.js +89 -0
- package/src/components/search/FilterDropdown.js +76 -0
- package/src/components/search/FilterItem.js +49 -0
- package/src/components/search/FilterMultilevelDropdown.js +200 -0
- package/src/components/search/HierarchyFilterDropdown.js +116 -0
- package/src/components/search/QualityControlFilters.js +60 -0
- package/src/components/search/QualityControlSelectedFilters.js +56 -0
- package/src/components/search/QualityControlsSearch.js +30 -0
- package/src/components/search/SearchContext.js +180 -0
- package/src/hooks/useQualityControls.js +74 -0
- 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.
|
|
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.
|
|
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": "
|
|
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 {
|
|
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>
|
|
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>
|
|
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
|
|
10
|
-
|
|
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) || !
|
|
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 =
|
|
37
|
-
(
|
|
38
|
-
(
|
|
39
|
-
|
|
40
|
-
|
|
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(
|
|
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
|
-
<
|
|
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
|
-
</
|
|
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
|
-
|
|
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="
|
|
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="
|
|
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
|
|
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
|
-
<
|
|
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
|
-
</
|
|
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="
|
|
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="
|
|
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="
|
|
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="
|
|
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
|
+
}
|