@qoretechnologies/reqraft 0.10.39-pr.99.gdbafa34 → 0.10.39
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/.claude/CLAUDE.md +12 -0
- package/dist/components/codeSize/index.d.ts +37 -0
- package/dist/components/codeSize/index.d.ts.map +1 -0
- package/dist/components/codeSize/index.js +87 -0
- package/dist/components/codeSize/index.js.map +1 -0
- package/dist/components/form/engine/CompactRow.d.ts +20 -1
- package/dist/components/form/engine/CompactRow.d.ts.map +1 -1
- package/dist/components/form/engine/CompactRow.js +365 -30
- package/dist/components/form/engine/CompactRow.js.map +1 -1
- package/dist/components/form/engine/CompactToolbar.d.ts.map +1 -1
- package/dist/components/form/engine/CompactToolbar.js +6 -8
- package/dist/components/form/engine/CompactToolbar.js.map +1 -1
- package/dist/components/form/engine/FormEngine.d.ts +24 -45
- package/dist/components/form/engine/FormEngine.d.ts.map +1 -1
- package/dist/components/form/engine/FormEngine.js +211 -84
- package/dist/components/form/engine/FormEngine.js.map +1 -1
- package/dist/components/form/engine/_structuredData/SchemaDataView.d.ts +37 -0
- package/dist/components/form/engine/_structuredData/SchemaDataView.d.ts.map +1 -0
- package/dist/components/form/engine/_structuredData/SchemaDataView.js +335 -0
- package/dist/components/form/engine/_structuredData/SchemaDataView.js.map +1 -0
- package/dist/components/form/engine/compactRowStyles.d.ts +13 -0
- package/dist/components/form/engine/compactRowStyles.d.ts.map +1 -1
- package/dist/components/form/engine/compactRowStyles.js +30 -5
- package/dist/components/form/engine/compactRowStyles.js.map +1 -1
- package/dist/components/form/engine/compactToolbarContext.d.ts +0 -8
- package/dist/components/form/engine/compactToolbarContext.d.ts.map +1 -1
- package/dist/components/form/engine/compactToolbarContext.js.map +1 -1
- package/dist/components/form/engine/readFirst.d.ts +73 -1
- package/dist/components/form/engine/readFirst.d.ts.map +1 -1
- package/dist/components/form/engine/readFirst.js +175 -5
- package/dist/components/form/engine/readFirst.js.map +1 -1
- package/dist/components/form/fields/Field.d.ts.map +1 -1
- package/dist/components/form/fields/Field.js +1 -1
- package/dist/components/form/fields/Field.js.map +1 -1
- package/dist/components/form/fields/allowed-values/AllowedValues.d.ts +12 -2
- package/dist/components/form/fields/allowed-values/AllowedValues.d.ts.map +1 -1
- package/dist/components/form/fields/allowed-values/AllowedValues.js +16 -6
- package/dist/components/form/fields/allowed-values/AllowedValues.js.map +1 -1
- package/dist/components/form/fields/array/ArrayAuto.d.ts.map +1 -1
- package/dist/components/form/fields/array/ArrayAuto.js +51 -21
- package/dist/components/form/fields/array/ArrayAuto.js.map +1 -1
- package/dist/components/form/fields/array/ArrayAutoField.d.ts.map +1 -1
- package/dist/components/form/fields/array/ArrayAutoField.js +42 -25
- package/dist/components/form/fields/array/ArrayAutoField.js.map +1 -1
- package/dist/components/form/fields/auto/AutoFormField.d.ts +4 -0
- package/dist/components/form/fields/auto/AutoFormField.d.ts.map +1 -1
- package/dist/components/form/fields/auto/AutoFormField.js +29 -4
- package/dist/components/form/fields/auto/AutoFormField.js.map +1 -1
- package/dist/components/form/fields/long-string/LongString.d.ts +9 -1
- package/dist/components/form/fields/long-string/LongString.d.ts.map +1 -1
- package/dist/components/form/fields/long-string/LongString.js +25 -4
- package/dist/components/form/fields/long-string/LongString.js.map +1 -1
- package/dist/components/form/fields/template/TemplateField.d.ts.map +1 -1
- package/dist/components/form/fields/template/TemplateField.js +12 -2
- package/dist/components/form/fields/template/TemplateField.js.map +1 -1
- package/dist/helpers/common.d.ts +8 -1
- package/dist/helpers/common.d.ts.map +1 -1
- package/dist/helpers/common.js +9 -1
- package/dist/helpers/common.js.map +1 -1
- package/dist/helpers/optionUiTypes.d.ts +30 -0
- package/dist/helpers/optionUiTypes.d.ts.map +1 -0
- package/dist/helpers/optionUiTypes.js +148 -0
- package/dist/helpers/optionUiTypes.js.map +1 -0
- package/dist/helpers/options.d.ts +19 -0
- package/dist/helpers/options.d.ts.map +1 -1
- package/dist/helpers/options.js +25 -1
- package/dist/helpers/options.js.map +1 -1
- package/dist/helpers/providerValue.d.ts +68 -0
- package/dist/helpers/providerValue.d.ts.map +1 -0
- package/dist/helpers/providerValue.js +50 -0
- package/dist/helpers/providerValue.js.map +1 -0
- package/dist/helpers/singleLineString.d.ts +37 -0
- package/dist/helpers/singleLineString.d.ts.map +1 -0
- package/dist/helpers/singleLineString.js +66 -0
- package/dist/helpers/singleLineString.js.map +1 -0
- package/dist/helpers/validations.d.ts +85 -2
- package/dist/helpers/validations.d.ts.map +1 -1
- package/dist/helpers/validations.js +802 -76
- package/dist/helpers/validations.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/codeSize/index.tsx +79 -0
- package/src/components/form/engine/CompactRow.tsx +450 -31
- package/src/components/form/engine/CompactToolbar.tsx +16 -21
- package/src/components/form/engine/FormEngine.stories.tsx +804 -17
- package/src/components/form/engine/FormEngine.tsx +207 -105
- package/src/components/form/engine/_structuredData/SchemaDataView.stories.tsx +233 -0
- package/src/components/form/engine/_structuredData/SchemaDataView.tsx +630 -0
- package/src/components/form/engine/compactRowStyles.ts +44 -2
- package/src/components/form/engine/compactToolbarContext.ts +0 -8
- package/src/components/form/engine/readFirst.ts +201 -5
- package/src/components/form/fields/Field.tsx +3 -1
- package/src/components/form/fields/allowed-values/AllowedValues.tsx +24 -2
- package/src/components/form/fields/array/ArrayAuto.tsx +35 -4
- package/src/components/form/fields/array/ArrayAutoField.stories.tsx +134 -0
- package/src/components/form/fields/array/ArrayAutoField.tsx +22 -4
- package/src/components/form/fields/auto/AutoFormField.tsx +30 -1
- package/src/components/form/fields/long-string/LongString.tsx +46 -5
- package/src/components/form/fields/template/TemplateField.tsx +16 -1
- package/src/helpers/common.ts +11 -1
- package/src/helpers/optionUiTypes.ts +153 -0
- package/src/helpers/options.ts +23 -0
- package/src/helpers/providerValue.ts +125 -0
- package/src/helpers/singleLineString.ts +61 -0
- package/src/helpers/validations.ts +1105 -80
- package/src/hooks/useWebSocket/useWebsocket.stories.tsx +23 -2
- package/src/index.tsx +1 -0
|
@@ -18,6 +18,7 @@ import React, { memo, useCallback, useEffect, useState } from 'react';
|
|
|
18
18
|
import { useUpdateEffect } from 'react-use';
|
|
19
19
|
import useMount from 'react-use/lib/useMount';
|
|
20
20
|
import { typedToYaml, yamlToTyped } from '../../../../helpers/common';
|
|
21
|
+
import { getListElementValue } from '../../../../helpers/options';
|
|
21
22
|
import {
|
|
22
23
|
getTypeFromValue,
|
|
23
24
|
getValueOrDefaultValue,
|
|
@@ -75,6 +76,10 @@ export interface IAutoFieldProps
|
|
|
75
76
|
/** Render the arg_schema sub-form in compact (read-first) mode, matching the
|
|
76
77
|
* parent engine. */
|
|
77
78
|
compact?: boolean;
|
|
79
|
+
/** Open the arg_schema sub-form's first attention row on mount, without taking
|
|
80
|
+
* focus — see FormEngine's `expandFirstRequired`. Set by ArrayAuto on a
|
|
81
|
+
* just-added row so its required choice is visible without a second click. */
|
|
82
|
+
expandFirstRequired?: boolean;
|
|
78
83
|
path?: string;
|
|
79
84
|
column?: boolean;
|
|
80
85
|
level?: number;
|
|
@@ -161,6 +166,10 @@ function AutoField<T = any>({
|
|
|
161
166
|
// Only the nested arg_schema mount sites re-forward this into their
|
|
162
167
|
// sub-forms explicitly.
|
|
163
168
|
inheritedFromParent,
|
|
169
|
+
// Destructured for the same reason as `inheritedFromParent`: only the nested
|
|
170
|
+
// sub-form mount site consumes it, and leaving it in `...rest` would spread an
|
|
171
|
+
// unknown attribute onto the primitive field renderers' DOM nodes.
|
|
172
|
+
expandFirstRequired,
|
|
164
173
|
...rest
|
|
165
174
|
}: IAutoFieldProps & T) {
|
|
166
175
|
const [currentType, setType] = useState<IQorusType>(defaultInternalType || null);
|
|
@@ -416,6 +425,9 @@ function AutoField<T = any>({
|
|
|
416
425
|
value={value}
|
|
417
426
|
name={name}
|
|
418
427
|
onChange={handleChange}
|
|
428
|
+
// The picker heads itself with the field's own name rather than a
|
|
429
|
+
// generic "Select one:".
|
|
430
|
+
label={rest.display_name}
|
|
419
431
|
size={rest.size}
|
|
420
432
|
disabled={rest.disabled}
|
|
421
433
|
app={rest.app}
|
|
@@ -499,6 +511,12 @@ function AutoField<T = any>({
|
|
|
499
511
|
return (
|
|
500
512
|
<LongStringFormField
|
|
501
513
|
{...rest}
|
|
514
|
+
// These four share one editor but not one shape: a `string` holds
|
|
515
|
+
// exactly one line, while `data`, `binary` and `long-string` hold
|
|
516
|
+
// a document. Without the type the field cannot tell them apart
|
|
517
|
+
// and treats them all as documents, so an interface's Internal
|
|
518
|
+
// Name — which becomes a YAML key — accepted Enter.
|
|
519
|
+
type={currentType}
|
|
502
520
|
onChange={(value) => handleChange(name, value)}
|
|
503
521
|
value={value}
|
|
504
522
|
/>
|
|
@@ -559,6 +577,9 @@ function AutoField<T = any>({
|
|
|
559
577
|
wrapperPadding='top'
|
|
560
578
|
flat
|
|
561
579
|
compact={compact}
|
|
580
|
+
// A row added just now: open the field it cannot be saved
|
|
581
|
+
// without, instead of making the author find it and click it.
|
|
582
|
+
expandFirstRequired={expandFirstRequired}
|
|
562
583
|
// Embedded sub-form: no scroll context of its own, so its toolbar
|
|
563
584
|
// isn't sticky and its header stays transparent (no dark backdrop).
|
|
564
585
|
compactNested
|
|
@@ -633,8 +654,16 @@ function AutoField<T = any>({
|
|
|
633
654
|
});
|
|
634
655
|
};
|
|
635
656
|
|
|
657
|
+
// An element may arrive bare or in a `{value, type}` envelope. The
|
|
658
|
+
// envelope is what `formatToServerValue` writes, so a list the user
|
|
659
|
+
// has edited in this session is always wrapped — but a list read
|
|
660
|
+
// back from storage holds exactly what the server contract asks
|
|
661
|
+
// for, which for a list of strings is the strings themselves.
|
|
662
|
+
// Reaching for `.value` on those answered `undefined` for every
|
|
663
|
+
// element: the row rendered as an empty, invalid item, and the
|
|
664
|
+
// emptiness was then written back over the stored value.
|
|
636
665
|
const formatFromServerValue = (value) => {
|
|
637
|
-
return (value || []).map(
|
|
666
|
+
return (value || []).map(getListElementValue);
|
|
638
667
|
};
|
|
639
668
|
|
|
640
669
|
const mappedAllowedValues = rest.element_allowed_values?.map((ev) => ({
|
|
@@ -1,17 +1,31 @@
|
|
|
1
1
|
import { ReqoreTextarea } from '@qoretechnologies/reqore';
|
|
2
2
|
import { IReqoreTextareaProps } from '@qoretechnologies/reqore/dist/components/Textarea';
|
|
3
|
-
import { ChangeEvent, useCallback, useEffect, useState } from 'react';
|
|
3
|
+
import { ChangeEvent, KeyboardEvent, useCallback, useEffect, useState } from 'react';
|
|
4
4
|
import { useDebounce } from 'react-use';
|
|
5
|
+
import {
|
|
6
|
+
flattenToSingleLine,
|
|
7
|
+
hasLineBreak,
|
|
8
|
+
isSingleLineStringType,
|
|
9
|
+
} from '../../../../helpers/singleLineString';
|
|
5
10
|
|
|
6
11
|
export interface ILongStringFormFieldProps extends Omit<IReqoreTextareaProps, 'onChange'> {
|
|
7
12
|
value?: string;
|
|
8
13
|
onChange?: (value: string, event?: ChangeEvent<HTMLTextAreaElement>) => void;
|
|
14
|
+
/**
|
|
15
|
+
* The field's declared type, which decides whether it holds one line.
|
|
16
|
+
*
|
|
17
|
+
* Without it every string field is a growing textarea, so a technical name, a
|
|
18
|
+
* version and an IP address all accept Enter — and become YAML keys, class
|
|
19
|
+
* names and URLs with a newline in them. See `helpers/singleLineString`.
|
|
20
|
+
*/
|
|
21
|
+
type?: string;
|
|
9
22
|
}
|
|
10
23
|
|
|
11
24
|
export const LongStringFormField = ({
|
|
12
25
|
value,
|
|
13
26
|
onChange,
|
|
14
27
|
onClearClick,
|
|
28
|
+
type,
|
|
15
29
|
...rest
|
|
16
30
|
}: ILongStringFormFieldProps) => {
|
|
17
31
|
const [localValue, setLocalValue] = useState<string>(value ?? '');
|
|
@@ -32,9 +46,32 @@ export const LongStringFormField = ({
|
|
|
32
46
|
[localValue, onChange]
|
|
33
47
|
);
|
|
34
48
|
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
49
|
+
const singleLine = isSingleLineStringType(type);
|
|
50
|
+
|
|
51
|
+
const handleChange = useCallback(
|
|
52
|
+
(event: ChangeEvent<HTMLTextAreaElement>): void => {
|
|
53
|
+
const raw = event.target.value;
|
|
54
|
+
// Flatten here rather than only blocking the Enter key: a line break also
|
|
55
|
+
// arrives by paste, by drag-and-drop, from autofill and from an IME, and
|
|
56
|
+
// a keydown guard catches none of those.
|
|
57
|
+
setLocalValue(singleLine && hasLineBreak(raw) ? flattenToSingleLine(raw) : raw);
|
|
58
|
+
},
|
|
59
|
+
[singleLine]
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
// Stop Enter before it inserts anything, so the caret does not jump to a
|
|
63
|
+
// second line that is then flattened away under the cursor.
|
|
64
|
+
const handleKeyDown = useCallback(
|
|
65
|
+
(event: KeyboardEvent<HTMLTextAreaElement>): void => {
|
|
66
|
+
if (singleLine && event.key === 'Enter') {
|
|
67
|
+
event.preventDefault();
|
|
68
|
+
}
|
|
69
|
+
(rest as { onKeyDown?: (event: KeyboardEvent<HTMLTextAreaElement>) => void }).onKeyDown?.(
|
|
70
|
+
event
|
|
71
|
+
);
|
|
72
|
+
},
|
|
73
|
+
[singleLine, rest.onKeyDown]
|
|
74
|
+
);
|
|
38
75
|
|
|
39
76
|
const handleClearClick = useCallback(() => {
|
|
40
77
|
setLocalValue('');
|
|
@@ -44,12 +81,16 @@ export const LongStringFormField = ({
|
|
|
44
81
|
|
|
45
82
|
return (
|
|
46
83
|
<ReqoreTextarea
|
|
47
|
-
|
|
84
|
+
// A one-line field must not grow, and must not offer a resize grip that
|
|
85
|
+
// implies it can hold more than it will keep.
|
|
86
|
+
scaleWithContent={!singleLine}
|
|
87
|
+
rows={singleLine ? 1 : undefined}
|
|
48
88
|
fluid
|
|
49
89
|
value={localValue}
|
|
50
90
|
onChange={handleChange}
|
|
51
91
|
onClearClick={handleClearClick}
|
|
52
92
|
{...rest}
|
|
93
|
+
onKeyDown={handleKeyDown}
|
|
53
94
|
/>
|
|
54
95
|
);
|
|
55
96
|
};
|
|
@@ -74,8 +74,17 @@ export const TemplatesListProps: IReqoreDropdownProps = {
|
|
|
74
74
|
// markup stays verbatim. `type`/`level`/`allowTemplates` are destructured
|
|
75
75
|
// only to keep them off the underlying ReQore component / DOM.
|
|
76
76
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
77
|
+
// `type` is FORWARDED here, unlike the other wrappers below: the long-string
|
|
78
|
+
// field consumes it to decide whether it holds one line (a `string` does, a
|
|
79
|
+
// `long-string`, `hash` or `list` does not) and never spreads it to the DOM.
|
|
80
|
+
// Dropping it made every string field in every FormEngine form a growing
|
|
81
|
+
// textarea, so an alert rule's Internal Name took Enter.
|
|
77
82
|
const LongStringField = ({ name, onChange, type, level, allowTemplates, ...rest }: any) => (
|
|
78
|
-
<LongStringFormField
|
|
83
|
+
<LongStringFormField
|
|
84
|
+
{...rest}
|
|
85
|
+
type={type}
|
|
86
|
+
onChange={(value: string) => onChange?.(name, value)}
|
|
87
|
+
/>
|
|
79
88
|
);
|
|
80
89
|
const Number = ({ name, onChange, type, level, allowTemplates, ...rest }: any) => (
|
|
81
90
|
<NumberFormField {...rest} onChange={(value: number | string) => onChange?.(name, value)} />
|
|
@@ -397,6 +406,11 @@ export const TemplateField = memo(
|
|
|
397
406
|
showTemplatesDropdown;
|
|
398
407
|
|
|
399
408
|
const Component = componentFromType ? ComponentMap[type] : Comp;
|
|
409
|
+
// Only a ComponentMap component gets `type`: it is ours and CONSUMES the
|
|
410
|
+
// prop (the long-string field needs it to know whether it holds one line)
|
|
411
|
+
// rather than spreading it onto the DOM. A caller-supplied `Comp` is someone
|
|
412
|
+
// else's, which is why `type` was kept off this call in the first place.
|
|
413
|
+
const componentTypeProp = componentFromType ? { type } : {};
|
|
400
414
|
const fieldAriaLabel = rest['aria-label'] ?? label ?? rest.display_name ?? name;
|
|
401
415
|
|
|
402
416
|
const filteredTemplates = useMemo<IReqoreFormTemplates>(():
|
|
@@ -769,6 +783,7 @@ export const TemplateField = memo(
|
|
|
769
783
|
name={name}
|
|
770
784
|
level={level}
|
|
771
785
|
{...rest}
|
|
786
|
+
{...componentTypeProp}
|
|
772
787
|
aria-label={fieldAriaLabel}
|
|
773
788
|
className={`${className} template-selector`}
|
|
774
789
|
templates={componentTemplates}
|
package/src/helpers/common.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { IReqoreRichTextEditorProps } from '@qoretechnologies/reqore/dist/components/RichTextEditor';
|
|
2
|
-
import { TQorusFormFieldSchema } from '@qoretechnologies/ts-toolkit';
|
|
2
|
+
import { TQorusFormFieldSchema, TQorusFormOperatorValue } from '@qoretechnologies/ts-toolkit';
|
|
3
3
|
import yaml from 'js-yaml';
|
|
4
4
|
|
|
5
5
|
// ─── Typed ↔ YAML helpers ──────────────────────────────────────────────────────
|
|
@@ -163,6 +163,16 @@ export const richtextToSegments = (
|
|
|
163
163
|
return segments;
|
|
164
164
|
};
|
|
165
165
|
|
|
166
|
+
/**
|
|
167
|
+
* An operator value as the list it logically is.
|
|
168
|
+
*
|
|
169
|
+
* An option's `op` holds either one operator or a chain of them, and both
|
|
170
|
+
* spellings are legitimate on the wire. Every reader wants the list.
|
|
171
|
+
*/
|
|
172
|
+
export const fixOperatorValue = (
|
|
173
|
+
operator: TQorusFormOperatorValue
|
|
174
|
+
): (string | null | undefined)[] => (Array.isArray(operator) ? operator : [operator]);
|
|
175
|
+
|
|
166
176
|
export const insertAtIndex = (array: any[] = [], index = 0, value: any): any[] => {
|
|
167
177
|
return [...array.slice(0, index), value, ...array.slice(index)];
|
|
168
178
|
};
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Qorus option `ui_type` vocabulary.
|
|
3
|
+
*
|
|
4
|
+
* A `ui_type` names the widget an option is rendered with, and — because the
|
|
5
|
+
* widget decides what a complete value looks like — it is also what
|
|
6
|
+
* `validateField` switches on. Both readers have to agree about which strings
|
|
7
|
+
* are in the vocabulary, so the lists live here rather than beside either one.
|
|
8
|
+
*
|
|
9
|
+
* Grouped by what the group means to a reader, not by alphabet: the scalar
|
|
10
|
+
* types hold a single value, the collection types hold several, the choice
|
|
11
|
+
* types pick from a supplied set, and the interface types name another Qorus
|
|
12
|
+
* object by its name — which is why `isOptionInterfaceUiType` is enough for
|
|
13
|
+
* validation to treat the whole group as a non-empty string.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export const OPTION_SCALAR_UI_TYPES = [
|
|
17
|
+
'string',
|
|
18
|
+
'long-string',
|
|
19
|
+
'data',
|
|
20
|
+
'binary',
|
|
21
|
+
'richtext',
|
|
22
|
+
'bool',
|
|
23
|
+
'boolean',
|
|
24
|
+
'date',
|
|
25
|
+
'int',
|
|
26
|
+
'integer',
|
|
27
|
+
'float',
|
|
28
|
+
'number',
|
|
29
|
+
'byte-size',
|
|
30
|
+
'url',
|
|
31
|
+
'code-editor',
|
|
32
|
+
'rgbcolor',
|
|
33
|
+
'any',
|
|
34
|
+
'auto',
|
|
35
|
+
// semantic string formats (string-backed types that carry a UI-renderable identity; see the qore
|
|
36
|
+
// DataProvider QoreStringFormatDataType)
|
|
37
|
+
'email',
|
|
38
|
+
'uri',
|
|
39
|
+
'uuid',
|
|
40
|
+
'hostname',
|
|
41
|
+
'ipv4',
|
|
42
|
+
'ipv6',
|
|
43
|
+
'phone',
|
|
44
|
+
] as const;
|
|
45
|
+
|
|
46
|
+
export const OPTION_COLLECTION_UI_TYPES = [
|
|
47
|
+
'hash',
|
|
48
|
+
'free-hash',
|
|
49
|
+
'list',
|
|
50
|
+
'free-list',
|
|
51
|
+
'option_hash',
|
|
52
|
+
'file',
|
|
53
|
+
'file-as-string',
|
|
54
|
+
'collection-documents',
|
|
55
|
+
] as const;
|
|
56
|
+
|
|
57
|
+
export const OPTION_CHOICE_UI_TYPES = [
|
|
58
|
+
'enum',
|
|
59
|
+
'select-string',
|
|
60
|
+
'select-array',
|
|
61
|
+
'multi-select',
|
|
62
|
+
] as const;
|
|
63
|
+
|
|
64
|
+
export const OPTION_INTERFACE_UI_TYPES = [
|
|
65
|
+
'workflow',
|
|
66
|
+
'service',
|
|
67
|
+
'job',
|
|
68
|
+
'connection',
|
|
69
|
+
'mapper',
|
|
70
|
+
'class',
|
|
71
|
+
'value-map',
|
|
72
|
+
'fsm',
|
|
73
|
+
'qog',
|
|
74
|
+
'api-schema',
|
|
75
|
+
'auth-profile',
|
|
76
|
+
'ai-collection',
|
|
77
|
+
'ai-endpoint',
|
|
78
|
+
'ai-guardrail',
|
|
79
|
+
'ml-model',
|
|
80
|
+
'release-config',
|
|
81
|
+
'alertrule',
|
|
82
|
+
'alertsilence',
|
|
83
|
+
'installed-release',
|
|
84
|
+
'business-process',
|
|
85
|
+
'schema',
|
|
86
|
+
'test',
|
|
87
|
+
] as const;
|
|
88
|
+
|
|
89
|
+
export const OPTION_CUSTOM_UI_TYPES = [
|
|
90
|
+
'processor',
|
|
91
|
+
'processor-mappings',
|
|
92
|
+
'data-provider',
|
|
93
|
+
'tool-catalog',
|
|
94
|
+
'schema-definition',
|
|
95
|
+
'test-cases',
|
|
96
|
+
'test-value-contract',
|
|
97
|
+
'dpql',
|
|
98
|
+
'active-windows',
|
|
99
|
+
] as const;
|
|
100
|
+
|
|
101
|
+
export const OPTION_UI_TYPES = [
|
|
102
|
+
...OPTION_SCALAR_UI_TYPES,
|
|
103
|
+
...OPTION_COLLECTION_UI_TYPES,
|
|
104
|
+
...OPTION_CHOICE_UI_TYPES,
|
|
105
|
+
...OPTION_INTERFACE_UI_TYPES,
|
|
106
|
+
...OPTION_CUSTOM_UI_TYPES,
|
|
107
|
+
] as const;
|
|
108
|
+
|
|
109
|
+
export type TOptionUiType = (typeof OPTION_UI_TYPES)[number];
|
|
110
|
+
export type TOptionInterfaceUiType = (typeof OPTION_INTERFACE_UI_TYPES)[number];
|
|
111
|
+
|
|
112
|
+
export const LEGACY_FIELD_UI_TYPES = [
|
|
113
|
+
'api-manager',
|
|
114
|
+
'array',
|
|
115
|
+
'array-auto',
|
|
116
|
+
'array-of-pairs',
|
|
117
|
+
'class-array',
|
|
118
|
+
'class-connectors',
|
|
119
|
+
'constant-array',
|
|
120
|
+
'context-selector',
|
|
121
|
+
'cron',
|
|
122
|
+
'data-provider-options',
|
|
123
|
+
'file-array',
|
|
124
|
+
'fsm-list',
|
|
125
|
+
'function-array',
|
|
126
|
+
'http-content',
|
|
127
|
+
'method-name',
|
|
128
|
+
'opcua-method-arguments',
|
|
129
|
+
'opcua-write-values',
|
|
130
|
+
'options',
|
|
131
|
+
'select',
|
|
132
|
+
'service-events',
|
|
133
|
+
'service-webhooks',
|
|
134
|
+
'type-selector',
|
|
135
|
+
] as const;
|
|
136
|
+
|
|
137
|
+
export const KNOWN_QORUS_UI_TYPES = [...OPTION_UI_TYPES, ...LEGACY_FIELD_UI_TYPES] as const;
|
|
138
|
+
|
|
139
|
+
export type TLegacyFieldUiType = (typeof LEGACY_FIELD_UI_TYPES)[number];
|
|
140
|
+
export type TKnownQorusUiType = (typeof KNOWN_QORUS_UI_TYPES)[number];
|
|
141
|
+
|
|
142
|
+
const optionUiTypeSet = new Set<string>(OPTION_UI_TYPES);
|
|
143
|
+
const interfaceUiTypeSet = new Set<string>(OPTION_INTERFACE_UI_TYPES);
|
|
144
|
+
const knownQorusUiTypeSet = new Set<string>(KNOWN_QORUS_UI_TYPES);
|
|
145
|
+
|
|
146
|
+
export const isOptionUiType = (type: unknown): type is TOptionUiType =>
|
|
147
|
+
typeof type === 'string' && optionUiTypeSet.has(type);
|
|
148
|
+
|
|
149
|
+
export const isOptionInterfaceUiType = (type: unknown): type is TOptionInterfaceUiType =>
|
|
150
|
+
typeof type === 'string' && interfaceUiTypeSet.has(type);
|
|
151
|
+
|
|
152
|
+
export const isKnownQorusUiType = (type: unknown): type is TKnownQorusUiType =>
|
|
153
|
+
typeof type === 'string' && knownQorusUiTypeSet.has(type);
|
package/src/helpers/options.ts
CHANGED
|
@@ -29,3 +29,26 @@ export const getRequiredOptionMessage = (
|
|
|
29
29
|
|
|
30
30
|
return `This field or ${requiredOptions} is required`;
|
|
31
31
|
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The value of one element of a list field.
|
|
35
|
+
*
|
|
36
|
+
* A list element arrives in either of two shapes, and both are legitimate. The
|
|
37
|
+
* editor keeps each element in a `{value, type}` envelope while the form is
|
|
38
|
+
* being worked on — that is what `formatToServerValue` writes — while a value
|
|
39
|
+
* read back from storage holds what the server contract asks for, which for a
|
|
40
|
+
* list of strings is the bare strings.
|
|
41
|
+
*
|
|
42
|
+
* Reaching straight for `.value` answered `undefined` for every stored element.
|
|
43
|
+
* On a read that emptied the row; on a validation pass it failed the element as
|
|
44
|
+
* empty; and on an autosaving form the emptiness was then written back over the
|
|
45
|
+
* stored value. One helper so the readers cannot disagree about which shape
|
|
46
|
+
* they are looking at.
|
|
47
|
+
*
|
|
48
|
+
* An array is never an envelope — a rich-text document is itself an array, and
|
|
49
|
+
* `'value' in []` is false anyway, but saying so keeps the intent legible.
|
|
50
|
+
*/
|
|
51
|
+
export const getListElementValue = (element: unknown): unknown =>
|
|
52
|
+
element && typeof element === 'object' && !Array.isArray(element) && 'value' in element ?
|
|
53
|
+
(element as { value: unknown }).value
|
|
54
|
+
: element;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { TQorusForm } from '@qoretechnologies/ts-toolkit';
|
|
2
|
+
|
|
3
|
+
export type TRecordType = 'search' | 'search-single' | 'create' | 'update' | 'delete';
|
|
4
|
+
export type TRealRecordType = 'read' | 'create' | 'update' | 'delete';
|
|
5
|
+
|
|
6
|
+
export type TProviderTypeSupports = {
|
|
7
|
+
[key in `supports_${TRealRecordType}`]?: boolean;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type TProviderTypeArgs = {
|
|
11
|
+
[key in `${TRecordType}_args`]?: TQorusForm | TQorusForm[];
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A data-provider selection, in the shape the form holds it.
|
|
16
|
+
*
|
|
17
|
+
* The `supports_*` flags say which record operations the selected provider can
|
|
18
|
+
* perform; validation reads them to decide whether a provider is a legitimate
|
|
19
|
+
* target for the operation the field was configured for.
|
|
20
|
+
*/
|
|
21
|
+
export interface IProviderType extends TProviderTypeSupports, TProviderTypeArgs {
|
|
22
|
+
type: string;
|
|
23
|
+
name: string;
|
|
24
|
+
path?: string;
|
|
25
|
+
options?: TQorusForm;
|
|
26
|
+
subtype?: 'request' | 'response';
|
|
27
|
+
hasApiContext?: boolean;
|
|
28
|
+
optionsChanged?: boolean;
|
|
29
|
+
searchOptionsChanged?: boolean;
|
|
30
|
+
desc?: string;
|
|
31
|
+
use_args?: boolean;
|
|
32
|
+
args?: any;
|
|
33
|
+
up?: boolean;
|
|
34
|
+
supports_request?: boolean;
|
|
35
|
+
supports_messages?: 'ASYNC' | 'SYNC' | 'NONE';
|
|
36
|
+
supports_observable?: boolean;
|
|
37
|
+
transaction_management?: boolean;
|
|
38
|
+
record_requires_search_options?: boolean;
|
|
39
|
+
create_args_freeform?: TQorusForm[];
|
|
40
|
+
is_api_call?: boolean;
|
|
41
|
+
search_options?: TQorusForm;
|
|
42
|
+
descriptions?: string[];
|
|
43
|
+
message_id?: string;
|
|
44
|
+
message?: {
|
|
45
|
+
type: string;
|
|
46
|
+
value: any;
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* A provider value as an object, from either representation a caller may hold.
|
|
52
|
+
*
|
|
53
|
+
* A provider reaches the form either already expanded into `IProviderType`, or
|
|
54
|
+
* as the flat path string the server stores — `"factory/db{driver=pgsql}/table"`
|
|
55
|
+
* or the plain `"type/name/path"`. Returns `null` for anything that is neither,
|
|
56
|
+
* so a caller reports an invalid value rather than reading fields off nothing.
|
|
57
|
+
*/
|
|
58
|
+
export const maybeBuildOptionProvider = (provider: unknown): IProviderType | null => {
|
|
59
|
+
if (!provider) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
if (typeof provider === 'object') {
|
|
63
|
+
return provider as IProviderType;
|
|
64
|
+
}
|
|
65
|
+
if (typeof provider !== 'string') {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (provider.startsWith('factory')) {
|
|
70
|
+
const fixedProvider = provider.endsWith('/') ? provider : `${provider}/`;
|
|
71
|
+
const [factoryType] = fixedProvider.split('/');
|
|
72
|
+
const factoryName = fixedProvider.substring(
|
|
73
|
+
fixedProvider.indexOf('/') + 1,
|
|
74
|
+
fixedProvider.lastIndexOf('{')
|
|
75
|
+
);
|
|
76
|
+
const options = fixedProvider.substring(
|
|
77
|
+
fixedProvider.indexOf('{') + 1,
|
|
78
|
+
fixedProvider.lastIndexOf('}')
|
|
79
|
+
);
|
|
80
|
+
const optionsObject = options
|
|
81
|
+
.split(',')
|
|
82
|
+
.filter(Boolean)
|
|
83
|
+
.reduce<Record<string, string>>((result, option) => {
|
|
84
|
+
const [key, value] = option.split('=');
|
|
85
|
+
result[key] = value;
|
|
86
|
+
return result;
|
|
87
|
+
}, {});
|
|
88
|
+
const result: IProviderType = {
|
|
89
|
+
type: factoryType,
|
|
90
|
+
name: factoryName,
|
|
91
|
+
path: fixedProvider.substring(fixedProvider.lastIndexOf('}/') + 2),
|
|
92
|
+
options: optionsObject as unknown as TQorusForm,
|
|
93
|
+
};
|
|
94
|
+
if (provider.includes('?options_changed')) {
|
|
95
|
+
result.optionsChanged = true;
|
|
96
|
+
}
|
|
97
|
+
return result;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const [type, name, ...path] = provider.split('/');
|
|
101
|
+
return { type, name, path: path.join('/') };
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* An FSM variable action — which variable, and what is done to it.
|
|
106
|
+
*
|
|
107
|
+
* The provider half is `Partial<IProviderType>` because the action's own
|
|
108
|
+
* `action_type` decides which provider fields have to be there; validation
|
|
109
|
+
* re-runs the matching provider validator over the merged value rather than
|
|
110
|
+
* duplicating those rules here.
|
|
111
|
+
*/
|
|
112
|
+
export type TVariableActionValue = {
|
|
113
|
+
var_type: 'globalvar' | 'localvar' | 'autovar';
|
|
114
|
+
var_name: string;
|
|
115
|
+
transaction_action?: 'commit' | 'rollback' | 'begin-transaction';
|
|
116
|
+
action_type?:
|
|
117
|
+
| 'search'
|
|
118
|
+
| 'search-single'
|
|
119
|
+
| 'update'
|
|
120
|
+
| 'create'
|
|
121
|
+
| 'delete'
|
|
122
|
+
| 'transaction'
|
|
123
|
+
| 'send-message'
|
|
124
|
+
| 'apicall';
|
|
125
|
+
} & Partial<IProviderType>;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which string field types hold one line, and how to keep them that way.
|
|
3
|
+
*
|
|
4
|
+
* The server already draws this distinction and the IDE was throwing it away:
|
|
5
|
+
* a service's `name` is declared `ui_type: "string"` while its `description` is
|
|
6
|
+
* `ui_type: "long-string"` with `markdown: True` (see `ServiceMetadata.qc`).
|
|
7
|
+
* Every one of them — plus `email`, `uuid`, `hostname`, `ipv4`, `ipv6` and
|
|
8
|
+
* `phone` — rendered through the same growing textarea, so a technical name, a
|
|
9
|
+
* version and an IP address all accepted Enter.
|
|
10
|
+
*
|
|
11
|
+
* That is not a cosmetic problem. These values become YAML keys, class names,
|
|
12
|
+
* URLs and identifiers; a newline in one of them is invalid input that the form
|
|
13
|
+
* happily accepted and passed on, and the operator had no way to see it.
|
|
14
|
+
*
|
|
15
|
+
* The rule is the server's own contract, stated once here rather than guessed
|
|
16
|
+
* per field, and it is enforced in `LongString.tsx`.
|
|
17
|
+
*
|
|
18
|
+
* It lives in reqraft rather than in a consumer because the consumer is not the
|
|
19
|
+
* only renderer: qorus-ide fixed this in its OWN long-string field, and every
|
|
20
|
+
* form that goes through FormEngine — which is where an alert rule's Internal
|
|
21
|
+
* Name, Name and Short Description are — reaches THIS one instead. The fix was
|
|
22
|
+
* real and the field in front of the operator still took Enter.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The string types that hold exactly one line — the ones routed to a textarea
|
|
27
|
+
* today.
|
|
28
|
+
*
|
|
29
|
+
* Listed explicitly rather than derived by excluding the document types,
|
|
30
|
+
* because the two directions fail very differently. Missing a single-line type
|
|
31
|
+
* here leaves it behaving as it does now — no worse. Wrongly treating a new
|
|
32
|
+
* DOCUMENT type as single-line would flatten a user's text as they typed it,
|
|
33
|
+
* which is data loss. So an unrecognised type stays multi-line.
|
|
34
|
+
*/
|
|
35
|
+
const SINGLE_LINE_STRING_TYPES = new Set([
|
|
36
|
+
'string',
|
|
37
|
+
'email',
|
|
38
|
+
'uuid',
|
|
39
|
+
'hostname',
|
|
40
|
+
'ipv4',
|
|
41
|
+
'ipv6',
|
|
42
|
+
'phone',
|
|
43
|
+
]);
|
|
44
|
+
|
|
45
|
+
/** True when a string field of this type holds a single line. */
|
|
46
|
+
export const isSingleLineStringType = (type?: string): boolean =>
|
|
47
|
+
!!type && SINGLE_LINE_STRING_TYPES.has(type);
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Flattens any line break to a single space.
|
|
51
|
+
*
|
|
52
|
+
* A space rather than nothing, because the newline is usually where a word
|
|
53
|
+
* boundary was — pasting two lines into a name should read as two words, not
|
|
54
|
+
* one run-together one. CR, LF and CRLF all collapse to one space, and the
|
|
55
|
+
* result is trimmed of leading/trailing whitespace the break introduced.
|
|
56
|
+
*/
|
|
57
|
+
export const flattenToSingleLine = (value: string): string =>
|
|
58
|
+
value.replace(/\r\n|\r|\n/g, ' ').replace(/ {2,}/g, ' ');
|
|
59
|
+
|
|
60
|
+
/** True when the value carries a line break this field should not hold. */
|
|
61
|
+
export const hasLineBreak = (value: string): boolean => /\r|\n/.test(value);
|