From 12dee66f906df3ed50e2e7d151558d0f67694cc7 Mon Sep 17 00:00:00 2001 From: prestoncraw Date: Fri, 31 Jul 2026 12:33:27 -0400 Subject: [PATCH 1/4] Fix issue with AssetType filters --- .../TransmissionElements/AssetTypes.cs | 57 +++++++++++++++++++ .../Controllers/OpenXDA/ModelControlers.cs | 3 + .../TSX/Components/Common/DefaultSearch.tsx | 19 ++++++- .../Scripts/TSX/Store/ControllerFunctions.ts | 2 + 4 files changed, 78 insertions(+), 3 deletions(-) create mode 100644 Libraries/openXDA.Model/TransmissionElements/AssetTypes.cs diff --git a/Libraries/openXDA.Model/TransmissionElements/AssetTypes.cs b/Libraries/openXDA.Model/TransmissionElements/AssetTypes.cs new file mode 100644 index 00000000..8b16eab7 --- /dev/null +++ b/Libraries/openXDA.Model/TransmissionElements/AssetTypes.cs @@ -0,0 +1,57 @@ +//****************************************************************************************************** +// AssetTypes.cs - Gbtc +// +// Copyright © 2019, Grid Protection Alliance. All Rights Reserved. +// +// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See +// the NOTICE file distributed with this work for additional information regarding copyright ownership. +// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may +// not use this file except in compliance with the License. You may obtain a copy of the License at: +// +// http://opensource.org/licenses/MIT +// +// Unless agreed to in writing, the subject software distributed under the License is distributed on an +// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the +// License for the specific language governing permissions and limitations. +// +// Code Modification History: +// ---------------------------------------------------------------------------------------------------- +// 12/24/2019 - C. Lackner +// Generated original version of source code. +// +//****************************************************************************************************** + +using System.ComponentModel.DataAnnotations; +using Gemstone.Data.Model; + +namespace openXDA.Model +{ + public enum AssetType + { + Line = 1, + Bus = 2, + Breaker = 3, + CapacitorBank = 4, + LineSegement = 5, + Transformer = 6, + CapBankRelay = 7, + DER = 8, + StationAux = 9, + StationBattery = 10, + Generation = 11 + } + + [TableName("AssetType")] + public class AssetTypes + { + [PrimaryKey(true)] + public int ID { get; set; } + + [StringLength(50)] + [DefaultSortOrder] + public string Name { get; set; } + + [StringLength(250)] + public string Description { get; set; } + } +} diff --git a/SEBrowser/Controllers/OpenXDA/ModelControlers.cs b/SEBrowser/Controllers/OpenXDA/ModelControlers.cs index 8dfc339a..3e405503 100644 --- a/SEBrowser/Controllers/OpenXDA/ModelControlers.cs +++ b/SEBrowser/Controllers/OpenXDA/ModelControlers.cs @@ -49,6 +49,9 @@ public class EventTypeController : ReadOnlyModelController; [Route("api/openXDA/Asset")] public class OpenXDAAssetController : ReadOnlyModelController; + [Route("api/openXDA/AssetType")] + public class AssetTypeController : ReadOnlyModelController; + [Route("api/openXDA/Meter")] public class OpenXDAMeterController : ReadOnlyModelController; diff --git a/SEBrowser/Scripts/TSX/Components/Common/DefaultSearch.tsx b/SEBrowser/Scripts/TSX/Components/Common/DefaultSearch.tsx index a7161c62..ed6b602f 100644 --- a/SEBrowser/Scripts/TSX/Components/Common/DefaultSearch.tsx +++ b/SEBrowser/Scripts/TSX/Components/Common/DefaultSearch.tsx @@ -26,6 +26,7 @@ import * as React from 'react'; import { SearchBar as GenericSearchBar, Search } from '@gpa-gemstone/react-interactive'; import { OpenXDA, SystemCenter, Application, Gemstone } from '@gpa-gemstone/application-typings'; import { useStringMemonization } from '@gpa-gemstone/helper-functions'; +import { AssetTypeController } from '../../Store/ControllerFunctions'; interface IProps { @@ -103,15 +104,27 @@ export namespace DefaultSearch { /** This Implements a standard Transmission Asset Search */ export const Asset = (props: React.PropsWithChildren>) => { const [addlFieldCols, setAddlFieldCols] = React.useState[]>([]); + const [assetTypes, setAssetTypes] = React.useState([]); const addlFilters = useStringMemonization[] | undefined>(props.AddlFilters); const setFilters = useSetFilters(props.SetFilter, addlFilters); + const searchCols = React.useMemo(() => getDefaultAssetSearchCols(assetTypes), [assetTypes]); React.useEffect(() => { return props.GetAddlFields(setAddlFieldCols); }, []); + React.useEffect(() => { + const handle = AssetTypeController.GetAll('Name', true); + + handle.done(setAssetTypes); + + return () => { + if (handle.abort != null) handle.abort(); + } + }, []); + return - CollumnList={[...defaultAssetSearchCols, ...addlFieldCols]} + CollumnList={[...searchCols, ...addlFieldCols]} SetFilter={setFilters} Direction={'left'} defaultCollumn={defaultAssetSearch} @@ -225,11 +238,11 @@ const defaultLocationSearchCols: Search.IField = { label: 'Name', key: 'Name', type: 'string', isPivotField: false }; -const defaultAssetSearchCols: Search.IField[] = [ +const getDefaultAssetSearchCols = (assetTypes: OpenXDA.Types.AssetType[]): Search.IField[] => [ { label: 'Key', key: 'AssetKey', type: 'string', isPivotField: false }, { label: 'Name', key: 'AssetName', type: 'string', isPivotField: false }, { label: 'Nominal Voltage (L-L kV)', key: 'VoltageKV', type: 'number', isPivotField: false }, - { label: 'Type', key: 'AssetType', type: 'enum', isPivotField: false }, + { label: 'Type', key: 'AssetType', type: 'enum', enum: assetTypes.map(assetType => ({ Label: assetType.Name, Value: assetType.Name })), isPivotField: false }, { label: 'Meter Key', key: 'Meter', type: 'string', isPivotField: false }, { label: 'Substation Key', key: 'Location', type: 'string', isPivotField: false }, { label: 'Number of Meters', key: 'Meters', type: 'integer', isPivotField: false }, diff --git a/SEBrowser/Scripts/TSX/Store/ControllerFunctions.ts b/SEBrowser/Scripts/TSX/Store/ControllerFunctions.ts index 7673b7fa..e8e4875e 100644 --- a/SEBrowser/Scripts/TSX/Store/ControllerFunctions.ts +++ b/SEBrowser/Scripts/TSX/Store/ControllerFunctions.ts @@ -30,6 +30,7 @@ declare let homePath: string; // Controller API paths export const MeterControllerPath = `${homePath}api/OpenXDA/Meter`; export const AssetControllerPath = `${homePath}api/OpenXDA/Asset`; +export const AssetTypeControllerPath = `${homePath}api/OpenXDA/AssetType`; export const EventTypeControllerPath = `${homePath}api/OpenXDA/EventType`; export const LocationControlerPath = `${homePath}api/OpenXDA/Location`; export const AssetGroupControllerPath = `${homePath}api/openXDA/AssetGroup`; @@ -39,6 +40,7 @@ export const ChannelGroupControllerPath = `${homePath}api/openXDA/ChannelGroup`; // Shared read-only controllers - instantiated once here and reused across the app export const MeterController = new ReadOnlyControllerFunctions_Gemstone(MeterControllerPath); export const AssetController = new ReadOnlyControllerFunctions_Gemstone(AssetControllerPath); +export const AssetTypeController = new ReadOnlyControllerFunctions_Gemstone(AssetTypeControllerPath); export const LocationController = new ReadOnlyControllerFunctions_Gemstone(LocationControlerPath); export const AssetGroupController = new ReadOnlyControllerFunctions_Gemstone(AssetGroupControllerPath); export const EventTypeController = new ReadOnlyControllerFunctions_Gemstone(EventTypeControllerPath); From fda8048e1058a0318eb293238113dfb526d762da Mon Sep 17 00:00:00 2001 From: prestoncraw Date: Fri, 31 Jul 2026 12:42:14 -0400 Subject: [PATCH 2/4] move filter logic to getEnum --- .../TSX/Components/Common/DefaultSearch.tsx | 19 +++---------------- .../EventSearch/Navbar/EventSearchNavbar.tsx | 14 ++++++++++++-- .../TrendData/NavBar/TrendDataNavbar.tsx | 15 +-------------- 3 files changed, 16 insertions(+), 32 deletions(-) diff --git a/SEBrowser/Scripts/TSX/Components/Common/DefaultSearch.tsx b/SEBrowser/Scripts/TSX/Components/Common/DefaultSearch.tsx index ed6b602f..43202403 100644 --- a/SEBrowser/Scripts/TSX/Components/Common/DefaultSearch.tsx +++ b/SEBrowser/Scripts/TSX/Components/Common/DefaultSearch.tsx @@ -26,7 +26,6 @@ import * as React from 'react'; import { SearchBar as GenericSearchBar, Search } from '@gpa-gemstone/react-interactive'; import { OpenXDA, SystemCenter, Application, Gemstone } from '@gpa-gemstone/application-typings'; import { useStringMemonization } from '@gpa-gemstone/helper-functions'; -import { AssetTypeController } from '../../Store/ControllerFunctions'; interface IProps { @@ -104,27 +103,15 @@ export namespace DefaultSearch { /** This Implements a standard Transmission Asset Search */ export const Asset = (props: React.PropsWithChildren>) => { const [addlFieldCols, setAddlFieldCols] = React.useState[]>([]); - const [assetTypes, setAssetTypes] = React.useState([]); const addlFilters = useStringMemonization[] | undefined>(props.AddlFilters); const setFilters = useSetFilters(props.SetFilter, addlFilters); - const searchCols = React.useMemo(() => getDefaultAssetSearchCols(assetTypes), [assetTypes]); React.useEffect(() => { return props.GetAddlFields(setAddlFieldCols); }, []); - React.useEffect(() => { - const handle = AssetTypeController.GetAll('Name', true); - - handle.done(setAssetTypes); - - return () => { - if (handle.abort != null) handle.abort(); - } - }, []); - return - CollumnList={[...searchCols, ...addlFieldCols]} + CollumnList={[...defaultAssetSearchCols, ...addlFieldCols]} SetFilter={setFilters} Direction={'left'} defaultCollumn={defaultAssetSearch} @@ -238,11 +225,11 @@ const defaultLocationSearchCols: Search.IField = { label: 'Name', key: 'Name', type: 'string', isPivotField: false }; -const getDefaultAssetSearchCols = (assetTypes: OpenXDA.Types.AssetType[]): Search.IField[] => [ +const defaultAssetSearchCols: Search.IField[] = [ { label: 'Key', key: 'AssetKey', type: 'string', isPivotField: false }, { label: 'Name', key: 'AssetName', type: 'string', isPivotField: false }, { label: 'Nominal Voltage (L-L kV)', key: 'VoltageKV', type: 'number', isPivotField: false }, - { label: 'Type', key: 'AssetType', type: 'enum', enum: assetTypes.map(assetType => ({ Label: assetType.Name, Value: assetType.Name })), isPivotField: false }, + { label: 'Type', key: 'AssetType', type: 'enum', enum: [{ Label: 'AssetType', Value: 'AssetType' }], isPivotField: false }, { label: 'Meter Key', key: 'Meter', type: 'string', isPivotField: false }, { label: 'Substation Key', key: 'Location', type: 'string', isPivotField: false }, { label: 'Number of Meters', key: 'Meters', type: 'integer', isPivotField: false }, diff --git a/SEBrowser/Scripts/TSX/Components/EventSearch/Navbar/EventSearchNavbar.tsx b/SEBrowser/Scripts/TSX/Components/EventSearch/Navbar/EventSearchNavbar.tsx index 356c78c8..98244745 100644 --- a/SEBrowser/Scripts/TSX/Components/EventSearch/Navbar/EventSearchNavbar.tsx +++ b/SEBrowser/Scripts/TSX/Components/EventSearch/Navbar/EventSearchNavbar.tsx @@ -28,7 +28,7 @@ import { TimeFilter } from '@gpa-gemstone/common-pages'; import { useAppDispatch, useAppSelector } from '../../../hooks'; import { SelectAssetGroupList, SelectAssetList, SelectMeterList, SelectReset, SelectStationList, SelectTimeFilter, SetFilterLists } from '../../../Store/EventSearchSlice'; import { ResetFilters, SetFilters } from '../../../Store/EventSearchSlice'; -import { AssetGroupControllerPath, AssetControllerPath, LocationControlerPath, MeterControllerPath } from '../../../Store/ControllerFunctions'; +import { AssetGroupControllerPath, AssetControllerPath, AssetTypeController, LocationControlerPath, MeterControllerPath } from '../../../Store/ControllerFunctions'; import { DefaultSelects } from '../../Common/DefaultSelects'; import NavbarFilterButton from '../../Common/NavbarFilterButton'; import { SystemCenter, OpenXDA, Gemstone } from '@gpa-gemstone/application-typings'; @@ -463,10 +463,20 @@ const orderAdditionalFields = (fields: SystemCenter.Types.AdditionalField[]) ); } -const getEnum = (setOptions: (options: Gemstone.TSX.Interfaces.ILabelValue[]) => void, field: Search.IField) => { +export const getEnum = (setOptions: (options: Gemstone.TSX.Interfaces.ILabelValue[]) => void, field: Search.IField) => { if (field.type != 'enum' || field.enum == undefined || field.enum.length != 1) return () => {/*Do Nothing*/ }; + if (field.key == 'AssetType') { + const handle = AssetTypeController.GetAll('Name', true); + + handle.done(d => setOptions(d.map(item => ({ Value: item.Name, Label: item.Name })))); + + return () => { + if (handle.abort != null) handle.abort(); + } + } + const handle: JQuery.jqXHR = getValueListGroup(field.enum[0].Value); handle.done(d => setOptions(d.map(item => ({ Value: item.Value.toString(), Label: item.AltValue ?? item.Value })))) diff --git a/SEBrowser/Scripts/TSX/Components/TrendData/NavBar/TrendDataNavbar.tsx b/SEBrowser/Scripts/TSX/Components/TrendData/NavBar/TrendDataNavbar.tsx index b0e2fc5a..f2f4e486 100644 --- a/SEBrowser/Scripts/TSX/Components/TrendData/NavBar/TrendDataNavbar.tsx +++ b/SEBrowser/Scripts/TSX/Components/TrendData/NavBar/TrendDataNavbar.tsx @@ -41,7 +41,7 @@ import { FilterType, TrendChannelTableState } from './Types'; import TrendChannelFilters from './ChannelFilters'; import TrendDataNavbarButtons from './TrendDataNavbarButtons'; import { useTrendDataNavbar } from './useTrendDataNavbar'; -import { getValueListGroup, ValueListItemResponse } from '../../EventSearch/Navbar/EventSearchNavbar'; +import { getEnum } from '../../EventSearch/Navbar/EventSearchNavbar'; import { TrendChannelSelectorTutorialAlert } from '../TrendDataTutorials'; interface IProps { @@ -108,19 +108,6 @@ const TrendSearchNavbar = React.memo((props: IProps) => { const tableHeight = timeOffsetHeight > filtOffsetHeight ? timeOffsetHeight : filtOffsetHeight; - function getEnum(setOptions, field) { - if (field.type != 'enum' || field.enum == undefined || field.enum.length != 1) - return () => { /*noop */}; - - const handle: JQuery.jqXHR = getValueListGroup(field.enum[0].Value); - - handle.done(d => setOptions(d.map(item => ({ Value: item.Value.toString(), Label: item.AltValue ?? item.Value })))) - return () => { - if (handle?.abort != null) - handle.abort(); - } - } - function getAdditionalMeterFields(setFields) { const handle = getAdditionalMeterFieldNames(); From bd8346399ea816f6fb4121866b0e4b9e81c1d282 Mon Sep 17 00:00:00 2001 From: prestoncraw Date: Fri, 31 Jul 2026 16:06:51 -0400 Subject: [PATCH 3/4] update pointer --- SEBrowser/EventWidgets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SEBrowser/EventWidgets b/SEBrowser/EventWidgets index 8203aa8e..b414a37c 160000 --- a/SEBrowser/EventWidgets +++ b/SEBrowser/EventWidgets @@ -1 +1 @@ -Subproject commit 8203aa8eb88900156f81ec4df027f8bee1d0b460 +Subproject commit b414a37c073460c6a0f7471ade180fc7bd768794 From 07dbb2c71b69eb4c11f6fd14cbd91da2cee2dccd Mon Sep 17 00:00:00 2001 From: prestoncraw Date: Fri, 31 Jul 2026 16:39:51 -0400 Subject: [PATCH 4/4] Filter out Assets that are of type LineSegment --- .../TSX/Components/Common/StandardSelectPopup.tsx | 5 +++-- .../EventSearch/Navbar/EventSearchNavbar.tsx | 13 ++++++++++++- .../Components/TrendData/NavBar/TrendDataNavbar.tsx | 7 +++++++ 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/SEBrowser/Scripts/TSX/Components/Common/StandardSelectPopup.tsx b/SEBrowser/Scripts/TSX/Components/Common/StandardSelectPopup.tsx index 9a6b9ec5..daddfd86 100644 --- a/SEBrowser/Scripts/TSX/Components/Common/StandardSelectPopup.tsx +++ b/SEBrowser/Scripts/TSX/Components/Common/StandardSelectPopup.tsx @@ -38,6 +38,7 @@ interface IProps { Searchbar: (children: React.ReactNode, SetFilter: (filters: Search.IFilter[]) => void, SearchStatus: Application.Types.Status, ResultCount: number) => React.ReactNode, Type?: 'single' | 'multiple', Title: string, + AddlFilters?: Search.IFilter[], MinSelection?: number, children?: React.ReactNode } @@ -45,7 +46,7 @@ interface IProps { const SelectPopup = (props: IProps) => { const controller = React.useMemo(() => new ReadOnlyControllerFunctions_Gemstone(props.ControllerAPIPath), [props.ControllerAPIPath]); const bulkHandle = React.useRef | null>(null); - const [filters, setFilters] = React.useState[]>([]); + const [filters, setFilters] = React.useState[]>(props.AddlFilters ?? []); const [sortField, setSortField] = React.useState(props.DefaultSortField); const [ascending, setAscending] = React.useState(true); @@ -248,4 +249,4 @@ const SelectPopup = (props: IProps) => { ) } -export default SelectPopup; \ No newline at end of file +export default SelectPopup; diff --git a/SEBrowser/Scripts/TSX/Components/EventSearch/Navbar/EventSearchNavbar.tsx b/SEBrowser/Scripts/TSX/Components/EventSearch/Navbar/EventSearchNavbar.tsx index 98244745..6843b400 100644 --- a/SEBrowser/Scripts/TSX/Components/EventSearch/Navbar/EventSearchNavbar.tsx +++ b/SEBrowser/Scripts/TSX/Components/EventSearch/Navbar/EventSearchNavbar.tsx @@ -249,6 +249,13 @@ const EventSearchNavbar = () => { ', + Type: 'enum', + IsPivotColumn: false + }]} Selection={assetList} OnClose={(selected, conf) => { setFilter('None'); @@ -468,7 +475,11 @@ export const getEnum = (setOptions: (options: Gemstone.TSX.Interfaces.ILabel return () => {/*Do Nothing*/ }; if (field.key == 'AssetType') { - const handle = AssetTypeController.GetAll('Name', true); + const handle = AssetTypeController.GetAll('Name', true, [{ + FieldName: 'Name', + SearchParameter: 'LineSegment', + Operator: '<>' + }]); handle.done(d => setOptions(d.map(item => ({ Value: item.Name, Label: item.Name })))); diff --git a/SEBrowser/Scripts/TSX/Components/TrendData/NavBar/TrendDataNavbar.tsx b/SEBrowser/Scripts/TSX/Components/TrendData/NavBar/TrendDataNavbar.tsx index f2f4e486..aaf5fdcd 100644 --- a/SEBrowser/Scripts/TSX/Components/TrendData/NavBar/TrendDataNavbar.tsx +++ b/SEBrowser/Scripts/TSX/Components/TrendData/NavBar/TrendDataNavbar.tsx @@ -313,6 +313,13 @@ const TrendSearchNavbar = React.memo((props: IProps) => { ', + Type: 'enum', + IsPivotColumn: false + }]} Selection={trendFilter?.AssetList ?? []} OnClose={(selected, conf) => { setShowFilter('None');