@truedat/qx 5.12.2 → 5.12.7

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 (52) hide show
  1. package/package.json +8 -3
  2. package/src/api.js +3 -1
  3. package/src/components/QxContext.js +3 -0
  4. package/src/components/QxRoutes.js +6 -1
  5. package/src/components/common/DescriptionInput.js +47 -0
  6. package/src/components/common/TestFormWrapper.js +33 -0
  7. package/src/components/common/TypeSelector.js +33 -0
  8. package/src/components/common/index.js +4 -0
  9. package/src/components/functions/FunctionEditor.js +200 -0
  10. package/src/components/functions/FunctionParams.js +122 -0
  11. package/src/components/functions/Functions.js +152 -0
  12. package/src/components/functions/__tests__/FunctionEditor.spec.js +195 -0
  13. package/src/components/functions/__tests__/FunctionParams.spec.js +108 -0
  14. package/src/components/functions/__tests__/Functions.spec.js +95 -0
  15. package/src/components/functions/__tests__/__snapshots__/FunctionEditor.spec.js.snap +1563 -0
  16. package/src/components/functions/__tests__/__snapshots__/FunctionParams.spec.js.snap +228 -0
  17. package/src/components/functions/__tests__/__snapshots__/Functions.spec.js.snap +86 -0
  18. package/src/components/functions/__tests__/useWatchParams.spec.js +23 -0
  19. package/src/components/functions/expressions/ConstantSelector.js +26 -0
  20. package/src/components/functions/expressions/Expression.js +40 -0
  21. package/src/components/functions/expressions/FieldSelector.js +56 -0
  22. package/src/components/functions/expressions/FunctionArgs.js +49 -0
  23. package/src/components/functions/expressions/FunctionSelector.js +95 -0
  24. package/src/components/functions/expressions/ParamSelector.js +51 -0
  25. package/src/components/functions/expressions/ShapeSelector.js +74 -0
  26. package/src/components/functions/expressions/__tests__/ConstantSelector.spec.js +64 -0
  27. package/src/components/functions/expressions/__tests__/Expression.spec.js +131 -0
  28. package/src/components/functions/expressions/__tests__/FunctionArgs.spec.js +86 -0
  29. package/src/components/functions/expressions/__tests__/FunctionSelector.spec.js +69 -0
  30. package/src/components/functions/expressions/__tests__/ParamSelector.spec.js +115 -0
  31. package/src/components/functions/expressions/__tests__/ShapeSelector.spec.js +107 -0
  32. package/src/components/functions/expressions/__tests__/__snapshots__/ConstantSelector.spec.js.snap +149 -0
  33. package/src/components/functions/expressions/__tests__/__snapshots__/Expression.spec.js.snap +904 -0
  34. package/src/components/functions/expressions/__tests__/__snapshots__/FunctionArgs.spec.js.snap +392 -0
  35. package/src/components/functions/expressions/__tests__/__snapshots__/FunctionSelector.spec.js.snap +377 -0
  36. package/src/components/functions/expressions/__tests__/__snapshots__/ParamSelector.spec.js.snap +95 -0
  37. package/src/components/functions/expressions/__tests__/__snapshots__/ShapeSelector.spec.js.snap +290 -0
  38. package/src/components/functions/expressions/constantInputs/AnySelector.js +29 -0
  39. package/src/components/functions/expressions/constantInputs/BooleanSelector.js +37 -0
  40. package/src/components/functions/expressions/constantInputs/DefaultSelector.js +34 -0
  41. package/src/components/functions/expressions/constantInputs/__tests__/AnySelector.spec.js +63 -0
  42. package/src/components/functions/expressions/constantInputs/__tests__/BooleanSelector.spec.js +51 -0
  43. package/src/components/functions/expressions/constantInputs/__tests__/DefaultSelector.spec.js +56 -0
  44. package/src/components/functions/expressions/constantInputs/__tests__/__snapshots__/AnySelector.spec.js.snap +236 -0
  45. package/src/components/functions/expressions/constantInputs/__tests__/__snapshots__/BooleanSelector.spec.js.snap +101 -0
  46. package/src/components/functions/expressions/constantInputs/__tests__/__snapshots__/DefaultSelector.spec.js.snap +39 -0
  47. package/src/components/functions/expressions/constantInputs/index.js +5 -0
  48. package/src/components/functions/useWatchParams.js +13 -0
  49. package/src/hooks/__tests__/useFunctions.spec.js +101 -0
  50. package/src/hooks/useFunctions.js +33 -0
  51. package/src/styles/Expression.less +102 -0
  52. package/src/types.js +38 -0
@@ -0,0 +1,228 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`<FunctionParams /> matches the latest snapshot with param 1`] = `
4
+ <div>
5
+ <div
6
+ class="ui list"
7
+ role="list"
8
+ >
9
+ <div
10
+ class="item"
11
+ role="listitem"
12
+ >
13
+ <div>
14
+ <div
15
+ class="ui grid"
16
+ >
17
+ <div
18
+ class="row"
19
+ >
20
+ <div
21
+ class="column"
22
+ />
23
+ <div
24
+ class="fifteen wide column"
25
+ >
26
+ <div
27
+ class="row"
28
+ >
29
+ <div
30
+ class="inline fields no-margin"
31
+ >
32
+ <div
33
+ class="field"
34
+ >
35
+ <div
36
+ class="ui action input"
37
+ >
38
+ <input
39
+ autocomplete="off"
40
+ placeholder="name"
41
+ type="text"
42
+ value="param1"
43
+ />
44
+ <div
45
+ aria-expanded="false"
46
+ class="ui basic button dropdown"
47
+ name="type"
48
+ role="listbox"
49
+ tabindex="0"
50
+ >
51
+ <div
52
+ aria-atomic="true"
53
+ aria-live="polite"
54
+ class="divider text"
55
+ role="alert"
56
+ >
57
+ boolean
58
+ </div>
59
+ <i
60
+ aria-hidden="true"
61
+ class="dropdown icon"
62
+ />
63
+ <div
64
+ class="menu transition"
65
+ >
66
+ <div
67
+ aria-checked="true"
68
+ aria-selected="true"
69
+ class="active selected item"
70
+ role="option"
71
+ style="pointer-events: all;"
72
+ >
73
+ <i
74
+ aria-hidden="true"
75
+ class="adjust icon"
76
+ />
77
+ <span
78
+ class="text"
79
+ >
80
+ boolean
81
+ </span>
82
+ </div>
83
+ <div
84
+ aria-checked="false"
85
+ aria-selected="false"
86
+ class="item"
87
+ role="option"
88
+ style="pointer-events: all;"
89
+ >
90
+ <i
91
+ aria-hidden="true"
92
+ class="font icon"
93
+ />
94
+ <span
95
+ class="text"
96
+ >
97
+ string
98
+ </span>
99
+ </div>
100
+ <div
101
+ aria-checked="false"
102
+ aria-selected="false"
103
+ class="item"
104
+ role="option"
105
+ style="pointer-events: all;"
106
+ >
107
+ <i
108
+ aria-hidden="true"
109
+ class="hashtag icon"
110
+ />
111
+ <span
112
+ class="text"
113
+ >
114
+ numeric
115
+ </span>
116
+ </div>
117
+ <div
118
+ aria-checked="false"
119
+ aria-selected="false"
120
+ class="item"
121
+ role="option"
122
+ style="pointer-events: all;"
123
+ >
124
+ <i
125
+ aria-hidden="true"
126
+ class="calendar alternate outline icon"
127
+ />
128
+ <span
129
+ class="text"
130
+ >
131
+ date
132
+ </span>
133
+ </div>
134
+ <div
135
+ aria-checked="false"
136
+ aria-selected="false"
137
+ class="item"
138
+ role="option"
139
+ style="pointer-events: all;"
140
+ >
141
+ <i
142
+ aria-hidden="true"
143
+ class="clock outline icon"
144
+ />
145
+ <span
146
+ class="text"
147
+ >
148
+ timestamp
149
+ </span>
150
+ </div>
151
+ <div
152
+ aria-checked="false"
153
+ aria-selected="false"
154
+ class="item"
155
+ role="option"
156
+ style="pointer-events: all;"
157
+ >
158
+ <i
159
+ aria-hidden="true"
160
+ class="question circle outline icon"
161
+ />
162
+ <span
163
+ class="text"
164
+ >
165
+ any
166
+ </span>
167
+ </div>
168
+ </div>
169
+ </div>
170
+ </div>
171
+ </div>
172
+ </div>
173
+ </div>
174
+ <div
175
+ class="row"
176
+ >
177
+ <div
178
+ class="ui horizontal link list"
179
+ role="list"
180
+ >
181
+ <a
182
+ class="item"
183
+ role="listitem"
184
+ style="font-style: italic; font-size: 13px; padding: 9.5px 14px;"
185
+ tabindex="0"
186
+ >
187
+ add_description
188
+ </a>
189
+ </div>
190
+ </div>
191
+ </div>
192
+ </div>
193
+ </div>
194
+ </div>
195
+ </div>
196
+ <div
197
+ class="item"
198
+ role="listitem"
199
+ >
200
+ <button
201
+ class="ui button"
202
+ >
203
+ add_param
204
+ </button>
205
+ </div>
206
+ </div>
207
+ </div>
208
+ `;
209
+
210
+ exports[`<FunctionParams /> matches the latest snapshot without param 1`] = `
211
+ <div>
212
+ <div
213
+ class="ui list"
214
+ role="list"
215
+ >
216
+ <div
217
+ class="item"
218
+ role="listitem"
219
+ >
220
+ <button
221
+ class="ui button"
222
+ >
223
+ add_param
224
+ </button>
225
+ </div>
226
+ </div>
227
+ </div>
228
+ `;
@@ -0,0 +1,86 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`<Functions /> matches the latest snapshot 1`] = `
4
+ <div>
5
+ <div
6
+ class="ui segment"
7
+ >
8
+ <h2
9
+ class="ui header"
10
+ >
11
+ <i
12
+ aria-hidden="true"
13
+ class="archive circular icon"
14
+ />
15
+ <div
16
+ class="content"
17
+ >
18
+ functions.header
19
+ <div
20
+ class="sub header"
21
+ >
22
+ functions.subheader
23
+ </div>
24
+ </div>
25
+ </h2>
26
+ <div
27
+ class="ui grid"
28
+ >
29
+ <div
30
+ class="four wide column"
31
+ >
32
+ <button
33
+ class="ui fluid button"
34
+ >
35
+ functions.action.new
36
+ </button>
37
+ <div
38
+ class="ui divided selection list"
39
+ role="list"
40
+ >
41
+ <div
42
+ class="item"
43
+ role="listitem"
44
+ >
45
+ <div
46
+ class="content"
47
+ >
48
+ <div
49
+ class="header"
50
+ >
51
+ <div
52
+ class="ui teal tiny circular label"
53
+ width="100px"
54
+ >
55
+ b
56
+ </div>
57
+
58
+ user_false
59
+ /
60
+ 0
61
+ </div>
62
+ </div>
63
+ </div>
64
+ </div>
65
+ </div>
66
+ <div
67
+ class="eleven wide column"
68
+ >
69
+ <h2
70
+ class="ui icon center aligned header"
71
+ >
72
+ <i
73
+ aria-hidden="true"
74
+ class="hand pointer outline icon"
75
+ />
76
+ <div
77
+ class="sub header"
78
+ >
79
+ functions.no_selection
80
+ </div>
81
+ </h2>
82
+ </div>
83
+ </div>
84
+ </div>
85
+ </div>
86
+ `;
@@ -0,0 +1,23 @@
1
+ import React from "react";
2
+ import { render } from "@truedat/test/render";
3
+ import TestFormWrapper from "@truedat/qx/components/common/TestFormWrapper";
4
+ import useWatchParams from "../useWatchParams";
5
+
6
+ describe("useWatchParams", () => {
7
+ it("matches the latest snapshot", () => {
8
+ const params = [{ name: "param", type: "boolean", id: 1 }];
9
+ const defaultValues = {
10
+ params,
11
+ };
12
+ const ParamHook = () => {
13
+ const watchedParams = useWatchParams();
14
+ expect(watchedParams).toEqual(params);
15
+ return null;
16
+ };
17
+ render(
18
+ <TestFormWrapper defaultValues={defaultValues}>
19
+ <ParamHook />
20
+ </TestFormWrapper>
21
+ );
22
+ });
23
+ });
@@ -0,0 +1,26 @@
1
+ import React, { useContext, useEffect } from "react";
2
+ import { useFormContext } from "react-hook-form";
3
+ import QxContext from "@truedat/qx/components/QxContext";
4
+ import {
5
+ AnySelector,
6
+ BooleanSelector,
7
+ DefaultSelector,
8
+ } from "./constantInputs";
9
+
10
+ export default function ConstantSelector() {
11
+ const { type, field } = useContext(QxContext);
12
+ const { setValue } = useFormContext();
13
+
14
+ useEffect(() => {
15
+ if (type && type != "any") setValue(`${field}.value.type`, type);
16
+ }, [type]);
17
+
18
+ switch (type) {
19
+ case "any":
20
+ return <AnySelector />;
21
+ case "boolean":
22
+ return <BooleanSelector />;
23
+ default:
24
+ return <DefaultSelector />;
25
+ }
26
+ }
@@ -0,0 +1,40 @@
1
+ import _ from "lodash/fp";
2
+ import React, { useContext, useEffect } from "react";
3
+ import PropTypes from "prop-types";
4
+ import { useFormContext } from "react-hook-form";
5
+ import QxContext from "@truedat/qx/components/QxContext";
6
+ import ShapeSelector from "./ShapeSelector";
7
+ import FunctionSelector from "./FunctionSelector";
8
+ import ParamSelector from "./ParamSelector";
9
+ import FieldSelector from "./FieldSelector";
10
+ import ConstantSelector from "./ConstantSelector";
11
+
12
+ export default function Expression({ defaultShape = "function" }) {
13
+ const { field } = useContext(QxContext);
14
+ const { watch, setValue } = useFormContext();
15
+ const shape = watch(`${field}.shape`);
16
+
17
+ useEffect(() => {
18
+ if (!shape) {
19
+ setValue(`${field}.shape`, defaultShape, { shouldValidate: true });
20
+ }
21
+ }, [shape]);
22
+
23
+ const valueComponentForShape = {
24
+ function: <FunctionSelector />,
25
+ constant: <ConstantSelector />,
26
+ param: <ParamSelector />,
27
+ field: <FieldSelector />,
28
+ };
29
+
30
+ return (
31
+ <div className="expression-container">
32
+ <ShapeSelector />
33
+ {shape ? valueComponentForShape[shape] : null}
34
+ </div>
35
+ );
36
+ }
37
+
38
+ Expression.propTypes = {
39
+ defaultShape: PropTypes.string,
40
+ };
@@ -0,0 +1,56 @@
1
+ import _ from "lodash/fp";
2
+ import React, { useContext } 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
+
8
+ export const fieldsToOptions = (fields) =>
9
+ _.map((field) => ({
10
+ key: JSON.stringify(field),
11
+ value: field,
12
+ text: field.name,
13
+ content: (
14
+ <>
15
+ {field.dataset ? (
16
+ <Label
17
+ color={field.dataset?.color}
18
+ content={field.dataset?.alias || field.dataset?.name}
19
+ />
20
+ ) : null}
21
+ {field.name}
22
+ </>
23
+ ),
24
+ color: field.dataset?.color,
25
+ }))(fields);
26
+
27
+ export default function FieldSelector() {
28
+ const { field, type, fields } = useContext(QxContext);
29
+ const { control } = useFormContext();
30
+ const fieldOptions = _.flow(
31
+ _.filter((field) => type === "any" || field.type == type),
32
+ fieldsToOptions
33
+ )(fields);
34
+
35
+ return _.isEmpty(fieldOptions) ? null : (
36
+ <Controller
37
+ control={control}
38
+ name={`${field}.value`}
39
+ rules={{ required: true }}
40
+ render={({
41
+ field: { onBlur, onChange, value },
42
+ fieldState: { error },
43
+ }) => (
44
+ <Dropdown
45
+ selection
46
+ fluid
47
+ onBlur={onBlur}
48
+ error={!!error}
49
+ options={fieldOptions}
50
+ value={value}
51
+ onChange={(_e, { value }) => onChange(value)}
52
+ />
53
+ )}
54
+ />
55
+ );
56
+ }
@@ -0,0 +1,49 @@
1
+ import _ from "lodash/fp";
2
+ import React, { useContext } from "react";
3
+ import { useFormContext } from "react-hook-form";
4
+ import { Label, Icon } from "semantic-ui-react";
5
+ import { typeToColor, typeToIcon } from "@truedat/qx/types";
6
+ import QxContext from "@truedat/qx/components/QxContext";
7
+
8
+ const Expression = React.lazy(() => import("./Expression"));
9
+
10
+ export default function FunctionArgs() {
11
+ const context = useContext(QxContext);
12
+ const { functions, field } = context;
13
+ const { watch } = useFormContext();
14
+
15
+ const { type, name } = watch(`${field}.value`) || {};
16
+ const func = _.find({ name, type })(functions);
17
+
18
+ return func ? (
19
+ <ul className="function-tree">
20
+ {func.params.map(({ name, type, description }, i) => (
21
+ <li key={i}>
22
+ <div className="flex-column">
23
+ <div className="param-label-header">
24
+ <Label size="large" image>
25
+ <Icon
26
+ fitted
27
+ color={typeToColor(type)}
28
+ name={typeToIcon(type)}
29
+ />
30
+ <Label.Detail>{name}</Label.Detail>
31
+ </Label>
32
+ <div className="param-label-description">{description}</div>
33
+ </div>
34
+
35
+ <QxContext.Provider
36
+ value={{
37
+ ...context,
38
+ type,
39
+ field: `${field}.value.args.${name}`,
40
+ }}
41
+ >
42
+ <Expression />
43
+ </QxContext.Provider>
44
+ </div>
45
+ </li>
46
+ ))}
47
+ </ul>
48
+ ) : null;
49
+ }
@@ -0,0 +1,95 @@
1
+ import _ from "lodash/fp";
2
+ import React, { useState, useContext } from "react";
3
+ import { useIntl } from "react-intl";
4
+ import { Controller, useFormContext } from "react-hook-form";
5
+ import { Dropdown, Accordion, Icon, Label, Header } from "semantic-ui-react";
6
+ import { typeToColor, typeToIcon } from "@truedat/qx/types";
7
+ import QxContext from "@truedat/qx/components/QxContext";
8
+ import FunctionArgs from "./FunctionArgs";
9
+
10
+ export default function FunctionSelector() {
11
+ const { formatMessage } = useIntl();
12
+ const { functions, field, type } = useContext(QxContext);
13
+ const { control, watch } = useFormContext();
14
+
15
+ const [collapsed, setCollapsed] = useState(false);
16
+
17
+ const fnToKey = ({ name, type }) => `${name}:${type}`;
18
+ const keyToFn = (key) => {
19
+ const [name, type] = key.split(":");
20
+ return { name, type };
21
+ };
22
+ const functionName = watch("name");
23
+ const functionType = watch("type");
24
+
25
+ const funcs = type === "any" ? functions : _.filter({ type })(functions);
26
+
27
+ const funcOpts = _.flow(
28
+ _.reject({ name: functionName, type: functionType }),
29
+ _.map(({ name, type, description, params }) => ({
30
+ key: fnToKey({ name, type }),
31
+ value: fnToKey({ name, type }),
32
+ text: fnToKey({ name, type }),
33
+ content: (
34
+ <>
35
+ <Header>
36
+ <div className="flex-center">
37
+ <div>{name}</div>
38
+ <div className="function-params-label">
39
+ {params.map(({ type: paramType, name: paramName }, index) => (
40
+ <Label key={index} color={typeToColor(paramType)}>
41
+ <Icon name={typeToIcon(paramType)} />
42
+ {paramName}
43
+ </Label>
44
+ ))}
45
+ </div>
46
+ </div>
47
+ </Header>
48
+ <Header.Subheader>{description}</Header.Subheader>
49
+ </>
50
+ ),
51
+ }))
52
+ )(funcs);
53
+
54
+ return (
55
+ <Accordion fluid className="no-margin">
56
+ <Accordion.Title
57
+ className="no-padding"
58
+ active={!collapsed}
59
+ onClick={() => setCollapsed(!collapsed)}
60
+ >
61
+ <div className="flex-center">
62
+ <Icon name="dropdown" />
63
+ <Controller
64
+ control={control}
65
+ name={`${field}.value`}
66
+ rules={{ required: true }}
67
+ render={({
68
+ field: { onBlur, onChange, value },
69
+ fieldState: { error },
70
+ }) => (
71
+ <Dropdown
72
+ deburr
73
+ onBlur={onBlur}
74
+ error={!!error}
75
+ options={funcOpts}
76
+ onChange={(_e, { value }) => onChange(keyToFn(value))}
77
+ placeholder={formatMessage(
78
+ { id: "functions.form.expression.function.placeholder" },
79
+ { type }
80
+ )}
81
+ search
82
+ fluid
83
+ selection
84
+ value={fnToKey(value || {})}
85
+ />
86
+ )}
87
+ />
88
+ </div>
89
+ </Accordion.Title>
90
+ <Accordion.Content className="no-padding" active={!collapsed}>
91
+ <FunctionArgs />
92
+ </Accordion.Content>
93
+ </Accordion>
94
+ );
95
+ }
@@ -0,0 +1,51 @@
1
+ import _ from "lodash/fp";
2
+ import React, { useContext, useEffect } from "react";
3
+ import { Controller, useFormContext } from "react-hook-form";
4
+ import { Dropdown } from "semantic-ui-react";
5
+ import QxContext from "@truedat/qx/components/QxContext";
6
+ import useWatchParams from "../useWatchParams";
7
+
8
+ export default function ParamSelector() {
9
+ const { field, type } = useContext(QxContext);
10
+ const { control, watch, setValue } = useFormContext();
11
+
12
+ const params = useWatchParams();
13
+ const value = watch(`${field}.value`);
14
+
15
+ const options = _.flow(
16
+ _.filter((field) => type === "any" || field.type == type),
17
+ _.map(({ name, id }) => ({
18
+ key: id,
19
+ value: id,
20
+ text: name,
21
+ }))
22
+ )(params);
23
+
24
+ useEffect(() => {
25
+ if (value && !_.find({ value })(options)) {
26
+ setValue(`${field}.value`, null, { shouldValidate: true });
27
+ }
28
+ }, [options]);
29
+
30
+ return (
31
+ <Controller
32
+ control={control}
33
+ name={`${field}.value`}
34
+ rules={{ required: true }}
35
+ render={({
36
+ field: { onBlur, onChange, value },
37
+ fieldState: { error },
38
+ }) => (
39
+ <Dropdown
40
+ selection
41
+ fluid
42
+ onBlur={onBlur}
43
+ error={!!error}
44
+ options={options}
45
+ value={value}
46
+ onChange={(_e, { value }) => onChange(value)}
47
+ />
48
+ )}
49
+ />
50
+ );
51
+ }