@truedat/qx 7.13.7 → 7.13.9

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 (74) hide show
  1. package/package.json +3 -3
  2. package/src/components/QxRoutes.js +8 -6
  3. package/src/components/common/ResourceSelector.js +25 -6
  4. package/src/components/common/TypeSelector.js +14 -9
  5. package/src/components/common/__tests__/__snapshots__/ResourceSelector.spec.js.snap +269 -241
  6. package/src/components/common/__tests__/__snapshots__/TypeSelector.spec.js.snap +198 -190
  7. package/src/components/common/expressions/Clauses.js +19 -11
  8. package/src/components/common/expressions/Condition.js +32 -31
  9. package/src/components/common/expressions/FieldSelector.js +30 -16
  10. package/src/components/common/expressions/FunctionSelector.js +38 -23
  11. package/src/components/common/expressions/ShapeSelector.js +6 -5
  12. package/src/components/common/expressions/__tests__/ShapeSelector.spec.js +1 -1
  13. package/src/components/common/expressions/__tests__/__snapshots__/Clauses.spec.js.snap +36 -12
  14. package/src/components/common/expressions/__tests__/__snapshots__/Condition.spec.js.snap +87 -75
  15. package/src/components/common/expressions/__tests__/__snapshots__/ConstantSelector.spec.js.snap +99 -97
  16. package/src/components/common/expressions/__tests__/__snapshots__/Expression.spec.js.snap +236 -216
  17. package/src/components/common/expressions/__tests__/__snapshots__/FunctionArgs.spec.js.snap +97 -89
  18. package/src/components/common/expressions/__tests__/__snapshots__/FunctionSelector.spec.js.snap +373 -345
  19. package/src/components/common/expressions/constantInputs/AnySelector.js +2 -1
  20. package/src/components/common/expressions/constantInputs/BooleanSelector.js +20 -15
  21. package/src/components/common/expressions/constantInputs/DefaultSelector.js +0 -1
  22. package/src/components/common/expressions/constantInputs/__tests__/__snapshots__/AnySelector.spec.js.snap +189 -182
  23. package/src/components/common/expressions/constantInputs/__tests__/__snapshots__/BooleanSelector.spec.js.snap +74 -66
  24. package/src/components/common/expressions/constantInputs/__tests__/__snapshots__/DefaultSelector.spec.js.snap +2 -4
  25. package/src/components/common/resourceSelectors/DataStructureSelector.js +5 -4
  26. package/src/components/common/resourceSelectors/DataViewSelector.js +4 -3
  27. package/src/components/common/resourceSelectors/ReferenceDatasetSelector.js +4 -3
  28. package/src/components/common/resourceSelectors/__tests__/__snapshots__/DataStructureSelector.spec.js.snap +65 -61
  29. package/src/components/common/resourceSelectors/__tests__/__snapshots__/DataViewSelector.spec.js.snap +38 -34
  30. package/src/components/common/resourceSelectors/__tests__/__snapshots__/ReferenceDatasetSelector.spec.js.snap +38 -34
  31. package/src/components/dataViews/BreadCrumb.js +20 -0
  32. package/src/components/dataViews/DataViewEditor.js +169 -178
  33. package/src/components/dataViews/DataViews.js +113 -135
  34. package/src/components/dataViews/__tests__/AdvancedDataViewEditor.spec.js +260 -0
  35. package/src/components/dataViews/__tests__/DataViewEditor.spec.js +173 -239
  36. package/src/components/dataViews/__tests__/DataViewSelect.spec.js +1 -1
  37. package/src/components/dataViews/__tests__/DataViews.spec.js +124 -51
  38. package/src/components/dataViews/__tests__/Queryable.spec.js +1 -1
  39. package/src/components/dataViews/__tests__/Queryables.spec.js +1 -1
  40. package/src/components/dataViews/__tests__/SimpleDataViewEditor.spec.js +164 -0
  41. package/src/components/dataViews/__tests__/__snapshots__/{DataViewEditor.spec.js.snap → AdvancedDataViewEditor.spec.js.snap} +230 -200
  42. package/src/components/dataViews/__tests__/__snapshots__/DataViews.spec.js.snap +141 -29
  43. package/src/components/dataViews/__tests__/__snapshots__/Queryable.spec.js.snap +184 -141
  44. package/src/components/dataViews/__tests__/__snapshots__/Queryables.spec.js.snap +126 -91
  45. package/src/components/dataViews/actions/CancelButton.js +33 -0
  46. package/src/components/dataViews/actions/DeleteButton.js +33 -0
  47. package/src/components/dataViews/advancedForm/AdvancedDataViewEditor.js +159 -0
  48. package/src/components/dataViews/{DataViewSelect.js → advancedForm/DataViewSelect.js} +2 -2
  49. package/src/components/dataViews/{Queryable.js → advancedForm/Queryable.js} +2 -2
  50. package/src/components/dataViews/queryableFunctions.js +7 -0
  51. package/src/components/dataViews/queryableProperties/GroupBy.js +23 -27
  52. package/src/components/dataViews/queryableProperties/Join.js +0 -3
  53. package/src/components/dataViews/queryableProperties/__tests__/__snapshots__/From.spec.js.snap +30 -26
  54. package/src/components/dataViews/queryableProperties/__tests__/__snapshots__/GroupBy.spec.js.snap +130 -102
  55. package/src/components/dataViews/queryableProperties/__tests__/__snapshots__/Join.spec.js.snap +42 -31
  56. package/src/components/dataViews/queryableProperties/__tests__/__snapshots__/Select.spec.js.snap +81 -69
  57. package/src/components/dataViews/queryableProperties/__tests__/__snapshots__/SelectField.spec.js.snap +62 -54
  58. package/src/components/dataViews/queryableProperties/__tests__/__snapshots__/Where.spec.js.snap +12 -4
  59. package/src/components/dataViews/simpleForm/AggregationForm.js +179 -0
  60. package/src/components/dataViews/simpleForm/DatasetForm.js +199 -0
  61. package/src/components/dataViews/simpleForm/FormQueryable.js +114 -0
  62. package/src/components/dataViews/simpleForm/InformationForm.js +107 -0
  63. package/src/components/dataViews/simpleForm/SelectionForm.js +50 -0
  64. package/src/components/dataViews/simpleForm/SimpleDataViewEditor.js +265 -0
  65. package/src/components/functions/__tests__/__snapshots__/FunctionEditor.spec.js.snap +663 -631
  66. package/src/components/functions/__tests__/__snapshots__/FunctionParams.spec.js.snap +113 -109
  67. package/src/components/qualityControls/__tests__/__snapshots__/ControlProperties.spec.js.snap +92 -76
  68. package/src/components/qualityControls/__tests__/__snapshots__/EditQualityControl.spec.js.snap +108 -80
  69. package/src/components/qualityControls/__tests__/__snapshots__/NewDraftQualityControl.spec.js.snap +108 -80
  70. package/src/components/qualityControls/__tests__/__snapshots__/QualityControlEditor.spec.js.snap +108 -80
  71. package/src/components/qualityControls/controlProperties/__tests__/__snapshots__/Count.spec.js.snap +40 -36
  72. package/src/components/qualityControls/controlProperties/__tests__/__snapshots__/Ratio.spec.js.snap +92 -76
  73. package/src/hooks/useDataViews.js +7 -0
  74. /package/src/components/dataViews/{Queryables.js → advancedForm/Queryables.js} +0 -0
@@ -98,38 +98,42 @@ exports[`<SelectField /> matches the latest snapshot for group_by 1`] = `
98
98
  class="dropdown icon"
99
99
  />
100
100
  <div
101
- aria-expanded="false"
102
- class="ui fluid search selection dropdown"
103
- role="combobox"
101
+ class="sixteen wide field"
104
102
  >
105
- <input
106
- aria-autocomplete="list"
107
- autocomplete="off"
108
- class="search"
109
- tabindex="0"
110
- type="text"
111
- value=""
112
- />
113
103
  <div
114
- aria-atomic="true"
115
- aria-live="polite"
116
- class="divider text"
117
- role="alert"
118
- >
119
- functions.form.expression.function.placeholder
120
- </div>
121
- <i
122
- aria-hidden="true"
123
- class="dropdown icon"
124
- />
125
- <div
126
- class="menu transition"
127
- role="listbox"
104
+ aria-expanded="false"
105
+ class="ui fluid search selection dropdown"
106
+ role="combobox"
128
107
  >
108
+ <input
109
+ aria-autocomplete="list"
110
+ autocomplete="off"
111
+ class="search"
112
+ tabindex="0"
113
+ type="text"
114
+ value=""
115
+ />
116
+ <div
117
+ aria-atomic="true"
118
+ aria-live="polite"
119
+ class="divider text"
120
+ role="alert"
121
+ >
122
+ functions.form.expression.function.placeholder
123
+ </div>
124
+ <i
125
+ aria-hidden="true"
126
+ class="dropdown icon"
127
+ />
129
128
  <div
130
- class="message"
129
+ class="menu transition"
130
+ role="listbox"
131
131
  >
132
- No results found.
132
+ <div
133
+ class="message"
134
+ >
135
+ No results found.
136
+ </div>
133
137
  </div>
134
138
  </div>
135
139
  </div>
@@ -268,38 +272,42 @@ exports[`<SelectField /> matches the latest snapshot for select 1`] = `
268
272
  class="dropdown icon"
269
273
  />
270
274
  <div
271
- aria-expanded="false"
272
- class="ui fluid search selection dropdown"
273
- role="combobox"
275
+ class="sixteen wide field"
274
276
  >
275
- <input
276
- aria-autocomplete="list"
277
- autocomplete="off"
278
- class="search"
279
- tabindex="0"
280
- type="text"
281
- value=""
282
- />
283
277
  <div
284
- aria-atomic="true"
285
- aria-live="polite"
286
- class="divider text"
287
- role="alert"
288
- >
289
- functions.form.expression.function.placeholder
290
- </div>
291
- <i
292
- aria-hidden="true"
293
- class="dropdown icon"
294
- />
295
- <div
296
- class="menu transition"
297
- role="listbox"
278
+ aria-expanded="false"
279
+ class="ui fluid search selection dropdown"
280
+ role="combobox"
298
281
  >
282
+ <input
283
+ aria-autocomplete="list"
284
+ autocomplete="off"
285
+ class="search"
286
+ tabindex="0"
287
+ type="text"
288
+ value=""
289
+ />
290
+ <div
291
+ aria-atomic="true"
292
+ aria-live="polite"
293
+ class="divider text"
294
+ role="alert"
295
+ >
296
+ functions.form.expression.function.placeholder
297
+ </div>
298
+ <i
299
+ aria-hidden="true"
300
+ class="dropdown icon"
301
+ />
299
302
  <div
300
- class="message"
303
+ class="menu transition"
304
+ role="listbox"
301
305
  >
302
- No results found.
306
+ <div
307
+ class="message"
308
+ >
309
+ No results found.
310
+ </div>
303
311
  </div>
304
312
  </div>
305
313
  </div>
@@ -11,11 +11,19 @@ exports[`<Where /> matches the latest snapshot 1`] = `
11
11
  <div
12
12
  class="ui horizontal divider"
13
13
  >
14
- <button
15
- class="ui mini button"
14
+ <div
15
+ class="ui medium basic buttons"
16
16
  >
17
- expression.clause.action.addGroup
18
- </button>
17
+ <button
18
+ class="ui button"
19
+ >
20
+ <i
21
+ aria-hidden="true"
22
+ class="plus icon"
23
+ />
24
+ expression.clause.action.addGroup
25
+ </button>
26
+ </div>
19
27
  </div>
20
28
  </div>
21
29
  </div>
@@ -0,0 +1,179 @@
1
+ import _ from "lodash/fp";
2
+ import { useEffect, useState } from "react";
3
+ import { Accordion, Button, Divider, Icon } from "semantic-ui-react";
4
+ import { useFormContext } from "react-hook-form";
5
+ import { hasErrors } from "../queryableFunctions";
6
+ import { useIntl } from "react-intl";
7
+ import FormQueryable from "./FormQueryable";
8
+
9
+ const formData = (groupByRange, havingRange) => {
10
+ const groupQueryables = _.map((idx) => `queryables[${idx}]`)(groupByRange);
11
+ const groupData = { group_by: { fieldNames: groupQueryables } };
12
+
13
+ const havingQueryables = _.map((idx) => `queryables[${idx}]`)(havingRange);
14
+ const havingData = { having: { fieldNames: havingQueryables } };
15
+
16
+ return { ...groupData, ...havingData };
17
+ };
18
+
19
+ const AggregationForm = ({
20
+ insert,
21
+ remove,
22
+ setStepInformation,
23
+ stepInformation,
24
+ }) => {
25
+ const { formState, watch } = useFormContext();
26
+ const { formatMessage } = useIntl();
27
+ const [activeIndex, setActiveIndex] = useState(0);
28
+ const queryables = watch("queryables");
29
+
30
+ const groupByIndex = _.flow(
31
+ _.findIndex((q) => q.type == "group_by"),
32
+ (index) => (index === -1 ? _.size(queryables) : index)
33
+ )(queryables);
34
+
35
+ const aggregations = _.slice(groupByIndex, _.size(queryables))(queryables);
36
+ const group_bys = _.filter((f) => f?.type == "group_by")(aggregations);
37
+ const havings = _.filter((f) => f?.type == "where")(aggregations);
38
+ const havingsIndex = groupByIndex + _.size(group_bys);
39
+ const groupByRange = _.range(groupByIndex, groupByIndex + _.size(group_bys));
40
+ const havingRange = _.range(havingsIndex, havingsIndex + _.size(havings));
41
+ const data = formData(groupByRange, havingRange);
42
+ const fieldNames = [
43
+ ...data?.group_by?.fieldNames,
44
+ ...data?.having?.fieldNames,
45
+ ];
46
+
47
+ useEffect(() => {
48
+ if (!_.isEqual(stepInformation?.fieldNames, fieldNames))
49
+ setStepInformation(2, { ...stepInformation, fieldNames });
50
+ }, [fieldNames, stepInformation, setStepInformation]);
51
+
52
+ const errors = formState?.errors;
53
+
54
+ const groupByHasError = hasErrors(data?.group_by?.fieldNames, errors);
55
+ const havingHasError = hasErrors(data?.having?.fieldNames, errors);
56
+
57
+ const newClause = () => [
58
+ { expressions: [{ shape: "function", value: { isCondition: true } }] },
59
+ ];
60
+ const defaultPropertiesForType = {
61
+ where: { clauses: newClause() },
62
+ join: { clauses: newClause() },
63
+ };
64
+ const newQueryable = (type, id) => ({
65
+ type,
66
+ id,
67
+ properties: _.propOr({}, type)(defaultPropertiesForType),
68
+ });
69
+ const onQueryableAdd = (type) => {
70
+ const nextId =
71
+ type === "group_by"
72
+ ? groupByIndex + _.size(group_bys)
73
+ : groupByIndex + _.size(aggregations);
74
+ const qId = _.maxBy("id")(queryables)?.id;
75
+ insert(nextId, newQueryable(type, qId + 1));
76
+ };
77
+ const onTitleClick = (e, { index }) => {
78
+ setActiveIndex((x) => (x == index ? null : index));
79
+ };
80
+
81
+ return (
82
+ <>
83
+ <Accordion styled fluid>
84
+ <>
85
+ <Accordion.Title
86
+ active={activeIndex === 0}
87
+ index={0}
88
+ onClick={onTitleClick}
89
+ >
90
+ <Icon name="dropdown" />
91
+ {formatMessage({ id: "dataViews.form.queryable.group_by" })}
92
+ {groupByHasError && (
93
+ <Icon
94
+ name="exclamation circle"
95
+ color="red"
96
+ style={{ marginLeft: 6 }}
97
+ />
98
+ )}
99
+ </Accordion.Title>
100
+ <Accordion.Content active={activeIndex === 0}>
101
+ {group_bys.map((queryable, idx) => (
102
+ <FormQueryable
103
+ key={groupByIndex + idx}
104
+ index={groupByIndex + idx}
105
+ prefix={`queryables[${groupByIndex + idx}]`}
106
+ type={queryable?.type}
107
+ onDelete={() => {
108
+ remove(groupByIndex + idx);
109
+ }}
110
+ />
111
+ ))}
112
+ <Divider hidden />
113
+ {_.size(group_bys) == 0 && (
114
+ <Button.Group basic size="medium">
115
+ <Button
116
+ icon="plus"
117
+ content={formatMessage({
118
+ id: "queryables.form.action.add_groupby",
119
+ })}
120
+ onClick={() => {
121
+ onQueryableAdd("group_by");
122
+ }}
123
+ />
124
+ </Button.Group>
125
+ )}
126
+ </Accordion.Content>
127
+ <Accordion.Title
128
+ active={activeIndex === 1}
129
+ index={1}
130
+ onClick={onTitleClick}
131
+ >
132
+ <Icon name="dropdown" />
133
+ {formatMessage({ id: "dataViews.form.queryable.having" })}
134
+ {havingHasError && (
135
+ <Icon
136
+ name="exclamation circle"
137
+ color="red"
138
+ style={{ marginLeft: 6 }}
139
+ />
140
+ )}
141
+ </Accordion.Title>
142
+ <Accordion.Content active={activeIndex === 1}>
143
+ <>
144
+ {havings.map((queryable, idx) => (
145
+ <FormQueryable
146
+ key={idx + havingsIndex}
147
+ index={idx + havingsIndex}
148
+ prefix={`queryables[${idx + havingsIndex}]`}
149
+ type={queryable?.type}
150
+ onDelete={() => {
151
+ remove(idx + havingsIndex);
152
+ }}
153
+ />
154
+ ))}
155
+ <Divider hidden />
156
+ {_.size(havings) == 0 && (
157
+ <Button.Group basic size="medium">
158
+ <Button
159
+ content={formatMessage({
160
+ id: "queryables.form.action.add_having",
161
+ })}
162
+ disabled={_.size(group_bys) == 0}
163
+ icon="plus"
164
+ onClick={(e) => {
165
+ onQueryableAdd("where");
166
+ }}
167
+ />
168
+ </Button.Group>
169
+ )}
170
+ </>
171
+ </Accordion.Content>
172
+ </>
173
+ </Accordion>
174
+ <Divider hidden />
175
+ </>
176
+ );
177
+ };
178
+
179
+ export default AggregationForm;
@@ -0,0 +1,199 @@
1
+ import _ from "lodash/fp";
2
+ import { useEffect, useState } from "react";
3
+ import { Accordion, Button, Divider, Icon } from "semantic-ui-react";
4
+ import { useFormContext } from "react-hook-form";
5
+ import { hasErrors } from "../queryableFunctions";
6
+ import { useIntl } from "react-intl";
7
+ import FormQueryable from "./FormQueryable";
8
+
9
+ const formData = (joinRange, whereRange) => {
10
+ const fromData = { from: { fieldNames: ["queryables[0]"] } };
11
+ const joinQueryables = _.map((idx) => `queryables[${idx}]`)(joinRange);
12
+ const joinData = { join: { fieldNames: joinQueryables } };
13
+
14
+ const whereQueryables = _.map((idx) => `queryables[${idx}]`)(whereRange);
15
+ const whereData = { where: { fieldNames: whereQueryables } };
16
+
17
+ return { ...fromData, ...joinData, ...whereData };
18
+ };
19
+
20
+ const DatasetForm = ({
21
+ insert,
22
+ remove,
23
+ setStepInformation,
24
+ stepInformation,
25
+ }) => {
26
+ const { formState, watch } = useFormContext();
27
+ const { formatMessage } = useIntl();
28
+ const [activeIndex, setActiveIndex] = useState(0);
29
+ const queryables = watch("queryables");
30
+
31
+ const dataset = _.takeWhile((q) => q.type !== "group_by")(queryables);
32
+ const joinIndex = _.findIndex((f) => f?.type == "join")(dataset);
33
+ const whereIndex = _.findIndex((f) => f?.type == "where")(dataset);
34
+ const joins = _.filter((f) => f?.type == "join")(dataset);
35
+ const wheres = _.filter((f) => f?.type == "where")(dataset);
36
+ const joinRange =
37
+ joinIndex === -1 ? [] : _.range(joinIndex, joinIndex + _.size(joins));
38
+ const whereRange =
39
+ whereIndex === -1 ? [] : _.range(whereIndex, whereIndex + _.size(wheres));
40
+ const data = formData(joinRange, whereRange);
41
+
42
+ const fieldNames = [
43
+ ...data?.from?.fieldNames,
44
+ ...data?.join?.fieldNames,
45
+ ...data?.where?.fieldNames,
46
+ ];
47
+
48
+ const errors = formState?.errors;
49
+
50
+ const fromHasError = hasErrors(data?.from?.fieldNames, errors);
51
+ const joinHasError = hasErrors(data?.join?.fieldNames, errors);
52
+ const whereHasError = hasErrors(data?.where?.fieldNames, errors);
53
+
54
+ useEffect(() => {
55
+ if (!_.isEqual(stepInformation?.fieldNames, fieldNames))
56
+ setStepInformation(1, { ...stepInformation, fieldNames });
57
+ }, [fieldNames, stepInformation, setStepInformation]);
58
+
59
+ const newClause = () => [
60
+ { expressions: [{ shape: "function", value: { isCondition: true } }] },
61
+ ];
62
+ const defaultPropertiesForType = {
63
+ where: { clauses: newClause() },
64
+ join: { clauses: newClause() },
65
+ };
66
+ const newQueryable = (type, id) => ({
67
+ type,
68
+ id,
69
+ properties: _.propOr({}, type)(defaultPropertiesForType),
70
+ });
71
+ const onQueryableAdd = (type) => {
72
+ const nextId = type === "join" ? _.size(joins) + 1 : _.size(dataset);
73
+ const qId = _.maxBy("id")(queryables)?.id;
74
+ insert(nextId, newQueryable(type, qId + 1));
75
+ };
76
+ const onTitleClick = (e, { index }) => {
77
+ setActiveIndex((x) => (x == index ? null : index));
78
+ };
79
+
80
+ return (
81
+ <>
82
+ <Accordion styled fluid>
83
+ <>
84
+ <Accordion.Title
85
+ active={activeIndex === 0}
86
+ index={0}
87
+ onClick={onTitleClick}
88
+ >
89
+ <Icon name="dropdown" />
90
+ {formatMessage({ id: "dataViews.form.queryable.from" })}
91
+ {fromHasError && (
92
+ <Icon
93
+ name="exclamation circle"
94
+ color="red"
95
+ style={{ marginLeft: 6 }}
96
+ />
97
+ )}
98
+ </Accordion.Title>
99
+ <Accordion.Content active={activeIndex === 0}>
100
+ <FormQueryable
101
+ index={0}
102
+ prefix={`queryables[0]`}
103
+ type={dataset[0]?.type}
104
+ />
105
+ </Accordion.Content>
106
+ <Accordion.Title
107
+ active={activeIndex === 1}
108
+ index={1}
109
+ onClick={onTitleClick}
110
+ icon={"dropdown"}
111
+ >
112
+ <Icon name="dropdown" />
113
+ {formatMessage({ id: "dataViews.form.queryable.join" })}
114
+ {joinHasError && (
115
+ <Icon
116
+ name="exclamation circle"
117
+ color="red"
118
+ style={{ marginLeft: 6 }}
119
+ />
120
+ )}
121
+ </Accordion.Title>
122
+ <Accordion.Content active={activeIndex === 1}>
123
+ {joins.map((queryable, idx) => (
124
+ <FormQueryable
125
+ key={idx}
126
+ index={idx + joinIndex}
127
+ prefix={`queryables[${idx + joinIndex}]`}
128
+ type={queryable?.type}
129
+ onDelete={() => {
130
+ remove(idx + joinIndex);
131
+ }}
132
+ />
133
+ ))}
134
+ <Divider hidden />
135
+ <Button.Group basic size="medium">
136
+ <Button
137
+ icon="plus"
138
+ content={formatMessage({
139
+ id: "queryables.form.action.add_join",
140
+ })}
141
+ onClick={() => {
142
+ onQueryableAdd("join");
143
+ }}
144
+ />
145
+ </Button.Group>
146
+ </Accordion.Content>
147
+ <Accordion.Title
148
+ active={activeIndex === 2}
149
+ index={2}
150
+ onClick={onTitleClick}
151
+ icon={"dropdown"}
152
+ >
153
+ <Icon name="dropdown" />
154
+ {formatMessage({ id: "dataViews.form.queryable.where" })}
155
+ {whereHasError && (
156
+ <Icon
157
+ name="exclamation circle"
158
+ color="red"
159
+ style={{ marginLeft: 6 }}
160
+ />
161
+ )}
162
+ </Accordion.Title>
163
+ <Accordion.Content active={activeIndex === 2}>
164
+ <>
165
+ {wheres.map((queryable, idx) => (
166
+ <FormQueryable
167
+ key={idx}
168
+ index={idx + whereIndex}
169
+ prefix={`queryables[${idx + whereIndex}]`}
170
+ type={queryable?.type}
171
+ onDelete={() => {
172
+ remove(idx + whereIndex);
173
+ }}
174
+ />
175
+ ))}
176
+ <Divider hidden />
177
+ {_.size(wheres) == 0 && (
178
+ <Button.Group basic size="medium">
179
+ <Button
180
+ content={formatMessage({
181
+ id: "queryables.form.action.add_where",
182
+ })}
183
+ icon="plus"
184
+ onClick={(e) => {
185
+ onQueryableAdd("where");
186
+ }}
187
+ />
188
+ </Button.Group>
189
+ )}
190
+ </>
191
+ </Accordion.Content>
192
+ </>
193
+ </Accordion>
194
+ <Divider hidden />
195
+ </>
196
+ );
197
+ };
198
+
199
+ export default DatasetForm;
@@ -0,0 +1,114 @@
1
+ import _ from "lodash/fp";
2
+ import { use } from "react";
3
+ import { useIntl } from "react-intl";
4
+ import { Button, Grid, Form, Segment } from "semantic-ui-react";
5
+ import { useFormContext, Controller } from "react-hook-form";
6
+ import QxContext from "@truedat/qx/components/QxContext";
7
+ import { From, GroupBy, Join, Where } from "../queryableProperties";
8
+ import { reduceQueryableFields } from "../queryableFunctions";
9
+
10
+ const FormQueryable = ({ index = 0, prefix, type = "from", onDelete }) => {
11
+ const { control, watch } = useFormContext();
12
+ const context = use(QxContext);
13
+ const { formatMessage } = useIntl();
14
+ const queryables = watch("queryables");
15
+ const fieldsOffset = _.prop(`[${index}].type`)(queryables) === "join" ? 1 : 0;
16
+ const fields = _.flow(
17
+ _.slice(0, index + fieldsOffset),
18
+ reduceQueryableFields(formatMessage)
19
+ )(queryables);
20
+
21
+ const queryable = watch(prefix);
22
+
23
+ const queryableResourceKey = (queryable) => {
24
+ switch (queryable.type) {
25
+ case "join":
26
+ case "from":
27
+ const { alias, properties } = queryable;
28
+ const { id, type } = properties.resource || {};
29
+ return [id, type, alias].join(":");
30
+ default:
31
+ return null;
32
+ }
33
+ };
34
+
35
+ const validateAlias = _.flow(
36
+ _.remove((qry) => qry.id == queryable.id),
37
+ _.map("alias"),
38
+ _.reject((alias) => _.isNil(alias) || _.isEmpty(alias)),
39
+ _.includes(queryable.alias)
40
+ )(queryables)
41
+ ? formatMessage({
42
+ id: "dataViews.form.queryable.alias.duplicated",
43
+ })
44
+ : null;
45
+
46
+ const validateResource = _.flow(
47
+ _.remove((qry) => qry.id == queryable.id),
48
+ _.map(queryableResourceKey),
49
+ _.reject(_.isNil),
50
+ _.includes(queryableResourceKey(queryable))
51
+ )(queryables)
52
+ ? formatMessage({
53
+ id: "dataViews.form.queryable.resource.duplicated",
54
+ })
55
+ : null;
56
+
57
+ return (
58
+ <Grid>
59
+ <Grid.Column>
60
+ <Segment>
61
+ {type !== "from" && onDelete ? (
62
+ <Button
63
+ onClick={onDelete}
64
+ icon="trash alternate outline"
65
+ basic
66
+ color="red"
67
+ aria-label="delete"
68
+ floated="right"
69
+ />
70
+ ) : null}
71
+ <Grid.Row className="vertical-space">
72
+ <Controller
73
+ control={control}
74
+ name={`${prefix}.alias`}
75
+ rules={{
76
+ validate: () => validateAlias || validateResource,
77
+ }}
78
+ render={({
79
+ field: { onBlur, onChange, value },
80
+ fieldState: { error },
81
+ }) => (
82
+ <Form.Input
83
+ label={formatMessage({
84
+ id: "dataViews.form.queryable.alias",
85
+ })}
86
+ autoComplete="off"
87
+ placeholder={formatMessage({
88
+ id: "dataViews.form.queryable.alias",
89
+ })}
90
+ error={error?.message}
91
+ onBlur={onBlur}
92
+ onChange={(_e, { value }) => onChange(value)}
93
+ value={value}
94
+ />
95
+ )}
96
+ />
97
+ </Grid.Row>
98
+ <Grid.Row className="vertical-space">
99
+ <QxContext
100
+ value={{ ...context, field: `${prefix}.properties`, fields }}
101
+ >
102
+ {type === "from" && <From />}
103
+ {type === "join" && <Join />}
104
+ {type === "group_by" && <GroupBy />}
105
+ {type === "where" && <Where />}
106
+ </QxContext>
107
+ </Grid.Row>
108
+ </Segment>
109
+ </Grid.Column>
110
+ </Grid>
111
+ );
112
+ };
113
+
114
+ export default FormQueryable;