Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
158e838
add filewatcher tab with base functionality
nbeatty-gpa Apr 13, 2026
0d636c7
improve styling and layout
nbeatty-gpa Apr 14, 2026
86419dd
add Y Axis label
nbeatty-gpa Apr 14, 2026
c40b4bc
change file watcher to files processed, with changes to data
nbeatty-gpa Apr 27, 2026
9f77bb0
add file name to system center data file
nbeatty-gpa Apr 27, 2026
32c4f99
move Data operation failures away from using StatusGroup
nbeatty-gpa Apr 28, 2026
76f402c
add stack trace and log to data operation failures
nbeatty-gpa Apr 30, 2026
4e187a0
add plot selection to filter processed files table and data operations
nbeatty-gpa Apr 30, 2026
1bc1f34
add selection for data operation failures and files
nbeatty-gpa Apr 30, 2026
5a201c7
fix plot bugs and include processing state in table
nbeatty-gpa May 1, 2026
64ee694
make data operation failure separate component
nbeatty-gpa May 4, 2026
5ebad32
page and scroll data operation failures
nbeatty-gpa May 4, 2026
360f455
refactor selection-based searches
nbeatty-gpa May 4, 2026
29cde9b
add files processed tab
nbeatty-gpa May 6, 2026
e5fe2c8
move processing status to common component
nbeatty-gpa May 8, 2026
08d7298
split into separate components
nbeatty-gpa May 8, 2026
34ec51c
make Y-axis label more accurate
nbeatty-gpa May 11, 2026
259b536
fix processing state column label
nbeatty-gpa May 11, 2026
ee8050b
revert unintentional default url change
nbeatty-gpa May 13, 2026
499d1bf
use custom sql query in RecentFailures
nbeatty-gpa May 13, 2026
c3683bc
convert Processing Status to use Enum explicitly
nbeatty-gpa May 13, 2026
1207efc
remove hardcoded database names
nbeatty-gpa May 13, 2026
f173a88
remove unneeded length check
nbeatty-gpa May 13, 2026
4713f69
move network request to helper function to allow for proper effect de…
nbeatty-gpa May 13, 2026
4e3acf7
include status setters
nbeatty-gpa May 13, 2026
6a95a67
move network request to pure helper function
nbeatty-gpa May 13, 2026
fab5590
move network request to pure helper function
nbeatty-gpa May 13, 2026
ef6d947
use loading screens and error boundaries in file watcher sub components
nbeatty-gpa May 13, 2026
ff104da
add auth check to aggregate recently processed files endpoint
nbeatty-gpa May 14, 2026
c14a0ec
add auth check to recent failures endpoint
nbeatty-gpa May 14, 2026
e8d2fef
use loading icon instead of loading screen
nbeatty-gpa May 18, 2026
1015a6d
remove LoadingScreen
nbeatty-gpa May 18, 2026
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
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
using openXDA.Model.SystemCenter;
using PQView.Model;
using System;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
Expand Down Expand Up @@ -103,7 +104,54 @@ public class EventTypeAssetTypeController : ModelController<EventTypeAssetType>
[RoutePrefix("api/OpenXDA/DataOperation")]
public class DataOperationController : ModelController<DataOperation> { }
[RoutePrefix("api/OpenXDA/DataOperationFailure")]
public class DataOperationFailureController : ModelController<DataOperationFailureDetails> { }
public class DataOperationFailureController : ModelController<DataOperationFailureDetails> {

[Route("RecentFailures/{page}"), HttpPost]
public IHttpActionResult RecentFailures([FromBody] PostData postData, [FromUri] int page)
Comment thread
nbeatty-gpa marked this conversation as resolved.
{
if (!GetAuthCheck())
return Unauthorized();

int recordsPerPage = Take ?? 50;

List<object> param = new();

string conditions = BuildWhereClause(postData.Searches, param);

//using DataTable value = GetSearchResults(postData, page);
string sql = $@"
SELECT * ,
(SELECT dataFile.FilePath FROM DataFile dataFile WHERE dataFile.FileGroupID = DetailedDataOperationFailure.FileGroupID) as FilePath
FROM({CustomView}) DetailedDataOperationFailure
WHERE {conditions}
ORDER BY {postData.OrderBy} {(postData.Ascending ? "ASC" : "DESC")}
OFFSET {page * recordsPerPage} ROWS FETCH NEXT {recordsPerPage} ROWS ONLY";

DataTable table;

using (AdoDataConnection connection = ConnectionFactory())
{
object[] paramArray = param.ToArray();
table = connection.RetrieveData(sql, paramArray);
table.Columns.Add("DataFileName", typeof(string));
foreach (DataRow row in table.Rows)
{
row["DataFileName"] = Path.GetFileName(row.Field<string>("FilePath"));
}
}

int searchResultsCount = CountSearchResults(postData);

return Ok(new PagedResults
{
Data = JsonConvert.SerializeObject(table),
RecordsPerPage = recordsPerPage,
TotalRecords = searchResultsCount,
NumberOfPages = (searchResultsCount + recordsPerPage - 1) / recordsPerPage
});
}

}
[RoutePrefix("api/OpenXDA/DataReader")]
public class DataReaderController : ModelController<DataReader> { }

Expand Down
66 changes: 65 additions & 1 deletion Source/Applications/SystemCenter/Model/DataFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@

using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
Expand All @@ -47,6 +49,7 @@ namespace SystemCenter.Model
SELECT
DataFile.*,
FileGroup.DataStartTime,
FileGroup.ProcessingStartTime,
FileGroup.ProcessingEndTime,
FileGroup.MeterID,
FileGroup.ProcessingStatus AS ProcessingState
Expand All @@ -59,10 +62,13 @@ public class DataFile : openXDA.Model.DataFile
{
[ParentKey(typeof(Meter))]
public int MeterID { get; set; }
public DateTime ProcessingStartTime { get; set; }
[DefaultSortOrder(false)]
public DateTime ProcessingEndTime { get; set; }
public DateTime DataStartTime { get; set; }
public int ProcessingState { get; set; }
[NonRecordField]
public string FileName => Path.GetFileName(FilePath);
}

[RoutePrefix("api/OpenXDA/DataFile")]
Expand Down Expand Up @@ -206,7 +212,65 @@ public IHttpActionResult Download(int id)
}
}


[Route("AggregateRecentlyProcessedFiles"), HttpGet]
public IHttpActionResult AggregateRecentlyProcessedFiles()
Comment thread
nbeatty-gpa marked this conversation as resolved.
{
if (!GetAuthCheck())
return Unauthorized();

String sqlQuery = @"
SELECT
FORMAT (FileGroup.ProcessingStartTime, 'yyyy-MM-dd HH') AS Hour,
COUNT(DataFile.ID) as Count
FROM
DataFile JOIN FileGroup ON DataFile.FileGroupID = FileGroup.ID
WHERE
FileGroup.ProcessingStartTime > DATEADD(HOUR, DATEDIFF(HOUR, 0, GETDATE()) - 48, 0)
GROUP BY FORMAT (FileGroup.ProcessingStartTime, 'yyyy-MM-dd HH');";
DataTable result;
using (AdoDataConnection connection = new AdoDataConnection("systemSettings"))
{
result = connection.RetrieveData(sqlQuery);
}
return Ok(result);
}

[Route("PagedResults"), HttpPost]
public override IHttpActionResult GetPagedList([FromBody] PostData postData, int page)
{
if (!GetAuthCheck())
return Unauthorized();

using DataTable table = GetSearchResults(postData, page);
DataFile[] results = table
.AsEnumerable()
.Select(row => new DataFile()
{
CreationTime = row.Field<DateTime>("CreationTime"),
ID = row.Field<int>("ID"),
FileGroupID = row.Field<int>("FileGroupID"),
FilePath = row.Field<string>("FilePath"),
FilePathHash = row.Field<int>("FilePathHash"),
FileSize = row.Field<long>("FileSize"),
LastWriteTime = row.Field<DateTime>("LastWriteTime"),
LastAccessTime = row.Field<DateTime>("LastAccessTime"),
MeterID = row.Field<int>("MeterID"),
DataStartTime = row.Field<DateTime>("DataStartTime"),
ProcessingEndTime = row.Field<DateTime>("ProcessingEndTime"),
ProcessingState = row.Field<int>("ProcessingState"),
ProcessingStartTime = row.Field<DateTime>("ProcessingStartTime")
}).ToArray();
int recordCount = CountSearchResults(postData);
int recordPerPage = Take ?? 50;
return Ok(new PagedResults()
{
Data = JsonConvert.SerializeObject(results),
RecordsPerPage = recordPerPage,
TotalRecords = recordCount,
NumberOfPages = (recordCount + recordPerPage - 1) / recordPerPage
});
}

}

}
36 changes: 19 additions & 17 deletions Source/Applications/SystemCenter/SystemCenter.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,8 @@
<Compile Include="WebClients\HIDSClient.cs" />
<Compile Include="WebClients\HttpClient.cs" />
<Compile Include="WebClients\XDANodeClient.cs" />
<TypeScriptCompile Include="wwwroot\Scripts\TSX\SystemCenter\AppHost\DataOperationFailure.tsx" />
<TypeScriptCompile Include="wwwroot\Scripts\TSX\SystemCenter\AppHost\FilesProcessed.tsx" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="DebugHost.resx">
Expand Down Expand Up @@ -493,30 +495,30 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<Content Include="wwwroot\Images\GiantLogo.png" />
<Content Include="wwwroot\Images\NodeTiles\openMIC.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="wwwroot\Images\NodeTiles\XDA.png">
<Content Include="wwwroot\Images\NodeTiles\openMIC.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="wwwroot\Images\NodeTiles\XDA.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="wwwroot\Images\NodeTiles\SystemCenter.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="wwwroot\Images\NodeTiles\miMD.png">
<Content Include="wwwroot\Images\NodeTiles\miMD.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="wwwroot\Images\NodeTiles\XDAIcon.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="wwwroot\Images\NodeTiles\openMICIcon.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="wwwroot\Images\NodeTiles\SystemCenterIcon.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="wwwroot\Images\NodeTiles\miMDIcon.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="wwwroot\Images\NodeTiles\XDAIcon.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="wwwroot\Images\NodeTiles\openMICIcon.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="wwwroot\Images\NodeTiles\SystemCenterIcon.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="wwwroot\Images\NodeTiles\miMDIcon.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="wwwroot\index.cshtml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
//******************************************************************************************************
// DataOperationFailure.tsx - Gbtc
//
// Copyright © 2026, Grid Protection Alliance. All Rights Reserved.
//
// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
// the NOTICE file distributed with this work for additional information regarding copyright ownership.
// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this
// file except in compliance with the License. You may obtain a copy of the License at:
//
// http://opensource.org/licenses/MIT
//
// Unless agreed to in writing, the subject software distributed under the License is distributed on an
// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
// License for the specific language governing permissions and limitations.
//
// Code Modification History:
// ----------------------------------------------------------------------------------------------------
// 05/04/2026 - Natalie Beatty
// Generated original version of source code.
//
//******************************************************************************************************
import * as React from 'react'
import moment from 'moment'
import { ToolTip } from '@gpa-gemstone/react-forms'
import { OpenXDA } from '@gpa-gemstone/application-typings';

export interface INamedDataOperationFailure extends OpenXDA.Types.DataOperationFailure {
DataFileName: string
}
interface IProps {
NamedDataOperationFailure: INamedDataOperationFailure
SelectedFile: number
Hovered: string
HandleViewMoreClick: (info: string, evt: React.MouseEvent) => void
SetHovered: React.Dispatch<React.SetStateAction<string>>
}

const DataOperationFailure = (props: IProps) => {
return <div className={`row alert-${props.NamedDataOperationFailure.FileGroupID === props.SelectedFile ? 'warning' : 'danger'} m-2`}
key={props.NamedDataOperationFailure.ID}
>
<div className={'col-2 d-flex justify-content-center align-items-center'}>
<span className={`badge badge-pill badge-secondary`}>{moment(props.NamedDataOperationFailure.TimeOfFailure).format('MM/DD/YYYY hh:mm')}</span>
</div>
<div className={'col-3 d-flex justify-content-center align-items-center'}>
<h6>{props.NamedDataOperationFailure.DataOperationTypeName.split('.')[props.NamedDataOperationFailure.DataOperationTypeName.split('.').length - 1]}</h6>
</div>
<div className={'col-3 d-flex justify-content-center align-items-center'}>{props.NamedDataOperationFailure.DataFileName}</div>
<div className={'col-2 d-flex justify-content-around align-items-center'}>
<div className={'btn btn-primary'}
onMouseEnter={() => props.SetHovered(`failurelog${props.NamedDataOperationFailure.ID.toString()}`)}
onMouseLeave={() => props.SetHovered('')}
data-tooltip={`failurelog${props.NamedDataOperationFailure.ID.toString()}`}
>
View Log
</div>
<ToolTip
Show={props.Hovered === `failurelog${props.NamedDataOperationFailure.ID.toString()}`}
Target={`failurelog${props.NamedDataOperationFailure.ID.toString()}`}
>
{props.NamedDataOperationFailure.Log.length > 100
? <>
<p>{`${props.NamedDataOperationFailure.Log.slice(0, 100)}...`}</p>
<a href="#" onClick={(evt) => { props.HandleViewMoreClick(props.NamedDataOperationFailure.Log, evt) }}>View more</a>
</>
: <p>{props.NamedDataOperationFailure.Log}</p>}
</ToolTip>
</div>
<div className={'col-2 d-flex justify-content-around align-items-center'}>
<div className={'btn btn-primary'}
onMouseEnter={() => props.SetHovered(`failurestacktrace${props.NamedDataOperationFailure.ID.toString()}`)}
onMouseLeave={() => props.SetHovered('')}
data-tooltip={`failurestacktrace${props.NamedDataOperationFailure.ID.toString()}`}
>
View Stack Trace
</div>
<ToolTip
Show={props.Hovered === `failurestacktrace${props.NamedDataOperationFailure.ID.toString()}`}
Target={`failurestacktrace${props.NamedDataOperationFailure.ID.toString()}`}
>
{props.NamedDataOperationFailure.StackTrace.length > 100
? <>
<p>{`${props.NamedDataOperationFailure.StackTrace.slice(0, 100)}...`}</p>
<a href="#" onClick={(evt) => { props.HandleViewMoreClick(props.NamedDataOperationFailure.StackTrace, evt) }}>View more</a>
</>
: <p>{props.NamedDataOperationFailure.StackTrace}</p>}
</ToolTip>
</div>
</div>
}

export default DataOperationFailure
Loading