Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions grafana/rmf-app/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Change Log

## 2.0.0.1 (2026-06-26)
- Add Grafana v13 support.

## 2.0.0 (2026-03-06)
- You can now import RMF Performance Monitoring dashboards and their associated data sources directly into Grafana for visualization.
- The custom RMF full report panel (ibm-rmf-panel) has been deprecated and removed. Full RMF reports now rely on standard Grafana visualizations.
Expand Down
106 changes: 39 additions & 67 deletions grafana/rmf-app/src/datasource/datasource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
* limitations under the License.
*/
import { DataSourceInstanceSettings, MetricFindValue, ScopedVars } from '@grafana/data';
import { DataSourceWithBackend, getBackendSrv, getTemplateSrv } from '@grafana/runtime';
import { DataSourceWithBackend, getTemplateSrv } from '@grafana/runtime';
import { getResourceName, queryValidation } from './common/common.helper';
import { RMFDataSourceJsonData, RMFQuery, resourceBaseData } from './common/types';

Expand Down Expand Up @@ -71,74 +71,46 @@ export class DataSource extends DataSourceWithBackend<RMFQuery, RMFDataSourceJso

// FIXME: options.scopedVars may contain Grafana datasource id which doesn't make sense at all!
let resourceString = getTemplateSrv().replace(queryResult.resourceCommand, options.scopedVars);
let id = this.id;

return new Promise((resolve) => {
const metricFindValue = this.loadDataFromService(resourceString, id, options)
.then((resp: any) => {
const result = JSON.parse(resp.data);
let resNames = getResourceName(result);
const retResult: MetricFindValue[] = [];
resNames.map((row: any, index: number) => {
let itemName = '';
try {
// itemName = row[queryResult.columnName.toLowerCase()][0].trim();
itemName = row[queryResult.columnName.toLowerCase()].trim();
let itemNameParts = itemName.split(',');
if (itemNameParts[1].trim() === '*') {
itemName = itemNameParts[2].trim(); // Ex: ULQ01,*,ALL_WLM_RESOURCE_GROUPS
} else {
itemName = itemNameParts[1].trim(); // Ex: ULQ01,BATCHLOW,WLM_RESOURCE_GROUP
}
if (
queryResult.filterTypes.trim() !== '' &&
queryResult.filterTypes.indexOf(row.restype.toUpperCase().trim()) !== -1
) {
retResult.push({ text: itemName });
} else if (queryResult.filterTypes.trim() === '') {
retResult.push({ text: itemName });
}
} catch (errorObj) {}
});
return retResult;
})
.catch((err) => {
throw new Error(err.message + ', [resource=' + queryResult.resourceCommand + ']');
});
return resolve(metricFindValue);
});
}
try {
const data = await this.postResource<string>('variablequery', JSON.stringify({ query: resourceString }), {
headers: {
Accept: 'application/text',
'Content-Type': 'application/text',
},
responseType: 'text',
});

if (!data) {
throw new Error('Test connection failed.');
}

async loadDataFromService(urlPath: string, id?: number, options?: any) {
return new Promise((resolve, reject) =>
getBackendSrv()
.fetch({
method: 'post',
headers: {
Accept: 'application/text',
'Content-Type': 'application/text',
},
url: `/api/datasources/${id}/resources/variablequery`,
responseType: 'text',
data: JSON.stringify({ query: urlPath }),
})
.subscribe(
(resp) => {
if (resp.data) {
if (options !== undefined && options === 'headres') {
} else {
resolve({
data: resp.data,
});
}
} else {
reject({ status: 'failure', message: 'Test connection failed.' });
}
},
(err) => {
reject({ status: 'failure', message: err.data.message });
const result = JSON.parse(data as string);
let resNames = getResourceName(result);
const retResult: MetricFindValue[] = [];
resNames.map((row: any, index: number) => {
let itemName = '';
try {
itemName = row[queryResult.columnName.toLowerCase()].trim();
let itemNameParts = itemName.split(',');
if (itemNameParts[1].trim() === '*') {
itemName = itemNameParts[2].trim(); // Ex: ULQ01,*,ALL_WLM_RESOURCE_GROUPS
} else {
itemName = itemNameParts[1].trim(); // Ex: ULQ01,BATCHLOW,WLM_RESOURCE_GROUP
}
)
);
if (
queryResult.filterTypes.trim() !== '' &&
queryResult.filterTypes.indexOf(row.restype.toUpperCase().trim()) !== -1
) {
retResult.push({ text: itemName });
} else if (queryResult.filterTypes.trim() === '') {
retResult.push({ text: itemName });
}
} catch (errorObj) {}
});
return retResult;
} catch (err: any) {
throw new Error((err.message || err.data?.message) + ', [resource=' + queryResult.resourceCommand + ']');
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
// import defaults from 'lodash/defaults';

import { QueryEditorProps } from '@grafana/data';
import { getBackendSrv, getTemplateSrv } from '@grafana/runtime';
import { getTemplateSrv } from '@grafana/runtime';
import { RadioButtonGroup, Spinner, Switch } from '@grafana/ui';
import React, { PureComponent } from 'react';
import { AutocompleteTextField } from '../autocomplete-text/autocomplete-textfield';
Expand Down Expand Up @@ -80,13 +80,6 @@ export class QueryEditorAutoCompleteComponent extends PureComponent<Props, state
this.autoComplDefProps = { ...autoCompleteDefaultProps };
this.autoComplDefProps.value = props.query?.selectedQuery ? props.query.selectedQuery : '';
this.refInput = React.createRef();
if (Object.keys(metricDict).length === 0) {
this.loadDataFromService(props.datasource.id)
.then((resp1: any) => {})
.catch((err: any) => {
this.setServiceCallInprogresState(false);
});
}
}

componentDidMount() {
Expand All @@ -95,37 +88,37 @@ export class QueryEditorAutoCompleteComponent extends PureComponent<Props, state
this.setState({ queryText: '' });
this.setState({ editorSelectedResource: this.editorSelectedResource });
this.setState({ enableTimeSeries: this.enableTimeSeries });
if (Object.keys(metricDict).length === 0) {
this.loadMetricsIndex()
.then((resp1: any) => {})
.catch((err: any) => {
this.setServiceCallInprogresState(false);
});
}
}

async loadDataFromService(id: number) {
return await new Promise((resolve, reject) =>
getBackendSrv()
.fetch({
method: 'post',
headers: {
Accept: 'application/text',
'Content-Type': 'application/text',
},
url: `/api/datasources/${id}/resources/autopopulate`,
responseType: 'text',
})
.subscribe(
(resp) => {
if (resp.data) {
let result = JSON.parse(resp.data as string);
let resourceList: any[] = getResource(result);
metricDict = loadDataToDictionary(resourceList);
resolve(true);
} else {
reject(false);
}
},
(err) => {
this.setServiceCallInprogresState(false);
reject(false);
}
)
);
async loadMetricsIndex() {
try {
const data = await this.props.datasource.postResource<string>('autopopulate', undefined, {
headers: {
Accept: 'application/text',
'Content-Type': 'application/text',
},
responseType: 'text',
});

if (data) {
let result = JSON.parse(data as string);
let resourceList: any[] = getResource(result);
metricDict = loadDataToDictionary(resourceList);
return true;
} else {
return Promise.reject(false);
}
} catch (err) {
this.setServiceCallInprogresState(false);
return Promise.reject(false);
}
}

onTextChange = (val: string, e: any) => {
Expand Down
Loading