chore: update types and export HyperCubeHandler (#1779)

* chore: updating types index.d.ts for HyperCubeHanlder

* chore: fix dependencies

* chore: remove nudleus dependecies and fixing comments

* chore: disable no-relative-package import

* chore: fix comments

* chore: update types for data-property and hypercube-handler

* chore: convert more types in data-property-handler to object

* chore: fix more comments

* chore: make all types private

* chore: update lockfile

* chore: make a correction

* chore: fix the classes example import path

---------

Co-authored-by: caele <tsm@qlik.com>
This commit is contained in:
Donya MashaallahPoor
2025-09-11 10:03:26 +02:00
committed by GitHub
parent af5566f40c
commit ccf66ebc50
16 changed files with 1013 additions and 221 deletions

View File

@@ -1,6 +1,7 @@
import hypercube from './hypercube';
import utils from './utils';
import helpers from './helpers';
import arrayUtil from './array-util';
const getType = async ({ halo, name, version }) => {
const { types } = halo;
@@ -118,4 +119,7 @@ const conversion = {
*/
hypercube,
};
export { utils, arrayUtil };
export default conversion;

View File

@@ -42,6 +42,8 @@ export {
useEmitter,
onTakeSnapshot,
onContextMenu,
HyperCubeHandler,
DataPropertyHandler,
} from '@nebula.js/supernova';
// component internals

View File

@@ -4,6 +4,7 @@
"version": "6.0.0-alpha.2",
"main": "src/index.js",
"devDependencies": {
"@nebula.js/conversion": "^6.0.0-alpha.2",
"extend": "3.0.2",
"node-event-emitter": "0.0.1"
}

View File

@@ -34,17 +34,16 @@ describe('DataPropertyHandler', () => {
describe('getDimensions()', () => {
test('should return null when dimension is undefined', () => {
jest.spyOn(handler, 'getDimensions').mockReturnValue([]);
const dimension = handler.getDimension(undefined);
const dimension = handler.getDimension({});
expect(dimension).toBeFalsy();
});
test('should return dimension when it exists in getDimensions()', () => {
jest.spyOn(handler, 'getDimensions').mockReturnValue([{ qDef: { cId: 'dim1' } }]);
jest.spyOn(handler, 'getAlternativeDimensions').mockReturnValue([{ qDef: { cId: 'altDim1' } }]);
const dimension = handler.getDimension('dim1');
const dimension = handler.getDimension({ id: 'dim1' });
expect(dimension).toEqual({ qDef: { cId: 'dim1' } });
const alternativeDimension = handler.getDimension('altDim1');
const alternativeDimension = handler.getDimension({ id: 'altDim1' });
expect(alternativeDimension).toEqual({ qDef: { cId: 'altDim1' } });
});
});
@@ -52,7 +51,7 @@ describe('DataPropertyHandler', () => {
describe('getMeasure()', () => {
test('should return null when both measures and alternative measures are empty', () => {
jest.spyOn(handler, 'getMeasures').mockReturnValue([]);
const measure = handler.getMeasure(undefined);
const measure = handler.getMeasure({});
expect(measure).toBeFalsy();
});
@@ -60,8 +59,8 @@ describe('DataPropertyHandler', () => {
jest.spyOn(handler, 'getMeasures').mockReturnValue([{ qDef: { cId: 'measure1' } }]);
jest.spyOn(handler, 'getAlternativeMeasures').mockReturnValue([{ qDef: { cId: 'altMeasure1' } }]);
const measure = handler.getMeasure('measure1');
const alternativeMeasure = handler.getMeasure('altMeasure1');
const measure = handler.getMeasure({ id: 'measure1' });
const alternativeMeasure = handler.getMeasure({ id: 'altMeasure1' });
expect(measure).toEqual({ qDef: { cId: 'measure1' } });
expect(alternativeMeasure).toEqual({ qDef: { cId: 'altMeasure1' } });
});
@@ -75,31 +74,26 @@ describe('DataPropertyHandler', () => {
});
test('should create a dimension with default properties when no field is provided', () => {
const result = handler.createFieldDimension(null, null, { customDefault: 'value' });
const fieldDimension = { field: '', label: '', defaults: { customDefault: 'value' } };
const result = handler.createFieldDimension(fieldDimension);
expect(result.qDef.qFieldDefs).toEqual([null]);
expect(result.qDef.qFieldDefs).toEqual(['']);
expect(result.qDef.qFieldLabels).toEqual(['']);
expect(result.qDef.qSortCriterias).toEqual(sortingProperties);
expect(result.qDef.autoSort).toBe(true);
expect(result.someProperty).toBe('defaultValue');
expect(result.customDefault).toBe('value');
});
test('should create a library dimension with default properties', () => {
const result = handler.createLibraryDimension('libraryId', { customDefault: 'value' });
expect(result.qLibraryId).toBe('libraryId');
expect(result.qDef.qSortCriterias).toEqual(sortingProperties);
expect(result.qDef.autoSort).toBe(true);
expect(result.someProperty).toBe('defaultValue');
expect(result.customDefault).toBe('value');
expect(result.qOtherTotalSpec).toEqual({});
});
test('should create a dimension with provided field and label', () => {
const result = handler.createFieldDimension('fieldName', 'fieldLabel', { customDefault: 'value' });
const fieldDimension = { field: 'fieldName', label: 'fieldLabel' };
const result = handler.createFieldDimension(fieldDimension);
expect(result.qDef.qFieldDefs).toEqual(['fieldName']);
expect(result.qDef.qFieldLabels).toEqual(['fieldLabel']);
expect(result.qDef.qSortCriterias).toEqual(sortingProperties);
expect(result.qDef.autoSort).toBe(true);
});
});
@@ -110,21 +104,17 @@ describe('DataPropertyHandler', () => {
});
});
test('should delete qFieldDefs and qFieldLabels from the dimension', () => {
const result = handler.createLibraryDimension('libraryId', {});
expect(result.qDef.qFieldDefs).toBeUndefined();
expect(result.qDef.qFieldLabels).toBeUndefined();
});
test('should create a library dimension with default properties', () => {
const result = handler.createLibraryDimension('libraryId', { customDefault: 'value' });
test('should create dimension and delete qFieldDefs and qFieldLabels from it', () => {
const libraryDimension = { id: 'libraryId', defaults: { customDefault: 'value' } };
const result = handler.createLibraryDimension(libraryDimension);
expect(result.qLibraryId).toBe('libraryId');
expect(result.qDef.qSortCriterias).toEqual(sortingProperties);
expect(result.qDef.autoSort).toBe(true);
expect(result.someProperty).toBe('defaultValue');
expect(result.customDefault).toBe('value');
expect(result.qDef.autoSort).toBe(true);
expect(result.qDef.qFieldDefs).toBeUndefined();
expect(result.qDef.qFieldLabels).toBeUndefined();
});
});
@@ -136,7 +126,12 @@ describe('DataPropertyHandler', () => {
});
test('should create a measure with provided expression and label', () => {
const result = handler.createExpressionMeasure('SUM(Sales)', 'Total Sales', { customDefault: 'value' });
const expressionMeasure = {
expression: 'SUM(Sales)',
label: 'Total Sales',
defaults: { customDefault: 'value' },
};
const result = handler.createExpressionMeasure(expressionMeasure);
expect(result.qDef.qDef).toBe('SUM(Sales)');
expect(result.qDef.qLabel).toBe('Total Sales');
@@ -146,14 +141,16 @@ describe('DataPropertyHandler', () => {
});
test('should initialize qDef and qNumFormat if not provided', () => {
const result = handler.createExpressionMeasure('SUM(Sales)', 'Total Sales', {});
const expressionMeasure = { expression: 'SUM(Sales)', label: 'Total Sales', defaults: {} };
const result = handler.createExpressionMeasure(expressionMeasure);
expect(result.qDef).toBeDefined();
expect(result.qDef.qNumFormat).toBeDefined();
});
test('should handle empty defaults gracefully', () => {
const result = handler.createExpressionMeasure('SUM(Sales)', 'Total Sales', null);
const expressionMeasure = { expression: 'SUM(Sales)', label: 'Total Sales' };
const result = handler.createExpressionMeasure(expressionMeasure);
expect(result.qDef.qDef).toBe('SUM(Sales)');
expect(result.qDef.qLabel).toBe('Total Sales');
@@ -170,7 +167,8 @@ describe('DataPropertyHandler', () => {
});
test('should create a library measure with provided id and defaults', () => {
const result = handler.createLibraryMeasure('libraryId', { customDefault: 'value' });
const libraryMeasure = { id: 'libraryId', defaults: { customDefault: 'value' } };
const result = handler.createLibraryMeasure(libraryMeasure);
expect(result.qLibraryId).toBe('libraryId');
expect(result.qDef.qNumFormat).toBeDefined();
@@ -180,14 +178,16 @@ describe('DataPropertyHandler', () => {
});
test('should initialize qDef and qNumFormat if not provided', () => {
const result = handler.createLibraryMeasure('libraryId', {});
const libraryMeasure = { id: 'libraryId', defaults: {} };
const result = handler.createLibraryMeasure(libraryMeasure);
expect(result.qDef).toBeDefined();
expect(result.qDef.qNumFormat).toBeDefined();
});
test('should delete qDef.qDef and qDef.qLabel from the measure', () => {
const result = handler.createLibraryMeasure('libraryId', {});
const libraryMeasure = { id: 'libraryId', defaults: {} };
const result = handler.createLibraryMeasure(libraryMeasure);
expect(result.qDef.qDef).toBeUndefined();
expect(result.qDef.qLabel).toBeUndefined();
@@ -195,14 +195,7 @@ describe('DataPropertyHandler', () => {
});
describe('maxMeasures', () => {
let galaxy;
beforeEach(() => {
galaxy = {
flags: {
isEnabled: jest.fn().mockReturnValue(false),
},
};
handler = new HyperCubeHandler({
measureDefinition: { max: 0 },
dimensionDefinition: { max: 0 },
@@ -221,15 +214,6 @@ describe('DataPropertyHandler', () => {
expect(result).toBe(5);
});
test('should pass properties to measureDefinition.max when feature flag is enabled', () => {
handler.measureDefinition.max = jest.fn().mockReturnValue(5);
galaxy.flags.isEnabled.mockReturnValue(true);
handler.properties = { someProperty: 'value' };
const result = handler.maxMeasures();
expect(result).toBe(5);
});
test('should return measureDefinition.max when it is a valid number', () => {
handler.measureDefinition.max = 8;
@@ -260,14 +244,7 @@ describe('DataPropertyHandler', () => {
});
describe('maxDimensions', () => {
let galaxy;
beforeEach(() => {
galaxy = {
flags: {
isEnabled: jest.fn().mockReturnValue(false),
},
};
handler = new HyperCubeHandler({
measureDefinition: { max: 0 },
dimensionDefinition: { max: 0 },
@@ -282,15 +259,6 @@ describe('DataPropertyHandler', () => {
expect(result).toBe(5);
});
test('should pass properties to dimensionDefinition.max when feature flag is enabled', () => {
handler.dimensionDefinition.max = jest.fn().mockReturnValue(10);
galaxy.flags.isEnabled.mockReturnValue(true);
handler.properties = { someProperty: 'value' };
const result = handler.maxDimensions();
expect(result).toBe(10);
});
test('should return dimensionDefinition.max when it is a valid number', () => {
handler.dimensionDefinition.max = 8;

View File

@@ -1,10 +1,38 @@
import extend from 'extend';
// eslint-disable-next-line import/no-relative-packages
import isEnabled from '../../../nucleus/src/flags/flags';
import { findFieldById, useMasterNumberFormat } from './utils/field-helper/field-utils';
import { findFieldById, initializeDim, useMasterNumberFormat } from './utils/field-helper/field-utils';
import { INITIAL_SORT_CRITERIAS } from './utils/constants';
import { notSupportedError } from './utils/hypercube-helper/hypercube-utils';
/**
* @private
* @class DataPropertyHandler
* @description A class to handle data properties for dimensions and measures in a data model.
* @param {object} opts - Parameters to add a hypercube handlers
* @param {qix.Doc} opts.app
* @param {object} opts.dimensionDefinition
* @param {object} opts.measureDefinition
* @param {object} opts.dimensionProperties
* @param {object} opts.measureProperties
* @param {object} opts.globalChangeListeners
* @entry
* @export
* @example
* import DataPropertyHandler from '@nebula.js/stardust';
*
* class PivotHyperCubeHandler extends DataPropertyHandler {
*
* addDimensionAsFirstRow: (hypercube: HyperCubeDef, dimension: NxDimension) => {
* const dimensions = this.getDimensions().length;
* const { qInterColumnSortOrder } = hypercube;
*
* if(dimensions !== 0 && dimensions < this.maxDimensions()) {
* hypercube.qNoOfLeftDims = 1;
* qInterColumnSortOrder?.unshift(dimensions);
* dimensions.splice(0, 0, dimension);
* }
* }
* }
*/
class DataPropertyHandler {
constructor(opts) {
const options = opts || {};
@@ -18,20 +46,85 @@ class DataPropertyHandler {
this.app = options.app;
}
/**
* @private
* @typeof {object} LibraryDimension
* @property {string} id
* @property {qix.NxDimension=} defaults
*/
/**
* @private
* @typeof {object} FieldDimension
* @property {string} field
* @property {string=} label
* @property {qix.NxDimension=} defaults
*/
/**
* @private
* @typeof {object} LibraryMeasure
* @property {string} id
* @property {qix.NxMeasure=} defaults
*/
/**
* @private
* @typeof {object} ExpressionMeasure
* @property {string} expression
* @property {string=} label
* @property {qix.NxMeasure=} defaults
*/
/**
* Sets the properties for the handler.
* @private
* @param {object=} properties - The properties object to set.
* @description Updates the handler's properties and analysis type flag.
* @memberof DataPropertyHandler
* @example
* handler.setProperties({ metaData: { isAnalysisType: true } });
*/
setProperties(properties) {
this.properties = properties;
this.isAnalysisType = this.properties?.metaData?.isAnalysisType;
}
/**
* Sets the global change listeners.
* @private
* @param {Function[]} arr - Array of listener functions.
* @description Assigns global change listeners to the handler.
* @memberof DataPropertyHandler
* @example
* handler.setGlobalChangeListeners([listener1, listener2]);
*/
setGlobalChangeListeners(arr) {
this.globalChangeListeners = arr;
}
/**
* @private
* @param {object=} layout - The layout object to set.
* @description Sets the layout for the handler.
* @memberof DataPropertyHandler
* @example
* handler.setLayout(layoutObj);
*/
setLayout(layout) {
this.layout = layout;
}
static type() {
/**
* @private
* @throws {Error}
* @description Throws an error indicating the method must be overridden.
* @memberof DataPropertyHandler
* @example
* DataPropertyHandler.type(); // Throws error
*/
// eslint-disable-next-line class-methods-use-this
type() {
throw new Error('Must override this method');
}
@@ -39,56 +132,138 @@ class DataPropertyHandler {
// ---------------DIMENSION---------------
// ---------------------------------------
static getDimensions() {
/**
* @private
* @returns {Array} Empty array.
* @description Returns the default dimension array.
* @memberof DataPropertyHandler
*/
// eslint-disable-next-line class-methods-use-this
getDimensions() {
return [];
}
getDimension(id) {
/**
* Gets a dimension by id from dimensions or alternative dimensions.
* @private
* @param {LibraryDimension} libraryDimension
* @returns {qix.NxDimension} - The found dimension.
* @description Searches for a dimension by id in both main and alternative dimensions.
* @memberof DataPropertyHandler
* @example
* const dim = handler.getDimension({ id: 'dimId' });
*/
getDimension(libraryDimension) {
const dimensions = this.getDimensions();
const alternativeDimensions = this.getAlternativeDimensions();
return findFieldById(dimensions, id) ?? findFieldById(alternativeDimensions, id);
return findFieldById(dimensions, libraryDimension.id) ?? findFieldById(alternativeDimensions, libraryDimension.id);
}
static getAlternativeDimensions() {
/**
* Throws an error indicating the method must be implemented in subclasses.
* @private
* @throws {Error}
* @memberof DataPropertyHandler
*/
// eslint-disable-next-line class-methods-use-this
getAlternativeDimensions() {
throw new Error('Method not implemented.');
}
static addDimension() {
/**
* Throws an error indicating addDimension is not supported in the base class.
* @private
* @throws {Error}
* @memberof DataPropertyHandler
*/
// eslint-disable-next-line class-methods-use-this
addDimension() {
throw notSupportedError;
}
static addDimensions() {
/**
* Throws an error indicating addDimensions is not supported in the base class.
* @private
* @throws {Error}
* @memberof DataPropertyHandler
*/
// eslint-disable-next-line class-methods-use-this
addDimensions() {
throw notSupportedError;
}
static removeDimension() {
/**
* Throws an error indicating removeDimension is not supported in the base class.
* @private
* @throws {Error}
* @memberof DataPropertyHandler
*/
// eslint-disable-next-line class-methods-use-this
removeDimension() {
throw notSupportedError;
}
static removeDimensions() {
/**
* Throws an error indicating removeDimensions is not supported in the base class.
* @private
* @throws {Error}
* @memberof DataPropertyHandler
*/
// eslint-disable-next-line class-methods-use-this
removeDimensions() {
throw notSupportedError;
}
static autoSortDimension() {
/**
* Throws an error indicating autoSortDimension is not supported in the base class.
* @private
* @throws {Error}
* @memberof DataPropertyHandler
*/
// eslint-disable-next-line class-methods-use-this
autoSortDimension() {
throw notSupportedError;
}
static replaceDimension() {
/**
* Throws an error indicating replaceDimension is not supported in the base class.
* @private
* @throws {Error}
* @memberof DataPropertyHandler
*/
// eslint-disable-next-line class-methods-use-this
replaceDimension() {
throw notSupportedError;
}
static getSorting() {
/**
* Throws an error indicating getSorting is not supported in the base class.
* @private
* @throws {Error}
* @memberof DataPropertyHandler
*/
// eslint-disable-next-line class-methods-use-this
getSorting() {
throw notSupportedError;
}
createLibraryDimension(id, defaults) {
const dimension = extend(true, {}, this.dimensionProperties || {}, defaults || {});
/**
* Creates a type of library dimension with a field definition.
* @private
* @param {LibraryDimension} libraryDimension
* @returns {qix.NxDimension} The created dimension object.
* @description Initializes a dimension and applying default properties and sort criteria.
* @memberof DataPropertyHandler
* @example
* const dim = handler.createLibraryDimension({ id:'dimId', { qDef: { cId: 'dim1' } }});
*/
createLibraryDimension(libraryDimension) {
let dimension = extend(true, {}, this.dimensionProperties || {}, libraryDimension.defaults || {});
dimension.qDef = dimension.qDef ?? {};
dimension.qOtherTotalSpec = dimension.qOtherTotalSpec ?? {};
dimension = initializeDim(dimension);
dimension.qLibraryId = id;
dimension.qLibraryId = libraryDimension.id;
dimension.qDef.autoSort = true;
dimension.qDef.qSortCriterias = INITIAL_SORT_CRITERIAS;
@@ -98,87 +273,201 @@ class DataPropertyHandler {
return dimension;
}
createFieldDimension(field, label, defaults) {
const dimension = extend(true, {}, this.dimensionProperties || {}, defaults || {});
/**
* Creates a type of field dimension with a field definition.
* @private
* @param {FieldDimension} fieldDimension
* @returns {qix.NxDimension} The created dimension object.
* @description Initializes a dimension with field definitions, labels, and default properties.
* @memberof DataPropertyHandler
* @example
* handler.createFieldDimension({field: 'currentField', label: 'label'});
*/
createFieldDimension(fieldDimension) {
let dimension = extend(true, {}, this.dimensionProperties || {}, fieldDimension.defaults || {});
dimension.qDef = dimension.qDef ?? {};
dimension.qOtherTotalSpec = dimension.qOtherTotalSpec ?? {};
dimension = initializeDim(dimension);
if (!field) {
if (!fieldDimension.field) {
dimension.qDef.qFieldDefs = [];
dimension.qDef.qFieldLabels = [];
dimension.qDef.qSortCriterias = [];
}
dimension.qDef.qFieldDefs = [field];
dimension.qDef.qFieldLabels = label ? [label] : [''];
dimension.qDef.qFieldDefs = [fieldDimension.field];
dimension.qDef.qFieldLabels = fieldDimension.label ? [fieldDimension.label] : [''];
dimension.qDef.qSortCriterias = INITIAL_SORT_CRITERIAS;
dimension.qDef.autoSort = true;
return dimension;
}
addFieldDimension(field, label, defaults) {
const dimension = this.createFieldDimension(field, label, defaults);
/**
* Adds a field dimension to the handler.
* @private
* @param {FieldDimension} fieldDimension
* @returns {Promise<qix.NxDimension=>} The result of addDimension.
* @description Creates and adds a field dimension.
* @memberof DataPropertyHandler
* @example
* handler.addFieldDimension({field: 'currentField', label: 'label'});
*/
addFieldDimension(fieldDimension) {
const dimension = this.createFieldDimension(fieldDimension);
return this.addDimension(dimension);
}
addFieldDimensions(args) {
const dimensions = args.map(({ field, label, defaults }) => this.createFieldDimension(field, label, defaults));
/**
* @private
* @param {FieldDimension[]} fieldDimensions - Array of field dimension.
* @returns {Promise<qix.NxDimension[]>} The result of addDimensions.
* @description Creates and adds multiple field dimensions.
* @memberof DataPropertyHandler
* @example
* handler.addFieldDimensions([{ field: 'A', label: 'AA' }, { field: 'B', label: 'BB' }]);
*/
addFieldDimensions(fieldDimensions) {
const dimensions = fieldDimensions.map((fieldDimension) => this.createFieldDimension(fieldDimension));
return this.addDimensions(dimensions);
}
addLibraryDimension(id, defaults) {
const dimension = this.createLibraryDimension(id, defaults);
/**
* Adds a library dimension to the handler.
* @private
* @param {LibraryDimension} libraryDimension
* @returns {Promise<qix.NxDimension=>} The result of addDimension.
* @description Creates and adds a library dimension.
* @memberof DataPropertyHandler
* @example
* handler.addLibraryDimension({ id: 'A'});
*/
addLibraryDimension(libraryDimension) {
const dimension = this.createLibraryDimension(libraryDimension);
return this.addDimension(dimension);
}
addLibraryDimensions(args) {
const dimensions = args.map(({ id, defaults }) => this.createLibraryDimension(id, defaults));
/**
* Adds multiple library dimensions to the handler.
* @private
* @param {LibraryDimension[]} libraryDimensions - Array of library dimension.
* @returns {Promise<qix.NxDimension[]>} The result of addDimensions.
* @description Creates and adds multiple library dimensions.
* @memberof DataPropertyHandler
* @example
* handler.addLibraryDimensions([{ id: 'A' }, { id: 'B', defaults: { ... } }]);
*/
addLibraryDimensions(libraryDimensions) {
const dimensions = libraryDimensions.map((libraryDimension) => this.createLibraryDimension(libraryDimension));
const result = this.addDimensions(dimensions);
return result;
}
async addAltLibraryDimensions(args) {
const dimensions = args.map(({ id }) => this.createLibraryDimension(id));
/**
* Adds multiple alternative library dimensions to the handler.
* @private
* @param {LibraryDimension[]} libraryDimensions - Array of library dimension.
* @returns {Promise<qix.NxDimension[]>} The result of addDimensions.
* @description Creates and adds multiple alternative library dimensions.
* @memberof DataPropertyHandler
* @example
* await handler.addAltLibraryDimensions([{ id: 'A' }, { id: 'B', defaults: { ... } }]);
*/
async addAltLibraryDimensions(libraryDimensions) {
const dimensions = libraryDimensions.map((libraryDimension) => this.createLibraryDimension(libraryDimension));
return this.addDimensions(dimensions, true);
}
async addAltFieldDimensions(args) {
const dimensions = args.map(({ field }) => this.createFieldDimension(field));
/**
* Adds multiple alternative field dimensions to the handler.
* @private
* @param {FieldDimension[]} fieldDimensions - Array of field dimension.
* @returns {Promise<qix.NxDimension[]>} The result of addDimensions.
* @description Creates and adds multiple alternative field dimensions.
* @memberof DataPropertyHandler
* @example
* await handler.addAltFieldDimensions([{ field: 'A', label: 'Label A' }, { field: 'B', label: 'Label B' }]);
*/
async addAltFieldDimensions(fieldDimensions) {
const dimensions = fieldDimensions.map((fieldDimension) => this.createFieldDimension(fieldDimension));
return this.addDimensions(dimensions, true);
}
addAlternativeFieldDimension(field, label, defaults) {
const dimension = this.createFieldDimension(field, label, defaults);
/**
* Adds an alternative field dimension to the handler.
* @private
* @param {FieldDimension} fieldDimension
* @returns {Promise<qix.NxDimension=>} The result of addDimension.
* @description Creates and adds an alternative field dimension.
* @memberof DataPropertyHandler
* @example
* handler.addAlternativeFieldDimension({ field: 'A', label: 'Label A' });
*/
addAlternativeFieldDimension(fieldDimension) {
const dimension = this.createFieldDimension(fieldDimension);
return this.addDimension(dimension, true);
}
addAlternativeLibraryDimension(id, defaults) {
const dimension = this.createLibraryDimension(id, defaults);
/**
* Adds an alternative library dimension to the handler.
* @private
* @param {LibraryDimension} libraryDimension
* @returns {Promise<qix.NxDimension=>} The result of addDimension.
* @description Creates and adds an alternative library dimension.
* @memberof DataPropertyHandler
* @example
* handler.addAlternativeLibraryDimension([{ id: 'A' }, { id: 'B', defaults: { ... } }]);
*/
addAlternativeLibraryDimension(libraryDimension) {
const dimension = this.createLibraryDimension(libraryDimension);
return this.addDimension(dimension, true);
}
/**
* Gets the minimum number of dimensions allowed.
* @private
* @returns {number} The minimum number of dimensions.
* @description Returns the minimum number of dimensions allowed by the handler.
* @memberof DataPropertyHandler
* @example
* const min = handler.minDimensions();
*/
minDimensions() {
if (typeof this.dimensionDefinition.min === 'function') {
return this.dimensionDefinition.min.call(null, this.properties, this);
}
return this.dimensionDefinition.min || 0;
}
/**
* Gets the maximum number of dimensions allowed.
* @private
* @param {number} [decrement=0] - The number to decrement from the current number of measures.
* @returns {number} The maximum number of dimensions allowed.
* @description Checks if the max property is a function and calls it with the current number of measures, or returns a default value.
* @memberof DataPropertyHandler
* @example
* const max = handler.maxDimensions();
*/
maxDimensions(decrement = 0) {
const measureLength = this.getMeasures().length - decrement;
if (typeof this.dimensionDefinition.max === 'function') {
const dimParams = isEnabled('PS_21371_ANALYSIS_TYPES') ? [measureLength, this.properties] : [measureLength];
const dimParams = [measureLength];
return this.dimensionDefinition.max?.apply(null, dimParams);
}
return Number.isNaN(+this.dimensionDefinition.max) ? 10000 : this.dimensionDefinition.max;
}
/**
* Checks if a new dimension can be added.
* @private
* @returns {boolean} True if a new dimension can be added, false otherwise.
* @description Returns whether the handler can add another dimension.
* @memberof DataPropertyHandler
* @example
* if (handler.canAddDimension()) { handler.addFieldDimension('A'); }
*/
canAddDimension() {
return this.getDimensions().length < this.maxDimensions();
}
@@ -187,80 +476,183 @@ class DataPropertyHandler {
// ----------------MEASURE----------------
// ---------------------------------------
getMeasure(id) {
const measures = this.getMeasures();
const alternativeMeasures = this.getAlternativeMeasures();
return findFieldById(measures, id) ?? findFieldById(alternativeMeasures, id);
}
static getMeasures() {
/**
* @private
* @returns {Array} Empty array.
* @description Returns the default measure array.
* @memberof DataPropertyHandler
*/
// eslint-disable-next-line class-methods-use-this
getMeasures() {
return [];
}
static getAlternativeMeasures() {
/**
* Throws an error indicating the method must be implemented in subclasses.
* @private
* @throws {Error}
* @memberof DataPropertyHandler
*/
// eslint-disable-next-line class-methods-use-this
getAlternativeMeasures() {
throw new Error('Method not implemented.');
}
static addMeasure() {
/**
* Throws an error indicating addMeasure is not supported in the base class.
* @private
* @throws {Error}
* @memberof DataPropertyHandler
*/
// eslint-disable-next-line class-methods-use-this
addMeasure() {
throw notSupportedError;
}
static addMeasures() {
/**
* Throws an error indicating addMeasures is not supported in the base class.
* @private
* @throws {Error}
* @memberof DataPropertyHandler
*/
// eslint-disable-next-line class-methods-use-this
addMeasures() {
throw notSupportedError;
}
static removeMeasure() {
/**
* Throws an error indicating removeMeasure is not supported in the base class.
* @private
* @throws {Error}
* @memberof DataPropertyHandler
*/
// eslint-disable-next-line class-methods-use-this
removeMeasure() {
throw notSupportedError;
}
static removeMeasures() {
/**
* Throws an error indicating removeMeasures is not supported in the base class.
* @private
* @throws {Error}
* @memberof DataPropertyHandler
*/
// eslint-disable-next-line class-methods-use-this
removeMeasures() {
throw notSupportedError;
}
static autoSortMeasure() {
/**
* Throws an error indicating autoSortMeasure is not supported in the base class.
* @private
* @throws {Error}
* @memberof DataPropertyHandler
*/
// eslint-disable-next-line class-methods-use-this
autoSortMeasure() {
throw notSupportedError;
}
static replaceMeasure() {
/**
* Throws an error indicating replaceMeasure is not supported in the base class.
* @private
* @throws {Error}
* @memberof DataPropertyHandler
*/
// eslint-disable-next-line class-methods-use-this
replaceMeasure() {
throw notSupportedError;
}
createExpressionMeasure(expression, label, defaults) {
const measure = extend(true, {}, this.measureProperties || {}, defaults || {});
/**
* Gets a measure by id from measures or alternative measures.
* @private
* @param {string} libraryDimension - The measure id to find.
* @returns {qix.NxMeasure} The found measure or undefined.
* @description Searches for a measure by id in both main and alternative measures.
* @memberof DataPropertyHandler
* @example
* const measure = handler.getMeasure('measId');
*/
getMeasure(libraryDimension) {
const measures = this.getMeasures();
const alternativeMeasures = this.getAlternativeMeasures();
return findFieldById(measures, libraryDimension.id) ?? findFieldById(alternativeMeasures, libraryDimension.id);
}
/**
* Creates an expression measure.
* @private
* @param {ExpressionMeasure} expressionMeasure
* @returns {Promise<qix.NxMeasure=>} The created measure object.
* @description Initializes a measure with an expression, label, and default properties.
* @memberof DataPropertyHandler
* @example
* const meas = handler.createExpressionMeasure({ expression: 'Sum(Sales)', label: 'Total Sales' });
*/
createExpressionMeasure(expressionMeasure) {
const measure = extend(true, {}, this.measureProperties || {}, expressionMeasure.defaults || {});
measure.qDef = measure.qDef ?? {};
measure.qDef.qNumFormat = measure.qDef.qNumFormat ?? {};
measure.qDef.qDef = expression;
measure.qDef.qLabel = label;
measure.qDef.qDef = expressionMeasure.expression;
measure.qDef.qLabel = expressionMeasure.label;
measure.qDef.autoSort = true;
return measure;
}
addExpressionMeasure(expression, label, defaults) {
const measure = this.createExpressionMeasure(expression, label, defaults);
/**
* Adds an expression measure to the handler.
* @private
* @param {ExpressionMeasure} expressionMeasure
* @returns {Promise<qix.NxMeasure=>} The result of addMeasure.
* @description Creates and adds an expression measure.
* @memberof DataPropertyHandler
* @example
* handler.addExpressionMeasure({ expression: 'Sum(Sales)', label: 'Total Sales' });
*/
addExpressionMeasure(expressionMeasure) {
const measure = this.createExpressionMeasure(expressionMeasure);
return this.addMeasure(measure);
}
addExpressionMeasures(args) {
const measures = args.map(({ expression, label, defaults }) =>
this.createExpressionMeasure(expression, label, defaults)
);
/**
* Adds multiple expression measures to the handler.
* @private
* @param {ExpressionMeasure[]} expressionMeasures - Array of expression measure.
* @returns {Promise<qix.NxMeasure[]>} The result of addMeasures.
* @description Creates and adds multiple expression measures.
* @memberof DataPropertyHandler
* @example
* handler.addExpressionMeasures([{ expression: 'Sum(A)' }, { expression: 'Sum(B)', label: 'B' }]);
*/
addExpressionMeasures(expressionMeasures) {
const measures = expressionMeasures.map((expressionMeasure) => this.createExpressionMeasure(expressionMeasure));
return this.addMeasures(measures);
}
createLibraryMeasure(id, defaults) {
const measure = extend(true, {}, this.measureProperties || {}, defaults || {});
/**
* Creates a library measure.
* @private
* @param {LibraryMeasure} libraryMeasure
* @returns {qix.NxMeasure} The created measure object.
* @description Initializes a library measure with default properties.
* @memberof DataPropertyHandler
* @example
* const meas = handler.createLibraryMeasure({ id: 'measId', defaults: { qDef: { ... } } });
*/
createLibraryMeasure(libraryMeasure) {
const measure = extend(true, {}, this.measureProperties || {}, libraryMeasure.defaults || {});
measure.qDef = measure.qDef ?? {};
measure.qDef.qNumFormat = measure.qDef.qNumFormat ?? {};
if (isEnabled('MASTER_MEASURE_FORMAT')) {
useMasterNumberFormat(measure.qDef);
}
useMasterNumberFormat(measure.qDef);
measure.qLibraryId = id;
measure.qLibraryId = libraryMeasure.id;
measure.qDef.autoSort = true;
delete measure.qDef.qDef;
@@ -269,36 +661,105 @@ class DataPropertyHandler {
return measure;
}
addLibraryMeasure(id, defaults) {
const measure = this.createLibraryMeasure(id, defaults);
/**
* Adds a library measure to the handler.
* @private
* @param {LibraryMeasure} libraryMeasure
* @returns {Promise<qix.NxMeasure=>} The result of addMeasure.
* @description Creates and adds a library measure.
* @memberof DataPropertyHandler
* @example
* handler.addLibraryMeasure({ id: 'measId', defaults: { qDef: { ... } } });
*/
addLibraryMeasure(libraryMeasure) {
const measure = this.createLibraryMeasure(libraryMeasure);
return this.addMeasure(measure);
}
addLibraryMeasures(args) {
const measures = args.map(({ id, defaults }) => this.createLibraryMeasure(id, defaults));
/**
* Adds multiple library measures to the handler.
* @private
* @param {LibraryMeasure[]} libraryMeasures - Array of library measure.
* @returns {Promise<qix.NxMeasure[]>} The result of addMeasures.
* @description Creates and adds multiple library measures.
* @memberof DataPropertyHandler
* @example
* handler.addLibraryMeasures([{ id: 'A' }, { id: 'B', defaults: { qDef: { ... } } }]);
*/
addLibraryMeasures(libraryMeasures) {
const measures = libraryMeasures.map((libraryMeasure) => this.createLibraryMeasure(libraryMeasure));
return this.addMeasures(measures);
}
addAltLibraryMeasures(args) {
const measures = args.map(({ id }) => this.createLibraryMeasure(id));
/**
* Adds multiple alternative library measures to the handler.
* @private
* @param {LibraryMeasure[]} libraryMeasures - Array of library measure.
* @returns {Promise<qix.NxMeasure[]>} The result of addMeasures.
* @description Creates and adds multiple alternative library measures.
* @memberof DataPropertyHandler
* @example
* handler.addAltLibraryMeasures([{ id: 'A' }, { id: 'B' }]);
*/
addAltLibraryMeasures(libraryMeasures) {
const measures = libraryMeasures.map((libraryMeasure) => this.createLibraryMeasure(libraryMeasure));
return this.addMeasures(measures, true);
}
addAltExpressionMeasures(args) {
const measures = args.map(({ expression }) => this.createExpressionMeasure(expression));
/**
* Adds multiple alternative expression measures to the handler.
* @private
* @param {ExpressionMeasure[]} expressionMeasures - Array of expression measure.
* @returns {Promise<qix.NxMeasure[]>} The result of addMeasures.
* @description Creates and adds multiple alternative expression measures.
* @memberof DataPropertyHandler
* @example
* handler.addAltExpressionMeasures([{ expression: 'Sum(A)' }, { expression: 'Sum(B)' }]);
*/
addAltExpressionMeasures(libraryMeasures) {
const measures = libraryMeasures.map((expressionMeasure) => this.createExpressionMeasure(expressionMeasure));
return this.addMeasures(measures, true);
}
addAlternativeExpressionMeasure(expression, label, defaults) {
const measure = this.createExpressionMeasure(expression, label, defaults);
/**
* Adds an alternative expression measure to the handler.
* @private
* @param {ExpressionMeasure} expressionMeasure
* @returns {Promise<qix.NxMeasure=>} The result of addMeasure.
* @description Creates and adds an alternative expression measure.
* @memberof DataPropertyHandler
* @example
* handler.addAlternativeExpressionMeasure({ expression: 'Sum(Sales)', label: 'Total Sales'});
*/
addAlternativeExpressionMeasure(expressionMeasure) {
const measure = this.createExpressionMeasure(expressionMeasure);
return this.addMeasure(measure, true);
}
addAlternativeLibraryMeasure(id, defaults) {
const measure = this.createLibraryMeasure(id, defaults);
/**
* Adds an alternative library measure to the handler.
* @private
* @param {LibraryMeasure} libraryMeasure
* @returns {qix.NxMeasure=} The result of addMeasure.
* @description Creates and adds an alternative library measure.
* @memberof DataPropertyHandler
* @example
* handler.addAlternativeLibraryMeasure({ id: 'measId', defaults: { qDef: { ... } } });
*/
addAlternativeLibraryMeasure(libraryMeasure) {
const measure = this.createLibraryMeasure(libraryMeasure);
return this.addMeasure(measure, true);
}
/**
* Gets the minimum number of measures allowed.
* @private
* @returns {number} The minimum number of measures.
* @description Returns the minimum number of measures allowed by the handler.
* @memberof DataPropertyHandler
* @example
* const min = handler.minMeasures();
*/
minMeasures() {
if (typeof this.measureDefinition.min === 'function') {
return this.measureDefinition.min.call(null, this.properties, this);
@@ -306,24 +767,51 @@ class DataPropertyHandler {
return this.measureDefinition.min || 0;
}
/**
* Gets the maximum number of measures allowed.
* @private
* @param {number} [decrement=0] - The number to decrement from the current number of dimensions.
* @returns {number} The maximum number of measures allowed.
* @description Checks if the max property is a function and calls it with the current number of dimensions, or returns a default value.
* @memberof DataPropertyHandler
* @example
* const max = handler.maxMeasures();
*/
maxMeasures(decrement = 0) {
if (typeof this.measureDefinition.max === 'function') {
const dimLength = this.getDimensions().length - decrement;
const measureParams = isEnabled('PS_21371_ANALYSIS_TYPES') ? [dimLength, this.properties] : [dimLength];
const measureParams = [dimLength];
return this.measureDefinition.max.apply(null, measureParams);
}
return Number.isNaN(+this.measureDefinition.max) ? 10000 : this.measureDefinition.max;
}
/**
* Checks if a new measure can be added.
* @private
* @returns {boolean} True if a new measure can be added, false otherwise.
* @description Returns whether the handler can add another measure.
* @memberof DataPropertyHandler
* @example
* if (handler.canAddMeasure()) { handler.addExpressionMeasure('Sum(A)'); }
*/
canAddMeasure() {
return this.getMeasures().length < this.maxMeasures();
// return this.getMeasures().length < 10000;
}
// ---------------------------------------
// ---------------OTHERS------------------
// ---------------------------------------
/**
* Calls all global change listeners with the current properties, handler, and layout.
* @private
* @param {object} layout - The layout object to pass to listeners.
* @description Invokes all registered global change listeners.
* @memberof DataPropertyHandler
* @example
* handler.updateGlobalChangeListeners(layoutObj);
*/
updateGlobalChangeListeners(layout) {
if (this.globalChangeListeners) {
(this.globalChangeListeners || []).forEach((func) => {

View File

@@ -1,10 +1,9 @@
// eslint-disable-next-line import/no-relative-packages
import utils from '../../../conversion/src/utils';
import { utils, arrayUtil } from '@nebula.js/conversion';
import DataPropertyHandler from './data-property-handler';
import * as hcUtils from './utils/hypercube-helper/hypercube-utils';
import getAutoSortLibraryDimension from './utils/field-helper/get-sorted-library-field';
import getAutoSortFieldDimension from './utils/field-helper/get-sorted-field';
import { initializeField, initializeId } from './utils/field-helper/field-utils';
import getAutoSortDimension from './utils/field-helper/get-sorted-field';
import { initializeDim, initializeId } from './utils/field-helper/field-utils';
import addMainDimension from './utils/hypercube-helper/add-main-dimension';
import addMainMeasure from './utils/hypercube-helper/add-main-measure';
import removeMainDimension from './utils/hypercube-helper/remove-main-dimension';
@@ -13,18 +12,55 @@ import removeMainMeasure from './utils/hypercube-helper/remove-main-measure';
import removeAlternativeDimension from './utils/hypercube-helper/remove-alternative-dimension';
import reinsertMainDimension from './utils/hypercube-helper/reinsert-main-dimension';
import reinsertMainMeasure from './utils/hypercube-helper/reinsert-main-measure';
// eslint-disable-next-line import/no-relative-packages
import arrayUtil from '../../../conversion/src/array-util';
/**
* HyperCubeHandler for managing hypercube data structure.
* @private
* @class HyperCubeHandler
* @description This class provides methods to handle hypercube properties, dimensions, and measures.
* @param {object} opts Parameters to add a hypercube handlers
* @param {qix.Doc} opts.app
* @param {object} opts.dimensionDefinition
* @param {object} opts.measureDefinition
* @param {object} opts.dimensionProperties
* @param {object} opts.measureProperties
* @param {object} opts.globalChangeListeners
* @param {object} opts.path
* @entry
* @export
* @example
* import { HyperCubeHandler } from '@nebula.js/stardust';
*
* class PivotHyperCubeHandler extends HyperCubeHandler {
*
* adjustPseudoDimOrder: (pseudoIdx?: number) => {
* const numberOfDims = this.getDimensions().length;
* const interColumnSortOrder = this.hcProperties.qInterColumnSortOrder;
*
* if (!interColumnSortOrder) {
* return;
* }
*
* interColumnSortOrder.splice(pseudoIdx || 0, 1);
* interColumnSortOrder.push(numberOfDims);
* interColumnSortOrder.splice((pseudoIdx || -1) + 1, 0, -1);
* };
* }
*/
class HyperCubeHandler extends DataPropertyHandler {
constructor(opts) {
super(opts);
this.path = opts.path;
}
/**
* @private
* @param {object=} properties
* @returns early return if properties is falsy
*/
setProperties(properties) {
if (!properties) {
return {};
return;
}
super.setProperties(properties);
@@ -32,7 +68,7 @@ class HyperCubeHandler extends DataPropertyHandler {
this.hcProperties = this.path ? utils.getValue(properties, `${this.path}.qHyperCubeDef`) : properties.qHyperCubeDef;
if (!this.hcProperties) {
return {};
return;
}
hcUtils.setDefaultProperties(this);
@@ -41,32 +77,76 @@ class HyperCubeHandler extends DataPropertyHandler {
// Set auto-sort property (compatibility 0.85 -> 0.9), can probably be removed in 1.0
this.hcProperties.qDimensions = hcUtils.setFieldProperties(this.hcProperties.qDimensions);
this.hcProperties.qMeasures = hcUtils.setFieldProperties(this.hcProperties.qMeasures);
return {};
}
// ----------------------------------
// ----------- DIMENSIONS -----------
// ----------------------------------
/**
* @private
* @returns {qix.NxDimension[]} dimensions
* @description Returns the dimensions of the hypercube.
* @memberof HyperCubeHandler
* @example
* const dimensions = hyperCubeHandler.getDimensions();
*/
getDimensions() {
return this.hcProperties ? this.hcProperties.qDimensions : [];
}
/**
* @private
* @returns {qix.NxDimension[]} alternative dimensions
* @description Returns the alternative dimensions of the hypercube.
* @memberof HyperCubeHandler
* @example
* const alternativeDimensions = hyperCubeHandler.getAlternativeDimensions();
*/
getAlternativeDimensions() {
return this.hcProperties?.qLayoutExclude?.qHyperCubeDef?.qDimensions ?? [];
}
/**
* @private
* @param {string} cId
* @returns {qix.NxDimensionInfo} dimension layout
* @description Returns the dimension layout of the hypercube for a given cId.
* @memberof HyperCubeHandler
* @example
* const dimensionLayout = hyperCubeHandler.getDimensionLayout(cId);
*/
getDimensionLayout(cId) {
return this.getDimensionLayouts().filter((item) => cId === item.cId)[0];
}
/**
* @private
* @returns {qix.NxDimensionInfo[]} dimension layouts
* @description Returns the dimension layouts of the hypercube.
* @memberof HyperCubeHandler
* @example
* const dimensionLayouts = hyperCubeHandler.getDimensionLayouts();
*/
getDimensionLayouts() {
const hc = hcUtils.getHyperCube(this.layout, this.path);
return hc ? hc.qDimensionInfo : [];
}
/**
* @private
* @param {qix.NxDimension} dimension
* @param {boolean} alternative
* @param {number=} idx
* @returns {qix.NxDimension} dimension
* @description Adds a dimension to the hypercube and updates the orders of the dimensions.
* If the dimension is an alternative, it will be added to the alternative dimensions.
* @memberof HyperCubeHandler
* @example
* const dimension = hyperCubeHandler.addDimension(dimension, alternative, idx);
*/
addDimension(dimension, alternative, idx) {
const dim = initializeField(dimension);
const dim = initializeDim(dimension);
if (hcUtils.isDimensionAlternative(this, alternative)) {
return hcUtils.addAlternativeDimension(this, dim, idx);
@@ -75,6 +155,18 @@ class HyperCubeHandler extends DataPropertyHandler {
return addMainDimension(this, dim, idx);
}
/**
* @private
* @param {qix.NxDimension[]} dimensions
* @param {boolean} alternative
* @returns {qix.NxDimension[]} added dimensions
* @description Adds multiple dimensions to the hypercube.
* If the dimensions are alternatives, they will be added to the alternative dimensions.
* If the total number of dimensions exceeds the limit, it will stop adding dimensions.
* @memberof HyperCubeHandler
* @example
* const addedDimensions = await hyperCubeHandler.addDimensions(dimensions, alternative);
*/
async addDimensions(dimensions, alternative = false) {
const existingDimensions = this.getDimensions();
const initialLength = existingDimensions.length;
@@ -87,7 +179,7 @@ class HyperCubeHandler extends DataPropertyHandler {
return addedDimensions;
}
const dim = initializeField(dimension);
const dim = initializeDim(dimension);
if (hcUtils.isDimensionAlternative(this, alternative)) {
const altDim = await hcUtils.addAlternativeDimension(this, dim);
@@ -101,6 +193,16 @@ class HyperCubeHandler extends DataPropertyHandler {
return addedDimensions;
}
/**
* @private
* @param {number} idx
* @param {boolean} alternative
* @description Removes a dimension from the hypercube by index.
* If the dimension is an alternative, it will be removed from the alternative dimensions.
* @memberof HyperCubeHandler
* @example
* hyperCubeHandler.removeDimension(idx, alternative);
*/
removeDimension(idx, alternative) {
if (alternative) {
removeAlternativeDimension(this, idx);
@@ -109,6 +211,18 @@ class HyperCubeHandler extends DataPropertyHandler {
removeMainDimension(this, idx);
}
/**
* @private
* @param {number[]} indexes
* @param {boolean} alternative
* @returns {qix.NxDimension[]} deleted dimensions
* @description Removes multiple dimensions from the hypercube by indexes.
* If the dimensions are alternatives, they will be removed from the alternative dimensions.
* If the indexes are empty, it will return an empty array.
* @memberof HyperCubeHandler
* @example
* const deletedDimensions = await hyperCubeHandler.removeDimensions(indexes, alternative);
*/
async removeDimensions(indexes, alternative) {
const altDimensions = this.getAlternativeDimensions();
const dimensions = this.getDimensions();
@@ -137,10 +251,31 @@ class HyperCubeHandler extends DataPropertyHandler {
return deletedDimensions;
}
/**
* Replaces a dimension in the hypercube.
* @private
* @param {number} index - The index of the dimension to replace.
* @param {qix.NxDimension} dimension - The new dimension to replace the old one.
* @returns {Promise<qix.NxDimension>} replaced dimension.
* @memberof HyperCubeHandler
* @example
* const replacedDimension = await hyperCubeHandler.replaceDimension(index, newDimension);
*/
replaceDimension(index, dimension) {
return this.autoSortDimension(dimension).then(() => hcUtils.replaceDimensionOrder(this, index, dimension));
}
/**
* Reinserts a dimension into the hypercube.
* @private
* @param {qix.NxDimension} dimension - The dimension to reinsert.
* @param {boolean} alternative - Whether the dimension is an alternative.
* @param {number} idx - The index to insert the dimension at.
* @returns {Promise<qix.NxDimension>} The reinserted dimension.
* @memberof HyperCubeHandler
* @example
* await hyperCubeHandler.reinsertDimension(dimension, alternative, idx);
*/
reinsertDimension(dimension, alternative, idx) {
const dim = initializeId(dimension);
@@ -153,19 +288,26 @@ class HyperCubeHandler extends DataPropertyHandler {
return reinsertMainDimension(this, dim, idx);
}
/**
* Moves a dimension within the hypercube.
* @private
* @param {number} fromIndex - The current index of the dimension.
* @param {number} toIndex - The new index of the dimension.
* @returns {Promise<qix.NxDimension[]>} updated dimensions.
* @memberof HyperCubeHandler
* @example
* await hyperCubeHandler.moveDimension(fromIndex, toIndex);
*/
moveDimension(fromIndex, toIndex) {
const dimensions = this.getDimensions();
const altDimensions = this.getAlternativeDimensions();
if (fromIndex < dimensions.length && toIndex < dimensions.length) {
// Move within main dimensions
return Promise.resolve(arrayUtil.move(dimensions, fromIndex, toIndex));
}
if (fromIndex < dimensions.length && toIndex >= dimensions.length) {
return hcUtils.moveDimensionFromMainToAlternative(fromIndex, toIndex, dimensions, altDimensions);
}
if (fromIndex >= dimensions.length && toIndex < dimensions.length) {
return Promise.resolve(hcUtils.moveMeasureFromAlternativeToMain(fromIndex, toIndex, dimensions, altDimensions));
}
@@ -173,45 +315,112 @@ class HyperCubeHandler extends DataPropertyHandler {
return Promise.resolve(hcUtils.moveDimensionWithinAlternative(fromIndex, toIndex, dimensions, altDimensions));
}
/**
* @private
* @param {qix.NxDimension} dimension
* @returns {qix.NxDimension} dimension with auto-sort properties
* @description Automatically sorts the dimension based on its properties.
* If the dimension has a qLibraryId, it will use the library dimension auto-sort.
* Otherwise, it will use the field dimension auto-sort.
* @memberof HyperCubeHandler
* @example
* const sortedDimension = hyperCubeHandler.autoSortDimension(dimension);
*/
autoSortDimension(dimension) {
if (dimension.qLibraryId) {
return getAutoSortLibraryDimension(this, dimension);
}
return getAutoSortFieldDimension(this, dimension);
return getAutoSortDimension(this, dimension);
}
// ----------------------------------
// ------------ MEASURES ------------
// ----------------------------------
/**
* @private
* @returns {qix.NxMeasure[]} measures
* @description Returns the measures of the hypercube.
* @memberof HyperCubeHandler
* @example
* const measures = hyperCubeHandler.getMeasures();
*/
getMeasures() {
return this.hcProperties ? this.hcProperties.qMeasures : [];
}
/**
* @private
* @returns {qix.NxMeasure[]} alternative measures
* @description Returns the alternative measures of the hypercube.
* @memberof HyperCubeHandler
* @example
* const alternativeMeasures = hyperCubeHandler.getAlternativeMeasures();
*/
getAlternativeMeasures() {
return this.hcProperties?.qLayoutExclude?.qHyperCubeDef?.qMeasures ?? [];
}
/**
* @private
* @returns {qix.NxMeasureInfo[]} measure layouts
* @description Returns the measure layouts of the hypercube.
* @memberof HyperCubeHandler
* @example
* const measureLayouts = hyperCubeHandler.getMeasureLayouts();
*/
getMeasureLayouts() {
const hc = hcUtils.getHyperCube(this.layout, this.path);
return hc ? hc.qMeasureInfo : [];
}
/**
* @private
* @param {string} cId
* @returns {object} measure layout
* @description Returns the measure layout of the hypercube for a given cId.
* @memberof HyperCubeHandler
* @example
* const measureLayout = hyperCubeHandler.getMeasureLayout(cId);
*/
getMeasureLayout(cId) {
return this.getMeasureLayouts().filter((item) => cId === item.cId)[0];
}
/**
* @private
* @param {qix.NxMeasure} measure
* @param {boolean} alternative
* @param {number=} idx
* @returns {Promise<qix.NxMeasure>} measure
* @description Adds a measure to the hypercube.
* If the measure is an alternative, it will be added to the alternative measures.
* If the total number of measures exceeds the limit, it will stop adding measures.
* @memberof HyperCubeHandler
* @example
* const measure = hyperCubeHandler.addMeasure(measure, alternative, idx);
*/
addMeasure(measure, alternative, idx) {
const measures = this.getAlternativeMeasure();
const meas = initializeId(measure);
if (hcUtils.isMeasureAlternative(this, measures, alternative)) {
return hcUtils.addAlternativeMeasure(this, meas, idx);
if (hcUtils.isMeasureAlternative(this, alternative)) {
const hcMeasures = this.hcProperties.qLayoutExclude.qHyperCubeDef.qMeasures;
return hcUtils.addAlternativeMeasure(meas, hcMeasures, idx);
}
return addMainMeasure(this, meas, idx);
}
/**
* @private
* @param {qix.NxMeasure} measure
* @returns {Promise<qix.NxMeasure>} measure with auto-sort properties
* @description Automatically sorts the measure based on its properties.
* It sets the qSortByLoadOrder and qSortByNumeric properties.
* @memberof HyperCubeHandler
* @example
* const sortedMeasure = hyperCubeHandler.autoSortMeasure(measure);
*/
// eslint-disable-next-line class-methods-use-this
autoSortMeasure(measure) {
const meas = measure;
@@ -222,6 +431,18 @@ class HyperCubeHandler extends DataPropertyHandler {
return Promise.resolve(meas);
}
/**
* @private
* @param {qix.NxMeasure[]} measures
* @param {boolean} alternative
* @returns {qix.NxMeasure[]} added measures
* @description Adds multiple measures to the hypercube.
* If the measures are alternatives, they will be added to the alternative measures.
* If the total number of measures exceeds the limit, it will stop adding measures.
* @memberof HyperCubeHandler
* @example
* const addedMeasures = await hyperCubeHandler.addMeasures(measures, alternative);
*/
addMeasures(measures, alternative = false) {
const existingMeasures = this.getMeasures();
const addedMeasures = [];
@@ -234,7 +455,7 @@ class HyperCubeHandler extends DataPropertyHandler {
const meas = initializeId(measure);
if (hcUtils.isMeasureAlternative(this, existingMeasures, alternative)) {
if (hcUtils.isMeasureAlternative(this, alternative)) {
hcUtils.addAlternativeMeasure(this, meas);
addedMeasures.push(meas);
} else if (existingMeasures.length < this.maxMeasures()) {
@@ -246,6 +467,16 @@ class HyperCubeHandler extends DataPropertyHandler {
return addedMeasures;
}
/**
* @private
* @param {number} idx
* @param {boolean} alternative
* @description Removes a measure from the hypercube by index.
* If the measure is an alternative, it will be removed from the alternative measures.
* @memberof HyperCubeHandler
* @example
* hyperCubeHandler.removeMeasure(idx, alternative);
*/
removeMeasure(idx, alternative) {
if (alternative) {
hcUtils.removeAltMeasureByIndex(this, idx);
@@ -253,13 +484,25 @@ class HyperCubeHandler extends DataPropertyHandler {
removeMainMeasure(this, idx);
}
/**
* @private
* @param {number[]} indexes
* @param {boolean} alternative
* @returns {Promise<number[]>} deleted measures
* @description Removes multiple measures from the hypercube by indexes.
* If the measures are alternatives, they will be removed from the alternative measures.
* If the indexes are empty, it will return an empty array.
* @memberof HyperCubeHandler
* @example
* const deletedMeasures = await hyperCubeHandler.removeMeasures(indexes, alternative);
*/
async removeMeasures(indexes, alternative) {
const measures = this.getMeasures();
const altMeasures = this.getAlternativeMeasures();
if (indexes.length === 0) return [];
let deletedMeasures = [];
if (indexes.length === 0) return deletedMeasures;
if (alternative && altMeasures.length > 0) {
// Keep the original deleted order
deletedMeasures = hcUtils.getDeletedFields(altMeasures, indexes);
@@ -278,21 +521,52 @@ class HyperCubeHandler extends DataPropertyHandler {
return deletedMeasures;
}
/**
* @private
* @param {number} index
* @param {qix.NxMeasure} measure
* @returns {Promise<qix.NxMeasure>} replaced measure
* @description Replaces a measure in the hypercube.
* @memberof HyperCubeHandler
* @example
* const updatedMeasure = await hyperCubeHandler.replaceMeasure(index, measure);
*/
replaceMeasure(index, measure) {
return this.autoSortMeasure(measure).then(() => hcUtils.replaceMeasureToColumnOrder(this, index, measure));
}
/**
* @private
* @param {qix.NxMeasure} measure
* @param {boolean} alternative
* @param {number} idx
* @returns {Promise<qix.NxMeasure>} reinserted measure
* @description Reinserts a measure into the hypercube.
* @memberof HyperCubeHandler
* @example
* const reinsertedMeasure = await hyperCubeHandler.reinsertMeasure(measure, alternative, idx);
*/
reinsertMeasure(measure, alternative, idx) {
const measures = this.getAlternativeMeasures();
const meas = initializeId(measure);
if (hcUtils.isMeasureAlternative(this, measures, alternative)) {
if (hcUtils.isMeasureAlternative(this, alternative)) {
return hcUtils.addAlternativeMeasure(this, meas, idx);
}
return reinsertMainMeasure(this, meas, idx);
}
/**
* Moves a measure within the hypercube.
* @private
* @param {number} fromIndex
* @param {number} toIndex
* @returns {Promise<void>}
* @description Move measure from one index to another
* @memberof HyperCubeHandler
* @example
* const result = await hyperCubeHandler.moveMeasure(0, 1);
*/
moveMeasure(fromIndex, toIndex) {
const measures = this.getMeasures();
const altMeasures = this.getAlternativeMeasures();
@@ -317,31 +591,74 @@ class HyperCubeHandler extends DataPropertyHandler {
// ------------ OTHERS---- ----------
// ----------------------------------
setSorting(ar) {
if (ar && ar.length === this.hcProperties.qInterColumnSortOrder.length) {
this.hcProperties.qInterColumnSortOrder = ar;
/**
* Sets the sorting order for the hypercube.
* @private
* @param {number[]} arr - The new sorting order.
* @memberof HyperCubeHandler
* @example
* const newSortingOrder = [2, 0, 1];
* hyperCubeHandler.setSorting(newSortingOrder);
*/
setSorting(arr) {
if (arr && arr.length === this.hcProperties.qInterColumnSortOrder.length) {
this.hcProperties.qInterColumnSortOrder = arr;
}
}
/**
* Gets the sorting order for the hypercube.
* @private
* @returns {number[]} The current sorting order.
* @memberof HyperCubeHandler
* @example
* const currentSortingOrder = hyperCubeHandler.getSorting();
*/
getSorting() {
return this.hcProperties.qInterColumnSortOrder;
}
/**
* Changes the sorting order for the hypercube.
* @private
* @param {number} fromIdx - The index to move from.
* @param {number} toIdx - The index to move to.
* @memberof HyperCubeHandler
* @example
* const newSortingOrder = hyperCubeHandler.changeSorting(0, 1);
*/
changeSorting(fromIdx, toIdx) {
utils.move(this.hcProperties.qInterColumnSortOrder, fromIdx, toIdx);
}
/**
* Returns whether the hypercube is in straight mode or pivot mode.
* @private
* @returns {string} 'S' for straight mode, 'P' for pivot mode
* @memberof HyperCubeHandler
*/
IsHCInStraightMode() {
return this.hcProperties.qMode === 'S';
}
/**
* @private
* @param {boolean} value
* @description This flag indicates whether we enabled HC modifier and have at least one script
* @memberof HyperCubeHandler
*/
setHCEnabled(value) {
// this flag indicate whether we enabled HC modifier and have at least one script
if (this.hcProperties) {
this.hcProperties.isHCEnabled = value;
}
}
/**
* Gets the dynamic scripts for the hypercube.
* @private
* @returns {Array} The dynamic scripts.
* @memberof HyperCubeHandler
*/
getDynamicScripts() {
return this.hcProperties?.qDynamicScript || [];
}

View File

@@ -12,3 +12,16 @@ export const INITIAL_SORT_CRITERIAS = [
qSortByAscii: 1,
},
];
export const uid = () => {
const idGen = [
[10, 31],
[0, 31],
[0, 31],
[0, 31],
[0, 31],
[0, 31],
];
const toChar = ([min, max]) => min + ((Math.random() * (max - min)) | 0).toString(32);
return idGen.map(toChar).join('');
};

View File

@@ -1,4 +1,4 @@
import getAutoSortFieldDimension from '../get-sorted-field';
import getAutoSortDimension from '../get-sorted-field';
import findFieldInExpandedList from '../find-field-in-expandedList';
import { setAutoSort } from '../field-utils';
@@ -7,7 +7,7 @@ jest.mock('../field-utils', () => ({
setAutoSort: jest.fn(),
}));
describe('getAutoSortFieldDimension', () => {
describe('getAutoSortDimension', () => {
let self;
let dimension;
@@ -32,7 +32,7 @@ describe('getAutoSortFieldDimension', () => {
const fieldList = [{ qName: 'field1' }];
self.app.getFieldList.mockResolvedValue(fieldList);
const result = await getAutoSortFieldDimension(self, dimension);
const result = await getAutoSortDimension(self, dimension);
expect(self.app.getFieldList).toHaveBeenCalled();
expect(findFieldInExpandedList).toHaveBeenCalledWith('field1', fieldList);
@@ -45,7 +45,7 @@ describe('getAutoSortFieldDimension', () => {
self.app.getFieldList.mockResolvedValue(fieldList);
findFieldInExpandedList.mockReturnValue(field);
await getAutoSortFieldDimension(self, dimension);
await getAutoSortDimension(self, dimension);
expect(setAutoSort).toHaveBeenCalledWith([field], dimension, self);
});
@@ -54,7 +54,7 @@ describe('getAutoSortFieldDimension', () => {
self.app.getFieldList.mockResolvedValue([]);
findFieldInExpandedList.mockReturnValue(null);
await getAutoSortFieldDimension(self, dimension);
await getAutoSortDimension(self, dimension);
expect(setAutoSort).not.toHaveBeenCalled();
});
@@ -62,7 +62,7 @@ describe('getAutoSortFieldDimension', () => {
test('should handle empty field list', async () => {
self.app.getFieldList.mockResolvedValue([]);
const result = await getAutoSortFieldDimension(self, dimension);
const result = await getAutoSortDimension(self, dimension);
expect(result).toBe(dimension);
expect(setAutoSort).not.toHaveBeenCalled();
@@ -72,7 +72,7 @@ describe('getAutoSortFieldDimension', () => {
dimension.qDef.qFieldDefs = undefined;
self.app.getFieldList.mockResolvedValue([]);
const result = await getAutoSortFieldDimension(self, dimension);
const result = await getAutoSortDimension(self, dimension);
expect(result).toBe(dimension);
expect(setAutoSort).not.toHaveBeenCalled();

View File

@@ -1,6 +1,4 @@
// eslint-disable-next-line import/no-relative-packages
import uid from '../../../../../nucleus/src/object/uid';
import { AUTOCALENDAR_NAME } from '../constants';
import { AUTOCALENDAR_NAME, uid } from '../constants';
export const getField = (expression) => {
let exp = expression;
@@ -33,7 +31,7 @@ export const initializeId = (field) => ({
},
});
export const initializeField = (field) => ({
export const initializeDim = (field) => ({
...initializeId(field),
qOtherTotalSpec: field.qOtherTotalSpec ?? {},
});
@@ -62,6 +60,13 @@ export const setAutoSort = (fields, dimension, self) => {
});
};
export const isDateField = (field) =>
field?.qDerivedFieldData && (field?.qTags?.indexOf('$date') > -1 || field?.qTags?.indexOf('$timestamp') > -1);
export const isGeoField = (field) => field.qTags.indexOf('$geoname') > -1;
export const trimAutoCalendarName = (fieldName) => (fieldName ? fieldName.split(AUTOCALENDAR_NAME).join('') : '');
export const useMasterNumberFormat = (formatting) => {
const format = formatting;
format.quarantine = {
@@ -71,10 +76,3 @@ export const useMasterNumberFormat = (formatting) => {
format.qNumFormat = null;
format.isCustomFormatted = undefined;
};
export const isDateField = (field) =>
field?.qDerivedFieldData && (field?.qTags?.indexOf('$date') > -1 || field?.qTags?.indexOf('$timestamp') > -1);
export const isGeoField = (field) => field.qTags.indexOf('$geoname') > -1;
export const trimAutoCalendarName = (fieldName) => (fieldName ? fieldName.split(AUTOCALENDAR_NAME).join('') : '');

View File

@@ -1,7 +1,7 @@
import findFieldInExpandedList from './find-field-in-expandedList';
import { setAutoSort } from './field-utils';
function getAutoSortFieldDimension(self, dimension) {
function getAutoSortDimension(self, dimension) {
return self.app.getFieldList().then((fieldList) => {
const field = dimension?.qDef?.qFieldDefs && findFieldInExpandedList(dimension.qDef.qFieldDefs[0], fieldList);
if (field) {
@@ -11,4 +11,4 @@ function getAutoSortFieldDimension(self, dimension) {
});
}
export default getAutoSortFieldDimension;
export default getAutoSortDimension;

View File

@@ -44,7 +44,7 @@ describe('addMainMeasure', () => {
const result = await addMainMeasure(self, newMeasure, index);
expect(result).toEqual(newMeasure);
expect(result).toBeUndefined();
expect(self.autoSortMeasure).not.toHaveBeenCalledWith(newMeasure);
expect(self.hcProperties.qInterColumnSortOrder).toEqual([1, 0, 2]);
});

View File

@@ -1,8 +1,9 @@
// eslint-disable-next-line import/no-relative-packages
import uid from '../../../../../../nucleus/src/object/uid';
import { uid } from '../../constants';
import * as hcUtils from '../hypercube-utils';
jest.mock('../../../../../../nucleus/src/object/uid', () => jest.fn());
jest.mock('../../constants', () => ({
uid: jest.fn(),
}));
describe('replaceDimensionToColumnOrder', () => {
let self;

View File

@@ -9,11 +9,12 @@ function addMainMeasure(self, measure, index) {
return self.autoSortMeasure(measure).then(() => {
addMeasureToColumnSortOrder(self, measures);
return addMeasureToColumnOrder(self, measure).then(() => measure);
addMeasureToColumnOrder(self, measure).then(() => measure);
return measure;
});
}
return measure;
return Promise.resolve();
}
export default addMainMeasure;

View File

@@ -1,10 +1,5 @@
// eslint-disable-next-line import/no-relative-packages
import getValue from '../../../../../conversion/src/utils';
// eslint-disable-next-line import/no-relative-packages
import arrayUtil from '../../../../../conversion/src/array-util';
import { TOTAL_MAX } from '../constants';
// eslint-disable-next-line import/no-relative-packages
import uid from '../../../../../nucleus/src/object/uid';
import { arrayUtil, utils } from '@nebula.js/conversion';
import { TOTAL_MAX, uid } from '../constants';
export const notSupportedError = new Error('Not supported in this object, need to implement in subclass.');
@@ -33,7 +28,7 @@ export const getHyperCube = (layout, path) => {
if (!layout) {
return undefined;
}
return path && getValue(layout, path) ? getValue(layout, path).qHyperCube : layout.qHyperCube;
return path && utils.getValue(layout, path) ? utils.getValue(layout, path).qHyperCube : layout.qHyperCube;
};
export function setDefaultProperties(self) {
@@ -241,7 +236,8 @@ export function isTotalMeasureExceeded(self, measures) {
return altMeasures.length + measures.length >= TOTAL_MAX.MEASURES;
}
export function isMeasureAlternative(self, measures, alternative) {
export function isMeasureAlternative(self, alternative) {
const measures = self.getMeasures();
return alternative || (self.maxMeasures() <= measures.length && measures.length < TOTAL_MAX.MEASURES);
}

View File

@@ -1,6 +1,8 @@
import generator from './generator';
import JSONPatch from './json-patch';
export { default as HyperCubeHandler } from './handler/hypercube-handler';
export { default as DataPropertyHandler } from './handler/data-property-handler';
export { generator, JSONPatch };
// core hooks

View File

@@ -4473,6 +4473,7 @@ __metadata:
version: 0.0.0-use.local
resolution: "@nebula.js/supernova@workspace:apis/supernova"
dependencies:
"@nebula.js/conversion": "npm:^6.0.0-alpha.2"
extend: "npm:3.0.2"
node-event-emitter: "npm:0.0.1"
languageName: unknown