From b612ead43ccf2c55725d1dabfd7b3789cc13df88 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Fri, 27 Feb 2026 11:06:09 -0500 Subject: [PATCH] Support comma-separated values in text column filters (#348) Text filter operators (Contains, Equals, NotEquals, StartsWith, EndsWith) now accept comma-separated values. Contains/Equals/StartsWith/EndsWith match ANY term (OR logic), NotEquals excludes ALL terms (AND logic). e.g., NotEquals "db1, db2" excludes both databases. Applied to both Dashboard and Lite popup filters. Co-Authored-By: Claude Opus 4.6 --- Dashboard/Services/DataGridFilterService.cs | 21 ++++++++++++++------ Lite/Services/DataGridFilterService.cs | 22 +++++++++++++++------ 2 files changed, 31 insertions(+), 12 deletions(-) diff --git a/Dashboard/Services/DataGridFilterService.cs b/Dashboard/Services/DataGridFilterService.cs index 13e52939..f524697e 100644 --- a/Dashboard/Services/DataGridFilterService.cs +++ b/Dashboard/Services/DataGridFilterService.cs @@ -118,18 +118,27 @@ public static bool MatchesFilter(object item, ColumnFilterState filter) var stringValue = rawValue?.ToString() ?? string.Empty; var filterValue = filter.Value ?? string.Empty; - // Handle comparison operators + // Split comma-separated values for text operators (e.g., "db1, db2") + var terms = filterValue.Split(',') + .Select(t => t.Trim()) + .Where(t => !string.IsNullOrEmpty(t)) + .ToArray(); + + if (terms.Length == 0) + return true; + + // Text operators match ANY term, NotEquals excludes ALL terms return filter.Operator switch { - FilterOperator.Contains => stringValue.Contains(filterValue, StringComparison.OrdinalIgnoreCase), - FilterOperator.Equals => CompareValues(rawValue, filterValue, (a, b) => a == b), - FilterOperator.NotEquals => CompareValues(rawValue, filterValue, (a, b) => a != b), + FilterOperator.Contains => terms.Any(t => stringValue.Contains(t, StringComparison.OrdinalIgnoreCase)), + FilterOperator.Equals => terms.Any(t => CompareValues(rawValue, t, (a, b) => a == b)), + FilterOperator.NotEquals => terms.All(t => CompareValues(rawValue, t, (a, b) => a != b)), FilterOperator.GreaterThan => CompareNumeric(rawValue, filterValue, (a, b) => a > b), FilterOperator.GreaterThanOrEqual => CompareNumeric(rawValue, filterValue, (a, b) => a >= b), FilterOperator.LessThan => CompareNumeric(rawValue, filterValue, (a, b) => a < b), FilterOperator.LessThanOrEqual => CompareNumeric(rawValue, filterValue, (a, b) => a <= b), - FilterOperator.StartsWith => stringValue.StartsWith(filterValue, StringComparison.OrdinalIgnoreCase), - FilterOperator.EndsWith => stringValue.EndsWith(filterValue, StringComparison.OrdinalIgnoreCase), + FilterOperator.StartsWith => terms.Any(t => stringValue.StartsWith(t, StringComparison.OrdinalIgnoreCase)), + FilterOperator.EndsWith => terms.Any(t => stringValue.EndsWith(t, StringComparison.OrdinalIgnoreCase)), _ => true }; } diff --git a/Lite/Services/DataGridFilterService.cs b/Lite/Services/DataGridFilterService.cs index 38e07bc5..409e13c1 100644 --- a/Lite/Services/DataGridFilterService.cs +++ b/Lite/Services/DataGridFilterService.cs @@ -8,6 +8,7 @@ using System; using System.Globalization; +using System.Linq; using PerformanceMonitorLite.Models; namespace PerformanceMonitorLite.Services; @@ -40,18 +41,27 @@ public static bool MatchesFilter(object item, ColumnFilterState filter) var stringValue = rawValue?.ToString() ?? string.Empty; var filterValue = filter.Value ?? string.Empty; - /* Handle comparison operators */ + /* Split comma-separated values for text operators (e.g., "db1, db2") */ + var terms = filterValue.Split(',') + .Select(t => t.Trim()) + .Where(t => !string.IsNullOrEmpty(t)) + .ToArray(); + + if (terms.Length == 0) + return true; + + /* Text operators match ANY term, NotEquals excludes ALL terms */ return filter.Operator switch { - FilterOperator.Contains => stringValue.Contains(filterValue, StringComparison.OrdinalIgnoreCase), - FilterOperator.Equals => CompareValues(rawValue, filterValue, (a, b) => a == b), - FilterOperator.NotEquals => CompareValues(rawValue, filterValue, (a, b) => a != b), + FilterOperator.Contains => terms.Any(t => stringValue.Contains(t, StringComparison.OrdinalIgnoreCase)), + FilterOperator.Equals => terms.Any(t => CompareValues(rawValue, t, (a, b) => a == b)), + FilterOperator.NotEquals => terms.All(t => CompareValues(rawValue, t, (a, b) => a != b)), FilterOperator.GreaterThan => CompareNumeric(rawValue, filterValue, (a, b) => a > b), FilterOperator.GreaterThanOrEqual => CompareNumeric(rawValue, filterValue, (a, b) => a >= b), FilterOperator.LessThan => CompareNumeric(rawValue, filterValue, (a, b) => a < b), FilterOperator.LessThanOrEqual => CompareNumeric(rawValue, filterValue, (a, b) => a <= b), - FilterOperator.StartsWith => stringValue.StartsWith(filterValue, StringComparison.OrdinalIgnoreCase), - FilterOperator.EndsWith => stringValue.EndsWith(filterValue, StringComparison.OrdinalIgnoreCase), + FilterOperator.StartsWith => terms.Any(t => stringValue.StartsWith(t, StringComparison.OrdinalIgnoreCase)), + FilterOperator.EndsWith => terms.Any(t => stringValue.EndsWith(t, StringComparison.OrdinalIgnoreCase)), _ => true }; }