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
74 changes: 2 additions & 72 deletions src/PlanViewer.App/Controls/QuerySessionControl.Execution.cs
Original file line number Diff line number Diff line change
Expand Up @@ -404,78 +404,8 @@ private async void GetActualPlan_Click(object? sender, RoutedEventArgs e)
/// <summary>
/// Shows a modal confirmation dialog and returns true if the user clicked OK.
/// </summary>
private async Task<bool> ShowConfirmationDialog(string title, string message)
{
var result = false;

var messageText = new TextBlock
{
Text = message,
TextWrapping = TextWrapping.Wrap,
FontSize = 13,
Foreground = new SolidColorBrush(Color.Parse("#E4E6EB")),
Margin = new Avalonia.Thickness(0, 0, 0, 16)
};

var okBtn = new Button
{
Content = "OK",
Height = 32,
Width = 80,
Padding = new Avalonia.Thickness(16, 0),
FontSize = 12,
HorizontalContentAlignment = HorizontalAlignment.Center,
VerticalContentAlignment = VerticalAlignment.Center,
Theme = (Avalonia.Styling.ControlTheme)this.FindResource("AppButton")!
};

var cancelBtn = new Button
{
Content = "Cancel",
Height = 32,
Width = 80,
Padding = new Avalonia.Thickness(16, 0),
FontSize = 12,
Margin = new Avalonia.Thickness(8, 0, 0, 0),
HorizontalContentAlignment = HorizontalAlignment.Center,
VerticalContentAlignment = VerticalAlignment.Center,
Theme = (Avalonia.Styling.ControlTheme)this.FindResource("AppButton")!
};

var buttonPanel = new StackPanel
{
Orientation = Avalonia.Layout.Orientation.Horizontal,
HorizontalAlignment = HorizontalAlignment.Right
};
buttonPanel.Children.Add(okBtn);
buttonPanel.Children.Add(cancelBtn);

var content = new StackPanel
{
Margin = new Avalonia.Thickness(20),
Children = { messageText, buttonPanel }
};

var dialog = new Window
{
Title = title,
Width = 420,
Height = 200,
MinWidth = 420,
MinHeight = 200,
Icon = GetParentWindow().Icon,
Background = new SolidColorBrush(Color.Parse("#1A1D23")),
Foreground = new SolidColorBrush(Color.Parse("#E4E6EB")),
Content = content,
WindowStartupLocation = WindowStartupLocation.CenterOwner
};

okBtn.Click += (_, _) => { result = true; dialog.Close(); };
cancelBtn.Click += (_, _) => dialog.Close();

await dialog.ShowDialog(GetParentWindow());
return result;
}
private Task<bool> ShowConfirmationDialog(string title, string message)
=> Dialogs.ConfirmationDialog.ShowAsync(GetParentWindow(), title, message);

/// <summary>
/// Extracts the database name from plan XML's StmtSimple DatabaseContext attribute.
Expand Down
88 changes: 88 additions & 0 deletions src/PlanViewer.App/Dialogs/ConfirmationDialog.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Layout;
using Avalonia.Media;

namespace PlanViewer.App.Dialogs;

/// <summary>
/// Modal yes/no confirmation. Shared rather than per-control: this dialog gates
/// executing SQL against a live server, and it is reached from both the query
/// session and a loaded plan file. Two copies would be free to drift, which is
/// exactly how one of those two paths ended up with no confirmation at all.
/// </summary>
public static class ConfirmationDialog
{
public static async Task<bool> ShowAsync(Window owner, string title, string message)
{
var result = false;

var messageText = new TextBlock
{
Text = message,
TextWrapping = TextWrapping.Wrap,
FontSize = 13,
Foreground = new SolidColorBrush(Color.Parse("#E4E6EB")),
Margin = new Avalonia.Thickness(0, 0, 0, 16)
};

var okBtn = new Button
{
Content = "OK",
Height = 32,
Width = 80,
Padding = new Avalonia.Thickness(16, 0),
FontSize = 12,
HorizontalContentAlignment = HorizontalAlignment.Center,
VerticalContentAlignment = VerticalAlignment.Center,
Theme = (Avalonia.Styling.ControlTheme)owner.FindResource("AppButton")!
};

var cancelBtn = new Button
{
Content = "Cancel",
Height = 32,
Width = 80,
Padding = new Avalonia.Thickness(16, 0),
FontSize = 12,
Margin = new Avalonia.Thickness(8, 0, 0, 0),
HorizontalContentAlignment = HorizontalAlignment.Center,
VerticalContentAlignment = VerticalAlignment.Center,
Theme = (Avalonia.Styling.ControlTheme)owner.FindResource("AppButton")!
};

var buttonPanel = new StackPanel
{
Orientation = Orientation.Horizontal,
HorizontalAlignment = HorizontalAlignment.Right
};
buttonPanel.Children.Add(okBtn);
buttonPanel.Children.Add(cancelBtn);

var content = new StackPanel
{
Margin = new Avalonia.Thickness(20),
Children = { messageText, buttonPanel }
};

var dialog = new Window
{
Title = title,
Width = 460,
Height = 260,
MinWidth = 460,
MinHeight = 260,
Icon = owner.Icon,
Background = new SolidColorBrush(Color.Parse("#1A1D23")),
Foreground = new SolidColorBrush(Color.Parse("#E4E6EB")),
Content = content,
WindowStartupLocation = WindowStartupLocation.CenterOwner
};

okBtn.Click += (_, _) => { result = true; dialog.Close(); };
cancelBtn.Click += (_, _) => dialog.Close();

await dialog.ShowDialog(owner);
return result;
}
}
19 changes: 19 additions & 0 deletions src/PlanViewer.App/MainWindow.PlanViewer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,25 @@ private async Task GetActualPlanFromFile(PlanViewerControl viewer)
return;

var database = dialog.ResultDatabase ?? ExtractDatabaseFromPlanXml(viewer.RawXml);

/* Confirm before running it. The query session asks first; this path did
not, and it is the riskier of the two: the SQL comes out of a .sqlplan
the user opened rather than something they typed, and plan files get
emailed around. Name the target so "which server was that?" is answered
before the query runs, not after. */
var target = string.IsNullOrEmpty(database)
? dialog.ResultConnection.ServerName
: $"{dialog.ResultConnection.ServerName}, database [{database}]";

var confirmed = await Dialogs.ConfirmationDialog.ShowAsync(
this,
"Get Actual Plan",
$"This will execute the query stored in this plan file against:\n\n{target}\n\n" +
"The query text comes from the plan file, not from you. Review it first if the file did not originate on this machine.\n\n" +
"It runs with SET STATISTICS XML ON and all data results are discarded.\n\nContinue?");

if (!confirmed) return;

var connectionString = dialog.ResultConnection.GetConnectionString(_credentialService, database);
var isAzure = dialog.ResultConnection.ServerName.Contains(".database.windows.net",
StringComparison.OrdinalIgnoreCase) ||
Expand Down
Loading