Skip to content

Commit

Permalink
Prepartion for version 5.1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
coderReview committed Aug 13, 2024
1 parent f9345d5 commit 9cf543f
Show file tree
Hide file tree
Showing 11 changed files with 252 additions and 92 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,11 @@
- Changed the query editor layout
- Support Grafana version 11
- Drop support for Grafana 8.x and 9.x

## 5.1.0

- Fixed an error in recorded max number of points
- Updated the query editor layout
- Added boundary type support in recorded values
- Recognize partial usage of variables in elements
- Added configuration to hide API errors in panel
2 changes: 1 addition & 1 deletion dist/module.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/module.js.map

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions dist/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@
{"name": "Datasource Configuration", "path": "img/configuration.png"},
{"name": "Annotations Editor", "path": "img/annotations.png"}
],
"version": "5.0.0",
"updated": "2024-06-14"
"version": "5.1.0",
"updated": "2024-08-13"
},
"dependencies": {
"grafanaDependency": ">=10.1.0",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "grid-protection-alliance-osisoftpi-grafana",
"version": "5.0.0",
"version": "5.1.0",
"description": "OSISoft PI Grafana Plugin",
"scripts": {
"build": "webpack -c ./.config/webpack/webpack.config.ts --env production",
Expand Down
7 changes: 5 additions & 2 deletions pkg/plugin/datasource.go
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ func (d *Datasource) QueryAnnotations(ctx context.Context, req *backend.QueryDat
return response, nil
}

// This function provides a way to proxy requests to the PI Web API. It is used to limit access fromt he frontend to the PI Web API.
// This function provides a way to proxy requests to the PI Web API. It is used to limit access from the frontend to the PI Web API.
// These endpoints are called by the front end while configuring the datasource, query, and annotations.
func (d *Datasource) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
// Create spans for this function.
Expand Down Expand Up @@ -275,7 +275,10 @@ func (d *Datasource) CallResource(ctx context.Context, req *backend.CallResource

r, err := apiGet(ctx, d, req.URL)
if err != nil {
return err
return sender.Send(&backend.CallResourceResponse{
Status: http.StatusNotFound,
Body: []byte(`{}`),
})
}
return sender.Send(&backend.CallResourceResponse{
Status: http.StatusOK,
Expand Down
16 changes: 13 additions & 3 deletions pkg/plugin/timeseries_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ func (d *Datasource) processQuery(query backend.DataQuery, datasourceUID string)
UID: datasourceUID,
IntervalNanoSeconds: PiQuery.Interval,
IsPIPoint: PiQuery.Pi.IsPiPoint,
HideError: PiQuery.Pi.HideError,
Streamable: PiQuery.isStreamable() && *d.dataSourceOptions.UseExperimental && *d.dataSourceOptions.UseStreaming,
FullTargetPath: fullTargetPath,
TargetPath: targetBasePath,
Expand Down Expand Up @@ -268,8 +269,10 @@ func (d *Datasource) processBatchtoFrames(processedQuery map[string][]PiProcesse
for _, q := range query {
// if there is an error in the query, we set the error in the subresponse and break out of the loop returning the error.
if q.Error != nil {
backend.Logger.Error("Error processing query", "RefID", RefID, "query", q)
subResponse.Error = q.Error
backend.Logger.Error("Error processing query", "RefID", RefID, "query", q, "hide", q.HideError)
if !q.HideError && strings.Contains(q.Error.Error(), "api error") {
subResponse.Error = q.Error
}
break
}

Expand Down Expand Up @@ -554,6 +557,13 @@ func (q *Query) getMaxDataPoints() int {
return q.MaxDataPoints
}

func (q *Query) getBoundaryType() string {
if q.Pi.RecordedValues.BoundaryType != nil {
return *q.Pi.RecordedValues.BoundaryType
}
return "Inside"
}

func (q Query) getQueryBaseURL() string {
var uri string
if q.Pi.isExpression() {
Expand Down Expand Up @@ -592,7 +602,7 @@ func (q Query) getQueryBaseURL() string {
} else if q.Pi.isInterpolated() {
uri += "/interpolated" + q.getTimeRangeURIComponent() + fmt.Sprintf("&interval=%s", q.getIntervalTime())
} else if q.Pi.isRecordedValues() {
uri += "/recorded" + q.getTimeRangeURIComponent() + fmt.Sprintf("&maxCount=%d", q.getMaxDataPoints()) + "&boundaryType=Interpolated"
uri += "/recorded" + q.getTimeRangeURIComponent() + fmt.Sprintf("&maxCount=%d", q.getMaxDataPoints()) + "&boundaryType=" + q.getBoundaryType()
} else {
uri += "/plot" + q.getTimeRangeURIComponent() + fmt.Sprintf("&intervals=%d", q.getMaxDataPoints())
}
Expand Down
7 changes: 5 additions & 2 deletions pkg/plugin/timeseries_query_models.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,12 @@ type PIWebAPIQuery struct {
Interval string `json:"interval"`
} `json:"interpolate"`
IsPiPoint bool `json:"isPiPoint"`
HideError bool `json:"hideError"`
MaxDataPoints *int `json:"maxDataPoints"`
RecordedValues *struct {
Enable *bool `json:"enable"`
MaxNumber *int `json:"maxNumber"`
Enable *bool `json:"enable"`
MaxNumber *int `json:"maxNumber"`
BoundaryType *string `json:"boundaryType"`
} `json:"recordedValues"`
RefID *string `json:"refId"`
Regex *Regex `json:"regex"`
Expand Down Expand Up @@ -186,6 +188,7 @@ type PiProcessedQuery struct {
UID string `json:"-"`
IntervalNanoSeconds int64 `json:"IntervalNanoSeconds"`
IsPIPoint bool `json:"IsPiPoint"`
HideError bool `json:"HideError"`
Streamable bool `json:"isStreamable"`
FullTargetPath string `json:"FullTargetPath"`
ResponseUnits string `json:"ResponseUnits"`
Expand Down
58 changes: 26 additions & 32 deletions src/datasource.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { filter, map } from 'lodash';

import { Observable, of, firstValueFrom } from 'rxjs';
import { Observable, of } from 'rxjs';

import {
DataSourceInstanceSettings,
Expand All @@ -12,9 +12,9 @@ import {
DataQueryRequest,
DataQueryResponse,
} from '@grafana/data';
import { BackendSrv, getBackendSrv, getTemplateSrv, TemplateSrv, DataSourceWithBackend } from '@grafana/runtime';
import { getTemplateSrv, TemplateSrv, DataSourceWithBackend } from '@grafana/runtime';

import { PIWebAPIQuery, PIWebAPIDataSourceJsonData, PiDataServer, PiwebapiInternalRsp, PiwebapiRsp } from './types';
import { PIWebAPIQuery, PIWebAPIDataSourceJsonData, PiDataServer, PiwebapiRsp } from './types';
import { metricQueryTransform } from 'helper';

import { PiWebAPIAnnotationsQueryEditor } from 'query/AnnotationsQueryEditor';
Expand All @@ -31,8 +31,7 @@ export class PiWebAPIDatasource extends DataSourceWithBackend<PIWebAPIQuery, PIW

constructor(
instanceSettings: DataSourceInstanceSettings<PIWebAPIDataSourceJsonData>,
readonly templateSrv: TemplateSrv = getTemplateSrv(),
private readonly backendSrv: BackendSrv = getBackendSrv()
readonly templateSrv: TemplateSrv = getTemplateSrv()
) {
super(instanceSettings);

Expand Down Expand Up @@ -225,13 +224,13 @@ export class PiWebAPIDatasource extends DataSourceWithBackend<PIWebAPIQuery, PIW
elementPath: this.templateSrv.replace(target.elementPath, options.scopedVars),
attributes: map(target.attributes, (att) => {
if (att.value) {
this.templateSrv.replace(att.value.value, options.scopedVars);
att.value.value = this.templateSrv.replace(att.value.value, options.scopedVars);
}
return att;
}),
segments: map(target.segments, (att) => {
if (att.value) {
this.templateSrv.replace(att.value.value, options.scopedVars);
att.value.value = this.templateSrv.replace(att.value.value, options.scopedVars);
}
return att;
}),
Expand All @@ -256,6 +255,7 @@ export class PiWebAPIDatasource extends DataSourceWithBackend<PIWebAPIQuery, PIW
startTime: options.range.from,
endTime: options.range.to,
isPiPoint: !!target.isPiPoint,
hideError: !!target.hideError,
scopedVars: options.scopedVars,
};

Expand Down Expand Up @@ -354,55 +354,49 @@ export class PiWebAPIDatasource extends DataSourceWithBackend<PIWebAPIQuery, PIW
*
* @memberOf PiWebApiDatasource
*/
private restGet(path: string): Promise<PiwebapiInternalRsp> {
const observable = this.backendSrv.fetch({
url: `/api/datasources/${this.id}/resources${path}`,
method: 'GET',
headers: { 'Content-Type': 'application/json' },
});

return firstValueFrom(observable).then((response: any) => {
return response as PiwebapiInternalRsp;
private restGet(path: string): Promise<PiwebapiRsp> {
return this.getResource(`${path}`).then((response: any) => {
return response as PiwebapiRsp;
});
}

// Get a list of all data (PI) servers
private getDataServers(): Promise<PiwebapiRsp[]> {
return this.restGet('/dataservers').then((response) => response.data.Items ?? []);
return this.restGet('/dataservers').then((response) => response.Items ?? []);
}
private getDataServer(name: string | undefined): Promise<PiwebapiRsp> {
if (!name) {
return Promise.resolve({});
}
return this.restGet('/dataservers?name=' + name).then((response) => response.data);
return this.restGet('/dataservers?name=' + name).then((response) => response);
}
// Get a list of all asset (AF) servers
private getAssetServers(): Promise<PiwebapiRsp[]> {
return this.restGet('/assetservers').then((response) => response.data.Items ?? []);
return this.restGet('/assetservers').then((response) => response.Items ?? []);
}
getAssetServer(name: string | undefined): Promise<PiwebapiRsp> {
if (!name) {
return Promise.resolve({});
}
return this.restGet('/assetservers?path=\\\\' + name).then((response) => response.data);
return this.restGet('/assetservers?path=\\\\' + name).then((response) => response);
}
getDatabase(path: string | undefined): Promise<PiwebapiRsp> {
if (!path) {
return Promise.resolve({});
}
return this.restGet('/assetdatabases?path=\\\\' + path).then((response) => response.data);
return this.restGet('/assetdatabases?path=\\\\' + path).then((response) => response);
}
getDatabases(serverId: string, options?: any): Promise<PiwebapiRsp[]> {
if (!serverId) {
return Promise.resolve([]);
}
return this.restGet('/assetservers/' + serverId + '/assetdatabases').then((response) => response.data.Items ?? []);
return this.restGet('/assetservers/' + serverId + '/assetdatabases').then((response) => response.Items ?? []);
}
getElement(path: string): Promise<PiwebapiRsp> {
if (!path) {
return Promise.resolve({});
}
return this.restGet('/elements?path=\\\\' + path).then((response) => response.data);
return this.restGet('/elements?path=\\\\' + path).then((response) => response);
}
getEventFrameTemplates(databaseId: string): Promise<PiwebapiRsp[]> {
if (!databaseId) {
Expand All @@ -411,7 +405,7 @@ export class PiWebAPIDatasource extends DataSourceWithBackend<PIWebAPIQuery, PIW
return this.restGet(
'/assetdatabases/' + databaseId + '/elementtemplates?selectedFields=Items.InstanceType%3BItems.Name%3BItems.WebId'
).then((response) => {
return filter(response.data.Items ?? [], (item) => item.InstanceType === 'EventFrame');
return filter(response.Items ?? [], (item) => item.InstanceType === 'EventFrame');
});
}
getElementTemplates(databaseId: string): Promise<PiwebapiRsp[]> {
Expand All @@ -421,7 +415,7 @@ export class PiWebAPIDatasource extends DataSourceWithBackend<PIWebAPIQuery, PIW
return this.restGet(
'/assetdatabases/' + databaseId + '/elementtemplates?selectedFields=Items.InstanceType%3BItems.Name%3BItems.WebId'
).then((response) => {
return filter(response.data.Items ?? [], (item) => item.InstanceType === 'Element');
return filter(response.Items ?? [], (item) => item.InstanceType === 'Element');
});
}

Expand Down Expand Up @@ -456,7 +450,7 @@ export class PiWebAPIDatasource extends DataSourceWithBackend<PIWebAPIQuery, PIW
}

return this.restGet('/elements/' + elementId + '/attributes' + querystring).then(
(response) => response.data.Items ?? []
(response) => response.Items ?? []
);
}

Expand Down Expand Up @@ -491,7 +485,7 @@ export class PiWebAPIDatasource extends DataSourceWithBackend<PIWebAPIQuery, PIW
}

return this.restGet('/assetdatabases/' + databaseId + '/elements' + querystring).then(
(response) => response.data.Items ?? []
(response) => response.Items ?? []
);
}

Expand Down Expand Up @@ -526,7 +520,7 @@ export class PiWebAPIDatasource extends DataSourceWithBackend<PIWebAPIQuery, PIW
}

return this.restGet('/elements/' + elementId + '/elements' + querystring).then(
(response) => response.data.Items ?? []
(response) => response.Items ?? []
);
}

Expand All @@ -541,7 +535,7 @@ export class PiWebAPIDatasource extends DataSourceWithBackend<PIWebAPIQuery, PIW
let filter2 = `${filter1}`;
let doFilter = false;
if (filter1 !== nameFilter) {
const regex = /\{(\w|,)+\}/gs;
const regex = /\{(\w|,)+\}/g;
let m;
while ((m = regex.exec(filter1)) !== null) {
// This is necessary to avoid infinite loops with zero-width matches
Expand All @@ -559,9 +553,9 @@ export class PiWebAPIDatasource extends DataSourceWithBackend<PIWebAPIQuery, PIW
});
}
}
return this.restGet('/dataservers/' + serverId + '/points?maxCount=50&nameFilter=' + filter2).then((results) => {
if (!!results && !!results.data?.Items) {
return doFilter ? results.data.Items.filter((item) => item.Name?.match(filter1)) : results.data.Items;
return this.restGet('/dataservers/' + serverId + '/points?maxCount=100&nameFilter=' + filter2).then((results) => {
if (!!results && !!results?.Items) {
return doFilter ? results.Items.filter((item) => item.Name?.match(filter1)) : results.Items;
}
return [];
});
Expand Down
Loading

0 comments on commit 9cf543f

Please sign in to comment.