Skip to content

feat: Add colorization based on health level for panels and toolbar#2178

Open
hunzlahmalik wants to merge 5 commits intodjango-commons:mainfrom
hunzlahmalik:main
Open

feat: Add colorization based on health level for panels and toolbar#2178
hunzlahmalik wants to merge 5 commits intodjango-commons:mainfrom
hunzlahmalik:main

Conversation

@hunzlahmalik
Copy link
Copy Markdown

@hunzlahmalik hunzlahmalik commented Aug 10, 2025

Description

This PR introduces a unified health/alert state for Django Debug Toolbar panels and updates the UI to reflect panel.
The name health is debatable, but the alert was already taken by the panel.

Key Changes

  • Adds a ToolbarHealthLevel enum (NONE, WARNING, CRITICAL) for numeric health states.

  • Adds a health_level property to the base Panel class, allowing panels to report their health status.

  • Implements health_level in AlertsPanel and SQLPanel for alert and slow query detection.

  • The toolbar now computes the maximum health level across all panels and uses it to color the hidden toolbar handle.

  • UI Updates:

    • Panel buttons and the hidden toolbar handle use new CSS classes and colors based on health level.
    • Adds CSS variables and styles for warning and critical states.
Warning Warning on Hover Critical Critical on Hover
image image image image
Critical on Panel Warning on Panel
image image

Motivation

  • Improves user experience by visually highlighting issues in the toolbar.

Checklist:

  • I have added the relevant tests for this change.
  • I have added an item to the Pending section of docs/changes.rst.
  • Add to Documentation

@hunzlahmalik
Copy link
Copy Markdown
Author

hunzlahmalik commented Aug 10, 2025

@matthiask @tim-schilling

I'll be writing the testcases for this. It would be great if you could take a look at it before testcases.

Thanks!

Edit: You can also suggest how to compute health(debatable name) across other panel profiling, time etc...

Copy link
Copy Markdown
Member

@matthiask matthiask left a comment

Choose a reason for hiding this comment

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

I like the idea! I added some nitpicks and comments/general questions.


--djdt-health-bg-1: #4b3f1b;
--djdt-health-color-1: #ffe761;
--djdt-health-bc-1: #ffcc00;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What is bc? Background color? I think we prefer using variable names without abbreviations.


#djDebug .djdt-toolbarhandle-health-2 {
border-color: var(--djdt-health-bc-2) !important;
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can you add a comment why all these !important are necessary? And/or remove them when they are not?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Overwriting CSS statements is also slow. Ideally, you set the correct value in one shot. You could even calculate it with a 120deg hue range between green and red.

</div>
<div class="djdt-hidden" id="djDebugToolbarHandle">
<div title="{% translate 'Show toolbar' %}" id="djShowToolBarButton">
<div title="{% translate 'Show toolbar' %}" id="djShowToolBarButton" class="{% if toolbar.health_level %} djdt-toolbarhandle-health-{{ toolbar.health_level }}{% endif %}">
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I generally do not like uppercase letters much in ID and class attributes, but I wonder if we should keep capitalization for internal consistency here? Any thoughts?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

They are inconsistent right now. Some places use camelCase when starting class names with dj, while others use lowercase with dashes (mostly when starting with djdt).

Rightnow, I can change them to camelcase if you want, but for consistency the changes in whole project will be required in future.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ah, don't worry about it then. Being as consistent as possible is good enough.

Comment thread debug_toolbar/toolbar.py Outdated
Return the maximum health level across all panels.
This is used to color the toolbar hidden button.
"""
return max(panel.health_level for panel in self.panels)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What happens when self.panels is empty? Is it possible? I think you might want to provide a default value as a fallback (max((...), NONE))

Comment thread debug_toolbar/utils.py Outdated
)


class ToolbarHealthLevel(IntEnum):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think this could be called HealthLevel only since it actually doesn't only apply to the toolbar itself but also to individual panels.

@hunzlahmalik hunzlahmalik force-pushed the main branch 2 times, most recently from 2c46d26 to b38335d Compare August 17, 2025 11:28
@hunzlahmalik hunzlahmalik requested a review from matthiask August 17, 2025 11:34
Comment on lines +38 to +43
--djdt-health-background-color-1: #4b3f1b;
--djdt-health-color-1: #ffe761;
--djdt-health-border-color-1: #ffcc00;
--djdt-health-background-color-2: #5a2327;
--djdt-health-color-2: #ffb3b3;
--djdt-health-border-color-2: #ff0000;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could we use a descriptive word rather than 1 and 2 here please?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This might also be a good application for the HSL color space. It also makes it much easier to review than RGB hex code.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This will also add a dark mode, much easier in the future.

Comment thread debug_toolbar/utils.py
)


class HealthLevel(IntEnum):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@matthiask @hunzlahmalik do we want to bikeshed a bit on the name here? If we build out an API, this will likely be included in that and no longer be a private-ish thing. In my mind, "severity" may be more appropriate, but then it may lack the context of what is the severity in relation to.

Not sure, but want to bring it up since it's likely to become a public interface in the near future (< 1 years).

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Yes we can change the name. I thought about it and was inclined to using the alert, but it was already taking in the app for the panel. We can use severity, we can see this as independent from the colorization. Later this can be use to get the panel severitylevel to do something else.

Comment thread debug_toolbar/toolbar.py Outdated
@tim-schilling
Copy link
Copy Markdown
Member

@hunzlahmalik this is really neat! How does it work with the history panel? Does the color only show for the request that renders the button? If you switch to a request that has problems, does the color update? Last question, I promise, did you consider adding the health level to the history panel so a person can view the high level issues of all the requests in a summary-esque view? I personally think that would be a fantastic way to leverage this feature.

Co-authored-by: Tim Schilling <schilling711@gmail.com>
@hunzlahmalik
Copy link
Copy Markdown
Author

hunzlahmalik commented Sep 23, 2025

For the panels that do define the health_level logic, after switching the request they should update the color too. Right now there are only two panels with health_level login, sqlpanel and alerts.

See this:
image
image

Regarding summary-esque view, what do you have in mind. We can colorize the rows in the history panel based on health, or do you want a separate some kind of filter/tab for it. I think coloring the rows looks good to me. Like if there is a request with health_level CRITICAL, then that row will be red, with warning, that row will be yellowish.

Copy link
Copy Markdown
Member

@codingjoe codingjoe left a comment

Choose a reason for hiding this comment

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

Hi @hunzlahmalik,

Thanks for the contribution! Since I am somewhat of a frontend nerd, I invested some time to leave you a thorough review. I hope it's helpful. Don't hesitate to ask questions should my comments have been too vague.

Cheers!
Joe

Comment on lines +195 to +200
stats = self.get_stats()
slow_queries = len([1 for q in stats.get("queries", []) if q.get("is_slow")])
if slow_queries > 10:
return HealthLevel.CRITICAL
elif slow_queries > 0:
return HealthLevel.WARNING
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The current implementation is susceptible to the number of queries executed on a page. A mean or median threshold might be more reliable.

Also, this is a great usecase for matcht..case.

Comment on lines +191 to +194
"""
Return the health level of the SQL panel.
This is determined by the number of slow queries recorded.
"""
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't love overwriting docstrings. PEP257 would have you document the behavior of a function, not its implementation. And the behavior usually doesn't change in subclasses, but only its implementation does.

@property
def health_level(self):
"""
Returns the health level of the panel as a `ToolbarHealthLevel` enum value.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I know we currently have a wild mix, but it's never too late to write docstrings in the present tense imperative mood.

Suggested change
Returns the health level of the panel as a `ToolbarHealthLevel` enum value.
Return a calculated health level of the page as a `ToolbarHealthLevel` enum value.

Comment on lines +137 to +138
This property is used by the toolbar to determine the overall health status of each panel.
The default implementation returns `ToolbarHealthLevel.NONE`, indicating no issues.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
This property is used by the toolbar to determine the overall health status of each panel.
The default implementation returns `ToolbarHealthLevel.NONE`, indicating no issues.
Used by the toolbar to determine the overall health status of each panel.
The default implementation returns `ToolbarHealthLevel.NONE`, indicating no issues.

Comment on lines +140 to +141
Subclasses should override this property to provide custom health logic, returning
`ToolbarHealthLevel.WARNING` or `ToolbarHealthLevel.ERROR` as appropriate based on panel-specific conditions.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The enum might get more entries later, you might not want to list them.

Comment on lines +38 to +43
--djdt-health-background-color-1: #4b3f1b;
--djdt-health-color-1: #ffe761;
--djdt-health-border-color-1: #ffcc00;
--djdt-health-background-color-2: #5a2327;
--djdt-health-color-2: #ffb3b3;
--djdt-health-border-color-2: #ff0000;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This might also be a good application for the HSL color space. It also makes it much easier to review than RGB hex code.

}

/* Panel and Toolbar hidden button health states */
#djDebug .djdt-health-1 {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

All browsers support nesting these days. So one #djDebug will do just fine.

Comment on lines +38 to +43
--djdt-health-background-color-1: #4b3f1b;
--djdt-health-color-1: #ffe761;
--djdt-health-border-color-1: #ffcc00;
--djdt-health-background-color-2: #5a2327;
--djdt-health-color-2: #ffb3b3;
--djdt-health-border-color-2: #ff0000;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This will also add a dark mode, much easier in the future.


#djDebug .djdt-toolbarhandle-health-2 {
border-color: var(--djdt-health-bc-2) !important;
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Overwriting CSS statements is also slow. Ideally, you set the correct value in one shot. You could even calculate it with a 120deg hue range between green and red.

Comment thread debug_toolbar/toolbar.py
Comment on lines +82 to +85
if not self.panels:
return HealthLevel.NONE

return max(panel.health_level for panel in self.enabled_panels)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

EAFP: Just try to return the max and catch the ValueError and return .NONE. Branching is expensive.

@hunzlahmalik
Copy link
Copy Markdown
Author

Thanks for the review @codingjoe. Will definitely go through these carefully in free time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants