Skip to content

Commit

Permalink
feat(asset): add calibration forecast property only row formatting (#68)
Browse files Browse the repository at this point in the history
Co-authored-by: Alex Kerezsi <[email protected]>
  • Loading branch information
kkerezsi and alexkerezsini authored Sep 19, 2024
1 parent 3a31c5b commit 162d782
Show file tree
Hide file tree
Showing 6 changed files with 559 additions and 44 deletions.
253 changes: 253 additions & 0 deletions src/datasources/asset-calibration/AssetCalibrationDataSource.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
import { BackendSrv } from "@grafana/runtime";
import { MockProxy } from "jest-mock-extended";
import {
createFetchError,
createFetchResponse,
getQueryBuilder,
requestMatching,
setupDataSource,
} from "test/fixtures";
import { AssetCalibrationDataSource } from "./AssetCalibrationDataSource";
import {
AssetCalibrationPropertyGroupByType,
AssetCalibrationQuery,
AssetCalibrationTimeBasedGroupByType,
CalibrationForecastResponse,
} from "./types";

let datastore: AssetCalibrationDataSource, backendServer: MockProxy<BackendSrv>

beforeEach(() => {
[datastore, backendServer] = setupDataSource(AssetCalibrationDataSource);
});

const monthGroupCalibrationForecastResponseMock: CalibrationForecastResponse =
{
calibrationForecast: {
columns: [
{ name: "Month", values: ["2022-01-01T00:00:00.0000000Z", "2022-02-01T00:00:00.0000000Z", "2022-03-01T00:00:00.0000000Z"] },
{ name: "Assets", values: [1, 0, 3] },
]
}
}

const weekGroupCalibrationForecastResponseMock: CalibrationForecastResponse =
{
calibrationForecast: {
columns: [
{ name: "Day", values: ["2022-01-01T00:00:00.0000000Z", "2022-01-02T00:00:00.0000000Z", "2022-01-03T00:00:00.0000000Z"] },
{ name: "Assets", values: [1, 2, 2] },
]
}
}

const dayGroupCalibrationForecastResponseMock: CalibrationForecastResponse =
{
calibrationForecast: {
columns: [
{ name: "Week", values: ["2022-01-03T00:00:00.0000000Z", "2022-01-10T00:00:00.0000000Z", "2022-01-17T00:00:00.0000000Z"] },
{ name: "Assets", values: [1, 2, 2] },
]
}
}

const locationGroupCalibrationForecastResponseMock: CalibrationForecastResponse =
{
calibrationForecast: {
columns: [
{ name: "Location1", values: [1] },
{ name: "Location2", values: [2] },
{ name: "Location3", values: [3] },
]
}
}

const modelGroupCalibrationForecastResponseMock: CalibrationForecastResponse =
{
calibrationForecast: {
columns: [
{ name: "Model1", values: [1] },
{ name: "Model2", values: [2] },
]
}
}

const emptyGroupCalibrationForecastResponseMock: CalibrationForecastResponse =
{
calibrationForecast: {
columns: [
]
}
}

const modelLocationGroupCalibrationForecastResponseMock: CalibrationForecastResponse =
{
calibrationForecast: {
columns: [
{ name: "Model1 - Localtion1", values: [1] },
{ name: "Model2 - Localtion1", values: [2] },
]
}
}

const monthLocationGroupCalibrationForecastResponseMock: CalibrationForecastResponse =
{
calibrationForecast: {
columns: [
{ name: "Month", values: ["2022-01-01T00:00:00.0000000Z", "2022-02-01T00:00:00.0000000Z", "2022-03-01T00:00:00.0000000Z"] },
{ name: "Location1", values: [1, 2, 3] },
{ name: "Location2", values: [2, 4, 1] },
{ name: "Location3", values: [4, 3, 1] },
]
}
}

const monthBasedCalibrationForecastQueryMock: AssetCalibrationQuery = {
refId: '',
groupBy: [AssetCalibrationTimeBasedGroupByType.Month],
}

const weekBasedCalibrationForecastQueryMock: AssetCalibrationQuery = {
refId: '',
groupBy: [AssetCalibrationTimeBasedGroupByType.Week],
}

const dayBasedCalibrationForecastQueryMock: AssetCalibrationQuery = {
refId: '',
groupBy: [AssetCalibrationTimeBasedGroupByType.Day],
}

const locationBasedCalibrationForecastQueryMock: AssetCalibrationQuery = {
refId: '',
groupBy: [AssetCalibrationPropertyGroupByType.Location],
}

const modelBasedCalibrationForecastQueryMock: AssetCalibrationQuery = {
refId: '',
groupBy: [AssetCalibrationPropertyGroupByType.Model],
}

const modelLocationBasedCalibrationForecastQueryMock: AssetCalibrationQuery = {
refId: '',
groupBy: [AssetCalibrationPropertyGroupByType.Model, AssetCalibrationPropertyGroupByType.Location],
}

const monthLocationBasedCalibrationForecastQueryMock: AssetCalibrationQuery = {
refId: '',
groupBy: [AssetCalibrationTimeBasedGroupByType.Month, AssetCalibrationPropertyGroupByType.Location],
}

const buildCalibrationForecastQuery = getQueryBuilder<AssetCalibrationQuery>()({
refId: '',
groupBy: []
});

describe('testDatasource', () => {
test('returns success', async () => {
backendServer.fetch
.calledWith(requestMatching({ url: '/niapm/v1/assets?take=1' }))
.mockReturnValue(createFetchResponse(25));

const result = await datastore.testDatasource();

expect(result.status).toEqual('success');
});

test('bubbles up exception', async () => {
backendServer.fetch
.calledWith(requestMatching({ url: '/niapm/v1/assets?take=1' }))
.mockReturnValue(createFetchError(400));

await expect(datastore.testDatasource()).rejects.toHaveProperty('status', 400);
});
})

describe('queries', () => {
test('asset calibration forecast with month groupBy', async () => {
backendServer.fetch
.calledWith(requestMatching({ url: '/niapm/v1/assets/calibration-forecast' }))
.mockReturnValue(createFetchResponse(monthGroupCalibrationForecastResponseMock as CalibrationForecastResponse))

const result = await datastore.query(buildCalibrationForecastQuery(monthBasedCalibrationForecastQueryMock))

expect(result.data).toMatchSnapshot()
})

test('asset calibration forecast with week groupBy', async () => {
backendServer.fetch
.calledWith(requestMatching({ url: '/niapm/v1/assets/calibration-forecast' }))
.mockReturnValue(createFetchResponse(weekGroupCalibrationForecastResponseMock as CalibrationForecastResponse))

const result = await datastore.query(buildCalibrationForecastQuery(weekBasedCalibrationForecastQueryMock))

expect(result.data).toMatchSnapshot()
})

test('asset calibration forecast with day groupBy', async () => {
backendServer.fetch
.calledWith(requestMatching({ url: '/niapm/v1/assets/calibration-forecast' }))
.mockReturnValue(createFetchResponse(dayGroupCalibrationForecastResponseMock as CalibrationForecastResponse))

const result = await datastore.query(buildCalibrationForecastQuery(dayBasedCalibrationForecastQueryMock))

expect(result.data).toMatchSnapshot()
})

test('calibration forecast with location groupBy', async () => {
backendServer.fetch
.calledWith(requestMatching({ url: '/niapm/v1/assets/calibration-forecast' }))
.mockReturnValue(createFetchResponse(locationGroupCalibrationForecastResponseMock as CalibrationForecastResponse))

const result = await datastore.query(buildCalibrationForecastQuery(locationBasedCalibrationForecastQueryMock))

expect(result.data).toMatchSnapshot()
})

test('calibration forecast with model groupBy', async () => {
backendServer.fetch
.calledWith(requestMatching({ url: '/niapm/v1/assets/calibration-forecast' }))
.mockReturnValue(createFetchResponse(modelGroupCalibrationForecastResponseMock as CalibrationForecastResponse))

const result = await datastore.query(buildCalibrationForecastQuery(modelBasedCalibrationForecastQueryMock))

expect(result.data).toMatchSnapshot()
})

test('calibration forecast with model and location groupBy', async () => {
backendServer.fetch
.calledWith(requestMatching({ url: '/niapm/v1/assets/calibration-forecast' }))
.mockReturnValue(createFetchResponse(modelLocationGroupCalibrationForecastResponseMock as CalibrationForecastResponse))

const result = await datastore.query(buildCalibrationForecastQuery(modelLocationBasedCalibrationForecastQueryMock))

expect(result.data).toMatchSnapshot()
})

test('calibration forecast with month and location groupBy', async () => {
backendServer.fetch
.calledWith(requestMatching({ url: '/niapm/v1/assets/calibration-forecast' }))
.mockReturnValue(createFetchResponse(monthLocationGroupCalibrationForecastResponseMock as CalibrationForecastResponse))

const result = await datastore.query(buildCalibrationForecastQuery(monthLocationBasedCalibrationForecastQueryMock))

expect(result.data).toMatchSnapshot()
})

test('calibration forecast with month groupBy returns empty results', async () => {
backendServer.fetch
.calledWith(requestMatching({ url: '/niapm/v1/assets/calibration-forecast' }))
.mockReturnValue(createFetchResponse(emptyGroupCalibrationForecastResponseMock as CalibrationForecastResponse))

const result = await datastore.query(buildCalibrationForecastQuery(monthBasedCalibrationForecastQueryMock))

expect(result.data).toMatchSnapshot()
})

test('handles metadata query error', async () => {
backendServer.fetch
.calledWith(requestMatching({ url: '/niapm/v1/assets/calibration-forecast' }))
.mockReturnValue(createFetchError(418))

await expect(datastore.query(buildCalibrationForecastQuery(monthBasedCalibrationForecastQueryMock))).rejects.toThrow()
})
})
71 changes: 40 additions & 31 deletions src/datasources/asset-calibration/AssetCalibrationDataSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import {
import { BackendSrv, getBackendSrv, getTemplateSrv, TemplateSrv } from '@grafana/runtime';
import { DataSourceBase } from 'core/DataSourceBase';
import {
AssetCalibrationForecastGroupByType,
AssetCalibrationForecastKey,
AssetCalibrationQuery,
AssetCalibrationTimeBasedGroupByType,
AssetModel,
AssetsResponse,
CalibrationForecastResponse,
Expand Down Expand Up @@ -44,41 +44,50 @@ export class AssetCalibrationDataSource extends DataSourceBase<AssetCalibrationQ
const calibrationForecastResponse: CalibrationForecastResponse = await this.queryCalibrationForecast(query.groupBy, from, to);

result.fields = calibrationForecastResponse.calibrationForecast.columns || [];
result.fields = result.fields.map(field => this.formatField(field, query));
if (this.isGroupByTime(query)) {
this.processResultsGroupedByTime(result)
} else {
this.processResultsGroupedByProperties(result)
}

return result;
}

formatField(field: FieldDTO, query: AssetCalibrationQuery): FieldDTO {
if (!field.values) {
return field;
}

if (field.name === AssetCalibrationForecastKey.Time) {
field.values = this.formatTimeField(field.values, query);
field.name = 'Formatted Time';
return field;
}
isGroupByTime(query: AssetCalibrationQuery) {
return query.groupBy.includes(AssetCalibrationTimeBasedGroupByType.Day) ||
query.groupBy.includes(AssetCalibrationTimeBasedGroupByType.Week) ||
query.groupBy.includes(AssetCalibrationTimeBasedGroupByType.Month);
}

return field;
processResultsGroupedByTime(result: DataFrameDTO) {
result.fields.forEach(field => {
switch (field.name) {
case AssetCalibrationForecastKey.Day:
field.values = field.values!.map(this.formatDateForDay)
break;
case AssetCalibrationForecastKey.Week:
field.values = field.values!.map(this.formatDateForWeek)
break;
case AssetCalibrationForecastKey.Month:
field.values = field.values!.map(this.formatDateForMonth)
break;
default:
break;
}
});
}

formatTimeField(values: string[], query: AssetCalibrationQuery): string[] {
const timeGrouping = query.groupBy.find(item =>
[AssetCalibrationForecastGroupByType.Day,
AssetCalibrationForecastGroupByType.Week,
AssetCalibrationForecastGroupByType.Month].includes(item as AssetCalibrationForecastGroupByType)
) as AssetCalibrationForecastGroupByType | undefined;

const formatFunctionMap = {
[AssetCalibrationForecastGroupByType.Day]: this.formatDateForDay,
[AssetCalibrationForecastGroupByType.Week]: this.formatDateForWeek,
[AssetCalibrationForecastGroupByType.Month]: this.formatDateForMonth,
};

const formatFunction = formatFunctionMap[timeGrouping!] || ((v: string) => v);
values = values.map(formatFunction);
return values;
processResultsGroupedByProperties(result: DataFrameDTO) {
const formattedFields = [] as FieldDTO[];
formattedFields.push({ name: "Group", values: [] } as FieldDTO);
formattedFields.push({ name: "Assets", values: [] } as FieldDTO);

for (let columnIndex = 0; columnIndex < result.fields.length; columnIndex++) {
formattedFields[0].values!.push(result.fields[columnIndex].name)
formattedFields[1].values!.push(result.fields[columnIndex].values?.at(0))
}

result.fields = formattedFields;
}

formatDateForDay(date: string): string {
Expand All @@ -96,8 +105,8 @@ export class AssetCalibrationDataSource extends DataSourceBase<AssetCalibrationQ
return new Date(date).toLocaleDateString('en-US', { year: 'numeric', month: 'long' });
}

shouldRunQuery(_: AssetCalibrationQuery): boolean {
return true;
shouldRunQuery(calibrationQuery: AssetCalibrationQuery): boolean {
return calibrationQuery.groupBy.length > 0;
}

async queryAssets(filter = '', take = -1): Promise<AssetModel[]> {
Expand Down
Loading

0 comments on commit 162d782

Please sign in to comment.