From 786a49d5b66ad1acece8f24e22f4e4e047687fac Mon Sep 17 00:00:00 2001 From: prestoncraw Date: Wed, 19 Aug 2026 13:25:25 -0400 Subject: [PATCH 01/14] cleanup --- Jenkinsfile | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 8ed10f30..d1b7f3f3 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -180,10 +180,10 @@ pipeline { } } - stage('Install UI Dependencies') { + stage('Build Production UI') { steps { dir('PQBrowser') { - bat(script: 'npm ci') + bat(script: 'npm run build') } } } @@ -206,14 +206,6 @@ pipeline { } } - stage('Build Production UI') { - steps { - dir('PQBrowser') { - bat(script: 'npm run build') - } - } - } - stage('Build Docker Images') { when { anyOf { From 290538ef1dffc163d98095ea1bf5d188ff5ba056 Mon Sep 17 00:00:00 2001 From: prestoncraw Date: Fri, 21 Aug 2026 10:56:47 -0400 Subject: [PATCH 02/14] add UI versioning --- Jenkinsfile | 2 + Scripts/PackageVersioning.ps1 | 86 +++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 Scripts/PackageVersioning.ps1 diff --git a/Jenkinsfile b/Jenkinsfile index d1b7f3f3..7930cd77 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -122,7 +122,9 @@ pipeline { env.GIT_COMMIT = bat(script: '@git rev-parse HEAD', returnStdout: true).trim() } powershell "powershell.exe -File .\\Scripts\\Versioning.ps1 -VersionFile './Scripts/PQBrowser.version' -Commit false" + powershell "powershell.exe -File .\\Scripts\\PackageVersioning.ps1 -VersionFile './PQBrowser/package.json'" bat(script: "@git add Scripts/PQBrowser.version") + bat(script: "@git add PQBrowser/package.json") bat(script: "git diff --cached --quiet || git commit -m \"Updated Version Number\"") } } diff --git a/Scripts/PackageVersioning.ps1 b/Scripts/PackageVersioning.ps1 new file mode 100644 index 00000000..c0c20631 --- /dev/null +++ b/Scripts/PackageVersioning.ps1 @@ -0,0 +1,86 @@ +param( + [string]$VersionFile +) + +#Compare Versions +function CompareVersions { + param( + [string]$Version1, + [string]$Version2 + ) + + $array1 = $Version1.Split(".") + $array2 = $Version2.Split(".") + + $i = 0 + while ($i -lt [Math]::Max($array1.Count, $array2.Count)) { + if ($i -ge $array1.Count) { + $v1 = 0 + } else { + $v1 = [int]$array1[$i] + } + if ($i -ge $array2.Count) { + $v2 = 0 + } else { + $v2 = [int]$array2[$i] + } + if ($v1 -gt $v2) { + return 1 + } + if ($v2 -gt $v1) { + return -1 + } + $i++ + } + return 0 +} + +#Increment Version +function IncrementVersion { + param( + [string]$prevVersion + ) + $array = $prevVersion.Split(".") + $array[$array.Count - 1] = [int]$array[$array.Count - 1] + 1 + return $array -join '.' +} + +#Get Latest Version on Github +git fetch origin master:refs/remotes/origin/master +$commit = git rev-parse origin/master +$tag = git describe --tags --abbrev=0 $commit + +if ([String]::IsNullOrEmpty($tag)) { + Write-Host "No previous tag found" + $tag = "v3.0.0" +} + +$tag = $tag.TrimStart("v") + +Write-Host "Last Published Version Found: $tag" + + +# Get Current Version +$currentVersion = $((Get-Content -Path $VersionFile | ConvertFrom-Json).version) +Write-Host "Current Version in Repository: $currentVersion" + +# Check if Update is needed +if ((CompareVersions -Version1 $currentVersion -Version2 $tag) -gt 0) { + Write-Host "No Version update neccesarry" + return; +} + +# Update Version +$updatedVersion = IncrementVersion -prevVersion $tag + +Write-Host "Updating to $updatedVersion" + +$content = [System.IO.File]::ReadAllText($VersionFile) +$versionPattern = [regex]'(?m)(^\s*"version"\s*:\s*")[^"]+("\s*,)' +$content = $versionPattern.Replace($content, { + param($match) + return $match.Groups[1].Value + $updatedVersion + $match.Groups[2].Value +}, 1) + +$utf8WithoutBom = New-Object System.Text.UTF8Encoding($false) +[System.IO.File]::WriteAllText($VersionFile, $content, $utf8WithoutBom) From e56887bcb424140a4efd52bcfae551ce872c1745 Mon Sep 17 00:00:00 2001 From: prestoncraw Date: Tue, 25 Aug 2026 16:18:14 -0400 Subject: [PATCH 03/14] Remove UIVersion from logic to bust cache --- PQBrowser/PQBrowser.csproj | 6 +----- PQBrowser/Startup.cs | 5 ----- PQBrowser/Views/Home/Index.cshtml | 6 +++++- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/PQBrowser/PQBrowser.csproj b/PQBrowser/PQBrowser.csproj index f31d6686..dc9e5ec6 100644 --- a/PQBrowser/PQBrowser.csproj +++ b/PQBrowser/PQBrowser.csproj @@ -84,11 +84,7 @@ PreserveNewest - - PreserveNewest - PreserveNewest - - + diff --git a/PQBrowser/Startup.cs b/PQBrowser/Startup.cs index 2e77036c..7637743e 100644 --- a/PQBrowser/Startup.cs +++ b/PQBrowser/Startup.cs @@ -42,7 +42,6 @@ using PQBrowser.Security; using System; using System.IO; -using System.Text.Json; namespace PQBrowser; public class Startup @@ -52,9 +51,6 @@ public Startup(IConfiguration configuration, IWebHostEnvironment env) SetupTempPath(); Configuration = configuration; Env = env; - - using JsonDocument package = JsonDocument.Parse(File.ReadAllText(Path.Combine(env.ContentRootPath, "package.json"))); - UIVersion = package.RootElement.GetProperty("version").GetString()!; } public static class Policies @@ -65,7 +61,6 @@ public static class Policies public IWebHostEnvironment Env { get; set; } public IConfiguration Configuration { get; } - public static string UIVersion { get; private set; } = ""; public void ConfigureServices(IServiceCollection services) { diff --git a/PQBrowser/Views/Home/Index.cshtml b/PQBrowser/Views/Home/Index.cshtml index 1614ff15..72ece45f 100644 --- a/PQBrowser/Views/Home/Index.cshtml +++ b/PQBrowser/Views/Home/Index.cshtml @@ -22,6 +22,7 @@ //*******************************************************************************************************@ @using Gemstone.Data @using Gemstone.Configuration; +@using Gemstone.Reflection @using Microsoft.AspNetCore.Antiforgery @using Microsoft.AspNetCore.Authentication.Cookies @using Microsoft.Extensions.Options @@ -40,6 +41,9 @@ @{ Layout = ""; + Version assemblyVersionInfo = AssemblyInfo.EntryAssembly.Version; + string applicationVersion = assemblyVersionInfo.Major + "." + assemblyVersionInfo.Minor + "." + assemblyVersionInfo.Build; + try { using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { @@ -123,6 +127,6 @@ - + From 493d187a4c271789adbbe9e134aaeb8c671e7cbc Mon Sep 17 00:00:00 2001 From: Christoph Lackner Date: Mon, 31 Aug 2026 11:28:29 -0400 Subject: [PATCH 04/14] Updated Query and Filters for EventSearch --- PQBrowser/Controllers/OpenXDAController.cs | 299 ++++++++++++--------- 1 file changed, 168 insertions(+), 131 deletions(-) diff --git a/PQBrowser/Controllers/OpenXDAController.cs b/PQBrowser/Controllers/OpenXDAController.cs index d1cae764..b2757c17 100644 --- a/PQBrowser/Controllers/OpenXDAController.cs +++ b/PQBrowser/Controllers/OpenXDAController.cs @@ -25,6 +25,7 @@ using Gemstone.Data; using Gemstone.Data.Model; using Gemstone.EnumExtensions; +using Gemstone.Numeric.Interpolation; using Gemstone.Security.AccessControl; using Microsoft.AspNetCore.Mvc; using openXDA.Model; @@ -55,15 +56,45 @@ public string Columns DataTable collumns = connection.RetrieveData(@" SELECT COLUMN_NAME,TABLE_NAME FROM INFORMATION_SCHEMA.COLUMNS - WHERE TABLE_NAME = 'SEBrowser.EventSearchEventView' - OR TABLE_NAME = 'SEBrowser.EventSearchDetailsView' - AND COLUMN_NAME NOT LIKE 'Sort.%'"); - - m_collumns = String.Join(",", collumns.Select() - .Select(r => r["TABLE_NAME"].ToString() == "SEBrowser.EventSearchDetailsView" && r["COLUMN_NAME"].ToString() == "EventID" - ? $"[{r["TABLE_NAME"]}].[{r["COLUMN_NAME"]}] AS [EventID1]" - : $"[{r["TABLE_NAME"]}].[{r["COLUMN_NAME"]}]") - ); + WHERE (TABLE_NAME = 'SEBrowser.EventSearchEventView' + OR TABLE_NAME = 'SEBrowser.EventSearchLongestDisturbanceView' + OR TABLE_NAME = 'SEBrowser.EventSearchShortestDisturbanceView' + OR TABLE_NAME = 'SEBrowser.EventSearchLargestDisturbanceView' + OR TABLE_NAME = 'SEBrowser.EventSearchSmallestDisturbanceView' + OR TABLE_NAME = 'SEBrowser.EventSearchFaultView') + AND COLUMN_NAME NOT LIKE 'Sort.%' + AND COLUMN_NAME NOT LIKE 'EventID' + AND COLUMN_NAME NOT LIKE 'DisturbanceID' + AND COLUMN_NAME NOT LIKE 'FaultID' + AND COLUMN_NAME NOT LIKE 'Event Type' + "); + + IEnumerable rows = collumns.Select(); + Dictionary uniqueCollumns = rows + .GroupBy(r => r["COLUMN_NAME"].ToString()) + .ToDictionary((k) => k.Key, (k) => k.Count()); + + Dictionary currentCount = new (); + List columns = new List(); + foreach (DataRow row in rows) + { + if (!uniqueCollumns.TryGetValue(row["COLUMN_NAME"].ToString(), out int count)) + continue; + if (count == 1) + { + columns.Add($"[{row["TABLE_NAME"]}].[{row["COLUMN_NAME"]}]"); + continue; + } + int i = 0; + if (!currentCount.TryGetValue(row["COLUMN_NAME"].ToString(), out i)) + { + currentCount.Add(row["COLUMN_NAME"].ToString(), 0); + } + currentCount[row["COLUMN_NAME"].ToString()] = i + 1; + columns.Add($"[{row["TABLE_NAME"]}].[{row["COLUMN_NAME"]}] AS [{row["COLUMN_NAME"]} {i}]"); + } + + m_collumns = string.Join(",", columns); } return m_collumns; } @@ -92,10 +123,12 @@ FROM INFORMATION_SCHEMA.COLUMNS } } + #endregion + #region [ Constructors ] - public OpenXDAController() : base() { } - #endregion + public OpenXDAController() : base() { } + #endregion #region [ Static ] private static MemoryCache s_memoryCache; @@ -126,9 +159,6 @@ public class EventSearchPostData public int curveID { get; set; } public bool curveInside { get; set; } public bool curveOutside { get; set; } - public string transientType { get; set; } - public string sagType { get; set; } - public string swellType { get; set; } public int[] meterIDs { get; set; } public int[] typeIDs { get; set; } public int[] assetIDs { get; set; } @@ -180,6 +210,12 @@ public DataTable GetEventSearchData([FromBody] EventSearchPostData postData) string recordFilter; string filters = ""; + Dictionary eventTypeLookup = new TableOperations(connection).QueryRecords().ToList() + .ToDictionary(x => x.Name, x => x.ID); + + string[] disturbanceTypes = { "Sag", "Swell", "Transient", "Interruption" }; + string[] faultTypes = { "Fault", "RecloseIntoFault" }; + //If eventID is provided no filters are needed this is a 1-1 lookup if (postData.eventID is not null) { @@ -193,7 +229,7 @@ public DataTable GetEventSearchData([FromBody] EventSearchPostData postData) string eventType = (postData.typeIDs is null) ? null : getEventTypeFilter(postData); string phase = (postData.phases is null) ? null : getPhaseFilter(postData); - string eventCharacteristic = getEventCharacteristicFilter(postData); + string eventCharacteristic = getEventCharacteristicFilter(postData, eventTypeLookup, disturbanceTypes); string asset = getAssetFilters(postData); filters = $"{(string.IsNullOrEmpty(eventType) ? "" : $"AND ({eventType})")} "; @@ -208,53 +244,98 @@ public DataTable GetEventSearchData([FromBody] EventSearchPostData postData) string sortBy = $"ORDER BY {sortColumn} {(postData.ascending ? "ASC" : "DESC")}"; - string query = + string query = $""" SELECT TOP {postData.numberResults?.ToString() ?? "100"} + EventType.Description AS [Event Type], + Main.Phase AS [Phase], + Main.EventID, + Main.LargestDisturbanceID AS DisturbanceID, {Columns} FROM ( SELECT - Event.ID EventID, - EventWorstDisturbance.WorstDisturbanceID DisturbanceID, - FaultSummary.FaultNumber FaultID + Event.ID EventID, + COALESCE(DisturbanceTypeID, EventTypeID) AS EventTypeID, + MaxMag.ID AS LargestDisturbanceID, + MinMAG.ID AS SmallestDisturbanceID, + MinDur.ID AS ShortestDisturbanceID, + MaxDur.ID AS LongestDisturbanceID, + MaxMag.PerUnitMagnitude AS LargestDisturbanceMagnitude, + MinMag.PerUnitMagnitude AS SmallestDisturbanceMagnitude, + MinDur.DurationSeconds AS SmallestDisturbanceDuration, + MaxDur.DurationSeconds AS LargestDisturbanceDuration, + Event.StartTime AS StartTime, + Event.AssetID AS AssetID, + Event.MeterID AS MeterID, + FaultSummary.FaultNumber AS FaultID, + COALESCE(FaultSummary.FaultType,(SELECT Name FROM Phase WHERE ID = MaxMag.PhaseID)) AS Phase FROM - Event JOIN - EventType ON Event.EventTypeID = EventType.ID LEFT OUTER JOIN - EventWorstDisturbance ON - EventWorstDisturbance.EventID = Event.ID AND - EventType.Name IN ('Sag', 'Swell', 'Interruption', 'Transient') LEFT OUTER JOIN - FaultGroup ON - FaultGroup.EventID = Event.ID AND - COALESCE(FaultGroup.FaultDetectionLogicResult, 0) <> 0 LEFT OUTER JOIN - FaultSummary ON - FaultSummary.EventID = Event.ID AND - FaultSummary.IsSelectedAlgorithm <> 0 AND - ( - FaultGroup.ID IS NOT NULL OR - ( - FaultSummary.IsValid <> 0 AND - FaultSummary.IsSuppressed = 0 - ) - ) AND - EventType.Name IN ('Fault', 'RecloseIntoFault') + Event CROSS APPLY ( + SELECT Disturbance.EventTypeID AS DisturbanceTypeID, + MAX(Disturbance.PerUnitMagnitude) AS MaxMagnitude, + MIN(Disturbance.PerUnitMagnitude) AS MinMagnitude, + MAX(Disturbance.DurationSeconds) AS MaxDuration, + MIN(Disturbance.DurationSeconds) AS MinDuration + FROM Disturbance WHERE Disturbance.EventID = Event.ID + GROUP BY (EventTypeID) + UNION ALL + SELECT NULL, NULL, NULL, NULL, NULL WHERE EVENT.EventTypeID NOT IN ({string.Join(",", disturbanceTypes.Select(x => eventTypeLookup.TryGetValue(x, out int id) ? id : -1))}) + ) D OUTER APPLY ( + SELECT TOP 1 ID, + PerUnitMagnitude, + PhaseID + FROM Disturbance + WHERE EventID = Event.ID AND + PerUnitMagnitude = D.MaxMagnitude AND + D.DisturbanceTypeID = EventTypeID + ) MaxMag OUTER APPLY ( + SELECT TOP 1 ID, + PerUnitMagnitude + FROM Disturbance + WHERE EventID = Event.ID AND + PerUnitMagnitude = D.MinMagnitude AND + D.DisturbanceTypeID = EventTypeID + ) MinMag OUTER APPLY ( + SELECT TOP 1 ID, + DurationSeconds + FROM Disturbance + WHERE EventID = Event.ID AND + DurationSeconds = D.MinDuration AND + D.DisturbanceTypeID = EventTypeID + ) MinDur OUTER APPLY ( + SELECT TOP 1 ID, + DurationSeconds + FROM Disturbance + WHERE EventID = Event.ID AND + DurationSeconds = D.MaxDuration AND + D.DisturbanceTypeID = EventTypeID + ) MaxDur LEFT OUTER JOIN + FaultSummary ON + FaultSummary.IsSelectedAlgorithm <> 0 AND + FaultSummary.IsValid <> 0 AND + FaultSummary.IsSuppressed = 0 AND + Event.EventTypeID IN ({string.Join(",", faultTypes.Select(x => eventTypeLookup.TryGetValue(x, out int id) ? id : -1))}) AND + D.DisturbanceTypeID IS NULL AND + Event.ID = FaultSummary.EventID WHERE - ({recordFilter}) AND - ( - EventWorstDisturbance.ID IS NOT NULL OR - FaultSummary.ID IS NOT NULL OR - EventType.Name IN ('BreakerOpen', 'Other') - ) + ({recordFilter}) {filters} - ) Main JOIN - [SEBrowser.EventSearchEventView] ON Main.EventID = [SEBrowser.EventSearchEventView].EventID JOIN - [SEBrowser.EventSearchDetailsView] ON - Main.EventID = [SEBrowser.EventSearchDetailsView].EventID AND - ( - (Main.DisturbanceID IS NOT NULL AND [SEBrowser.EventSearchDetailsView].DisturbanceID = Main.DisturbanceID) OR - (Main.FaultID IS NOT NULL AND [SEBrowser.EventSearchDetailsView].FaultID = Main.FaultID) OR - (COALESCE([SEBrowser.EventSearchDetailsView].DisturbanceID, Main.DisturbanceID) IS NULL AND COALESCE([SEBrowser.EventSearchDetailsView].FaultID, Main.FaultID) IS NULL) - ) {sortBy} + ) Main INNER JOIN + EventType ON Main.EventTypeID = EventType.ID INNER JOIN + [SEBrowser.EventSearchEventView] ON Main.EventID = [SEBrowser.EventSearchEventView].EventID LEFT JOIN + [SEBrowser.EventSearchLongestDisturbanceView] ON + (Main.LargestDisturbanceID IS NOT NULL AND [SEBrowser.EventSearchLongestDisturbanceView].DisturbanceID = Main.LargestDisturbanceID) LEFT JOIN + [SEBrowser.EventSearchShortestDisturbanceView] ON + (Main.ShortestDisturbanceID IS NOT NULL AND [SEBrowser.EventSearchShortestDisturbanceView].DisturbanceID = Main.ShortestDisturbanceID) LEFT JOIN + [SEBrowser.EventSearchSmallestDisturbanceView] ON + (Main.SmallestDisturbanceID IS NOT NULL AND [SEBrowser.EventSearchSmallestDisturbanceView].DisturbanceID = Main.SmallestDisturbanceID) LEFT JOIN + [SEBrowser.EventSearchLargestDisturbanceView] ON + (Main.LargestDisturbanceID IS NOT NULL AND [SEBrowser.EventSearchLargestDisturbanceView].DisturbanceID = Main.LargestDisturbanceID) LEFT JOIN + [SEBrowser.EventSearchFaultView] ON + Main.FaultID IS NOT NULL AND [SEBrowser.EventSearchFaultView].FaultID = Main.FaultID AND + Main.EventID = [SEBrowser.EventSearchFaultView].EventID + {sortBy} """; DataTable table = connection.RetrieveData(query, queryParameter); @@ -267,25 +348,14 @@ private string getTimeFilter(EventSearchPostData postData) { string timeWindowUnits = ((TimeWindowUnits)postData.timeWindowUnits).GetDescription(); - return $"Event.StartTime BETWEEN DATEADD({timeWindowUnits},{-1 * postData.windowSize}, {{0}}) AND DATEADD({timeWindowUnits},{postData.windowSize}, {{0}})"; + return $"StartTime BETWEEN DATEADD({timeWindowUnits},{-1 * postData.windowSize}, {{0}}) AND DATEADD({timeWindowUnits},{postData.windowSize}, {{0}})"; } private string getEventTypeFilter(EventSearchPostData postData) { - List eventTypes = new(); - - // The ELSE clause is required because TVA would like to be able to specify filters that make no sense... - if (postData.typeIDs.Count() > 0) - eventTypes.Add($"(SELECT d.EventTypeID FROM Disturbance d WHERE d.ID = EventWorstDisturbance.WorstDisturbanceID) IN ({string.Join(",", postData.typeIDs)})"); - else - eventTypes.Add($"(SELECT d.EventTypeID FROM Disturbance d WHERE d.ID = EventWorstDisturbance.WorstDisturbanceID) IN (-1)"); - if (postData.typeIDs.Count() > 0) - eventTypes.Add($"Event.EventTypeID IN ({string.Join(",", postData.typeIDs)})"); - else - eventTypes.Add($"Event.EventTypeID IN (-1)"); - - return string.Join(" OR ", eventTypes); + return ($"EventTypeID IN ({string.Join(",", postData.typeIDs)})"); + return ($"EventTypeID IN (-1)"); } private string getPhaseFilter(EventSearchPostData postData) @@ -316,10 +386,10 @@ private string getPhaseFilter(EventSearchPostData postData) string phaseCombined = string.Join(", ", phases.Where(item => item.Value).Select(item => "\'" + item.Key + "\'")); - return $"(EventWorstDisturbance.WorstDisturbanceID IN (SELECT Disturbance.ID FROM Disturbance WHERE Disturbance.PhaseID IN (Select Phase.ID FROM Phase Where Phase.Name IN ({phaseCombined}))) OR FaultSummary.FaultType IN ({phaseCombined}))"; + return $"Phase IN ({phaseCombined})))"; } - private string getEventCharacteristicFilter(EventSearchPostData postData) + private string getEventCharacteristicFilter(EventSearchPostData postData, Dictionary eventTypesLookup, string[] disturbanceTypes) { List characteristics = new(); @@ -327,94 +397,61 @@ private string getEventCharacteristicFilter(EventSearchPostData postData) //Min and Max Durations if (postData.durationMin > 0) { - string filt = $"((SELECT d.DurationCycles FROM Disturbance d WHERE d.ID = EventWorstDisturbance.WorstDisturbanceID) >= {postData.durationMin} OR "; - filt += $" FaultSummary.DurationCycles >= {postData.durationMin})"; - characteristics.Add(filt); + characteristics.Add($"(LargestDisturbanceDuration > {postData.durationMin} OR EventTypeID NOT IN ({string.Join(", ", disturbanceTypes.Select(x => eventTypesLookup.TryGetValue(x, out int id) ? id : -1))}))"); } if (postData.durationMax > 0) { - string filt = $" ((SELECT d.DurationCycles FROM Disturbance d WHERE d.ID = EventWorstDisturbance.WorstDisturbanceID) <= {postData.durationMax} OR"; - filt += $" FaultSummary.DurationCycles <= {postData.durationMax})"; - characteristics.Add(filt); + characteristics.Add($"(SmallestDisturbanceDuration < {postData.durationMax} OR EventTypeID NOT IN ({string.Join(", ", disturbanceTypes.Select(x => eventTypesLookup.TryGetValue(x, out int id) ? id : -1))}))"); + } // Sag Min and Max if (postData.sagMin > 0) { - string filt; - if (postData.sagType == "LL") - filt = $"((SELECT d.PerUnitMagnitude FROM Disturbance d WHERE d.ID = EventWorstDisturbance.WorstLLDisturbanceID) >= {postData.sagMin} OR (SELECT d.EventTypeID FROM Disturbance d WHERE d.ID = EventWorstDisturbance.WorstDisturbanceID) IN (SELECT ID FROM EventType WHERE Name <> 'Sag'))"; - else if (postData.sagType == "LN") - filt = $"((SELECT d.PerUnitMagnitude FROM Disturbance d WHERE d.ID = EventWorstDisturbance.WorstLNDisturbanceID) >= {postData.sagMin} OR (SELECT d.EventTypeID FROM Disturbance d WHERE d.ID = EventWorstDisturbance.WorstDisturbanceID) IN (SELECT ID FROM EventType WHERE Name <> 'Sag'))"; - else - filt = $"((SELECT d.PerUnitMagnitude FROM Disturbance d WHERE d.ID = EventWorstDisturbance.WorstLLDisturbanceID) >= {postData.sagMin} OR (SELECT d.PerUnitMagnitude FROM Disturbance d WHERE d.ID = EventWorstDisturbance.WorstLNDisturbanceID) >= {postData.sagMin} OR (SELECT d.EventTypeID FROM Disturbance d WHERE d.ID = EventWorstDisturbance.WorstDisturbanceID) IN (SELECT ID FROM EventType WHERE Name <> 'Sag'))"; - characteristics.Add(filt); + characteristics.Add($"(LargestDisturbanceMagnitude > {postData.sagMin} OR EventTypeID <> {eventTypesLookup["Sag"]})"); } if (postData.sagMax > 0) { - string filt; - if (postData.sagType == "LL") - filt = $"((SELECT d.PerUnitMagnitude FROM Disturbance d WHERE d.ID = EventWorstDisturbance.WorstLLDisturbanceID) <= {postData.sagMax} OR (SELECT d.EventTypeID FROM Disturbance d WHERE d.ID = EventWorstDisturbance.WorstDisturbanceID) IN (SELECT ID FROM EventType WHERE Name <> 'Sag'))"; - else if (postData.sagType == "LN") - filt = $"((SELECT d.PerUnitMagnitude FROM Disturbance d WHERE d.ID = EventWorstDisturbance.WorstLNDisturbanceID) <= {postData.sagMax} OR (SELECT d.EventTypeID FROM Disturbance d WHERE d.ID = EventWorstDisturbance.WorstDisturbanceID) IN (SELECT ID FROM EventType WHERE Name <> 'Sag'))"; - else - filt = $"((SELECT d.PerUnitMagnitude FROM Disturbance d WHERE d.ID = EventWorstDisturbance.WorstLLDisturbanceID) <= {postData.sagMax} OR (SELECT d.PerUnitMagnitude FROM Disturbance d WHERE d.ID = EventWorstDisturbance.WorstLNDisturbanceID) <= {postData.sagMax} OR (SELECT d.EventTypeID FROM Disturbance d WHERE d.ID = EventWorstDisturbance.WorstDisturbanceID) IN (SELECT ID FROM EventType WHERE Name <> 'Sag'))"; - characteristics.Add(filt); + characteristics.Add($"(SmallestDisturbanceMagnitude < {postData.sagMax} OR EventTypeID <> {eventTypesLookup["Sag"]})"); } // Swell Min and Max if (postData.swellMin > 0) { - string filt; - if (postData.swellType == "LL") - filt = $"((SELECT d.PerUnitMagnitude FROM Disturbance d WHERE d.ID = EventWorstDisturbance.WorstLLDisturbanceID) >= {postData.swellMin} OR (SELECT d.EventTypeID FROM Disturbance d WHERE d.ID = EventWorstDisturbance.WorstDisturbanceID) IN (SELECT ID FROM EventType WHERE Name <> 'Swell'))"; - else if (postData.swellType == "LN") - filt = $"((SELECT d.PerUnitMagnitude FROM Disturbance d WHERE d.ID = EventWorstDisturbance.WorstLNDisturbanceID) >= {postData.swellMin} OR (SELECT d.EventTypeID FROM Disturbance d WHERE d.ID = EventWorstDisturbance.WorstDisturbanceID) IN (SELECT ID FROM EventType WHERE Name <> 'Swell'))"; - else - filt = $"((SELECT d.PerUnitMagnitude FROM Disturbance d WHERE d.ID = EventWorstDisturbance.WorstLLDisturbanceID) >= {postData.swellMin} OR (SELECT d.PerUnitMagnitude FROM Disturbance d WHERE d.ID = EventWorstDisturbance.WorstLNDisturbanceID) >= {postData.swellMin} OR (SELECT d.EventTypeID FROM Disturbance d WHERE d.ID = EventWorstDisturbance.WorstDisturbanceID) IN (SELECT ID FROM EventType WHERE Name <> 'Swell'))"; - characteristics.Add(filt); + characteristics.Add($"(LargestDisturbanceMagnitude > {postData.swellMin} OR EventTypeID <> {eventTypesLookup["Swell"]})"); + } if (postData.swellMax > 0) { - string filt; - if (postData.swellType == "LL") - filt = $"((SELECT d.PerUnitMagnitude FROM Disturbance d WHERE d.ID = EventWorstDisturbance.WorstLLDisturbanceID) <= {postData.swellMax} OR (SELECT d.EventTypeID FROM Disturbance d WHERE d.ID = EventWorstDisturbance.WorstDisturbanceID) IN (SELECT ID FROM EventType WHERE Name <> 'Swell'))"; - else if (postData.swellType == "LN") - filt = $"((SELECT d.PerUnitMagnitude FROM Disturbance d WHERE d.ID = EventWorstDisturbance.WorstLNDisturbanceID) <= {postData.swellMax} OR (SELECT d.EventTypeID FROM Disturbance d WHERE d.ID = EventWorstDisturbance.WorstDisturbanceID) IN (SELECT ID FROM EventType WHERE Name <> 'Swell'))"; - else - filt = $"((SELECT d.PerUnitMagnitude FROM Disturbance d WHERE d.ID = EventWorstDisturbance.WorstLLDisturbanceID) <= {postData.swellMax} OR (SELECT d.PerUnitMagnitude FROM Disturbance d WHERE d.ID = EventWorstDisturbance.WorstLNDisturbanceID) <= {postData.swellMax} OR (SELECT d.EventTypeID FROM Disturbance d WHERE d.ID = EventWorstDisturbance.WorstDisturbanceID) IN (SELECT ID FROM EventType WHERE Name <> 'Swell'))"; - characteristics.Add(filt); + characteristics.Add($"(SmallestDisturbanceMagnitude < {postData.swellMax} OR EventTypeID <> {eventTypesLookup["Swell"]})"); + } // Transient min and max if (postData.transientMin > 0) { - string filt; - if (postData.transientType == "LL") - filt = $"((SELECT d.PerUnitMagnitude FROM Disturbance d WHERE d.ID = EventWorstDisturbance.WorstLLDisturbanceID) >= {postData.transientMin} OR (SELECT d.EventTypeID FROM Disturbance d WHERE d.ID = EventWorstDisturbance.WorstDisturbanceID) IN (SELECT ID FROM EventType WHERE Name <> 'Transient'))"; - else if (postData.transientType == "LN") - filt = $"((SELECT d.PerUnitMagnitude FROM Disturbance d WHERE d.ID = EventWorstDisturbance.WorstLNDisturbanceID) >= {postData.transientMin} OR (SELECT d.EventTypeID FROM Disturbance d WHERE d.ID = EventWorstDisturbance.WorstDisturbanceID) IN (SELECT ID FROM EventType WHERE Name <> 'Transient'))"; - else - filt = $"((SELECT d.PerUnitMagnitude FROM Disturbance d WHERE d.ID = EventWorstDisturbance.WorstLLDisturbanceID) >= {postData.transientMin} OR (SELECT d.PerUnitMagnitude FROM Disturbance d WHERE d.ID = EventWorstDisturbance.WorstLNDisturbanceID) >= {postData.transientMin} OR (SELECT d.EventTypeID FROM Disturbance d WHERE d.ID = EventWorstDisturbance.WorstDisturbanceID) IN (SELECT ID FROM EventType WHERE Name <> 'Transient'))"; - characteristics.Add(filt); + characteristics.Add($"(LargestDisturbanceMagnitude > {postData.transientMin} OR EventTypeID <> {eventTypesLookup["Transient"]})"); } if (postData.transientMax > 0) { - string filt; - if (postData.transientType == "LL") - filt = $"((SELECT d.PerUnitMagnitude FROM Disturbance d WHERE d.ID = EventWorstDisturbance.WorstLLDisturbanceID) <= {postData.transientMax} OR (SELECT d.EventTypeID FROM Disturbance d WHERE d.ID = EventWorstDisturbance.WorstDisturbanceID) IN (SELECT ID FROM EventType WHERE Name <> 'Transient'))"; - else if (postData.transientType == "LN") - filt = $"((SELECT d.PerUnitMagnitude FROM Disturbance d WHERE d.ID = EventWorstDisturbance.WorstLNDisturbanceID) <= {postData.transientMax} OR (SELECT d.EventTypeID FROM Disturbance d WHERE d.ID = EventWorstDisturbance.WorstDisturbanceID) IN (SELECT ID FROM EventType WHERE Name <> 'Transient'))"; - else - filt = $"((SELECT d.PerUnitMagnitude FROM Disturbance d WHERE d.ID = EventWorstDisturbance.WorstLLDisturbanceID) <= {postData.transientMax} OR (SELECT d.PerUnitMagnitude FROM Disturbance d WHERE d.ID = EventWorstDisturbance.WorstLNDisturbanceID) <= {postData.transientMax} OR (SELECT d.EventTypeID FROM Disturbance d WHERE d.ID = EventWorstDisturbance.WorstDisturbanceID) IN (SELECT ID FROM EventType WHERE Name <> 'Transient'))"; - characteristics.Add(filt); + characteristics.Add($"(SmallestDisturbanceMagnitude < {postData.transientMax} OR EventTypeID <> {eventTypesLookup["Transient"]})"); + } // Mag Dur Curves if (!postData.curveOutside || !postData.curveInside) { - string filt = $"( (SELECT d.DurationSeconds FROM Disturbance d WHERE d.ID = WorstDisturbanceID) IS NOT NULL AND (SELECT d.PerUnitMagnitude FROM Disturbance d WHERE d.ID = WorstDisturbanceID) IS NOT NULL AND (SELECT TOP 1 Area FROM StandardMagDurCurve WHERE ID = {postData.curveID})"; - filt += $".STContains(geometry::Point((SELECT d.DurationSeconds FROM Disturbance d WHERE d.ID = WorstDisturbanceID),(SELECT d.PerUnitMagnitude FROM Disturbance d WHERE d.ID = WorstDisturbanceID),0)) = {(postData.curveInside ? 1 : 0)})"; + string filt = "((SmallestDisturbanceMagnitude IS NOT NULL AND LargestDisturbanceMagnitude IS NOT NULL AND "; + filt += "LargestDisturbanceDuration IS NOT NULL AND SmallestDisturbanceDuration IS NOT NULL AND "; + string curve = $"(SELECT TOP 1 Area FROM StandardMagDurCurve WHERE ID = {postData.curveID})"; + // Special because for INSIDE we check if there is any overlap + filt += $"{curve}.STIntersects(geometry::STGeomFromText(CONCAT('Polygon((',SmallestDisturbanceDuration,' ',SmallestDisturbanceMagnitude, ',' " + + $"LargestDisturbanceDuration,' ',SmallestDisturbanceMagnitude, ','" + + $"LargestDisturbanceDuration,' ',LargestDisturbanceMagnitude, ','" + + $"SmallestDisturbanceDuration,' ',LargestDisturbanceMagnitude, ','" + + $"SmallestDisturbanceDuration,' ',SmallestDisturbanceMagnitude," + + $"'))',0)) = {(postData.curveInside ? 1 : 0)}) OR " + + $" EventTypeID NOT IN ({string.Join(",", disturbanceTypes.Select(x => eventTypesLookup.TryGetValue(x, out int id) ? id : -1))}))"; characteristics.Add(filt); } @@ -426,22 +463,22 @@ private string getAssetFilters(EventSearchPostData postData) List assets = new(); if (postData.meterIDs.Count() > 0) - assets.Add($"Event.MeterID IN ({string.Join(",", postData.meterIDs)})"); + assets.Add($"MeterID IN ({string.Join(",", postData.meterIDs)})"); if (postData.assetIDs.Count() > 0) - assets.Add($"Event.AssetID IN ({string.Join(",", postData.assetIDs)})"); + assets.Add($"AssetID IN ({string.Join(",", postData.assetIDs)})"); if (postData.locationIDs.Count() > 0) { - string filt = $"(Event.AssetID IN (SELECT AssetLocation.AssetID FROM AssetLocation WHERE AssetLocation.LocationID IN ({string.Join(",", postData.locationIDs)}))"; - filt += $" OR Event.MeterID IN (SELECT Meter.ID FROM Meter WHERE Meter.LocationID IN ({string.Join(",", postData.locationIDs)})))"; + string filt = $"(AssetID IN (SELECT AssetLocation.AssetID FROM AssetLocation WHERE AssetLocation.LocationID IN ({string.Join(",", postData.locationIDs)}))"; + filt += $" OR MeterID IN (SELECT Meter.ID FROM Meter WHERE Meter.LocationID IN ({string.Join(",", postData.locationIDs)})))"; assets.Add(filt); } if (postData.groupIDs.Count() > 0) { - string filt = $"(Event.AssetID IN (SELECT AssetAssetGroup.AssetID FROM AssetAssetGroup WHERE AssetAssetGroup.AssetGroupID IN ({string.Join(",", postData.groupIDs)}))"; - filt += $" OR Event.MeterID IN (SELECT MeterAssetGroup.MeterID FROM MeterAssetGroup WHERE MeterAssetGroup.AssetGroupID IN ({string.Join(",", postData.groupIDs)})))"; + string filt = $"(AssetID IN (SELECT AssetAssetGroup.AssetID FROM AssetAssetGroup WHERE AssetAssetGroup.AssetGroupID IN ({string.Join(",", postData.groupIDs)}))"; + filt += $" OR MeterID IN (SELECT MeterAssetGroup.MeterID FROM MeterAssetGroup WHERE MeterAssetGroup.AssetGroupID IN ({string.Join(",", postData.groupIDs)})))"; assets.Add(filt); } From 965fba06863a9c36c863ca87aceb849079b644a7 Mon Sep 17 00:00:00 2001 From: Christoph Lackner Date: Tue, 1 Sep 2026 10:48:23 -0400 Subject: [PATCH 05/14] Updated Mag Dur Chart --- PQBrowser/Controllers/OpenXDAController.cs | 104 +++++++++++++++--- .../Components/EventSearch/EventSearch.tsx | 8 +- 2 files changed, 92 insertions(+), 20 deletions(-) diff --git a/PQBrowser/Controllers/OpenXDAController.cs b/PQBrowser/Controllers/OpenXDAController.cs index b2757c17..08f7ed1d 100644 --- a/PQBrowser/Controllers/OpenXDAController.cs +++ b/PQBrowser/Controllers/OpenXDAController.cs @@ -344,6 +344,76 @@ [SEBrowser.EventSearchFaultView] ON } } + // Read-style POST; without this, Gemstone's verb mapping would require Create access. + [Route("GetMagDurChartData"), HttpPost, ResourceAccess(ResourceAccessType.Read)] + public DataTable GetMagDurChartData([FromBody] EventSearchPostData postData) + { + if (postData is null) + throw new Exception("Unable to parse request body"); + + using AdoDataConnection connection = new(Settings.Default); + { + // When an eventID is provided, the request targets that single event and the time/characteristic filters are skipped + object queryParameter; + string recordFilter; + string filters = ""; + + Dictionary eventTypeLookup = new TableOperations(connection).QueryRecords().ToList() + .ToDictionary(x => x.Name, x => x.ID); + + string[] disturbanceTypes = { "Sag", "Swell", "Transient", "Interruption" }; + string[] faultTypes = { "Fault", "RecloseIntoFault" }; + + //If eventID is provided no filters are needed this is a 1-1 lookup + if (postData.eventID is not null) + { + queryParameter = postData.eventID; + recordFilter = "Event.ID = {0}"; + } + else + { + queryParameter = DateTime.ParseExact(postData.date + " " + postData.time, "MM/dd/yyyy HH:mm:ss.fff", new CultureInfo("en-US")); + recordFilter = getTimeFilter(postData); + + string eventType = (postData.typeIDs is null) ? null : getEventTypeFilter(postData); + string phase = (postData.phases is null) ? null : getPhaseFilter(postData); + string eventCharacteristic = getEventCharacteristicFilter(postData, eventTypeLookup, disturbanceTypes, "MagDurDuration", "MagDurMagnitude"); + string asset = getAssetFilters(postData); + + filters = $"{(string.IsNullOrEmpty(eventType) ? "" : $"AND ({eventType})")} "; + filters += $"{(string.IsNullOrEmpty(phase) ? "" : $"AND ({phase})")} "; + filters += $"{(string.IsNullOrEmpty(eventCharacteristic) ? "" : $"AND {eventCharacteristic}")} "; + filters += $"{(string.IsNullOrEmpty(asset) ? "" : $"AND {asset}")}"; + } + + string query = + $""" + SELECT TOP {postData.numberResults?.ToString() ?? "100"} + * FROM ( + SELECT + Event.ID EventID, + Disturbance.EventTypeID AS EventTypeID, + Event.StartTime AS StartTime, + Event.AssetID AS AssetID, + Event.MeterID AS MeterID, + (SELECT Name FROM Phase WHERE ID = Disturbance.PhaseID) AS Phase, + Disturbance.PerUnitMagnitude AS MagDurMagnitude, + Disturbance.DurationCycles AS MagDurDuration + FROM + Disturbance INNER JOIN Event ON + Disturbance.EventID = Event.ID + ) Main + WHERE + ({recordFilter}) + {filters} + """; + + DataTable table = connection.RetrieveData(query, queryParameter); + + return table; + } + } + private string getTimeFilter(EventSearchPostData postData) { string timeWindowUnits = ((TimeWindowUnits)postData.timeWindowUnits).GetDescription(); @@ -389,7 +459,7 @@ private string getPhaseFilter(EventSearchPostData postData) return $"Phase IN ({phaseCombined})))"; } - private string getEventCharacteristicFilter(EventSearchPostData postData, Dictionary eventTypesLookup, string[] disturbanceTypes) + private string getEventCharacteristicFilter(EventSearchPostData postData, Dictionary eventTypesLookup, string[] disturbanceTypes, string durationColumn = null, string magnitudeColumn = null) { List characteristics = new(); @@ -397,59 +467,61 @@ private string getEventCharacteristicFilter(EventSearchPostData postData, Dictio //Min and Max Durations if (postData.durationMin > 0) { - characteristics.Add($"(LargestDisturbanceDuration > {postData.durationMin} OR EventTypeID NOT IN ({string.Join(", ", disturbanceTypes.Select(x => eventTypesLookup.TryGetValue(x, out int id) ? id : -1))}))"); + characteristics.Add($"({durationColumn ?? "LargestDisturbanceDuration"} > {postData.durationMin} OR EventTypeID NOT IN ({string.Join(", ", disturbanceTypes.Select(x => eventTypesLookup.TryGetValue(x, out int id) ? id : -1))}))"); } if (postData.durationMax > 0) { - characteristics.Add($"(SmallestDisturbanceDuration < {postData.durationMax} OR EventTypeID NOT IN ({string.Join(", ", disturbanceTypes.Select(x => eventTypesLookup.TryGetValue(x, out int id) ? id : -1))}))"); + characteristics.Add($"({durationColumn ?? "SmallestDisturbanceDuration"} < {postData.durationMax} OR EventTypeID NOT IN ({string.Join(", ", disturbanceTypes.Select(x => eventTypesLookup.TryGetValue(x, out int id) ? id : -1))}))"); } // Sag Min and Max if (postData.sagMin > 0) { - characteristics.Add($"(LargestDisturbanceMagnitude > {postData.sagMin} OR EventTypeID <> {eventTypesLookup["Sag"]})"); + characteristics.Add($"({magnitudeColumn ?? "LargestDisturbanceMagnitude"} > {postData.sagMin} OR EventTypeID <> {eventTypesLookup["Sag"]})"); } if (postData.sagMax > 0) { - characteristics.Add($"(SmallestDisturbanceMagnitude < {postData.sagMax} OR EventTypeID <> {eventTypesLookup["Sag"]})"); + characteristics.Add($"({magnitudeColumn ?? "SmallestDisturbanceMagnitude"} < {postData.sagMax} OR EventTypeID <> {eventTypesLookup["Sag"]})"); } // Swell Min and Max if (postData.swellMin > 0) { - characteristics.Add($"(LargestDisturbanceMagnitude > {postData.swellMin} OR EventTypeID <> {eventTypesLookup["Swell"]})"); + characteristics.Add($"({magnitudeColumn ?? "LargestDisturbanceMagnitude"} > {postData.swellMin} OR EventTypeID <> {eventTypesLookup["Swell"]})"); } if (postData.swellMax > 0) { - characteristics.Add($"(SmallestDisturbanceMagnitude < {postData.swellMax} OR EventTypeID <> {eventTypesLookup["Swell"]})"); + characteristics.Add($"({magnitudeColumn ?? "SmallestDisturbanceMagnitude"} < {postData.swellMax} OR EventTypeID <> {eventTypesLookup["Swell"]})"); } // Transient min and max if (postData.transientMin > 0) { - characteristics.Add($"(LargestDisturbanceMagnitude > {postData.transientMin} OR EventTypeID <> {eventTypesLookup["Transient"]})"); + characteristics.Add($"({magnitudeColumn ?? "LargestDisturbanceMagnitude"} > {postData.transientMin} OR EventTypeID <> {eventTypesLookup["Transient"]})"); } if (postData.transientMax > 0) { - characteristics.Add($"(SmallestDisturbanceMagnitude < {postData.transientMax} OR EventTypeID <> {eventTypesLookup["Transient"]})"); + characteristics.Add($"({magnitudeColumn ?? "SmallestDisturbanceMagnitude"} < {postData.transientMax} OR EventTypeID <> {eventTypesLookup["Transient"]})"); } // Mag Dur Curves if (!postData.curveOutside || !postData.curveInside) { - string filt = "((SmallestDisturbanceMagnitude IS NOT NULL AND LargestDisturbanceMagnitude IS NOT NULL AND "; - filt += "LargestDisturbanceDuration IS NOT NULL AND SmallestDisturbanceDuration IS NOT NULL AND "; + string filt = $"(({magnitudeColumn ?? "SmallestDisturbanceMagnitude"} IS NOT NULL AND " + + $"{magnitudeColumn ?? "LargestDisturbanceMagnitude"} IS NOT NULL AND "; + filt += $"{durationColumn ?? "LargestDisturbanceDuration"} IS NOT NULL AND " + + $"{durationColumn ?? "SmallestDisturbanceDuration"} IS NOT NULL AND "; string curve = $"(SELECT TOP 1 Area FROM StandardMagDurCurve WHERE ID = {postData.curveID})"; // Special because for INSIDE we check if there is any overlap - filt += $"{curve}.STIntersects(geometry::STGeomFromText(CONCAT('Polygon((',SmallestDisturbanceDuration,' ',SmallestDisturbanceMagnitude, ',' " + - $"LargestDisturbanceDuration,' ',SmallestDisturbanceMagnitude, ','" + - $"LargestDisturbanceDuration,' ',LargestDisturbanceMagnitude, ','" + - $"SmallestDisturbanceDuration,' ',LargestDisturbanceMagnitude, ','" + - $"SmallestDisturbanceDuration,' ',SmallestDisturbanceMagnitude," + + filt += $"{curve}.STIntersects(geometry::STGeomFromText(CONCAT('Polygon((',{durationColumn ?? "SmallestDisturbanceDuration"},' ',{magnitudeColumn ?? "SmallestDisturbanceMagnitude"}, ',' " + + $"{durationColumn ?? "LargestDisturbanceDuration"},' ',{magnitudeColumn ?? "SmallestDisturbanceMagnitude"}, ','" + + $"{durationColumn ?? "LargestDisturbanceDuration"},' ',{magnitudeColumn ?? "LargestDisturbanceMagnitude"}, ','" + + $"{durationColumn ?? "SmallestDisturbanceDuration"},' ',{magnitudeColumn ?? "LargestDisturbanceMagnitude"}, ','" + + $"{durationColumn ?? "SmallestDisturbanceDuration"},' ',{magnitudeColumn ?? "SmallestDisturbanceMagnitude"}," + $"'))',0)) = {(postData.curveInside ? 1 : 0)}) OR " + $" EventTypeID NOT IN ({string.Join(",", disturbanceTypes.Select(x => eventTypesLookup.TryGetValue(x, out int id) ? id : -1))}))"; characteristics.Add(filt); diff --git a/PQBrowser/Scripts/TSX/Components/EventSearch/EventSearch.tsx b/PQBrowser/Scripts/TSX/Components/EventSearch/EventSearch.tsx index 92ba4065..f439ad6e 100644 --- a/PQBrowser/Scripts/TSX/Components/EventSearch/EventSearch.tsx +++ b/PQBrowser/Scripts/TSX/Components/EventSearch/EventSearch.tsx @@ -108,11 +108,11 @@ const EventSearch = () => { }, [referenceDataReady]); const getEventData = React.useCallback((query: IDynamicEventSearchQuery) => - GetDynamicEventSearchData({ ...eventRequest, sortKey: query?.SortField, ascending: query?.Ascending ?? true }, `${homePath}api/OpenXDA/GetEventSearchData`) + GetDynamicEventSearchData({ ...eventRequest, sortKey: query?.SortField, ascending: query?.Ascending ?? true }, `${homePath}api/OpenXDA/${(showMagDur ? 'GetMagDurChartData' : 'GetEventSearchData')}`) .done((data) => { localStorage.setItem('SEbrowser.EventSearch.EventIDs', data.map(d => d.EventID).join(',')); }), - [eventRequest]); + [eventRequest, showMagDur]); // Effect to load the selected event's record directly by ID (e.g. deep-linked from the URL) so selection does not depend on the current filter results React.useEffect(() => { @@ -149,7 +149,7 @@ const EventSearch = () => { const magDurWidgetView = React.useMemo(() => ({ ID: 0, - Name: DynamicMagDurChart.Name, + Name: 'Magnitude Duration Chart', Type: DynamicMagDurChart.Name, Setting: JSON.stringify({ ...DynamicMagDurChart.DefaultSettings, @@ -160,7 +160,7 @@ const EventSearch = () => { const eventSearchWidgetView = React.useMemo(() => ({ ID: 1, - Name: DynamicEventSearch.Name, + Name: 'Eventsearch Results Table', Type: DynamicEventSearch.Name, Setting: JSON.stringify({ ...DynamicEventSearch.DefaultSettings, From 2938e7e6d589ca640bcda187810ae712e20aeb66 Mon Sep 17 00:00:00 2001 From: Christoph Lackner Date: Tue, 1 Sep 2026 11:45:05 -0400 Subject: [PATCH 06/14] removed LL and LN options --- .../Components/EventSearch/EventSearchData.ts | 3 -- .../Navbar/EventCharacteristics.tsx | 42 ------------------- .../Scripts/TSX/Store/EventSearchSlice.ts | 16 +------ PQBrowser/Scripts/TSX/global.d.ts | 3 -- 4 files changed, 1 insertion(+), 63 deletions(-) diff --git a/PQBrowser/Scripts/TSX/Components/EventSearch/EventSearchData.ts b/PQBrowser/Scripts/TSX/Components/EventSearch/EventSearchData.ts index b8cb3cd9..271f362b 100644 --- a/PQBrowser/Scripts/TSX/Components/EventSearch/EventSearchData.ts +++ b/PQBrowser/Scripts/TSX/Components/EventSearch/EventSearchData.ts @@ -67,13 +67,10 @@ export function BuildDynamicEventSearchRequest( }, transientMin: characteristics.transientMin ?? 0, transientMax: characteristics.transientMax ?? 0, - transientType: characteristics.transientType, sagMin: characteristics.sagMin ?? 0, sagMax: characteristics.sagMax ?? 0, - sagType: characteristics.sagType, swellMin: characteristics.swellMin ?? 0, swellMax: characteristics.swellMax ?? 0, - swellType: characteristics.swellType, curveID: characteristics.curveID, curveInside: characteristics.curveInside, curveOutside: characteristics.curveOutside, diff --git a/PQBrowser/Scripts/TSX/Components/EventSearch/Navbar/EventCharacteristics.tsx b/PQBrowser/Scripts/TSX/Components/EventSearch/Navbar/EventCharacteristics.tsx index 5e175d40..3201d541 100644 --- a/PQBrowser/Scripts/TSX/Components/EventSearch/Navbar/EventCharacteristics.tsx +++ b/PQBrowser/Scripts/TSX/Components/EventSearch/Navbar/EventCharacteristics.tsx @@ -232,20 +232,6 @@ const EventSearchNavbar = () => { -
- - Record={newEventCharacteristicFilter} - Label='' - Field='sagType' - Setter={handleSetEventCharacteristicFilter} - Options={[ - { Label: 'LL', Value: 'LL' }, - { Label: 'LN', Value: 'LN' }, - { Label: 'Both', Value: 'both' } - ]} - Style={{ marginBottom: 0 }} - /> -
@@ -308,20 +294,6 @@ const EventSearchNavbar = () => { -
- - Record={newEventCharacteristicFilter} - Label='' - Field='transientType' - Setter={handleSetEventCharacteristicFilter} - Options={[ - { Label: 'LL', Value: 'LL' }, - { Label: 'LN', Value: 'LN' }, - { Label: 'Both', Value: 'both' } - ]} - Style={{ marginBottom: 0 }} - /> -
@@ -362,20 +334,6 @@ const EventSearchNavbar = () => { -
- - Record={newEventCharacteristicFilter} - Label='' - Field='swellType' - Setter={handleSetEventCharacteristicFilter} - Options={[ - { Label: 'LL', Value: 'LL' }, - { Label: 'LN', Value: 'LN' }, - { Label: 'Both', Value: 'both' } - ]} - Style={{ marginBottom: 0 }} - /> -
diff --git a/PQBrowser/Scripts/TSX/Store/EventSearchSlice.ts b/PQBrowser/Scripts/TSX/Store/EventSearchSlice.ts index 93f990cb..28c25d2e 100644 --- a/PQBrowser/Scripts/TSX/Store/EventSearchSlice.ts +++ b/PQBrowser/Scripts/TSX/Store/EventSearchSlice.ts @@ -80,9 +80,6 @@ const initialState: Redux.EventSearchState = { sagMax: null, swellMin: null, swellMax: null, - sagType: 'both', - swellType: 'both', - transientType: 'both', curveID: 1, curveInside: true, curveOutside: true @@ -138,10 +135,6 @@ export const EventSearchsSlice = createSlice({ state.EventCharacteristic.swellMax = parseOptionalNumber(action.payload.query['swellMax']); state.EventCharacteristic.swellMin = parseOptionalNumber(action.payload.query['swellMin']); - state.EventCharacteristic.sagType = (action.payload.query['sagType'] ?? 'both') as ('both' | 'LL' | 'LN'); - state.EventCharacteristic.swellType = (action.payload.query['swellType'] ?? 'both') as ('both' | 'LL' | 'LN'); - state.EventCharacteristic.transientType = (action.payload.query['transientType'] ?? 'both') as ('both' | 'LL' | 'LN'); - state.EventCharacteristic.curveID = parseInt(action.payload.query['curveID']?.toString() ?? '1'); state.EventCharacteristic.curveInside = (action.payload.query['curveInside'] ?? 'true') == 'true'; state.EventCharacteristic.curveOutside = (action.payload.query['curveOutside'] ?? 'true') == 'true'; @@ -183,7 +176,7 @@ export const EventSearchsSlice = createSlice({ state.EventCharacteristic = { durationMax: null, durationMin: null, phases: { AN: true, BN: true, CN: true, AB: true, BC: true, CA: true, ABG: true, BCG: true, ABC: true, ABCG: true }, - transientMin: null, transientMax: null, sagMin: null, sagMax: null, swellMin: null, swellMax: null, sagType: 'both', swellType: 'both', transientType: 'both', + transientMin: null, transientMax: null, sagMin: null, sagMax: null, swellMin: null, swellMax: null, curveID: 1, curveInside: true, curveOutside: true }; @@ -329,13 +322,6 @@ export function GenerateQueryParams( if (event.swellMin != 0) result['swellMin'] = event.swellMin - if (event.sagType != 'both') - result['sagType'] = event.sagType - if (event.swellType != 'both') - result['swellType'] = event.swellType - if (event.transientType != 'both') - result['transientType'] = event.transientType - if (event.curveID != 1) result['curveID'] = event.curveID if (!event.curveInside) diff --git a/PQBrowser/Scripts/TSX/global.d.ts b/PQBrowser/Scripts/TSX/global.d.ts index 9f7b7cfa..9561ecd2 100644 --- a/PQBrowser/Scripts/TSX/global.d.ts +++ b/PQBrowser/Scripts/TSX/global.d.ts @@ -126,13 +126,10 @@ export namespace PQBrowser { phases: IPhaseFilters, transientMin: number | null, transientMax: number | null, - transientType: ('LL'|'LN'|'both'), sagMin: number | null, sagMax: number | null, - sagType: ('LL' | 'LN' | 'both'), swellMin: number | null, swellMax: number | null, - swellType: ('LL' | 'LN' | 'both'), curveID: number, curveInside: boolean, curveOutside: boolean From fc782633baa18db7c64d6657f3652d8bd464c2be Mon Sep 17 00:00:00 2001 From: Christoph Lackner Date: Tue, 1 Sep 2026 11:45:23 -0400 Subject: [PATCH 07/14] Cleaned up filters --- PQBrowser/Controllers/OpenXDAController.cs | 143 ++++++++++----------- 1 file changed, 70 insertions(+), 73 deletions(-) diff --git a/PQBrowser/Controllers/OpenXDAController.cs b/PQBrowser/Controllers/OpenXDAController.cs index 08f7ed1d..6dd9eabe 100644 --- a/PQBrowser/Controllers/OpenXDAController.cs +++ b/PQBrowser/Controllers/OpenXDAController.cs @@ -122,12 +122,11 @@ FROM INFORMATION_SCHEMA.COLUMNS return m_sortCollumns; } } - #endregion #region [ Constructors ] - public OpenXDAController() : base() { } + public OpenXDAController() : base() { } #endregion #region [ Static ] @@ -136,7 +135,23 @@ public OpenXDAController() : base() { } static OpenXDAController() { s_memoryCache = new MemoryCache("OpenXDA"); + s_disturbanceTypes = new[] { "Sag", "Swell", "Transient", "Interruption" }; + s_faultTypes = new[] { "Fault", "RecloseIntoFault" }; + + using AdoDataConnection connection = new(Settings.Default); + { + + s_eventTypeLookup = new TableOperations(connection).QueryRecords().ToList() + .ToDictionary(x => x.Name, x => x.ID); + } + } + + private static string[] s_disturbanceTypes; + private static string[] s_faultTypes; + private static Dictionary s_eventTypeLookup; + + #endregion #region [ Event Search Page ] @@ -210,12 +225,7 @@ public DataTable GetEventSearchData([FromBody] EventSearchPostData postData) string recordFilter; string filters = ""; - Dictionary eventTypeLookup = new TableOperations(connection).QueryRecords().ToList() - .ToDictionary(x => x.Name, x => x.ID); - - string[] disturbanceTypes = { "Sag", "Swell", "Transient", "Interruption" }; - string[] faultTypes = { "Fault", "RecloseIntoFault" }; - + //If eventID is provided no filters are needed this is a 1-1 lookup if (postData.eventID is not null) { @@ -225,12 +235,14 @@ public DataTable GetEventSearchData([FromBody] EventSearchPostData postData) else { queryParameter = DateTime.ParseExact(postData.date + " " + postData.time, "MM/dd/yyyy HH:mm:ss.fff", new CultureInfo("en-US")); - recordFilter = getTimeFilter(postData); + recordFilter = getTimeFilter(postData, "Event.StartTime"); - string eventType = (postData.typeIDs is null) ? null : getEventTypeFilter(postData); - string phase = (postData.phases is null) ? null : getPhaseFilter(postData); - string eventCharacteristic = getEventCharacteristicFilter(postData, eventTypeLookup, disturbanceTypes); - string asset = getAssetFilters(postData); + string eventType = (postData.typeIDs is null) ? null : getEventTypeFilter(postData, "COALESCE(DisturbanceTypeID, EventTypeID)"); + string phase = (postData.phases is null) ? null : getPhaseFilter(postData, "COALESCE(FaultSummary.FaultType,(SELECT Name FROM Phase WHERE ID = MaxMag.PhaseID))"); + + string eventCharacteristic = getEventCharacteristicFilter(postData, "MinDur.DurationSeconds", + "MaxDur.DurationSeconds", "MinMag.PerUnitMagnitude", "MaxMag.PerUnitMagnitude", "COALESCE(DisturbanceTypeID, EventTypeID)"); + string asset = getAssetFilters(postData, "Event.AssetID", "Event.MeterID"); filters = $"{(string.IsNullOrEmpty(eventType) ? "" : $"AND ({eventType})")} "; filters += $"{(string.IsNullOrEmpty(phase) ? "" : $"AND ({phase})")} "; @@ -265,9 +277,6 @@ public DataTable GetEventSearchData([FromBody] EventSearchPostData postData) MinMag.PerUnitMagnitude AS SmallestDisturbanceMagnitude, MinDur.DurationSeconds AS SmallestDisturbanceDuration, MaxDur.DurationSeconds AS LargestDisturbanceDuration, - Event.StartTime AS StartTime, - Event.AssetID AS AssetID, - Event.MeterID AS MeterID, FaultSummary.FaultNumber AS FaultID, COALESCE(FaultSummary.FaultType,(SELECT Name FROM Phase WHERE ID = MaxMag.PhaseID)) AS Phase FROM @@ -280,7 +289,7 @@ Event CROSS APPLY ( FROM Disturbance WHERE Disturbance.EventID = Event.ID GROUP BY (EventTypeID) UNION ALL - SELECT NULL, NULL, NULL, NULL, NULL WHERE EVENT.EventTypeID NOT IN ({string.Join(",", disturbanceTypes.Select(x => eventTypeLookup.TryGetValue(x, out int id) ? id : -1))}) + SELECT NULL, NULL, NULL, NULL, NULL WHERE EVENT.EventTypeID NOT IN ({string.Join(",", s_disturbanceTypes.Select(x => s_eventTypeLookup.TryGetValue(x, out int id) ? id : -1))}) ) D OUTER APPLY ( SELECT TOP 1 ID, PerUnitMagnitude, @@ -315,7 +324,7 @@ FaultSummary ON FaultSummary.IsSelectedAlgorithm <> 0 AND FaultSummary.IsValid <> 0 AND FaultSummary.IsSuppressed = 0 AND - Event.EventTypeID IN ({string.Join(",", faultTypes.Select(x => eventTypeLookup.TryGetValue(x, out int id) ? id : -1))}) AND + Event.EventTypeID IN ({string.Join(",", s_faultTypes.Select(x => s_eventTypeLookup.TryGetValue(x, out int id) ? id : -1))}) AND D.DisturbanceTypeID IS NULL AND Event.ID = FaultSummary.EventID WHERE @@ -358,12 +367,6 @@ public DataTable GetMagDurChartData([FromBody] EventSearchPostData postData) string recordFilter; string filters = ""; - Dictionary eventTypeLookup = new TableOperations(connection).QueryRecords().ToList() - .ToDictionary(x => x.Name, x => x.ID); - - string[] disturbanceTypes = { "Sag", "Swell", "Transient", "Interruption" }; - string[] faultTypes = { "Fault", "RecloseIntoFault" }; - //If eventID is provided no filters are needed this is a 1-1 lookup if (postData.eventID is not null) { @@ -373,12 +376,16 @@ public DataTable GetMagDurChartData([FromBody] EventSearchPostData postData) else { queryParameter = DateTime.ParseExact(postData.date + " " + postData.time, "MM/dd/yyyy HH:mm:ss.fff", new CultureInfo("en-US")); - recordFilter = getTimeFilter(postData); + recordFilter = getTimeFilter(postData, "Event.StartTime"); + + string eventType = (postData.typeIDs is null) ? null : getEventTypeFilter(postData, "Disturbance.EventTypeID"); + string phase = (postData.phases is null) ? null : getPhaseFilter(postData, "(SELECT Name FROM Phase WHERE ID = Disturbance.PhaseID)"); - string eventType = (postData.typeIDs is null) ? null : getEventTypeFilter(postData); - string phase = (postData.phases is null) ? null : getPhaseFilter(postData); - string eventCharacteristic = getEventCharacteristicFilter(postData, eventTypeLookup, disturbanceTypes, "MagDurDuration", "MagDurMagnitude"); - string asset = getAssetFilters(postData); + string eventCharacteristic = getEventCharacteristicFilter(postData, + "Disturbance.DurationSeconds", "Disturbance.DurationSeconds", + "Disturbance.PerUnitMagnitude", "Disturbance.PerUnitMagnitude", + "Disturbance.EventTypeID"); + string asset = getAssetFilters(postData, "Event.AssetID", "Event.MeterID"); filters = $"{(string.IsNullOrEmpty(eventType) ? "" : $"AND ({eventType})")} "; filters += $"{(string.IsNullOrEmpty(phase) ? "" : $"AND ({phase})")} "; @@ -389,20 +396,12 @@ public DataTable GetMagDurChartData([FromBody] EventSearchPostData postData) string query = $""" SELECT TOP {postData.numberResults?.ToString() ?? "100"} - * FROM ( - SELECT Event.ID EventID, - Disturbance.EventTypeID AS EventTypeID, - Event.StartTime AS StartTime, - Event.AssetID AS AssetID, - Event.MeterID AS MeterID, - (SELECT Name FROM Phase WHERE ID = Disturbance.PhaseID) AS Phase, Disturbance.PerUnitMagnitude AS MagDurMagnitude, - Disturbance.DurationCycles AS MagDurDuration + Disturbance.DurationSeconds AS MagDurDuration FROM Disturbance INNER JOIN Event ON Disturbance.EventID = Event.ID - ) Main WHERE ({recordFilter}) {filters} @@ -414,21 +413,21 @@ Disturbance INNER JOIN Event ON } } - private string getTimeFilter(EventSearchPostData postData) + private string getTimeFilter(EventSearchPostData postData, string column) { string timeWindowUnits = ((TimeWindowUnits)postData.timeWindowUnits).GetDescription(); - return $"StartTime BETWEEN DATEADD({timeWindowUnits},{-1 * postData.windowSize}, {{0}}) AND DATEADD({timeWindowUnits},{postData.windowSize}, {{0}})"; + return $"{column} BETWEEN DATEADD({timeWindowUnits},{-1 * postData.windowSize}, {{0}}) AND DATEADD({timeWindowUnits},{postData.windowSize}, {{0}})"; } - private string getEventTypeFilter(EventSearchPostData postData) + private string getEventTypeFilter(EventSearchPostData postData, string column) { if (postData.typeIDs.Count() > 0) - return ($"EventTypeID IN ({string.Join(",", postData.typeIDs)})"); - return ($"EventTypeID IN (-1)"); + return ($"{column} IN ({string.Join(",", postData.typeIDs)})"); + return ($"{column} IN (-1)"); } - private string getPhaseFilter(EventSearchPostData postData) + private string getPhaseFilter(EventSearchPostData postData, string column) { Dictionary phases = new Dictionary { @@ -456,10 +455,10 @@ private string getPhaseFilter(EventSearchPostData postData) string phaseCombined = string.Join(", ", phases.Where(item => item.Value).Select(item => "\'" + item.Key + "\'")); - return $"Phase IN ({phaseCombined})))"; + return $"{column} IN ({phaseCombined})))"; } - private string getEventCharacteristicFilter(EventSearchPostData postData, Dictionary eventTypesLookup, string[] disturbanceTypes, string durationColumn = null, string magnitudeColumn = null) + private string getEventCharacteristicFilter(EventSearchPostData postData, string minDurationColumn, string maxDurationColumn, string minMagnitudeColumn, string maxMagnitudeColumn, string eventTypeColumn) { List characteristics = new(); @@ -467,90 +466,88 @@ private string getEventCharacteristicFilter(EventSearchPostData postData, Dictio //Min and Max Durations if (postData.durationMin > 0) { - characteristics.Add($"({durationColumn ?? "LargestDisturbanceDuration"} > {postData.durationMin} OR EventTypeID NOT IN ({string.Join(", ", disturbanceTypes.Select(x => eventTypesLookup.TryGetValue(x, out int id) ? id : -1))}))"); + characteristics.Add($"({maxDurationColumn} > {postData.durationMin} OR {eventTypeColumn} NOT IN ({string.Join(", ", s_disturbanceTypes.Select(x => s_eventTypeLookup.TryGetValue(x, out int id) ? id : -1))}))"); } if (postData.durationMax > 0) { - characteristics.Add($"({durationColumn ?? "SmallestDisturbanceDuration"} < {postData.durationMax} OR EventTypeID NOT IN ({string.Join(", ", disturbanceTypes.Select(x => eventTypesLookup.TryGetValue(x, out int id) ? id : -1))}))"); - + characteristics.Add($"({minDurationColumn} < {postData.durationMax} OR {eventTypeColumn} NOT IN ({string.Join(", ", s_disturbanceTypes.Select(x => s_eventTypeLookup.TryGetValue(x, out int id) ? id : -1))}))"); } // Sag Min and Max if (postData.sagMin > 0) { - characteristics.Add($"({magnitudeColumn ?? "LargestDisturbanceMagnitude"} > {postData.sagMin} OR EventTypeID <> {eventTypesLookup["Sag"]})"); + characteristics.Add($"({maxMagnitudeColumn} > {postData.sagMin} OR {eventTypeColumn} <> {s_eventTypeLookup["Sag"]})"); } if (postData.sagMax > 0) { - characteristics.Add($"({magnitudeColumn ?? "SmallestDisturbanceMagnitude"} < {postData.sagMax} OR EventTypeID <> {eventTypesLookup["Sag"]})"); + characteristics.Add($"({minMagnitudeColumn} < {postData.sagMax} OR {eventTypeColumn} <> {s_eventTypeLookup["Sag"]})"); } // Swell Min and Max if (postData.swellMin > 0) { - characteristics.Add($"({magnitudeColumn ?? "LargestDisturbanceMagnitude"} > {postData.swellMin} OR EventTypeID <> {eventTypesLookup["Swell"]})"); + characteristics.Add($"({maxMagnitudeColumn} > {postData.swellMin} OR {eventTypeColumn} <> {s_eventTypeLookup["Swell"]})"); } if (postData.swellMax > 0) { - characteristics.Add($"({magnitudeColumn ?? "SmallestDisturbanceMagnitude"} < {postData.swellMax} OR EventTypeID <> {eventTypesLookup["Swell"]})"); + characteristics.Add($"({minMagnitudeColumn} < {postData.swellMax} OR {eventTypeColumn} <> {s_eventTypeLookup["Swell"]})"); } // Transient min and max if (postData.transientMin > 0) { - characteristics.Add($"({magnitudeColumn ?? "LargestDisturbanceMagnitude"} > {postData.transientMin} OR EventTypeID <> {eventTypesLookup["Transient"]})"); + characteristics.Add($"({maxMagnitudeColumn} > {postData.transientMin} OR {eventTypeColumn} <> {s_eventTypeLookup["Transient"]})"); } if (postData.transientMax > 0) { - characteristics.Add($"({magnitudeColumn ?? "SmallestDisturbanceMagnitude"} < {postData.transientMax} OR EventTypeID <> {eventTypesLookup["Transient"]})"); - + characteristics.Add($"({minMagnitudeColumn} < {postData.transientMax} OR {eventTypeColumn} <> {s_eventTypeLookup["Transient"]})"); } // Mag Dur Curves if (!postData.curveOutside || !postData.curveInside) { - string filt = $"(({magnitudeColumn ?? "SmallestDisturbanceMagnitude"} IS NOT NULL AND " + - $"{magnitudeColumn ?? "LargestDisturbanceMagnitude"} IS NOT NULL AND "; - filt += $"{durationColumn ?? "LargestDisturbanceDuration"} IS NOT NULL AND " + - $"{durationColumn ?? "SmallestDisturbanceDuration"} IS NOT NULL AND "; + string filt = $"(({minMagnitudeColumn} IS NOT NULL AND " + + $"{maxMagnitudeColumn} IS NOT NULL AND "; + filt += $"{maxDurationColumn} IS NOT NULL AND " + + $"{minDurationColumn} IS NOT NULL AND "; string curve = $"(SELECT TOP 1 Area FROM StandardMagDurCurve WHERE ID = {postData.curveID})"; // Special because for INSIDE we check if there is any overlap - filt += $"{curve}.STIntersects(geometry::STGeomFromText(CONCAT('Polygon((',{durationColumn ?? "SmallestDisturbanceDuration"},' ',{magnitudeColumn ?? "SmallestDisturbanceMagnitude"}, ',' " + - $"{durationColumn ?? "LargestDisturbanceDuration"},' ',{magnitudeColumn ?? "SmallestDisturbanceMagnitude"}, ','" + - $"{durationColumn ?? "LargestDisturbanceDuration"},' ',{magnitudeColumn ?? "LargestDisturbanceMagnitude"}, ','" + - $"{durationColumn ?? "SmallestDisturbanceDuration"},' ',{magnitudeColumn ?? "LargestDisturbanceMagnitude"}, ','" + - $"{durationColumn ?? "SmallestDisturbanceDuration"},' ',{magnitudeColumn ?? "SmallestDisturbanceMagnitude"}," + + filt += $"{curve}.STIntersects(geometry::STGeomFromText(CONCAT('Polygon((',{minDurationColumn},' ',{minMagnitudeColumn}, ',' " + + $"{maxDurationColumn},' ',{minMagnitudeColumn}, ','" + + $"{maxDurationColumn},' ',{maxMagnitudeColumn}, ','" + + $"{minDurationColumn},' ',{maxMagnitudeColumn}, ','" + + $"{minDurationColumn},' ',{minMagnitudeColumn}," + $"'))',0)) = {(postData.curveInside ? 1 : 0)}) OR " + - $" EventTypeID NOT IN ({string.Join(",", disturbanceTypes.Select(x => eventTypesLookup.TryGetValue(x, out int id) ? id : -1))}))"; + $" {eventTypeColumn} NOT IN ({string.Join(",", s_disturbanceTypes.Select(x => s_eventTypeLookup.TryGetValue(x, out int id) ? id : -1))}))"; characteristics.Add(filt); } return string.Join(" AND ", characteristics); } - private string getAssetFilters(EventSearchPostData postData) + private string getAssetFilters(EventSearchPostData postData, string meterIDColumn, string assetIDColumn) { List assets = new(); if (postData.meterIDs.Count() > 0) - assets.Add($"MeterID IN ({string.Join(",", postData.meterIDs)})"); + assets.Add($"{meterIDColumn} IN ({string.Join(",", postData.meterIDs)})"); if (postData.assetIDs.Count() > 0) - assets.Add($"AssetID IN ({string.Join(",", postData.assetIDs)})"); + assets.Add($"{assetIDColumn} IN ({string.Join(",", postData.assetIDs)})"); if (postData.locationIDs.Count() > 0) { - string filt = $"(AssetID IN (SELECT AssetLocation.AssetID FROM AssetLocation WHERE AssetLocation.LocationID IN ({string.Join(",", postData.locationIDs)}))"; - filt += $" OR MeterID IN (SELECT Meter.ID FROM Meter WHERE Meter.LocationID IN ({string.Join(",", postData.locationIDs)})))"; + string filt = $"({assetIDColumn} IN (SELECT AssetLocation.AssetID FROM AssetLocation WHERE AssetLocation.LocationID IN ({string.Join(",", postData.locationIDs)}))"; + filt += $" OR {meterIDColumn} IN (SELECT Meter.ID FROM Meter WHERE Meter.LocationID IN ({string.Join(",", postData.locationIDs)})))"; assets.Add(filt); } if (postData.groupIDs.Count() > 0) { - string filt = $"(AssetID IN (SELECT AssetAssetGroup.AssetID FROM AssetAssetGroup WHERE AssetAssetGroup.AssetGroupID IN ({string.Join(",", postData.groupIDs)}))"; - filt += $" OR MeterID IN (SELECT MeterAssetGroup.MeterID FROM MeterAssetGroup WHERE MeterAssetGroup.AssetGroupID IN ({string.Join(",", postData.groupIDs)})))"; + string filt = $"({assetIDColumn} IN (SELECT AssetAssetGroup.AssetID FROM AssetAssetGroup WHERE AssetAssetGroup.AssetGroupID IN ({string.Join(",", postData.groupIDs)}))"; + filt += $" OR {meterIDColumn} IN (SELECT MeterAssetGroup.MeterID FROM MeterAssetGroup WHERE MeterAssetGroup.AssetGroupID IN ({string.Join(",", postData.groupIDs)})))"; assets.Add(filt); } From 80fbf79c069ce6e8196e20c538efa4a07801fde3 Mon Sep 17 00:00:00 2001 From: Christoph Lackner Date: Tue, 1 Sep 2026 12:25:53 -0400 Subject: [PATCH 08/14] Cleaned up Column Logic --- PQBrowser/Controllers/OpenXDAController.cs | 146 +++++++++++---------- 1 file changed, 80 insertions(+), 66 deletions(-) diff --git a/PQBrowser/Controllers/OpenXDAController.cs b/PQBrowser/Controllers/OpenXDAController.cs index 6dd9eabe..cb2188ee 100644 --- a/PQBrowser/Controllers/OpenXDAController.cs +++ b/PQBrowser/Controllers/OpenXDAController.cs @@ -43,17 +43,32 @@ namespace PQBrowser.Controllers public class OpenXDAController : ControllerBase { #region [ Members ] - private string m_collumns = null; - private Dictionary m_sortCollumns = null; - public string Columns + + #endregion + + #region [ Constructors ] + public OpenXDAController() : base() { } + #endregion + + #region [ Static ] + private static MemoryCache s_memoryCache; + + static OpenXDAController() { - get + s_memoryCache = new MemoryCache("OpenXDA"); + s_disturbanceTypes = new[] { "Sag", "Swell", "Transient", "Interruption" }; + s_faultTypes = new[] { "Fault", "RecloseIntoFault" }; + + using AdoDataConnection connection = new(Settings.Default); { - if (m_collumns is null) - using (AdoDataConnection connection = new(Settings.Default)) - { - DataTable collumns = connection.RetrieveData(@" + + s_eventTypeLookup = new TableOperations(connection).QueryRecords().ToList() + .ToDictionary(x => x.Name, x => x.ID); + + s_columnsByTable = new(); + + DataTable collumns = connection.RetrieveData(@" SELECT COLUMN_NAME,TABLE_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE (TABLE_NAME = 'SEBrowser.EventSearchEventView' @@ -69,38 +84,60 @@ AND COLUMN_NAME NOT LIKE 'FaultID' AND COLUMN_NAME NOT LIKE 'Event Type' "); - IEnumerable rows = collumns.Select(); - Dictionary uniqueCollumns = rows - .GroupBy(r => r["COLUMN_NAME"].ToString()) - .ToDictionary((k) => k.Key, (k) => k.Count()); - - Dictionary currentCount = new (); - List columns = new List(); - foreach (DataRow row in rows) - { - if (!uniqueCollumns.TryGetValue(row["COLUMN_NAME"].ToString(), out int count)) - continue; - if (count == 1) - { - columns.Add($"[{row["TABLE_NAME"]}].[{row["COLUMN_NAME"]}]"); - continue; - } - int i = 0; - if (!currentCount.TryGetValue(row["COLUMN_NAME"].ToString(), out i)) - { - currentCount.Add(row["COLUMN_NAME"].ToString(), 0); - } - currentCount[row["COLUMN_NAME"].ToString()] = i + 1; - columns.Add($"[{row["TABLE_NAME"]}].[{row["COLUMN_NAME"]}] AS [{row["COLUMN_NAME"]} {i}]"); - } - - m_collumns = string.Join(",", columns); + IEnumerable rows = collumns.Select(); + + Dictionary uniqueCollumns = rows + .GroupBy(r => r["COLUMN_NAME"].ToString()) + .ToDictionary((k) => k.Key, (k) => k.Count()); + + Dictionary currentCount = new(); + + Action addColumn = (string tbl, string col) => { + if (s_columnsByTable.ContainsKey(tbl)) + s_columnsByTable[tbl].Add($"{col}"); + else + s_columnsByTable.Add(tbl, new() { $"{col}" }); + }; + + foreach (DataRow row in rows) + { + string table = row["TABLE_NAME"].ToString(); + string col = row["COLUMN_NAME"].ToString(); + + if (!uniqueCollumns.TryGetValue(col, out int count)) + continue; + if (count == 1) + { + addColumn(table, $"[{col}]"); + continue; } - return m_collumns; + int i = 0; + + if (!currentCount.TryGetValue(col, out i)) + { + currentCount.Add(col, 0); + } + currentCount[col] = i + 1; + + addColumn(table, $"[{col}] AS [{col} {i}]"); + } } + + + } - public Dictionary SortColumns + private static string[] s_disturbanceTypes; + private static string[] s_faultTypes; + private static Dictionary s_eventTypeLookup; + + private string m_collumns = null; + private Dictionary m_sortCollumns = null; + + private static string Columns => string.Join(",", s_columnsByTable + .Select((v) => string.Join(",", v.Value.Select(c => $"[{v.Key}].{c}")))); + + public Dictionary SortColumns { get { @@ -122,35 +159,8 @@ FROM INFORMATION_SCHEMA.COLUMNS return m_sortCollumns; } } - - #endregion - - #region [ Constructors ] - public OpenXDAController() : base() { } - #endregion - - #region [ Static ] - private static MemoryCache s_memoryCache; - - static OpenXDAController() - { - s_memoryCache = new MemoryCache("OpenXDA"); - s_disturbanceTypes = new[] { "Sag", "Swell", "Transient", "Interruption" }; - s_faultTypes = new[] { "Fault", "RecloseIntoFault" }; - - using AdoDataConnection connection = new(Settings.Default); - { - - s_eventTypeLookup = new TableOperations(connection).QueryRecords().ToList() - .ToDictionary(x => x.Name, x => x.ID); - } - - } - - private static string[] s_disturbanceTypes; - private static string[] s_faultTypes; - private static Dictionary s_eventTypeLookup; + private static Dictionary> s_columnsByTable; #endregion @@ -398,10 +408,14 @@ public DataTable GetMagDurChartData([FromBody] EventSearchPostData postData) SELECT TOP {postData.numberResults?.ToString() ?? "100"} Event.ID EventID, Disturbance.PerUnitMagnitude AS MagDurMagnitude, - Disturbance.DurationSeconds AS MagDurDuration + Disturbance.DurationSeconds AS MagDurDuration, + EventType.Description AS [Event Type], + {string.Join(",", s_columnsByTable["SEBrowser.EventSearchEventView"])} FROM Disturbance INNER JOIN Event ON - Disturbance.EventID = Event.ID + Disturbance.EventID = Event.ID INNER JOIN + EventType ON Disturbance.EventTypeID = EventType.ID LEFT JOIN + [SEBrowser.EventSearchEventView] ON Disturbance.EventID = [SEBrowser.EventSearchEventView].EventID WHERE ({recordFilter}) {filters} From ffe1d6a3bab3280b40f9f45d84685bea7f60eda2 Mon Sep 17 00:00:00 2001 From: Christoph Lackner Date: Tue, 1 Sep 2026 12:27:39 -0400 Subject: [PATCH 09/14] Updated EventWidgets --- PQBrowser/EventWidgets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PQBrowser/EventWidgets b/PQBrowser/EventWidgets index 5e010f9c..c6484bbd 160000 --- a/PQBrowser/EventWidgets +++ b/PQBrowser/EventWidgets @@ -1 +1 @@ -Subproject commit 5e010f9cca19c879336b1e31dec4bd69f43a8704 +Subproject commit c6484bbdcbb6cdae0b434407593b46cecbeb5015 From c50f6e1dca451a16c080c272ed12caf4f453a9f9 Mon Sep 17 00:00:00 2001 From: prestoncraw Date: Tue, 1 Sep 2026 13:57:20 -0400 Subject: [PATCH 10/14] Fix phaseFilter paranthesis --- PQBrowser/Controllers/OpenXDAController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PQBrowser/Controllers/OpenXDAController.cs b/PQBrowser/Controllers/OpenXDAController.cs index cb2188ee..e38cba79 100644 --- a/PQBrowser/Controllers/OpenXDAController.cs +++ b/PQBrowser/Controllers/OpenXDAController.cs @@ -469,7 +469,7 @@ private string getPhaseFilter(EventSearchPostData postData, string column) string phaseCombined = string.Join(", ", phases.Where(item => item.Value).Select(item => "\'" + item.Key + "\'")); - return $"{column} IN ({phaseCombined})))"; + return $"{column} IN ({phaseCombined})"; } private string getEventCharacteristicFilter(EventSearchPostData postData, string minDurationColumn, string maxDurationColumn, string minMagnitudeColumn, string maxMagnitudeColumn, string eventTypeColumn) From 455c4eeedcdbfbfddc677e8982681da454a39690 Mon Sep 17 00:00:00 2001 From: prestoncraw Date: Tue, 1 Sep 2026 14:40:42 -0400 Subject: [PATCH 11/14] Bug fixes --- PQBrowser/Controllers/OpenXDAController.cs | 57 ++++++++++++++-------- 1 file changed, 38 insertions(+), 19 deletions(-) diff --git a/PQBrowser/Controllers/OpenXDAController.cs b/PQBrowser/Controllers/OpenXDAController.cs index e38cba79..0480e12c 100644 --- a/PQBrowser/Controllers/OpenXDAController.cs +++ b/PQBrowser/Controllers/OpenXDAController.cs @@ -44,12 +44,12 @@ public class OpenXDAController : ControllerBase { #region [ Members ] - + #endregion #region [ Constructors ] public OpenXDAController() : base() { } - #endregion + #endregion #region [ Static ] private static MemoryCache s_memoryCache; @@ -85,14 +85,15 @@ AND COLUMN_NAME NOT LIKE 'Event Type' "); IEnumerable rows = collumns.Select(); - + Dictionary uniqueCollumns = rows .GroupBy(r => r["COLUMN_NAME"].ToString()) .ToDictionary((k) => k.Key, (k) => k.Count()); Dictionary currentCount = new(); - Action addColumn = (string tbl, string col) => { + Action addColumn = (string tbl, string col) => + { if (s_columnsByTable.ContainsKey(tbl)) s_columnsByTable[tbl].Add($"{col}"); else @@ -123,7 +124,7 @@ AND COLUMN_NAME NOT LIKE 'Event Type' } } - + } @@ -147,9 +148,16 @@ public Dictionary SortColumns DataTable collumns = connection.RetrieveData(@" SELECT COLUMN_NAME,TABLE_NAME FROM INFORMATION_SCHEMA.COLUMNS - WHERE (TABLE_NAME = 'SEBrowser.EventSearchEventView' - OR TABLE_NAME = 'SEBrowser.EventSearchDetailsView') - AND COLUMN_NAME LIKE 'Sort.%'"); + WHERE TABLE_NAME IN ( + 'SEBrowser.EventSearchEventView', + 'SEBrowser.EventSearchLongestDisturbanceView', + 'SEBrowser.EventSearchShortestDisturbanceView', + 'SEBrowser.EventSearchLargestDisturbanceView', + 'SEBrowser.EventSearchSmallestDisturbanceView', + 'SEBrowser.EventSearchFaultView' + ) + AND COLUMN_NAME LIKE 'Sort.%'" + ); m_sortCollumns = collumns.Select() .ToDictionary( @@ -225,9 +233,13 @@ public class Phase [Route("GetEventSearchData"), HttpPost, ResourceAccess(ResourceAccessType.Read)] public DataTable GetEventSearchData([FromBody] EventSearchPostData postData) { - if(postData is null) + if (postData is null) throw new Exception("Unable to parse request body"); + // Convert input cycles to seconds. + postData.durationMin = postData.durationMin * (1 / 60.0); + postData.durationMax = postData.durationMax * (1 / 60.0); + using AdoDataConnection connection = new(Settings.Default); { // When an eventID is provided, the request targets that single event and the time/characteristic filters are skipped @@ -235,7 +247,7 @@ public DataTable GetEventSearchData([FromBody] EventSearchPostData postData) string recordFilter; string filters = ""; - + //If eventID is provided no filters are needed this is a 1-1 lookup if (postData.eventID is not null) { @@ -249,10 +261,10 @@ public DataTable GetEventSearchData([FromBody] EventSearchPostData postData) string eventType = (postData.typeIDs is null) ? null : getEventTypeFilter(postData, "COALESCE(DisturbanceTypeID, EventTypeID)"); string phase = (postData.phases is null) ? null : getPhaseFilter(postData, "COALESCE(FaultSummary.FaultType,(SELECT Name FROM Phase WHERE ID = MaxMag.PhaseID))"); - - string eventCharacteristic = getEventCharacteristicFilter(postData, "MinDur.DurationSeconds", + + string eventCharacteristic = getEventCharacteristicFilter(postData, "MinDur.DurationSeconds", "MaxDur.DurationSeconds", "MinMag.PerUnitMagnitude", "MaxMag.PerUnitMagnitude", "COALESCE(DisturbanceTypeID, EventTypeID)"); - string asset = getAssetFilters(postData, "Event.AssetID", "Event.MeterID"); + string asset = getAssetFilters(postData, "Event.MeterID", "Event.AssetID"); filters = $"{(string.IsNullOrEmpty(eventType) ? "" : $"AND ({eventType})")} "; filters += $"{(string.IsNullOrEmpty(phase) ? "" : $"AND ({phase})")} "; @@ -266,12 +278,13 @@ public DataTable GetEventSearchData([FromBody] EventSearchPostData postData) string sortBy = $"ORDER BY {sortColumn} {(postData.ascending ? "ASC" : "DESC")}"; - string query = - $""" + string query = + $""" SELECT TOP {postData.numberResults?.ToString() ?? "100"} EventType.Description AS [Event Type], Main.Phase AS [Phase], Main.EventID, + Main.FaultID, Main.LargestDisturbanceID AS DisturbanceID, {Columns} FROM @@ -344,7 +357,7 @@ D.DisturbanceTypeID IS NULL AND EventType ON Main.EventTypeID = EventType.ID INNER JOIN [SEBrowser.EventSearchEventView] ON Main.EventID = [SEBrowser.EventSearchEventView].EventID LEFT JOIN [SEBrowser.EventSearchLongestDisturbanceView] ON - (Main.LargestDisturbanceID IS NOT NULL AND [SEBrowser.EventSearchLongestDisturbanceView].DisturbanceID = Main.LargestDisturbanceID) LEFT JOIN + (Main.LongestDisturbanceID IS NOT NULL AND [SEBrowser.EventSearchLongestDisturbanceView].DisturbanceID = Main.LongestDisturbanceID) LEFT JOIN [SEBrowser.EventSearchShortestDisturbanceView] ON (Main.ShortestDisturbanceID IS NOT NULL AND [SEBrowser.EventSearchShortestDisturbanceView].DisturbanceID = Main.ShortestDisturbanceID) LEFT JOIN [SEBrowser.EventSearchSmallestDisturbanceView] ON @@ -370,6 +383,10 @@ public DataTable GetMagDurChartData([FromBody] EventSearchPostData postData) if (postData is null) throw new Exception("Unable to parse request body"); + // Convert input cycles to seconds. + postData.durationMin = postData.durationMin * (1 / 60.0); + postData.durationMax = postData.durationMax * (1 / 60.0); + using AdoDataConnection connection = new(Settings.Default); { // When an eventID is provided, the request targets that single event and the time/characteristic filters are skipped @@ -392,10 +409,11 @@ public DataTable GetMagDurChartData([FromBody] EventSearchPostData postData) string phase = (postData.phases is null) ? null : getPhaseFilter(postData, "(SELECT Name FROM Phase WHERE ID = Disturbance.PhaseID)"); string eventCharacteristic = getEventCharacteristicFilter(postData, - "Disturbance.DurationSeconds", "Disturbance.DurationSeconds", + "Disturbance.DurationSeconds", "Disturbance.DurationSeconds", "Disturbance.PerUnitMagnitude", "Disturbance.PerUnitMagnitude", "Disturbance.EventTypeID"); - string asset = getAssetFilters(postData, "Event.AssetID", "Event.MeterID"); + + string asset = getAssetFilters(postData, "Event.MeterID", "Event.AssetID"); filters = $"{(string.IsNullOrEmpty(eventType) ? "" : $"AND ({eventType})")} "; filters += $"{(string.IsNullOrEmpty(phase) ? "" : $"AND ({phase})")} "; @@ -406,10 +424,11 @@ public DataTable GetMagDurChartData([FromBody] EventSearchPostData postData) string query = $""" SELECT TOP {postData.numberResults?.ToString() ?? "100"} - Event.ID EventID, + Event.ID AS EventID, Disturbance.PerUnitMagnitude AS MagDurMagnitude, Disturbance.DurationSeconds AS MagDurDuration, EventType.Description AS [Event Type], + Disturbance.ID AS DisturbanceID, {string.Join(",", s_columnsByTable["SEBrowser.EventSearchEventView"])} FROM Disturbance INNER JOIN Event ON From e0a8f38957db8ee351ccb76d85e08bd068470142 Mon Sep 17 00:00:00 2001 From: gsfbuildbot Date: Tue, 1 Sep 2026 14:45:48 -0400 Subject: [PATCH 12/14] Updated Version Number --- PQBrowser/package.json | 2 +- Scripts/PQBrowser.version | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/PQBrowser/package.json b/PQBrowser/package.json index 5ad13818..ab1b7e48 100644 --- a/PQBrowser/package.json +++ b/PQBrowser/package.json @@ -1,5 +1,5 @@ { - "version": "3.1.0", + "version": "3.1.2", "name": "pqbrowser", "private": true, "devDependencies": { diff --git a/Scripts/PQBrowser.version b/Scripts/PQBrowser.version index 94ff29cc..6ebad148 100644 --- a/Scripts/PQBrowser.version +++ b/Scripts/PQBrowser.version @@ -1 +1 @@ -3.1.1 +3.1.2 \ No newline at end of file From 6c161c4eb511783a6fe95eeec02de1819e97dd87 Mon Sep 17 00:00:00 2001 From: gsfbuildbot Date: Tue, 1 Sep 2026 14:46:13 -0400 Subject: [PATCH 13/14] Updated Dependencies --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index c44239c4..94c5b209 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,5 +1,5 @@  - 1.0.181 + 1.0.182 From 27fbeab55964b028652b1c6677e05f2ca74fb1cb Mon Sep 17 00:00:00 2001 From: prestoncraw Date: Tue, 1 Sep 2026 15:43:49 -0400 Subject: [PATCH 14/14] comment out docker stage --- Jenkinsfile | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 7930cd77..c886cbd5 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -221,17 +221,19 @@ pipeline { } steps { script { - env.pqBrowserDockerTag = env.CHANGE_BRANCH == "${env.devBranch}" ? "${env.pqBrowserVersion}a" : env.pqBrowserVersion - println("Building PQBrowser Docker image tag: pqbrowser:${env.pqBrowserDockerTag}") + //env.pqBrowserDockerTag = env.CHANGE_BRANCH == "${env.devBranch}" ? "${env.pqBrowserVersion}a" : env.pqBrowserVersion + //println("Building PQBrowser Docker image tag: pqbrowser:${env.pqBrowserDockerTag}") + println("Skipping docker stage intentionally") } - powershell """ + /*powershell """ dotnet publish '.\\PQBrowser\\PQBrowser.csproj' ` --configuration Release ` '-p:PublishProfile=Docker Release Profile PQBrowser' """ powershell "docker build --build-arg CONFIGURATION=Release -f .\\PQBrowser.dockerfile -t pqbrowser:${env.pqBrowserDockerTag} ." + */ } }