@gooddata/sdk-ui-catalog 11.46.0-alpha.4 → 11.46.0-alpha.6

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.
@@ -30,12 +30,13 @@ export function CatalogItemShareProvider({ shareableItem, target, labels, childr
30
30
  // One controller drives both the dialog and the inline access row; the row reads
31
31
  // `state.summary`, so a save inside the dialog refreshes it too. labelsLoading/
32
32
  // labelsError keep the controller label-unresolved while labels are pending.
33
+ const [isOpen, setIsOpen] = useState(false);
33
34
  const controller = useObjectShare(target, {
34
35
  labels: labels.labels,
35
36
  labelsError: labels.error,
36
37
  labelsLoading: labels.loading,
38
+ isOpen,
37
39
  });
38
- const [isOpen, setIsOpen] = useState(false);
39
40
  // The detail view is reused as the user navigates between objects, so close an
40
41
  // open dialog when the shareable target changes (or becomes non-shareable) —
41
42
  // otherwise it would linger open on the next object. Reset during render (React's
@@ -670,7 +670,7 @@ export const en_US = {
670
670
  "crowdinContext": "Section header label above the YAML editor in the parameter dialog."
671
671
  },
672
672
  "analyticsCatalog.parameter.dialog.sectionHeader.tooltip": {
673
- "text": "Create a YAML statement that defines a numeric parameter.",
673
+ "text": "Create a YAML statement that defines a parameter.",
674
674
  "crowdinContext": "Tooltip shown for the help icon next to the parameter definition section header."
675
675
  },
676
676
  "analyticsCatalog.parameter.dialog.create.submit": {
@@ -710,20 +710,20 @@ export const en_US = {
710
710
  "crowdinContext": "Error shown when the parameter YAML does not follow the expected object structure."
711
711
  },
712
712
  "analyticsCatalog.parameter.validation.unsupportedType": {
713
- "text": "Only NUMBER parameters are supported.",
714
- "crowdinContext": "Error shown when the parameter YAML declares an unsupported parameter type."
713
+ "text": "Only {enabledTypes} parameters are supported.",
714
+ "crowdinContext": "Error shown when the parameter YAML declares an unsupported parameter type. {enabledTypes} is a comma-separated list of the supported type names (e.g. 'NUMBER' or 'NUMBER and STRING')."
715
715
  },
716
716
  "analyticsCatalog.parameter.validation.invalidDefaultValue": {
717
- "text": "Default value must be a number.",
718
- "crowdinContext": "Error shown when the parameter YAML contains a non-numeric defaultValue."
717
+ "text": "Default value must be {type, select, STRING {a string} other {a number}}.",
718
+ "crowdinContext": "Error shown when the parameter YAML contains a defaultValue whose kind does not match the declared parameter type. {type} is the declared type: STRING expects a string, otherwise a number."
719
719
  },
720
720
  "analyticsCatalog.parameter.validation.invalidConstraints": {
721
- "text": "Parameter constraints must use numeric min and max values.",
722
- "crowdinContext": "Error shown when the parameter YAML contains invalid constraint values."
721
+ "text": "Invalid constraints: {type, select, STRING {STRING parameters allow non-negative integer minLength and maxLength} other {NUMBER parameters allow numeric min and max}}.",
722
+ "crowdinContext": "Error shown when the parameter YAML contains invalid constraint values. {type} is the declared type, selecting the constraint keys valid for that type."
723
723
  },
724
724
  "analyticsCatalog.parameter.validation.invalidConstraintRange": {
725
- "text": "Constraint min cannot be greater than max.",
726
- "crowdinContext": "Error shown when the parameter YAML declares an invalid numeric range."
725
+ "text": "Constraint {type, select, STRING {minLength cannot be greater than maxLength} other {min cannot be greater than max}}.",
726
+ "crowdinContext": "Error shown when the parameter YAML declares an invalid constraint range. {type} is the declared type, selecting the constraint key names for that type."
727
727
  },
728
728
  "analyticsCatalog.parameter.validation.invalidTags": {
729
729
  "text": "Tags must be a list of strings.",
@@ -1 +1 @@
1
- {"version":3,"file":"ParameterDialog.d.ts","sourceRoot":"","sources":["../../src/parameter/ParameterDialog.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,kCAAkC,EAAE,MAAM,qBAAqB,CAAC;AAM9E,OAAO,EAAE,KAAK,cAAc,EAA4B,MAAM,6BAA6B,CAAC;AAyB5F,MAAM,MAAM,mBAAmB,GAAG,QAAQ,GAAG,MAAM,CAAC;AAEpD,KAAK,KAAK,GAAG;IACT,IAAI,EAAE,mBAAmB,CAAC;IAC1B,gBAAgB,CAAC,EAAE,cAAc,CAAC;IAClC,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,EAAE,CAAC,SAAS,EAAE,kCAAkC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3E,WAAW,CAAC,EAAE,CAAC,SAAS,EAAE,kCAAkC,KAAK,IAAI,CAAC;CACzE,CAAC;AAEF,wBAAgB,eAAe,CAAC,KAAK,EAAE,KAAK,2CAyJ3C"}
1
+ {"version":3,"file":"ParameterDialog.d.ts","sourceRoot":"","sources":["../../src/parameter/ParameterDialog.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,kCAAkC,EAAE,MAAM,qBAAqB,CAAC;AAO9E,OAAO,EAAE,KAAK,cAAc,EAA4B,MAAM,6BAA6B,CAAC;AAyB5F,MAAM,MAAM,mBAAmB,GAAG,QAAQ,GAAG,MAAM,CAAC;AAEpD,KAAK,KAAK,GAAG;IACT,IAAI,EAAE,mBAAmB,CAAC;IAC1B,gBAAgB,CAAC,EAAE,cAAc,CAAC;IAClC,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,EAAE,CAAC,SAAS,EAAE,kCAAkC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3E,WAAW,CAAC,EAAE,CAAC,SAAS,EAAE,kCAAkC,KAAK,IAAI,CAAC;CACzE,CAAC;AAEF,wBAAgB,eAAe,CAAC,KAAK,EAAE,KAAK,2CAqK3C"}
@@ -5,6 +5,7 @@ import { FormattedMessage, defineMessages, useIntl } from "react-intl";
5
5
  import { ConfirmDialog, UiButton, UiIcon, UiLink, UiTooltip } from "@gooddata/sdk-ui-kit";
6
6
  import { useIsWhiteLabeled } from "../permission/PermissionsContext.js";
7
7
  import { extractBackendErrorDetail } from "../utils/backendError.js";
8
+ import { useEnabledParameterTypes } from "./gate.js";
8
9
  import { serializeParameterToYaml } from "./parameterSerialization.js";
9
10
  import { validateParameterYaml } from "./parameterValidation.js";
10
11
  import { ParameterYamlEditor } from "./ParameterYamlEditor.js";
@@ -42,11 +43,17 @@ export function ParameterDialog(props) {
42
43
  const submitMessage = intl.formatMessage(isEdit ? messages.dialogEditSubmit : messages.dialogCreateSubmit);
43
44
  const cancelMessage = intl.formatMessage(messages.dialogCancel);
44
45
  const fixedIdentifier = isEdit ? initialParameter?.id : undefined;
46
+ const enabledTypes = useEnabledParameterTypes();
45
47
  const validate = useCallback((value, allowIdChange = false) => {
46
48
  return validateParameterYaml(value, {
49
+ enabledTypes,
47
50
  fixedIdentifier: isEdit && !allowIdChange ? fixedIdentifier : undefined,
48
51
  });
49
- }, [fixedIdentifier, isEdit]);
52
+ }, [enabledTypes, fixedIdentifier, isEdit]);
53
+ const formatValidationError = useCallback((result) => intl.formatMessage(messages[result.errorCode], {
54
+ type: result.type,
55
+ enabledTypes: intl.formatList(enabledTypes, { type: "conjunction" }),
56
+ }), [enabledTypes, intl]);
50
57
  const handleClose = useCallback(() => {
51
58
  if (!isSubmitting) {
52
59
  onClose();
@@ -61,7 +68,7 @@ export function ParameterDialog(props) {
61
68
  const handleSubmit = useCallback(async () => {
62
69
  const result = validate(yamlValue.current);
63
70
  if (!result.isValid) {
64
- setValidationError(intl.formatMessage(messages[result.errorCode]));
71
+ setValidationError(formatValidationError(result));
65
72
  return;
66
73
  }
67
74
  setIsSubmitting(true);
@@ -75,7 +82,7 @@ export function ParameterDialog(props) {
75
82
  finally {
76
83
  setIsSubmitting(false);
77
84
  }
78
- }, [intl, onSubmit, validate]);
85
+ }, [formatValidationError, intl, onSubmit, validate]);
79
86
  const handleDuplicate = useCallback(() => {
80
87
  if (!onDuplicate) {
81
88
  return;
@@ -83,17 +90,17 @@ export function ParameterDialog(props) {
83
90
  setSubmitError(null);
84
91
  const result = validate(yamlValue.current, true);
85
92
  if (!result.isValid) {
86
- setValidationError(intl.formatMessage(messages[result.errorCode]));
93
+ setValidationError(formatValidationError(result));
87
94
  return;
88
95
  }
89
96
  onDuplicate(result.parameter);
90
- }, [intl, onDuplicate, validate]);
97
+ }, [formatValidationError, onDuplicate, validate]);
91
98
  const footerLeftRenderer = useCallback(() => (_jsxs("div", { className: "gd-ascode-dialog-footer-left", children: [isWhiteLabeled ? null : (_jsxs(UiLink, { variant: "secondary", href: PARAMETER_DOCS_URL, target: "_blank", rel: "noopener noreferrer", children: [
92
99
  _jsx(UiIcon, { type: "question", size: 14, color: "complementary-6" }), _jsx(FormattedMessage, { id: "analyticsCatalog.parameter.dialog.help" })
93
100
  ] })), isEdit && onDuplicate ? (_jsx(UiButton, { label: intl.formatMessage(messages.dialogDuplicate), variant: "tertiary", isDisabled: isSubmitting, onClick: handleDuplicate })) : null] })), [handleDuplicate, intl, isEdit, isSubmitting, isWhiteLabeled, onDuplicate]);
94
101
  return (_jsx(ConfirmDialog, { className: "gd-ascode-dialog", containerClassName: "gd-ascode-dialog-overlay", headline: headlineMessage, cancelButtonText: cancelMessage, submitButtonText: submitMessage, isPositive: true, isSubmitDisabled: isSubmitting || (isEdit && !isDirty), isCancelDisabled: isSubmitting, shouldCloseOnEscape: !isSubmitting, onCancel: handleClose, onClose: handleClose, onSubmit: handleSubmit, displayCloseButton: !isSubmitting, footerLeftRenderer: footerLeftRenderer, children: _jsxs("div", { className: "gd-ascode-dialog-content", children: [
95
102
  _jsxs("div", { className: "gd-ascode-dialog-section-header", children: [
96
103
  _jsx(FormattedMessage, { id: "analyticsCatalog.parameter.dialog.sectionHeader" }), _jsx(UiTooltip, { arrowPlacement: "top", optimalPlacement: true, triggerBy: ["hover", "click", "focus"], anchor: _jsx(UiIcon, { type: "question", size: 14, color: "complementary-6" }), content: _jsx(FormattedMessage, { id: "analyticsCatalog.parameter.dialog.sectionHeader.tooltip" }) })
97
- ] }), _jsx("div", { className: "gd-ascode-dialog-editor", children: _jsx(ParameterYamlEditor, { initialValue: initialYaml, onChange: handleChange, disabled: isSubmitting }) }), validationError || submitError ? (_jsxs("div", { className: "gd-ascode-dialog-error", children: [
104
+ ] }), _jsx("div", { className: "gd-ascode-dialog-editor", children: _jsx(ParameterYamlEditor, { initialValue: initialYaml, enabledTypes: enabledTypes, onChange: handleChange, disabled: isSubmitting }) }), validationError || submitError ? (_jsxs("div", { className: "gd-ascode-dialog-error", children: [
98
105
  _jsx("span", { className: "gd-ascode-dialog-error-label", children: _jsx(FormattedMessage, { id: "analyticsCatalog.dialog.error.label" }) }), " ", validationError ?? submitError] })) : null] }) }));
99
106
  }
@@ -1,8 +1,10 @@
1
+ import type { ParameterType } from "@gooddata/sdk-model";
1
2
  type Props = {
2
3
  initialValue: string;
4
+ enabledTypes: ParameterType[];
3
5
  onChange?: (value: string) => void;
4
6
  disabled?: boolean;
5
7
  };
6
- export declare function ParameterYamlEditor({ initialValue, onChange, disabled }: Props): import("react/jsx-runtime").JSX.Element;
8
+ export declare function ParameterYamlEditor({ initialValue, enabledTypes, onChange, disabled }: Props): import("react/jsx-runtime").JSX.Element;
7
9
  export {};
8
10
  //# sourceMappingURL=ParameterYamlEditor.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ParameterYamlEditor.d.ts","sourceRoot":"","sources":["../../src/parameter/ParameterYamlEditor.tsx"],"names":[],"mappings":"AAcA,KAAK,KAAK,GAAG;IACT,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,QAAQ,CAAC,EAAE,OAAO,CAAC;CACtB,CAAC;AAEF,wBAAgB,mBAAmB,CAAC,EAAE,YAAY,EAAE,QAAQ,EAAE,QAAgB,EAAE,EAAE,KAAK,2CAwBtF"}
1
+ {"version":3,"file":"ParameterYamlEditor.d.ts","sourceRoot":"","sources":["../../src/parameter/ParameterYamlEditor.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAMzD,KAAK,KAAK,GAAG;IACT,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,aAAa,EAAE,CAAC;IAC9B,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,QAAQ,CAAC,EAAE,OAAO,CAAC;CACtB,CAAC;AAEF,wBAAgB,mBAAmB,CAAC,EAAE,YAAY,EAAE,YAAY,EAAE,QAAQ,EAAE,QAAgB,EAAE,EAAE,KAAK,2CAyBpG"}
@@ -6,15 +6,16 @@ import { lintGutter } from "@codemirror/lint";
6
6
  import { lineNumbers } from "@codemirror/view";
7
7
  import { useIntl } from "react-intl";
8
8
  import { SyntaxHighlightingInput } from "@gooddata/sdk-ui-kit";
9
- import { parameterSchemaCompletions } from "./parameterCompletions.js";
9
+ import { createParameterCompletions } from "./parameterCompletions.js";
10
10
  import { createYamlLinter } from "./yamlLinter.js";
11
- export function ParameterYamlEditor({ initialValue, onChange, disabled = false }) {
11
+ export function ParameterYamlEditor({ initialValue, enabledTypes, onChange, disabled = false }) {
12
12
  const intl = useIntl();
13
13
  const [value, setValue] = useState(initialValue);
14
14
  const extensions = useMemo(() => [yaml(), lineNumbers(), createYamlLinter(intl), lintGutter()], [intl]);
15
+ const completions = useMemo(() => createParameterCompletions(enabledTypes), [enabledTypes]);
15
16
  const handleChange = useCallback((nextValue) => {
16
17
  setValue(nextValue);
17
18
  onChange?.(nextValue);
18
19
  }, [onChange]);
19
- return (_jsx(SyntaxHighlightingInput, { className: "gd-ascode-dialog-editor-input", value: value, onChange: handleChange, extensions: extensions, onCompletion: parameterSchemaCompletions, disabled: disabled }));
20
+ return (_jsx(SyntaxHighlightingInput, { className: "gd-ascode-dialog-editor-input", value: value, onChange: handleChange, extensions: extensions, onCompletion: completions, disabled: disabled }));
20
21
  }
@@ -1,3 +1,4 @@
1
+ import type { ParameterType } from "@gooddata/sdk-model";
1
2
  /**
2
3
  * Catalog parameter feature gate.
3
4
  */
@@ -6,4 +7,8 @@ export declare function useIsParametersEnabled(): boolean;
6
7
  * Whether the current user can manage parameters in the catalog.
7
8
  */
8
9
  export declare function useCanManageParameter(): boolean;
10
+ /**
11
+ * Parameter types authorable in the catalog, gated by `enableStringParameters`.
12
+ */
13
+ export declare function useEnabledParameterTypes(): ParameterType[];
9
14
  //# sourceMappingURL=gate.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"gate.d.ts","sourceRoot":"","sources":["../../src/parameter/gate.ts"],"names":[],"mappings":"AAIA;;GAEG;AACH,wBAAgB,sBAAsB,IAAI,OAAO,CAEhD;AAED;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,OAAO,CAI/C"}
1
+ {"version":3,"file":"gate.d.ts","sourceRoot":"","sources":["../../src/parameter/gate.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAIzD;;GAEG;AACH,wBAAgB,sBAAsB,IAAI,OAAO,CAEhD;AAED;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,OAAO,CAI/C;AAED;;GAEG;AACH,wBAAgB,wBAAwB,IAAI,aAAa,EAAE,CAM1D"}
@@ -1,4 +1,5 @@
1
1
  // (C) 2026 GoodData Corporation
2
+ import { useMemo } from "react";
2
3
  import { useFeatureFlag, useWorkspacePermission } from "../permission/PermissionsContext.js";
3
4
  /**
4
5
  * Catalog parameter feature gate.
@@ -14,3 +15,10 @@ export function useCanManageParameter() {
14
15
  const canManageProject = useWorkspacePermission("canManageProject");
15
16
  return isParametersEnabled && canManageProject;
16
17
  }
18
+ /**
19
+ * Parameter types authorable in the catalog, gated by `enableStringParameters`.
20
+ */
21
+ export function useEnabledParameterTypes() {
22
+ const enableStringParameters = useFeatureFlag("enableStringParameters");
23
+ return useMemo(() => (enableStringParameters ? ["NUMBER", "STRING"] : ["NUMBER"]), [enableStringParameters]);
24
+ }
@@ -1,7 +1,13 @@
1
1
  import type { CompletionContext, CompletionResult } from "@codemirror/autocomplete";
2
+ import type { ParameterType } from "@gooddata/sdk-model";
2
3
  /**
3
- * CodeMirror completion source that suggests allowed YAML property names
4
- * based on the parameter zod schema and cursor position in the Lezer tree.
4
+ * Creates a CodeMirror completion source that suggests allowed YAML property
5
+ * names and `type:` values.
6
+ *
7
+ * The parent mapping is derived from the cursor's indentation column. Key
8
+ * suggestions are narrowed to the declared `definition.type` when present
9
+ * (falling back to the union of the enabled types); `type:` value suggestions
10
+ * always offer every enabled type.
5
11
  */
6
- export declare function parameterSchemaCompletions(context: CompletionContext): CompletionResult | null;
12
+ export declare function createParameterCompletions(enabledTypes: ParameterType[]): (context: CompletionContext) => CompletionResult | null;
7
13
  //# sourceMappingURL=parameterCompletions.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"parameterCompletions.d.ts","sourceRoot":"","sources":["../../src/parameter/parameterCompletions.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAcpF;;;GAGG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,iBAAiB,GAAG,gBAAgB,GAAG,IAAI,CAoB9F"}
1
+ {"version":3,"file":"parameterCompletions.d.ts","sourceRoot":"","sources":["../../src/parameter/parameterCompletions.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAc,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAGhG,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAIzD;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,CAAC,YAAY,EAAE,aAAa,EAAE,2DAuCvE"}
@@ -1,53 +1,78 @@
1
1
  // (C) 2026 GoodData Corporation
2
- import { syntaxTree } from "@codemirror/language";
3
- import { PARAMETER_SCHEMA_KEYS } from "./parameterSchema.js";
4
- /** Pre-built completion options from the schema key map. */
5
- const completionOptions = Object.fromEntries(Object.entries(PARAMETER_SCHEMA_KEYS).map(([parent, keys]) => [
6
- parent,
7
- keys.map((label) => ({ label, type: "property" })),
8
- ]));
2
+ import { parameterSchemaKeys } from "./parameterSchema.js";
9
3
  /**
10
- * CodeMirror completion source that suggests allowed YAML property names
11
- * based on the parameter zod schema and cursor position in the Lezer tree.
4
+ * Creates a CodeMirror completion source that suggests allowed YAML property
5
+ * names and `type:` values.
6
+ *
7
+ * The parent mapping is derived from the cursor's indentation column. Key
8
+ * suggestions are narrowed to the declared `definition.type` when present
9
+ * (falling back to the union of the enabled types); `type:` value suggestions
10
+ * always offer every enabled type.
12
11
  */
13
- export function parameterSchemaCompletions(context) {
14
- const line = context.state.doc.lineAt(context.pos);
15
- const before = line.text.slice(0, context.pos - line.from);
16
- // Only trigger when typing a key name at the start of a line
17
- const match = before.match(/^(\s*)(\w*)$/);
18
- if (!match) {
19
- return null;
20
- }
21
- const word = match[2];
22
- const from = context.pos - word.length;
23
- const parentKey = getParentKey(context.state, context.pos);
24
- const options = completionOptions[parentKey];
25
- if (!options) {
26
- return null;
27
- }
28
- return { from, options };
12
+ export function createParameterCompletions(enabledTypes) {
13
+ const keyOptionsByType = new Map(enabledTypes.map((type) => [type, toOptions(parameterSchemaKeys([type]))]));
14
+ const fallbackKeyOptions = toOptions(parameterSchemaKeys(enabledTypes));
15
+ const typeValueOptions = {
16
+ "": [{ label: "parameter", type: "constant" }],
17
+ definition: enabledTypes.map((label) => ({ label, type: "constant" })),
18
+ };
19
+ return function completeParameter(context) {
20
+ const line = context.state.doc.lineAt(context.pos);
21
+ const before = line.text.slice(0, context.pos - line.from);
22
+ // Trigger on a key name at the start of a line, or on the value of a `type:` key
23
+ const keyMatch = before.match(/^(\s*)(\w*)$/);
24
+ const typeMatch = keyMatch ? null : before.match(/^(\s*)type:\s+(\w*)$/);
25
+ const match = keyMatch ?? typeMatch;
26
+ if (!match) {
27
+ return null;
28
+ }
29
+ const indent = match[1].length;
30
+ const word = match[2];
31
+ const parentKey = getParentKey(context.state.doc, line.number, indent);
32
+ if (parentKey === null) {
33
+ return null;
34
+ }
35
+ const declaredType = getDeclaredType(context.state.doc);
36
+ const keyOptions = (declaredType ? keyOptionsByType.get(declaredType) : undefined) ?? fallbackKeyOptions;
37
+ const options = (keyMatch ? keyOptions : typeValueOptions)[parentKey];
38
+ if (!options) {
39
+ return null;
40
+ }
41
+ return { from: context.pos - word.length, options };
42
+ };
43
+ }
44
+ function toOptions(keyMap) {
45
+ return Object.fromEntries(Object.entries(keyMap).map(([parent, keys]) => [
46
+ parent,
47
+ keys.map((label) => ({ label, type: "property" })),
48
+ ]));
29
49
  }
30
50
  /**
31
- * Walk up the Lezer YAML tree from cursor to find which key's BlockMapping
32
- * we're inside. Returns "" for the top-level mapping.
51
+ * The key whose block mapping a new entry at column `indent` would join: the
52
+ * nearest less-indented `key:` line above. Returns "" for the top-level
53
+ * mapping and null when the enclosing line is not a mapping key.
33
54
  */
34
- function getParentKey(state, pos) {
35
- const tree = syntaxTree(state);
36
- let node = tree.resolveInner(pos, -1).parent;
37
- // Find the BlockMapping we're inside
38
- while (node) {
39
- if (node.name === "BlockMapping") {
40
- // If this BlockMapping is the value of a Pair, return the Pair's key
41
- const pair = node.parent;
42
- if (pair?.name === "Pair") {
43
- const keyNode = pair.getChild("Key");
44
- if (keyNode) {
45
- return state.sliceDoc(keyNode.from, keyNode.to).trim();
46
- }
47
- }
48
- return "";
55
+ function getParentKey(doc, lineNumber, indent) {
56
+ if (indent === 0) {
57
+ return "";
58
+ }
59
+ for (let number = lineNumber - 1; number >= 1; number--) {
60
+ const text = doc.line(number).text;
61
+ const content = text.trimStart();
62
+ if (content === "" || content.startsWith("#") || text.length - content.length >= indent) {
63
+ continue;
49
64
  }
50
- node = node.parent;
65
+ return content.match(/^([\w-]+):/)?.[1] ?? null;
51
66
  }
52
67
  return "";
53
68
  }
69
+ /** Value of the first indented `type:` key, i.e. `definition.type` (the top-level `type` is unindented). */
70
+ function getDeclaredType(doc) {
71
+ for (let number = 1; number <= doc.lines; number++) {
72
+ const match = doc.line(number).text.match(/^\s+type:\s*(\w+)/);
73
+ if (match) {
74
+ return match[1];
75
+ }
76
+ }
77
+ return undefined;
78
+ }
@@ -1,20 +1,40 @@
1
1
  import * as z from "zod/mini";
2
- export declare const parameterSchema: z.ZodMiniObject<{
2
+ import type { ParameterType } from "@gooddata/sdk-model";
3
+ /** Full-union schema: single source for the editor vocabulary and the exported types, independent of the flag. */
4
+ declare const parameterSchema: z.ZodMiniObject<{
3
5
  type: z.ZodMiniDefault<z.ZodMiniLiteral<"parameter">>;
4
6
  id: z.ZodMiniOptional<z.ZodMiniString<string>>;
5
7
  title: z.ZodMiniOptional<z.ZodMiniString<string>>;
6
8
  description: z.ZodMiniOptional<z.ZodMiniString<string>>;
7
9
  tags: z.ZodMiniOptional<z.ZodMiniArray<z.ZodMiniString<string>>>;
8
- definition: z.ZodMiniDiscriminatedUnion<readonly [z.ZodMiniObject<{
10
+ definition: z.ZodMiniDiscriminatedUnion<[z.ZodMiniObject<{
9
11
  type: z.ZodMiniLiteral<"NUMBER">;
10
12
  defaultValue: z.ZodMiniNumber<number>;
11
13
  constraints: z.ZodMiniOptional<z.ZodMiniObject<{
12
14
  min: z.ZodMiniOptional<z.ZodMiniNumber<number>>;
13
15
  max: z.ZodMiniOptional<z.ZodMiniNumber<number>>;
14
16
  }, z.core.$strict>>;
17
+ }, z.core.$strict>, z.ZodMiniObject<{
18
+ type: z.ZodMiniLiteral<"STRING">;
19
+ defaultValue: z.ZodMiniString<string>;
20
+ constraints: z.ZodMiniOptional<z.ZodMiniObject<{
21
+ minLength: z.ZodMiniOptional<z.ZodMiniNumberFormat>;
22
+ maxLength: z.ZodMiniOptional<z.ZodMiniNumberFormat>;
23
+ }, z.core.$strict>>;
15
24
  }, z.core.$strict>], "type">;
16
25
  }, z.core.$strict>;
17
26
  export type ParameterSchemaInput = z.input<typeof parameterSchema>;
18
27
  export type ParameterSchema = z.infer<typeof parameterSchema>;
19
- export declare const PARAMETER_SCHEMA_KEYS: Record<string, string[]>;
28
+ /** Parent key allowed child keys for the schema restricted to `enabledTypes`; powers YAML editor autocompletion. */
29
+ export declare function parameterSchemaKeys(enabledTypes: ParameterType[]): Record<string, string[]>;
30
+ /** Runtime validation schema restricted to the enabled types; the static types stay the full union. */
31
+ export declare function buildParameterSchema(enabledTypes: ParameterType[]): z.ZodMiniObject<{
32
+ type: z.ZodMiniDefault<z.ZodMiniLiteral<"parameter">>;
33
+ id: z.ZodMiniOptional<z.ZodMiniString<string>>;
34
+ title: z.ZodMiniOptional<z.ZodMiniString<string>>;
35
+ description: z.ZodMiniOptional<z.ZodMiniString<string>>;
36
+ tags: z.ZodMiniOptional<z.ZodMiniArray<z.ZodMiniString<string>>>;
37
+ definition: z.ZodMiniDiscriminatedUnion<readonly [z.core.$ZodTypeDiscriminable, ...z.core.$ZodTypeDiscriminable[]], "type">;
38
+ }, z.core.$strict>;
39
+ export {};
20
40
  //# sourceMappingURL=parameterSchema.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"parameterSchema.d.ts","sourceRoot":"","sources":["../../src/parameter/parameterSchema.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,CAAC,MAAM,UAAU,CAAC;AA6C9B,eAAO,MAAM,eAAe;;;;;;;;;;;;;;kBAO1B,CAAC;AAEH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AACnE,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAE9D,eAAO,MAAM,qBAAqB,0BAAoC,CAAC"}
1
+ {"version":3,"file":"parameterSchema.d.ts","sourceRoot":"","sources":["../../src/parameter/parameterSchema.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,CAAC,MAAM,UAAU,CAAC;AAE9B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAoDzD,kHAAkH;AAClH,QAAA,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;kBAOnB,CAAC;AAEH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AACnE,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAE9D,wHAAsH;AACtH,wBAAgB,mBAAmB,CAAC,YAAY,EAAE,aAAa,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAE3F;AAED,uGAAuG;AACvG,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,aAAa,EAAE;;;;;;;mBAMjE"}
@@ -17,9 +17,22 @@ const numberConstraintsSchema = z
17
17
  });
18
18
  }
19
19
  });
20
- const stringConstraintsSchema = z.strictObject({
21
- minLength: z.optional(z.number()),
22
- maxLength: z.optional(z.number()),
20
+ const lengthSchema = z.int().check(z.nonnegative());
21
+ const stringConstraintsSchema = z
22
+ .strictObject({
23
+ minLength: z.optional(lengthSchema),
24
+ maxLength: z.optional(lengthSchema),
25
+ })
26
+ .check((ctx) => {
27
+ const { minLength, maxLength } = ctx.value;
28
+ if (minLength !== undefined && maxLength !== undefined && minLength > maxLength) {
29
+ ctx.issues.push({
30
+ code: "custom",
31
+ message: "invalidConstraintRange",
32
+ input: ctx.value,
33
+ path: [],
34
+ });
35
+ }
23
36
  });
24
37
  /** One arm per model type; `satisfies` breaks the build if the model gains a type without an arm. */
25
38
  const definitionSchemas = {
@@ -34,19 +47,28 @@ const definitionSchemas = {
34
47
  constraints: z.optional(stringConstraintsSchema),
35
48
  }),
36
49
  };
37
- /** Policy knob: the types the UI validates. Others still display via the seed but are rejected at submit. */
38
- const supportedDefinitionSchemas = [definitionSchemas.NUMBER];
39
- const definitionSchema = z.discriminatedUnion("type", supportedDefinitionSchemas);
40
- export const parameterSchema = z.strictObject({
50
+ /** Full-union schema: single source for the editor vocabulary and the exported types, independent of the flag. */
51
+ const parameterSchema = z.strictObject({
41
52
  type: z._default(z.literal("parameter"), "parameter"),
42
53
  id: z.optional(z.string()),
43
54
  title: z.optional(z.string()),
44
55
  description: z.optional(z.string()),
45
56
  tags: z.optional(z.array(z.string())),
46
- definition: definitionSchema,
57
+ definition: z.discriminatedUnion("type", [definitionSchemas.NUMBER, definitionSchemas.STRING]),
47
58
  });
48
- export const PARAMETER_SCHEMA_KEYS = deriveSchemaKeys(parameterSchema);
49
- /** Map of parent key → allowed child keys, powering YAML editor autocompletion. */
59
+ /** Parent key allowed child keys for the schema restricted to `enabledTypes`; powers YAML editor autocompletion. */
60
+ export function parameterSchemaKeys(enabledTypes) {
61
+ return deriveSchemaKeys(buildParameterSchema(enabledTypes));
62
+ }
63
+ /** Runtime validation schema restricted to the enabled types; the static types stay the full union. */
64
+ export function buildParameterSchema(enabledTypes) {
65
+ // Re-spread to a non-empty tuple: `discriminatedUnion` requires it and it preserves the inferred union.
66
+ const [first, ...rest] = enabledTypes.map((type) => definitionSchemas[type]);
67
+ return z.extend(parameterSchema, {
68
+ definition: z.discriminatedUnion("type", [first, ...rest]),
69
+ });
70
+ }
71
+ /** Map of parent key → allowed child keys. */
50
72
  function deriveSchemaKeys(schema, parentKey = "", result = {}) {
51
73
  for (const object of objectSchemasOf(schema)) {
52
74
  const { shape } = object._zod.def;
@@ -1,3 +1,4 @@
1
+ import type { ParameterType } from "@gooddata/sdk-model";
1
2
  import { type ParameterSchema } from "./parameterSchema.js";
2
3
  export type ParameterValidationResult = {
3
4
  isValid: true;
@@ -5,11 +6,13 @@ export type ParameterValidationResult = {
5
6
  } | {
6
7
  isValid: false;
7
8
  errorCode: ParameterValidationErrorCode;
9
+ type?: ParameterType;
8
10
  };
9
11
  export type ParameterValidationErrorCode = "empty" | "syntax" | "invalidStructure" | "idImmutable" | "unsupportedType" | "invalidDefaultValue" | "invalidConstraints" | "invalidConstraintRange" | "invalidTags";
10
12
  type ValidateParameterYamlOptions = {
13
+ enabledTypes: ParameterType[];
11
14
  fixedIdentifier?: string;
12
15
  };
13
- export declare function validateParameterYaml(value: string, options?: ValidateParameterYamlOptions): ParameterValidationResult;
16
+ export declare function validateParameterYaml(value: string, options: ValidateParameterYamlOptions): ParameterValidationResult;
14
17
  export {};
15
18
  //# sourceMappingURL=parameterValidation.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"parameterValidation.d.ts","sourceRoot":"","sources":["../../src/parameter/parameterValidation.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,eAAe,EAAmB,MAAM,sBAAsB,CAAC;AAE7E,MAAM,MAAM,yBAAyB,GAC/B;IACI,OAAO,EAAE,IAAI,CAAC;IACd,SAAS,EAAE,eAAe,CAAC;CAC9B,GACD;IACI,OAAO,EAAE,KAAK,CAAC;IACf,SAAS,EAAE,4BAA4B,CAAC;CAC3C,CAAC;AAER,MAAM,MAAM,4BAA4B,GAClC,OAAO,GACP,QAAQ,GACR,kBAAkB,GAClB,aAAa,GACb,iBAAiB,GACjB,qBAAqB,GACrB,oBAAoB,GACpB,wBAAwB,GACxB,aAAa,CAAC;AAEpB,KAAK,4BAA4B,GAAG;IAChC,eAAe,CAAC,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF,wBAAgB,qBAAqB,CACjC,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,4BAAiC,GAC3C,yBAAyB,CAyB3B"}
1
+ {"version":3,"file":"parameterValidation.d.ts","sourceRoot":"","sources":["../../src/parameter/parameterValidation.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,OAAO,EAAE,KAAK,eAAe,EAAwB,MAAM,sBAAsB,CAAC;AAElF,MAAM,MAAM,yBAAyB,GAC/B;IACI,OAAO,EAAE,IAAI,CAAC;IACd,SAAS,EAAE,eAAe,CAAC;CAC9B,GACD;IACI,OAAO,EAAE,KAAK,CAAC;IACf,SAAS,EAAE,4BAA4B,CAAC;IACxC,IAAI,CAAC,EAAE,aAAa,CAAC;CACxB,CAAC;AAER,MAAM,MAAM,4BAA4B,GAClC,OAAO,GACP,QAAQ,GACR,kBAAkB,GAClB,aAAa,GACb,iBAAiB,GACjB,qBAAqB,GACrB,oBAAoB,GACpB,wBAAwB,GACxB,aAAa,CAAC;AAEpB,KAAK,4BAA4B,GAAG;IAChC,YAAY,EAAE,aAAa,EAAE,CAAC;IAC9B,eAAe,CAAC,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF,wBAAgB,qBAAqB,CACjC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,4BAA4B,GACtC,yBAAyB,CAyB3B"}
@@ -1,7 +1,7 @@
1
1
  // (C) 2026 GoodData Corporation
2
2
  import { parse as parseYaml } from "yaml";
3
- import { parameterSchema } from "./parameterSchema.js";
4
- export function validateParameterYaml(value, options = {}) {
3
+ import { buildParameterSchema } from "./parameterSchema.js";
4
+ export function validateParameterYaml(value, options) {
5
5
  if (value.trim() === "") {
6
6
  return invalid("empty");
7
7
  }
@@ -12,9 +12,9 @@ export function validateParameterYaml(value, options = {}) {
12
12
  catch {
13
13
  return invalid("syntax");
14
14
  }
15
- const result = parameterSchema.safeParse(parsed);
15
+ const result = buildParameterSchema(options.enabledTypes).safeParse(parsed);
16
16
  if (!result.success) {
17
- return classifySchemaError(result.error);
17
+ return classifySchemaError(result.error, readDeclaredType(parsed));
18
18
  }
19
19
  if (options.fixedIdentifier !== undefined && result.data.id !== options.fixedIdentifier) {
20
20
  return invalid("idImmutable");
@@ -24,22 +24,22 @@ export function validateParameterYaml(value, options = {}) {
24
24
  parameter: result.data,
25
25
  };
26
26
  }
27
- function classifySchemaError(error) {
27
+ function classifySchemaError(error, declaredType) {
28
28
  for (const issue of error.issues) {
29
29
  const path = issue.path.map(String).join(".");
30
30
  if (issue.code === "custom") {
31
31
  if (issue.message === "invalidConstraintRange") {
32
- return invalid("invalidConstraintRange");
32
+ return invalid("invalidConstraintRange", declaredType);
33
33
  }
34
34
  }
35
35
  if (path === "definition.type") {
36
36
  return invalid("unsupportedType");
37
37
  }
38
38
  if (path === "definition.defaultValue") {
39
- return invalid("invalidDefaultValue");
39
+ return invalid("invalidDefaultValue", declaredType);
40
40
  }
41
41
  if (path.startsWith("definition.constraints")) {
42
- return invalid("invalidConstraints");
42
+ return invalid("invalidConstraints", declaredType);
43
43
  }
44
44
  if (path === "tags" || path.startsWith("tags.")) {
45
45
  return invalid("invalidTags");
@@ -47,9 +47,22 @@ function classifySchemaError(error) {
47
47
  }
48
48
  return invalid("invalidStructure");
49
49
  }
50
- function invalid(code) {
50
+ /** The declared `definition.type`, when it names a known model type; drives the type-specific error copy. */
51
+ function readDeclaredType(parsed) {
52
+ if (!parsed || typeof parsed !== "object") {
53
+ return undefined;
54
+ }
55
+ const { definition } = parsed;
56
+ if (!definition || typeof definition !== "object") {
57
+ return undefined;
58
+ }
59
+ const { type } = definition;
60
+ return type === "NUMBER" || type === "STRING" ? type : undefined;
61
+ }
62
+ function invalid(code, type) {
51
63
  return {
52
64
  isValid: false,
53
65
  errorCode: code,
66
+ type,
54
67
  };
55
68
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gooddata/sdk-ui-catalog",
3
- "version": "11.46.0-alpha.4",
3
+ "version": "11.46.0-alpha.6",
4
4
  "description": "GoodData SDK - Analytics Catalog",
5
5
  "license": "MIT",
6
6
  "author": "GoodData Corporation",
@@ -45,15 +45,15 @@
45
45
  "tslib": "2.8.1",
46
46
  "yaml": "2.8.3",
47
47
  "zod": "4.3.6",
48
- "@gooddata/sdk-backend-spi": "11.46.0-alpha.4",
49
- "@gooddata/sdk-code-schemas": "11.46.0-alpha.4",
50
- "@gooddata/sdk-ui": "11.46.0-alpha.4",
51
- "@gooddata/sdk-model": "11.46.0-alpha.4",
52
- "@gooddata/sdk-ui-ext": "11.46.0-alpha.4",
53
- "@gooddata/sdk-ui-kit": "11.46.0-alpha.4",
54
- "@gooddata/sdk-ui-semantic-search": "11.46.0-alpha.4",
55
- "@gooddata/sdk-ui-theme-provider": "11.46.0-alpha.4",
56
- "@gooddata/util": "11.46.0-alpha.4"
48
+ "@gooddata/sdk-backend-spi": "11.46.0-alpha.6",
49
+ "@gooddata/sdk-code-schemas": "11.46.0-alpha.6",
50
+ "@gooddata/sdk-model": "11.46.0-alpha.6",
51
+ "@gooddata/sdk-ui": "11.46.0-alpha.6",
52
+ "@gooddata/sdk-ui-ext": "11.46.0-alpha.6",
53
+ "@gooddata/sdk-ui-kit": "11.46.0-alpha.6",
54
+ "@gooddata/sdk-ui-semantic-search": "11.46.0-alpha.6",
55
+ "@gooddata/util": "11.46.0-alpha.6",
56
+ "@gooddata/sdk-ui-theme-provider": "11.46.0-alpha.6"
57
57
  },
58
58
  "devDependencies": {
59
59
  "@microsoft/api-documenter": "^7.17.0",
@@ -93,11 +93,11 @@
93
93
  "typescript": "5.9.3",
94
94
  "vitest": "4.1.8",
95
95
  "vitest-dom": "0.1.1",
96
- "@gooddata/oxlint-config": "11.46.0-alpha.4",
97
- "@gooddata/i18n-toolkit": "11.46.0-alpha.4",
98
- "@gooddata/eslint-config": "11.46.0-alpha.4",
99
- "@gooddata/sdk-backend-mockingbird": "11.46.0-alpha.4",
100
- "@gooddata/stylelint-config": "11.46.0-alpha.4"
96
+ "@gooddata/eslint-config": "11.46.0-alpha.6",
97
+ "@gooddata/i18n-toolkit": "11.46.0-alpha.6",
98
+ "@gooddata/oxlint-config": "11.46.0-alpha.6",
99
+ "@gooddata/sdk-backend-mockingbird": "11.46.0-alpha.6",
100
+ "@gooddata/stylelint-config": "11.46.0-alpha.6"
101
101
  },
102
102
  "peerDependencies": {
103
103
  "react": "^18.0.0 || ^19.0.0",