Skip to content

Commit 30ad2f5

Browse files
Updated OSI and eDNA Grafana controller ancillary API operations
1 parent 5d46bfc commit 30ad2f5

File tree

5 files changed

+339
-150
lines changed

5 files changed

+339
-150
lines changed

Source/Libraries/Adapters/openHistorian.OSIPIGrafanaController/OSIPIGrafanaController.cs

Lines changed: 150 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,17 @@
2222
//******************************************************************************************************
2323

2424
using GrafanaAdapters;
25+
using GrafanaAdapters.DataSourceValueTypes;
2526
using GrafanaAdapters.DataSourceValueTypes.BuiltIn;
2627
using GrafanaAdapters.Functions;
2728
using GrafanaAdapters.Model.Annotations;
2829
using GrafanaAdapters.Model.Common;
30+
using GrafanaAdapters.Model.Functions;
31+
using GrafanaAdapters.Model.Metadata;
2932
using GSF;
3033
using GSF.Collections;
3134
using GSF.TimeSeries;
32-
using Newtonsoft.Json;
35+
using GSF.Web.Security;
3336
using OSIsoft.AF.Asset;
3437
using OSIsoft.AF.PI;
3538
using OSIsoft.AF.Time;
@@ -46,6 +49,9 @@
4649
using System.Threading;
4750
using System.Threading.Tasks;
4851
using System.Web.Http;
52+
using GrafanaAdapters.Model.Database;
53+
using AlarmState = GrafanaAdapters.Model.Database.AlarmState;
54+
using CancellationToken = System.Threading.CancellationToken;
4955

5056
namespace openHistorian.OSIPIGrafanaController
5157
{
@@ -57,7 +63,7 @@ namespace openHistorian.OSIPIGrafanaController
5763
/// <para>
5864
/// This adapter assumes that a PIOutputAdapter is being used to synchronize metadata and send data
5965
/// to PI, this way the adapter does not query the PI database for its metadata - which can be slow.
60-
/// Instead the adapter uses the locally accessible cached metadata, which is synchronized with PI,
66+
/// Instead, the adapter uses the locally accessible cached metadata, which is synchronized with PI,
6167
/// for Grafana queries. Because of this, the OSIPIGrafanaController is linked to its parent
6268
/// PIOutputAdapter instance by the "ServerName" connection string parameter, which becomes a query
6369
/// parameter of the OSIPIGrafanaController URL route template.
@@ -208,6 +214,8 @@ private MeasurementStateFlags ConvertStatusFlags(AFValueStatus status)
208214
/// <summary>
209215
/// Validates that openHistorian Grafana data source is responding as expected.
210216
/// </summary>
217+
/// <param name="instanceName">Historian instance name.</param>
218+
/// <param name="serverName">OSI-PI server name.</param>
211219
[HttpGet]
212220
public HttpResponseMessage Index(string instanceName, string serverName)
213221
{
@@ -235,63 +243,160 @@ public virtual Task<IEnumerable<TimeSeriesValues>> Query(string instanceName, st
235243
}
236244

237245
/// <summary>
238-
/// Queries OSI-PI as a Grafana Metadata source.
246+
/// Gets the data source value types, i.e., any type that has implemented <see cref="IDataSourceValueType"/>,
247+
/// that have been loaded into the application domain.
239248
/// </summary>
240249
/// <param name="instanceName">Historian instance name.</param>
241250
/// <param name="serverName">OSI-PI server name.</param>
242-
/// <param name="request">Query request.</param>
251+
[HttpPost]
252+
public virtual IEnumerable<DataSourceValueType> GetValueTypes(string instanceName, string serverName)
253+
{
254+
return DataSource(instanceName, serverName)?.GetValueTypes() ?? Enumerable.Empty<DataSourceValueType>();
255+
}
256+
257+
258+
/// <summary>
259+
/// Gets the table names that, at a minimum, contain all the fields that the value type has defined as
260+
/// required, see <see cref="IDataSourceValueType.RequiredMetadataFieldNames"/>.
261+
/// </summary>
262+
/// <param name="instanceName">Historian instance name.</param>
263+
/// <param name="serverName">OSI-PI server name.</param>
264+
/// <param name="request">Search request.</param>
265+
/// <param name="cancellationToken">Cancellation token.</param>
266+
[HttpPost]
267+
public virtual Task<IEnumerable<string>> GetValueTypeTables(string instanceName, string serverName, SearchRequest request, CancellationToken cancellationToken)
268+
{
269+
return DataSource(instanceName, serverName)?.GetValueTypeTables(request, cancellationToken) ?? Task.FromResult(Enumerable.Empty<string>());
270+
}
271+
272+
/// <summary>
273+
/// Gets the field names for a given table.
274+
/// </summary>
275+
/// <param name="instanceName">Historian instance name.</param>
276+
/// <param name="serverName">OSI-PI server name.</param>
277+
/// <param name="request">Search request.</param>
278+
/// <param name="cancellationToken">Cancellation token.</param>
279+
[HttpPost]
280+
public virtual Task<IEnumerable<FieldDescription>> GetValueTypeTableFields(string instanceName, string serverName, SearchRequest request, CancellationToken cancellationToken)
281+
{
282+
return DataSource(instanceName, serverName)?.GetValueTypeTableFields(request, cancellationToken) ?? Task.FromResult(Enumerable.Empty<FieldDescription>());
283+
}
284+
285+
/// <summary>
286+
/// Gets the functions that are available for a given data source value type.
287+
/// </summary>
288+
/// <param name="instanceName">Historian instance name.</param>
289+
/// <param name="serverName">OSI-PI server name.</param>
290+
/// <param name="request">Search request.</param>
291+
/// <param name="cancellationToken">Cancellation token.</param>
292+
/// <remarks>
293+
/// <see cref="SearchRequest.expression"/> is used to filter functions by group operation, specifically a
294+
/// value of "None", "Slice", or "Set" as defined in the <see cref="GroupOperations"/> enumeration. If all
295+
/// function descriptions are desired, regardless of group operation, an empty string can be provided.
296+
/// Combinations are also supported, e.g., "Slice,Set".
297+
/// </remarks>
298+
[HttpPost]
299+
public virtual Task<IEnumerable<FunctionDescription>> GetValueTypeFunctions(string instanceName, string serverName, SearchRequest request, CancellationToken cancellationToken)
300+
{
301+
return DataSource(instanceName, serverName)?.GetValueTypeFunctions(request, cancellationToken) ?? Task.FromResult(Enumerable.Empty<FunctionDescription>());
302+
}
303+
304+
/// <summary>
305+
/// Search openHistorian for a target.
306+
/// </summary>
307+
/// <param name="instanceName">Historian instance name.</param>
308+
/// <param name="serverName">OSI-PI server name.</param>
309+
/// <param name="request">Search target.</param>
243310
/// <param name="cancellationToken">Propagates notification from client that operations should be canceled.</param>
244311
[HttpPost]
245-
[SuppressMessage("Security", "SG0016", Justification = "Current operation dictated by Grafana. CSRF exposure limited to data access.")]
246-
public virtual Task<string> GetMetadata(string instanceName, string serverName, Target request, CancellationToken cancellationToken)
312+
public virtual Task<string[]> Search(string instanceName, string serverName, SearchRequest request, CancellationToken cancellationToken)
247313
{
248-
return Task.Factory.StartNew(() =>
249-
{
250-
if (string.IsNullOrWhiteSpace(request.target))
251-
return string.Empty;
314+
return DataSource(instanceName, serverName)?.Search(request, cancellationToken) ?? Task.FromResult(Array.Empty<string>());
315+
}
252316

253-
DataSet metadata = DataSource(instanceName, serverName)?.Metadata.GetAugmentedDataSet<MeasurementValue>();
254-
DataTable table = new();
255-
DataRow[] rows = metadata?.Tables["ActiveMeasurements"].Select($"PointTag IN ({request.target})") ?? Array.Empty<DataRow>();
317+
/// <summary>
318+
/// Reloads data source value types cache.
319+
/// </summary>
320+
/// <param name="instanceName">Historian instance name.</param>
321+
/// <param name="serverName">OSI-PI server name.</param>
322+
/// <remarks>
323+
/// This function is used to support dynamic data source value type loading. Function only needs to be called
324+
/// when a new data source value is added to Grafana at run-time and end-user wants to use newly installed
325+
/// data source value type without restarting host.
326+
/// </remarks>
327+
[HttpGet]
328+
[AuthorizeControllerRole("Administrator")]
329+
public virtual void ReloadValueTypes(string instanceName, string serverName)
330+
{
331+
DataSource(instanceName, serverName)?.ReloadDataSourceValueTypes();
332+
}
333+
334+
/// <summary>
335+
/// Reloads Grafana functions cache.
336+
/// </summary>
337+
/// <param name="instanceName">Historian instance name.</param>
338+
/// <param name="serverName">OSI-PI server name.</param>
339+
/// <remarks>
340+
/// This function is used to support dynamic loading for Grafana functions. Function only needs to be called
341+
/// when a new function is added to Grafana at run-time and end-user wants to use newly installed function
342+
/// without restarting host.
343+
/// </remarks>
344+
[HttpGet]
345+
[AuthorizeControllerRole("Administrator")]
346+
public virtual void ReloadGrafanaFunctions(string instanceName, string serverName)
347+
{
348+
DataSource(instanceName, serverName)?.ReloadGrafanaFunctions();
349+
}
350+
351+
/// <summary>
352+
/// Queries openHistorian for alarm state.
353+
/// </summary>
354+
/// <param name="instanceName">Historian instance name.</param>
355+
/// <param name="serverName">OSI-PI server name.</param>
356+
/// <param name="cancellationToken">Propagates notification from client that operations should be canceled.</param>
357+
[HttpPost]
358+
public virtual Task<IEnumerable<AlarmDeviceStateView>> GetAlarmState(string instanceName, string serverName, CancellationToken cancellationToken)
359+
{
360+
return DataSource(instanceName, serverName)?.GetAlarmState(cancellationToken) ?? Task.FromResult(Enumerable.Empty<AlarmDeviceStateView>());
361+
}
256362

257-
if (rows.Length > 0)
258-
table = rows.CopyToDataTable();
363+
/// <summary>
364+
/// Queries openHistorian for device alarms.
365+
/// </summary>
366+
/// <param name="instanceName">Historian instance name.</param>
367+
/// <param name="serverName">OSI-PI server name.</param>
368+
/// <param name="cancellationToken">Propagates notification from client that operations should be canceled.</param>
369+
[HttpPost]
370+
public virtual Task<IEnumerable<AlarmState>> GetDeviceAlarms(string instanceName, string serverName, CancellationToken cancellationToken)
371+
{
372+
return DataSource(instanceName, serverName)?.GetDeviceAlarms(cancellationToken) ?? Task.FromResult(Enumerable.Empty<AlarmState>());
373+
}
259374

260-
return JsonConvert.SerializeObject(table);
261-
},
262-
cancellationToken);
375+
/// <summary>
376+
/// Queries openHistorian for device groups.
377+
/// </summary>
378+
/// <param name="instanceName">Historian instance name.</param>
379+
/// <param name="serverName">OSI-PI server name.</param>
380+
/// <param name="cancellationToken">Propagates notification from client that operations should be canceled.</param>
381+
[HttpPost]
382+
public virtual Task<IEnumerable<DeviceGroup>> GetDeviceGroups(string instanceName, string serverName, CancellationToken cancellationToken)
383+
{
384+
return DataSource(instanceName, serverName)?.GetDeviceGroups(cancellationToken) ?? Task.FromResult(Enumerable.Empty<DeviceGroup>());
263385
}
264386

265-
///// <summary>
266-
///// Search OSI-PI for a target.
267-
///// </summary>
268-
///// <param name="instanceName">Historian instance name.</param>
269-
///// <param name="serverName">OSI-PI server name.</param>
270-
///// <param name="request">Search target.</param>
271-
///// <param name="cancellationToken">Propagates notification from client that operations should be canceled.</param>
272-
//[HttpPost]
273-
//[SuppressMessage("Security", "SG0016", Justification = "Current operation dictated by Grafana. CSRF exposure limited to data access.")]
274-
//public Task<string[]> Search(string instanceName, string serverName, Target request, CancellationToken cancellationToken)
275-
//{
276-
// return DataSource(instanceName, serverName)?.Search(request, cancellationToken) ?? Task.FromResult(new string[0]);
277-
//}
278-
279-
280387
/// <summary>
281-
/// Queries OSI-PI for annotations in a time-range (e.g., Alarms).
388+
/// Queries openHistorian for annotations in a time-range (e.g., Alarms).
282389
/// </summary>
283390
/// <param name="instanceName">Historian instance name.</param>
284391
/// <param name="serverName">OSI-PI server name.</param>
285392
/// <param name="request">Annotation request.</param>
286393
/// <param name="cancellationToken">Propagates notification from client that operations should be canceled.</param>
287394
[HttpPost]
288-
[SuppressMessage("Security", "SG0016", Justification = "Current operation dictated by Grafana. CSRF exposure limited to data access.")]
289-
public Task<List<AnnotationResponse>> Annotations(string instanceName, string serverName, AnnotationRequest request, CancellationToken cancellationToken)
395+
public virtual Task<List<AnnotationResponse>> Annotations(string instanceName, string serverName, AnnotationRequest request, CancellationToken cancellationToken)
290396
{
291397
return DataSource(instanceName, serverName)?.Annotations(request, cancellationToken) ?? Task.FromResult(new List<AnnotationResponse>());
292398
}
293399

294-
295400
/// <summary>
296401
/// Gets OSI-PI data source for this Grafana adapter.
297402
/// </summary>
@@ -314,7 +419,7 @@ private OSIPIDataSource DataSource(string instanceName, string serverName)
314419
return dataSource;
315420
}
316421

317-
private OSIPIDataSource CreateNewDataSource(string keyName)
422+
private static OSIPIDataSource CreateNewDataSource(string keyName)
318423
{
319424
string[] parts = keyName.Split('.');
320425
string instanceName = parts[0];
@@ -331,13 +436,15 @@ private OSIPIDataSource CreateNewDataSource(string keyName)
331436
Metadata = metadata,
332437
KeyName = keyName,
333438
PrefixRemoveCount = adapterInstance.TagNamePrefixRemoveCount,
334-
Connection = new PIConnection()
439+
Connection = new PIConnection
440+
{
441+
ServerName = serverName,
442+
UserName = adapterInstance.UserName,
443+
Password = adapterInstance.Password,
444+
ConnectTimeout = adapterInstance.ConnectTimeout
445+
}
335446
};
336447

337-
dataSource.Connection.ServerName = serverName;
338-
dataSource.Connection.UserName = adapterInstance.UserName;
339-
dataSource.Connection.Password = adapterInstance.Password;
340-
dataSource.Connection.ConnectTimeout = adapterInstance.ConnectTimeout;
341448
dataSource.Connection.Open();
342449

343450
// On successful connection, kick off a thread to start meta-data ID to OSI-PI point ID mapping

Source/Libraries/Adapters/openHistorian.OSIPIGrafanaController/openHistorian.OSIPIGrafanaController.csproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@
4444
<Reference Include="GSF.TimeSeries">
4545
<HintPath>..\..\..\Dependencies\GSF\GSF.TimeSeries.dll</HintPath>
4646
</Reference>
47+
<Reference Include="GSF.Web, Version=2.4.208.0, Culture=neutral, processorArchitecture=MSIL">
48+
<SpecificVersion>False</SpecificVersion>
49+
<HintPath>..\..\..\Dependencies\GSF\GSF.Web.dll</HintPath>
50+
</Reference>
4751
<Reference Include="Microsoft.Bcl.AsyncInterfaces, Version=7.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
4852
<SpecificVersion>False</SpecificVersion>
4953
<HintPath>..\..\..\Dependencies\GSF\Microsoft.Bcl.AsyncInterfaces.dll</HintPath>
@@ -75,6 +79,10 @@
7579
<Reference Include="Microsoft.CSharp" />
7680
<Reference Include="System.Data" />
7781
<Reference Include="System.Net.Http" />
82+
<Reference Include="System.Web.Mvc, Version=5.2.4.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
83+
<SpecificVersion>False</SpecificVersion>
84+
<HintPath>..\..\..\Dependencies\GSF\System.Web.Mvc.dll</HintPath>
85+
</Reference>
7886
<Reference Include="System.Xml" />
7987
</ItemGroup>
8088
<ItemGroup>

0 commit comments

Comments
 (0)