@truedat/qx 5.14.0 → 5.15.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 (120) hide show
  1. package/package.json +3 -3
  2. package/src/api.js +3 -2
  3. package/src/components/QxRoutes.js +4 -4
  4. package/src/components/common/ResourceSelector.js +80 -0
  5. package/src/components/common/TestFormWrapper.js +9 -6
  6. package/src/components/common/TypeSelector.js +3 -1
  7. package/src/components/common/__tests__/DescriptionInput.spec.js +48 -0
  8. package/src/components/common/__tests__/ResourceSelector.spec.js +124 -0
  9. package/src/components/common/__tests__/TypeSelector.spec.js +29 -0
  10. package/src/components/common/__tests__/__snapshots__/DescriptionInput.spec.js.snap +19 -0
  11. package/src/components/common/__tests__/__snapshots__/ResourceSelector.spec.js.snap +484 -0
  12. package/src/components/common/__tests__/__snapshots__/TypeSelector.spec.js.snap +230 -0
  13. package/src/components/common/expressions/Clauses.js +100 -0
  14. package/src/components/common/expressions/Condition.js +178 -0
  15. package/src/components/common/expressions/Expression.js +54 -0
  16. package/src/components/common/expressions/FieldSelector.js +66 -0
  17. package/src/components/{functions → common}/expressions/FunctionArgs.js +1 -0
  18. package/src/components/{functions → common}/expressions/FunctionSelector.js +24 -4
  19. package/src/components/{functions → common}/expressions/ParamSelector.js +3 -3
  20. package/src/components/{functions → common}/expressions/ShapeSelector.js +22 -6
  21. package/src/components/common/expressions/__tests__/Clauses.spec.js +53 -0
  22. package/src/components/common/expressions/__tests__/Condition.spec.js +33 -0
  23. package/src/components/{functions → common}/expressions/__tests__/ConstantSelector.spec.js +6 -1
  24. package/src/components/{functions → common}/expressions/__tests__/Expression.spec.js +7 -5
  25. package/src/components/common/expressions/__tests__/FieldSelector.spec.js +24 -0
  26. package/src/components/{functions → common}/expressions/__tests__/FunctionArgs.spec.js +20 -6
  27. package/src/components/{functions → common}/expressions/__tests__/FunctionSelector.spec.js +1 -1
  28. package/src/components/{functions → common}/expressions/__tests__/ParamSelector.spec.js +22 -10
  29. package/src/components/{functions → common}/expressions/__tests__/ShapeSelector.spec.js +3 -3
  30. package/src/components/common/expressions/__tests__/__snapshots__/Clauses.spec.js.snap +64 -0
  31. package/src/components/common/expressions/__tests__/__snapshots__/Condition.spec.js.snap +316 -0
  32. package/src/components/{functions → common}/expressions/__tests__/__snapshots__/ConstantSelector.spec.js.snap +1 -1
  33. package/src/components/common/expressions/__tests__/__snapshots__/Expression.spec.js.snap +939 -0
  34. package/src/components/common/expressions/__tests__/__snapshots__/FieldSelector.spec.js.snap +5 -0
  35. package/src/components/common/expressions/__tests__/__snapshots__/FunctionArgs.spec.js.snap +344 -0
  36. package/src/components/{functions → common}/expressions/__tests__/__snapshots__/ParamSelector.spec.js.snap +4 -4
  37. package/src/components/{functions → common}/expressions/__tests__/__snapshots__/ShapeSelector.spec.js.snap +17 -24
  38. package/src/components/common/expressions/constantInputs/AnySelector.js +44 -0
  39. package/src/components/{functions → common}/expressions/constantInputs/__tests__/AnySelector.spec.js +21 -14
  40. package/src/components/{functions → common}/expressions/constantInputs/__tests__/DefaultSelector.spec.js +5 -2
  41. package/src/components/{functions → common}/expressions/constantInputs/__tests__/__snapshots__/AnySelector.spec.js.snap +2 -2
  42. package/src/components/common/expressions/index.js +4 -0
  43. package/src/components/common/resourceSelectors/DataStructureSelector.js +63 -0
  44. package/src/components/common/resourceSelectors/DataViewSelector.js +65 -0
  45. package/src/components/common/resourceSelectors/ReferenceDatasetSelector.js +61 -0
  46. package/src/components/common/resourceSelectors/__tests__/DataStructureSelector.spec.js +31 -0
  47. package/src/components/common/resourceSelectors/__tests__/DataViewSelector.spec.js +133 -0
  48. package/src/components/common/resourceSelectors/__tests__/ReferenceDatasetSelector.spec.js +97 -0
  49. package/src/components/common/resourceSelectors/__tests__/__snapshots__/DataStructureSelector.spec.js.snap +105 -0
  50. package/src/components/common/resourceSelectors/__tests__/__snapshots__/DataViewSelector.spec.js.snap +50 -0
  51. package/src/components/common/resourceSelectors/__tests__/__snapshots__/ReferenceDatasetSelector.spec.js.snap +50 -0
  52. package/src/components/common/resourceSelectors/index.js +5 -0
  53. package/src/components/dataViews/DataViewEditor.js +170 -0
  54. package/src/components/dataViews/DataViewSelect.js +47 -0
  55. package/src/components/dataViews/DataViews.js +169 -0
  56. package/src/components/dataViews/Queryable.js +138 -0
  57. package/src/components/dataViews/Queryables.js +53 -0
  58. package/src/components/dataViews/__tests__/DataViewEditor.spec.js +293 -0
  59. package/src/components/dataViews/__tests__/DataViewSelect.spec.js +28 -0
  60. package/src/components/dataViews/__tests__/DataViews.spec.js +57 -0
  61. package/src/components/dataViews/__tests__/Queryable.spec.js +201 -0
  62. package/src/components/dataViews/__tests__/Queryables.spec.js +183 -0
  63. package/src/components/dataViews/__tests__/__snapshots__/DataViewEditor.spec.js.snap +1000 -0
  64. package/src/components/dataViews/__tests__/__snapshots__/DataViewSelect.spec.js.snap +55 -0
  65. package/src/components/dataViews/__tests__/__snapshots__/DataViews.spec.js.snap +77 -0
  66. package/src/components/dataViews/__tests__/__snapshots__/Queryable.spec.js.snap +874 -0
  67. package/src/components/dataViews/__tests__/__snapshots__/Queryables.spec.js.snap +911 -0
  68. package/src/components/dataViews/__tests__/queryableFunctions.spec.js +193 -0
  69. package/src/components/dataViews/queryableFunctions.js +80 -0
  70. package/src/components/dataViews/queryableProperties/From.js +15 -0
  71. package/src/components/dataViews/queryableProperties/GroupBy.js +124 -0
  72. package/src/components/dataViews/queryableProperties/Join.js +62 -0
  73. package/src/components/dataViews/queryableProperties/JoinTypeIcon.js +49 -0
  74. package/src/components/dataViews/queryableProperties/Select.js +76 -0
  75. package/src/components/dataViews/queryableProperties/SelectField.js +116 -0
  76. package/src/components/dataViews/queryableProperties/Where.js +15 -0
  77. package/src/components/dataViews/queryableProperties/__tests__/From.spec.js +30 -0
  78. package/src/components/dataViews/queryableProperties/__tests__/GroupBy.spec.js +75 -0
  79. package/src/components/dataViews/queryableProperties/__tests__/Join.spec.js +38 -0
  80. package/src/components/dataViews/queryableProperties/__tests__/JoinTypeIcon.spec.js +36 -0
  81. package/src/components/dataViews/queryableProperties/__tests__/Select.spec.js +144 -0
  82. package/src/components/dataViews/queryableProperties/__tests__/SelectField.spec.js +107 -0
  83. package/src/components/dataViews/queryableProperties/__tests__/Where.spec.js +27 -0
  84. package/src/components/dataViews/queryableProperties/__tests__/__snapshots__/From.spec.js.snap +75 -0
  85. package/src/components/dataViews/queryableProperties/__tests__/__snapshots__/GroupBy.spec.js.snap +276 -0
  86. package/src/components/dataViews/queryableProperties/__tests__/__snapshots__/Join.spec.js.snap +252 -0
  87. package/src/components/dataViews/queryableProperties/__tests__/__snapshots__/JoinTypeIcon.spec.js.snap +77 -0
  88. package/src/components/dataViews/queryableProperties/__tests__/__snapshots__/Select.spec.js.snap +411 -0
  89. package/src/components/dataViews/queryableProperties/__tests__/__snapshots__/SelectField.spec.js.snap +343 -0
  90. package/src/components/dataViews/queryableProperties/__tests__/__snapshots__/Where.spec.js.snap +22 -0
  91. package/src/components/dataViews/queryableProperties/index.js +7 -0
  92. package/src/components/functions/FunctionEditor.js +30 -29
  93. package/src/components/functions/Functions.js +3 -11
  94. package/src/components/functions/__tests__/FunctionEditor.spec.js +6 -0
  95. package/src/components/functions/__tests__/FunctionParams.spec.js +6 -0
  96. package/src/components/functions/__tests__/__snapshots__/FunctionEditor.spec.js.snap +490 -466
  97. package/src/components/functions/__tests__/__snapshots__/FunctionParams.spec.js.snap +1 -1
  98. package/src/hooks/__tests__/{useDataSets.spec.js → useDataViews.spec.js} +5 -5
  99. package/src/hooks/useDataViews.js +33 -0
  100. package/src/styles/Expression.less +150 -0
  101. package/src/types.js +11 -3
  102. package/src/components/DataSets.js +0 -64
  103. package/src/components/__tests__/DataSets.spec.js +0 -46
  104. package/src/components/__tests__/__snapshots__/DataSets.spec.js.snap +0 -82
  105. package/src/components/functions/expressions/Expression.js +0 -40
  106. package/src/components/functions/expressions/FieldSelector.js +0 -56
  107. package/src/components/functions/expressions/__tests__/__snapshots__/Expression.spec.js.snap +0 -904
  108. package/src/components/functions/expressions/__tests__/__snapshots__/FunctionArgs.spec.js.snap +0 -392
  109. package/src/components/functions/expressions/constantInputs/AnySelector.js +0 -29
  110. package/src/hooks/useDataSets.js +0 -8
  111. /package/src/components/{functions → common}/expressions/ConstantSelector.js +0 -0
  112. /package/src/components/{functions → common}/expressions/__tests__/__snapshots__/FunctionSelector.spec.js.snap +0 -0
  113. /package/src/components/{functions → common/expressions}/__tests__/useWatchParams.spec.js +0 -0
  114. /package/src/components/{functions → common}/expressions/constantInputs/BooleanSelector.js +0 -0
  115. /package/src/components/{functions → common}/expressions/constantInputs/DefaultSelector.js +0 -0
  116. /package/src/components/{functions → common}/expressions/constantInputs/__tests__/BooleanSelector.spec.js +0 -0
  117. /package/src/components/{functions → common}/expressions/constantInputs/__tests__/__snapshots__/BooleanSelector.spec.js.snap +0 -0
  118. /package/src/components/{functions → common}/expressions/constantInputs/__tests__/__snapshots__/DefaultSelector.spec.js.snap +0 -0
  119. /package/src/components/{functions → common}/expressions/constantInputs/index.js +0 -0
  120. /package/src/components/{functions → common/expressions}/useWatchParams.js +0 -0
@@ -0,0 +1,100 @@
1
+ import _ from "lodash/fp";
2
+ import { useIntl } from "react-intl";
3
+ import React, { useState, useContext } from "react";
4
+ import { useFieldArray } from "react-hook-form";
5
+ import { Segment, Divider, Button, Form } from "semantic-ui-react";
6
+ import QxContext from "@truedat/qx/components/QxContext";
7
+ import Condition from "./Condition";
8
+
9
+ const newExpression = () => ({ shape: "function", value: null });
10
+
11
+ export default function Clauses() {
12
+ const { formatMessage } = useIntl();
13
+ const context = useContext(QxContext);
14
+ const { field } = context;
15
+ const { fields, append, remove } = useFieldArray({ name: field });
16
+
17
+ return (
18
+ <Form.Field>
19
+ <label>{formatMessage({ id: "expression.form.clause" })}</label>
20
+ {fields.map((_clauseGroup, groupIndex) => (
21
+ <ClauseExpression
22
+ key={`clauseGroup-${groupIndex}`}
23
+ groupIndex={groupIndex}
24
+ removeGroup={_.size(fields) >= 2 ? remove : null}
25
+ lastGroup={groupIndex === _.size(fields) - 1}
26
+ />
27
+ ))}
28
+
29
+ <Divider horizontal>
30
+ <Button
31
+ size="mini"
32
+ onClick={() => append({ expressions: [newExpression()] })}
33
+ >
34
+ {formatMessage({ id: "expression.clause.action.addGroup" })}
35
+ </Button>
36
+ </Divider>
37
+ </Form.Field>
38
+ );
39
+ }
40
+
41
+ const ClauseExpression = ({ groupIndex, removeGroup, lastGroup }) => {
42
+ const { formatMessage } = useIntl();
43
+ const context = useContext(QxContext);
44
+ const { field } = context;
45
+ const [isShownDelete, setIsShownDelete] = useState();
46
+ const { fields, append, remove } = useFieldArray({
47
+ name: `${field}[${groupIndex}].expressions`,
48
+ });
49
+
50
+ return (
51
+ <div
52
+ onMouseEnter={() => setIsShownDelete(true)}
53
+ onMouseLeave={() => setIsShownDelete(false)}
54
+ >
55
+ <Segment>
56
+ <div className="clause-and-group">
57
+ {isShownDelete && removeGroup ? (
58
+ <Button
59
+ size="mini"
60
+ onClick={() => removeGroup(groupIndex)}
61
+ icon="trash alternate outline"
62
+ basic
63
+ color="red"
64
+ aria-label="delete"
65
+ />
66
+ ) : null}
67
+ </div>
68
+
69
+ {fields.map((_clauseExpression, expressionIndex) => (
70
+ <div key={`and-${groupIndex}-${expressionIndex}`}>
71
+ <QxContext.Provider
72
+ value={{
73
+ ...context,
74
+ field: `${field}[${groupIndex}].expressions[${expressionIndex}]`,
75
+ type: "boolean",
76
+ }}
77
+ >
78
+ <Condition
79
+ onDelete={
80
+ _.size(fields) < 2 ? null : () => remove(expressionIndex)
81
+ }
82
+ />
83
+ </QxContext.Provider>
84
+ <Divider horizontal>
85
+ {expressionIndex === _.size(fields) - 1 ? null : "AND"}
86
+ </Divider>
87
+ </div>
88
+ ))}
89
+ <Divider horizontal>
90
+ <Button size="mini" onClick={() => append(newExpression())}>
91
+ {formatMessage({
92
+ id: "expression.clause.action.addExpression",
93
+ })}
94
+ </Button>
95
+ </Divider>
96
+ </Segment>
97
+ <Divider horizontal>{lastGroup ? null : "OR"}</Divider>
98
+ </div>
99
+ );
100
+ };
@@ -0,0 +1,178 @@
1
+ import _ from "lodash/fp";
2
+ import { useIntl } from "react-intl";
3
+ import React, { useState, useContext } from "react";
4
+ import { Dropdown, Button, Grid } from "semantic-ui-react";
5
+ import { Controller, useFormContext } from "react-hook-form";
6
+ import QxContext from "@truedat/qx/components/QxContext";
7
+ import Expression from "./Expression";
8
+
9
+ const notEmptyExpression = _.conforms({
10
+ name: _.negate(_.isNil),
11
+ type: _.negate(_.isNil),
12
+ });
13
+ const isConditionFunction = _.conforms({
14
+ type: _.isEqual("boolean"),
15
+ params: _.conforms([
16
+ _.conforms({ name: _.isEqual("arg1"), type: _.isEqual("any") }),
17
+ _.conforms({ name: _.isEqual("arg2"), type: _.isEqual("any") }),
18
+ ]),
19
+ });
20
+ const isConditionExpression = (expression, functions) => {
21
+ const value = _.propOr({}, "value")(expression);
22
+ const func = _.find({ name: value?.name, type: value?.type })(functions);
23
+ return _.has("isCondition")(value)
24
+ ? value?.isCondition
25
+ : isConditionFunction(func) || !notEmptyExpression(value);
26
+ };
27
+
28
+ export default function Condition({ onDelete }) {
29
+ const { formatMessage } = useIntl();
30
+ const [isShownDelete, setIsShownDelete] = useState();
31
+ const context = useContext(QxContext);
32
+ const { control, watch } = useFormContext();
33
+ const { field, functions } = context;
34
+
35
+ const expression = watch(field);
36
+ const isCondition = isConditionExpression(expression, functions);
37
+
38
+ const conditionFunctions = _.filter(isConditionFunction)(functions);
39
+ const condFuncOptions = [
40
+ ..._.map(({ name, operator, description }) => ({
41
+ key: name,
42
+ text: operator || name,
43
+ content: <span title={description}>{operator || name}</span>,
44
+ value: name,
45
+ }))(conditionFunctions),
46
+ {
47
+ key: "customExpression",
48
+ text: formatMessage({ id: "expression.condition.customExpression" }),
49
+ value: "customExpression",
50
+ },
51
+ ];
52
+
53
+ const operatorTrigger = (name) => {
54
+ const currentCondFunction = _.find({ name })(conditionFunctions);
55
+ return (
56
+ <div className="condition-operator-trigger">
57
+ {currentCondFunction ? (
58
+ <b>
59
+ <code>
60
+ {currentCondFunction.operator || currentCondFunction.name}
61
+ </code>
62
+ </b>
63
+ ) : (
64
+ <span>
65
+ {formatMessage({
66
+ id: "expression.condition.selectFunction",
67
+ })}
68
+ </span>
69
+ )}
70
+ </div>
71
+ );
72
+ };
73
+
74
+ return (
75
+ <div
76
+ onMouseEnter={() => setIsShownDelete(true)}
77
+ onMouseLeave={() => setIsShownDelete(false)}
78
+ >
79
+ {!isCondition ? (
80
+ <QxContext.Provider
81
+ value={{
82
+ ...context,
83
+ field,
84
+ omitLogicOperators: true,
85
+ }}
86
+ >
87
+ <Expression onDelete={isShownDelete ? onDelete : null} deletable />
88
+ </QxContext.Provider>
89
+ ) : (
90
+ <Grid className="condition-row">
91
+ <Grid.Column width={6}>
92
+ <QxContext.Provider
93
+ value={{
94
+ ...context,
95
+ type: "any",
96
+ field: `${field}.value.args.arg1`,
97
+ omitLogicOperators: true,
98
+ defaultShape: "field",
99
+ }}
100
+ >
101
+ <Expression />
102
+ </QxContext.Provider>
103
+ </Grid.Column>
104
+
105
+ <Grid.Column width={3}>
106
+ <div className="flex-justify-center">
107
+ <Controller
108
+ control={control}
109
+ name={`${field}.value`}
110
+ rules={{
111
+ required: formatMessage({ id: "functions.form.required" }),
112
+ }}
113
+ render={({
114
+ field: { onBlur, onChange },
115
+ fieldState: { error },
116
+ }) => (
117
+ <Dropdown
118
+ options={condFuncOptions}
119
+ onBlur={onBlur}
120
+ error={!!error}
121
+ value={expression?.value?.name || ""}
122
+ onChange={(_e, { value }) => {
123
+ if (value == "customExpression") {
124
+ onChange({
125
+ ...(expression?.value || {}),
126
+ isCondition: false,
127
+ });
128
+ } else {
129
+ onChange({
130
+ ...(expression?.value || {}),
131
+ name: value,
132
+ type: "boolean",
133
+ isCondition: true,
134
+ });
135
+ }
136
+ }}
137
+ trigger={operatorTrigger(expression?.value?.name)}
138
+ pointing="top left"
139
+ icon={null}
140
+ />
141
+ )}
142
+ />
143
+ </div>
144
+ </Grid.Column>
145
+
146
+ <Grid.Column width={6}>
147
+ <QxContext.Provider
148
+ value={{
149
+ ...context,
150
+ type: "any",
151
+ field: `${field}.value.args.arg2`,
152
+ omitLogicOperators: true,
153
+ defaultShape: "field",
154
+ }}
155
+ >
156
+ <Expression />
157
+ </QxContext.Provider>
158
+ </Grid.Column>
159
+
160
+ <Grid.Column width={1} verticalAlign="middle">
161
+ {onDelete && isShownDelete ? (
162
+ <div className="delete-expression-column">
163
+ <Button
164
+ size="mini"
165
+ onClick={onDelete}
166
+ icon="trash alternate outline"
167
+ basic
168
+ color="red"
169
+ aria-label="delete"
170
+ />
171
+ </div>
172
+ ) : null}
173
+ </Grid.Column>
174
+ </Grid>
175
+ )}
176
+ </div>
177
+ );
178
+ }
@@ -0,0 +1,54 @@
1
+ import _ from "lodash/fp";
2
+ import React, { useContext } from "react";
3
+ import PropTypes from "prop-types";
4
+ import { useFormContext } from "react-hook-form";
5
+ import { Button, Grid, Icon } from "semantic-ui-react";
6
+ import QxContext from "@truedat/qx/components/QxContext";
7
+ import ShapeSelector from "./ShapeSelector";
8
+ import FunctionSelector from "./FunctionSelector";
9
+ import ParamSelector from "./ParamSelector";
10
+ import FieldSelector from "./FieldSelector";
11
+ import ConstantSelector from "./ConstantSelector";
12
+
13
+ export default function Expression({ onDelete, deletable }) {
14
+ const { field, aggregate } = useContext(QxContext);
15
+ const { watch } = useFormContext();
16
+ const shape = watch(`${field}.shape`);
17
+
18
+ const valueComponentForShape = {
19
+ function: <FunctionSelector />,
20
+ constant: <ConstantSelector />,
21
+ param: <ParamSelector />,
22
+ field: <FieldSelector />,
23
+ };
24
+
25
+ return (
26
+ <Grid>
27
+ <Grid.Column width={deletable ? 15 : 16}>
28
+ <div className="expression-container">
29
+ {!aggregate ? <ShapeSelector /> : null}
30
+ {shape ? valueComponentForShape[shape] : null}
31
+ </div>
32
+ </Grid.Column>
33
+ {deletable ? (
34
+ <Grid.Column width={1} verticalAlign="middle">
35
+ {onDelete ? (
36
+ <Button
37
+ size="mini"
38
+ onClick={onDelete}
39
+ icon="trash alternate outline"
40
+ basic
41
+ color="red"
42
+ aria-label="delete"
43
+ />
44
+ ) : null}
45
+ </Grid.Column>
46
+ ) : null}
47
+ </Grid>
48
+ );
49
+ }
50
+
51
+ Expression.propTypes = {
52
+ onDelete: PropTypes.func,
53
+ deletable: PropTypes.bool,
54
+ };
@@ -0,0 +1,66 @@
1
+ import _ from "lodash/fp";
2
+ import React, { useContext, useEffect } from "react";
3
+ import { Controller, useFormContext } from "react-hook-form";
4
+ import { Dropdown, Label } from "semantic-ui-react";
5
+
6
+ import QxContext from "@truedat/qx/components/QxContext";
7
+ import { getColorById } from "../../dataViews/queryableFunctions";
8
+
9
+ const fieldToKey = ({ parent_id, id }) => `${parent_id}:${id}`;
10
+ const keyToField = (key) => {
11
+ if (!key) return {};
12
+ const [parent_id, id] = key.split(":");
13
+ return { parent_id: parseInt(parent_id), id: parseInt(id) };
14
+ };
15
+ export const fieldsToOptions = _.map((field) => ({
16
+ key: fieldToKey(field),
17
+ value: fieldToKey(field),
18
+ text: field.name,
19
+ image: (
20
+ <Label
21
+ color={getColorById(field.parent_id)}
22
+ content={field?.alias || field?.parent_name}
23
+ />
24
+ ),
25
+ }));
26
+
27
+ export default function FieldSelector() {
28
+ const { field, type, fields } = useContext(QxContext);
29
+ const { control, watch, setValue } = useFormContext();
30
+ const fieldOptions = _.flow(
31
+ _.filter((field) => type === "any" || field.type == type),
32
+ fieldsToOptions
33
+ )(fields);
34
+
35
+ const selectedField = watch(`${field}.value`);
36
+ const selectedKey = selectedField ? fieldToKey(selectedField) : null;
37
+ const selectedOption = _.find({ key: selectedKey })(fieldOptions);
38
+
39
+ useEffect(() => {
40
+ if (selectedKey && !selectedOption) setValue(`${field}.value`, null);
41
+ }, [selectedOption, selectedKey]);
42
+
43
+ return _.isEmpty(fieldOptions) ? null : (
44
+ <Controller
45
+ control={control}
46
+ name={`${field}.value`}
47
+ rules={{ required: true }}
48
+ render={({ field: { onBlur, onChange }, fieldState: { error } }) => (
49
+ <Dropdown
50
+ className="select-field-dropdown"
51
+ selection
52
+ fluid
53
+ pointing
54
+ scrolling
55
+ onBlur={onBlur}
56
+ error={!!error}
57
+ value={selectedKey}
58
+ options={fieldOptions}
59
+ onChange={(_e, { value }) =>
60
+ onChange(_.find(keyToField(value))(fields))
61
+ }
62
+ />
63
+ )}
64
+ />
65
+ );
66
+ }
@@ -37,6 +37,7 @@ export default function FunctionArgs() {
37
37
  ...context,
38
38
  type,
39
39
  field: `${field}.value.args.${name}`,
40
+ aggregate: false,
40
41
  }}
41
42
  >
42
43
  <Expression />
@@ -9,7 +9,8 @@ import FunctionArgs from "./FunctionArgs";
9
9
 
10
10
  export default function FunctionSelector() {
11
11
  const { formatMessage } = useIntl();
12
- const { functions, field, type } = useContext(QxContext);
12
+ const { functions, field, type, omitLogicOperators, aggregate } =
13
+ useContext(QxContext);
13
14
  const { control, watch } = useFormContext();
14
15
 
15
16
  const [collapsed, setCollapsed] = useState(false);
@@ -21,8 +22,20 @@ export default function FunctionSelector() {
21
22
  };
22
23
  const functionName = watch("name");
23
24
  const functionType = watch("type");
25
+ const currentFunction = watch(`${field}.value`);
24
26
 
25
- const funcs = type === "any" ? functions : _.filter({ type })(functions);
27
+ const validFunctionType = (func) => type === "any" || func.type === type;
28
+ const validLogicOperator = (func) =>
29
+ !omitLogicOperators ||
30
+ !(_.includes(func.name)(["and", "or"]) && func.type === "boolean");
31
+ const validAggregate = (func) => !(aggregate ^ (func.class === "aggregator"));
32
+
33
+ const funcs = _.filter(
34
+ (func) =>
35
+ validFunctionType(func) &&
36
+ validLogicOperator(func) &&
37
+ validAggregate(func)
38
+ )(functions);
26
39
 
27
40
  const funcOpts = _.flow(
28
41
  _.reject({ name: functionName, type: functionType }),
@@ -36,7 +49,7 @@ export default function FunctionSelector() {
36
49
  <div className="flex-center">
37
50
  <div>{name}</div>
38
51
  <div className="function-params-label">
39
- {params.map(({ type: paramType, name: paramName }, index) => (
52
+ {params?.map(({ type: paramType, name: paramName }, index) => (
40
53
  <Label key={index} color={typeToColor(paramType)}>
41
54
  <Icon name={typeToIcon(paramType)} />
42
55
  {paramName}
@@ -73,7 +86,14 @@ export default function FunctionSelector() {
73
86
  onBlur={onBlur}
74
87
  error={!!error}
75
88
  options={funcOpts}
76
- onChange={(_e, { value }) => onChange(keyToFn(value))}
89
+ onChange={(_e, { value }) =>
90
+ onChange(
91
+ _.omit(["isCondition"])({
92
+ ...currentFunction,
93
+ ...keyToFn(value),
94
+ })
95
+ )
96
+ }
77
97
  placeholder={formatMessage(
78
98
  { id: "functions.form.expression.function.placeholder" },
79
99
  { type }
@@ -3,14 +3,14 @@ import React, { useContext, useEffect } from "react";
3
3
  import { Controller, useFormContext } from "react-hook-form";
4
4
  import { Dropdown } from "semantic-ui-react";
5
5
  import QxContext from "@truedat/qx/components/QxContext";
6
- import useWatchParams from "../useWatchParams";
6
+ import useWatchParams from "./useWatchParams";
7
7
 
8
8
  export default function ParamSelector() {
9
9
  const { field, type } = useContext(QxContext);
10
10
  const { control, watch, setValue } = useFormContext();
11
11
 
12
12
  const params = useWatchParams();
13
- const value = watch(`${field}.value`);
13
+ const value = watch(`${field}.value.id`);
14
14
 
15
15
  const options = _.flow(
16
16
  _.filter((field) => type === "any" || field.type == type),
@@ -30,7 +30,7 @@ export default function ParamSelector() {
30
30
  return (
31
31
  <Controller
32
32
  control={control}
33
- name={`${field}.value`}
33
+ name={`${field}.value.id`}
34
34
  rules={{ required: true }}
35
35
  render={({
36
36
  field: { onBlur, onChange, value },
@@ -1,5 +1,5 @@
1
1
  import _ from "lodash/fp";
2
- import React, { useContext } from "react";
2
+ import React, { useEffect, useContext } 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";
@@ -14,7 +14,12 @@ const shapeToSymbol = {
14
14
 
15
15
  export default function ShapeSelector() {
16
16
  const { formatMessage } = useIntl();
17
- const { fields, field, type } = useContext(QxContext);
17
+ const {
18
+ fields,
19
+ field,
20
+ type,
21
+ defaultShape = "function",
22
+ } = useContext(QxContext);
18
23
  const { control, watch, setValue } = useFormContext();
19
24
 
20
25
  const params = watch("params");
@@ -34,11 +39,22 @@ export default function ShapeSelector() {
34
39
  (shape === "field" && emptyTypeFields)
35
40
  )(["constant", "function", "param", "field"]);
36
41
 
42
+ const shape = watch(`${field}.shape`);
43
+
44
+ useEffect(() => {
45
+ if (!shape) {
46
+ const initialShape = _.contains(defaultShape)(shapes)
47
+ ? defaultShape
48
+ : "function";
49
+ setValue(`${field}.shape`, initialShape, { shouldValidate: true });
50
+ }
51
+ }, [shape]);
52
+
37
53
  const shapeDropdownOptions = _.map((v) => ({
38
54
  key: v,
39
55
  value: v,
40
56
  content: (
41
- <div style={{ display: "flex", alignItems: "center" }}>
57
+ <div className="flex-center">
42
58
  <small style={{ marginRight: "5px" }}>
43
59
  <b>
44
60
  <code>{shapeToSymbol[v]}</code>
@@ -53,7 +69,7 @@ export default function ShapeSelector() {
53
69
  <Controller
54
70
  name={`${field}.shape`}
55
71
  control={control}
56
- render={({ field: { onBlur, onChange, value } }) => (
72
+ render={({ field: { onBlur, onChange } }) => (
57
73
  <Dropdown
58
74
  onBlur={onBlur}
59
75
  options={shapeDropdownOptions}
@@ -62,9 +78,9 @@ export default function ShapeSelector() {
62
78
  setValue(`${field}.value`, undefined);
63
79
  }}
64
80
  trigger={
65
- <div className="shape-selector-trigger">{shapeToSymbol[value]}</div>
81
+ <div className="shape-selector-trigger">{shapeToSymbol[shape]}</div>
66
82
  }
67
- value={value}
83
+ value={shape}
68
84
  pointing="top left"
69
85
  icon={null}
70
86
  />
@@ -0,0 +1,53 @@
1
+ import React from "react";
2
+ import { render } from "@truedat/test/render";
3
+ import TestFormWrapper from "@truedat/qx/components/common/TestFormWrapper";
4
+ import Clauses from "../Clauses";
5
+
6
+ const renderOpts = {
7
+ messages: {
8
+ en: {
9
+ "expression.clause.action.addGroup": "Add Group",
10
+ "expression.form.clause": "Clause",
11
+ "expression.clause.action.addExpression": "addExpression",
12
+ },
13
+ },
14
+ fallback: "lazy",
15
+ };
16
+
17
+ describe("<Clauses />", () => {
18
+ it("matches the latest snapshot", () => {
19
+ const { container } = render(
20
+ <TestFormWrapper>
21
+ <Clauses />
22
+ </TestFormWrapper>,
23
+ renderOpts
24
+ );
25
+ expect(container).toMatchSnapshot();
26
+ });
27
+ it("matches the latest snapshot with content", () => {
28
+ const { container } = render(
29
+ <TestFormWrapper
30
+ context={{
31
+ field: "test",
32
+ }}
33
+ defaultValues={{
34
+ test: [
35
+ [
36
+ {
37
+ shape: "function",
38
+ value: {
39
+ name: "eq",
40
+ type: "boolean",
41
+ },
42
+ },
43
+ ],
44
+ ],
45
+ }}
46
+ >
47
+ <Clauses />
48
+ </TestFormWrapper>,
49
+ renderOpts
50
+ );
51
+ expect(container).toMatchSnapshot();
52
+ });
53
+ });
@@ -0,0 +1,33 @@
1
+ import React from "react";
2
+ import { act } from "react-dom/test-utils";
3
+ import { render } from "@truedat/test/render";
4
+ import TestFormWrapper from "@truedat/qx/components/common/TestFormWrapper";
5
+ import Condition from "../Condition";
6
+
7
+ const renderOpts = {
8
+ messages: {
9
+ en: {
10
+ "functions.form.required": "Required",
11
+ "expression.condition.selectFunction": "Select Function",
12
+ "expression.condition.customExpression": "Custom Expression",
13
+ "functions.form.expression.function.placeholder": "function placeholder",
14
+ "functions.expression.shape.function": "Function",
15
+ "functions.expression.shape.constant": "Constant",
16
+ },
17
+ },
18
+ };
19
+
20
+ describe("<Condition />", () => {
21
+ it("matches the latest snapshot", async () => {
22
+ const { container } = render(
23
+ <TestFormWrapper>
24
+ <Condition />
25
+ </TestFormWrapper>,
26
+ renderOpts
27
+ );
28
+
29
+ await act(async () => {
30
+ expect(container).toMatchSnapshot();
31
+ });
32
+ });
33
+ });
@@ -10,6 +10,11 @@ const renderOpts = {
10
10
  "functions.expression.constant.false": "false",
11
11
  "functions.expression.constant.true": "true",
12
12
  "functions.form.required": "required",
13
+ "expressions.data_type.timestamp": "timestamp",
14
+ "expressions.data_type.date": "date",
15
+ "expressions.data_type.number": "number",
16
+ "expressions.data_type.string": "string",
17
+ "expressions.data_type.boolean": "boolean",
13
18
  },
14
19
  },
15
20
  fallback: "lazy",
@@ -30,7 +35,7 @@ describe("<ConstantSelector />", () => {
30
35
  const watcher = jest.fn();
31
36
 
32
37
  const { container, findByText } = render(
33
- <TestFormWrapper watcher={watcher} type="any">
38
+ <TestFormWrapper watcher={watcher} context={{ type: "any" }}>
34
39
  <ConstantSelector />
35
40
  </TestFormWrapper>,
36
41
  renderOpts