Skip to content

Dashboard tab with live message counts for all entities - #866

Merged
SeanFeldman merged 47 commits into
paolosalvatori:mainfrom
tailormade-eu:pr/dashboard
Apr 14, 2026
Merged

SeanFeldman merged 47 commits into
paolosalvatori:mainfrom
tailormade-eu:pr/dashboard

Conversation

@RaoulJacobs

@RaoulJacobs RaoulJacobs commented Apr 2, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • New Dashboard tab showing Active, Dead Letter, Scheduled and Total message counts for all queues and subscriptions at a glance
  • Auto-refresh with configurable interval (30s / 1min / 5min) and manual Refresh button
  • Per-row right-click refresh — refresh a single entity without reloading the full namespace
  • Single-click syncs TreeView selection; double-click opens the entity in the Explorer tab
  • Color-coded dead-letter alerts: rows with Dead Letter > 0 highlighted in pink
  • Dashboard rows stay in sync when queues or subscriptions are created or deleted

Dependencies

⚠️ This PR depends on #865 (TreeView search/filter) — please merge that one first. The Dashboard integrates with the filter (clears it on row navigation) and shares changes in MainForm.cs and MainForm.Designer.cs.

Screenshots

Dashboard tab

Right-click context menu:

Dashboard right-click refresh

Test plan

  • Dashboard tab loads all queues and subscriptions with correct counts
  • Auto-refresh updates counts at the configured interval
  • Single-click on a row selects the matching node in TreeView
  • Double-click opens the entity in the Explorer tab
  • Right-click → Refresh updates only the selected row
  • Creating/deleting a queue or subscription adds/removes the row in Dashboard
  • Dead-letter > 0 rows are highlighted pink

- Replace stale allChildNodes cache with restore-then-snapshot approach
  (always restore full node set before re-filtering)
- Add 250ms debounce timer on TextChanged to prevent UI stutter
- filterSnapshot cleared on tree refresh via InvalidateTreeViewFilter
…tainer

Timer was lazily created but never disposed, causing a resource leak.
Now created in constructor and registered with components for automatic disposal.
TabControl in Panel2 with Dashboard (default) and Explorer tabs.
DashboardControl shows DataGridView with Active/DLQ/Scheduled counts,
async loading, refresh button, auto-refresh timer, DLQ color coding.
…disposal

- Add isLoading guard to prevent overlapping LoadDataAsync calls
- Move writeToLog calls to UI thread (collect errors in Task.Run, log after await)
- Extract Font objects to fields, dispose in Dispose(bool)
- Revert AssemblyVersion to 1.0.0.1 (upstream test convention)
Replace topic rows (always 0 counts) with subscription rows that show
actual Active/DLQ message counts from SubscriptionDescription.
Filter now also filters child nodes (subscriptions) under topics.
When a topic is kept because a child matches, non-matching children
are hidden. Child snapshots are restored when filter is cleared.
Clicking a row in the Dashboard DataGridView now selects the matching
node in the TreeView, scrolls to it, and switches to the Explorer tab.
Supports both queue rows and subscription rows (TopicName / SubName).
Ctrl+C copies selected row as TSV, context menu offers Copy row + Copy name.
Root cause: FilterChildNodes only checked direct children text match.
Topic structure is Topic → "Subscriptions" container → actual subs.
The container node text never matched, so all subscriptions were hidden.

Fix: use recursive NodeMatchesFilter + recurse FilterChildNodes into
container nodes. RestoreChildNodes also recurses for nested snapshots.
…RowSelected

- ContextMenuStrip promoted from local var to field with explicit Dispose()
- Added rootNode null check in DashboardRowSelected to prevent NRE before connection
…e sync)

Dashboard refresh button and auto-refresh timer now trigger ShowEntities(EntityType.All)
via OnRefreshRequested callback instead of calling Azure SDK directly. This ensures
log panel, TreeView, and dashboard stay in sync through one unified refresh flow.
DashboardControl.UpdateRow() finds and updates one row by entity name.
RefreshSelectedEntity() calls it after queue and subscription refresh.
- DashboardControl.RemoveRow: removes row by name (case-insensitive)
- DashboardControl.RemoveRowsWithPrefix: removes all subscription rows for a topic
- DashboardControl.AddRow: idempotent sorted insert with 0 counts
- MainForm OnDelete: queue->RemoveRow, topic->RemoveRowsWithPrefix, sub->RemoveRow
- MainForm OnCreate: queue->AddRow, subscription->AddRow

@ErikMogensen ErikMogensen left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got this error:

C:\GitHub\paolosalvatori\ServiceBusExplorer\src\ServiceBusExplorer\Forms\MainForm.cs(250,53,250,73): error CS0234: The type or namespace name 'TreeViewFilterHelper' does not exist in the namespace 'ServiceBusExplorer.Helpers' (are you missing an assembly reference?)

I assume we have to wait until the treeview PR is merged.

…ilterDebounceTimer conflict (keep readonly + constructor init)
@RaoulJacobs

Copy link
Copy Markdown
Contributor Author

Hi @ErikMogensen, #865 has been merged — rebased this PR on the updated main. The compile error should be gone now. Ready for re-review!

@RaoulJacobs

Copy link
Copy Markdown
Contributor Author

Hi @ErikMogensen , @SeanFeldman
Still something blocking?

@ErikMogensen

Copy link
Copy Markdown
Collaborator

Hi @ErikMogensen , @SeanFeldman Still something blocking?

Yes, as mentioned in this comment #616 (comment) from 2022 the message count operation puts a heavy burden on the Service Bus service. Therefore I just sent an email to the Service Bus Program Management to check if what they said in 2022 still applies.

@paolosalvatori

Copy link
Copy Markdown
Owner

Thanks @RaoulJacobs for the additions, @ErikMogensen sent an email to the guy who left #616 comment in 2022. If there are no meaningful drawbacks regarding the auto-refresh feature, we'll merge your PR right away. Thanks for the contribution!

@RaoulJacobs

Copy link
Copy Markdown
Contributor Author

Thanks Paolo!

The auto-refresh is disabled by default — it's a gadget feature, so no meaningful drawbacks and zero impact on existing behavior.

In the meantime I've made a few additional improvements that further align the overall UX. Would you prefer I include them in this PR, or open a separate one?

@ErikMogensen
ErikMogensen self-requested a review April 14, 2026 18:46
@ErikMogensen

ErikMogensen commented Apr 14, 2026 •

Copy link
Copy Markdown
Collaborator

In the meantime I've made a few additional improvements that further align the overall UX. Would you prefer I include them in this PR, or open a separate one?

Are they closely related to this one? In that case yes please, otherwise no thanks.

The product team hasn't responded about the auto-refresh. I suggest we go for what they wanted in 2022 which was:

  • Not enabled by default
  • 1 minute as the lowest setting
  • A warning banner indicating the implication of enabling this and increasing the frequency.

If they reply with a different answer this time we can always fix it.

@RaoulJacobs, please remove the possibility to set it to 30 seconds and add a text stating something like: "Auto refresh affects the performance of the Service Bus resource, especially when set to a high frequency". I hope that text will fit...

@ErikMogensen
ErikMogensen dismissed their stale review April 14, 2026 19:07

I want to start over.

@ErikMogensen ErikMogensen left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some picky changes to the code and some questions about some files.

Comment thread tools/version-bump.js

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was decided to remove this file.

Comment thread CHANGELOG.md

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove this file since we are not using such a file.

Comment thread TESTING.md

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems that this file is for manual testing of the dashboard. Is that so?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it was an internal manual test checklist for our own verification during development — not intended for upstream. Removed.

Comment thread docs/img/dashboard-copy.png Outdated

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see this file used anywhere. Is the file referencing this missing?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see this file used anywhere. Is the file referencing this missing?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dashboard-tab.png and dashboard-refresh.png are referenced in docs/documentation.md — the Dashboard section uses both images to illustrate the tab and the right-click refresh. dashboard-copy.png was indeed not referenced and has been removed.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see this file used anywhere. Is the file referencing this missing?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dashboard-tab.png and dashboard-refresh.png are referenced in docs/documentation.md — the Dashboard section uses both images to illustrate the tab and the right-click refresh. dashboard-copy.png was indeed not referenced and has been removed.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see docs/documentation.md in this PR, but I guess it was in the other PR.

}
}

public async void LoadDataAsync()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change this to async task LoadDataAsync() for better error handling.

Comment on lines +259 to +261
if (getQueues == null || getTopics == null || getSubscriptions == null || isLoading) return;

isLoading = true;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be a race condition since there is a gap between the read and the write. Currently that does not occur since this method will be only called from the UI thread, but to be prepared for future code changes please secure it. One way of doing it is:

  1. Change isloading to an int since interlocked does not work with bool.
  2. Change the above code to:
if (getQueues == null || getTopics == null || getSubscriptions == null) return;           
if (System.Threading.Interlocked.CompareExchange(ref isLoading, 1, 0) != 0) return;

Dock = DockStyle.Bottom,
AutoSize = false,
Height = 20,
Font = new Font("Segoe UI", 8F),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please handle this font in the same way you handle the other fonts, such as headerFont. Assigning it to a field and disposing it.

dataGridView.Rows.Clear();
foreach (var row in rows.OrderBy(r => r.Type).ThenBy(r => r.Name))
{
var total = row.Active + row.DeadLetter + row.Scheduled;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

total is calculated in two places, here and in UpdateRow. If you add a Total property to DashboardRow it is only done in one place.

…ements

Auto-refresh UX:
- Remove 30s option; minimum is 1min (1/5/15/30/60min options)
- Add warning banner when auto-refresh is enabled

Dashboard:
- Sync with treeview checkbox: when off, dashboard shows all entities ignoring active filter
- Sort preservation: PopulateGrid retains sorted column and direction after refresh
- Pending selection fix: clicking dashboard row while filter active + sync off clears filter first, then selects node

Code quality (Erik's review):
- async void LoadDataAsync -> async Task for proper error handling
- isLoading bool -> int with Interlocked.CompareExchange (race condition fix)
- DashboardRow.Total as computed property via static ComputeTotal (single source of truth)
- hintFont and warningFont as fields, properly disposed
- ComboBox width 80 -> 90px, syncWithTreeViewCheckBox position corrected

TreeView filter:
- Clear button: hidden when empty, visible on input, clears filter on click
- Clear button forecolor fix (black text on hover)
- ApplyFilter wired to filter text changes and sync checkbox toggle

Removed:
- docs/img/dashboard-copy.png (unreferenced)
- CHANGELOG.md (not used in this project)
- tools/version-bump.js (decided to remove)
@RaoulJacobs

Copy link
Copy Markdown
Contributor Author

Hi @ErikMogensen, addressed all your review comments:

Removed files:

  • tools/version-bump.js
  • CHANGELOG.md
  • TESTING.md (was an internal manual test checklist, not intended for upstream)
  • docs/img/dashboard-copy.png (unreferenced)

Note: dashboard-tab.png and dashboard-refresh.png are referenced in docs/documentation.md — kept those.

Auto-refresh:

  • Removed 30s option; minimum is now 1min
  • Options: 1min / 5min / 15min / 30min / 60min
  • Added warning banner when auto-refresh is enabled

Code quality:

  • async void LoadDataAsync → async Task
  • isLoading bool → int with Interlocked.CompareExchange (race condition fix)
  • DashboardRow.Total as computed property via static ComputeTotal() (single source of truth for both PopulateGrid and UpdateRow)
  • hintFont and warningFont as fields, properly disposed

Additional improvements included in this push:

  • Sync with treeview checkbox: when unchecked, dashboard shows all entities regardless of active filter
  • Sort preservation: column sort is retained after refresh
  • TreeView filter clear button (hidden when empty)
  • Pending selection fix when clicking a dashboard row while filter is active

RaoulJacobs added a commit to tailormade-eu/ServiceBusExplorer that referenced this pull request Apr 14, 2026
- Auto-refresh: remove 30s option, add 1/5/15/30/60min intervals
- Add warning banner when auto-refresh is enabled
- async void LoadDataAsync -> async Task for proper error handling
- isLoading bool -> int with Interlocked.CompareExchange (race condition fix)
- DashboardRow.Total as computed property via static ComputeTotal helper
- hintFont and warningFont as fields, properly disposed
- Fix syncWithTreeViewCheckBox position (gap after wider combobox)
- Remove tools/version-bump.js, CHANGELOG.md, docs/img/dashboard-copy.png
- MainForm.RefreshDashboard: await LoadDataAsync
@ErikMogensen ErikMogensen changed the title feat: Dashboard tab with live message counts for all entities Dashboard tab with live message counts for all entities Apr 14, 2026

@ErikMogensen ErikMogensen left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great!

@SeanFeldman
SeanFeldman merged commit 4be2685 into paolosalvatori:main Apr 14, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants