chore: add hypercube generic functions - part03 (#1729)

* chore: add hypercube generic functions - part03
This commit is contained in:
Donya MashaallahPoor
2025-05-16 15:18:14 +02:00
committed by GitHub
parent 72fef8889f
commit 6b690f3ecc
16 changed files with 866 additions and 99 deletions

View File

@@ -177,4 +177,130 @@ describe('DataPropertyHandler', () => {
expect(result.qDef.qLabel).toBeUndefined();
});
});
describe('maxMeasures', () => {
let galaxy;
beforeEach(() => {
galaxy = {
flags: {
isEnabled: jest.fn().mockReturnValue(false),
},
};
handler = new HyperCubeHandler({
measureDefinition: { max: 0 },
dimensionDefinition: { max: 0 },
});
handler.getDimensions = jest.fn().mockReturnValue([{ qDef: { cId: 'dim1' } }, { qDef: { cId: 'dim2' } }]);
});
afterEach(() => {
jest.clearAllMocks();
});
test('should return the result of measureDefinition.max when it is a function', () => {
handler.measureDefinition.max = jest.fn().mockReturnValue(5);
const result = handler.maxMeasures();
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;
const result = handler.maxMeasures();
expect(result).toBe(8);
});
test('should return 10000 when measureDefinition.max is NaN', () => {
handler.measureDefinition.max = NaN;
const result = handler.maxMeasures();
expect(result).toBe(10000);
});
test('should decrement the dimension length when decrement is provided', () => {
handler.measureDefinition.max = jest.fn().mockReturnValue(4);
const result = handler.maxMeasures(1);
expect(result).toBe(4);
});
test('should return 10000 when measureDefinition.max is not defined', () => {
handler.measureDefinition.max = undefined;
const result = handler.maxMeasures();
expect(result).toBe(10000);
});
});
describe('maxDimensions', () => {
let galaxy;
beforeEach(() => {
galaxy = {
flags: {
isEnabled: jest.fn().mockReturnValue(false),
},
};
handler = new HyperCubeHandler({
measureDefinition: { max: 0 },
dimensionDefinition: { max: 0 },
});
handler.getMeasures = jest.fn().mockReturnValue([{ qDef: { cId: 'meas1' } }, { qDef: { cId: 'meas2' } }]);
});
test('should return the result of dimensionDefinition.max when it is a function', () => {
handler.dimensionDefinition.max = jest.fn().mockReturnValue(5);
const result = handler.maxDimensions();
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;
const result = handler.maxDimensions();
expect(result).toBe(8);
});
test('should return 10000 when dimensionDefinition.max is NaN', () => {
handler.dimensionDefinition.max = NaN;
const result = handler.maxDimensions();
expect(result).toBe(10000);
});
test('should decrement the measure length when decrement is provided', () => {
handler.dimensionDefinition.max = jest.fn().mockReturnValue(4);
const result = handler.maxDimensions(1);
expect(result).toBe(4);
});
test('should return 10000 when dimensionDefinition.max is not defined', () => {
handler.dimensionDefinition.max = undefined;
const result = handler.maxDimensions();
expect(result).toBe(10000);
});
});
});

View File

@@ -1,20 +1,25 @@
import * as hcHelper from '../utils/hypercube-helper/hypercube-utils';
import * as hcUtils from '../utils/hypercube-helper/hypercube-utils';
import HyperCubeHandler from '../hypercube-handler';
import addMainDimension from '../utils/hypercube-helper/add-main-dimension';
jest.mock('../utils/hypercube-helper/add-main-dimension', () => jest.fn());
describe('HyperCube Handlers', () => {
let handler;
let properties;
let index = 1;
beforeEach(() => {
index = 1;
properties = {
qHyperCubeDef: {
qDimensions: [{ qDef: { cId: 'dim1' } }],
qMeasures: [],
qInterColumnSortOrder: [0, 1],
qDimensions: [{ qDef: { cId: 'dim1' } }, { qDef: { cId: 'dim2' } }],
qMeasures: [{ qDef: { cId: 'meas1' } }, { qDef: { cId: 'meas2' } }],
qInterColumnSortOrder: [1, 0, 2, 3],
qLayoutExclude: {
qHyperCubeDef: {
qDimensions: [{ qDef: { cId: 'altDim1' } }],
qMeasures: [{ qDef: { cId: 'altMeas1' } }],
qDimensions: [{ qDef: { cId: 'altDim1' } }, { qDef: { cId: 'altDim2' } }],
qMeasures: [{ qDef: { cId: 'altMeas1' } }, { qDef: { cId: 'altMeas2' } }],
},
},
},
@@ -25,6 +30,7 @@ describe('HyperCube Handlers', () => {
afterEach(() => {
handler.hcProperties = undefined;
jest.clearAllMocks();
jest.restoreAllMocks();
});
describe('setProperties', () => {
@@ -37,17 +43,16 @@ describe('HyperCube Handlers', () => {
expect(handler.hcProperties).toBeUndefined();
});
test('should set properties when qHyperCubeDef provides defined/undefined values', () => {
properties.qHyperCubeDef.qLayoutExclude.qHyperCubeDef.qDimensions = undefined;
test('should set properties when qHyperCubeDef provides defined values', () => {
handler.setProperties(properties);
expect(handler.hcProperties.qDimensions[0]).toEqual({ qDef: { cId: 'dim1' } });
expect(handler.hcProperties.qMeasures).toEqual([]);
expect(handler.hcProperties.qInterColumnSortOrder).toEqual([0, 1]);
expect(handler.hcProperties.qDimensions).toEqual([{ qDef: { cId: 'dim1' } }, { qDef: { cId: 'dim2' } }]);
expect(handler.hcProperties.qMeasures).toEqual([{ qDef: { cId: 'meas1' } }, { qDef: { cId: 'meas2' } }]);
expect(handler.hcProperties.qInterColumnSortOrder).toEqual([1, 0, 2, 3]);
expect(handler.hcProperties.qLayoutExclude).toEqual({
qHyperCubeDef: {
qDimensions: [],
qMeasures: [{ qDef: { cId: 'altMeas1' } }],
qDimensions: [{ qDef: { cId: 'altDim1' } }, { qDef: { cId: 'altDim2' } }],
qMeasures: [{ qDef: { cId: 'altMeas1' } }, { qDef: { cId: 'altMeas2' } }],
},
});
});
@@ -105,10 +110,50 @@ describe('HyperCube Handlers', () => {
});
});
describe('addDimension', () => {
let dimension;
beforeEach(() => {
dimension = { qDef: { cId: 'dim3' }, qOtherTotalSpec: {} };
jest.spyOn(hcUtils, 'isDimensionAlternative').mockReturnValue(false);
jest.spyOn(hcUtils, 'addAlternativeDimension').mockResolvedValue(dimension);
addMainDimension.mockResolvedValue(dimension);
});
afterEach(() => {
handler.hcProperties = undefined;
jest.clearAllMocks();
});
test('should add a main dimension when alternative is false', () => {
handler.addDimension(dimension, false, index).then((result) => {
expect(hcUtils.isDimensionAlternative).toHaveBeenCalledWith(handler, false);
expect(addMainDimension).toHaveBeenCalledWith(handler, dimension, index);
expect(result).toEqual(dimension);
});
});
test('should add an alternative dimension when alternative is true', () => {
jest.spyOn(hcUtils, 'isDimensionAlternative').mockReturnValue(true);
handler.addDimension(dimension, true, index).then((result) => {
expect(hcUtils.isDimensionAlternative).toHaveBeenCalledWith(handler, true);
expect(hcUtils.addAlternativeDimension).toHaveBeenCalledWith(handler, dimension, index);
expect(result).toEqual(dimension);
});
});
});
describe('addDimensions', () => {
beforeEach(() => {
index = 1;
handler.setProperties(properties);
jest.spyOn(hcHelper, 'isTotalDimensionsExceeded').mockReturnValue(false);
jest.spyOn(hcUtils, 'isTotalDimensionsExceeded').mockReturnValue(false);
});
afterEach(() => {
handler.hcProperties = undefined;
jest.clearAllMocks();
});
test('should return an empty array when newDimensions is empty', async () => {
@@ -117,56 +162,65 @@ describe('HyperCube Handlers', () => {
});
test('should add dimensions to alternative dimensions when alternative is true', async () => {
const newDimensions = [{ qDef: { cId: 'altDim2' } }, { qDef: { cId: 'altDim3' } }];
const newDimensions = [{ qDef: { cId: 'altDim3' } }, { qDef: { cId: 'altDim4' } }];
const dimensions = await handler.addDimensions(newDimensions, true);
expect(dimensions).toEqual([
{ qDef: { cId: 'altDim2' }, qOtherTotalSpec: {} },
{ qDef: { cId: 'altDim3' }, qOtherTotalSpec: {} },
{ qDef: { cId: 'altDim4' }, qOtherTotalSpec: {} },
]);
expect(handler.hcProperties.qLayoutExclude.qHyperCubeDef.qDimensions).toEqual([
{ qDef: { cId: 'altDim1' } },
{ qDef: { cId: 'altDim2' }, qOtherTotalSpec: {} },
{ qDef: { cId: 'altDim2' } },
{ qDef: { cId: 'altDim3' }, qOtherTotalSpec: {} },
{ qDef: { cId: 'altDim4' }, qOtherTotalSpec: {} },
]);
});
test('should add dimensions to main dimensions when alternative is false', async () => {
const newDimensions = [{ qDef: { cId: 'dim2' } }];
handler.maxDimensions = jest.fn().mockReturnValue(2);
const newDimensions = [{ qDef: { cId: 'dim3' } }, { qDef: { cId: 'dim4' } }];
handler.maxDimensions = jest.fn().mockReturnValue(4);
handler.autoSortDimension = jest.fn();
const dimensions = await handler.addDimensions(newDimensions, false);
expect(handler.autoSortDimension).toHaveBeenCalledTimes(1);
expect(handler.autoSortDimension).toHaveBeenCalledWith({ qDef: { cId: 'dim2' }, qOtherTotalSpec: {} });
expect(handler.autoSortDimension).toHaveBeenCalledTimes(2);
expect(handler.autoSortDimension).toHaveBeenCalledWith({ qDef: { cId: 'dim3' }, qOtherTotalSpec: {} });
expect(handler.autoSortDimension).toHaveBeenCalledWith({ qDef: { cId: 'dim4' }, qOtherTotalSpec: {} });
expect(dimensions).toEqual([{ qDef: { cId: 'dim2' }, qOtherTotalSpec: {} }]);
expect(dimensions).toEqual([
{ qDef: { cId: 'dim3' }, qOtherTotalSpec: {} },
{ qDef: { cId: 'dim4' }, qOtherTotalSpec: {} },
]);
expect(handler.hcProperties.qInterColumnSortOrder).toEqual([1, 0, 4, 5, 2, 3]);
expect(handler.hcProperties.qDimensions).toEqual([
{ qDef: { cId: 'dim1' } },
{ qDef: { cId: 'dim2' }, qOtherTotalSpec: {} },
{ qDef: { cId: 'dim2' } },
{ qDef: { cId: 'dim3' }, qOtherTotalSpec: {} },
{ qDef: { cId: 'dim4' }, qOtherTotalSpec: {} },
]);
});
test('should not add dimensions when isTotalDimensionsExceeded returns true', async () => {
jest.spyOn(hcHelper, 'isTotalDimensionsExceeded').mockReturnValue(true);
jest.spyOn(hcUtils, 'isTotalDimensionsExceeded').mockReturnValue(true);
const newDimensions = [{ qDef: { cId: 'dim2' } }];
const dimensions = await handler.addDimensions(newDimensions);
const result = await handler.addDimensions(newDimensions);
expect(dimensions).toEqual([]);
expect(result).toEqual([]);
});
test('should add dimensions to alternative dimensions when maxDim is exceeded but less than total maxDim', async () => {
handler.maxDimensions = jest.fn().mockReturnValue(1);
const newDimensions = [{ qDef: { cId: 'dim2' } }];
const newDimensions = [{ qDef: { cId: 'dim3' } }];
const dimension = await handler.addDimensions(newDimensions, false);
const result = await handler.addDimensions(newDimensions, false);
expect(dimension).toEqual([{ qDef: { cId: 'dim2' }, qOtherTotalSpec: {} }]);
expect(handler.hcProperties.qDimensions).toEqual([{ qDef: { cId: 'dim1' } }]);
expect(result).toEqual([{ qDef: { cId: 'dim3' }, qOtherTotalSpec: {} }]);
expect(handler.hcProperties.qDimensions).toEqual([{ qDef: { cId: 'dim1' } }, { qDef: { cId: 'dim2' } }]);
expect(handler.hcProperties.qLayoutExclude.qHyperCubeDef.qDimensions).toEqual([
{ qDef: { cId: 'altDim1' } },
{ qDef: { cId: 'dim2' }, qOtherTotalSpec: {} },
{ qDef: { cId: 'altDim2' } },
{ qDef: { cId: 'dim3' }, qOtherTotalSpec: {} },
]);
});
});
@@ -216,7 +270,7 @@ describe('HyperCube Handlers', () => {
beforeEach(() => {
properties.qHyperCubeDef.qMeasures = [{ qDef: { cId: 'meas1' } }];
handler.setProperties(properties);
jest.spyOn(hcHelper, 'isTotalMeasureExceeded').mockReturnValue(false);
jest.spyOn(hcUtils, 'isTotalMeasureExceeded').mockReturnValue(false);
});
test('should return an empty array when new measure is empty', () => {
@@ -225,15 +279,16 @@ describe('HyperCube Handlers', () => {
});
test('should add measures to alternative measures when alternative is true', () => {
const newMeasures = [{ qDef: { cId: 'altMeas2' } }, { qDef: { cId: 'altMeas3' } }];
const newMeasures = [{ qDef: { cId: 'altMeas3' } }, { qDef: { cId: 'altMeas4' } }];
const measures = handler.addMeasures(newMeasures, true);
expect(measures).toEqual([{ qDef: { cId: 'altMeas2' } }, { qDef: { cId: 'altMeas3' } }]);
expect(measures).toEqual([{ qDef: { cId: 'altMeas3' } }, { qDef: { cId: 'altMeas4' } }]);
expect(handler.hcProperties.qMeasures).toEqual([{ qDef: { cId: 'meas1' } }]);
expect(handler.hcProperties.qLayoutExclude.qHyperCubeDef.qMeasures).toEqual([
{ qDef: { cId: 'altMeas1' } },
{ qDef: { cId: 'altMeas2' } },
{ qDef: { cId: 'altMeas3' } },
{ qDef: { cId: 'altMeas4' } },
]);
});
@@ -265,7 +320,7 @@ describe('HyperCube Handlers', () => {
});
test('should not add measures when isTotalMeasureExceeded returns true', () => {
jest.spyOn(hcHelper, 'isTotalMeasureExceeded').mockReturnValue(true);
jest.spyOn(hcUtils, 'isTotalMeasureExceeded').mockReturnValue(true);
const newMeasure = [{ qDef: { cId: 'meas2' } }];
const measure = handler.addMeasures(newMeasure);
@@ -282,10 +337,188 @@ describe('HyperCube Handlers', () => {
expect(handler.hcProperties.qMeasures).toEqual([{ qDef: { cId: 'meas1' } }]);
expect(handler.hcProperties.qLayoutExclude.qHyperCubeDef.qMeasures).toEqual([
{ qDef: { cId: 'altMeas1' } },
{
qDef: { cId: 'meas2' },
},
{ qDef: { cId: 'altMeas2' } },
{ qDef: { cId: 'meas2' } },
]);
});
});
describe('removeMeasures', () => {
beforeEach(() => {
properties.qHyperCubeDef.qMeasures = [
{ qDef: { cId: 'meas1' } },
{ qDef: { cId: 'meas2' } },
{ qDef: { cId: 'meas3' } },
];
properties.qHyperCubeDef.qLayoutExclude.qHyperCubeDef.qMeasures = [
{ qDef: { cId: 'altMeas1' } },
{ qDef: { cId: 'altMeas2' } },
{ qDef: { cId: 'altMeas3' } },
];
properties.qHyperCubeDef.qInterColumnSortOrder = [0, 1, 2];
handler.setProperties(properties);
});
afterEach(() => {
handler.hcProperties = undefined;
jest.clearAllMocks();
});
test('should return an empty array if indexes is empty', async () => {
jest.spyOn(hcUtils, 'removeMeasureFromColumnSortOrder');
jest.spyOn(hcUtils, 'removeMeasureFromColumnOrder');
const result = await handler.removeMeasures([], false);
expect(result).toEqual([]);
expect(hcUtils.removeMeasureFromColumnSortOrder).not.toHaveBeenCalled();
expect(hcUtils.removeMeasureFromColumnOrder).not.toHaveBeenCalled();
});
test('should return deleted measures if alternative is true', async () => {
const indexes = [2, 0];
const result = await handler.removeMeasures(indexes, true);
expect(result).toEqual([{ qDef: { cId: 'altMeas1' } }, { qDef: { cId: 'altMeas3' } }]);
expect(handler.hcProperties.qLayoutExclude.qHyperCubeDef.qMeasures).toEqual([{ qDef: { cId: 'altMeas2' } }]);
expect(handler.hcProperties.qMeasures).toEqual([
{ qDef: { cId: 'meas1' } },
{ qDef: { cId: 'meas2' } },
{ qDef: { cId: 'meas3' } },
]);
});
test('should return deleted measures in the order when alternative is false', async () => {
properties.qHyperCubeDef.qInterColumnSortOrder = [0, 1, 2];
handler.setProperties(properties);
const indexes = [1, 2];
const result = await handler.removeMeasures(indexes, false);
expect(result).toEqual([{ qDef: { cId: 'meas2' } }, { qDef: { cId: 'meas3' } }]);
expect(handler.hcProperties.qMeasures).toEqual([{ qDef: { cId: 'meas1' } }]);
expect(handler.hcProperties.qInterColumnSortOrder).toEqual([2]);
expect(handler.hcProperties.qLayoutExclude.qHyperCubeDef.qMeasures).toEqual([
{ qDef: { cId: 'altMeas1' } },
{ qDef: { cId: 'altMeas2' } },
{ qDef: { cId: 'altMeas3' } },
]);
});
test('should handle empty qMeasures', async () => {
properties.qHyperCubeDef.qMeasures = [];
handler.setProperties(properties);
const indexes = [1];
const result = await handler.removeMeasures(indexes, false);
expect(result).toEqual([]);
});
});
describe('removeDimensions', () => {
beforeEach(() => {
properties.qHyperCubeDef.qDimensions = [
{ qDef: { cId: 'dim1' } },
{ qDef: { cId: 'dim2' } },
{ qDef: { cId: 'dim3' } },
];
properties.qHyperCubeDef.qLayoutExclude.qHyperCubeDef.qDimensions = [
{ qDef: { cId: 'altDim1' } },
{ qDef: { cId: 'altDim2' } },
{ qDef: { cId: 'altDim3' } },
];
properties.qHyperCubeDef.qInterColumnSortOrder = [0, 1, 2];
handler.setProperties(properties);
});
afterEach(() => {
handler.hcProperties = undefined;
jest.resetModules();
jest.clearAllMocks();
});
test('should return an empty array if indexes is empty', async () => {
jest.doMock('../utils/hypercube-helper/remove-alternative-dimension', () => ({
removeAlternativeDimension: jest.fn().mockResolvedValue(),
}));
jest.doMock('../utils/hypercube-helper/remove-main-dimension', () => ({
removeMainDimension: jest.fn().mockResolvedValue(),
}));
const { removeMainDimension } = await import('../utils/hypercube-helper/remove-main-dimension');
const { removeAlternativeDimension } = await import('../utils/hypercube-helper/remove-alternative-dimension');
const result = await handler.removeDimensions([], false);
expect(result).toEqual([]);
expect(removeAlternativeDimension).not.toHaveBeenCalled();
expect(removeMainDimension).not.toHaveBeenCalled();
});
test('should remove alternative dimensions when alternative is true', async () => {
const indexes = [0, 1];
const result = await handler.removeDimensions(indexes, true);
expect(result).toEqual([{ qDef: { cId: 'altDim1' } }, { qDef: { cId: 'altDim2' } }]);
expect(handler.hcProperties.qLayoutExclude.qHyperCubeDef.qDimensions).toEqual([{ qDef: { cId: 'altDim3' } }]);
expect(handler.hcProperties.qDimensions).toEqual([
{ qDef: { cId: 'dim1' } },
{ qDef: { cId: 'dim2' } },
{ qDef: { cId: 'dim3' } },
]);
});
test('should remove main dimensions when alternative is false', async () => {
const indexes = [2, 0];
const result = await handler.removeDimensions(indexes, false);
expect(result).toEqual([{ qDef: { cId: 'dim1' } }, { qDef: { cId: 'dim3' } }]);
expect(handler.hcProperties.qDimensions).toEqual([{ qDef: { cId: 'dim2' } }]);
expect(handler.hcProperties.qInterColumnSortOrder).toEqual([0]);
expect(handler.hcProperties.qLayoutExclude.qHyperCubeDef.qDimensions).toEqual([
{ qDef: { cId: 'altDim1' } },
{ qDef: { cId: 'altDim2' } },
{ qDef: { cId: 'altDim3' } },
]);
});
test('should handle empty alternative qDimensions', async () => {
jest.doMock('../utils/hypercube-helper/remove-alternative-dimension', () => ({
removeAlternativeDimension: jest.fn().mockResolvedValue(),
}));
const { removeAlternativeDimension } = await import('../utils/hypercube-helper/remove-alternative-dimension');
properties.qHyperCubeDef.qLayoutExclude.qHyperCubeDef.qDimensions = [];
properties.qHyperCubeDef.qDimensions = [];
handler.setProperties(properties);
const indexes = [0, 1];
const result = await handler.removeDimensions(indexes, true);
expect(result).toEqual([]);
expect(removeAlternativeDimension).not.toHaveBeenCalled();
});
test('should handle empty main qDimensions', async () => {
jest.doMock('../utils/hypercube-helper/remove-main-dimension', () => ({
removeMainDimension: jest.fn().mockResolvedValue(),
}));
const { removeMainDimension } = await import('../utils/hypercube-helper/remove-main-dimension');
properties.qHyperCubeDef.qLayoutExclude.qHyperCubeDef.qDimensions = [];
properties.qHyperCubeDef.qDimensions = [];
handler.setProperties(properties);
const indexes = [0, 1];
const result = await handler.removeDimensions(indexes, false);
expect(result).toEqual([]);
expect(removeMainDimension).not.toHaveBeenCalled();
});
});
});

View File

@@ -63,6 +63,14 @@ class DataPropertyHandler {
throw notSupportedError;
}
static removeDimension() {
throw notSupportedError;
}
static removeDimensions() {
throw notSupportedError;
}
static autoSortDimension() {
throw notSupportedError;
}
@@ -181,6 +189,14 @@ class DataPropertyHandler {
throw notSupportedError;
}
static removeMeasure() {
throw notSupportedError;
}
static removeMeasures() {
throw notSupportedError;
}
static autoSortMeasure() {
throw notSupportedError;
}
@@ -258,10 +274,9 @@ class DataPropertyHandler {
return this.addMeasure(measure, true);
}
maxMeasures(decrement) {
const decr = decrement || 0;
maxMeasures(decrement = 0) {
if (typeof this.measureDefinition.max === 'function') {
const dimLength = this.getDimensions().length - decr;
const dimLength = this.getDimensions().length - decrement;
const measureParams = isEnabled('PS_21371_ANALYSIS_TYPES') ? [dimLength, this.properties] : [dimLength];
return this.measureDefinition.max.apply(null, measureParams);
}

View File

@@ -1,12 +1,16 @@
// eslint-disable-next-line import/no-relative-packages
import utils from '../../../conversion/src/utils';
import DataPropertyHandler from './data-property-handler';
import * as hcHelper from './utils/hypercube-helper/hypercube-utils';
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 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';
import removeAlternativeMeasure from './utils/hypercube-helper/remove-alternative-measure';
import removeMainMeasure from './utils/hypercube-helper/remove-main-measure';
import removeAlternativeDimension from './utils/hypercube-helper/remove-alternative-dimension';
class HyperCubeHandler extends DataPropertyHandler {
constructor(opts) {
@@ -27,13 +31,13 @@ class HyperCubeHandler extends DataPropertyHandler {
return {};
}
hcHelper.setDefaultProperties(this);
hcHelper.setPropForLineChartWithForecast(this);
hcUtils.setDefaultProperties(this);
hcUtils.setPropForLineChartWithForecast(this);
// Set auto-sort property (compatibility 0.85 -> 0.9),
// can probably be removed in 1.0
this.hcProperties.qDimensions = hcHelper.setFieldProperties(this.hcProperties.qDimensions);
this.hcProperties.qMeasures = hcHelper.setFieldProperties(this.hcProperties.qMeasures);
this.hcProperties.qDimensions = hcUtils.setFieldProperties(this.hcProperties.qDimensions);
this.hcProperties.qMeasures = hcUtils.setFieldProperties(this.hcProperties.qMeasures);
return {};
}
@@ -54,15 +58,15 @@ class HyperCubeHandler extends DataPropertyHandler {
}
getDimensionLayouts() {
const hc = hcHelper.getHyperCube(this.layout, this.path);
const hc = hcUtils.getHyperCube(this.layout, this.path);
return hc ? hc.qDimensionInfo : [];
}
addDimension(dimension, alternative, idx) {
const dim = initializeField(dimension);
if (hcHelper.isDimensionAlternative(this, dim, alternative)) {
return hcHelper.addAlternativeDimension(this, dim, idx);
if (hcUtils.isDimensionAlternative(this, alternative)) {
return hcUtils.addAlternativeDimension(this, dim, idx);
}
return addMainDimension(this, dim, idx);
@@ -70,22 +74,23 @@ class HyperCubeHandler extends DataPropertyHandler {
async addDimensions(dimensions, alternative = false) {
const existingDimensions = this.getDimensions();
const initialLength = existingDimensions.length;
const addedDimensions = [];
let addedActive = 0;
// eslint-disable-next-line no-restricted-syntax
for await (const dimension of dimensions) {
if (hcHelper.isTotalDimensionsExceeded(this, existingDimensions)) {
if (hcUtils.isTotalDimensionsExceeded(this, existingDimensions)) {
return addedDimensions;
}
const dim = initializeField(dimension);
if (hcHelper.isDimensionAlternative(this, alternative)) {
const altDim = await hcHelper.addAlternativeDimension(this, dim);
if (hcUtils.isDimensionAlternative(this, alternative)) {
const altDim = await hcUtils.addAlternativeDimension(this, dim);
addedDimensions.push(altDim);
} else if (existingDimensions.length < this.maxDimensions()) {
await hcHelper.addActiveDimension(this, dim, existingDimensions, addedDimensions, addedActive);
await hcUtils.addActiveDimension(this, dim, initialLength, existingDimensions, addedDimensions, addedActive);
addedActive++;
}
}
@@ -93,6 +98,42 @@ class HyperCubeHandler extends DataPropertyHandler {
return addedDimensions;
}
removeDimension(idx, alternative) {
if (alternative) {
removeAlternativeDimension(this, idx);
}
removeMainDimension(this, idx);
}
async removeDimensions(indexes, alternative) {
const altDimensions = this.getAlternativeDimensions();
const dimensions = this.getDimensions();
if (indexes.length === 0) return [];
let deletedDimensions = [];
// Start deleting from the end of the list first otherwise the idx is messed up
const sortedIndexes = [...indexes].sort((a, b) => b - a);
if (alternative && altDimensions.length > 0) {
// Keep the original deleted order
deletedDimensions = hcUtils.getDeletedFields(altDimensions, indexes);
// eslint-disable-next-line no-restricted-syntax
for await (const index of sortedIndexes) {
await removeAlternativeDimension(this, index);
}
} else if (dimensions.length > 0) {
// Keep the original deleted order
deletedDimensions = hcUtils.getDeletedFields(dimensions, indexes);
// eslint-disable-next-line no-restricted-syntax
for await (const index of sortedIndexes) {
await removeMainDimension(this, index);
}
}
return deletedDimensions;
}
autoSortDimension(dimension) {
if (dimension.qLibraryId) {
return getAutoSortLibraryDimension(this, dimension);
@@ -113,7 +154,7 @@ class HyperCubeHandler extends DataPropertyHandler {
}
getMeasureLayouts() {
const hc = hcHelper.getHyperCube(this.layout, this.path);
const hc = hcUtils.getHyperCube(this.layout, this.path);
return hc ? hc.qMeasureInfo : [];
}
@@ -124,9 +165,9 @@ class HyperCubeHandler extends DataPropertyHandler {
addMeasure(measure, alternative, idx) {
const meas = initializeField(measure);
if (hcHelper.isMeasureAlternative(this, meas, alternative)) {
if (hcUtils.isMeasureAlternative(this, meas, alternative)) {
const hcMeasures = this.hcProperties.qLayoutExclude.qHyperCubeDef.qMeasures;
return hcHelper.addAlternativeMeasure(meas, hcMeasures, idx);
return hcUtils.addAlternativeMeasure(meas, hcMeasures, idx);
}
return addMainMeasure(this, meas, idx);
@@ -147,23 +188,55 @@ class HyperCubeHandler extends DataPropertyHandler {
const addedMeasures = [];
let addedActive = 0;
measures.forEach(async (measure) => {
if (hcHelper.isTotalMeasureExceeded(this, existingMeasures)) {
if (hcUtils.isTotalMeasureExceeded(this, existingMeasures)) {
return false;
}
const meas = initializeId(measure);
if (hcHelper.isMeasureAlternative(this, existingMeasures, alternative)) {
hcHelper.addAlternativeMeasure(this, meas);
if (hcUtils.isMeasureAlternative(this, existingMeasures, alternative)) {
hcUtils.addAlternativeMeasure(this, meas);
addedMeasures.push(meas);
} else if (existingMeasures.length < this.maxMeasures()) {
await hcHelper.addActiveMeasure(this, meas, existingMeasures, addedMeasures, addedActive);
await hcUtils.addActiveMeasure(this, meas, existingMeasures, addedMeasures, addedActive);
addedActive++;
}
return true;
});
return addedMeasures;
}
removeMeasure(idx, alternative) {
if (alternative) {
hcUtils.removeAltMeasureByIndex(this, idx);
}
removeMainMeasure(this, idx);
}
async removeMeasures(indexes, alternative) {
const measures = this.getMeasures();
const altMeasures = this.getAlternativeMeasures();
if (indexes.length === 0) return [];
let deletedMeasures = [];
if (alternative && altMeasures.length > 0) {
// Keep the original deleted order
deletedMeasures = hcUtils.getDeletedFields(altMeasures, indexes);
removeAlternativeMeasure(this, indexes);
} else if (measures.length > 0) {
// Keep the original deleted order
deletedMeasures = hcUtils.getDeletedFields(measures, indexes);
const sortedIndexes = [...indexes].sort((a, b) => b - a);
// eslint-disable-next-line no-restricted-syntax
for await (const index of sortedIndexes) {
await removeMainMeasure(this, index);
}
}
return deletedMeasures;
}
}
export default HyperCubeHandler;

View File

@@ -0,0 +1,57 @@
import addMainDimension from '../add-main-dimension';
import updateDimensionOrders from '../update-dimension-orders';
jest.mock('../update-dimension-orders', () => jest.fn().mockReturnValue({ qDef: { cId: 'dim3' } }));
describe('addMainDimension', () => {
let self;
let index;
let newDimension;
beforeEach(() => {
index = 1;
newDimension = { qDef: { cId: 'dim3' } };
self = {
getDimensions: jest.fn().mockReturnValue([{ qDef: { cId: 'dim1' } }, { qDef: { cId: 'dim2' } }]),
maxDimensions: jest.fn().mockReturnValue(3),
};
});
afterEach(() => {
jest.clearAllMocks();
});
test('should call updateDimensionOrders if dimensions are below the maximum limit', () => {
const result = addMainDimension(self, newDimension, index);
expect(result).toEqual(newDimension);
expect(updateDimensionOrders).toHaveBeenCalledWith(self, newDimension, index);
});
test('should return a resolved promise if dimensions are at the maximum limit', async () => {
self.maxDimensions.mockReturnValue(2);
const result = await addMainDimension(self, newDimension, index);
expect(result).toEqual(newDimension);
expect(updateDimensionOrders).not.toHaveBeenCalled();
});
test('should handle empty dimensions array', () => {
self.getDimensions.mockReturnValue([]);
const result = addMainDimension(self, newDimension, index);
expect(result).toEqual(newDimension);
expect(updateDimensionOrders).toHaveBeenCalledWith(self, newDimension, index);
});
test('should handle dimension when index is undefined', async () => {
index = undefined;
const result = await addMainDimension(self, newDimension, index);
expect(result).toEqual(newDimension);
expect(updateDimensionOrders).toHaveBeenCalledWith(self, newDimension, 2);
});
});

View File

@@ -0,0 +1,72 @@
import addMainMeasure from '../add-main-measure';
import * as hcUtils from '../hypercube-utils';
describe('addMainMeasure', () => {
let self;
let index;
const newMeasure = { qDef: { cId: 'meas3' } };
beforeEach(() => {
index = 1;
self = {
hcProperties: {
qMeasures: [{ qDef: { cId: 'meas1' } }, { qDef: { cId: 'meas2' } }],
qInterColumnSortOrder: [1, 0, 2],
qLayoutExclude: {
qHyperCubeDef: {
qDimensions: [],
qMeasures: [],
},
},
},
autoSortMeasure: jest.fn().mockResolvedValue(),
getMeasures: jest.fn().mockReturnValue([{ qDef: { cId: 'meas1' } }, { qDef: { cId: 'meas2' } }]),
getDimensions: jest.fn().mockReturnValue([{ qDef: { cId: 'dim1' } }]),
maxMeasures: jest.fn().mockReturnValue(4),
};
jest.spyOn(hcUtils, 'addMeasureToColumnOrder').mockResolvedValue();
});
afterEach(() => {
jest.clearAllMocks();
});
test('should add the measure to the specified index', async () => {
const result = await addMainMeasure(self, newMeasure, index);
expect(result).toBe(newMeasure);
expect(self.autoSortMeasure).toHaveBeenCalledWith(newMeasure);
expect(self.hcProperties.qInterColumnSortOrder).toEqual([1, 0, 2, 3]);
});
test('should return a resolved promise if measures are at the maximum limit', async () => {
self.maxMeasures.mockReturnValue(2);
const result = await addMainMeasure(self, newMeasure, index);
expect(result).toEqual(newMeasure);
expect(self.autoSortMeasure).not.toHaveBeenCalledWith(newMeasure);
expect(self.hcProperties.qInterColumnSortOrder).toEqual([1, 0, 2]);
});
test('should handle empty measures array', async () => {
self.getMeasures.mockReturnValue([]);
// Have only a dimension in the layout
self.hcProperties.qInterColumnSortOrder = [0];
const result = await addMainMeasure(self, newMeasure, index);
expect(result).toEqual(newMeasure);
expect(self.autoSortMeasure).toHaveBeenCalledWith(newMeasure);
expect(self.hcProperties.qInterColumnSortOrder).toEqual([0, 1]);
});
test('should handle measure when index is undefined', async () => {
index = undefined;
const result = await addMainMeasure(self, newMeasure, index);
expect(result).toEqual(newMeasure);
expect(self.autoSortMeasure).toHaveBeenCalledWith(newMeasure);
expect(self.hcProperties.qInterColumnSortOrder).toEqual([1, 0, 2, 3]);
});
});

View File

@@ -0,0 +1,49 @@
import removeAlternativeDimension from '../remove-alternative-dimension';
describe('removeAlternativeDimension', () => {
let self;
let index;
beforeEach(() => {
index = 1;
self = {
getAlternativeDimensions: jest.fn().mockReturnValue([{ qDef: { cId: 'altDim1' } }, { qDef: { cId: 'altDim2' } }]),
dimensionDefinition: {
remove: jest.fn().mockResolvedValue(),
},
properties: {},
};
});
afterEach(() => {
jest.clearAllMocks();
});
test('should remove the alternative dimension at the specified index', async () => {
await removeAlternativeDimension(self, index);
expect(self.getAlternativeDimensions()).toEqual([{ qDef: { cId: 'altDim1' } }]);
expect(self.dimensionDefinition.remove).toHaveBeenCalledWith(
{ qDef: { cId: 'altDim2' } },
self.properties,
self,
index
);
});
test('should return undefined if dimensionDefinition.remove is not a function', async () => {
self.dimensionDefinition.remove = undefined;
const result = await removeAlternativeDimension(self, index);
expect(result).toBeUndefined();
});
test('should handle removing the dimension if the index is undefined', async () => {
index = undefined;
await removeAlternativeDimension(self, index);
expect(self.dimensionDefinition.remove).toHaveBeenCalled();
});
});

View File

@@ -0,0 +1,49 @@
import updateDimensionOrders from '../update-dimension-orders';
import * as hcUtils from '../hypercube-utils';
describe('updateDimensionOrders', () => {
let self;
let newDimension;
let index;
beforeEach(() => {
self = {
hcProperties: {
qDimensions: [{ qDef: { cId: 'dim1' } }, { qDef: { cId: 'dim2' } }],
qInterColumnSortOrder: [1, 0],
qLayoutExclude: {
qHyperCubeDef: {
qDimensions: [],
qMeasures: [],
},
},
},
autoSortDimension: jest.fn().mockResolvedValue(),
getDimensions: jest.fn().mockReturnValue([{ qDef: { cId: 'dim1' } }, { qDef: { cId: 'dim2' } }]),
};
newDimension = { qDef: { cId: 'dim3' } };
index = 1;
jest.spyOn(hcUtils, 'addDimensionToColumnOrder').mockResolvedValue();
});
afterEach(() => {
jest.clearAllMocks();
});
test('should add dimension to the specified index', async () => {
const result = await updateDimensionOrders(self, newDimension, index);
expect(result).toEqual(newDimension);
expect(self.autoSortDimension).toHaveBeenCalledWith(newDimension);
expect(self.hcProperties.qInterColumnSortOrder).toEqual([2, 0, 1]);
});
test('should handle adding dimension to the default order when index is not defined ', async () => {
index = undefined;
const result = await updateDimensionOrders(self, newDimension, index);
expect(result).toEqual(newDimension);
expect(self.autoSortDimension).toHaveBeenCalledWith(newDimension);
expect(self.hcProperties.qInterColumnSortOrder).toEqual([1, 0, 2]);
});
});

View File

@@ -1,12 +1,14 @@
import { insertMainDimension } from './hypercube-utils';
import updateDimensionOrders from './update-dimension-orders';
export default function addMainDimension(self, dimension, index) {
function addMainDimension(self, dimension, index) {
const dimensions = self.getDimensions();
const idx = index ?? dimensions.length;
if (dimensions.length < self.maxDimensions()) {
return insertMainDimension(self, dimension, dimensions, idx);
return updateDimensionOrders(self, dimension, idx);
}
return Promise.resolve();
return Promise.resolve(dimension);
}
export default addMainDimension;

View File

@@ -1,12 +1,19 @@
import { insertMainMeasure } from './hypercube-utils';
import { addMeasureToColumnOrder, addMeasureToColumnSortOrder } from './hypercube-utils';
export default function addMainMeasure(self, measure, index) {
function addMainMeasure(self, measure, index) {
const measures = self.getMeasures();
const idx = index ?? measures.length;
if (measures.length < self.maxMeasures()) {
insertMainMeasure(measure, measures, idx);
measures.splice(idx, 0, measure);
return self.autoSortMeasure(measure).then(() => {
addMeasureToColumnSortOrder(self, measures);
return addMeasureToColumnOrder(self, measure).then(() => measure);
});
}
return measure;
}
export default addMainMeasure;

View File

@@ -63,6 +63,15 @@ export function setPropForLineChartWithForecast(self) {
}
}
export function getDeletedFields(fields, indexes) {
// Keep the original deleted order
return fields.filter((_, idx) => indexes.includes(idx));
}
export function getRemainedFields(fields, indexes) {
return fields.filter((_, idx) => !indexes.includes(idx));
}
// ----------------------------------
// ----------- DIMENSIONS -----------
// ----------------------------------
@@ -74,29 +83,30 @@ export function addAlternativeDimension(self, dimension, index = undefined) {
return Promise.resolve(dimension);
}
export function insertMainDimension(self, dimension, dimensions, idx) {
dimensions.splice(idx, 0, dimension);
return self.autoSortDimension(dimension).then(() => {
arrayUtil.indexAdded(self.hcProperties.qInterColumnSortOrder, self.getDimensions().length + dimension.length - 1);
if (typeof self.dimensionDefinition.add === 'function') {
return Promise.resolve(self.dimensionDefinition.add.call(null, dimension, self.properties, self));
}
return dimension;
});
export function addDimensionToColumnSortOrder(self, dimensions, index) {
arrayUtil.indexAdded(self.hcProperties.qInterColumnSortOrder, index ?? dimensions.length - 1);
}
export function addSortedDimension(self, dimension, dimensions, idx) {
const dimIdx = idx ?? dimensions.length;
dimensions.splice(dimIdx, 0, dimension);
export function addDimensionToColumnOrder(self, dimension) {
if (typeof self.dimensionDefinition.add === 'function') {
return Promise.resolve(self.dimensionDefinition.add.call(null, dimension, self.properties, self)).then(
() => dimension
);
}
return undefined;
}
return self.autoSortDimension(dimension).then(() => {
arrayUtil.indexAdded(self.hcProperties.qInterColumnSortOrder, dimIdx ?? dimensions.length - 1);
export function removeDimensionFromColumnSortOrder(self, index) {
arrayUtil.indexRemoved(self.hcProperties.qInterColumnSortOrder, index);
}
return self.dimensionDefinition.add?.call(self, dimension, self.properties, self) || Promise.resolve(dimension);
});
export function removeDimensionFromColumnOrder(self, index) {
const [dimension] = self.hcProperties.qDimensions.splice(index, 1);
if (dimension && typeof self.dimensionDefinition.remove === 'function') {
return Promise.resolve(self.dimensionDefinition.remove.call(null, dimension, self.properties, self, index));
}
return undefined;
}
export function isTotalDimensionsExceeded(self, dimensions) {
@@ -109,8 +119,14 @@ export function isDimensionAlternative(self, alternative) {
return alternative || (self.maxDimensions() <= dimensions.length && dimensions.length < TOTAL_MAX.DIMENSIONS);
}
export async function addActiveDimension(self, dimension, existingDimensions, addedDimensions, addedActive) {
const initialLength = existingDimensions.length;
export async function addActiveDimension(
self,
dimension,
initialLength,
existingDimensions,
addedDimensions,
addedActive
) {
await self.autoSortDimension(dimension);
// Update sorting order
@@ -135,18 +151,15 @@ export function addAlternativeMeasure(self, measure, index = undefined) {
return Promise.resolve(measure);
}
export function insertMainMeasure(self, measure, measures, idx) {
measures.splice(idx, 0, measure);
export function addMeasureToColumnSortOrder(self, measures) {
arrayUtil.indexAdded(self.hcProperties.qInterColumnSortOrder, self.getDimensions().length + measures.length - 1);
}
return self.autoSortMeasure(measure).then(() => {
arrayUtil.indexAdded(self.hcProperties.qInterColumnSortOrder, self.getDimensions().length + measure.length - 1);
if (typeof self.measureDefinition.add === 'function') {
return Promise.resolve(self.measureDefinition.add.call(null, measure, self.properties, self));
}
return measure;
});
export function addMeasureToColumnOrder(self, measure) {
if (typeof self.measureDefinition.add === 'function') {
return Promise.resolve(self.measureDefinition.add.call(null, measure, self.properties, self));
}
return undefined;
}
export function isTotalMeasureExceeded(self, measures) {
@@ -180,3 +193,19 @@ export function addActiveMeasure(self, measure, existingMeasures, addedMeasures,
return Promise.resolve(addedMeasures);
}
export function removeMeasureFromColumnSortOrder(self, index) {
arrayUtil.indexRemoved(self.hcProperties.qInterColumnSortOrder, self.getDimensions().length + index);
}
export function removeMeasureFromColumnOrder(self, index) {
const [measure] = self.hcProperties.qMeasures.splice(index, 1);
if (measure && typeof self.measureDefinition.remove === 'function') {
return Promise.resolve(self.measureDefinition.remove.call(null, measure, self.properties, self, index));
}
return undefined;
}
export function removeAltMeasureByIndex(self, index) {
return self.hcProperties.qLayoutExclude.qHyperCubeDef.qMeasures.splice(index, 1);
}

View File

@@ -0,0 +1,15 @@
function removeAlternativeDimension(self, index) {
const [dimension] = self.getAlternativeDimensions().splice(index, 1);
if (dimension && typeof self.dimensionDefinition.remove === 'function') {
dimension.isAlternative = true;
return Promise.resolve(self.dimensionDefinition.remove.call(null, dimension, self.properties, self, index)).then(
() => {
delete dimension.isAlternative;
}
);
}
return undefined;
}
export default removeAlternativeDimension;

View File

@@ -0,0 +1,10 @@
import { getRemainedFields } from './hypercube-utils';
function removeAlternativeMeasure(self, indexes) {
const current = self;
const measures = current.getAlternativeMeasures();
const remainedFields = getRemainedFields(measures, indexes);
current.hcProperties.qLayoutExclude.qHyperCubeDef.qMeasures = remainedFields;
}
export default removeAlternativeMeasure;

View File

@@ -0,0 +1,8 @@
import { removeDimensionFromColumnOrder, removeDimensionFromColumnSortOrder } from './hypercube-utils';
async function removeMainDimension(self, index) {
removeDimensionFromColumnSortOrder(self, index);
await removeDimensionFromColumnOrder(self, index);
}
export default removeMainDimension;

View File

@@ -0,0 +1,8 @@
import { removeMeasureFromColumnOrder, removeMeasureFromColumnSortOrder } from './hypercube-utils';
async function removeMainMeasure(self, index) {
removeMeasureFromColumnSortOrder(self, index);
await removeMeasureFromColumnOrder(self, index);
}
export default removeMainMeasure;

View File

@@ -0,0 +1,14 @@
import { addDimensionToColumnOrder, addDimensionToColumnSortOrder } from './hypercube-utils';
function updateDimensionOrders(self, dimension, index) {
const dimensions = self.getDimensions();
dimensions.splice(index, 0, dimension);
return self.autoSortDimension(dimension).then(async () => {
addDimensionToColumnSortOrder(self, dimensions, index);
await addDimensionToColumnOrder(self, dimension);
return dimension;
});
}
export default updateDimensionOrders;