Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions src/PlanViewer.App/Controls/ColumnFilterPopup.axaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="PlanViewer.App.Controls.ColumnFilterPopup"
Width="220">
<Border Background="{DynamicResource BackgroundDarkBrush}"
BorderBrush="{DynamicResource BorderBrush}"
BorderThickness="1" CornerRadius="6">
<StackPanel Margin="12" Spacing="4">
<TextBlock x:Name="HeaderText" Text="Filter Column"
FontWeight="SemiBold" FontSize="13" Margin="0,0,0,8"
Foreground="{DynamicResource ForegroundBrush}"/>
<TextBlock Text="Operator:" FontSize="11" Margin="0,0,0,2"
Foreground="{DynamicResource ForegroundBrush}"/>
<ComboBox x:Name="OperatorComboBox" HorizontalAlignment="Stretch"
Height="28" FontSize="12" Margin="0,0,0,8"
SelectionChanged="OperatorComboBox_SelectionChanged"/>
<TextBlock x:Name="ValueLabel" Text="Value:" FontSize="11" Margin="0,0,0,2"
Foreground="{DynamicResource ForegroundBrush}"/>
<TextBox x:Name="ValueTextBox" HorizontalAlignment="Stretch"
Height="28" FontSize="12" Margin="0,0,0,12"
KeyDown="ValueTextBox_KeyDown"/>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Spacing="8">
<Button Content="Clear" Width="65" Height="28"
Click="ClearButton_Click"
Theme="{StaticResource AppButton}"/>
<Button Content="Apply" Width="65" Height="28"
Click="ApplyButton_Click"
Theme="{StaticResource AppButton}"/>
</StackPanel>
</StackPanel>
</Border>
</UserControl>
113 changes: 113 additions & 0 deletions src/PlanViewer.App/Controls/ColumnFilterPopup.axaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
using System;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;

namespace PlanViewer.App.Controls;

public partial class ColumnFilterPopup : UserControl
{
public event EventHandler<FilterAppliedEventArgs>? FilterApplied;
public event EventHandler? FilterCleared;

private string _currentColumnName = "";

private static readonly (string Display, FilterOperator Op)[] Operators =
[
("Contains", FilterOperator.Contains),
("Equals (=)", FilterOperator.Equals),
("Not Equals (!=)", FilterOperator.NotEquals),
("Starts With", FilterOperator.StartsWith),
("Ends With", FilterOperator.EndsWith),
("Greater Than (>)", FilterOperator.GreaterThan),
("Greater or Equal (>=)", FilterOperator.GreaterThanOrEqual),
("Less Than (<)", FilterOperator.LessThan),
("Less or Equal (<=)", FilterOperator.LessThanOrEqual),
("Is Empty", FilterOperator.IsEmpty),
("Is Not Empty", FilterOperator.IsNotEmpty),
];

public ColumnFilterPopup()
{
InitializeComponent();
foreach (var (display, _) in Operators)
OperatorComboBox.Items.Add(display);
OperatorComboBox.SelectedIndex = 0;
}

public void Initialize(string columnName, ColumnFilterState? existingFilter)
{
_currentColumnName = columnName;
HeaderText.Text = $"Filter: {columnName}";

if (existingFilter?.IsActive == true)
{
var idx = Array.FindIndex(Operators, o => o.Op == existingFilter.Operator);
OperatorComboBox.SelectedIndex = idx >= 0 ? idx : 0;
ValueTextBox.Text = existingFilter.Value;
}
else
{
OperatorComboBox.SelectedIndex = 0;
ValueTextBox.Text = "";
}

UpdateValueVisibility();
ValueTextBox.Focus();
}

private void UpdateValueVisibility()
{
var idx = OperatorComboBox.SelectedIndex;
var op = (idx >= 0 && idx < Operators.Length) ? Operators[idx].Op : FilterOperator.Contains;
var showValue = op != FilterOperator.IsEmpty && op != FilterOperator.IsNotEmpty;
ValueLabel.IsVisible = showValue;
ValueTextBox.IsVisible = showValue;
}

private void OperatorComboBox_SelectionChanged(object? sender, SelectionChangedEventArgs e)
{
UpdateValueVisibility();
}

private void ApplyFilter()
{
var idx = OperatorComboBox.SelectedIndex;
if (idx < 0 || idx >= Operators.Length) return;

FilterApplied?.Invoke(this, new FilterAppliedEventArgs
{
FilterState = new ColumnFilterState
{
ColumnName = _currentColumnName,
Operator = Operators[idx].Op,
Value = ValueTextBox.Text ?? "",
}
});
}

private void ApplyButton_Click(object? sender, RoutedEventArgs e) => ApplyFilter();

private void ClearButton_Click(object? sender, RoutedEventArgs e)
{
FilterApplied?.Invoke(this, new FilterAppliedEventArgs
{
FilterState = new ColumnFilterState { ColumnName = _currentColumnName }
});
FilterCleared?.Invoke(this, EventArgs.Empty);
}

private void ValueTextBox_KeyDown(object? sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
ApplyFilter();
e.Handled = true;
}
else if (e.Key == Key.Escape)
{
FilterCleared?.Invoke(this, EventArgs.Empty);
e.Handled = true;
}
}
}
59 changes: 59 additions & 0 deletions src/PlanViewer.App/Controls/ColumnFilterState.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using System;

namespace PlanViewer.App.Controls;

public enum FilterOperator
{
Contains,
Equals,
NotEquals,
StartsWith,
EndsWith,
GreaterThan,
GreaterThanOrEqual,
LessThan,
LessThanOrEqual,
IsEmpty,
IsNotEmpty,
}

public class ColumnFilterState
{
public string ColumnName { get; set; } = string.Empty;
public FilterOperator Operator { get; set; } = FilterOperator.Contains;
public string Value { get; set; } = string.Empty;

public bool IsActive =>
!string.IsNullOrEmpty(Value) ||
Operator == FilterOperator.IsEmpty ||
Operator == FilterOperator.IsNotEmpty;

public string DisplayText
{
get
{
if (!IsActive) return string.Empty;

return Operator switch
{
FilterOperator.Contains => $"Contains '{Value}'",
FilterOperator.Equals => $"= '{Value}'",
FilterOperator.NotEquals => $"!= '{Value}'",
FilterOperator.GreaterThan => $"> {Value}",
FilterOperator.GreaterThanOrEqual => $">= {Value}",
FilterOperator.LessThan => $"< {Value}",
FilterOperator.LessThanOrEqual => $"<= {Value}",
FilterOperator.StartsWith => $"Starts with '{Value}'",
FilterOperator.EndsWith => $"Ends with '{Value}'",
FilterOperator.IsEmpty => "Is Empty",
FilterOperator.IsNotEmpty => "Is Not Empty",
_ => Value,
};
}
}
}

public class FilterAppliedEventArgs : EventArgs
{
public ColumnFilterState FilterState { get; set; } = new ColumnFilterState();
}
33 changes: 17 additions & 16 deletions src/PlanViewer.App/Controls/QueryStoreGridControl.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -70,15 +70,16 @@
<DataGrid Grid.Row="1" x:Name="ResultsGrid"
AutoGenerateColumns="False"
CanUserSortColumns="True"
CanUserReorderColumns="False"
CanUserReorderColumns="True"
CanUserResizeColumns="True"
IsReadOnly="False"
SelectionMode="Single"
GridLinesVisibility="Horizontal"
HeadersVisibility="Column"
FontSize="11"
Background="{DynamicResource BackgroundDarkBrush}"
BorderThickness="0">
BorderThickness="0"
ScrollViewer.HorizontalScrollBarVisibility="Auto">
<DataGrid.ContextMenu>
<ContextMenu>
<MenuItem Header="Load Plan" Click="LoadHighlightedPlan_Click"/>
Expand All @@ -95,20 +96,20 @@
<DataGridTextColumn Header="QueryId" Binding="{ReflectionBinding QueryId}" Width="90"/>
<DataGridTextColumn Header="PlanId" Binding="{ReflectionBinding PlanId}" Width="80"/>
<DataGridTextColumn Header="Last Executed (Local)" Binding="{ReflectionBinding LastExecutedLocal}" Width="160"/>
<DataGridTextColumn Header="Executions" Binding="{ReflectionBinding ExecsDisplay}" Width="100"/>
<DataGridTextColumn Header="Total CPU (ms)" Binding="{ReflectionBinding TotalCpuDisplay}" Width="120"/>
<DataGridTextColumn Header="Avg CPU (ms)" Binding="{ReflectionBinding AvgCpuDisplay}" Width="110"/>
<DataGridTextColumn Header="Total Duration (ms)" Binding="{ReflectionBinding TotalDurDisplay}" Width="150"/>
<DataGridTextColumn Header="Avg Duration (ms)" Binding="{ReflectionBinding AvgDurDisplay}" Width="140"/>
<DataGridTextColumn Header="Total Reads" Binding="{ReflectionBinding TotalReadsDisplay}" Width="100"/>
<DataGridTextColumn Header="Avg Reads" Binding="{ReflectionBinding AvgReadsDisplay}" Width="100"/>
<DataGridTextColumn Header="Total Writes" Binding="{ReflectionBinding TotalWritesDisplay}" Width="100"/>
<DataGridTextColumn Header="Avg Writes" Binding="{ReflectionBinding AvgWritesDisplay}" Width="100"/>
<DataGridTextColumn Header="Total Physical Reads" Binding="{ReflectionBinding TotalPhysReadsDisplay}" Width="155"/>
<DataGridTextColumn Header="Avg Physical Reads" Binding="{ReflectionBinding AvgPhysReadsDisplay}" Width="140"/>
<DataGridTextColumn Header="Total Memory (MB)" Binding="{ReflectionBinding TotalMemDisplay}" Width="140"/>
<DataGridTextColumn Header="Avg Memory (MB)" Binding="{ReflectionBinding AvgMemDisplay}" Width="130"/>
<DataGridTemplateColumn Header="Query Text" Width="*">
<DataGridTextColumn Header="Executions" Binding="{ReflectionBinding ExecsDisplay}" SortMemberPath="ExecsSort" Width="100"/>
<DataGridTextColumn Header="Total CPU (ms)" Binding="{ReflectionBinding TotalCpuDisplay}" SortMemberPath="TotalCpuSort" Width="120"/>
<DataGridTextColumn Header="Avg CPU (ms)" Binding="{ReflectionBinding AvgCpuDisplay}" SortMemberPath="AvgCpuSort" Width="110"/>
<DataGridTextColumn Header="Total Duration (ms)" Binding="{ReflectionBinding TotalDurDisplay}" SortMemberPath="TotalDurSort" Width="150"/>
<DataGridTextColumn Header="Avg Duration (ms)" Binding="{ReflectionBinding AvgDurDisplay}" SortMemberPath="AvgDurSort" Width="140"/>
<DataGridTextColumn Header="Total Reads" Binding="{ReflectionBinding TotalReadsDisplay}" SortMemberPath="TotalReadsSort" Width="100"/>
<DataGridTextColumn Header="Avg Reads" Binding="{ReflectionBinding AvgReadsDisplay}" SortMemberPath="AvgReadsSort" Width="100"/>
<DataGridTextColumn Header="Total Writes" Binding="{ReflectionBinding TotalWritesDisplay}" SortMemberPath="TotalWritesSort" Width="100"/>
<DataGridTextColumn Header="Avg Writes" Binding="{ReflectionBinding AvgWritesDisplay}" SortMemberPath="AvgWritesSort" Width="100"/>
<DataGridTextColumn Header="Total Physical Reads" Binding="{ReflectionBinding TotalPhysReadsDisplay}" SortMemberPath="TotalPhysReadsSort" Width="155"/>
<DataGridTextColumn Header="Avg Physical Reads" Binding="{ReflectionBinding AvgPhysReadsDisplay}" SortMemberPath="AvgPhysReadsSort" Width="140"/>
<DataGridTextColumn Header="Total Memory (MB)" Binding="{ReflectionBinding TotalMemDisplay}" SortMemberPath="TotalMemSort" Width="140"/>
<DataGridTextColumn Header="Avg Memory (MB)" Binding="{ReflectionBinding AvgMemDisplay}" SortMemberPath="AvgMemSort" Width="130"/>
<DataGridTemplateColumn Header="Query Text" Width="300" MinWidth="300">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate x:DataType="local:QueryStoreRow">
<TextBlock Text="{Binding QueryPreview}"
Expand Down
Loading
Loading