Skip to content

Commit

Permalink
feat(asset): multiple values from global vals filter support (#80)
Browse files Browse the repository at this point in the history
Co-authored-by: Robert Aradei <[email protected]>
  • Loading branch information
saradei-ni and Robert Aradei authored Oct 15, 2024
1 parent c1ebd6b commit a710a2e
Show file tree
Hide file tree
Showing 2 changed files with 121 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
} from "./types";
import { SystemMetadata } from "datasources/system/types";
import { dateTime } from "@grafana/data";
import { AssetCalibrationFieldNames } from "./constants";

let datastore: AssetCalibrationDataSource, backendServer: MockProxy<BackendSrv>

Expand Down Expand Up @@ -371,7 +372,7 @@ describe('queries', () => {

await expect(datastore.query(buildCalibrationForecastQuery(monthBasedCalibrationForecastQueryMock))).rejects.toThrow()
})

test('validate DAY grouping', async () => {
const request = buildCalibrationForecastQuery(dayBasedCalibrationForecastQueryMock);
const numberOfDays = 31 * 3 + 1;
Expand All @@ -396,3 +397,70 @@ describe('queries', () => {
await expect(datastore.query(request)).rejects.toThrow('Query range exceeds range limit of MONTH grouping method: 5 years');
})
})

describe('Asset calibration location queries', () => {
let processCalibrationForecastQuerySpy: jest.SpyInstance;

beforeEach(() => {
processCalibrationForecastQuerySpy = jest.spyOn(datastore, 'processCalibrationForecastQuery').mockImplementation();
});

test('should transform LOCATION field with single value', async () => {
const query = buildCalibrationForecastQuery({
refId: '',
groupBy: [AssetCalibrationTimeBasedGroupByType.Month],
filter: `${AssetCalibrationFieldNames.LOCATION} = "Location1"`,
});

await datastore.query(query);

expect(processCalibrationForecastQuerySpy).toHaveBeenCalledWith(
expect.objectContaining({
groupBy: [AssetCalibrationTimeBasedGroupByType.Month],
filter: "(Location.MinionId = \"Location1\" || Location.PhysicalLocation = \"Location1\")"
}),
expect.anything()
);
});

test('should transform LOCATION field with single value and cache hit', async () => {
datastore.systemAliasCache.set('Location1', { id: 'Location1', alias: 'Location1-alias', state: 'CONNECTED', workspace: '1' });

const query = buildCalibrationForecastQuery({
refId: '',
groupBy: [AssetCalibrationTimeBasedGroupByType.Month],
filter: `${AssetCalibrationFieldNames.LOCATION} = "Location1"`,
});

await datastore.query(query);

expect(processCalibrationForecastQuerySpy).toHaveBeenCalledWith(
expect.objectContaining({
groupBy: [AssetCalibrationTimeBasedGroupByType.Month],
filter: "Location.MinionId = \"Location1\""
}),
expect.anything()
);
});

test('should transform LOCATION field with multiple values and cache hit', async () => {
datastore.systemAliasCache.set('Location1', { id: 'Location1', alias: 'Location1-alias', state: 'CONNECTED', workspace: '1' });
datastore.systemAliasCache.set('Location2', { id: 'Location2', alias: 'Location2-alias', state: 'CONNECTED', workspace: '2' });

const query = buildCalibrationForecastQuery({
refId: '',
groupBy: [AssetCalibrationTimeBasedGroupByType.Month],
filter: `${AssetCalibrationFieldNames.LOCATION} = "{Location1,Location2}"`,
});

await datastore.query(query);

expect(processCalibrationForecastQuerySpy).toHaveBeenCalledWith(
expect.objectContaining({
groupBy: [AssetCalibrationTimeBasedGroupByType.Month],
filter: "(Location.MinionId = \"Location1\" || Location.MinionId = \"Location2\")"
}),
expect.anything()
);
});
});
65 changes: 52 additions & 13 deletions src/datasources/asset-calibration/AssetCalibrationDataSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,36 +51,75 @@ export class AssetCalibrationDataSource extends DataSourceBase<AssetCalibrationQ
super(instanceSettings, backendSrv, templateSrv);
}

async runQuery(query: AssetCalibrationQuery, options: DataQueryRequest): Promise<DataFrameDTO> {
await this.loadDependencies();
this.validateQueryRange(query, options);

if (query.filter) {
query.filter = transformComputedFieldsQuery(
this.templateSrv.replace(query.filter, options.scopedVars),
this.assetComputedDataFields,
this.queryTransformationOptions
);
}

return await this.processCalibrationForecastQuery(query as AssetCalibrationQuery, options);
}

private readonly assetComputedDataFields = new Map<AssetCalibrationFieldNames, ExpressionTransformFunction>([
...Object.values(AssetCalibrationFieldNames).map(field => [field, this.multipleValuesQuery(field)] as [AssetCalibrationFieldNames, ExpressionTransformFunction]),
[
AssetCalibrationFieldNames.LOCATION,
(value: string, operation: string, options?: TTLCache<string, unknown>) => {
let values = [value];

if (this.isMultiSelectValue(value)) {
values = this.getMultipleValuesArray(value);
}

if (values.length > 1) {
return `(${values.map(val => `Location.MinionId ${operation} "${val}"`).join(` ${this.getLocicalOperator(operation)} `)})`;
}

if (options?.has(value)) {
return `Location.MinionId ${operation} "${value}"`
}

const logicalOperator = operation === QueryBuilderOperations.EQUALS.name ? '||' : '&&';
return `(Location.MinionId ${operation} "${value}" ${logicalOperator} Location.PhysicalLocation ${operation} "${value}")`;
return `(Location.MinionId ${operation} "${value}" ${this.getLocicalOperator(operation)} Location.PhysicalLocation ${operation} "${value}")`;
}
],
]
]);

private readonly queryTransformationOptions = new Map<AssetCalibrationFieldNames, TTLCache<string, unknown>>([
[AssetCalibrationFieldNames.LOCATION, this.systemAliasCache]
]);
private multipleValuesQuery(field: AssetCalibrationFieldNames): ExpressionTransformFunction {
return (value: string, operation: string, _options?: any) => {
if (this.isMultiSelectValue(value)) {
const query = this.getMultipleValuesArray(value)
.map(val => `${field} ${operation} "${val}"`)
.join(` ${this.getLocicalOperator(operation)} `);

async runQuery(query: AssetCalibrationQuery, options: DataQueryRequest): Promise<DataFrameDTO> {
await this.loadDependencies();
this.validateQueryRange(query, options);
return `(${query})`;
}

if (query.filter) {
const transformedQuery = transformComputedFieldsQuery(query.filter, this.assetComputedDataFields, this.queryTransformationOptions);
query.filter = this.templateSrv.replace(transformedQuery, options.scopedVars);
return `${field} ${operation} "${value}"`
}
}

return await this.processCalibrationForecastQuery(query as AssetCalibrationQuery, options);
private isMultiSelectValue(value: string): boolean {
return value.startsWith('{') && value.endsWith('}');
}

private getMultipleValuesArray(value: string): string[] {
return value.replace(/({|})/g, '').split(',');
}

private getLocicalOperator(operation: string): string {
return operation === QueryBuilderOperations.EQUALS.name ? '||' : '&&';
}

private readonly queryTransformationOptions = new Map<AssetCalibrationFieldNames, TTLCache<string, unknown>>([
[AssetCalibrationFieldNames.LOCATION, this.systemAliasCache]
]);

async processCalibrationForecastQuery(query: AssetCalibrationQuery, options: DataQueryRequest) {
const result: DataFrameDTO = { refId: query.refId, fields: [] };
const from = options.range!.from.toISOString();
Expand Down

0 comments on commit a710a2e

Please sign in to comment.