@gooddata/sdk-code-convertors 11.36.0-alpha.3 → 11.36.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.
@@ -25,6 +25,20 @@ export function declarativeDashboardToYaml(entities, dashboard, filterContexts,
25
25
  });
26
26
  // Add intro comment to the document
27
27
  doc.commentBefore = DASHBOARD_COMMENT;
28
+ // Infer the YAML model version from the declarative structure:
29
+ // - V3 (tabs are the sole source of content) when tabs are present and no root-level
30
+ // layout / filter configs / parameters exist.
31
+ // - V2 (legacy) otherwise — covers both root-only dashboards and tab dashboards that
32
+ // still carry mirrored root content for backward compatibility.
33
+ const hasContentTabs = (content.tabs?.length ?? 0) > 0;
34
+ const hasRootContent = Boolean(content.layout ||
35
+ content.dateFilterConfig ||
36
+ (content.dateFilterConfigs && content.dateFilterConfigs.length > 0) ||
37
+ (content.attributeFilterConfigs && content.attributeFilterConfigs.length > 0) ||
38
+ (content.measureValueFilterConfigs && content.measureValueFilterConfigs.length > 0) ||
39
+ (content.parameters && content.parameters.length > 0));
40
+ const yamlVersion = hasContentTabs && !hasRootContent ? "3" : "2";
41
+ doc.add(entryWithSpace("version", yamlVersion));
28
42
  // Add optional meta fields
29
43
  fillOptionalMetaFields(doc, dashboard);
30
44
  //settings
@@ -40,10 +54,6 @@ export function declarativeDashboardToYaml(entities, dashboard, filterContexts,
40
54
  if (content.disableFilterViews) {
41
55
  doc.add(entryWithSpace("filter_views", false));
42
56
  }
43
- // Add enable_section_headers
44
- if (content.layout?.configuration?.sections?.enableHeader) {
45
- doc.add(entryWithSpace("enable_section_headers", content.layout?.configuration?.sections?.enableHeader));
46
- }
47
57
  // Add dashboard tabs
48
58
  // Note: If there's only 1 tab without a title (implicit first tab visible in edit mode),
49
59
  // treat as "no tabs" and use sections format to preserve previous YAML structure
@@ -51,12 +61,20 @@ export function declarativeDashboardToYaml(entities, dashboard, filterContexts,
51
61
  const hasSingleTabWithTitle = content.tabs?.length === 1 && content.tabs[0].title;
52
62
  const shouldUseTabs = hasMultipleTabs || hasSingleTabWithTitle;
53
63
  if (content.tabs && shouldUseTabs) {
64
+ // Add enable_section_headers
65
+ if (content.tabs[0].layout?.configuration?.sections?.enableHeader) {
66
+ doc.add(entryWithSpace("enable_section_headers", content.tabs[0].layout?.configuration?.sections?.enableHeader));
67
+ }
54
68
  const tabs = declarativeTabsToYaml(content.tabs, filterContexts, entities, updateErrorContext(errorContext, { path: ["tabs"] }));
55
69
  if (tabs) {
56
70
  doc.add(entryWithSpace("tabs", tabs));
57
71
  }
58
72
  }
59
73
  else if (content.tabs?.length === 1) {
74
+ // Add enable_section_headers
75
+ if (content.tabs[0].layout?.configuration?.sections?.enableHeader) {
76
+ doc.add(entryWithSpace("enable_section_headers", content.tabs[0].layout?.configuration?.sections?.enableHeader));
77
+ }
60
78
  // Dashboard with implicit single tab - create root yaml structure like without tabs
61
79
  // but read all metadata from a single tab
62
80
  const tab = content.tabs[0];
@@ -76,6 +94,10 @@ export function declarativeDashboardToYaml(entities, dashboard, filterContexts,
76
94
  }
77
95
  else {
78
96
  // Dashboard without tabs - add sections and filters
97
+ // Add enable_section_headers
98
+ if (content.layout?.configuration?.sections?.enableHeader) {
99
+ doc.add(entryWithSpace("enable_section_headers", content.layout?.configuration?.sections?.enableHeader));
100
+ }
79
101
  const section = declarativeSectionsToYaml(content.layout, entities, updateErrorContext(errorContext, { path: ["layout"] }));
80
102
  if (section) {
81
103
  doc.add(entryWithSpace("sections", section));
@@ -923,7 +923,8 @@ export declare enum CoreErrorCode {
923
923
  OnlyOneAttributeItemAllowed = "core.onlyOneAttributeItemAllowed",
924
924
  MultipleCommonDateFilters = "core.multipleCommonDateFilters",
925
925
  DuplicateFilterLocalIdentifier = "core.duplicateFilterLocalIdentifier",
926
- DuplicateTabIdentifier = "core.duplicateTabIdentifier"
926
+ DuplicateTabIdentifier = "core.duplicateTabIdentifier",
927
+ TabsAndRootContentMutuallyExclusive = "core.tabsAndRootContentMutuallyExclusive"
927
928
  }
928
929
 
929
930
  /**
@@ -1208,7 +1209,8 @@ export declare type ExportEntities = Array<{
1208
1209
  data: Dataset | DateDataset | Metric | Visualisation | Dashboard | Plugin_2 | AttributeHierarchy;
1209
1210
  declarative?: DeclarativeDataset | DeclarativeDateDataset | DeclarativeMetric | DeclarativeVisualizationObject | DeclarativeDashboardPlugin | DeclarativeAttributeHierarchy | {
1210
1211
  dashboard: DeclarativeAnalyticalDashboard;
1211
- filterContext: DeclarativeFilterContext;
1212
+ filterContext?: DeclarativeFilterContext;
1213
+ tabFilterContexts?: DeclarativeFilterContext[];
1212
1214
  };
1213
1215
  }>;
1214
1216
 
@@ -2783,7 +2785,7 @@ export declare function yamlBucketsToDeclarative(entities: ExportEntities, input
2783
2785
  /** @public */
2784
2786
  export declare function yamlDashboardToDeclarative(entities: ExportEntities, input: Dashboard): {
2785
2787
  dashboard: DeclarativeAnalyticalDashboard;
2786
- filterContext: DeclarativeFilterContext;
2788
+ filterContext?: DeclarativeFilterContext;
2787
2789
  tabFilterContexts?: DeclarativeFilterContext[];
2788
2790
  };
2789
2791
 
@@ -20,7 +20,7 @@ export type EmptyValueHandling = NonNullable<IDashboardDateFilter["dateFilter"][
20
20
  /** @public */
21
21
  export declare function yamlDashboardToDeclarative(entities: ExportEntities, input: Dashboard): {
22
22
  dashboard: DeclarativeAnalyticalDashboard;
23
- filterContext: DeclarativeFilterContext;
23
+ filterContext?: DeclarativeFilterContext;
24
24
  tabFilterContexts?: DeclarativeFilterContext[];
25
25
  };
26
26
  /** @internal */
@@ -2,6 +2,7 @@
2
2
  import { v4 as uuidV4 } from "uuid";
3
3
  import { VisualisationsTypes } from "../conts.js";
4
4
  import { parseUrlTarget } from "../utils/customUrl.js";
5
+ import { CoreErrorCode, newError } from "../utils/errors.js";
5
6
  import { yamlConditionToMatch } from "../utils/filterUtils.js";
6
7
  import { convertGranularity } from "../utils/granularityUtils.js";
7
8
  import { toDeclarativePermissions } from "../utils/permissionUtils.js";
@@ -52,11 +53,11 @@ function yamlTabsToDeclarative(entities, tabs, enableSectionHeaders) {
52
53
  };
53
54
  }
54
55
  /**
55
- * Process dashboard with tabs - uses active tab or first tab as main dashboard for backwards compatibility
56
+ * V2 (legacy): tabs present in YAML mirror tabs[0] content into root-level properties
57
+ * for backward compatibility with consumers that read root layout/filters.
56
58
  */
57
- function processDashboardWithTabs(entities, input) {
59
+ function processDashboardWithTabsV2(entities, input) {
58
60
  const tabsResult = yamlTabsToDeclarative(entities, input.tabs, input.enable_section_headers);
59
- // Use first tab as default for backwards compatibility
60
61
  const defaultTabIndex = 0;
61
62
  const defaultTab = tabsResult.tabs[defaultTabIndex];
62
63
  return {
@@ -71,10 +72,12 @@ function processDashboardWithTabs(entities, input) {
71
72
  };
72
73
  }
73
74
  /**
74
- * Process traditional dashboard without tabs
75
+ * V2 (legacy): no tabs in YAML — wrap root content in a synthetic single tab AND keep
76
+ * the same content at root level. Produces the duplicated declarative shape the older
77
+ * SDK readers expect.
75
78
  */
76
- function processDashboardWithoutTabs(entities, input) {
77
- const { filterContext, tabFilterContexts: _tabFilterContexts, ...rest } = processDashboardWithTabs(entities, {
79
+ function processDashboardWithoutTabsV2(entities, input) {
80
+ const { filterContext, tabFilterContexts: _tabFilterContexts, ...rest } = processDashboardWithTabsV2(entities, {
78
81
  ...input,
79
82
  tabs: [
80
83
  {
@@ -100,27 +103,95 @@ function processDashboardWithoutTabs(entities, input) {
100
103
  tabFilterContexts: [dashboardFilterContext],
101
104
  };
102
105
  }
106
+ /**
107
+ * V3 (clean): tabs present in YAML — tabs are the sole source of layout and filter content.
108
+ * No root-level mirroring. Root filter context reference is left empty; per-tab filter
109
+ * context refs are the only source.
110
+ */
111
+ function processDashboardWithTabsV3(entities, input) {
112
+ const tabsResult = yamlTabsToDeclarative(entities, input.tabs, input.enable_section_headers);
113
+ return {
114
+ layout: undefined,
115
+ filterContext: undefined,
116
+ dateFilterConfig: undefined,
117
+ dateFilterConfigs: undefined,
118
+ attributeFilterConfigs: undefined,
119
+ measureValueFilterConfigs: undefined,
120
+ tabs: tabsResult.tabs,
121
+ tabFilterContexts: tabsResult.filterContexts,
122
+ };
123
+ }
124
+ /**
125
+ * V3 (clean): no tabs in YAML — wrap root content in a synthetic single tab. No content
126
+ * is left at root level in the resulting declarative model.
127
+ */
128
+ function processDashboardWithoutTabsV3(entities, input) {
129
+ const tabsResult = yamlTabsToDeclarative(entities, [
130
+ {
131
+ id: "defaultTabId",
132
+ title: "",
133
+ filters: input.filters,
134
+ sections: input.sections ?? [],
135
+ },
136
+ ], input.enable_section_headers);
137
+ return {
138
+ layout: undefined,
139
+ filterContext: undefined,
140
+ dateFilterConfig: undefined,
141
+ dateFilterConfigs: undefined,
142
+ attributeFilterConfigs: undefined,
143
+ measureValueFilterConfigs: undefined,
144
+ tabs: tabsResult.tabs,
145
+ tabFilterContexts: tabsResult.filterContexts,
146
+ };
147
+ }
103
148
  /** @public */
104
149
  export function yamlDashboardToDeclarative(entities, input) {
105
- // Process dashboard based on whether it has tabs
106
- const { layout, filterContext, dateFilterConfig, dateFilterConfigs, attributeFilterConfigs, measureValueFilterConfigs, tabs, tabFilterContexts, } = input.tabs ? processDashboardWithTabs(entities, input) : processDashboardWithoutTabs(entities, input);
150
+ const hasTabs = Boolean(input.tabs && input.tabs.length > 0);
151
+ const hasRootContent = Boolean((input.sections && input.sections.length > 0) ||
152
+ (input.filters && Object.keys(input.filters).length > 0));
153
+ if (hasTabs && hasRootContent) {
154
+ throw newError(CoreErrorCode.TabsAndRootContentMutuallyExclusive, [input.id]);
155
+ }
156
+ const yamlVersion = input.version ?? "2";
157
+ // Process dashboard based on YAML version and whether it has tabs.
158
+ // V2 mirrors content into root for backward compat; V3 keeps content only inside tabs.
159
+ const { layout, filterContext, dateFilterConfig, dateFilterConfigs, attributeFilterConfigs, measureValueFilterConfigs, tabs, tabFilterContexts, } = yamlVersion === "3"
160
+ ? hasTabs
161
+ ? processDashboardWithTabsV3(entities, input)
162
+ : processDashboardWithoutTabsV3(entities, input)
163
+ : hasTabs
164
+ ? processDashboardWithTabsV2(entities, input)
165
+ : processDashboardWithoutTabsV2(entities, input);
107
166
  const plugins = yamlPluginsToDeclarative(input.plugins);
108
167
  const [, permissions] = toDeclarativePermissions(input.permissions);
109
- const content = {
110
- version: "2",
111
- layout,
112
- plugins,
113
- attributeFilterConfigs,
114
- measureValueFilterConfigs,
115
- dateFilterConfig,
116
- dateFilterConfigs,
117
- ...(tabs ? { tabs } : {}),
118
- ...(input.cross_filtering === false ? { disableCrossFiltering: true } : {}),
119
- ...(input.user_filters_save === false ? { disableUserFilterSave: true } : {}),
120
- ...(input.user_filters_reset === false ? { disableUserFilterReset: true } : {}),
121
- ...(input.filter_views === false ? { disableFilterViews: true } : {}),
122
- filterContextRef: createIdentifier(filterContext.id, { forceType: "filterContext" }),
123
- };
168
+ const content = yamlVersion === "3"
169
+ ? {
170
+ version: "2",
171
+ plugins,
172
+ tabs,
173
+ ...(input.cross_filtering === false ? { disableCrossFiltering: true } : {}),
174
+ ...(input.user_filters_save === false ? { disableUserFilterSave: true } : {}),
175
+ ...(input.user_filters_reset === false ? { disableUserFilterReset: true } : {}),
176
+ ...(input.filter_views === false ? { disableFilterViews: true } : {}),
177
+ }
178
+ : {
179
+ version: "2",
180
+ layout,
181
+ plugins,
182
+ attributeFilterConfigs,
183
+ measureValueFilterConfigs,
184
+ dateFilterConfig,
185
+ dateFilterConfigs,
186
+ ...(tabs ? { tabs } : {}),
187
+ ...(input.cross_filtering === false ? { disableCrossFiltering: true } : {}),
188
+ ...(input.user_filters_save === false ? { disableUserFilterSave: true } : {}),
189
+ ...(input.user_filters_reset === false ? { disableUserFilterReset: true } : {}),
190
+ ...(input.filter_views === false ? { disableFilterViews: true } : {}),
191
+ filterContextRef: filterContext
192
+ ? createIdentifier(filterContext.id, { forceType: "filterContext" })
193
+ : undefined,
194
+ };
124
195
  const dashboard = {
125
196
  id: input.id,
126
197
  title: input.title ?? convertIdToTitle(input.id),
@@ -504,7 +575,7 @@ function yamlIgnoredFilterToDeclarative(input) {
504
575
  if (ref?.type === "metric") {
505
576
  return {
506
577
  type: "measureValueFilterReference",
507
- measure: createIdentifier(input),
578
+ measure: createIdentifier(input, { forceMetric: true }),
508
579
  };
509
580
  }
510
581
  return null;
package/esm/types.d.ts CHANGED
@@ -16,7 +16,8 @@ export type ExportEntities = Array<{
16
16
  data: Dataset | DateDataset | Metric | Visualisation | Dashboard | Plugin | AttributeHierarchy;
17
17
  declarative?: DeclarativeDataset | DeclarativeDateDataset | DeclarativeMetric | DeclarativeVisualizationObject | DeclarativeDashboardPlugin | DeclarativeAttributeHierarchy | {
18
18
  dashboard: DeclarativeAnalyticalDashboard;
19
- filterContext: DeclarativeFilterContext;
19
+ filterContext?: DeclarativeFilterContext;
20
+ tabFilterContexts?: DeclarativeFilterContext[];
20
21
  };
21
22
  }>;
22
23
  /** @public */
@@ -13,7 +13,8 @@ export declare enum CoreErrorCode {
13
13
  OnlyOneAttributeItemAllowed = "core.onlyOneAttributeItemAllowed",
14
14
  MultipleCommonDateFilters = "core.multipleCommonDateFilters",
15
15
  DuplicateFilterLocalIdentifier = "core.duplicateFilterLocalIdentifier",
16
- DuplicateTabIdentifier = "core.duplicateTabIdentifier"
16
+ DuplicateTabIdentifier = "core.duplicateTabIdentifier",
17
+ TabsAndRootContentMutuallyExclusive = "core.tabsAndRootContentMutuallyExclusive"
17
18
  }
18
19
  /**
19
20
  * @public
@@ -18,6 +18,7 @@ var CoreErrorCode;
18
18
  CoreErrorCode["MultipleCommonDateFilters"] = "core.multipleCommonDateFilters";
19
19
  CoreErrorCode["DuplicateFilterLocalIdentifier"] = "core.duplicateFilterLocalIdentifier";
20
20
  CoreErrorCode["DuplicateTabIdentifier"] = "core.duplicateTabIdentifier";
21
+ CoreErrorCode["TabsAndRootContentMutuallyExclusive"] = "core.tabsAndRootContentMutuallyExclusive";
21
22
  })(CoreErrorCode || (CoreErrorCode = {}));
22
23
  /**
23
24
  * @public
@@ -35,6 +36,7 @@ export const CoreErrorMessages = {
35
36
  [CoreErrorCode.MultipleCommonDateFilters]: `Multiple usage of common date filters in dashboard. Define exactly one date filter that has no date dataset defined.`,
36
37
  [CoreErrorCode.DuplicateFilterLocalIdentifier]: `Duplicate filter local identifier "{0}". Each filter must have a unique local identifier across a dashboard tab, including filters in groups.`,
37
38
  [CoreErrorCode.DuplicateTabIdentifier]: `Duplicate tab identifier "{0}". Each tab must have a unique identifier within the dashboard.`,
39
+ [CoreErrorCode.TabsAndRootContentMutuallyExclusive]: `Dashboard "{0}" defines both "tabs" and root-level "sections" or "filters". These are mutually exclusive — keep all content inside tabs or at the root, not both.`,
38
40
  };
39
41
  /**
40
42
  * @public
@@ -52,6 +54,7 @@ export const CoreErrorTypes = {
52
54
  [CoreErrorCode.MultipleCommonDateFilters]: "MultipleCommonDateFilters",
53
55
  [CoreErrorCode.DuplicateFilterLocalIdentifier]: "DuplicateFilterLocalIdentifier",
54
56
  [CoreErrorCode.DuplicateTabIdentifier]: "DuplicateTabIdentifier",
57
+ [CoreErrorCode.TabsAndRootContentMutuallyExclusive]: "TabsAndRootContentMutuallyExclusive",
55
58
  };
56
59
  /**
57
60
  * Update error context with new values
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gooddata/sdk-code-convertors",
3
- "version": "11.36.0-alpha.3",
3
+ "version": "11.36.0-alpha.6",
4
4
  "description": "GoodData AAC declarative converters",
5
5
  "license": "MIT",
6
6
  "author": "GoodData",
@@ -25,10 +25,10 @@
25
25
  },
26
26
  "dependencies": {
27
27
  "uuid": "11.1.0",
28
- "yaml": "^2.2.2",
29
- "@gooddata/api-client-tiger": "11.36.0-alpha.3",
30
- "@gooddata/sdk-code-schemas": "11.36.0-alpha.3",
31
- "@gooddata/sdk-model": "11.36.0-alpha.3"
28
+ "yaml": "2.8.3",
29
+ "@gooddata/api-client-tiger": "11.36.0-alpha.6",
30
+ "@gooddata/sdk-model": "11.36.0-alpha.6",
31
+ "@gooddata/sdk-code-schemas": "11.36.0-alpha.6"
32
32
  },
33
33
  "devDependencies": {
34
34
  "@microsoft/api-extractor": "^7.55.2",
@@ -50,8 +50,8 @@
50
50
  "oxlint-tsgolint": "0.11.4",
51
51
  "typescript": "5.9.3",
52
52
  "vitest": "4.1.0",
53
- "@gooddata/eslint-config": "11.36.0-alpha.3",
54
- "@gooddata/oxlint-config": "11.36.0-alpha.3"
53
+ "@gooddata/oxlint-config": "11.36.0-alpha.6",
54
+ "@gooddata/eslint-config": "11.36.0-alpha.6"
55
55
  },
56
56
  "scripts": {
57
57
  "_phase:build": "npm run build",