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

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 (96) hide show
  1. package/esm/AnalyticsCatalogDetail.d.ts.map +1 -1
  2. package/esm/AnalyticsCatalogDetail.js +2 -1
  3. package/esm/catalog/Catalog.d.ts.map +1 -1
  4. package/esm/catalog/Catalog.js +5 -2
  5. package/esm/catalogDetail/CatalogDetailActionBar.d.ts +6 -4
  6. package/esm/catalogDetail/CatalogDetailActionBar.d.ts.map +1 -1
  7. package/esm/catalogDetail/CatalogDetailActionBar.js +5 -6
  8. package/esm/catalogDetail/CatalogDetailActions.d.ts +2 -1
  9. package/esm/catalogDetail/CatalogDetailActions.d.ts.map +1 -1
  10. package/esm/catalogDetail/CatalogDetailActions.js +6 -2
  11. package/esm/catalogDetail/CatalogDetailContent.d.ts.map +1 -1
  12. package/esm/catalogDetail/CatalogDetailContent.js +3 -1
  13. package/esm/catalogItem/query.d.ts +7 -2
  14. package/esm/catalogItem/query.d.ts.map +1 -1
  15. package/esm/catalogItem/query.js +16 -1
  16. package/esm/header/CreateObjectButton.d.ts +2 -1
  17. package/esm/header/CreateObjectButton.d.ts.map +1 -1
  18. package/esm/header/CreateObjectButton.js +42 -38
  19. package/esm/localization/bundles/en-US.localization-bundle.d.ts +112 -0
  20. package/esm/localization/bundles/en-US.localization-bundle.d.ts.map +1 -1
  21. package/esm/localization/bundles/en-US.localization-bundle.js +112 -0
  22. package/esm/main/Main.d.ts.map +1 -1
  23. package/esm/main/Main.js +2 -3
  24. package/esm/metric/MetricCreateDialog.d.ts +11 -0
  25. package/esm/metric/MetricCreateDialog.d.ts.map +1 -0
  26. package/esm/metric/MetricCreateDialog.js +55 -0
  27. package/esm/metric/MetricDeleteDialog.d.ts +9 -0
  28. package/esm/metric/MetricDeleteDialog.d.ts.map +1 -0
  29. package/esm/metric/MetricDeleteDialog.js +68 -0
  30. package/esm/metric/MetricDetailActions.d.ts +23 -0
  31. package/esm/metric/MetricDetailActions.d.ts.map +1 -0
  32. package/esm/metric/MetricDetailActions.js +101 -0
  33. package/esm/metric/MetricDialog.d.ts +14 -0
  34. package/esm/metric/MetricDialog.d.ts.map +1 -0
  35. package/esm/metric/MetricDialog.js +100 -0
  36. package/esm/metric/MetricEditDialog.d.ts +11 -0
  37. package/esm/metric/MetricEditDialog.d.ts.map +1 -0
  38. package/esm/metric/MetricEditDialog.js +51 -0
  39. package/esm/metric/MetricMutationContext.d.ts +16 -0
  40. package/esm/metric/MetricMutationContext.d.ts.map +1 -0
  41. package/esm/metric/MetricMutationContext.js +32 -0
  42. package/esm/metric/MetricYamlEditor.d.ts +8 -0
  43. package/esm/metric/MetricYamlEditor.d.ts.map +1 -0
  44. package/esm/metric/MetricYamlEditor.js +20 -0
  45. package/esm/metric/gate.d.ts +9 -0
  46. package/esm/metric/gate.d.ts.map +1 -0
  47. package/esm/metric/gate.js +16 -0
  48. package/esm/metric/metricCompletions.d.ts +7 -0
  49. package/esm/metric/metricCompletions.d.ts.map +1 -0
  50. package/esm/metric/metricCompletions.js +59 -0
  51. package/esm/metric/metricConverter.d.ts +49 -0
  52. package/esm/metric/metricConverter.d.ts.map +1 -0
  53. package/esm/metric/metricConverter.js +101 -0
  54. package/esm/metric/metricCopy.d.ts +4 -0
  55. package/esm/metric/metricCopy.d.ts.map +1 -0
  56. package/esm/metric/metricCopy.js +60 -0
  57. package/esm/metric/metricMutationPort.d.ts +18 -0
  58. package/esm/metric/metricMutationPort.d.ts.map +1 -0
  59. package/esm/metric/metricMutationPort.js +39 -0
  60. package/esm/metric/metricSchema.d.ts +29 -0
  61. package/esm/metric/metricSchema.d.ts.map +1 -0
  62. package/esm/metric/metricSchema.js +30 -0
  63. package/esm/metric/metricSerialization.d.ts +10 -0
  64. package/esm/metric/metricSerialization.d.ts.map +1 -0
  65. package/esm/metric/metricSerialization.js +33 -0
  66. package/esm/metric/metricValidation.d.ts +15 -0
  67. package/esm/metric/metricValidation.d.ts.map +1 -0
  68. package/esm/metric/metricValidation.js +47 -0
  69. package/esm/metric/metricYamlLinter.d.ts +7 -0
  70. package/esm/metric/metricYamlLinter.d.ts.map +1 -0
  71. package/esm/metric/metricYamlLinter.js +13 -0
  72. package/esm/metric/tests/metricMutationPort.test.utils.d.ts +7 -0
  73. package/esm/metric/tests/metricMutationPort.test.utils.d.ts.map +1 -0
  74. package/esm/metric/tests/metricMutationPort.test.utils.js +43 -0
  75. package/esm/parameter/ParameterDialog.d.ts.map +1 -1
  76. package/esm/parameter/ParameterDialog.js +7 -7
  77. package/esm/parameter/ParameterYamlEditor.js +1 -1
  78. package/esm/parameter/yamlLinter.d.ts +1 -4
  79. package/esm/parameter/yamlLinter.d.ts.map +1 -1
  80. package/esm/parameter/yamlLinter.js +3 -24
  81. package/esm/utils/backendError.d.ts +11 -0
  82. package/esm/utils/backendError.d.ts.map +1 -0
  83. package/esm/utils/backendError.js +21 -0
  84. package/esm/utils/yamlSyntaxLinter.d.ts +11 -0
  85. package/esm/utils/yamlSyntaxLinter.d.ts.map +1 -0
  86. package/esm/utils/yamlSyntaxLinter.js +31 -0
  87. package/package.json +15 -14
  88. package/styles/css/{parameter.css → codeDialog.css} +26 -18
  89. package/styles/css/codeDialog.css.map +1 -0
  90. package/styles/css/detail.css +25 -17
  91. package/styles/css/detail.css.map +1 -1
  92. package/styles/css/main.css +25 -17
  93. package/styles/css/main.css.map +1 -1
  94. package/styles/scss/{parameter.scss → codeDialog.scss} +17 -8
  95. package/styles/scss/detail.scss +1 -1
  96. package/styles/css/parameter.css.map +0 -1
@@ -761,6 +761,118 @@ export const en_US = {
761
761
  "text": "Parameter could not be deleted.",
762
762
  "crowdinContext": "Error toast shown when parameter deletion fails."
763
763
  },
764
+ "analyticsCatalog.metric.dialog.create.title": {
765
+ "text": "Create metric",
766
+ "crowdinContext": "Title of the dialog for creating a new metric as code."
767
+ },
768
+ "analyticsCatalog.metric.dialog.edit.title": {
769
+ "text": "Edit metric",
770
+ "crowdinContext": "Title of the dialog for editing a metric as code."
771
+ },
772
+ "analyticsCatalog.metric.dialog.sectionHeader": {
773
+ "text": "Metric definition",
774
+ "crowdinContext": "Header above the as-code editor in the metric dialog."
775
+ },
776
+ "analyticsCatalog.metric.dialog.sectionHeader.tooltip": {
777
+ "text": "Define the metric in YAML, including its MAQL expression and format.",
778
+ "crowdinContext": "Tooltip explaining the as-code metric editor."
779
+ },
780
+ "analyticsCatalog.metric.dialog.create.submit": {
781
+ "text": "Create",
782
+ "crowdinContext": "Submit button of the create metric dialog."
783
+ },
784
+ "analyticsCatalog.metric.dialog.edit.submit": {
785
+ "text": "Save",
786
+ "crowdinContext": "Submit button of the edit metric dialog."
787
+ },
788
+ "analyticsCatalog.metric.actions.openInEditor": {
789
+ "text": "Open in metric editor",
790
+ "crowdinContext": "Detail-panel menu action that opens the metric in the standalone metric editor (as opposed to the inline as-code editor)."
791
+ },
792
+ "analyticsCatalog.metric.dialog.edit.duplicate": {
793
+ "text": "Duplicate",
794
+ "crowdinContext": "Button that duplicates the metric being edited into a new metric."
795
+ },
796
+ "analyticsCatalog.metric.dialog.edit.idImmutable": {
797
+ "text": "The metric id cannot be changed. Use Duplicate to create a metric with a different id.",
798
+ "crowdinContext": "Validation error shown when the user changes the id while editing a metric."
799
+ },
800
+ "analyticsCatalog.metric.dialog.cancel": {
801
+ "text": "Cancel",
802
+ "crowdinContext": "Cancel button of the metric dialogs."
803
+ },
804
+ "analyticsCatalog.metric.dialog.help": {
805
+ "text": "How to write a metric",
806
+ "crowdinContext": "Help link in the metric as-code dialog."
807
+ },
808
+ "analyticsCatalog.metric.dialog.submit.error": {
809
+ "text": "The metric could not be saved.",
810
+ "crowdinContext": "Generic error shown when saving a metric fails."
811
+ },
812
+ "analyticsCatalog.metric.validation.empty": {
813
+ "text": "The metric definition cannot be empty.",
814
+ "crowdinContext": "Validation error shown when the metric editor is empty."
815
+ },
816
+ "analyticsCatalog.metric.validation.syntax": {
817
+ "text": "The metric definition is not valid YAML.",
818
+ "crowdinContext": "Validation error shown when the metric YAML has a syntax error."
819
+ },
820
+ "analyticsCatalog.metric.validation.invalidStructure": {
821
+ "text": "The metric definition has an invalid structure.",
822
+ "crowdinContext": "Validation error shown when the metric YAML does not match the expected structure."
823
+ },
824
+ "analyticsCatalog.metric.validation.missingMaql": {
825
+ "text": "The metric must define a MAQL expression.",
826
+ "crowdinContext": "Validation error shown when the metric YAML is missing the maql field."
827
+ },
828
+ "analyticsCatalog.metric.validation.invalidTags": {
829
+ "text": "Tags must be a list of text values.",
830
+ "crowdinContext": "Validation error shown when the metric tags are not a list of strings."
831
+ },
832
+ "analyticsCatalog.dialog.error.label": {
833
+ "text": "Error:",
834
+ "crowdinContext": "Bold label prefixing the error message in the as-code editor dialogs (metrics, parameters)."
835
+ },
836
+ "analyticsCatalog.metric.create.defaultTitle": {
837
+ "text": "My metric",
838
+ "crowdinContext": "Default placeholder title seeded into the new-metric as-code editor before the user types their own."
839
+ },
840
+ "analyticsCatalog.metric.create.success": {
841
+ "text": "Metric created.",
842
+ "crowdinContext": "Toast shown after a metric is created successfully from the Analytics Catalog."
843
+ },
844
+ "analyticsCatalog.metric.update.success": {
845
+ "text": "Metric saved.",
846
+ "crowdinContext": "Toast shown after a metric is updated successfully from the Analytics Catalog."
847
+ },
848
+ "analyticsCatalog.metric.load.error": {
849
+ "text": "The metric could not be loaded.",
850
+ "crowdinContext": "Error toast shown when loading a metric for editing or duplication fails."
851
+ },
852
+ "analyticsCatalog.metric.dialog.delete.title": {
853
+ "text": "Delete metric",
854
+ "crowdinContext": "Title of the confirmation dialog for deleting a metric."
855
+ },
856
+ "analyticsCatalog.metric.dialog.delete.body": {
857
+ "text": "Are you sure you want to delete <b>{name}</b>?",
858
+ "crowdinContext": "Body of the confirmation dialog for deleting a metric. {name} is the metric title."
859
+ },
860
+ "analyticsCatalog.metric.dialog.delete.usageWarning": {
861
+ "text": "This metric is used by {count, plural, one {# object} other {# objects}}. Deleting it may break them.",
862
+ "crowdinContext": "Warning shown in the metric delete dialog when the metric is referenced by other objects."
863
+ },
864
+ "analyticsCatalog.metric.dialog.delete.submit": {
865
+ "text": "Delete",
866
+ "crowdinContext": "Submit button of the delete metric confirmation dialog."
867
+ },
868
+ "analyticsCatalog.metric.delete.success": {
869
+ "text": "Metric deleted.",
870
+ "crowdinContext": "Toast shown after a metric is deleted successfully from the Analytics Catalog."
871
+ },
872
+ "analyticsCatalog.metric.delete.error": {
873
+ "text": "Metric could not be deleted.",
874
+ "crowdinContext": "Error toast shown when metric deletion fails."
875
+ },
764
876
  "analyticsCatalog.share.button": {
765
877
  "text": "Share",
766
878
  "crowdinContext": "Button label opening the share dialog from the catalog detail page."
@@ -1 +1 @@
1
- {"version":3,"file":"Main.d.ts","sourceRoot":"","sources":["../../src/main/Main.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,OAAO,CAAC;AAIxC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAIpE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAElE,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAelF,KAAK,KAAK,GAAG;IACT,OAAO,EAAE,kBAAkB,CAAC;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,OAAO,CAAC;IACd,UAAU,EAAE,eAAe,GAAG,YAAY,GAAG,IAAI,CAAC;IAClD,aAAa,EAAE,CAAC,IAAI,EAAE,eAAe,GAAG,YAAY,GAAG,IAAI,KAAK,IAAI,CAAC;IACrE,YAAY,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,IAAI,CAAC;IAC3C,aAAa,EAAE,MAAM,IAAI,CAAC;IAC1B,WAAW,EAAE,CAAC,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAC3E,uBAAuB,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,EAAE,GAAG,EAAE,eAAe,KAAK,IAAI,CAAC;CAC/E,CAAC;AAQF,wBAAgB,IAAI,CAAC,EACjB,OAAO,EACP,SAAS,EACT,IAAI,EACJ,UAAU,EACV,aAAa,EACb,YAAY,EACZ,aAAa,EACb,WAAW,EACX,uBAAuB,EAC1B,EAAE,KAAK,2CAmEP"}
1
+ {"version":3,"file":"Main.d.ts","sourceRoot":"","sources":["../../src/main/Main.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,OAAO,CAAC;AAIxC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAIpE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAElE,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAclF,KAAK,KAAK,GAAG;IACT,OAAO,EAAE,kBAAkB,CAAC;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,OAAO,CAAC;IACd,UAAU,EAAE,eAAe,GAAG,YAAY,GAAG,IAAI,CAAC;IAClD,aAAa,EAAE,CAAC,IAAI,EAAE,eAAe,GAAG,YAAY,GAAG,IAAI,KAAK,IAAI,CAAC;IACrE,YAAY,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,IAAI,CAAC;IAC3C,aAAa,EAAE,MAAM,IAAI,CAAC;IAC1B,WAAW,EAAE,CAAC,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAC3E,uBAAuB,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,EAAE,GAAG,EAAE,eAAe,KAAK,IAAI,CAAC;CAC/E,CAAC;AAQF,wBAAgB,IAAI,CAAC,EACjB,OAAO,EACP,SAAS,EACT,IAAI,EACJ,UAAU,EACV,aAAa,EACb,YAAY,EACZ,aAAa,EACb,WAAW,EACX,uBAAuB,EAC1B,EAAE,KAAK,2CAmEP"}
package/esm/main/Main.js CHANGED
@@ -16,7 +16,6 @@ import { FilterQualityMemo } from "../filter/FilterQuality.js";
16
16
  import { FilterResetButtonMemo } from "../filter/FilterResetButton.js";
17
17
  import { FilterTagsMemo } from "../filter/FilterTags.js";
18
18
  import { FilterVisibilityMemo } from "../filter/FilterVisibility.js";
19
- import { ObjectTypes } from "../objectType/constants.js";
20
19
  import { useIsCatalogQualityEnabled } from "../quality/gate.js";
21
20
  import { Table } from "../table/Table.js";
22
21
  const messages = defineMessages({
@@ -41,13 +40,13 @@ export function Main({ backend, workspace, open, openedItem, setItemOpened, onOp
41
40
  onCatalogItemNavigation?.(event, ref);
42
41
  }, onCatalogItemCreate: (item) => {
43
42
  setItemOpened(item);
44
- refetchObjectType(ObjectTypes.PARAMETER);
43
+ refetchObjectType(item.type);
45
44
  }, onCatalogItemUpdate: (item) => {
46
45
  setItemOpened(item);
47
46
  updateItem(item);
48
47
  }, onCatalogItemDelete: (ref) => {
49
48
  removeItem(ref);
50
- refetchObjectType(ObjectTypes.PARAMETER);
49
+ refetchObjectType(ref.type);
51
50
  onCloseDetail();
52
51
  }, onCatalogItemUpdateError: (err) => {
53
52
  addError(messages.updateFailed, {
@@ -0,0 +1,11 @@
1
+ import type { IMeasureMetadataObjectDefinition } from "@gooddata/sdk-model";
2
+ import type { ICatalogItemMeasure } from "../catalogItem/types.js";
3
+ type Props = {
4
+ sourceDefinition?: IMeasureMetadataObjectDefinition;
5
+ isLoading?: boolean;
6
+ onClose: () => void;
7
+ onCreated?: (item: ICatalogItemMeasure) => void;
8
+ };
9
+ export declare function MetricCreateDialog({ sourceDefinition, isLoading, onClose, onCreated }: Props): import("react/jsx-runtime").JSX.Element;
10
+ export {};
11
+ //# sourceMappingURL=MetricCreateDialog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MetricCreateDialog.d.ts","sourceRoot":"","sources":["../../src/metric/MetricCreateDialog.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,gCAAgC,EAAE,MAAM,qBAAqB,CAAC;AAG5E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAYnE,KAAK,KAAK,GAAG;IACT,gBAAgB,CAAC,EAAE,gCAAgC,CAAC;IACpD,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,mBAAmB,KAAK,IAAI,CAAC;CACnD,CAAC;AAEF,wBAAgB,kBAAkB,CAAC,EAAE,gBAAgB,EAAE,SAAiB,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,KAAK,2CA0DpG"}
@@ -0,0 +1,55 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ // (C) 2026 GoodData Corporation
3
+ import { useCallback, useMemo } from "react";
4
+ import { defineMessages, useIntl } from "react-intl";
5
+ import { useToastMessage } from "@gooddata/sdk-ui-kit";
6
+ import { definitionToMetricYaml, mergeCopiedMetricDefinition } from "./metricConverter.js";
7
+ import { createCopiedMetric, isDuplicateIdError } from "./metricCopy.js";
8
+ import { MetricDialog } from "./MetricDialog.js";
9
+ import { useMetricMutation } from "./MetricMutationContext.js";
10
+ const messages = defineMessages({
11
+ metricCreateSuccess: { id: "analyticsCatalog.metric.create.success" },
12
+ metricCreateDefaultTitle: { id: "analyticsCatalog.metric.create.defaultTitle" },
13
+ });
14
+ export function MetricCreateDialog({ sourceDefinition, isLoading = false, onClose, onCreated }) {
15
+ const intl = useIntl();
16
+ const { addSuccess } = useToastMessage();
17
+ const mutation = useMetricMutation();
18
+ // The single source of truth for a duplicate's copy semantics (title bump, id derivation, and
19
+ // which author-owned fields carry over). Undefined for a blank create.
20
+ const copiedMetric = useMemo(() => (sourceDefinition ? createCopiedMetric(sourceDefinition) : undefined), [sourceDefinition]);
21
+ const initialMetric = useMemo(() => {
22
+ if (!copiedMetric) {
23
+ return {
24
+ type: "metric",
25
+ title: intl.formatMessage(messages.metricCreateDefaultTitle),
26
+ maql: "SELECT 1",
27
+ };
28
+ }
29
+ return definitionToMetricYaml(copiedMetric);
30
+ }, [copiedMetric, intl]);
31
+ const copiedId = copiedMetric ? initialMetric.id : undefined;
32
+ const handleSubmit = useCallback(async (metric) => {
33
+ // A duplicate layers the author's YAML edits over the copied source so its non-YAML
34
+ // fields (metricType, isHiddenFromKda) survive the lossy YAML round-trip; a blank create
35
+ // has no source and persists the parsed YAML as-is.
36
+ const definition = copiedMetric ? mergeCopiedMetricDefinition(copiedMetric, metric) : metric;
37
+ let createdMetric;
38
+ try {
39
+ createdMetric = await mutation.create(definition);
40
+ }
41
+ catch (error) {
42
+ if (copiedId !== undefined && definition.id === copiedId && isDuplicateIdError(error)) {
43
+ const { id: _id, ...metricWithoutId } = definition;
44
+ createdMetric = await mutation.create(metricWithoutId);
45
+ }
46
+ else {
47
+ throw error;
48
+ }
49
+ }
50
+ onCreated?.(createdMetric);
51
+ onClose();
52
+ addSuccess(messages.metricCreateSuccess);
53
+ }, [addSuccess, copiedId, copiedMetric, mutation, onClose, onCreated]);
54
+ return (_jsx(MetricDialog, { mode: "create", isLoading: isLoading, initialMetric: initialMetric, onClose: onClose, onSubmit: handleSubmit }));
55
+ }
@@ -0,0 +1,9 @@
1
+ import { type ICatalogItemMeasure } from "../catalogItem/types.js";
2
+ type Props = {
3
+ item: ICatalogItemMeasure;
4
+ onClose: () => void;
5
+ onDeleted: () => void;
6
+ };
7
+ export declare function MetricDeleteDialog({ item, onClose, onDeleted }: Props): import("react/jsx-runtime").JSX.Element;
8
+ export {};
9
+ //# sourceMappingURL=MetricDeleteDialog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MetricDeleteDialog.d.ts","sourceRoot":"","sources":["../../src/metric/MetricDeleteDialog.tsx"],"names":[],"mappings":"AAQA,OAAO,EAAE,KAAK,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAcnE,KAAK,KAAK,GAAG;IACT,IAAI,EAAE,mBAAmB,CAAC;IAC1B,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,SAAS,EAAE,MAAM,IAAI,CAAC;CACzB,CAAC;AAEF,wBAAgB,kBAAkB,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,KAAK,2CA+ErE"}
@@ -0,0 +1,68 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // (C) 2026 GoodData Corporation
3
+ import { useCallback, useEffect, useState } from "react";
4
+ import { FormattedMessage, defineMessages, useIntl } from "react-intl";
5
+ import { ConfirmDialog, useToastMessage } from "@gooddata/sdk-ui-kit";
6
+ import {} from "../catalogItem/types.js";
7
+ import { useMetricMutation } from "./MetricMutationContext.js";
8
+ const messages = defineMessages({
9
+ title: { id: "analyticsCatalog.metric.dialog.delete.title" },
10
+ body: { id: "analyticsCatalog.metric.dialog.delete.body" },
11
+ usageWarning: { id: "analyticsCatalog.metric.dialog.delete.usageWarning" },
12
+ submit: { id: "analyticsCatalog.metric.dialog.delete.submit" },
13
+ cancel: { id: "analyticsCatalog.metric.dialog.cancel" },
14
+ deleteSuccess: { id: "analyticsCatalog.metric.delete.success" },
15
+ deleteError: { id: "analyticsCatalog.metric.delete.error" },
16
+ });
17
+ export function MetricDeleteDialog({ item, onClose, onDeleted }) {
18
+ const intl = useIntl();
19
+ const { addSuccess, addError } = useToastMessage();
20
+ const mutation = useMetricMutation();
21
+ const [isDeleting, setIsDeleting] = useState(false);
22
+ // undefined until the usage lookup resolves; deletion is withheld until then so the dependent-object
23
+ // warning can surface before the user can confirm.
24
+ const [referencingCount, setReferencingCount] = useState(undefined);
25
+ const displayName = item.title || item.identifier;
26
+ useEffect(() => {
27
+ let cancelled = false;
28
+ mutation
29
+ .getReferencingObjects(item)
30
+ .then((referencing) => {
31
+ if (!cancelled) {
32
+ const count = (referencing.insights?.length ?? 0) + (referencing.measures?.length ?? 0);
33
+ setReferencingCount(count);
34
+ }
35
+ })
36
+ .catch(() => {
37
+ // Usage lookup is advisory; on failure fall back to no warning rather than block deletion.
38
+ if (!cancelled) {
39
+ setReferencingCount(0);
40
+ }
41
+ });
42
+ return () => {
43
+ cancelled = true;
44
+ };
45
+ }, [item, mutation]);
46
+ const handleDelete = useCallback(async () => {
47
+ setIsDeleting(true);
48
+ try {
49
+ await mutation.delete(item);
50
+ onDeleted();
51
+ onClose();
52
+ addSuccess(messages.deleteSuccess);
53
+ }
54
+ catch {
55
+ addError(messages.deleteError);
56
+ setIsDeleting(false);
57
+ }
58
+ }, [addError, addSuccess, item, mutation, onClose, onDeleted]);
59
+ const handleClose = useCallback(() => {
60
+ if (!isDeleting) {
61
+ onClose();
62
+ }
63
+ }, [isDeleting, onClose]);
64
+ return (_jsxs(ConfirmDialog, { headline: intl.formatMessage(messages.title), cancelButtonText: intl.formatMessage(messages.cancel), submitButtonText: intl.formatMessage(messages.submit), isPositive: false, isSubmitDisabled: isDeleting || referencingCount === undefined, isCancelDisabled: isDeleting, showProgressIndicator: isDeleting, onCancel: handleClose, onClose: handleClose, onSubmit: handleDelete, displayCloseButton: !isDeleting, children: [_jsx(FormattedMessage, { ...messages.body, values: {
65
+ name: displayName,
66
+ b: (chunks) => _jsx("b", { children: chunks }),
67
+ } }), referencingCount ? (_jsx("div", { children: _jsx(FormattedMessage, { ...messages.usageWarning, values: { count: referencingCount } }) })) : null] }));
68
+ }
@@ -0,0 +1,23 @@
1
+ import { type MouseEvent } from "react";
2
+ import type { OpenHandlerEvent } from "../catalogDetail/types.js";
3
+ import type { ICatalogItemMeasure, ICatalogItemRef } from "../catalogItem/types.js";
4
+ /**
5
+ * @internal
6
+ */
7
+ export interface IMetricDetailActionsProps {
8
+ item: ICatalogItemMeasure;
9
+ /** When provided, adds an "Open in metric editor" menu action that opens the standalone editor. */
10
+ onOpen?: (event: MouseEvent, openEvent: OpenHandlerEvent) => void;
11
+ canShare?: boolean;
12
+ onShare?: () => void;
13
+ onCatalogItemCreate?: (item: ICatalogItemMeasure) => void;
14
+ onCatalogItemUpdate?: (item: ICatalogItemMeasure) => void;
15
+ onCatalogItemDelete?: (ref: ICatalogItemRef) => void;
16
+ }
17
+ /**
18
+ * Detail-panel actions for a metric: inline edit, duplicate, and delete via the as-code editor,
19
+ * alongside the shared Share button and "Open in metric editor" action.
20
+ * @internal
21
+ */
22
+ export declare function MetricDetailActions({ item, onOpen, canShare, onShare, onCatalogItemCreate, onCatalogItemUpdate, onCatalogItemDelete }: IMetricDetailActionsProps): import("react/jsx-runtime").JSX.Element;
23
+ //# sourceMappingURL=MetricDetailActions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MetricDetailActions.d.ts","sourceRoot":"","sources":["../../src/metric/MetricDetailActions.tsx"],"names":[],"mappings":"AAEA,OAAO,EAEH,KAAK,UAAU,EAMlB,MAAM,OAAO,CAAC;AAWf,OAAO,KAAK,EAAwB,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AACxF,OAAO,KAAK,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAqBpF;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACtC,IAAI,EAAE,mBAAmB,CAAC;IAC1B,mGAAmG;IACnG,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAClE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,mBAAmB,CAAC,EAAE,CAAC,IAAI,EAAE,mBAAmB,KAAK,IAAI,CAAC;IAC1D,mBAAmB,CAAC,EAAE,CAAC,IAAI,EAAE,mBAAmB,KAAK,IAAI,CAAC;IAC1D,mBAAmB,CAAC,EAAE,CAAC,GAAG,EAAE,eAAe,KAAK,IAAI,CAAC;CACxD;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,EAChC,IAAI,EACJ,MAAM,EACN,QAAQ,EACR,OAAO,EACP,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,EACtB,EAAE,yBAAyB,2CA6H3B"}
@@ -0,0 +1,101 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // (C) 2026 GoodData Corporation
3
+ import { useCallback, useEffect, useMemo, useRef, useState, } from "react";
4
+ import { defineMessages, useIntl } from "react-intl";
5
+ import { useWorkspaceStrict } from "@gooddata/sdk-ui";
6
+ import { useToastMessage } from "@gooddata/sdk-ui-kit";
7
+ import { catalogDetailActionDelete, catalogDetailActionDuplicate } from "../automation/testIds.js";
8
+ import { CatalogDetailActionBar } from "../catalogDetail/CatalogDetailActionBar.js";
9
+ import { ShareButton } from "../catalogDetail/share/ShareButton.js";
10
+ import { MetricCreateDialog } from "./MetricCreateDialog.js";
11
+ import { MetricDeleteDialog } from "./MetricDeleteDialog.js";
12
+ import { MetricEditDialog } from "./MetricEditDialog.js";
13
+ import { useMetricMutation } from "./MetricMutationContext.js";
14
+ const messages = defineMessages({
15
+ openInEditor: { id: "analyticsCatalog.metric.actions.openInEditor" },
16
+ duplicate: { id: "analyticsCatalog.metric.dialog.edit.duplicate" },
17
+ delete: { id: "analyticsCatalog.metric.dialog.delete.submit" },
18
+ loadError: { id: "analyticsCatalog.metric.load.error" },
19
+ });
20
+ /**
21
+ * Detail-panel actions for a metric: inline edit, duplicate, and delete via the as-code editor,
22
+ * alongside the shared Share button and "Open in metric editor" action.
23
+ * @internal
24
+ */
25
+ export function MetricDetailActions({ item, onOpen, canShare, onShare, onCatalogItemCreate, onCatalogItemUpdate, onCatalogItemDelete, }) {
26
+ const intl = useIntl();
27
+ const workspaceId = useWorkspaceStrict();
28
+ const { addError } = useToastMessage();
29
+ const mutation = useMetricMutation();
30
+ const [dialog, setDialog] = useState(undefined);
31
+ const isMounted = useRef(true);
32
+ useEffect(() => {
33
+ // Reset on (re)mount: StrictMode runs mount -> cleanup -> remount, and without this the ref
34
+ // would stay false after the remount even though the component is mounted.
35
+ isMounted.current = true;
36
+ return () => {
37
+ isMounted.current = false;
38
+ };
39
+ }, []);
40
+ const actions = useMemo(() => {
41
+ const result = [];
42
+ // The standalone metric editor stays reachable alongside the inline Edit; omitted when the
43
+ // host does not provide an open handler, so the menu never shows a dead item.
44
+ if (onOpen) {
45
+ result.push({ id: "open", label: intl.formatMessage(messages.openInEditor) });
46
+ }
47
+ result.push({
48
+ id: "duplicate",
49
+ label: intl.formatMessage(messages.duplicate),
50
+ dataTestId: catalogDetailActionDuplicate,
51
+ }, {
52
+ id: "delete",
53
+ label: intl.formatMessage(messages.delete),
54
+ isDestructive: true,
55
+ dataTestId: catalogDetailActionDelete,
56
+ });
57
+ return result;
58
+ }, [intl, onOpen]);
59
+ const closeDialog = useCallback(() => setDialog(undefined), []);
60
+ const handleEditOpen = useCallback(() => setDialog({ kind: "edit", item }), [item]);
61
+ const handleActionsMenuSelect = useCallback(async (actionId, event) => {
62
+ if (actionId === "open") {
63
+ onOpen?.(event, {
64
+ item,
65
+ workspaceId,
66
+ newTab: event.metaKey || event.ctrlKey,
67
+ preventDefault: event.preventDefault.bind(event),
68
+ });
69
+ return;
70
+ }
71
+ if (actionId === "delete") {
72
+ setDialog({ kind: "delete", item });
73
+ return;
74
+ }
75
+ if (actionId === "duplicate") {
76
+ // The catalog item carries no MAQL, so show the create dialog in its loading state
77
+ // and feed it the copied definition once the full metric is fetched. Only advance
78
+ // while still in this loading state, so closing the dialog or opening Edit/Delete
79
+ // meanwhile cancels the duplicate.
80
+ setDialog({ kind: "duplicate", isLoading: true });
81
+ try {
82
+ const measure = await mutation.load(item);
83
+ if (isMounted.current) {
84
+ setDialog((current) => current?.kind === "duplicate" && current.isLoading
85
+ ? { kind: "duplicate", sourceDefinition: measure, isLoading: false }
86
+ : current);
87
+ }
88
+ }
89
+ catch {
90
+ if (isMounted.current) {
91
+ addError(messages.loadError);
92
+ setDialog((current) => current?.kind === "duplicate" && current.isLoading ? undefined : current);
93
+ }
94
+ }
95
+ }
96
+ }, [addError, item, mutation, onOpen, workspaceId]);
97
+ const handleEditDuplicate = useCallback((sourceDefinition) => {
98
+ setDialog({ kind: "duplicate", sourceDefinition, isLoading: false });
99
+ }, []);
100
+ return (_jsxs(_Fragment, { children: [_jsx(CatalogDetailActionBar, { item: item, workspaceId: workspaceId, actions: actions, leadingActions: canShare && onShare ? _jsx(ShareButton, { onClick: onShare }) : null, onEditClick: handleEditOpen, onActionsMenuSelect: handleActionsMenuSelect }), dialog?.kind === "edit" ? (_jsx(MetricEditDialog, { item: dialog.item, onClose: closeDialog, onSaved: (saved) => onCatalogItemUpdate?.(saved), onDuplicate: handleEditDuplicate })) : null, dialog?.kind === "duplicate" ? (_jsx(MetricCreateDialog, { sourceDefinition: dialog.sourceDefinition, isLoading: dialog.isLoading, onClose: closeDialog, onCreated: (created) => onCatalogItemCreate?.(created) })) : null, dialog?.kind === "delete" ? (_jsx(MetricDeleteDialog, { item: dialog.item, onClose: closeDialog, onDeleted: () => onCatalogItemDelete?.(dialog.item) })) : null] }));
101
+ }
@@ -0,0 +1,14 @@
1
+ import type { IMeasureMetadataObjectDefinition } from "@gooddata/sdk-model";
2
+ import type { MetricYaml } from "./metricConverter.js";
3
+ export type MetricDialogMode = "create" | "edit";
4
+ type Props = {
5
+ mode: MetricDialogMode;
6
+ initialMetric?: MetricYaml;
7
+ isLoading?: boolean;
8
+ onClose: () => void;
9
+ onSubmit: (measure: IMeasureMetadataObjectDefinition) => Promise<void>;
10
+ onDuplicate?: (measure: IMeasureMetadataObjectDefinition) => void;
11
+ };
12
+ export declare function MetricDialog(props: Props): import("react/jsx-runtime").JSX.Element;
13
+ export {};
14
+ //# sourceMappingURL=MetricDialog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MetricDialog.d.ts","sourceRoot":"","sources":["../../src/metric/MetricDialog.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,gCAAgC,EAAE,MAAM,qBAAqB,CAAC;AAO5E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AA0BvD,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,MAAM,CAAC;AAEjD,KAAK,KAAK,GAAG;IACT,IAAI,EAAE,gBAAgB,CAAC;IAGvB,aAAa,CAAC,EAAE,UAAU,CAAC;IAC3B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,EAAE,CAAC,OAAO,EAAE,gCAAgC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACvE,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,gCAAgC,KAAK,IAAI,CAAC;CACrE,CAAC;AAEF,wBAAgB,YAAY,CAAC,KAAK,EAAE,KAAK,2CA8KxC"}
@@ -0,0 +1,100 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // (C) 2026 GoodData Corporation
3
+ import { Suspense, lazy, useCallback, useMemo, useRef, useState } from "react";
4
+ import { FormattedMessage, defineMessages, useIntl } from "react-intl";
5
+ import { LoadingComponent } from "@gooddata/sdk-ui";
6
+ import { ConfirmDialog, UiButton, UiIcon, UiLink, UiTooltip } from "@gooddata/sdk-ui-kit";
7
+ import { useIsWhiteLabeled } from "../permission/PermissionsContext.js";
8
+ import { extractBackendErrorDetail } from "../utils/backendError.js";
9
+ import { serializeMetricToYaml } from "./metricSerialization.js";
10
+ import { validateMetricYaml } from "./metricValidation.js";
11
+ const MetricYamlEditor = lazy(() => import("./MetricYamlEditor.js").then((m) => ({ default: m.MetricYamlEditor })));
12
+ const METRIC_DOCS_URL = "https://www.gooddata.ai/docs/cloud/api-and-sdk/vs-code-extension/structures/#metric";
13
+ const messages = defineMessages({
14
+ empty: { id: "analyticsCatalog.metric.validation.empty" },
15
+ syntax: { id: "analyticsCatalog.metric.validation.syntax" },
16
+ invalidStructure: { id: "analyticsCatalog.metric.validation.invalidStructure" },
17
+ idImmutable: { id: "analyticsCatalog.metric.dialog.edit.idImmutable" },
18
+ missingMaql: { id: "analyticsCatalog.metric.validation.missingMaql" },
19
+ invalidTags: { id: "analyticsCatalog.metric.validation.invalidTags" },
20
+ dialogSubmitError: { id: "analyticsCatalog.metric.dialog.submit.error" },
21
+ dialogCreateTitle: { id: "analyticsCatalog.metric.dialog.create.title" },
22
+ dialogEditTitle: { id: "analyticsCatalog.metric.dialog.edit.title" },
23
+ dialogCreateSubmit: { id: "analyticsCatalog.metric.dialog.create.submit" },
24
+ dialogEditSubmit: { id: "analyticsCatalog.metric.dialog.edit.submit" },
25
+ dialogCancel: { id: "analyticsCatalog.metric.dialog.cancel" },
26
+ dialogDuplicate: { id: "analyticsCatalog.metric.dialog.edit.duplicate" },
27
+ });
28
+ export function MetricDialog(props) {
29
+ const { mode, initialMetric, isLoading = false, onClose, onSubmit, onDuplicate } = props;
30
+ const intl = useIntl();
31
+ const isEdit = mode === "edit";
32
+ const isWhiteLabeled = useIsWhiteLabeled();
33
+ const initialYaml = useMemo(() => (initialMetric ? serializeMetricToYaml(initialMetric) : ""), [initialMetric]);
34
+ // The user's edits; `null` until they type. Submit falls back to `initialYaml`, so the editor
35
+ // mounts once with real data and is the single source of truth — no re-seed effect needed.
36
+ const yamlValue = useRef(null);
37
+ const [validationError, setValidationError] = useState(null);
38
+ const [submitError, setSubmitError] = useState(null);
39
+ const [isSubmitting, setIsSubmitting] = useState(false);
40
+ const [isDirty, setIsDirty] = useState(false);
41
+ const headlineMessage = intl.formatMessage(isEdit ? messages.dialogEditTitle : messages.dialogCreateTitle);
42
+ const submitMessage = intl.formatMessage(isEdit ? messages.dialogEditSubmit : messages.dialogCreateSubmit);
43
+ const cancelMessage = intl.formatMessage(messages.dialogCancel);
44
+ const fixedIdentifier = isEdit ? initialMetric?.id : undefined;
45
+ const validate = useCallback((value, allowIdChange = false) => {
46
+ return validateMetricYaml(value, {
47
+ fixedIdentifier: isEdit && !allowIdChange ? fixedIdentifier : undefined,
48
+ });
49
+ }, [fixedIdentifier, isEdit]);
50
+ const handleClose = useCallback(() => {
51
+ if (!isSubmitting) {
52
+ onClose();
53
+ }
54
+ }, [isSubmitting, onClose]);
55
+ const handleChange = useCallback((nextValue) => {
56
+ yamlValue.current = nextValue;
57
+ setIsDirty(true);
58
+ setSubmitError(null);
59
+ setValidationError(null);
60
+ }, []);
61
+ const handleSubmit = useCallback(async () => {
62
+ const result = validate(yamlValue.current ?? initialYaml);
63
+ if (!result.isValid) {
64
+ setValidationError(intl.formatMessage(messages[result.errorCode]));
65
+ return;
66
+ }
67
+ setIsSubmitting(true);
68
+ setSubmitError(null);
69
+ try {
70
+ await onSubmit(result.measure);
71
+ }
72
+ catch (error) {
73
+ setSubmitError(extractBackendErrorDetail(error) ?? intl.formatMessage(messages.dialogSubmitError));
74
+ }
75
+ finally {
76
+ setIsSubmitting(false);
77
+ }
78
+ }, [initialYaml, intl, onSubmit, validate]);
79
+ const handleDuplicate = useCallback(() => {
80
+ if (!onDuplicate) {
81
+ return;
82
+ }
83
+ setSubmitError(null);
84
+ const result = validate(yamlValue.current ?? initialYaml, true);
85
+ if (!result.isValid) {
86
+ setValidationError(intl.formatMessage(messages[result.errorCode]));
87
+ return;
88
+ }
89
+ onDuplicate(result.measure);
90
+ }, [initialYaml, intl, onDuplicate, validate]);
91
+ const footerLeftRenderer = useCallback(() => (_jsxs("div", { className: "gd-ascode-dialog-footer-left", children: [isWhiteLabeled ? null : (_jsxs(UiLink, { variant: "secondary", href: METRIC_DOCS_URL, target: "_blank", rel: "noopener noreferrer", children: [
92
+ _jsx(UiIcon, { type: "question", size: 14, color: "complementary-6" }), _jsx(FormattedMessage, { id: "analyticsCatalog.metric.dialog.help" })
93
+ ] })), isEdit && onDuplicate ? (_jsx(UiButton, { label: intl.formatMessage(messages.dialogDuplicate), variant: "tertiary", isDisabled: isSubmitting || isLoading, onClick: handleDuplicate })) : null] })), [handleDuplicate, intl, isEdit, isLoading, isSubmitting, isWhiteLabeled, onDuplicate]);
94
+ const bodySpinner = (_jsx("div", { className: "gd-ascode-dialog-loading", children: _jsx(LoadingComponent, {}) }));
95
+ return (_jsx(ConfirmDialog, { className: "gd-ascode-dialog", containerClassName: "gd-ascode-dialog-overlay", headline: headlineMessage, cancelButtonText: cancelMessage, submitButtonText: submitMessage, isPositive: true, isSubmitDisabled: isSubmitting || isLoading || (isEdit && !isDirty), isCancelDisabled: isSubmitting, shouldCloseOnEscape: !isSubmitting, onCancel: handleClose, onClose: handleClose, onSubmit: handleSubmit, displayCloseButton: !isSubmitting, footerLeftRenderer: footerLeftRenderer, children: _jsx("div", { className: "gd-ascode-dialog-content", children: _jsx(Suspense, { fallback: bodySpinner, children: isLoading ? (bodySpinner) : (_jsxs(_Fragment, { children: [
96
+ _jsxs("div", { className: "gd-ascode-dialog-section-header", children: [
97
+ _jsx(FormattedMessage, { id: "analyticsCatalog.metric.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.metric.dialog.sectionHeader.tooltip" }) })
98
+ ] }), _jsx("div", { className: "gd-ascode-dialog-editor", children: _jsx(MetricYamlEditor, { initialValue: initialYaml, onChange: handleChange, disabled: isSubmitting }) }), validationError || submitError ? (_jsxs("div", { className: "gd-ascode-dialog-error", children: [
99
+ _jsx("span", { className: "gd-ascode-dialog-error-label", children: _jsx(FormattedMessage, { id: "analyticsCatalog.dialog.error.label" }) }), " ", validationError ?? submitError] })) : null] })) }) }) }));
100
+ }
@@ -0,0 +1,11 @@
1
+ import type { IMeasureMetadataObjectDefinition } from "@gooddata/sdk-model";
2
+ import { type ICatalogItemMeasure } from "../catalogItem/types.js";
3
+ type Props = {
4
+ item: ICatalogItemMeasure;
5
+ onClose: () => void;
6
+ onSaved: (item: ICatalogItemMeasure) => void;
7
+ onDuplicate?: (metric: IMeasureMetadataObjectDefinition) => void;
8
+ };
9
+ export declare function MetricEditDialog({ item, onClose, onSaved, onDuplicate }: Props): import("react/jsx-runtime").JSX.Element;
10
+ export {};
11
+ //# sourceMappingURL=MetricEditDialog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MetricEditDialog.d.ts","sourceRoot":"","sources":["../../src/metric/MetricEditDialog.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAA0B,gCAAgC,EAAE,MAAM,qBAAqB,CAAC;AAIpG,OAAO,EAAE,KAAK,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAWnE,KAAK,KAAK,GAAG;IACT,IAAI,EAAE,mBAAmB,CAAC;IAC1B,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,OAAO,EAAE,CAAC,IAAI,EAAE,mBAAmB,KAAK,IAAI,CAAC;IAC7C,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,gCAAgC,KAAK,IAAI,CAAC;CACpE,CAAC;AAEF,wBAAgB,gBAAgB,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,EAAE,KAAK,2CA8D9E"}
@@ -0,0 +1,51 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ // (C) 2026 GoodData Corporation
3
+ import { useCallback, useMemo } from "react";
4
+ import { defineMessages } from "react-intl";
5
+ import { useCancelablePromise } from "@gooddata/sdk-ui";
6
+ import { useToastMessage } from "@gooddata/sdk-ui-kit";
7
+ import {} from "../catalogItem/types.js";
8
+ import { definitionToMetricYaml, overlayMetricYamlFields } from "./metricConverter.js";
9
+ import { MetricDialog } from "./MetricDialog.js";
10
+ import { useMetricMutation } from "./MetricMutationContext.js";
11
+ const messages = defineMessages({
12
+ metricUpdateSuccess: { id: "analyticsCatalog.metric.update.success" },
13
+ metricLoadError: { id: "analyticsCatalog.metric.load.error" },
14
+ });
15
+ export function MetricEditDialog({ item, onClose, onSaved, onDuplicate }) {
16
+ const { addSuccess, addError } = useToastMessage();
17
+ const mutation = useMetricMutation();
18
+ // The catalog item carries only metadata; the full metric (incl. MAQL and fields the YAML does
19
+ // not carry, such as metricType) is fetched on open and reused as the base for the update. The
20
+ // fetch is re-run only when the item or backend port changes, and a stale in-flight load is
21
+ // discarded when they do.
22
+ const { result: loadedMetric } = useCancelablePromise({
23
+ promise: () => mutation.load(item),
24
+ onError: () => {
25
+ addError(messages.metricLoadError);
26
+ onClose();
27
+ },
28
+ }, [item, mutation]);
29
+ const handleSubmit = useCallback(async (definition) => {
30
+ if (!loadedMetric) {
31
+ return;
32
+ }
33
+ const savedMetric = await mutation.update(loadedMetric, definition);
34
+ onSaved(savedMetric);
35
+ onClose();
36
+ addSuccess(messages.metricUpdateSuccess);
37
+ }, [addSuccess, loadedMetric, mutation, onClose, onSaved]);
38
+ // The duplicate is seeded from the loaded metric overlaid with the current YAML edits, so the
39
+ // copy inherits fields the YAML cannot express (metricType, isHiddenFromKda) that the parsed
40
+ // editor content alone would not carry.
41
+ const handleDuplicate = useCallback((edited) => {
42
+ if (!loadedMetric || !onDuplicate) {
43
+ return;
44
+ }
45
+ onDuplicate(overlayMetricYamlFields(loadedMetric, edited));
46
+ }, [loadedMetric, onDuplicate]);
47
+ // Undefined until the fetch completes: the eager dialog frame shows an in-frame spinner via
48
+ // isLoading and mounts the editor (seeded with the real metric) only once this is present.
49
+ const initialMetric = useMemo(() => (loadedMetric ? definitionToMetricYaml(loadedMetric) : undefined), [loadedMetric]);
50
+ return (_jsx(MetricDialog, { mode: "edit", isLoading: !loadedMetric, initialMetric: initialMetric, onClose: onClose, onSubmit: handleSubmit, onDuplicate: onDuplicate ? handleDuplicate : undefined }));
51
+ }