Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
0c19ef8
- Add FetchHistoryByHashAsync method to QueryStoreService
rferraton Mar 29, 2026
581f851
README: add shields.io badge flair (repo + social)
erikdarlingdata Mar 29, 2026
eff03a3
Merge pull request #157 from erikdarlingdata/feature/readme-badges
erikdarlingdata Mar 29, 2026
fd9c6f7
Query History improvment step 2 :
rferraton Mar 29, 2026
7ff48fd
polish average line and label in query history
rferraton Mar 29, 2026
ecc960e
Fix grid color column correctly in query history
rferraton Mar 29, 2026
223ba41
in the query history :
rferraton Mar 29, 2026
53a4861
remove double using system
rferraton Mar 30, 2026
6d525e7
avgline LabelBackgroundColor Alpha 270 ==> 170
rferraton Mar 30, 2026
77ec4a4
add TotalMemoryMB metric for query history
rferraton Mar 30, 2026
e88096b
update TotalLogicalReads, TotalLogicalWrites, TotalPhysicalReads form…
rferraton Mar 30, 2026
bd8efda
Merge pull request #158 from rferraton/feature/query-history-v2
erikdarlingdata Mar 30, 2026
d3acba7
Add SignPath code signing to release workflow
erikdarlingdata Mar 30, 2026
60924db
Merge pull request #159 from erikdarlingdata/ci/signpath-signing
erikdarlingdata Mar 30, 2026
f03ea27
Fix Rule 2 false positive: eager table spools misidentified as eager …
erikdarlingdata Mar 30, 2026
49847ed
Merge pull request #160 from erikdarlingdata/fix/eager-index-spool-fa…
erikdarlingdata Mar 30, 2026
638025c
- Allow to zoom in/out in humanadvices using mousewheel
rferraton Apr 4, 2026
1dada02
make the node quoted in the human advices window clickable and naviga…
rferraton Apr 4, 2026
7293283
Add contextual schema lookup from query editor (issue #1)
erikdarlingdata Apr 5, 2026
af3f903
Merge pull request #163 from erikdarlingdata/feature/schema-lookup
erikdarlingdata Apr 5, 2026
1a849dd
PlanScrollViewer_PointerWheelChanged — now a 4-line method that just …
rferraton Apr 6, 2026
f1e94dc
Future changes to the advice window (styling, behavior, new features)…
rferraton Apr 6, 2026
3c715e3
The Hand cursor is now a single cached static field (HandCursor) allo…
rferraton Apr 6, 2026
deec72b
Merge pull request #162 from rferraton/feature/plan-viewer-improvment…
erikdarlingdata Apr 6, 2026
931aadf
Add READ UNCOMMITTED isolation level to schema queries
erikdarlingdata Apr 6, 2026
45ef810
Merge pull request #164 from erikdarlingdata/fix/schema-query-isolation
erikdarlingdata Apr 6, 2026
8a62e61
Show nonclustered index count on modification operators (#167)
erikdarlingdata Apr 6, 2026
4b5dc54
Merge pull request #168 from erikdarlingdata/feature/nc-index-count
erikdarlingdata Apr 6, 2026
a1428c8
Add 'Open in Query Editor' to plan viewer statements grid (#165)
erikdarlingdata Apr 6, 2026
8afa92c
Merge pull request #169 from erikdarlingdata/feature/open-in-editor
erikdarlingdata Apr 6, 2026
62b05d5
Add schema lookup to plan viewer operator right-click menu (#166)
erikdarlingdata Apr 6, 2026
3b1aedd
Merge pull request #170 from erikdarlingdata/feature/plan-viewer-sche…
erikdarlingdata Apr 6, 2026
5bdb420
Add connection toolbar to plan viewer for schema lookups (#166)
erikdarlingdata Apr 6, 2026
b9622e7
Merge pull request #171 from erikdarlingdata/feature/plan-viewer-conn…
erikdarlingdata Apr 6, 2026
3d71773
Bump version to 1.4.0
erikdarlingdata Apr 6, 2026
fbb182b
Merge pull request #172 from erikdarlingdata/release/v1.4.0
erikdarlingdata Apr 6, 2026
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
341 changes: 193 additions & 148 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,148 +1,193 @@
name: Release

on:
pull_request:
branches: [main]
types: [closed]

permissions:
contents: write

jobs:
release:
if: github.event.pull_request.merged == true && github.event.pull_request.head.ref == 'dev'
runs-on: windows-latest

steps:
- uses: actions/checkout@v4

- name: Get version
id: version
shell: pwsh
run: |
$version = ([xml](Get-Content src/PlanViewer.App/PlanViewer.App.csproj)).Project.PropertyGroup.Version | Where-Object { $_ }
echo "VERSION=$version" >> $env:GITHUB_OUTPUT

- name: Check if release already exists
id: check
shell: bash
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
if gh release view "v${{ steps.version.outputs.VERSION }}" > /dev/null 2>&1; then
echo "EXISTS=true" >> $GITHUB_OUTPUT
else
echo "EXISTS=false" >> $GITHUB_OUTPUT
fi

- name: Create release
if: steps.check.outputs.EXISTS == 'false'
shell: bash
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release create "v${{ steps.version.outputs.VERSION }}" --title "v${{ steps.version.outputs.VERSION }}" --generate-notes --target main

- name: Setup .NET 8.0
if: steps.check.outputs.EXISTS == 'false'
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x

- name: Build and test
if: steps.check.outputs.EXISTS == 'false'
run: |
dotnet restore
dotnet build -c Release
dotnet test tests/PlanViewer.Core.Tests/PlanViewer.Core.Tests.csproj -c Release --no-build --verbosity normal

- name: Publish App (all platforms)
if: steps.check.outputs.EXISTS == 'false'
run: |
dotnet publish src/PlanViewer.App/PlanViewer.App.csproj -c Release -r win-x64 --self-contained -o publish/win-x64
dotnet publish src/PlanViewer.App/PlanViewer.App.csproj -c Release -r linux-x64 --self-contained -o publish/linux-x64
dotnet publish src/PlanViewer.App/PlanViewer.App.csproj -c Release -r osx-x64 --self-contained -o publish/osx-x64
dotnet publish src/PlanViewer.App/PlanViewer.App.csproj -c Release -r osx-arm64 --self-contained -o publish/osx-arm64

- name: Create Velopack release (Windows)
if: steps.check.outputs.EXISTS == 'false'
shell: pwsh
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION: ${{ steps.version.outputs.VERSION }}
run: |
dotnet tool install -g vpk
New-Item -ItemType Directory -Force -Path releases/velopack

# Download previous release for delta generation
vpk download github --repoUrl https://github.com/${{ github.repository }} --channel win -o releases/velopack --token $env:GH_TOKEN

# Pack Windows release
vpk pack -u PerformanceStudio -v $env:VERSION -p publish/win-x64 -e PlanViewer.App.exe -o releases/velopack --channel win

- name: Package and upload
if: steps.check.outputs.EXISTS == 'false'
shell: pwsh
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION: ${{ steps.version.outputs.VERSION }}
run: |
New-Item -ItemType Directory -Force -Path releases

# Package Windows and Linux as flat zips
foreach ($rid in @('win-x64', 'linux-x64')) {
if (Test-Path 'README.md') { Copy-Item 'README.md' "publish/$rid/" }
if (Test-Path 'LICENSE') { Copy-Item 'LICENSE' "publish/$rid/" }
Compress-Archive -Path "publish/$rid/*" -DestinationPath "releases/PerformanceStudio-$rid.zip" -Force
}

# Package macOS as proper .app bundles
foreach ($rid in @('osx-x64', 'osx-arm64')) {
$appName = "PerformanceStudio.app"
$bundleDir = "publish/$rid-bundle/$appName"

# Create .app bundle structure
New-Item -ItemType Directory -Force -Path "$bundleDir/Contents/MacOS"
New-Item -ItemType Directory -Force -Path "$bundleDir/Contents/Resources"

# Copy all published files into Contents/MacOS
Copy-Item -Path "publish/$rid/*" -Destination "$bundleDir/Contents/MacOS/" -Recurse

# Move Info.plist to Contents/ (it was copied to MacOS/ with the publish output)
if (Test-Path "$bundleDir/Contents/MacOS/Info.plist") {
Move-Item -Path "$bundleDir/Contents/MacOS/Info.plist" -Destination "$bundleDir/Contents/Info.plist" -Force
}

# Update version in Info.plist to match csproj
$plist = Get-Content "$bundleDir/Contents/Info.plist" -Raw
$plist = $plist -replace '(<key>CFBundleVersion</key>\s*<string>)[^<]*(</string>)', "`${1}$env:VERSION`${2}"
$plist = $plist -replace '(<key>CFBundleShortVersionString</key>\s*<string>)[^<]*(</string>)', "`${1}$env:VERSION`${2}"
Set-Content -Path "$bundleDir/Contents/Info.plist" -Value $plist -NoNewline

# Move icon to Contents/Resources
if (Test-Path "$bundleDir/Contents/MacOS/EDD.icns") {
Move-Item -Path "$bundleDir/Contents/MacOS/EDD.icns" -Destination "$bundleDir/Contents/Resources/EDD.icns" -Force
}

# Add README and LICENSE alongside the .app bundle
$wrapperDir = "publish/$rid-bundle"
if (Test-Path 'README.md') { Copy-Item 'README.md' "$wrapperDir/" }
if (Test-Path 'LICENSE') { Copy-Item 'LICENSE' "$wrapperDir/" }

Compress-Archive -Path "$wrapperDir/*" -DestinationPath "releases/PerformanceStudio-$rid.zip" -Force
}

# Checksums (zips only, Velopack has its own checksums)
$checksums = Get-ChildItem releases/*.zip | ForEach-Object {
$hash = (Get-FileHash $_.FullName -Algorithm SHA256).Hash.ToLower()
"$hash $($_.Name)"
}
$checksums | Out-File -FilePath releases/SHA256SUMS.txt -Encoding utf8
Write-Host "Checksums:"
$checksums | ForEach-Object { Write-Host $_ }

# Upload zips + checksums
gh release upload "v$env:VERSION" releases/*.zip releases/SHA256SUMS.txt --clobber

# Upload Velopack artifacts
vpk upload github --repoUrl https://github.com/${{ github.repository }} --channel win -o releases/velopack --releaseName "v$env:VERSION" --tag "v$env:VERSION" --merge --token $env:GH_TOKEN
name: Release

on:
pull_request:
branches: [main]
types: [closed]

permissions:
contents: write
id-token: write
actions: read

jobs:
release:
if: github.event.pull_request.merged == true && github.event.pull_request.head.ref == 'dev'
runs-on: windows-latest

steps:
- uses: actions/checkout@v4

- name: Get version
id: version
shell: pwsh
run: |
$version = ([xml](Get-Content src/PlanViewer.App/PlanViewer.App.csproj)).Project.PropertyGroup.Version | Where-Object { $_ }
echo "VERSION=$version" >> $env:GITHUB_OUTPUT

- name: Check if release already exists
id: check
shell: bash
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
if gh release view "v${{ steps.version.outputs.VERSION }}" > /dev/null 2>&1; then
echo "EXISTS=true" >> $GITHUB_OUTPUT
else
echo "EXISTS=false" >> $GITHUB_OUTPUT
fi

- name: Create release
if: steps.check.outputs.EXISTS == 'false'
shell: bash
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release create "v${{ steps.version.outputs.VERSION }}" --title "v${{ steps.version.outputs.VERSION }}" --generate-notes --target main

- name: Setup .NET 8.0
if: steps.check.outputs.EXISTS == 'false'
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x

- name: Build and test
if: steps.check.outputs.EXISTS == 'false'
run: |
dotnet restore
dotnet build -c Release
dotnet test tests/PlanViewer.Core.Tests/PlanViewer.Core.Tests.csproj -c Release --no-build --verbosity normal

- name: Publish App (all platforms)
if: steps.check.outputs.EXISTS == 'false'
run: |
dotnet publish src/PlanViewer.App/PlanViewer.App.csproj -c Release -r win-x64 --self-contained -o publish/win-x64
dotnet publish src/PlanViewer.App/PlanViewer.App.csproj -c Release -r linux-x64 --self-contained -o publish/linux-x64
dotnet publish src/PlanViewer.App/PlanViewer.App.csproj -c Release -r osx-x64 --self-contained -o publish/osx-x64
dotnet publish src/PlanViewer.App/PlanViewer.App.csproj -c Release -r osx-arm64 --self-contained -o publish/osx-arm64

# ── SignPath code signing (Windows only, skipped if secret not configured) ──
- name: Check if signing is configured
if: steps.check.outputs.EXISTS == 'false'
id: signing
shell: bash
run: |
if [ -n "${{ secrets.SIGNPATH_API_TOKEN }}" ]; then
echo "ENABLED=true" >> $GITHUB_OUTPUT
else
echo "ENABLED=false" >> $GITHUB_OUTPUT
echo "::warning::SIGNPATH_API_TOKEN not configured — releasing unsigned binaries"
fi

- name: Upload Windows build for signing
if: steps.check.outputs.EXISTS == 'false' && steps.signing.outputs.ENABLED == 'true'
id: upload-unsigned
uses: actions/upload-artifact@v4
with:
name: App-unsigned
path: publish/win-x64/

- name: Sign Windows build
if: steps.check.outputs.EXISTS == 'false' && steps.signing.outputs.ENABLED == 'true'
uses: signpath/github-action-submit-signing-request@v1
with:
api-token: '${{ secrets.SIGNPATH_API_TOKEN }}'
organization-id: '7969f8b6-d946-4a74-9bac-a55856d8b8e0'
project-slug: 'PerformanceStudio'
signing-policy-slug: 'test-signing'
artifact-configuration-slug: 'App'
github-artifact-id: '${{ steps.upload-unsigned.outputs.artifact-id }}'
wait-for-completion: true
output-artifact-directory: 'signed/win-x64'

- name: Replace unsigned Windows build with signed
if: steps.check.outputs.EXISTS == 'false' && steps.signing.outputs.ENABLED == 'true'
shell: pwsh
run: |
Remove-Item -Recurse -Force publish/win-x64
Copy-Item -Recurse signed/win-x64 publish/win-x64

# ── Velopack (uses signed Windows binaries) ───────────────────────
- name: Create Velopack release (Windows)
if: steps.check.outputs.EXISTS == 'false'
shell: pwsh
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION: ${{ steps.version.outputs.VERSION }}
run: |
dotnet tool install -g vpk
New-Item -ItemType Directory -Force -Path releases/velopack

# Download previous release for delta generation
vpk download github --repoUrl https://github.com/${{ github.repository }} --channel win -o releases/velopack --token $env:GH_TOKEN

# Pack Windows release (now signed)
vpk pack -u PerformanceStudio -v $env:VERSION -p publish/win-x64 -e PlanViewer.App.exe -o releases/velopack --channel win

# ── Package and upload ────────────────────────────────────────────
- name: Package and upload
if: steps.check.outputs.EXISTS == 'false'
shell: pwsh
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION: ${{ steps.version.outputs.VERSION }}
run: |
New-Item -ItemType Directory -Force -Path releases

# Package Windows (signed) and Linux as flat zips
foreach ($rid in @('win-x64', 'linux-x64')) {
if (Test-Path 'README.md') { Copy-Item 'README.md' "publish/$rid/" }
if (Test-Path 'LICENSE') { Copy-Item 'LICENSE' "publish/$rid/" }
Compress-Archive -Path "publish/$rid/*" -DestinationPath "releases/PerformanceStudio-$rid.zip" -Force
}

# Package macOS as proper .app bundles
foreach ($rid in @('osx-x64', 'osx-arm64')) {
$appName = "PerformanceStudio.app"
$bundleDir = "publish/$rid-bundle/$appName"

# Create .app bundle structure
New-Item -ItemType Directory -Force -Path "$bundleDir/Contents/MacOS"
New-Item -ItemType Directory -Force -Path "$bundleDir/Contents/Resources"

# Copy all published files into Contents/MacOS
Copy-Item -Path "publish/$rid/*" -Destination "$bundleDir/Contents/MacOS/" -Recurse

# Move Info.plist to Contents/ (it was copied to MacOS/ with the publish output)
if (Test-Path "$bundleDir/Contents/MacOS/Info.plist") {
Move-Item -Path "$bundleDir/Contents/MacOS/Info.plist" -Destination "$bundleDir/Contents/Info.plist" -Force
}

# Update version in Info.plist to match csproj
$plist = Get-Content "$bundleDir/Contents/Info.plist" -Raw
$plist = $plist -replace '(<key>CFBundleVersion</key>\s*<string>)[^<]*(</string>)', "`${1}$env:VERSION`${2}"
$plist = $plist -replace '(<key>CFBundleShortVersionString</key>\s*<string>)[^<]*(</string>)', "`${1}$env:VERSION`${2}"
Set-Content -Path "$bundleDir/Contents/Info.plist" -Value $plist -NoNewline

# Move icon to Contents/Resources
if (Test-Path "$bundleDir/Contents/MacOS/EDD.icns") {
Move-Item -Path "$bundleDir/Contents/MacOS/EDD.icns" -Destination "$bundleDir/Contents/Resources/EDD.icns" -Force
}

# Add README and LICENSE alongside the .app bundle
$wrapperDir = "publish/$rid-bundle"
if (Test-Path 'README.md') { Copy-Item 'README.md' "$wrapperDir/" }
if (Test-Path 'LICENSE') { Copy-Item 'LICENSE' "$wrapperDir/" }

Compress-Archive -Path "$wrapperDir/*" -DestinationPath "releases/PerformanceStudio-$rid.zip" -Force
}

# Checksums (zips only, Velopack has its own checksums)
$checksums = Get-ChildItem releases/*.zip | ForEach-Object {
$hash = (Get-FileHash $_.FullName -Algorithm SHA256).Hash.ToLower()
"$hash $($_.Name)"
}
$checksums | Out-File -FilePath releases/SHA256SUMS.txt -Encoding utf8
Write-Host "Checksums:"
$checksums | ForEach-Object { Write-Host $_ }

# Upload zips + checksums
gh release upload "v$env:VERSION" releases/*.zip releases/SHA256SUMS.txt --clobber

# Upload Velopack artifacts
vpk upload github --repoUrl https://github.com/${{ github.repository }} --channel win -o releases/velopack --releaseName "v$env:VERSION" --tag "v$env:VERSION" --merge --token $env:GH_TOKEN
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
# Performance Studio

<p align="center">
<a href="https://github.com/erikdarlingdata/PerformanceStudio/stargazers"><img src="https://img.shields.io/github/stars/erikdarlingdata/PerformanceStudio?style=for-the-badge&logo=github&color=gold&logoColor=black" alt="GitHub Stars"></a>
<a href="https://github.com/erikdarlingdata/PerformanceStudio/network/members"><img src="https://img.shields.io/github/forks/erikdarlingdata/PerformanceStudio?style=for-the-badge&logo=github" alt="GitHub Forks"></a>
<a href="https://github.com/erikdarlingdata/PerformanceStudio/blob/main/LICENSE"><img src="https://img.shields.io/github/license/erikdarlingdata/PerformanceStudio?style=for-the-badge" alt="License: MIT"></a>
<a href="https://github.com/erikdarlingdata/PerformanceStudio/releases/latest"><img src="https://img.shields.io/github/v/release/erikdarlingdata/PerformanceStudio?style=for-the-badge" alt="Latest Release"></a>
<a href="https://github.com/erikdarlingdata/PerformanceStudio/issues"><img src="https://img.shields.io/github/issues/erikdarlingdata/PerformanceStudio?style=for-the-badge" alt="Open Issues"></a>
<a href="https://github.com/erikdarlingdata/PerformanceStudio/commits/main"><img src="https://img.shields.io/github/last-commit/erikdarlingdata/PerformanceStudio?style=for-the-badge" alt="Last Commit"></a>
<a href="https://github.com/erikdarlingdata/PerformanceStudio/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/erikdarlingdata/PerformanceStudio/ci.yml?style=for-the-badge&label=CI" alt="CI"></a>
</p>
<p align="center">
<a href="https://x.com/erikdarlingdata"><img src="https://img.shields.io/badge/Follow_%40ErikDarlingData-black?style=for-the-badge&logo=x&logoColor=white" alt="Follow @ErikDarlingData on X"></a>
<a href="https://www.youtube.com/@ErikDarlingData"><img src="https://img.shields.io/badge/YouTube-Subscribe-red?style=for-the-badge&logo=youtube&logoColor=white" alt="YouTube Subscribe"></a>
<a href="https://www.linkedin.com/in/erik-darling-data/"><img src="https://img.shields.io/badge/LinkedIn-Connect-0077B5?style=for-the-badge&logo=linkedin&logoColor=white" alt="LinkedIn Connect"></a>
<a href="https://erikdarling.com"><img src="https://img.shields.io/badge/Blog-erikdarling.com-FF6B35?style=for-the-badge&logo=wordpress&logoColor=white" alt="Blog"></a>
</p>

A cross-platform SQL Server execution plan analyzer with built-in MCP server for AI-assisted analysis. Parses `.sqlplan` XML, identifies performance problems, suggests missing indexes, and provides actionable warnings — from the command line or a desktop GUI.

Built for developers and DBAs who want fast, automated plan analysis without clicking through SSMS.
Expand Down
Loading
Loading