mirror of
https://github.com/qlik-oss/nebula.js.git
synced 2025-12-19 17:58:43 -05:00
refactor: add hypercube generic functions - part01 (#1716)
* chore: add hypercube generic functions - part01
This commit is contained in:
committed by
GitHub
parent
799c2b75a7
commit
7603c9b0fd
@@ -189,4 +189,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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' }]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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' } });
|
||||
});
|
||||
});
|
||||
});
|
||||
115
apis/supernova/src/handler/__test__/hypercube-handler.test.js
Normal file
115
apis/supernova/src/handler/__test__/hypercube-handler.test.js
Normal 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' } }]);
|
||||
});
|
||||
});
|
||||
});
|
||||
74
apis/supernova/src/handler/data-property-handler.js
Normal file
74
apis/supernova/src/handler/data-property-handler.js
Normal 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;
|
||||
99
apis/supernova/src/handler/hypercube-handler.js
Normal file
99
apis/supernova/src/handler/hypercube-handler.js
Normal 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;
|
||||
32
apis/supernova/src/handler/utils/handler-helper.js
Normal file
32
apis/supernova/src/handler/utils/handler-helper.js
Normal 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;
|
||||
};
|
||||
Reference in New Issue
Block a user