diff --git a/apis/conversion/src/index.js b/apis/conversion/src/index.js index 3a13e0fa1..c866ab32b 100644 --- a/apis/conversion/src/index.js +++ b/apis/conversion/src/index.js @@ -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; diff --git a/apis/stardust/src/index.js b/apis/stardust/src/index.js index 49877a28c..1a92b26b1 100644 --- a/apis/stardust/src/index.js +++ b/apis/stardust/src/index.js @@ -42,6 +42,8 @@ export { useEmitter, onTakeSnapshot, onContextMenu, + HyperCubeHandler, + DataPropertyHandler, } from '@nebula.js/supernova'; // component internals diff --git a/apis/supernova/package.json b/apis/supernova/package.json index 3a1a50355..ae798fbef 100644 --- a/apis/supernova/package.json +++ b/apis/supernova/package.json @@ -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" } diff --git a/apis/supernova/src/handler/__tests__/data-property-handler.test.js b/apis/supernova/src/handler/__tests__/data-property-handler.test.js index 546a10d71..fc574e986 100644 --- a/apis/supernova/src/handler/__tests__/data-property-handler.test.js +++ b/apis/supernova/src/handler/__tests__/data-property-handler.test.js @@ -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; diff --git a/apis/supernova/src/handler/data-property-handler.js b/apis/supernova/src/handler/data-property-handler.js index 8c25b0715..05116a57e 100644 --- a/apis/supernova/src/handler/data-property-handler.js +++ b/apis/supernova/src/handler/data-property-handler.js @@ -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} 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} 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} 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} 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} 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} 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} 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} 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} 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} 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} 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} 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} 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} 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} 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} 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) => { diff --git a/apis/supernova/src/handler/hypercube-handler.js b/apis/supernova/src/handler/hypercube-handler.js index 326abb98c..010a28693 100644 --- a/apis/supernova/src/handler/hypercube-handler.js +++ b/apis/supernova/src/handler/hypercube-handler.js @@ -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} 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} 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} 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} 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} 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} 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} 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} 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} + * @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 || []; } diff --git a/apis/supernova/src/handler/utils/constants.js b/apis/supernova/src/handler/utils/constants.js index 463a8324c..5c510024a 100644 --- a/apis/supernova/src/handler/utils/constants.js +++ b/apis/supernova/src/handler/utils/constants.js @@ -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(''); +}; diff --git a/apis/supernova/src/handler/utils/field-helper/__tests__/get-sorted-field.test.js b/apis/supernova/src/handler/utils/field-helper/__tests__/get-sorted-field.test.js index ba4ff1e10..edd7dab37 100644 --- a/apis/supernova/src/handler/utils/field-helper/__tests__/get-sorted-field.test.js +++ b/apis/supernova/src/handler/utils/field-helper/__tests__/get-sorted-field.test.js @@ -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(); diff --git a/apis/supernova/src/handler/utils/field-helper/field-utils.js b/apis/supernova/src/handler/utils/field-helper/field-utils.js index dd496187d..8ce44be23 100644 --- a/apis/supernova/src/handler/utils/field-helper/field-utils.js +++ b/apis/supernova/src/handler/utils/field-helper/field-utils.js @@ -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('') : ''); diff --git a/apis/supernova/src/handler/utils/field-helper/get-sorted-field.js b/apis/supernova/src/handler/utils/field-helper/get-sorted-field.js index 70b43c2a4..4f35087c7 100644 --- a/apis/supernova/src/handler/utils/field-helper/get-sorted-field.js +++ b/apis/supernova/src/handler/utils/field-helper/get-sorted-field.js @@ -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; diff --git a/apis/supernova/src/handler/utils/hypercube-helper/__tests__/add-main-measure.test.js b/apis/supernova/src/handler/utils/hypercube-helper/__tests__/add-main-measure.test.js index 81a6d1c50..b46f20adf 100644 --- a/apis/supernova/src/handler/utils/hypercube-helper/__tests__/add-main-measure.test.js +++ b/apis/supernova/src/handler/utils/hypercube-helper/__tests__/add-main-measure.test.js @@ -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]); }); diff --git a/apis/supernova/src/handler/utils/hypercube-helper/__tests__/hypercube-utils.test.js b/apis/supernova/src/handler/utils/hypercube-helper/__tests__/hypercube-utils.test.js index b197c825b..4ef617a61 100644 --- a/apis/supernova/src/handler/utils/hypercube-helper/__tests__/hypercube-utils.test.js +++ b/apis/supernova/src/handler/utils/hypercube-helper/__tests__/hypercube-utils.test.js @@ -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; diff --git a/apis/supernova/src/handler/utils/hypercube-helper/add-main-measure.js b/apis/supernova/src/handler/utils/hypercube-helper/add-main-measure.js index cafe28b23..265770a7f 100644 --- a/apis/supernova/src/handler/utils/hypercube-helper/add-main-measure.js +++ b/apis/supernova/src/handler/utils/hypercube-helper/add-main-measure.js @@ -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; diff --git a/apis/supernova/src/handler/utils/hypercube-helper/hypercube-utils.js b/apis/supernova/src/handler/utils/hypercube-helper/hypercube-utils.js index 3d5ef8b5b..de7f6e7eb 100644 --- a/apis/supernova/src/handler/utils/hypercube-helper/hypercube-utils.js +++ b/apis/supernova/src/handler/utils/hypercube-helper/hypercube-utils.js @@ -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); } diff --git a/apis/supernova/src/index.js b/apis/supernova/src/index.js index 88b7f75d9..06715b425 100644 --- a/apis/supernova/src/index.js +++ b/apis/supernova/src/index.js @@ -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 diff --git a/yarn.lock b/yarn.lock index b9616baa5..d69c121e3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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