From 1f87960a752bae7b1b86066359bfc045a9812e22 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Mar 2026 20:00:49 +0000 Subject: [PATCH 1/3] Add Enabled flag to AlarmGroups and filter AlarmOperation accordingly Co-authored-by: clackner-gpa <52460212+clackner-gpa@users.noreply.github.com> --- .gitignore | 1 + .../Scripts/TSX/AlarmGroup/AlarmGroup.tsx | 16 ++- .../TSX/Wizard/DynamicWizzardSlice.tsx | 2 +- .../Scripts/TSX/Wizard/GeneralSettings.tsx | 7 +- .../wwwroot/SPCTools/Scripts/TSX/global.d.ts | 4 +- Source/Data/01 - openXDA.sql | 12 ++- Source/Data/AlarmGroup-Enabled-Migration.sql | 100 ++++++++++++++++++ .../Alarms/AlarmGroups/AlarmGroup.cs | 5 + 8 files changed, 138 insertions(+), 9 deletions(-) create mode 100644 Source/Data/AlarmGroup-Enabled-Migration.sql diff --git a/.gitignore b/.gitignore index 68f6d70ed3..e0038d8c9c 100644 --- a/.gitignore +++ b/.gitignore @@ -218,3 +218,4 @@ _Pvt_Extensions # SPCTools JS File **/SPCTools/Scripts/js/SPCTools.js* +.nuget/ diff --git a/Source/Applications/openXDA/openXDA/wwwroot/SPCTools/Scripts/TSX/AlarmGroup/AlarmGroup.tsx b/Source/Applications/openXDA/openXDA/wwwroot/SPCTools/Scripts/TSX/AlarmGroup/AlarmGroup.tsx index 8c09a48219..7236eb20f9 100644 --- a/Source/Applications/openXDA/openXDA/wwwroot/SPCTools/Scripts/TSX/AlarmGroup/AlarmGroup.tsx +++ b/Source/Applications/openXDA/openXDA/wwwroot/SPCTools/Scripts/TSX/AlarmGroup/AlarmGroup.tsx @@ -51,7 +51,8 @@ const AlarmGroupHome = (props: { loadAlarm: () => void }) => { { label: 'Name', key: 'Name', type: 'string', isPivotField: false }, { label: 'Number of Meters', key: 'Meters', type: 'integer', isPivotField: true }, { label: 'Number of Channels', key: 'Channels', type: 'integer', isPivotField: true }, - { label: 'Severity', key: 'AlarmSeverity', type: 'string', isPivotField: true } + { label: 'Severity', key: 'AlarmSeverity', type: 'string', isPivotField: true }, + { label: 'Enabled', key: 'Enabled', type: 'boolean', isPivotField: false } ] const [search, setSearch] = React.useState>>([]); @@ -156,6 +157,19 @@ const AlarmGroupHome = (props: { loadAlarm: () => void }) => { > Alarm Severity + + Key={'Enabled'} + AllowSort={true} + Field={'Enabled'} + RowStyle={{ width: 'auto' }} + Content={({ item }) => ( + + {item.Enabled ? 'Enabled' : 'Disabled'} + + )} + > + Status + Key={'LastAlarmEnd'} AllowSort={true} diff --git a/Source/Applications/openXDA/openXDA/wwwroot/SPCTools/Scripts/TSX/Wizard/DynamicWizzardSlice.tsx b/Source/Applications/openXDA/openXDA/wwwroot/SPCTools/Scripts/TSX/Wizard/DynamicWizzardSlice.tsx index c0c18bf3c5..b33d714bc2 100644 --- a/Source/Applications/openXDA/openXDA/wwwroot/SPCTools/Scripts/TSX/Wizard/DynamicWizzardSlice.tsx +++ b/Source/Applications/openXDA/openXDA/wwwroot/SPCTools/Scripts/TSX/Wizard/DynamicWizzardSlice.tsx @@ -103,7 +103,7 @@ export const DynamicWizzardSlice = createSlice({ const dt = new Date(); state.Step = 'general' - state.AlarmGroup = { AlarmTypeID: 1, SeverityID: 1, ID: -1, Name: "" } + state.AlarmGroup = { AlarmTypeID: 1, SeverityID: 1, ID: -1, Name: "", Enabled: true } state.Status = 'idle' state.SelectedMeter = [] state.MeasurmentTypeID = 1 diff --git a/Source/Applications/openXDA/openXDA/wwwroot/SPCTools/Scripts/TSX/Wizard/GeneralSettings.tsx b/Source/Applications/openXDA/openXDA/wwwroot/SPCTools/Scripts/TSX/Wizard/GeneralSettings.tsx index 811cf09f47..4cd2c5423d 100644 --- a/Source/Applications/openXDA/openXDA/wwwroot/SPCTools/Scripts/TSX/Wizard/GeneralSettings.tsx +++ b/Source/Applications/openXDA/openXDA/wwwroot/SPCTools/Scripts/TSX/Wizard/GeneralSettings.tsx @@ -25,7 +25,7 @@ import * as React from 'react'; import { SearchBar, LoadingIcon } from '@gpa-gemstone/react-interactive' import { SPCTools, openXDA, Filter } from '../global'; import { SelectTable, ReactTable } from '@gpa-gemstone/react-table'; -import { Input, Select, ArrayCheckBoxes } from '@gpa-gemstone/react-forms'; +import { Input, Select, ArrayCheckBoxes, CheckBox } from '@gpa-gemstone/react-forms'; import { updateAlarmGroup, selectSelectedMeter, selectSelectedMeterASC, selectSelectedMeterSort, sortSelectedMeters, removeMeter, addMeter, selectMeasurmentTypeID, updateMeasurmentTypeID, selectAlarmGroup, selectSeriesTypeID, updateSeriesTypeID, updateAlarmDayGroupID, selectAlarmDayGroupID, SelectWizardType } from './DynamicWizzardSlice' import { useSelector, useDispatch } from 'react-redux'; import { SelectMeasurmentTypes } from '../store/MeasurmentTypeSlice'; @@ -80,6 +80,11 @@ const GeneralSettings = () => { Valid={() => (group.Name != undefined && group.Name.length > 0 ? true : false)} />
+ + Record={group} + Field={'Enabled'} + Setter={(r) => dispatch(updateAlarmGroup(r))} + Label={'Enabled'} />
diff --git a/Source/Applications/openXDA/openXDA/wwwroot/SPCTools/Scripts/TSX/global.d.ts b/Source/Applications/openXDA/openXDA/wwwroot/SPCTools/Scripts/TSX/global.d.ts index 2966439544..0b907ba46e 100644 --- a/Source/Applications/openXDA/openXDA/wwwroot/SPCTools/Scripts/TSX/global.d.ts +++ b/Source/Applications/openXDA/openXDA/wwwroot/SPCTools/Scripts/TSX/global.d.ts @@ -89,7 +89,7 @@ export namespace SPCTools { //r export type IntervallDataType = ('Minimum' | 'Maximum' | 'Average'); - export interface IAlarmGroupView { ID: number, Name: string, Channels: number, Meters: number, AlarmSeverity: string, LastAlarmStart: string, LastAlarmEnd: string, LastChannel: string, LastMeter: string, AlarmType: string } + export interface IAlarmGroupView { ID: number, Name: string, Channels: number, Meters: number, AlarmSeverity: string, LastAlarmStart: string, LastAlarmEnd: string, LastChannel: string, LastMeter: string, AlarmType: string, Enabled: boolean } export interface IChannelOverview { ID: number, Meter: string, Channel: string, Type: string, Phase: string, Asset: string } @@ -97,7 +97,7 @@ export namespace SPCTools { export interface IChannelAlarmGroup { ID: number, ChannelID: number, Name: string, AlarmSeverityID: number, AlarmSeverity: string, TimeInAlarm: string } export interface IMeterAlarmGroup { ID: number, Channel: number, Name: string, AlarmSeverity: string, TimeInAlarm: string } - export interface IAlarmGroup { ID: number, Name: string, AlarmTypeID: number, SeverityID: number } + export interface IAlarmGroup { ID: number, Name: string, AlarmTypeID: number, SeverityID: number, Enabled: boolean } //r export interface IStatisticData { StartDate: string, EndDate: string, DataFilter: IDataFilter } diff --git a/Source/Data/01 - openXDA.sql b/Source/Data/01 - openXDA.sql index 5850d82657..0451daa036 100644 --- a/Source/Data/01 - openXDA.sql +++ b/Source/Data/01 - openXDA.sql @@ -3887,7 +3887,8 @@ CREATE TABLE AlarmGroup ID INT IDENTITY(1, 1) NOT NULL PRIMARY KEY, Name VARCHAR(200) NOT NULL, AlarmTypeID INT NOT NULL REFERENCES AlarmType(ID), - SeverityID INT NOT NULL REFERENCES AlarmSeverity(ID) + SeverityID INT NOT NULL REFERENCES AlarmSeverity(ID), + Enabled BIT NOT NULL DEFAULT(1) ) GO @@ -4088,7 +4089,8 @@ SELECT LastAlarm.EndTime LastAlarmEnd, LastAlarm.ChannelName LastChannel, LastAlarm.MeterName LastMeter, - AlarmType.Description AS AlarmType + AlarmType.Description AS AlarmType, + AlarmGroup.Enabled FROM AlarmGroup LEFT JOIN AlarmSeverity ON AlarmGroup.SeverityID = AlarmSeverity.ID LEFT JOIN @@ -4199,8 +4201,9 @@ SELECT AlarmFactor.Factor AS Value FROM ( - SELECT ID, Factor, AlarmGroupID, SeverityID - FROM AlarmFactor + SELECT AF.ID, AF.Factor, AF.AlarmGroupID, AF.SeverityID + FROM AlarmFactor AF + INNER JOIN AlarmGroup AG ON AF.AlarmGroupID = AG.ID AND AG.Enabled = 1 UNION SELECT NULL AS ID, @@ -4208,6 +4211,7 @@ FROM AlarmGroup.ID AS AlarmGroupID, AlarmGroup.SeverityID FROM AlarmGroup + WHERE AlarmGroup.Enabled = 1 ) AlarmFactor LEFT JOIN Alarm ON AlarmFactor.AlarmGroupID = alarm.AlarmGroupID LEFT JOIN AlarmGroup ON Alarm.AlarmGroupID = AlarmGroup.ID diff --git a/Source/Data/AlarmGroup-Enabled-Migration.sql b/Source/Data/AlarmGroup-Enabled-Migration.sql new file mode 100644 index 0000000000..f977d5423d --- /dev/null +++ b/Source/Data/AlarmGroup-Enabled-Migration.sql @@ -0,0 +1,100 @@ +-- Migration: Add Enabled flag to AlarmGroup +-- This script adds the Enabled column to the AlarmGroup table and updates +-- the ActiveAlarmView and AlarmGroupView to reflect the new column. +-- Existing rows default to Enabled = 1 (true) to preserve existing behavior. + +-- Add Enabled column to AlarmGroup if it does not already exist +IF NOT EXISTS ( + SELECT 1 + FROM sys.columns + WHERE object_id = OBJECT_ID('AlarmGroup') + AND name = 'Enabled' +) +BEGIN + ALTER TABLE AlarmGroup + ADD Enabled BIT NOT NULL DEFAULT(1) +END +GO + +-- Drop and recreate ActiveAlarmView to filter disabled AlarmGroups +IF OBJECT_ID('ActiveAlarmView', 'V') IS NOT NULL + DROP VIEW ActiveAlarmView +GO + +CREATE VIEW ActiveAlarmView AS +SELECT + Alarm.ID AS AlarmID, + Alarm.AlarmGroupID AS AlarmGroupID, + AlarmGroup.AlarmTypeID AS AlarmTypeID, + AlarmFactor.ID AS AlarmFactorID, + AlarmFactor.SeverityID, + Alarm.SeriesID AS SeriesID, + AlarmFactor.Factor AS Value +FROM + ( + SELECT AF.ID, AF.Factor, AF.AlarmGroupID, AF.SeverityID + FROM AlarmFactor AF + INNER JOIN AlarmGroup AG ON AF.AlarmGroupID = AG.ID AND AG.Enabled = 1 + UNION + SELECT + NULL AS ID, + 1.0 AS Factor, + AlarmGroup.ID AS AlarmGroupID, + AlarmGroup.SeverityID + FROM AlarmGroup + WHERE AlarmGroup.Enabled = 1 + ) AlarmFactor LEFT JOIN + Alarm ON AlarmFactor.AlarmGroupID = alarm.AlarmGroupID LEFT JOIN + AlarmGroup ON Alarm.AlarmGroupID = AlarmGroup.ID +GO + +-- Drop and recreate AlarmGroupView to include Enabled column +IF OBJECT_ID('AlarmGroupView', 'V') IS NOT NULL + DROP VIEW AlarmGroupView +GO + +CREATE VIEW AlarmGroupView AS +SELECT + AlarmGroup.ID, + AlarmGroup.Name, + AlarmSeverity.Name AlarmSeverity, + CountStats.ChannelCount Channels, + CountStats.MeterCount Meters, + LastAlarm.StartTime LastAlarmStart, + LastAlarm.EndTime LastAlarmEnd, + LastAlarm.ChannelName LastChannel, + LastAlarm.MeterName LastMeter, + AlarmType.Description AS AlarmType, + AlarmGroup.Enabled +FROM + AlarmGroup LEFT JOIN + AlarmSeverity ON AlarmGroup.SeverityID = AlarmSeverity.ID LEFT JOIN + AlarmType ON AlarmGroup.AlarmTypeID = AlarmType.ID OUTER APPLY + ( + SELECT + COUNT(DISTINCT Channel.ID) ChannelCount, + COUNT(DISTINCT Channel.MeterID) MeterCount + FROM + Channel JOIN + Series ON Series.ChannelID = Channel.ID JOIN + Alarm ON Alarm.SeriesID = Series.ID + WHERE Alarm.AlarmGroupID = AlarmGroup.ID + ) CountStats OUTER APPLY + ( + SELECT TOP 1 + LatestAlarmLog.StartTime, + LatestAlarmLog.EndTime, + Channel.Name ChannelName, + Meter.Name MeterName + FROM + Alarm JOIN + Series ON Alarm.SeriesID = Series.ID JOIN + Channel ON Series.ChannelID = Channel.ID JOIN + Meter ON Channel.MeterID = Meter.ID JOIN + LatestAlarmLog ON LatestAlarmLog.AlarmID = Alarm.ID + WHERE Alarm.AlarmGroupID = AlarmGroup.ID + ORDER BY + LatestAlarmLog.StartTime DESC, + LatestAlarmLog.AlarmLogID DESC + ) LastAlarm +GO diff --git a/Source/Libraries/openXDA.Model/Alarms/AlarmGroups/AlarmGroup.cs b/Source/Libraries/openXDA.Model/Alarms/AlarmGroups/AlarmGroup.cs index 7ca5739ff1..4f7d88b2c8 100644 --- a/Source/Libraries/openXDA.Model/Alarms/AlarmGroups/AlarmGroup.cs +++ b/Source/Libraries/openXDA.Model/Alarms/AlarmGroups/AlarmGroup.cs @@ -22,6 +22,7 @@ //****************************************************************************************************** using System; +using System.ComponentModel; using System.ComponentModel.DataAnnotations; using GSF.Data.Model; @@ -41,6 +42,9 @@ public class AlarmGroup public int AlarmTypeID { get; set; } public int SeverityID { get; set; } + + [DefaultValue(true)] + public bool Enabled { get; set; } } /// @@ -60,6 +64,7 @@ public class AlarmGroupView public string LastChannel { get; set; } public string LastMeter { get; set; } public string AlarmType { get; set; } + public bool Enabled { get; set; } } } From 3aa33fc0faa552dfc23276f7c4526b60d8e47391 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Mar 2026 23:07:13 +0000 Subject: [PATCH 2/3] Remove AlarmGroup-Enabled-Migration.sql; migration provided as PR comment Co-authored-by: clackner-gpa <52460212+clackner-gpa@users.noreply.github.com> Agent-Logs-Url: https://github.com/GridProtectionAlliance/openXDA/sessions/00a7e9c8-0c77-45e4-a0a9-4079d3ee589b --- Source/Data/AlarmGroup-Enabled-Migration.sql | 100 ------------------- 1 file changed, 100 deletions(-) delete mode 100644 Source/Data/AlarmGroup-Enabled-Migration.sql diff --git a/Source/Data/AlarmGroup-Enabled-Migration.sql b/Source/Data/AlarmGroup-Enabled-Migration.sql deleted file mode 100644 index f977d5423d..0000000000 --- a/Source/Data/AlarmGroup-Enabled-Migration.sql +++ /dev/null @@ -1,100 +0,0 @@ --- Migration: Add Enabled flag to AlarmGroup --- This script adds the Enabled column to the AlarmGroup table and updates --- the ActiveAlarmView and AlarmGroupView to reflect the new column. --- Existing rows default to Enabled = 1 (true) to preserve existing behavior. - --- Add Enabled column to AlarmGroup if it does not already exist -IF NOT EXISTS ( - SELECT 1 - FROM sys.columns - WHERE object_id = OBJECT_ID('AlarmGroup') - AND name = 'Enabled' -) -BEGIN - ALTER TABLE AlarmGroup - ADD Enabled BIT NOT NULL DEFAULT(1) -END -GO - --- Drop and recreate ActiveAlarmView to filter disabled AlarmGroups -IF OBJECT_ID('ActiveAlarmView', 'V') IS NOT NULL - DROP VIEW ActiveAlarmView -GO - -CREATE VIEW ActiveAlarmView AS -SELECT - Alarm.ID AS AlarmID, - Alarm.AlarmGroupID AS AlarmGroupID, - AlarmGroup.AlarmTypeID AS AlarmTypeID, - AlarmFactor.ID AS AlarmFactorID, - AlarmFactor.SeverityID, - Alarm.SeriesID AS SeriesID, - AlarmFactor.Factor AS Value -FROM - ( - SELECT AF.ID, AF.Factor, AF.AlarmGroupID, AF.SeverityID - FROM AlarmFactor AF - INNER JOIN AlarmGroup AG ON AF.AlarmGroupID = AG.ID AND AG.Enabled = 1 - UNION - SELECT - NULL AS ID, - 1.0 AS Factor, - AlarmGroup.ID AS AlarmGroupID, - AlarmGroup.SeverityID - FROM AlarmGroup - WHERE AlarmGroup.Enabled = 1 - ) AlarmFactor LEFT JOIN - Alarm ON AlarmFactor.AlarmGroupID = alarm.AlarmGroupID LEFT JOIN - AlarmGroup ON Alarm.AlarmGroupID = AlarmGroup.ID -GO - --- Drop and recreate AlarmGroupView to include Enabled column -IF OBJECT_ID('AlarmGroupView', 'V') IS NOT NULL - DROP VIEW AlarmGroupView -GO - -CREATE VIEW AlarmGroupView AS -SELECT - AlarmGroup.ID, - AlarmGroup.Name, - AlarmSeverity.Name AlarmSeverity, - CountStats.ChannelCount Channels, - CountStats.MeterCount Meters, - LastAlarm.StartTime LastAlarmStart, - LastAlarm.EndTime LastAlarmEnd, - LastAlarm.ChannelName LastChannel, - LastAlarm.MeterName LastMeter, - AlarmType.Description AS AlarmType, - AlarmGroup.Enabled -FROM - AlarmGroup LEFT JOIN - AlarmSeverity ON AlarmGroup.SeverityID = AlarmSeverity.ID LEFT JOIN - AlarmType ON AlarmGroup.AlarmTypeID = AlarmType.ID OUTER APPLY - ( - SELECT - COUNT(DISTINCT Channel.ID) ChannelCount, - COUNT(DISTINCT Channel.MeterID) MeterCount - FROM - Channel JOIN - Series ON Series.ChannelID = Channel.ID JOIN - Alarm ON Alarm.SeriesID = Series.ID - WHERE Alarm.AlarmGroupID = AlarmGroup.ID - ) CountStats OUTER APPLY - ( - SELECT TOP 1 - LatestAlarmLog.StartTime, - LatestAlarmLog.EndTime, - Channel.Name ChannelName, - Meter.Name MeterName - FROM - Alarm JOIN - Series ON Alarm.SeriesID = Series.ID JOIN - Channel ON Series.ChannelID = Channel.ID JOIN - Meter ON Channel.MeterID = Meter.ID JOIN - LatestAlarmLog ON LatestAlarmLog.AlarmID = Alarm.ID - WHERE Alarm.AlarmGroupID = AlarmGroup.ID - ORDER BY - LatestAlarmLog.StartTime DESC, - LatestAlarmLog.AlarmLogID DESC - ) LastAlarm -GO From 6f18ee8f9a148425c445f6e7693df77a6b78286c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Mar 2026 23:14:24 +0000 Subject: [PATCH 3/3] Add quick-save button to WizardHome for edit mode (btn-success, left of Next) Co-authored-by: clackner-gpa <52460212+clackner-gpa@users.noreply.github.com> Agent-Logs-Url: https://github.com/GridProtectionAlliance/openXDA/sessions/bad3bfe2-e021-4f1b-a406-78811ea47a03 --- .../SPCTools/Scripts/TSX/Wizard/WizardHome.tsx | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/Source/Applications/openXDA/openXDA/wwwroot/SPCTools/Scripts/TSX/Wizard/WizardHome.tsx b/Source/Applications/openXDA/openXDA/wwwroot/SPCTools/Scripts/TSX/Wizard/WizardHome.tsx index e5458b536b..222854de94 100644 --- a/Source/Applications/openXDA/openXDA/wwwroot/SPCTools/Scripts/TSX/Wizard/WizardHome.tsx +++ b/Source/Applications/openXDA/openXDA/wwwroot/SPCTools/Scripts/TSX/Wizard/WizardHome.tsx @@ -24,7 +24,7 @@ import * as React from 'react'; import GeneralSettings from './GeneralSettings'; -import { selectStatus, next, back, selectTab, selectErrors, SaveWizard, selectWizardEror, ResetWizzard } from './DynamicWizzardSlice' +import { selectStatus, next, back, selectTab, selectErrors, SaveWizard, selectWizardEror, ResetWizzard, selectAlarmGroup } from './DynamicWizzardSlice' import { useSelector, useDispatch } from 'react-redux'; import { ToolTip, ProgressBar, ServerErrorIcon, LoadingIcon } from '@gpa-gemstone/react-interactive'; @@ -47,8 +47,12 @@ const WizardHome = (props: IProps) => { const dispatch = useDispatch(); const errors = useSelector(selectErrors); const wizardError = useSelector(selectWizardEror); + const alarmGroup = useSelector(selectAlarmGroup); + + const isEditMode = alarmGroup != null && alarmGroup.ID != null && alarmGroup.ID !== -1; const [hover, setHover] = React.useState(false); + const [hoverSave, setHoverSave] = React.useState(false); // Define Step Numbers @@ -116,6 +120,13 @@ const WizardHome = (props: IProps) => {
+ {isEditMode && tab !== 'test' ? + + : null}
);