2222//******************************************************************************************************
2323
2424using GrafanaAdapters ;
25+ using GrafanaAdapters . DataSourceValueTypes ;
2526using GrafanaAdapters . DataSourceValueTypes . BuiltIn ;
2627using GrafanaAdapters . Functions ;
2728using GrafanaAdapters . Model . Annotations ;
2829using GrafanaAdapters . Model . Common ;
30+ using GrafanaAdapters . Model . Functions ;
31+ using GrafanaAdapters . Model . Metadata ;
2932using GSF ;
3033using GSF . Collections ;
3134using GSF . TimeSeries ;
32- using Newtonsoft . Json ;
35+ using GSF . Web . Security ;
3336using OSIsoft . AF . Asset ;
3437using OSIsoft . AF . PI ;
3538using OSIsoft . AF . Time ;
4649using System . Threading ;
4750using System . Threading . Tasks ;
4851using System . Web . Http ;
52+ using GrafanaAdapters . Model . Database ;
53+ using AlarmState = GrafanaAdapters . Model . Database . AlarmState ;
54+ using CancellationToken = System . Threading . CancellationToken ;
4955
5056namespace 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
0 commit comments