Skip to content

Commit

Permalink
fix(assets): split asset utilization plugin code from asset calibrati…
Browse files Browse the repository at this point in the history
…on plugin code (#65)

Co-authored-by: Alex Kerezsi <[email protected]>
Co-authored-by: Alex Kerezsi <[email protected]>
  • Loading branch information
3 people authored Sep 9, 2024
1 parent 31f515a commit 2c99c56
Show file tree
Hide file tree
Showing 21 changed files with 494 additions and 565 deletions.
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
* @mure @cameronwaterman

/src/datasources/asset-calibration @CiprianAnton @kkerezsi
/src/datasources/asset @CiprianAnton @kkerezsi
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Tools](https://grafana.github.io/plugin-tools/).
- [Systems](src/datasources/system/)
- [Tags](src/datasources/tag/)
- [Assets](src/datasources/asset/)
- [Assets calibration](src/datasources/asset-calibration/)

### Panels

Expand Down
6 changes: 5 additions & 1 deletion provisioning/example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,11 @@ datasources:
type: ni-slworkspace-datasource
uid: workspace
<<: *config
- name: SystemLink Assets
- name: SystemLink Assets Utilization
type: ni-slasset-datasource
uid: asset
<<: *config
- name: SystemLink Assets Calibration
type: ni-slassetcalibration-datasource
uid: assetcalibration
<<: *config
141 changes: 141 additions & 0 deletions src/datasources/asset-calibration/AssetCalibrationDataSource.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import {
DataFrameDTO,
DataQueryRequest,
DataSourceInstanceSettings,
FieldDTO,
TestDataSourceResponse,
} from '@grafana/data';
import { BackendSrv, getBackendSrv, getTemplateSrv, TemplateSrv } from '@grafana/runtime';
import { DataSourceBase } from 'core/DataSourceBase';
import {
AssetCalibrationForecastGroupByType,
AssetCalibrationForecastKey,
AssetCalibrationQuery,
AssetModel,
AssetsResponse,
CalibrationForecastResponse,
} from './types';
import { SystemMetadata } from "../system/types";
import { defaultOrderBy, defaultProjection } from "../system/constants";

export class AssetCalibrationDataSource extends DataSourceBase<AssetCalibrationQuery> {
constructor(
readonly instanceSettings: DataSourceInstanceSettings,
readonly backendSrv: BackendSrv = getBackendSrv(),
readonly templateSrv: TemplateSrv = getTemplateSrv()
) {
super(instanceSettings, backendSrv, templateSrv);
}

defaultQuery = {
groupBy: [],
};

baseUrl = this.instanceSettings.url + '/niapm/v1';

async runQuery(query: AssetCalibrationQuery, options: DataQueryRequest): Promise<DataFrameDTO> {
return await this.processCalibrationForecastQuery(query as AssetCalibrationQuery, options);
}
async processCalibrationForecastQuery(query: AssetCalibrationQuery, options: DataQueryRequest) {
const result: DataFrameDTO = { refId: query.refId, fields: [] };
const from = options.range!.from.toISOString();
const to = options.range!.to.toISOString();

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));

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;
}

return field;
}

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;
}

formatDateForDay(date: string): string {
return new Date(date).toISOString().split('T')[0];
}

formatDateForWeek(date: string): string {
const startDate = new Date(date);
const endDate = new Date(startDate);
endDate.setDate(startDate.getDate() + 6);
return `${startDate.toISOString().split('T')[0]} : ${endDate.toISOString().split('T')[0]}`;
}

formatDateForMonth(date: string): string {
return new Date(date).toLocaleDateString('en-US', { year: 'numeric', month: 'long' });
}

shouldRunQuery(_: AssetCalibrationQuery): boolean {
return true;
}

async queryAssets(filter = '', take = -1): Promise<AssetModel[]> {
let data = { filter, take };
try {
let response = await this.post<AssetsResponse>(this.baseUrl + '/query-assets', data);
return response.assets;
} catch (error) {
throw new Error(`An error occurred while querying assets: ${error}`);
}
}

async queryCalibrationForecast(groupBy: string[], startTime: string, endTime: string, filter = ''): Promise<CalibrationForecastResponse> {
let data = { groupBy, startTime, endTime, filter };
try {
let response = await this.post<CalibrationForecastResponse>(this.baseUrl + '/assets/calibration-forecast', data);
return response;
} catch (error) {
throw new Error(`An error occurred while querying assets calibration forecast: ${error}`);
}
}

async querySystems(filter = '', projection = defaultProjection): Promise<SystemMetadata[]> {
try {
let response = await this.getSystems({
filter: filter,
projection: `new(${projection.join()})`,
orderBy: defaultOrderBy,
})

return response.data;
} catch (error) {
throw new Error(`An error occurred while querying systems: ${error}`);
}
}

async testDatasource(): Promise<TestDataSourceResponse> {
await this.get(this.baseUrl + '/assets?take=1');
return { status: 'success', message: 'Data source connected and authentication successful!' };
}
}
5 changes: 5 additions & 0 deletions src/datasources/asset-calibration/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# SystemLink Asset Calibration data source

This is a plugin for the Asset service calibration related functionalities. It allows you to:

- Visualize asset calibration forecast
Original file line number Diff line number Diff line change
@@ -1,24 +1,27 @@
import { SelectableValue, toOption } from '@grafana/data';
import { AssetDataSource } from '../AssetDataSource';
import { AssetCalibrationForecastGroupByType, AssetCalibrationForecastQuery, AssetQuery } from '../types';
import { QueryEditorProps, SelectableValue, toOption } from '@grafana/data';
import { AssetCalibrationDataSource } from '../AssetCalibrationDataSource';
import { AssetCalibrationForecastGroupByType, AssetCalibrationQuery } from '../types';
import { InlineField, MultiSelect } from '@grafana/ui';
import React from 'react';
import { enumToOptions } from '../../../core/utils';
import _ from 'lodash';

type Props = {
query: AssetCalibrationForecastQuery;
handleQueryChange: (value: AssetQuery, runQuery: boolean) => void;
datasource: AssetDataSource;
};
type Props = QueryEditorProps<AssetCalibrationDataSource, AssetCalibrationQuery>;

export function QueryCalibrationForecastEditor({ query, handleQueryChange, datasource }: Props) {
query = datasource.prepareQuery(query) as AssetCalibrationForecastQuery;
export function AssetCalibrationQueryEditor({ query, onChange, onRunQuery, datasource }: Props) {
query = datasource.prepareQuery(query) as AssetCalibrationQuery;
const handleGroupByChange = (items?: Array<SelectableValue<string>>): void => {
if (!items || _.isEqual(query.groupBy, items)) {
return;
}

const handleQueryChange = (value: AssetCalibrationQuery, runQuery: boolean): void => {
onChange(value);
if (runQuery) {
onRunQuery();
}
};

let groupBy: string[] = [];
let timeGrouping: string = null!;

Expand All @@ -42,7 +45,7 @@ export function QueryCalibrationForecastEditor({ query, handleQueryChange, datas
};

return (
<>
<div style={{ position: 'relative' }}>
<InlineField label="Group by" tooltip={tooltips.calibrationForecast.groupBy} labelWidth={22}>
<MultiSelect
options={enumToOptions(AssetCalibrationForecastGroupByType)}
Expand All @@ -51,7 +54,7 @@ export function QueryCalibrationForecastEditor({ query, handleQueryChange, datas
value={query.groupBy.map(toOption) || []}
/>
</InlineField>
</>
</div>
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,47 +1,29 @@
import { screen, waitFor } from '@testing-library/react';
import { setupRenderer } from '../../../test/fixtures';
import { SystemMetadata } from '../../system/types';
import { AssetDataSource } from '../AssetDataSource';
import { AssetQueryEditor } from './AssetQueryEditor';
import { AssetCalibrationForecastGroupByType, AssetCalibrationForecastQuery, AssetQueryType } from '../types';
import { AssetCalibrationForecastGroupByType, AssetCalibrationQuery } from '../types';
import { select } from 'react-select-event';
import { AssetCalibrationDataSource } from '../AssetCalibrationDataSource';
import { AssetCalibrationQueryEditor } from './AssetCalibrationQueryEditor';

const fakeSystems: SystemMetadata[] = [
{
id: '1',
state: 'CONNECTED',
workspace: '1',
},
{
id: '2',
state: 'CONNECTED',
workspace: '2',
},
];

class FakeAssetDataSource extends AssetDataSource {
querySystems(filter?: string, projection?: string[]): Promise<SystemMetadata[]> {
return Promise.resolve(fakeSystems);
}
class FakeAssetCalibrationDataSource extends AssetCalibrationDataSource {
}

const render = setupRenderer(AssetQueryEditor, FakeAssetDataSource);
const render = setupRenderer(AssetCalibrationQueryEditor ,FakeAssetCalibrationDataSource);

it('renders with query type calibration forecast', async () => {
render({ queryKind: AssetQueryType.CalibrationForecast } as AssetCalibrationForecastQuery);
render({} as AssetCalibrationQuery);

const groupBy = screen.getAllByRole('combobox')[1];
const groupBy = screen.getAllByRole('combobox')[0];
expect(groupBy).not.toBeNull();
});

it('renders with query type calibration forecast and updates group by', async () => {
const [onChange] = render({
queryKind: AssetQueryType.CalibrationForecast,
groupBy: [AssetCalibrationForecastGroupByType.Month],
} as AssetCalibrationForecastQuery);
} as AssetCalibrationQuery);

// User selects group by day
const groupBy = screen.getAllByRole('combobox')[1];
const groupBy = screen.getAllByRole('combobox')[0];
await select(groupBy, "Day", { container: document.body });
await waitFor(() => {
expect(onChange).toHaveBeenCalledWith(
Expand Down
11 changes: 11 additions & 0 deletions src/datasources/asset-calibration/img/logo-ni.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions src/datasources/asset-calibration/module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { DataSourcePlugin } from '@grafana/data';
import { AssetCalibrationDataSource } from './AssetCalibrationDataSource';
import { HttpConfigEditor } from 'core/components/HttpConfigEditor';
import { AssetCalibrationQueryEditor } from './components/AssetCalibrationQueryEditor';

export const plugin = new DataSourcePlugin(AssetCalibrationDataSource)
.setConfigEditor(HttpConfigEditor)
.setQueryEditor(AssetCalibrationQueryEditor);
15 changes: 15 additions & 0 deletions src/datasources/asset-calibration/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"type": "datasource",
"name": "SystemLink Asset Calibration",
"id": "ni-slassetcalibration-datasource",
"metrics": true,
"info": {
"author": {
"name": "NI"
},
"logos": {
"small": "img/logo-ni.svg",
"large": "img/logo-ni.svg"
}
}
}
Loading

0 comments on commit 2c99c56

Please sign in to comment.