refactor: add hypercube generic functions - part01 (#1716)

* chore: add hypercube generic functions - part01
This commit is contained in:
Donya MashaallahPoor
2025-04-22 10:17:40 +02:00
committed by GitHub
parent 799c2b75a7
commit 7603c9b0fd
8 changed files with 450 additions and 12 deletions

View File

@@ -189,4 +189,4 @@
}
}
}
}
}

View File

@@ -1,13 +1,20 @@
import generator from '../../src/generator';
import HyperCubeHandler from '../../src/handler/hypercube-handler';
import * as Creator from '../../src/creator';
import * as Qae from '../../src/qae';
jest.mock('../../src/handler/hypercube-handler');
describe('generator', () => {
let creatorMock;
let qaeMock;
let galaxy;
beforeEach(() => {
galaxy = {
flags: {
isEnabled: jest.fn().mockReturnValue(false),
},
};
creatorMock = jest.fn().mockImplementation((...a) => [...a]);
qaeMock = jest.fn().mockImplementation((qae) => qae || 'qae');
@@ -21,42 +28,87 @@ describe('generator', () => {
});
test('should have a default qae property', () => {
expect(generator({}).qae).toBe('qae');
expect(generator({}, galaxy).qae).toBe('qae');
});
test('should have a component property', () => {
expect(generator({}).component).toEqual({});
expect(generator({}, galaxy).component).toEqual({});
});
test('should not override reserved properties', () => {
test('should not override reserved properties with flag disabled', () => {
expect(
generator({
foo: 'bar',
component: 'c',
}).definition
generator(
{
foo: 'bar',
component: 'c',
},
galaxy
).definition
).toEqual({
foo: 'bar',
});
});
test('should not override reserved properties with flag enabled', () => {
const mockDataHandler = jest.fn();
HyperCubeHandler.mockImplementation((opts) => {
mockDataHandler(opts);
});
galaxy.flags.isEnabled = jest.fn().mockReturnValue(true);
const input = {
foo: 'bar',
component: 'c',
};
const result = generator(input, galaxy).definition;
// Verify that the reserved property `dataHandler` is not overridden
expect(result.dataHandler).toBeInstanceOf(Function);
expect(result.dataHandler).not.toBe(input.dataHandler);
expect(result).toEqual(
expect.objectContaining({
foo: 'bar',
})
);
// `dataHandler` is the mocked HyperCubeHandler
const opts = { someOption: true };
result.dataHandler(opts);
expect(mockDataHandler).toHaveBeenCalledWith(opts);
});
test('should accept a function', () => {
const spy = jest.fn().mockReturnValue({});
generator(spy, { translator: 't', Promise: 'P' });
const isEnabled = jest.fn().mockReturnValue(false);
generator(spy, {
translator: 't',
Promise: 'P',
flags: {
isEnabled, // Use the mock function here
},
});
expect(spy).toHaveBeenCalledWith({
translator: 't',
flags: { isEnabled },
Promise: 'P',
});
});
test('should create an instance', () => {
const isEnabled = jest.fn().mockReturnValue(false);
const g = generator(
{},
{
translator: 't',
Promise: 'P',
flags: {
isEnabled,
},
}
);
const ret = g.create('a');
expect(ret).toEqual([g, 'a', { translator: 't', Promise: 'P' }]);
expect(ret).toEqual([g, 'a', { translator: 't', flags: { isEnabled }, Promise: 'P' }]);
});
});

View File

@@ -1,3 +1,4 @@
import HyperCubeHandler from './handler/hypercube-handler';
import create from './creator';
// import translator from './translator';
import qae from './qae';
@@ -73,7 +74,11 @@ export default function generatorFn(UserSN, galaxy) {
const ss = create(generator, params, galaxy);
return ss;
},
definition: {},
definition: galaxy.flags.isEnabled('NEBULA_DATA_HANDLERS')
? {
dataHandler: (opts) => new HyperCubeHandler(opts),
}
: {},
};
Object.keys(sn).forEach((key) => {

View File

@@ -0,0 +1,61 @@
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

@@ -0,0 +1,115 @@
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,74 @@
import { getFieldById } from './utils/handler-helper';
class DataPropertyHandler {
constructor(opts) {
const options = opts || {};
this.dimensionDefinition = options.dimensionDefinition || { max: 0 };
this.measureDefinition = options.measureDefinition || { max: 0 };
this.dimensionProperties = options.dimensionProperties ?? {};
this.measureProperties = options.measureProperties ?? {};
this.globalChangeListeners = options.globalChangeListeners;
this.app = options.app;
}
setProperties(properties) {
this.properties = properties;
this.isAnalysisType = this.properties?.metaData?.isAnalysisType;
}
setGlobalChangeListeners(arr) {
this.globalChangeListeners = arr;
}
setLayout(layout) {
this.layout = layout;
}
static type() {
throw new Error('Must override this method');
}
// ----------------DIMENSION----------------
static getDimensions() {
return [];
}
getDimension(id) {
const dimensions = this.getDimensions();
const alternativeDimensions = this.getAlternativeDimensions();
const dim = getFieldById(dimensions, id);
const altDim = getFieldById(alternativeDimensions, id);
return dim ?? altDim;
}
static getAlternativeDimensions() {
throw new Error('Method not implemented.');
}
// ----------------MEASURE----------------
getMeasure(id) {
const measures = this.getMeasures();
const alternativeMeasures = this.getAlternativeMeasures();
const meas = getFieldById(measures, id);
const altMeas = getFieldById(alternativeMeasures, id);
return meas ?? altMeas;
}
static getMeasures() {
return [];
}
static getAlternativeMeasures() {
throw new Error('Method not implemented.');
}
}
export default DataPropertyHandler;

View File

@@ -0,0 +1,99 @@
// 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';
class HyperCubeHandler extends DataPropertyHandler {
constructor(opts) {
super(opts);
this.path = opts.path;
}
setProperties(properties) {
if (!properties) {
return;
}
super.setProperties(properties);
this.hcProperties = this.path ? utils.getValue(properties, `${this.path}.qHyperCubeDef`) : properties.qHyperCubeDef;
if (!this.hcProperties) {
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 = [];
}
// 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);
}
// ----------------------------------
// ----------- DIMENSIONS -----------
// ----------------------------------
getDimensions() {
return this.hcProperties ? this.hcProperties.qDimensions : [];
}
getAlternativeDimensions() {
return this.hcProperties?.qLayoutExclude?.qHyperCubeDef?.qDimensions ?? [];
}
getDimensionLayout(cId) {
return this.getDimensionLayouts().filter((item) => cId === item.cId)[0];
}
getDimensionLayouts() {
const hc = getHyperCube(this.layout, this.path);
return hc ? hc.qDimensionInfo : [];
}
// ----------------------------------
// ------------ MEASURES ------------
// ----------------------------------
getMeasures() {
return this.hcProperties ? this.hcProperties.qMeasures : [];
}
getAlternativeMeasures() {
return this.hcProperties?.qLayoutExclude?.qHyperCubeDef?.qMeasures ?? [];
}
getMeasureLayouts() {
const hc = getHyperCube(this.layout, this.path);
return hc ? hc.qMeasureInfo : [];
}
getMeasureLayout(cId) {
return this.getMeasureLayouts().filter((item) => cId === item.cId)[0];
}
}
export default HyperCubeHandler;

View File

@@ -0,0 +1,32 @@
// 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;
};