@sisense/sdk-data 2.18.1 → 2.20.0

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 (36) hide show
  1. package/dist/cjs/dimensional-model/attributes.js +5 -2
  2. package/dist/cjs/dimensional-model/base.d.ts +29 -0
  3. package/dist/cjs/dimensional-model/base.js +39 -1
  4. package/dist/cjs/dimensional-model/dimensions/dimensions.js +2 -1
  5. package/dist/cjs/dimensional-model/filters/filter-relations.js +3 -1
  6. package/dist/cjs/dimensional-model/filters/utils/condition-filter-util.js +2 -0
  7. package/dist/cjs/dimensional-model/filters/utils/filter-from-jaql-util.js +2 -0
  8. package/dist/cjs/dimensional-model/measures/factory.d.ts +3 -1
  9. package/dist/cjs/dimensional-model/measures/factory.js +4 -2
  10. package/dist/cjs/dimensional-model/measures/measures.js +9 -5
  11. package/dist/cjs/dimensional-model/types.d.ts +1 -0
  12. package/dist/cjs/translation/resources/en.d.ts +1 -2
  13. package/dist/cjs/translation/resources/en.js +8 -9
  14. package/dist/cjs/translation/resources/index.d.ts +2 -4
  15. package/dist/cjs/translation/resources/uk.js +7 -8
  16. package/dist/cjs/utils.d.ts +11 -0
  17. package/dist/cjs/utils.js +28 -2
  18. package/dist/dimensional-model/attributes.js +6 -3
  19. package/dist/dimensional-model/base.d.ts +29 -0
  20. package/dist/dimensional-model/base.js +36 -0
  21. package/dist/dimensional-model/dimensions/dimensions.js +4 -3
  22. package/dist/dimensional-model/filters/filter-relations.js +3 -1
  23. package/dist/dimensional-model/filters/utils/condition-filter-util.js +2 -0
  24. package/dist/dimensional-model/filters/utils/filter-from-jaql-util.js +2 -0
  25. package/dist/dimensional-model/measures/factory.d.ts +3 -1
  26. package/dist/dimensional-model/measures/factory.js +4 -2
  27. package/dist/dimensional-model/measures/measures.js +9 -5
  28. package/dist/dimensional-model/types.d.ts +1 -0
  29. package/dist/translation/resources/en.d.ts +1 -2
  30. package/dist/translation/resources/en.js +8 -9
  31. package/dist/translation/resources/index.d.ts +2 -4
  32. package/dist/translation/resources/uk.js +7 -8
  33. package/dist/tsconfig.prod.cjs.tsbuildinfo +1 -1
  34. package/dist/utils.d.ts +11 -0
  35. package/dist/utils.js +26 -1
  36. package/package.json +2 -2
@@ -30,9 +30,10 @@ class DimensionalAttribute extends base_js_1.DimensionalElement {
30
30
  this._sort = types_js_1.Sort.None;
31
31
  this.expression = expression;
32
32
  // if composeCode is not explicitly set by the caller, extract it from expression
33
+ // Use [[delimiters]] to preserve original names that need normalization
33
34
  if (!composeCode && expression) {
34
35
  const { table, column } = (0, utils_js_1.parseExpression)(expression);
35
- this.composeCode = normalizeAttributeName(table, column, '', consts_js_1.DATA_MODEL_MODULE_NAME);
36
+ this.composeCode = `${consts_js_1.DATA_MODEL_MODULE_NAME}.${(0, base_js_1.wrapIfNeedsNormalization)(table)}.${(0, base_js_1.wrapIfNeedsNormalization)(column)}`;
36
37
  }
37
38
  // panel is not needed in most cases, this is to support break by columns functionality
38
39
  if (panel === 'columns') {
@@ -117,9 +118,11 @@ class DimensionalLevelAttribute extends DimensionalAttribute {
117
118
  this._format = format;
118
119
  this.granularity = granularity;
119
120
  // if composeCode is not explicitly set by the caller, extract it from expression and granularity
121
+ // Use [[delimiters]] to preserve original names that need normalization
120
122
  if (!composeCode && expression) {
121
123
  const { table, column } = (0, utils_js_1.parseExpression)(expression);
122
- this.composeCode = normalizeAttributeName(table, column, granularity, consts_js_1.DATA_MODEL_MODULE_NAME);
124
+ const granularityPart = granularity ? `.${granularity}` : '';
125
+ this.composeCode = `${consts_js_1.DATA_MODEL_MODULE_NAME}.${(0, base_js_1.wrapIfNeedsNormalization)(table)}.${(0, base_js_1.wrapIfNeedsNormalization)(column)}${granularityPart}`;
123
126
  }
124
127
  // panel is not needed in most cases, this is to support break by columns functionality
125
128
  if (panel === 'columns') {
@@ -74,3 +74,32 @@ export declare abstract class DimensionalElement implements Element {
74
74
  * @internal
75
75
  */
76
76
  export declare function normalizeName(name: string): string;
77
+ /**
78
+ * Checks if a name contains characters that would be modified by normalizeName().
79
+ * Includes: spaces, special chars (!@#$%^&*), dots, brackets, or starts with number.
80
+ *
81
+ * @example
82
+ * needsNormalization("Age Range") // true (space)
83
+ * needsNormalization("Cost ($)") // true (special chars)
84
+ * needsNormalization("Rev.2024") // true (dot)
85
+ * needsNormalization("2024Data") // true (starts with number)
86
+ * needsNormalization("Revenue") // false
87
+ *
88
+ * @param name - The name to check
89
+ * @returns true if the name would be modified by normalizeName()
90
+ * @internal
91
+ */
92
+ export declare function needsNormalization(name: string): boolean;
93
+ /**
94
+ * Wraps name in [[delimiters]] if it would be modified by normalizeName().
95
+ * Used to preserve original names in composeCode while marking them for transformation.
96
+ *
97
+ * @example
98
+ * wrapIfNeedsNormalization("Age Range") // "[[Age Range]]"
99
+ * wrapIfNeedsNormalization("Revenue") // "Revenue" (unchanged)
100
+ *
101
+ * @param name - The name to potentially wrap
102
+ * @returns The name wrapped in [[]] if it needs normalization, otherwise unchanged
103
+ * @internal
104
+ */
105
+ export declare function wrapIfNeedsNormalization(name: string): string;
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  /* eslint-disable @typescript-eslint/no-unsafe-return */
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.normalizeName = exports.DimensionalElement = void 0;
4
+ exports.wrapIfNeedsNormalization = exports.needsNormalization = exports.normalizeName = exports.DimensionalElement = void 0;
5
5
  /**
6
6
  * @internal
7
7
  */
@@ -91,3 +91,41 @@ function normalizeName(name) {
91
91
  return normalizedName;
92
92
  }
93
93
  exports.normalizeName = normalizeName;
94
+ /**
95
+ * Checks if a name contains characters that would be modified by normalizeName().
96
+ * Includes: spaces, special chars (!@#$%^&*), dots, brackets, or starts with number.
97
+ *
98
+ * @example
99
+ * needsNormalization("Age Range") // true (space)
100
+ * needsNormalization("Cost ($)") // true (special chars)
101
+ * needsNormalization("Rev.2024") // true (dot)
102
+ * needsNormalization("2024Data") // true (starts with number)
103
+ * needsNormalization("Revenue") // false
104
+ *
105
+ * @param name - The name to check
106
+ * @returns true if the name would be modified by normalizeName()
107
+ * @internal
108
+ */
109
+ function needsNormalization(name) {
110
+ // Check for invalid characters (anything not a-zA-Z0-9_)
111
+ // Note: dots are also "invalid" as they get replaced with underscores
112
+ // Check if starts with a number (gets prefixed with _)
113
+ return /[^a-zA-Z0-9_]/.test(name) || /^[0-9]/.test(name);
114
+ }
115
+ exports.needsNormalization = needsNormalization;
116
+ /**
117
+ * Wraps name in [[delimiters]] if it would be modified by normalizeName().
118
+ * Used to preserve original names in composeCode while marking them for transformation.
119
+ *
120
+ * @example
121
+ * wrapIfNeedsNormalization("Age Range") // "[[Age Range]]"
122
+ * wrapIfNeedsNormalization("Revenue") // "Revenue" (unchanged)
123
+ *
124
+ * @param name - The name to potentially wrap
125
+ * @returns The name wrapped in [[]] if it needs normalization, otherwise unchanged
126
+ * @internal
127
+ */
128
+ function wrapIfNeedsNormalization(name) {
129
+ return needsNormalization(name) ? `[[${name}]]` : name;
130
+ }
131
+ exports.wrapIfNeedsNormalization = wrapIfNeedsNormalization;
@@ -23,9 +23,10 @@ class DimensionalDimension extends base_js_1.DimensionalElement {
23
23
  this._attributes = [];
24
24
  this._sort = types_js_1.Sort.None;
25
25
  // if composeCode is not explicitly set by the caller, extract it from expression
26
+ // Use [[delimiters]] to preserve original names that need normalization
26
27
  if (!composeCode && expression) {
27
28
  const { table, column } = (0, utils_js_1.parseExpression)(expression);
28
- this.composeCode = (0, attributes_js_1.normalizeAttributeName)(table, column, '', consts_js_1.DATA_MODEL_MODULE_NAME);
29
+ this.composeCode = `${consts_js_1.DATA_MODEL_MODULE_NAME}.${(0, base_js_1.wrapIfNeedsNormalization)(table)}.${(0, base_js_1.wrapIfNeedsNormalization)(column)}`;
29
30
  }
30
31
  this._sort = sort || types_js_1.Sort.None;
31
32
  this._expression = expression;
@@ -514,7 +514,9 @@ function getFilterRelationsFromJaql(filters, highlights, filterRelations) {
514
514
  if ('instanceid' in node) {
515
515
  const filter = filters.find((filter) => filter.config.guid === node.instanceid);
516
516
  if (!filter) {
517
- throw new translatable_error_js_1.TranslatableError('errors.unknownFilterInFilterRelations');
517
+ throw new translatable_error_js_1.TranslatableError('errors.unknownFilterInFilterRelations', {
518
+ filterGuid: node.instanceid,
519
+ });
518
520
  }
519
521
  return filter;
520
522
  }
@@ -180,6 +180,7 @@ const createAttributeFilterFromConditionFilterJaql = (attribute, conditionFilter
180
180
  }
181
181
  throw new translatable_error_js_1.TranslatableError('errors.filter.unsupportedConditionFilter', {
182
182
  filter: JSON.stringify(conditionFilterJaql),
183
+ attributeName: attribute.name,
183
184
  });
184
185
  };
185
186
  exports.createAttributeFilterFromConditionFilterJaql = createAttributeFilterFromConditionFilterJaql;
@@ -216,6 +217,7 @@ const createMeasureFilterFromConditionFilterJaql = (measure, conditionFilterJaql
216
217
  }
217
218
  throw new translatable_error_js_1.TranslatableError('errors.filter.unsupportedConditionFilter', {
218
219
  filter: JSON.stringify(conditionFilterJaql),
220
+ attributeName: measure.name,
219
221
  });
220
222
  };
221
223
  exports.createMeasureFilterFromConditionFilterJaql = createMeasureFilterFromConditionFilterJaql;
@@ -163,11 +163,13 @@ exports.createFilterFromCustomFilterJaql = createFilterFromCustomFilterJaql;
163
163
  * @returns Filter object.
164
164
  */
165
165
  const createFilterFromJaqlInternal = (jaql, guid) => {
166
+ var _a, _b;
166
167
  try {
167
168
  if ('formula' in jaql) {
168
169
  // generic pass-through JAQL filter will be used instead
169
170
  throw new translatable_error_js_1.TranslatableError('errors.filter.formulaFiltersNotSupported', {
170
171
  filter: JSON.stringify(jaql),
172
+ attributeName: (_b = (_a = jaql.title) !== null && _a !== void 0 ? _a : jaql.column) !== null && _b !== void 0 ? _b : jaql.dim,
171
173
  });
172
174
  }
173
175
  const filterJaqlWrapperWithType = (0, filter_types_util_js_1.extractFilterTypeFromFilterJaql)(jaql, jaql.datatype);
@@ -81,10 +81,12 @@ export declare const RankingSortTypes: {
81
81
  * @param title - Title of the measure to be displayed in legend
82
82
  * @param formula - Formula to be used for the measure
83
83
  * @param context - Formula context as a map of strings to attributes, measures, or filters
84
+ * @param format - Optional format string for the measure
85
+ * @param description - Optional description of the measure
84
86
  * @returns A calculated measure instance
85
87
  * @group Advanced Analytics
86
88
  */
87
- export declare const customFormula: (title: string, formula: string, context: CustomFormulaContext) => CalculatedMeasure;
89
+ export declare const customFormula: (title: string, formula: string, context: CustomFormulaContext, format?: string, description?: string) => CalculatedMeasure;
88
90
  /**
89
91
  * Creates an aggregated measure.
90
92
  *
@@ -124,13 +124,15 @@ function measureFunction(measure, name, func, options) {
124
124
  * @param title - Title of the measure to be displayed in legend
125
125
  * @param formula - Formula to be used for the measure
126
126
  * @param context - Formula context as a map of strings to attributes, measures, or filters
127
+ * @param format - Optional format string for the measure
128
+ * @param description - Optional description of the measure
127
129
  * @returns A calculated measure instance
128
130
  * @group Advanced Analytics
129
131
  */
130
- exports.customFormula = (0, compose_code_utils_js_1.withComposeCodeForMeasure)((title, formula, context) => {
132
+ exports.customFormula = (0, compose_code_utils_js_1.withComposeCodeForMeasure)((title, formula, context, format, description) => {
131
133
  // context keys must be in brackets
132
134
  const newContext = Object.fromEntries(Object.entries(context).map(([key, val]) => [key.startsWith('[') ? key : `[${key}]`, val]));
133
- return new measures_js_1.DimensionalCalculatedMeasure(title, formula, newContext);
135
+ return new measures_js_1.DimensionalCalculatedMeasure(title, formula, newContext, format, description);
134
136
  }, 'customFormula');
135
137
  function arithmetic(operand1, operator, operand2, name, withParentheses) {
136
138
  const builder = [];
@@ -424,7 +424,9 @@ function createMeasure(json) {
424
424
  }
425
425
  if (types_js_1.MetadataTypes.isCalculatedMeasure(json)) {
426
426
  if (json.context === undefined) {
427
- throw new translatable_error_js_1.TranslatableError('errors.measure.dimensionalCalculatedMeasure.noContext');
427
+ throw new translatable_error_js_1.TranslatableError('errors.measure.dimensionalCalculatedMeasure.noContext', {
428
+ measureName: name,
429
+ });
428
430
  }
429
431
  const context = {};
430
432
  Object.getOwnPropertyNames(json.context).forEach((pname) => {
@@ -434,20 +436,22 @@ function createMeasure(json) {
434
436
  }
435
437
  else if (types_js_1.MetadataTypes.isMeasureTemplate(json)) {
436
438
  if (att === undefined) {
437
- throw new translatable_error_js_1.TranslatableError('errors.measure.dimensionalBaseMeasure.noAttributeDimExpression');
439
+ throw new translatable_error_js_1.TranslatableError('errors.measure.dimensionalBaseMeasure.noAttributeDimExpression', { measureName: name });
438
440
  }
439
441
  return new DimensionalMeasureTemplate(name, att, format, desc, sort);
440
442
  }
441
443
  else if (types_js_1.MetadataTypes.isBaseMeasure(json)) {
442
444
  if (att === undefined) {
443
- throw new translatable_error_js_1.TranslatableError('errors.measure.dimensionalBaseMeasure.noAttributeDimExpression');
445
+ throw new translatable_error_js_1.TranslatableError('errors.measure.dimensionalBaseMeasure.noAttributeDimExpression', { measureName: name });
444
446
  }
445
447
  const agg = json.agg || json.aggregation;
446
448
  if (!agg) {
447
- throw new translatable_error_js_1.TranslatableError('errors.measure.dimensionalBaseMeasure.noAggAggregation');
449
+ throw new translatable_error_js_1.TranslatableError('errors.measure.dimensionalBaseMeasure.noAggAggregation', {
450
+ measureName: name,
451
+ });
448
452
  }
449
453
  return new DimensionalBaseMeasure(name, att, agg, format, desc, sort);
450
454
  }
451
- throw new translatable_error_js_1.TranslatableError('errors.measure.unsupportedType');
455
+ throw new translatable_error_js_1.TranslatableError('errors.measure.unsupportedType', { measureName: name });
452
456
  }
453
457
  exports.createMeasure = createMeasure;
@@ -217,6 +217,7 @@ export declare type FormulaJaql = {
217
217
  formula: string;
218
218
  context?: Record<FormulaID, FormulaContext>;
219
219
  datasource?: JaqlDataSource;
220
+ description?: string;
220
221
  };
221
222
  /** @internal */
222
223
  export declare type BaseFilterJaql = IncludeAllFilterJaql | IncludeMembersFilterJaql | ExcludeMembersFilterJaql | NumericFilterJaql | ConditionFilterJaql | AndFilterJaql<NumericFilterJaql | ConditionFilterJaql> | OrFilterJaql<NumericFilterJaql | ConditionFilterJaql>;
@@ -12,9 +12,8 @@ export declare const translation: {
12
12
  noAttributeDimExpression: string;
13
13
  noAggAggregation: string;
14
14
  };
15
- notAFormula: string;
16
15
  };
17
- dataModelConfig: {
16
+ dataModel: {
18
17
  noName: string;
19
18
  noMetadata: string;
20
19
  };
@@ -7,26 +7,25 @@ exports.translation = void 0;
7
7
  exports.translation = {
8
8
  errors: {
9
9
  measure: {
10
- unsupportedType: 'Unsupported measure type',
10
+ unsupportedType: 'Unsupported measure type for measure: {{measureName}}',
11
11
  dimensionalCalculatedMeasure: {
12
- noContext: "DimensionalCalculatedMeasure must have 'context' property",
12
+ noContext: "DimensionalCalculatedMeasure {{measureName}} must have 'context' property",
13
13
  },
14
14
  dimensionalBaseMeasure: {
15
- noAttributeDimExpression: "DimensionalBaseMeasure must have 'attribute'/'dim'/'expression' property",
16
- noAggAggregation: "DimensionalBaseMeasure must have 'agg' or 'aggregation' property",
15
+ noAttributeDimExpression: "DimensionalBaseMeasure {{measureName}} must have 'attribute'/'dim'/'expression' property",
16
+ noAggAggregation: "DimensionalBaseMeasure {{measureName}} must have 'agg' or 'aggregation' property",
17
17
  },
18
- notAFormula: 'Jaql is not a formula',
19
18
  },
20
- dataModelConfig: {
19
+ dataModel: {
21
20
  noName: "'name' must be specified in config for DataModel",
22
21
  noMetadata: "'metadata' must be specified in config for DataModel",
23
22
  },
24
23
  filter: {
25
24
  unsupportedType: 'Unsupported filter type: {{filterType}}',
26
- unsupportedDatetimeLevel: 'Filters do not support the next "datetime" levels: Hours, MinutesRoundTo30, MinutesRoundTo15, Minutes, Seconds',
25
+ unsupportedDatetimeLevel: 'Filters do not support the following "datetime" levels: Hours, MinutesRoundTo30, MinutesRoundTo15, Minutes, Seconds',
27
26
  membersFilterNullMember: 'MembersFilter of {{attributeId}} - member cannot be null',
28
- unsupportedConditionFilter: 'Jaql contains unsupported condition filter: {{filter}}',
29
- formulaFiltersNotSupported: 'Formula-based filter not supported yet: {{filter}}',
27
+ unsupportedConditionFilter: 'Jaql for {{attributeName}} contains unsupported condition filter: {{filter}}',
28
+ formulaFiltersNotSupported: 'Formula-based filter for {{attributeName}} not supported yet: {{filter}}',
30
29
  },
31
30
  unsupportedDimensionalElement: 'Unsupported dimensional element type',
32
31
  },
@@ -19,9 +19,8 @@ export declare const resources: {
19
19
  noAttributeDimExpression: string;
20
20
  noAggAggregation: string;
21
21
  };
22
- notAFormula: string;
23
22
  };
24
- dataModelConfig: {
23
+ dataModel: {
25
24
  noName: string;
26
25
  noMetadata: string;
27
26
  };
@@ -46,9 +45,8 @@ export declare const resources: {
46
45
  noAttributeDimExpression: string;
47
46
  noAggAggregation: string;
48
47
  };
49
- notAFormula: string;
50
48
  };
51
- dataModelConfig: {
49
+ dataModel: {
52
50
  noName: string;
53
51
  noMetadata: string;
54
52
  };
@@ -7,17 +7,16 @@ exports.translation = void 0;
7
7
  exports.translation = {
8
8
  errors: {
9
9
  measure: {
10
- unsupportedType: 'Непідтримуваний тип measure',
10
+ unsupportedType: 'Непідтримуваний тип measure: {{measureName}}',
11
11
  dimensionalCalculatedMeasure: {
12
- noContext: "DimensionalCalculatedMeasure має мати властивість 'context'",
12
+ noContext: "DimensionalCalculatedMeasure {{measureName}} має мати властивість 'context'",
13
13
  },
14
14
  dimensionalBaseMeasure: {
15
- noAttributeDimExpression: "DimensionalBaseMeasure має мати властивість 'attribute'/'dim'/'expression'",
16
- noAggAggregation: "DimensionalBaseMeasure має мати властивість 'agg' або 'aggregation'",
15
+ noAttributeDimExpression: "DimensionalBaseMeasure {{measureName}} має мати властивість 'attribute'/'dim'/'expression'",
16
+ noAggAggregation: "DimensionalBaseMeasure {{measureName}} має мати властивість 'agg' або 'aggregation'",
17
17
  },
18
- notAFormula: 'Jaql не формула',
19
18
  },
20
- dataModelConfig: {
19
+ dataModel: {
21
20
  noName: "'name' має бути вказано в конфігурації для DataModel",
22
21
  noMetadata: "'metadata' має бути вказано в конфігурації для DataModel",
23
22
  },
@@ -25,8 +24,8 @@ exports.translation = {
25
24
  unsupportedType: 'Непідтримуваний тип фільтра: {{filterType}}',
26
25
  unsupportedDatetimeLevel: 'Фільтри не підтримують наступні рівні "datetime": Hours, MinutesRoundTo30, MinutesRoundTo15, Minutes, Seconds',
27
26
  membersFilterNullMember: 'MembersFilter у {{attributeId}} - member не може бути нульовим',
28
- unsupportedConditionFilter: 'Jaql містить непідтримуваний condition фільтр: {{filter}}',
29
- formulaFiltersNotSupported: 'Фільтри, що містять формули наразі не підтримуються: {{filter}}',
27
+ unsupportedConditionFilter: 'Jaql для {{attributeName}} містить непідтримуваний condition фільтр: {{filter}}',
28
+ formulaFiltersNotSupported: 'Фільтри, що містять формули для {{attributeName}} наразі не підтримуються: {{filter}}',
30
29
  },
31
30
  unsupportedDimensionalElement: 'Непідтримуваний тип елемента',
32
31
  },
@@ -184,3 +184,14 @@ export declare function createDimensionalElementFromJaql(jaql: Jaql, datetimeFor
184
184
  * @internal
185
185
  */
186
186
  export declare function getGranularityFromJaql(jaql: BaseJaql | FilterJaql | FilterJaqlInternal | RankingFilterJaql | MetadataItemJaql): string | undefined;
187
+ /**
188
+ * Translates Fusion FormulaJaql structures to CSDK CalculatedMeasure array.
189
+ *
190
+ * This is a pure Node.js function that converts Fusion structures to CSDK structures.
191
+ *
192
+ * @param formulas - Array of Fusion FormulaJaql structures
193
+ * @returns Array of CSDK CalculatedMeasure objects
194
+ * @throws Error if any formula cannot be converted (includes formula title in error message)
195
+ * @internal
196
+ */
197
+ export declare function translateSharedFormulas(formulas: FormulaJaql[]): CalculatedMeasure[];
package/dist/cjs/utils.js CHANGED
@@ -26,7 +26,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
26
26
  return (mod && mod.__esModule) ? mod : { "default": mod };
27
27
  };
28
28
  Object.defineProperty(exports, "__esModule", { value: true });
29
- exports.getGranularityFromJaql = exports.createDimensionalElementFromJaql = exports.createCalculatedMeasureHelper = exports.createMeasureHelper = exports.createAttributeHelper = exports.getSortType = exports.getColumnNameFromAttribute = exports.getTableNameFromAttribute = exports.parseExpression = exports.createFilterFromJaql = exports.convertSortDirectionToSort = exports.convertSort = exports.convertJaqlDataSourceForDto = exports.convertJaqlDataSource = exports.convertDataSource = exports.isDataSourceInfo = exports.getDataSourceName = exports.getFilterListAndRelationsJaql = exports.guidFast = exports.secureRandom = void 0;
29
+ exports.translateSharedFormulas = exports.getGranularityFromJaql = exports.createDimensionalElementFromJaql = exports.createCalculatedMeasureHelper = exports.createMeasureHelper = exports.createAttributeHelper = exports.getSortType = exports.getColumnNameFromAttribute = exports.getTableNameFromAttribute = exports.parseExpression = exports.createFilterFromJaql = exports.convertSortDirectionToSort = exports.convertSort = exports.convertJaqlDataSourceForDto = exports.convertJaqlDataSource = exports.convertDataSource = exports.isDataSourceInfo = exports.getDataSourceName = exports.getFilterListAndRelationsJaql = exports.guidFast = exports.secureRandom = void 0;
30
30
  const cloneDeep_js_1 = __importDefault(require("lodash-es/cloneDeep.js"));
31
31
  const mapValues_js_1 = __importDefault(require("lodash-es/mapValues.js"));
32
32
  const attributes_js_1 = require("./dimensional-model/attributes.js");
@@ -399,7 +399,7 @@ const createCalculatedMeasureHelper = (jaql) => {
399
399
  }
400
400
  return jaqlContextValue && createDimensionalElementFromJaql(jaqlContextValue);
401
401
  });
402
- const measure = measureFactory.customFormula(jaql.title, jaql.formula, context);
402
+ const measure = measureFactory.customFormula(jaql.title, jaql.formula, context, undefined, jaql.description);
403
403
  // Apply sort if present in the JAQL
404
404
  if (jaql.sort) {
405
405
  const sortEnum = convertSort(jaql.sort);
@@ -464,3 +464,29 @@ function getGranularityFromJaql(jaql) {
464
464
  : undefined;
465
465
  }
466
466
  exports.getGranularityFromJaql = getGranularityFromJaql;
467
+ /**
468
+ * Translates Fusion FormulaJaql structures to CSDK CalculatedMeasure array.
469
+ *
470
+ * This is a pure Node.js function that converts Fusion structures to CSDK structures.
471
+ *
472
+ * @param formulas - Array of Fusion FormulaJaql structures
473
+ * @returns Array of CSDK CalculatedMeasure objects
474
+ * @throws Error if any formula cannot be converted (includes formula title in error message)
475
+ * @internal
476
+ */
477
+ function translateSharedFormulas(formulas) {
478
+ return formulas.map((formula) => {
479
+ try {
480
+ const result = createDimensionalElementFromJaql(formula);
481
+ if (!('expression' in result && 'context' in result)) {
482
+ throw new Error(`Expected CalculatedMeasure but got ${result.__serializable || 'unknown type'}`);
483
+ }
484
+ return result;
485
+ }
486
+ catch (error) {
487
+ const msg = error instanceof Error ? error.message : 'Unknown error';
488
+ throw new Error(`Failed to translate shared formula "${formula.title}": ${msg}`);
489
+ }
490
+ });
491
+ }
492
+ exports.translateSharedFormulas = translateSharedFormulas;
@@ -5,7 +5,7 @@
5
5
  /* eslint-disable @typescript-eslint/no-unsafe-argument */
6
6
  /* eslint-disable sonarjs/no-nested-switch */
7
7
  import { parseExpression } from '../utils.js';
8
- import { DimensionalElement, normalizeName } from './base.js';
8
+ import { DimensionalElement, normalizeName, wrapIfNeedsNormalization } from './base.js';
9
9
  import { DATA_MODEL_MODULE_NAME } from './consts.js';
10
10
  import { simpleColumnType } from './simple-column-types.js';
11
11
  import { DateLevels, MetadataTypes, Sort, } from './types.js';
@@ -26,9 +26,10 @@ export class DimensionalAttribute extends DimensionalElement {
26
26
  this._sort = Sort.None;
27
27
  this.expression = expression;
28
28
  // if composeCode is not explicitly set by the caller, extract it from expression
29
+ // Use [[delimiters]] to preserve original names that need normalization
29
30
  if (!composeCode && expression) {
30
31
  const { table, column } = parseExpression(expression);
31
- this.composeCode = normalizeAttributeName(table, column, '', DATA_MODEL_MODULE_NAME);
32
+ this.composeCode = `${DATA_MODEL_MODULE_NAME}.${wrapIfNeedsNormalization(table)}.${wrapIfNeedsNormalization(column)}`;
32
33
  }
33
34
  // panel is not needed in most cases, this is to support break by columns functionality
34
35
  if (panel === 'columns') {
@@ -111,9 +112,11 @@ export class DimensionalLevelAttribute extends DimensionalAttribute {
111
112
  this._format = format;
112
113
  this.granularity = granularity;
113
114
  // if composeCode is not explicitly set by the caller, extract it from expression and granularity
115
+ // Use [[delimiters]] to preserve original names that need normalization
114
116
  if (!composeCode && expression) {
115
117
  const { table, column } = parseExpression(expression);
116
- this.composeCode = normalizeAttributeName(table, column, granularity, DATA_MODEL_MODULE_NAME);
118
+ const granularityPart = granularity ? `.${granularity}` : '';
119
+ this.composeCode = `${DATA_MODEL_MODULE_NAME}.${wrapIfNeedsNormalization(table)}.${wrapIfNeedsNormalization(column)}${granularityPart}`;
117
120
  }
118
121
  // panel is not needed in most cases, this is to support break by columns functionality
119
122
  if (panel === 'columns') {
@@ -74,3 +74,32 @@ export declare abstract class DimensionalElement implements Element {
74
74
  * @internal
75
75
  */
76
76
  export declare function normalizeName(name: string): string;
77
+ /**
78
+ * Checks if a name contains characters that would be modified by normalizeName().
79
+ * Includes: spaces, special chars (!@#$%^&*), dots, brackets, or starts with number.
80
+ *
81
+ * @example
82
+ * needsNormalization("Age Range") // true (space)
83
+ * needsNormalization("Cost ($)") // true (special chars)
84
+ * needsNormalization("Rev.2024") // true (dot)
85
+ * needsNormalization("2024Data") // true (starts with number)
86
+ * needsNormalization("Revenue") // false
87
+ *
88
+ * @param name - The name to check
89
+ * @returns true if the name would be modified by normalizeName()
90
+ * @internal
91
+ */
92
+ export declare function needsNormalization(name: string): boolean;
93
+ /**
94
+ * Wraps name in [[delimiters]] if it would be modified by normalizeName().
95
+ * Used to preserve original names in composeCode while marking them for transformation.
96
+ *
97
+ * @example
98
+ * wrapIfNeedsNormalization("Age Range") // "[[Age Range]]"
99
+ * wrapIfNeedsNormalization("Revenue") // "Revenue" (unchanged)
100
+ *
101
+ * @param name - The name to potentially wrap
102
+ * @returns The name wrapped in [[]] if it needs normalization, otherwise unchanged
103
+ * @internal
104
+ */
105
+ export declare function wrapIfNeedsNormalization(name: string): string;
@@ -86,3 +86,39 @@ export function normalizeName(name) {
86
86
  }
87
87
  return normalizedName;
88
88
  }
89
+ /**
90
+ * Checks if a name contains characters that would be modified by normalizeName().
91
+ * Includes: spaces, special chars (!@#$%^&*), dots, brackets, or starts with number.
92
+ *
93
+ * @example
94
+ * needsNormalization("Age Range") // true (space)
95
+ * needsNormalization("Cost ($)") // true (special chars)
96
+ * needsNormalization("Rev.2024") // true (dot)
97
+ * needsNormalization("2024Data") // true (starts with number)
98
+ * needsNormalization("Revenue") // false
99
+ *
100
+ * @param name - The name to check
101
+ * @returns true if the name would be modified by normalizeName()
102
+ * @internal
103
+ */
104
+ export function needsNormalization(name) {
105
+ // Check for invalid characters (anything not a-zA-Z0-9_)
106
+ // Note: dots are also "invalid" as they get replaced with underscores
107
+ // Check if starts with a number (gets prefixed with _)
108
+ return /[^a-zA-Z0-9_]/.test(name) || /^[0-9]/.test(name);
109
+ }
110
+ /**
111
+ * Wraps name in [[delimiters]] if it would be modified by normalizeName().
112
+ * Used to preserve original names in composeCode while marking them for transformation.
113
+ *
114
+ * @example
115
+ * wrapIfNeedsNormalization("Age Range") // "[[Age Range]]"
116
+ * wrapIfNeedsNormalization("Revenue") // "Revenue" (unchanged)
117
+ *
118
+ * @param name - The name to potentially wrap
119
+ * @returns The name wrapped in [[]] if it needs normalization, otherwise unchanged
120
+ * @internal
121
+ */
122
+ export function wrapIfNeedsNormalization(name) {
123
+ return needsNormalization(name) ? `[[${name}]]` : name;
124
+ }
@@ -1,7 +1,7 @@
1
1
  /* eslint-disable sonarjs/no-duplicate-string */
2
2
  import { parseExpression } from '../../utils.js';
3
- import { DimensionalAttribute, DimensionalLevelAttribute, jaqlSimpleColumnType, normalizeAttributeName, } from '../attributes.js';
4
- import { DimensionalElement, normalizeName } from '../base.js';
3
+ import { DimensionalAttribute, DimensionalLevelAttribute, jaqlSimpleColumnType, } from '../attributes.js';
4
+ import { DimensionalElement, normalizeName, wrapIfNeedsNormalization } from '../base.js';
5
5
  import { DATA_MODEL_MODULE_NAME } from '../consts.js';
6
6
  import { DateLevels, MetadataTypes, Sort, } from '../types.js';
7
7
  /**
@@ -20,9 +20,10 @@ export class DimensionalDimension extends DimensionalElement {
20
20
  this._attributes = [];
21
21
  this._sort = Sort.None;
22
22
  // if composeCode is not explicitly set by the caller, extract it from expression
23
+ // Use [[delimiters]] to preserve original names that need normalization
23
24
  if (!composeCode && expression) {
24
25
  const { table, column } = parseExpression(expression);
25
- this.composeCode = normalizeAttributeName(table, column, '', DATA_MODEL_MODULE_NAME);
26
+ this.composeCode = `${DATA_MODEL_MODULE_NAME}.${wrapIfNeedsNormalization(table)}.${wrapIfNeedsNormalization(column)}`;
26
27
  }
27
28
  this._sort = sort || Sort.None;
28
29
  this._expression = expression;
@@ -465,7 +465,9 @@ export function getFilterRelationsFromJaql(filters, highlights, filterRelations)
465
465
  if ('instanceid' in node) {
466
466
  const filter = filters.find((filter) => filter.config.guid === node.instanceid);
467
467
  if (!filter) {
468
- throw new TranslatableError('errors.unknownFilterInFilterRelations');
468
+ throw new TranslatableError('errors.unknownFilterInFilterRelations', {
469
+ filterGuid: node.instanceid,
470
+ });
469
471
  }
470
472
  return filter;
471
473
  }
@@ -153,6 +153,7 @@ export const createAttributeFilterFromConditionFilterJaql = (attribute, conditio
153
153
  }
154
154
  throw new TranslatableError('errors.filter.unsupportedConditionFilter', {
155
155
  filter: JSON.stringify(conditionFilterJaql),
156
+ attributeName: attribute.name,
156
157
  });
157
158
  };
158
159
  /**
@@ -188,5 +189,6 @@ export const createMeasureFilterFromConditionFilterJaql = (measure, conditionFil
188
189
  }
189
190
  throw new TranslatableError('errors.filter.unsupportedConditionFilter', {
190
191
  filter: JSON.stringify(conditionFilterJaql),
192
+ attributeName: measure.name,
191
193
  });
192
194
  };
@@ -130,11 +130,13 @@ export const createFilterFromCustomFilterJaql = (attribute, customFilterJaql, gu
130
130
  * @returns Filter object.
131
131
  */
132
132
  export const createFilterFromJaqlInternal = (jaql, guid) => {
133
+ var _a, _b;
133
134
  try {
134
135
  if ('formula' in jaql) {
135
136
  // generic pass-through JAQL filter will be used instead
136
137
  throw new TranslatableError('errors.filter.formulaFiltersNotSupported', {
137
138
  filter: JSON.stringify(jaql),
139
+ attributeName: (_b = (_a = jaql.title) !== null && _a !== void 0 ? _a : jaql.column) !== null && _b !== void 0 ? _b : jaql.dim,
138
140
  });
139
141
  }
140
142
  const filterJaqlWrapperWithType = extractFilterTypeFromFilterJaql(jaql, jaql.datatype);
@@ -81,10 +81,12 @@ export declare const RankingSortTypes: {
81
81
  * @param title - Title of the measure to be displayed in legend
82
82
  * @param formula - Formula to be used for the measure
83
83
  * @param context - Formula context as a map of strings to attributes, measures, or filters
84
+ * @param format - Optional format string for the measure
85
+ * @param description - Optional description of the measure
84
86
  * @returns A calculated measure instance
85
87
  * @group Advanced Analytics
86
88
  */
87
- export declare const customFormula: (title: string, formula: string, context: CustomFormulaContext) => CalculatedMeasure;
89
+ export declare const customFormula: (title: string, formula: string, context: CustomFormulaContext, format?: string, description?: string) => CalculatedMeasure;
88
90
  /**
89
91
  * Creates an aggregated measure.
90
92
  *
@@ -121,13 +121,15 @@ function measureFunction(measure, name, func, options) {
121
121
  * @param title - Title of the measure to be displayed in legend
122
122
  * @param formula - Formula to be used for the measure
123
123
  * @param context - Formula context as a map of strings to attributes, measures, or filters
124
+ * @param format - Optional format string for the measure
125
+ * @param description - Optional description of the measure
124
126
  * @returns A calculated measure instance
125
127
  * @group Advanced Analytics
126
128
  */
127
- export const customFormula = withComposeCodeForMeasure((title, formula, context) => {
129
+ export const customFormula = withComposeCodeForMeasure((title, formula, context, format, description) => {
128
130
  // context keys must be in brackets
129
131
  const newContext = Object.fromEntries(Object.entries(context).map(([key, val]) => [key.startsWith('[') ? key : `[${key}]`, val]));
130
- return new DimensionalCalculatedMeasure(title, formula, newContext);
132
+ return new DimensionalCalculatedMeasure(title, formula, newContext, format, description);
131
133
  }, 'customFormula');
132
134
  function arithmetic(operand1, operator, operand2, name, withParentheses) {
133
135
  const builder = [];