Skip to content

Commit 7f2ad72

Browse files
Support JS events when loading a panel. (#1441)
* Support panel rendered JS event. This fixes the problem of the Timer Panel not inserting the browser timings section after being loaded via the HistoryPanel. These events could be wired into to better render panels or support a more dynamic toolbar and/or panel. Co-authored-by: Matthias Kestenholz <[email protected]>
1 parent e8c9036 commit 7f2ad72

File tree

6 files changed

+143
-62
lines changed

6 files changed

+143
-62
lines changed

debug_toolbar/panels/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,10 @@ def content(self):
107107
def scripts(self):
108108
"""
109109
Scripts used by the HTML content of the panel when it's displayed.
110+
111+
When a panel is rendered on the frontend, the ``djdt.panel.render``
112+
JavaScript event will be dispatched. The scripts can listen for
113+
this event to support dynamic functionality.
110114
"""
111115
return []
112116

Lines changed: 72 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,75 @@
1-
const timingOffset = performance.timing.navigationStart,
2-
timingEnd = performance.timing.loadEventEnd,
3-
totalTime = timingEnd - timingOffset;
4-
function getLeft(stat) {
5-
return ((performance.timing[stat] - timingOffset) / totalTime) * 100.0;
6-
}
7-
function getCSSWidth(stat, endStat) {
8-
let width =
9-
((performance.timing[endStat] - performance.timing[stat]) / totalTime) *
10-
100.0;
11-
// Calculate relative percent (same as sql panel logic)
12-
width = (100.0 * width) / (100.0 - getLeft(stat));
13-
return width < 1 ? "2px" : width + "%";
14-
}
15-
function addRow(tbody, stat, endStat) {
16-
const row = document.createElement("tr");
17-
if (endStat) {
18-
// Render a start through end bar
19-
row.innerHTML =
20-
"<td>" +
21-
stat.replace("Start", "") +
22-
"</td>" +
23-
'<td><svg class="djDebugLineChart" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 5" preserveAspectRatio="none"><rect y="0" height="5" fill="#ccc" /></svg></td>' +
24-
"<td>" +
25-
(performance.timing[stat] - timingOffset) +
26-
" (+" +
27-
(performance.timing[endStat] - performance.timing[stat]) +
28-
")</td>";
29-
row.querySelector("rect").setAttribute(
30-
"width",
31-
getCSSWidth(stat, endStat)
32-
);
33-
} else {
34-
// Render a point in time
35-
row.innerHTML =
36-
"<td>" +
37-
stat +
38-
"</td>" +
39-
'<td><svg class="djDebugLineChart" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 5" preserveAspectRatio="none"><rect y="0" height="5" fill="#ccc" /></svg></td>' +
40-
"<td>" +
41-
(performance.timing[stat] - timingOffset) +
42-
"</td>";
43-
row.querySelector("rect").setAttribute("width", 2);
1+
import { $$ } from "./utils.js";
2+
3+
function insertBrowserTiming() {
4+
console.log(["inserted"]);
5+
const timingOffset = performance.timing.navigationStart,
6+
timingEnd = performance.timing.loadEventEnd,
7+
totalTime = timingEnd - timingOffset;
8+
function getLeft(stat) {
9+
return ((performance.timing[stat] - timingOffset) / totalTime) * 100.0;
10+
}
11+
function getCSSWidth(stat, endStat) {
12+
let width =
13+
((performance.timing[endStat] - performance.timing[stat]) /
14+
totalTime) *
15+
100.0;
16+
// Calculate relative percent (same as sql panel logic)
17+
width = (100.0 * width) / (100.0 - getLeft(stat));
18+
return width < 1 ? "2px" : width + "%";
19+
}
20+
function addRow(tbody, stat, endStat) {
21+
const row = document.createElement("tr");
22+
if (endStat) {
23+
// Render a start through end bar
24+
row.innerHTML =
25+
"<td>" +
26+
stat.replace("Start", "") +
27+
"</td>" +
28+
'<td><svg class="djDebugLineChart" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 5" preserveAspectRatio="none"><rect y="0" height="5" fill="#ccc" /></svg></td>' +
29+
"<td>" +
30+
(performance.timing[stat] - timingOffset) +
31+
" (+" +
32+
(performance.timing[endStat] - performance.timing[stat]) +
33+
")</td>";
34+
row.querySelector("rect").setAttribute(
35+
"width",
36+
getCSSWidth(stat, endStat)
37+
);
38+
} else {
39+
// Render a point in time
40+
row.innerHTML =
41+
"<td>" +
42+
stat +
43+
"</td>" +
44+
'<td><svg class="djDebugLineChart" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 5" preserveAspectRatio="none"><rect y="0" height="5" fill="#ccc" /></svg></td>' +
45+
"<td>" +
46+
(performance.timing[stat] - timingOffset) +
47+
"</td>";
48+
row.querySelector("rect").setAttribute("width", 2);
49+
}
50+
row.querySelector("rect").setAttribute("x", getLeft(stat));
51+
tbody.appendChild(row);
52+
}
53+
54+
const browserTiming = document.getElementById("djDebugBrowserTiming");
55+
// Determine if the browser timing section has already been rendered.
56+
if (browserTiming.classList.contains("djdt-hidden")) {
57+
const tbody = document.getElementById("djDebugBrowserTimingTableBody");
58+
// This is a reasonably complete and ordered set of timing periods (2 params) and events (1 param)
59+
addRow(tbody, "domainLookupStart", "domainLookupEnd");
60+
addRow(tbody, "connectStart", "connectEnd");
61+
addRow(tbody, "requestStart", "responseEnd"); // There is no requestEnd
62+
addRow(tbody, "responseStart", "responseEnd");
63+
addRow(tbody, "domLoading", "domComplete"); // Spans the events below
64+
addRow(tbody, "domInteractive");
65+
addRow(tbody, "domContentLoadedEventStart", "domContentLoadedEventEnd");
66+
addRow(tbody, "loadEventStart", "loadEventEnd");
67+
browserTiming.classList.remove("djdt-hidden");
4468
}
45-
row.querySelector("rect").setAttribute("x", getLeft(stat));
46-
tbody.appendChild(row);
4769
}
4870

49-
const tbody = document.getElementById("djDebugBrowserTimingTableBody");
50-
// This is a reasonably complete and ordered set of timing periods (2 params) and events (1 param)
51-
addRow(tbody, "domainLookupStart", "domainLookupEnd");
52-
addRow(tbody, "connectStart", "connectEnd");
53-
addRow(tbody, "requestStart", "responseEnd"); // There is no requestEnd
54-
addRow(tbody, "responseStart", "responseEnd");
55-
addRow(tbody, "domLoading", "domComplete"); // Spans the events below
56-
addRow(tbody, "domInteractive");
57-
addRow(tbody, "domContentLoadedEventStart", "domContentLoadedEventEnd");
58-
addRow(tbody, "loadEventStart", "loadEventEnd");
59-
document.getElementById("djDebugBrowserTiming").classList.remove("djdt-hidden");
71+
const djDebug = document.getElementById("djDebug");
72+
// Insert the browser timing now since it's possible for this
73+
// script to miss the initial panel load event.
74+
insertBrowserTiming();
75+
$$.onPanelRender(djDebug, "TimerPanel", insertBrowserTiming);

debug_toolbar/static/debug_toolbar/js/toolbar.js

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ const djdt = {
2020
if (!this.className) {
2121
return;
2222
}
23-
const current = document.getElementById(this.className);
23+
const panelId = this.className;
24+
const current = document.getElementById(panelId);
2425
if ($$.visible(current)) {
2526
djdt.hide_panels();
2627
} else {
@@ -39,13 +40,24 @@ const djdt = {
3940
window.location
4041
);
4142
url.searchParams.append("store_id", store_id);
42-
url.searchParams.append("panel_id", this.className);
43+
url.searchParams.append("panel_id", panelId);
4344
ajax(url).then(function (data) {
4445
inner.previousElementSibling.remove(); // Remove AJAX loader
4546
inner.innerHTML = data.content;
4647
$$.executeScripts(data.scripts);
4748
$$.applyStyles(inner);
49+
djDebug.dispatchEvent(
50+
new CustomEvent("djdt.panel.render", {
51+
detail: { panelId: panelId },
52+
})
53+
);
4854
});
55+
} else {
56+
djDebug.dispatchEvent(
57+
new CustomEvent("djdt.panel.render", {
58+
detail: { panelId: panelId },
59+
})
60+
);
4961
}
5062
}
5163
}

debug_toolbar/static/debug_toolbar/js/utils.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,21 @@ const $$ = {
77
}
88
});
99
},
10+
onPanelRender(root, panelId, fn) {
11+
/*
12+
This is a helper function to attach a handler for a `djdt.panel.render`
13+
event of a specific panel.
14+
15+
root: The container element that the listener should be attached to.
16+
panelId: The Id of the panel.
17+
fn: A function to execute when the event is triggered.
18+
*/
19+
root.addEventListener("djdt.panel.render", function (event) {
20+
if (event.detail.panelId === panelId) {
21+
fn.call(event);
22+
}
23+
});
24+
},
1025
show(element) {
1126
element.classList.remove("djdt-hidden");
1227
},

docs/changes.rst

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,13 @@ Next version
1616
* Added ``PRETTIFY_SQL`` configuration option to support controlling
1717
SQL token grouping. By default it's set to True. When set to False,
1818
a performance improvement can be seen by the SQL panel.
19-
* Fixed issue with toolbar expecting URL paths to start with `/__debug__/`
20-
while the documentation indicates it's not required.
19+
* Added a JavaScript event when a panel loads of the format
20+
``djdt.panel.[PanelId]`` where PanelId is the ``panel_id`` property
21+
of the panel's Python class. Listening for this event corrects the bug
22+
in the Timer Panel in which it didn't insert the browser timings
23+
after switching requests in the History Panel.
24+
* Fixed issue with the toolbar expecting URL paths to start with
25+
``/__debug__/`` while the documentation indicates it's not required.
2126

2227
3.2 (2020-12-03)
2328
----------------

docs/panels.rst

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -184,9 +184,9 @@ URL: https://github.com/danyi1212/django-windowsauth
184184

185185
Path: ``windows_auth.panels.LDAPPanel``
186186

187-
LDAP Operations performed during the request, including timing, request and response messages,
187+
LDAP Operations performed during the request, including timing, request and response messages,
188188
the entries received, write changes list, stack-tracing and error debugging.
189-
This panel also shows connection usage metrics when it is collected.
189+
This panel also shows connection usage metrics when it is collected.
190190
`Check out the docs <https://django-windowsauth.readthedocs.io/en/latest/howto/debug_toolbar.html>`_.
191191

192192
Line Profiler
@@ -402,3 +402,32 @@ common methods available.
402402
.. js:function:: djdt.show_toolbar
403403

404404
Shows the toolbar.
405+
406+
Events
407+
^^^^^^
408+
409+
.. js:attribute:: djdt.panel.render
410+
411+
This is an event raised when a panel is rendered. It has the property
412+
``detail.panelId`` which identifies which panel has been loaded. This
413+
event can be useful when creating custom scripts to process the HTML
414+
further.
415+
416+
An example of this for the ``CustomPanel`` would be:
417+
418+
.. code-block:: javascript
419+
420+
import { $$ } from "./utils.js";
421+
function addCustomMetrics() {
422+
// Logic to process/add custom metrics here.
423+
424+
// Be sure to cover the case of this function being called twice
425+
// due to file being loaded asynchronously.
426+
}
427+
const djDebug = document.getElementById("djDebug");
428+
$$.onPanelRender(djDebug, "CustomPanel", addCustomMetrics);
429+
// Since a panel's scripts are loaded asynchronously, it's possible that
430+
// the above statement would occur after the djdt.panel.render event has
431+
// been raised. To account for that, the rendering function should be
432+
// called here as well.
433+
addCustomMetrics();

0 commit comments

Comments
 (0)