chore: add hypercube generic functions - part02 (#1715)

* chore: add hypercube generic functions - part02
This commit is contained in:
Donya MashaallahPoor
2025-05-06 13:07:19 +02:00
committed by GitHub
parent 6b84132273
commit 445d6949e7
25 changed files with 1662 additions and 249 deletions

View File

@@ -1,61 +0,0 @@
import HyperCubeHandler from '../hypercube-handler';
describe('DataPropertyHandler - getDimensions and getMeasure', () => {
let handler;
let properties;
beforeEach(() => {
properties = {
qHyperCubeDef: {
qDimensions: [{ qDef: { cId: 'dim1' } }],
qLayoutExclude: {
qHyperCubeDef: {
qDimensions: [{ qDef: { cId: 'altDim1' } }],
qMeasures: [{ qDef: { cId: 'altMeasure1' } }],
},
},
},
};
handler = new HyperCubeHandler(properties);
});
afterEach(() => {
jest.clearAllMocks();
});
describe('getDimensions()', () => {
test('should return null when dimension is undefined', () => {
jest.spyOn(handler, 'getDimensions').mockReturnValue([]);
const dimension = handler.getDimension(undefined);
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');
expect(dimension).toEqual({ qDef: { cId: 'dim1' } });
const alternativeDimension = handler.getDimension('altDim1');
expect(alternativeDimension).toEqual({ qDef: { cId: 'altDim1' } });
});
});
describe('getMeasure()', () => {
test('should return null when both measures and alternative measures are empty', () => {
jest.spyOn(handler, 'getMeasures').mockReturnValue([]);
const measure = handler.getMeasure(undefined);
expect(measure).toBeFalsy();
});
test('should return measure when it exists in getMeasures()', () => {
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');
expect(measure).toEqual({ qDef: { cId: 'measure1' } });
expect(alternativeMeasure).toEqual({ qDef: { cId: 'altMeasure1' } });
});
});
});

View File

@@ -1,115 +0,0 @@
import HyperCubeHandler from '../hypercube-handler';
describe('HyperCube Handlers', () => {
let handler;
let properties;
beforeEach(() => {
properties = {
qHyperCubeDef: {
qDimensions: [{ qDef: { cId: 'dim1' } }],
qInterColumnSortOrder: [0, 1],
qLayoutExclude: {
qHyperCubeDef: {
qDimensions: [{ qDef: { cId: 'altDim1' } }],
qMeasures: [{ qDef: { cId: 'altMeasure1' } }],
},
},
},
};
handler = new HyperCubeHandler(properties);
});
afterEach(() => {
jest.clearAllMocks();
});
describe('setProperties', () => {
test('should return undefined when properties is null or undefined', () => {
handler.setProperties(null);
expect(handler.hcProperties).toBeUndefined();
handler.setProperties({});
expect(handler.hcProperties).toBeUndefined();
handler.setProperties(undefined);
expect(handler.hcProperties).toBeUndefined();
});
test('should set properties when qHyperCubeDef provides defined/undefined values', () => {
properties.qHyperCubeDef.qLayoutExclude.qHyperCubeDef.qDimensions = undefined;
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.qLayoutExclude).toEqual({
qHyperCubeDef: {
qDimensions: [],
qMeasures: [{ qDef: { cId: 'altMeasure1' } }],
},
});
});
test('should set properties when qLayoutExclude.qHyperCubeDef is undefined', () => {
properties.qHyperCubeDef.qLayoutExclude.qHyperCubeDef = undefined;
handler.setProperties(properties);
expect(handler.hcProperties.qLayoutExclude).toEqual({
qHyperCubeDef: {
qDimensions: [],
qMeasures: [],
},
});
});
});
describe('getDimensions and getAlternativeDimensions', () => {
test('should return empty arrays when hcProperties is null', () => {
handler.hcProperties = null;
expect(handler.getDimensions()).toEqual([]);
expect(handler.getAlternativeDimensions()).toEqual([]);
});
test('should return empty arrays when qDimensions and qLayoutExclude.qHyperCubeDef.qDimensions are empty', () => {
handler.hcProperties = {
qDimensions: [],
qLayoutExclude: {
qHyperCubeDef: {
qDimensions: [],
},
},
};
expect(handler.getDimensions()).toEqual([]);
expect(handler.getAlternativeDimensions()).toEqual([]);
});
test('should return qDimensions when qDimensions contains dimensions', () => {
handler.hcProperties = {
qDimensions: [{ qDef: { cId: 'dim1' } }, { qDef: { cId: 'dim2' } }],
qLayoutExclude: {
qHyperCubeDef: {
qDimensions: [],
},
},
};
expect(handler.getDimensions()).toEqual([{ qDef: { cId: 'dim1' } }, { qDef: { cId: 'dim2' } }]);
expect(handler.getAlternativeDimensions()).toEqual([]);
});
test('should return qLayoutExclude.qHyperCubeDef.qDimensions when it contains alternative dimensions', () => {
handler.hcProperties = {
qDimensions: [],
qLayoutExclude: {
qHyperCubeDef: {
qDimensions: [{ qDef: { cId: 'altDim1' } }, { qDef: { cId: 'altDim2' } }],
},
},
};
expect(handler.getDimensions()).toEqual([]);
expect(handler.getAlternativeDimensions()).toEqual([{ qDef: { cId: 'altDim1' } }, { qDef: { cId: 'altDim2' } }]);
});
});
});

View File

@@ -0,0 +1,180 @@
import DataPropertyHandler from '../data-property-handler';
import HyperCubeHandler from '../hypercube-handler';
describe('DataPropertyHandler', () => {
let handler;
let properties;
const sortingProperties = [
{
qSortByLoadOrder: 1,
qSortByNumeric: 1,
qSortByAscii: 1,
},
];
beforeEach(() => {
properties = {
qHyperCubeDef: {
qDimensions: [{ qDef: { cId: 'dim1' } }],
qLayoutExclude: {
qHyperCubeDef: {
qDimensions: [{ qDef: { cId: 'altDim1' } }],
qMeasures: [{ qDef: { cId: 'altMeasure1' } }],
},
},
},
};
handler = new HyperCubeHandler(properties);
});
afterEach(() => {
jest.clearAllMocks();
});
describe('getDimensions()', () => {
test('should return null when dimension is undefined', () => {
jest.spyOn(handler, 'getDimensions').mockReturnValue([]);
const dimension = handler.getDimension(undefined);
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');
expect(dimension).toEqual({ qDef: { cId: 'dim1' } });
const alternativeDimension = handler.getDimension('altDim1');
expect(alternativeDimension).toEqual({ qDef: { cId: 'altDim1' } });
});
});
describe('getMeasure()', () => {
test('should return null when both measures and alternative measures are empty', () => {
jest.spyOn(handler, 'getMeasures').mockReturnValue([]);
const measure = handler.getMeasure(undefined);
expect(measure).toBeFalsy();
});
test('should return measure when it exists in getMeasures()', () => {
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');
expect(measure).toEqual({ qDef: { cId: 'measure1' } });
expect(alternativeMeasure).toEqual({ qDef: { cId: 'altMeasure1' } });
});
});
describe('createFieldDimension', () => {
beforeEach(() => {
handler = new DataPropertyHandler({
dimensionProperties: { someProperty: 'defaultValue' },
});
});
test('should create a dimension with default properties when no field is provided', () => {
const result = handler.createFieldDimension(null, null, { customDefault: 'value' });
expect(result.qDef.qFieldDefs).toEqual([null]);
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');
});
describe('createLibraryDimension', () => {
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');
});
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 dimension with provided field and label', () => {
const result = handler.createFieldDimension('fieldName', 'fieldLabel', { customDefault: 'value' });
expect(result.qDef.qFieldDefs).toEqual(['fieldName']);
expect(result.qDef.qFieldLabels).toEqual(['fieldLabel']);
});
});
describe('createExpressionMeasure', () => {
beforeEach(() => {
handler = new DataPropertyHandler({
measureProperties: { someProperty: 'defaultValue' },
});
});
test('should create a measure with provided expression and label', () => {
const result = handler.createExpressionMeasure('SUM(Sales)', 'Total Sales', { customDefault: 'value' });
expect(result.qDef.qDef).toBe('SUM(Sales)');
expect(result.qDef.qLabel).toBe('Total Sales');
expect(result.qDef.autoSort).toBe(true);
expect(result.someProperty).toBe('defaultValue');
expect(result.customDefault).toBe('value');
});
test('should initialize qDef and qNumFormat if not provided', () => {
const result = handler.createExpressionMeasure('SUM(Sales)', 'Total Sales', {});
expect(result.qDef).toBeDefined();
expect(result.qDef.qNumFormat).toBeDefined();
});
test('should handle empty defaults gracefully', () => {
const result = handler.createExpressionMeasure('SUM(Sales)', 'Total Sales', null);
expect(result.qDef.qDef).toBe('SUM(Sales)');
expect(result.qDef.qLabel).toBe('Total Sales');
expect(result.qDef.autoSort).toBe(true);
expect(result.someProperty).toBe('defaultValue');
});
});
describe('createLibraryMeasure', () => {
beforeEach(() => {
handler = new DataPropertyHandler({
measureProperties: { someProperty: 'defaultValue' },
});
});
test('should create a library measure with provided id and defaults', () => {
const result = handler.createLibraryMeasure('libraryId', { customDefault: 'value' });
expect(result.qLibraryId).toBe('libraryId');
expect(result.qDef.qNumFormat).toBeDefined();
expect(result.qDef.autoSort).toBe(true);
expect(result.someProperty).toBe('defaultValue');
expect(result.customDefault).toBe('value');
});
test('should initialize qDef and qNumFormat if not provided', () => {
const result = handler.createLibraryMeasure('libraryId', {});
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', {});
expect(result.qDef.qDef).toBeUndefined();
expect(result.qDef.qLabel).toBeUndefined();
});
});
});

View File

@@ -0,0 +1,291 @@
import * as hcHelper from '../utils/hypercube-helper/hypercube-utils';
import HyperCubeHandler from '../hypercube-handler';
describe('HyperCube Handlers', () => {
let handler;
let properties;
beforeEach(() => {
properties = {
qHyperCubeDef: {
qDimensions: [{ qDef: { cId: 'dim1' } }],
qMeasures: [],
qInterColumnSortOrder: [0, 1],
qLayoutExclude: {
qHyperCubeDef: {
qDimensions: [{ qDef: { cId: 'altDim1' } }],
qMeasures: [{ qDef: { cId: 'altMeas1' } }],
},
},
},
};
handler = new HyperCubeHandler(properties);
});
afterEach(() => {
handler.hcProperties = undefined;
jest.clearAllMocks();
});
describe('setProperties', () => {
test('should return undefined when properties is null or undefined', () => {
handler.setProperties(null);
expect(handler.hcProperties).toBeUndefined();
handler.setProperties({});
expect(handler.hcProperties).toBeUndefined();
handler.setProperties(undefined);
expect(handler.hcProperties).toBeUndefined();
});
test('should set properties when qHyperCubeDef provides defined/undefined values', () => {
properties.qHyperCubeDef.qLayoutExclude.qHyperCubeDef.qDimensions = undefined;
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.qLayoutExclude).toEqual({
qHyperCubeDef: {
qDimensions: [],
qMeasures: [{ qDef: { cId: 'altMeas1' } }],
},
});
});
test('should set properties when qLayoutExclude.qHyperCubeDef is undefined', () => {
properties.qHyperCubeDef.qLayoutExclude.qHyperCubeDef = undefined;
handler.setProperties(properties);
expect(handler.hcProperties.qLayoutExclude).toEqual({
qHyperCubeDef: {
qDimensions: [],
qMeasures: [],
},
});
});
});
describe('getDimensions and getAlternativeDimensions', () => {
test('should return empty arrays when hcProperties is null', () => {
handler.setProperties(properties);
handler.hcProperties = null;
expect(handler.getDimensions()).toEqual([]);
expect(handler.getAlternativeDimensions()).toEqual([]);
});
test('should return empty arrays when qDimensions and alternative dimension are empty', () => {
handler.setProperties(properties);
handler.hcProperties = {
qDimensions: [],
qLayoutExclude: {
qHyperCubeDef: {
qDimensions: [],
},
},
};
expect(handler.getDimensions()).toEqual([]);
expect(handler.getAlternativeDimensions()).toEqual([]);
});
test('should return alternative dimensions when it has value', () => {
handler.setProperties(properties);
handler.hcProperties = {
qDimensions: [{ qDef: { cId: 'dim1' } }, { qDef: { cId: 'dim2' } }],
qLayoutExclude: {
qHyperCubeDef: {
qDimensions: [{ qDef: { cId: 'altDim1' } }, { qDef: { cId: 'altDim2' } }],
},
},
};
expect(handler.getDimensions()).toEqual([{ qDef: { cId: 'dim1' } }, { qDef: { cId: 'dim2' } }]);
expect(handler.getAlternativeDimensions()).toEqual([{ qDef: { cId: 'altDim1' } }, { qDef: { cId: 'altDim2' } }]);
});
});
describe('addDimensions', () => {
beforeEach(() => {
handler.setProperties(properties);
jest.spyOn(hcHelper, 'isTotalDimensionsExceeded').mockReturnValue(false);
});
test('should return an empty array when newDimensions is empty', async () => {
const result = await handler.addDimensions([]);
expect(result).toEqual([]);
});
test('should add dimensions to alternative dimensions when alternative is true', async () => {
const newDimensions = [{ qDef: { cId: 'altDim2' } }, { qDef: { cId: 'altDim3' } }];
const dimensions = await handler.addDimensions(newDimensions, true);
expect(dimensions).toEqual([
{ qDef: { cId: 'altDim2' }, qOtherTotalSpec: {} },
{ qDef: { cId: 'altDim3' }, qOtherTotalSpec: {} },
]);
expect(handler.hcProperties.qLayoutExclude.qHyperCubeDef.qDimensions).toEqual([
{ qDef: { cId: 'altDim1' } },
{ qDef: { cId: 'altDim2' }, qOtherTotalSpec: {} },
{ qDef: { cId: 'altDim3' }, qOtherTotalSpec: {} },
]);
});
test('should add dimensions to main dimensions when alternative is false', async () => {
const newDimensions = [{ qDef: { cId: 'dim2' } }];
handler.maxDimensions = jest.fn().mockReturnValue(2);
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(dimensions).toEqual([{ qDef: { cId: 'dim2' }, qOtherTotalSpec: {} }]);
expect(handler.hcProperties.qDimensions).toEqual([
{ qDef: { cId: 'dim1' } },
{ qDef: { cId: 'dim2' }, qOtherTotalSpec: {} },
]);
});
test('should not add dimensions when isTotalDimensionsExceeded returns true', async () => {
jest.spyOn(hcHelper, 'isTotalDimensionsExceeded').mockReturnValue(true);
const newDimensions = [{ qDef: { cId: 'dim2' } }];
const dimensions = await handler.addDimensions(newDimensions);
expect(dimensions).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 dimension = await handler.addDimensions(newDimensions, false);
expect(dimension).toEqual([{ qDef: { cId: 'dim2' }, qOtherTotalSpec: {} }]);
expect(handler.hcProperties.qDimensions).toEqual([{ qDef: { cId: 'dim1' } }]);
expect(handler.hcProperties.qLayoutExclude.qHyperCubeDef.qDimensions).toEqual([
{ qDef: { cId: 'altDim1' } },
{ qDef: { cId: 'dim2' }, qOtherTotalSpec: {} },
]);
});
});
describe('getMeasures and getAlternativeMeasures', () => {
test('should return empty arrays when hcProperties is null', () => {
handler.setProperties(properties);
handler.hcProperties = null;
expect(handler.getMeasures()).toEqual([]);
expect(handler.getAlternativeMeasures()).toEqual([]);
});
test('should return empty arrays when qMeasures and alternative measures are empty', () => {
handler.setProperties(properties);
handler.hcProperties = {
qMeasures: [],
qLayoutExclude: {
qHyperCubeDef: {
qDimensions: [],
qMeasures: [],
},
},
};
expect(handler.getMeasures()).toEqual([]);
expect(handler.getAlternativeMeasures()).toEqual([]);
});
test('should return qMeasures when qMeasures has value', () => {
handler.setProperties(properties);
handler.hcProperties = {
qMeasures: [{ qDef: { cId: 'meas1' } }, { qDef: { cId: 'meas2' } }],
qLayoutExclude: {
qHyperCubeDef: {
qMeasures: [{ qDef: { cId: 'altMeas1' } }, { qDef: { cId: 'altMeas2' } }],
},
},
};
expect(handler.getMeasures()).toEqual([{ qDef: { cId: 'meas1' } }, { qDef: { cId: 'meas2' } }]);
expect(handler.getAlternativeMeasures()).toEqual([{ qDef: { cId: 'altMeas1' } }, { qDef: { cId: 'altMeas2' } }]);
});
});
describe('addMeasures', () => {
beforeEach(() => {
properties.qHyperCubeDef.qMeasures = [{ qDef: { cId: 'meas1' } }];
handler.setProperties(properties);
jest.spyOn(hcHelper, 'isTotalMeasureExceeded').mockReturnValue(false);
});
test('should return an empty array when new measure is empty', () => {
const result = handler.addMeasures([]);
expect(result).toEqual([]);
});
test('should add measures to alternative measures when alternative is true', () => {
const newMeasures = [{ qDef: { cId: 'altMeas2' } }, { qDef: { cId: 'altMeas3' } }];
const measures = handler.addMeasures(newMeasures, true);
expect(measures).toEqual([{ qDef: { cId: 'altMeas2' } }, { qDef: { cId: 'altMeas3' } }]);
expect(handler.hcProperties.qMeasures).toEqual([{ qDef: { cId: 'meas1' } }]);
expect(handler.hcProperties.qLayoutExclude.qHyperCubeDef.qMeasures).toEqual([
{ qDef: { cId: 'altMeas1' } },
{ qDef: { cId: 'altMeas2' } },
{ qDef: { cId: 'altMeas3' } },
]);
});
test('should add measures to main measures when alternative is false', () => {
const newMeasures = [{ qDef: { cId: 'meas2' } }];
handler.maxMeasures = jest.fn().mockReturnValue(2);
handler.autoSortDimension = jest.fn();
const measures = handler.addMeasures(newMeasures, false);
expect(measures).toEqual([
{
qDef: { cId: 'meas2' },
qSortBy: {
qSortByLoadOrder: 1,
qSortByNumeric: -1,
},
},
]);
expect(handler.hcProperties.qMeasures).toEqual([
{ qDef: { cId: 'meas1' } },
{
qDef: { cId: 'meas2' },
qSortBy: {
qSortByLoadOrder: 1,
qSortByNumeric: -1,
},
},
]);
});
test('should not add measures when isTotalMeasureExceeded returns true', () => {
jest.spyOn(hcHelper, 'isTotalMeasureExceeded').mockReturnValue(true);
const newMeasure = [{ qDef: { cId: 'meas2' } }];
const measure = handler.addMeasures(newMeasure);
expect(measure).toEqual([]);
});
test('should add measure to alternative measures when maxMeasure is exceeded but less than total', () => {
handler.maxMeasures = jest.fn().mockReturnValue(1);
const newMeasure = [{ qDef: { cId: 'meas2' } }];
const measure = handler.addMeasures(newMeasure, false);
expect(measure).toEqual([{ qDef: { cId: 'meas2' } }]);
expect(handler.hcProperties.qMeasures).toEqual([{ qDef: { cId: 'meas1' } }]);
expect(handler.hcProperties.qLayoutExclude.qHyperCubeDef.qMeasures).toEqual([
{ qDef: { cId: 'altMeas1' } },
{
qDef: { cId: 'meas2' },
},
]);
});
});
});

View File

@@ -1,4 +1,10 @@
import { getFieldById } from './utils/handler-helper';
// eslint-disable-next-line import/no-extraneous-dependencies
import { merge } from 'lodash';
// eslint-disable-next-line import/no-relative-packages
import isEnabled from '../../../nucleus/src/flags/flags';
import { findFieldById, initializeField, useMasterNumberFormat } from './utils/field-helper/field-utils';
import { INITIAL_SORT_CRITERIAS } from './utils/constants';
import { notSupportedError } from './utils/hypercube-helper/hypercube-utils';
class DataPropertyHandler {
constructor(opts) {
@@ -30,7 +36,9 @@ class DataPropertyHandler {
throw new Error('Must override this method');
}
// ----------------DIMENSION----------------
// ---------------------------------------
// ---------------DIMENSION---------------
// ---------------------------------------
static getDimensions() {
return [];
@@ -40,26 +48,121 @@ class DataPropertyHandler {
const dimensions = this.getDimensions();
const alternativeDimensions = this.getAlternativeDimensions();
const dim = getFieldById(dimensions, id);
const altDim = getFieldById(alternativeDimensions, id);
return dim ?? altDim;
return findFieldById(dimensions, id) ?? findFieldById(alternativeDimensions, id);
}
static getAlternativeDimensions() {
throw new Error('Method not implemented.');
}
static addDimension() {
throw notSupportedError;
}
static addDimensions() {
throw notSupportedError;
}
static autoSortDimension() {
throw notSupportedError;
}
createLibraryDimension(id, defaults) {
let dimension = merge({}, this.dimensionProperties || {}, defaults || {});
dimension = initializeField(dimension);
dimension.qLibraryId = id;
dimension.qDef.autoSort = true;
dimension.qDef.qSortCriterias = INITIAL_SORT_CRITERIAS;
delete dimension.qDef.qFieldDefs;
delete dimension.qDef.qFieldLabels;
return dimension;
}
createFieldDimension(field, label, defaults) {
let dimension = merge({}, this.dimensionProperties || {}, defaults || {});
dimension = initializeField(dimension);
if (!field) {
dimension.qDef.qFieldDefs = [];
dimension.qDef.qFieldLabels = [];
dimension.qDef.qSortCriterias = [];
}
dimension.qDef.qFieldDefs = [field];
dimension.qDef.qFieldLabels = label ? [label] : [''];
dimension.qDef.qSortCriterias = INITIAL_SORT_CRITERIAS;
dimension.qDef.autoSort = true;
return dimension;
}
addFieldDimension(field, label, defaults) {
const dimension = this.createFieldDimension(field, label, defaults);
return this.addDimension(dimension);
}
addFieldDimensions(args) {
const dimensions = args.map(({ field, label, defaults }) => this.createFieldDimension(field, label, defaults));
return this.addDimensions(dimensions);
}
addLibraryDimension(id, defaults) {
const dimension = this.createLibraryDimension(id, defaults);
return this.addDimension(dimension);
}
addLibraryDimensions(args) {
const dimensions = args.map(({ id, defaults }) => this.createLibraryDimension(id, defaults));
const result = this.addDimensions(dimensions);
return result;
}
async addAltLibraryDimensions(args) {
const dimensions = args.map(({ id }) => this.createLibraryDimension(id));
return this.addDimensions(dimensions, true);
}
async addAltFieldDimensions(args) {
const dimensions = args.map(({ field }) => this.createFieldDimension(field));
return this.addDimensions(dimensions, true);
}
addAlternativeFieldDimension(field, label, defaults) {
const dimension = this.createFieldDimension(field, label, defaults);
return this.addDimension(dimension, true);
}
addAlternativeLibraryDimension(id, defaults) {
const dimension = this.createLibraryDimension(id, defaults);
return this.addDimension(dimension, true);
}
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];
return this.dimensionDefinition.max?.apply(null, dimParams);
}
return Number.isNaN(+this.dimensionDefinition.max) ? 10000 : this.dimensionDefinition.max;
}
// ---------------------------------------
// ----------------MEASURE----------------
// ---------------------------------------
getMeasure(id) {
const measures = this.getMeasures();
const alternativeMeasures = this.getAlternativeMeasures();
const meas = getFieldById(measures, id);
const altMeas = getFieldById(alternativeMeasures, id);
return meas ?? altMeas;
return findFieldById(measures, id) ?? findFieldById(alternativeMeasures, id);
}
static getMeasures() {
@@ -69,6 +172,101 @@ class DataPropertyHandler {
static getAlternativeMeasures() {
throw new Error('Method not implemented.');
}
static addMeasure() {
throw notSupportedError;
}
static addMeasures() {
throw notSupportedError;
}
static autoSortMeasure() {
throw notSupportedError;
}
createExpressionMeasure(expression, label, defaults) {
const measure = merge({}, this.measureProperties || {}, defaults || {});
measure.qDef = measure.qDef ?? {};
measure.qDef.qNumFormat = measure.qDef.qNumFormat ?? {};
measure.qDef.qDef = expression;
measure.qDef.qLabel = label;
measure.qDef.autoSort = true;
return measure;
}
addExpressionMeasure(expression, label, defaults) {
const measure = this.createExpressionMeasure(expression, label, defaults);
return this.addMeasure(measure);
}
addExpressionMeasures(args) {
const measures = args.map(({ expression, label, defaults }) =>
this.createExpressionMeasure(expression, label, defaults)
);
return this.addMeasures(measures);
}
createLibraryMeasure(id, defaults) {
const measure = merge({}, this.measureProperties || {}, defaults || {});
measure.qDef = measure.qDef ?? {};
measure.qDef.qNumFormat = measure.qDef.qNumFormat ?? {};
if (isEnabled('MASTER_MEASURE_FORMAT')) {
useMasterNumberFormat(measure.qDef);
}
measure.qLibraryId = id;
measure.qDef.autoSort = true;
delete measure.qDef.qDef;
delete measure.qDef.qLabel;
return measure;
}
addLibraryMeasure(id, defaults) {
const measure = this.createLibraryMeasure(id, defaults);
return this.addMeasure(measure);
}
addLibraryMeasures(args) {
const measures = args.map(({ id, defaults }) => this.createLibraryMeasure(id, defaults));
return this.addMeasures(measures);
}
addAltLibraryMeasures(args) {
const measures = args.map(({ id }) => this.createLibraryMeasure(id));
return this.addMeasures(measures, true);
}
addAltExpressionMeasures(args) {
const measures = args.map(({ expression }) => this.createExpressionMeasure(expression));
return this.addMeasures(measures, true);
}
addAlternativeExpressionMeasure(expression, label, defaults) {
const measure = this.createExpressionMeasure(expression, label, defaults);
return this.addMeasure(measure, true);
}
addAlternativeLibraryMeasure(id, defaults) {
const measure = this.createLibraryMeasure(id, defaults);
return this.addMeasure(measure, true);
}
maxMeasures(decrement) {
const decr = decrement || 0;
if (typeof this.measureDefinition.max === 'function') {
const dimLength = this.getDimensions().length - decr;
const measureParams = isEnabled('PS_21371_ANALYSIS_TYPES') ? [dimLength, this.properties] : [dimLength];
return this.measureDefinition.max.apply(null, measureParams);
}
return Number.isNaN(+this.measureDefinition.max) ? 10000 : this.measureDefinition.max;
}
}
export default DataPropertyHandler;

View File

@@ -1,7 +1,12 @@
// eslint-disable-next-line import/no-relative-packages
import utils from '../../../conversion/src/utils';
import DataPropertyHandler from './data-property-handler';
import { getHyperCube, setFieldProperties } from './utils/handler-helper';
import * as hcHelper 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';
class HyperCubeHandler extends DataPropertyHandler {
constructor(opts) {
@@ -11,7 +16,7 @@ class HyperCubeHandler extends DataPropertyHandler {
setProperties(properties) {
if (!properties) {
return;
return {};
}
super.setProperties(properties);
@@ -19,38 +24,17 @@ class HyperCubeHandler extends DataPropertyHandler {
this.hcProperties = this.path ? utils.getValue(properties, `${this.path}.qHyperCubeDef`) : properties.qHyperCubeDef;
if (!this.hcProperties) {
return;
return {};
}
// Set defaults
this.hcProperties.qDimensions = this.hcProperties.qDimensions ?? [];
this.hcProperties.qMeasures = this.hcProperties.qMeasures ?? [];
this.hcProperties.qInterColumnSortOrder = this.hcProperties.qInterColumnSortOrder ?? [];
this.hcProperties.qLayoutExclude = this.hcProperties.qLayoutExclude ?? {
qHyperCubeDef: { qDimensions: [], qMeasures: [] },
};
this.hcProperties.qLayoutExclude.qHyperCubeDef = this.hcProperties.qLayoutExclude.qHyperCubeDef ?? {
qDimensions: [],
qMeasures: [],
};
this.hcProperties.qLayoutExclude.qHyperCubeDef.qDimensions =
this.hcProperties.qLayoutExclude.qHyperCubeDef.qDimensions ?? [];
this.hcProperties.qLayoutExclude.qHyperCubeDef.qMeasures =
this.hcProperties.qLayoutExclude.qHyperCubeDef.qMeasures ?? [];
if (
this.hcProperties.isHCEnabled &&
this.hcProperties.qDynamicScript.length === 0 &&
this.hcProperties.qMode === 'S'
) {
// this is only for line chart with forecast
this.hcProperties.qDynamicScript = [];
}
hcHelper.setDefaultProperties(this);
hcHelper.setPropForLineChartWithForecast(this);
// Set auto-sort property (compatibility 0.85 -> 0.9),
// can probably be removed in 1.0
this.hcProperties.qDimensions = setFieldProperties(this.hcProperties.qDimensions);
this.hcProperties.qMeasures = setFieldProperties(this.hcProperties.qMeasures);
this.hcProperties.qDimensions = hcHelper.setFieldProperties(this.hcProperties.qDimensions);
this.hcProperties.qMeasures = hcHelper.setFieldProperties(this.hcProperties.qMeasures);
return {};
}
// ----------------------------------
@@ -70,10 +54,52 @@ class HyperCubeHandler extends DataPropertyHandler {
}
getDimensionLayouts() {
const hc = getHyperCube(this.layout, this.path);
const hc = hcHelper.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);
}
return addMainDimension(this, dim, idx);
}
async addDimensions(dimensions, alternative = false) {
const existingDimensions = this.getDimensions();
const addedDimensions = [];
let addedActive = 0;
// eslint-disable-next-line no-restricted-syntax
for await (const dimension of dimensions) {
if (hcHelper.isTotalDimensionsExceeded(this, existingDimensions)) {
return addedDimensions;
}
const dim = initializeField(dimension);
if (hcHelper.isDimensionAlternative(this, alternative)) {
const altDim = await hcHelper.addAlternativeDimension(this, dim);
addedDimensions.push(altDim);
} else if (existingDimensions.length < this.maxDimensions()) {
await hcHelper.addActiveDimension(this, dim, existingDimensions, addedDimensions, addedActive);
addedActive++;
}
}
return addedDimensions;
}
autoSortDimension(dimension) {
if (dimension.qLibraryId) {
return getAutoSortLibraryDimension(this, dimension);
}
return getAutoSortFieldDimension(this, dimension);
}
// ----------------------------------
// ------------ MEASURES ------------
// ----------------------------------
@@ -87,13 +113,57 @@ class HyperCubeHandler extends DataPropertyHandler {
}
getMeasureLayouts() {
const hc = getHyperCube(this.layout, this.path);
const hc = hcHelper.getHyperCube(this.layout, this.path);
return hc ? hc.qMeasureInfo : [];
}
getMeasureLayout(cId) {
return this.getMeasureLayouts().filter((item) => cId === item.cId)[0];
}
addMeasure(measure, alternative, idx) {
const meas = initializeField(measure);
if (hcHelper.isMeasureAlternative(this, meas, alternative)) {
const hcMeasures = this.hcProperties.qLayoutExclude.qHyperCubeDef.qMeasures;
return hcHelper.addAlternativeMeasure(meas, hcMeasures, idx);
}
return addMainMeasure(this, meas, idx);
}
// eslint-disable-next-line class-methods-use-this
autoSortMeasure(measure) {
const meas = { ...measure };
meas.qSortBy = {
qSortByLoadOrder: 1,
qSortByNumeric: -1,
};
return Promise.resolve(meas);
}
addMeasures(measures, alternative = false) {
const existingMeasures = this.getMeasures();
const addedMeasures = [];
let addedActive = 0;
measures.forEach(async (measure) => {
if (hcHelper.isTotalMeasureExceeded(this, existingMeasures)) {
return false;
}
const meas = initializeId(measure);
if (hcHelper.isMeasureAlternative(this, existingMeasures, alternative)) {
hcHelper.addAlternativeMeasure(this, meas);
addedMeasures.push(meas);
} else if (existingMeasures.length < this.maxMeasures()) {
await hcHelper.addActiveMeasure(this, meas, existingMeasures, addedMeasures, addedActive);
addedActive++;
}
return true;
});
return addedMeasures;
}
}
export default HyperCubeHandler;

View File

@@ -0,0 +1,14 @@
export const TOTAL_MAX = {
DIMENSIONS: 1000, // Maximum number of active dimensions + disabled dimensions
MEASURES: 1000, // Maximum number of active measures + disabled measures
};
export const AUTOCALENDAR_NAME = '.autoCalendar';
export const INITIAL_SORT_CRITERIAS = [
{
qSortByLoadOrder: 1,
qSortByNumeric: 1,
qSortByAscii: 1,
},
];

View File

@@ -0,0 +1,70 @@
import expandFieldsWithDerivedData from '../expand-field-derived-data';
import * as getDataGeoField from '../get-data-geo-field';
import * as getDerivedFields from '../get-derived-fields';
jest.mock('../get-data-geo-field', () => jest.fn());
jest.mock('../get-derived-fields', () => jest.fn());
describe('expandFieldsWithDerivedData', () => {
let inputList;
let geoField;
let derivedFields;
beforeEach(() => {
inputList = [{ name: 'field1' }];
geoField = { name: 'geoField' };
derivedFields = [{ name: 'derivedFields' }];
});
test('should expand fields with geo and derived fields', () => {
getDataGeoField.mockReturnValue(geoField);
getDerivedFields.mockReturnValue(derivedFields);
const result = expandFieldsWithDerivedData(inputList);
expect(result).toEqual([geoField, { name: 'derivedFields' }]);
});
test('should handle an empty input list', () => {
inputList = [];
getDataGeoField.mockReturnValue(geoField);
getDerivedFields.mockReturnValue(derivedFields);
const result = expandFieldsWithDerivedData(inputList);
expect(getDataGeoField.default).not.toHaveBeenCalled();
expect(getDerivedFields.default).not.toHaveBeenCalled();
expect(result).toEqual([]);
});
test('should handle fields with no derived fields', () => {
derivedFields = [];
getDataGeoField.mockReturnValue(geoField);
getDerivedFields.mockReturnValue(derivedFields);
const result = expandFieldsWithDerivedData(inputList);
expect(getDataGeoField).toHaveBeenCalledTimes(1);
expect(getDataGeoField).toHaveBeenCalledWith({ name: 'field1' });
expect(getDerivedFields).toHaveBeenCalledTimes(1);
expect(getDerivedFields).toHaveBeenCalledWith({ name: 'field1' });
expect(result).toEqual([geoField]);
});
test('should handle fields with no geo field', () => {
geoField = [];
getDataGeoField.mockReturnValue([]);
getDerivedFields.mockReturnValue(derivedFields);
const result = expandFieldsWithDerivedData(inputList);
expect(getDataGeoField).toHaveBeenCalledTimes(1);
expect(getDataGeoField).toHaveBeenCalledWith({ name: 'field1' });
expect(getDerivedFields).toHaveBeenCalledTimes(1);
expect(getDerivedFields).toHaveBeenCalledWith({ name: 'field1' });
expect(result).toEqual([[], ...derivedFields]);
});
});

View File

@@ -0,0 +1,39 @@
import findFieldInExpandedList from '../find-field-in-expandedList';
import * as expandFieldsWithDerivedData from '../expand-field-derived-data';
describe('findFieldInExpandedList', () => {
let fieldList;
let expandedList;
beforeEach(() => {
fieldList = [{ qName: 'field1' }, { qName: 'field2' }];
expandedList = [{ qName: 'field1' }, { qName: 'field2' }, { qName: 'derivedField' }];
});
afterEach(() => {
jest.clearAllMocks();
});
test('should return the field if it exists in the expanded list', () => {
jest.spyOn(expandFieldsWithDerivedData, 'default').mockImplementation(() => expandedList);
const result = findFieldInExpandedList('field1', fieldList);
expect(result).toEqual({ qName: 'field1' });
});
test('should return null if the field does not exist in the expanded list', () => {
jest.spyOn(expandFieldsWithDerivedData, 'default').mockImplementation(() => expandedList);
const result = findFieldInExpandedList('nonExistentField', fieldList);
expect(result).toBeNull();
});
test('should return null if the expanded list is empty', () => {
fieldList = [];
expandedList = null;
jest.spyOn(expandFieldsWithDerivedData, 'default').mockImplementation(() => expandedList);
const result = findFieldInExpandedList('field1', fieldList);
expect(result).toBeNull();
});
});

View File

@@ -0,0 +1,24 @@
import getDataGeoField from '../get-data-geo-field';
import { isDateField, isGeoField } from '../field-utils';
jest.mock('../field-utils', () => ({
isDateField: jest.fn(),
isGeoField: jest.fn(),
}));
describe('getDataGeoField', () => {
afterEach(() => {
jest.clearAllMocks();
});
test('should return field with the correct property values', () => {
const field = { name: 'dateField' };
isDateField.mockReturnValue(true);
isGeoField.mockReturnValue(false);
const result = getDataGeoField(field);
expect(result.isDateField).toBe(true);
expect(result.isGeoField).toBe(false);
});
});

View File

@@ -0,0 +1,131 @@
import getDerivedFields from '../get-derived-fields';
import { trimAutoCalendarName } from '../field-utils';
jest.mock('../field-utils', () => ({
trimAutoCalendarName: jest.fn((name) => `trimmed_${name}`),
}));
describe('getDerivedFields', () => {
let field;
beforeEach(() => {
field = {
qName: 'field1',
qSrcTables: ['table1'],
isDateField: true,
qDerivedFieldData: {
qDerivedFieldLists: [
{
qDerivedDefinitionName: 'DerivedDef1',
qFieldDefs: [
{
qName: 'derivedField1',
qTags: ['tag1'],
},
],
},
],
},
};
});
afterEach(() => {
jest.clearAllMocks();
});
test('should return an empty array if derived field data is undefined', () => {
field = { qName: 'field1' };
const result = getDerivedFields(field);
expect(result).toEqual([]);
});
test('should return an empty array if derived field data list is empty', () => {
field = {
qName: 'field1',
qDerivedFieldData: {
qDerivedFieldLists: [],
},
};
const result = getDerivedFields(field);
expect(result).toEqual([]);
});
test('should return derived fields with correct properties', () => {
const result = getDerivedFields(field);
expect(trimAutoCalendarName).toHaveBeenCalledWith('derivedField1');
expect(result).toEqual([
{
qName: 'derivedField1',
displayName: 'trimmed_derivedField1',
qSrcTables: ['table1'],
qTags: ['tag1'],
isDerived: true,
isDerivedFromDate: true,
sourceField: 'field1',
derivedDefinitionName: 'DerivedDef1',
},
]);
});
test('should set isDerivedFromDate to false if field.isDateField is false', () => {
field.isDateField = false;
const result = getDerivedFields(field);
expect(result[0].isDerivedFromDate).toBe(false);
});
test('should handle multiple derived fields', () => {
field = {
qName: 'field1',
qSrcTables: ['table1'],
isDateField: true,
qDerivedFieldData: {
qDerivedFieldLists: [
{
qDerivedDefinitionName: 'DerivedDef1',
qFieldDefs: [
{
qName: 'derivedField1',
qTags: ['tag1'],
},
{
qName: 'derivedField2',
qTags: ['tag2'],
},
],
},
],
},
};
const result = getDerivedFields(field);
expect(result).toEqual([
{
qName: 'derivedField1',
displayName: 'trimmed_derivedField1',
qSrcTables: ['table1'],
qTags: ['tag1'],
isDerived: true,
isDerivedFromDate: true,
sourceField: 'field1',
derivedDefinitionName: 'DerivedDef1',
},
{
qName: 'derivedField2',
displayName: 'trimmed_derivedField2',
qSrcTables: ['table1'],
qTags: ['tag2'],
isDerived: true,
isDerivedFromDate: true,
sourceField: 'field1',
derivedDefinitionName: 'DerivedDef1',
},
]);
});
});

View File

@@ -0,0 +1,80 @@
import getAutoSortFieldDimension from '../get-sorted-field';
import findFieldInExpandedList from '../find-field-in-expandedList';
import { setAutoSort } from '../field-utils';
jest.mock('../find-field-in-expandedList', () => jest.fn());
jest.mock('../field-utils', () => ({
setAutoSort: jest.fn(),
}));
describe('getAutoSortFieldDimension', () => {
let self;
let dimension;
beforeEach(() => {
self = {
app: {
getFieldList: jest.fn(),
},
};
dimension = {
qDef: {
qFieldDefs: ['field1'],
},
};
});
afterEach(() => {
jest.clearAllMocks();
});
test('should call findFieldInExpandedList with correct arguments', async () => {
const fieldList = [{ qName: 'field1' }];
self.app.getFieldList.mockResolvedValue(fieldList);
const result = await getAutoSortFieldDimension(self, dimension);
expect(self.app.getFieldList).toHaveBeenCalled();
expect(findFieldInExpandedList).toHaveBeenCalledWith('field1', fieldList);
expect(result).toBe(dimension);
});
test('should call setAutoSort if a field is found', async () => {
const fieldList = [{ qName: 'field1' }];
const field = { qName: 'field1' };
self.app.getFieldList.mockResolvedValue(fieldList);
findFieldInExpandedList.mockReturnValue(field);
await getAutoSortFieldDimension(self, dimension);
expect(setAutoSort).toHaveBeenCalledWith([field], dimension, self);
});
test('should not call setAutoSort if no field is found', async () => {
self.app.getFieldList.mockResolvedValue([]);
findFieldInExpandedList.mockReturnValue(null);
await getAutoSortFieldDimension(self, dimension);
expect(setAutoSort).not.toHaveBeenCalled();
});
test('should handle empty field list', async () => {
self.app.getFieldList.mockResolvedValue([]);
const result = await getAutoSortFieldDimension(self, dimension);
expect(result).toBe(dimension);
expect(setAutoSort).not.toHaveBeenCalled();
});
test('should handle missing qFieldDefs', async () => {
dimension.qDef.qFieldDefs = undefined;
self.app.getFieldList.mockResolvedValue([]);
const result = await getAutoSortFieldDimension(self, dimension);
expect(result).toBe(dimension);
expect(setAutoSort).not.toHaveBeenCalled();
});
});

View File

@@ -0,0 +1,77 @@
import getAutoSortLibraryDimension from '../get-sorted-library-field';
import { findLibraryItem, setAutoSort } from '../field-utils';
jest.mock('../field-utils', () => ({
findLibraryItem: jest.fn(),
setAutoSort: jest.fn(),
}));
describe('getAutoSortLibraryDimension', () => {
let self;
let dimension;
beforeEach(() => {
self = {
app: {
getDimensionList: jest.fn(),
},
};
dimension = {
qLibraryId: 'libDim1',
};
});
afterEach(() => {
jest.clearAllMocks();
});
test('should call findLibraryItem with correct arguments', async () => {
const dimensionList = [{ qInfo: { qId: 'libDim1' }, qData: { info: ['field1'] } }];
const libDim = dimensionList[0];
self.app.getDimensionList.mockResolvedValue(dimensionList);
findLibraryItem.mockReturnValue(libDim);
const result = await getAutoSortLibraryDimension(self, dimension);
expect(findLibraryItem).toHaveBeenCalledWith('libDim1', dimensionList);
expect(result).toBe(dimension);
});
test('should call setAutoSort if a library dimension is found', async () => {
const dimensionList = [{ qInfo: { qId: 'libDim1' }, qData: { info: ['field1'] } }];
const libDim = dimensionList[0];
self.app.getDimensionList.mockResolvedValue(dimensionList);
await getAutoSortLibraryDimension(self, dimension);
expect(setAutoSort).toHaveBeenCalledWith(libDim.qData.info, dimension, self);
});
test('should not call setAutoSort if no library dimension is found', async () => {
self.app.getDimensionList.mockResolvedValue([]);
findLibraryItem.mockReturnValue(null);
await getAutoSortLibraryDimension(self, dimension);
expect(setAutoSort).not.toHaveBeenCalled();
});
test('should handle empty dimension list', async () => {
self.app.getDimensionList.mockResolvedValue([]);
const result = await getAutoSortLibraryDimension(self, dimension);
expect(result).toBe(dimension);
expect(setAutoSort).not.toHaveBeenCalled();
});
test('should handle missing dimension list', async () => {
dimension = undefined;
self.app.getDimensionList.mockResolvedValue([]);
const result = await getAutoSortLibraryDimension(self, dimension);
expect(result).toBe(dimension);
expect(setAutoSort).not.toHaveBeenCalled();
});
});

View File

@@ -0,0 +1,65 @@
import { isDateField } from '../field-utils';
describe('isDateField', () => {
test('should return true if field has $date tag', () => {
const field01 = {
qDerivedFieldData: {},
qTags: ['$date', 'otherTag'],
};
const field02 = {
qDerivedFieldData: {},
qTags: ['otherTag', '$timestamp'],
};
const result01 = isDateField(field01);
expect(result01).toBe(true);
const result02 = isDateField(field02);
expect(result02).toBe(true);
});
test('should return false if field does not have $date or $timestamp tag', () => {
const field = {
qDerivedFieldData: {},
qTags: ['otherTag'],
};
const result = isDateField(field);
expect(result).toBeFalsy();
});
test('should return false if qDerivedFieldData is missing', () => {
const field = {
qTags: ['$date'],
};
const result = isDateField(field);
expect(result).toBeFalsy();
});
test('should return false if qTags is empty', () => {
const field = {
qDerivedFieldData: {},
qTags: [],
};
const result = isDateField(field);
expect(result).toBeFalsy();
});
test('should return false if field is null or undefined', () => {
expect(isDateField([])).toBeFalsy();
expect(isDateField(undefined)).toBeFalsy();
});
test('should return false if qTags is not an array', () => {
const field = {
qDerivedFieldData: {},
qTags: null,
};
const result = isDateField(field);
expect(result).toBeFalsy();
});
});

View File

@@ -0,0 +1,16 @@
import getDataGeoField from './get-data-geo-field';
import getDerivedFields from './get-derived-fields';
const expandFieldsWithDerivedData = (list) => {
const fieldList = [];
list.forEach((field) => {
fieldList.push(getDataGeoField(field));
const derivedFields = getDerivedFields(field);
fieldList.push(...derivedFields);
});
return fieldList;
};
export default expandFieldsWithDerivedData;

View File

@@ -0,0 +1,85 @@
// eslint-disable-next-line import/no-relative-packages
import uid from '../../../../../nucleus/src/object/uid';
import { AUTOCALENDAR_NAME } from '../constants';
/**
* Get the field name from the expression.
* @param {string} expression
* @returns the field
*/
export const getField = (expression) => {
let exp = expression;
exp = exp.trim();
if (exp.charAt(0) === '=') {
exp = exp.substring(1);
exp = exp.trim();
}
const lastIndex = exp.length - 1;
if (exp.charAt(0) === '[' && exp.charAt(lastIndex) === ']') {
exp = exp.substring(1, lastIndex);
exp = exp.trim();
}
return exp;
};
export const findFieldById = (fields, id) => (fields && fields.find((field) => field.qDef?.cId === id)) || null;
export const findLibraryItem = (id, masterItemList) =>
(masterItemList && masterItemList.find((item) => item.qInfo.qId === id)) || null;
export const findFieldByName = (name, fieldList) =>
(fieldList && fieldList.find((field) => field.qName === name)) || null;
export const initializeId = (field) => ({
...field,
qDef: {
...field.qDef,
cId: field.qDef?.cId ?? uid(),
},
});
export const initializeField = (field) => ({
...initializeId(field),
qOtherTotalSpec: field.qOtherTotalSpec ?? {},
});
export const setAutoSort = (fields, dimension, self) => {
const dim = dimension;
fields.forEach((field, index) => {
const tags = field.qTags;
const sortCriterias = {
qSortByLoadOrder: 1,
};
if (typeof self.dimensionDefinition.autoSort === 'function') {
self.dimensionDefinition.autoSort(dim, self.properties, tags, sortCriterias, self);
} else {
// Default auto sorting
sortCriterias.qSortByNumeric = 1;
sortCriterias.qSortByAscii = 1;
}
if (!dim.qDef.qSortCriterias) {
dim.qDef.qSortCriterias = [sortCriterias];
} else {
dim.qDef.qSortCriterias[index] = sortCriterias;
}
});
};
export const useMasterNumberFormat = (formatting) => {
const format = formatting;
format.quarantine = {
qNumFormat: format.qNumFormat || {},
isCustomFormatted: format.isCustomFormatted || false,
};
format.qNumFormat = null;
format.isCustomFormatted = undefined;
};
export const isDateField = (field) =>
field?.qDerivedFieldData && (field?.qTags?.indexOf('$date') > -1 || field?.qTags?.indexOf('$timestamp') > -1);
export const isGeoField = (field) => field.qTags.indexOf('$geoname') > -1;
export const trimAutoCalendarName = (fieldName) => (fieldName ? fieldName.split(AUTOCALENDAR_NAME).join('') : '');

View File

@@ -0,0 +1,10 @@
import expandFieldsWithDerivedData from './expand-field-derived-data';
import { findFieldByName, getField } from './field-utils';
const findFieldInExpandedList = (name, fieldList) => {
const expandedList = expandFieldsWithDerivedData(fieldList.slice(0));
const fieldName = getField(name);
return (expandedList && findFieldByName(fieldName, expandedList)) || null;
};
export default findFieldInExpandedList;

View File

@@ -0,0 +1,10 @@
import { isDateField, isGeoField } from './field-utils';
const getDataGeoField = (field) => {
const item = field;
item.isDateField = isDateField(item);
item.isGeoField = isGeoField(item);
return item;
};
export default getDataGeoField;

View File

@@ -0,0 +1,28 @@
import { trimAutoCalendarName } from './field-utils';
const getDerivedFields = (field) => {
const derivedFields = [];
if (!field.qDerivedFieldData) {
return derivedFields;
}
field.qDerivedFieldData.qDerivedFieldLists.forEach((derived) => {
derived.qFieldDefs.forEach((derivedField) => {
derivedFields.push({
qName: derivedField.qName,
displayName: trimAutoCalendarName(derivedField.qName),
qSrcTables: field.qSrcTables,
qTags: derivedField.qTags,
isDerived: true,
isDerivedFromDate: field.isDateField,
sourceField: field.qName,
derivedDefinitionName: derived.qDerivedDefinitionName,
});
});
});
return derivedFields;
};
export default getDerivedFields;

View File

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

View File

@@ -0,0 +1,13 @@
import { findLibraryItem, setAutoSort } from './field-utils';
function getAutoSortLibraryDimension(self, dimension) {
return self.app.getDimensionList().then((dimensionList) => {
const libDim = dimension?.qLibraryId && findLibraryItem(dimension.qLibraryId, dimensionList);
if (libDim) {
setAutoSort(libDim.qData.info, dimension, self);
}
return dimension;
});
}
export default getAutoSortLibraryDimension;

View File

@@ -1,32 +0,0 @@
// eslint-disable-next-line import/no-relative-packages
import getValue from '../../../../conversion/src/utils';
export const getFieldById = (fields, id) => fields.find((field) => field.qDef?.cId === id) || null;
export const setFieldProperties = (hcFieldProperties) => {
if (!hcFieldProperties) {
return [];
}
const updatedProperties = [...hcFieldProperties];
return updatedProperties.map((field) => {
if (field.qDef?.autoSort && field.autoSort !== undefined) {
return {
...field,
qDef: {
...field.qDef,
autoSort: field.autoSort,
},
autoSort: undefined,
};
}
return field;
});
};
export const getHyperCube = (layout, path) => {
if (!layout) {
return undefined;
}
return path && getValue(layout, path) ? getValue(layout, path).qHyperCube : layout.qHyperCube;
};

View File

@@ -0,0 +1,12 @@
import { insertMainDimension } from './hypercube-utils';
export default 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 Promise.resolve();
}

View File

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

View File

@@ -0,0 +1,182 @@
// 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';
export const notSupportedError = new Error('Not supported in this object, need to implement in subclass.');
export const setFieldProperties = (hcFieldProperties) => {
if (!hcFieldProperties) {
return [];
}
const updatedProperties = [...hcFieldProperties];
return updatedProperties.map((field) => {
if (field.qDef?.autoSort && field.autoSort !== undefined) {
return {
...field,
qDef: {
...field.qDef,
autoSort: field.autoSort,
},
autoSort: undefined,
};
}
return field;
});
};
export const getHyperCube = (layout, path) => {
if (!layout) {
return undefined;
}
return path && getValue(layout, path) ? getValue(layout, path).qHyperCube : layout.qHyperCube;
};
export function setDefaultProperties(self) {
const current = self;
current.hcProperties.qDimensions = current.hcProperties.qDimensions ?? [];
current.hcProperties.qMeasures = current.hcProperties.qMeasures ?? [];
current.hcProperties.qInterColumnSortOrder = current.hcProperties.qInterColumnSortOrder ?? [];
current.hcProperties.qLayoutExclude = current.hcProperties.qLayoutExclude ?? {
qHyperCubeDef: { qDimensions: [], qMeasures: [] },
};
current.hcProperties.qLayoutExclude.qHyperCubeDef = current.hcProperties.qLayoutExclude.qHyperCubeDef ?? {
qDimensions: [],
qMeasures: [],
};
current.hcProperties.qLayoutExclude.qHyperCubeDef.qDimensions =
current.hcProperties.qLayoutExclude.qHyperCubeDef.qDimensions ?? [];
current.hcProperties.qLayoutExclude.qHyperCubeDef.qMeasures =
current.hcProperties.qLayoutExclude.qHyperCubeDef.qMeasures ?? [];
}
export function setPropForLineChartWithForecast(self) {
const current = self;
if (
current.hcProperties.isHCEnabled &&
current.hcProperties.qDynamicScript.length === 0 &&
current.hcProperties.qMode === 'S'
) {
current.hcProperties.qDynamicScript = [];
}
}
// ----------------------------------
// ----------- DIMENSIONS -----------
// ----------------------------------
export function addAlternativeDimension(self, dimension, index = undefined) {
const dimensions = self.hcProperties.qLayoutExclude.qHyperCubeDef.qDimensions;
const idx = index ?? dimensions.length;
dimensions.splice(idx, 0, dimension);
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 addSortedDimension(self, dimension, dimensions, idx) {
const dimIdx = idx ?? dimensions.length;
dimensions.splice(dimIdx, 0, dimension);
return self.autoSortDimension(dimension).then(() => {
arrayUtil.indexAdded(self.hcProperties.qInterColumnSortOrder, dimIdx ?? dimensions.length - 1);
return self.dimensionDefinition.add?.call(self, dimension, self.properties, self) || Promise.resolve(dimension);
});
}
export function isTotalDimensionsExceeded(self, dimensions) {
const altDimensions = self.getAlternativeDimensions();
return altDimensions.length + dimensions.length >= TOTAL_MAX.DIMENSIONS;
}
export function isDimensionAlternative(self, alternative) {
const dimensions = self.hcProperties.qLayoutExclude.qHyperCubeDef.qDimensions;
return alternative || (self.maxDimensions() <= dimensions.length && dimensions.length < TOTAL_MAX.DIMENSIONS);
}
export async function addActiveDimension(self, dimension, existingDimensions, addedDimensions, addedActive) {
const initialLength = existingDimensions.length;
await self.autoSortDimension(dimension);
// Update sorting order
arrayUtil.indexAdded(self.hcProperties.qInterColumnSortOrder, initialLength + addedActive);
existingDimensions.push(dimension);
addedDimensions.push(dimension);
if (typeof self.dimensionDefinition.add === 'function') {
self.dimensionDefinition.add.call(self, dimension, self.properties, self);
}
}
// ----------------------------------
// ------------ MEASURES ------------
// ----------------------------------
export function addAlternativeMeasure(self, measure, index = undefined) {
const measures = self.hcProperties.qLayoutExclude.qHyperCubeDef.qMeasures;
const idx = index ?? measures.length;
measures.splice(idx, 0, measure);
return Promise.resolve(measure);
}
export function insertMainMeasure(self, measure, measures, idx) {
measures.splice(idx, 0, measure);
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 isTotalMeasureExceeded(self, measures) {
// Adding more measures than TOTAL_MAX_MEASURES is not allowed and we expect this.maxMeasures() to always be <= TOTAL_MAX_MEASURES
const altMeasures = self.getAlternativeMeasures();
return altMeasures.length + measures.length >= TOTAL_MAX.MEASURES;
}
export function isMeasureAlternative(self, measures, alternative) {
return alternative || (self.maxMeasures() <= measures.length && measures.length < TOTAL_MAX.MEASURES);
}
export function addActiveMeasure(self, measure, existingMeasures, addedMeasures, addedActive) {
const dimensions = self.getDimensions();
const meas = { ...measure };
meas.qSortBy = {
qSortByLoadOrder: 1,
qSortByNumeric: -1,
};
arrayUtil.indexAdded(
self.hcProperties.qInterColumnSortOrder,
dimensions.length + existingMeasures.length + addedActive
);
existingMeasures.push(meas);
addedMeasures.push(meas);
if (typeof self.measureDefinition.add === 'function') {
self.measureDefinition.add.call(null, meas, self.properties, self);
}
return Promise.resolve(addedMeasures);
}