Skip to content

Support JS events when loading a panel. #1441

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Apr 19, 2021
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
4 changes: 4 additions & 0 deletions debug_toolbar/panels/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,10 @@ def content(self):
def scripts(self):
"""
Scripts used by the HTML content of the panel when it's displayed.

When a panel is rendered on the frontend, the ``djdt.panel.render``
JavaScript event will be dispatched. The scripts can listen for
this event to support dynamic functionality.
"""
return []

Expand Down
128 changes: 72 additions & 56 deletions debug_toolbar/static/debug_toolbar/js/timer.js
Original file line number Diff line number Diff line change
@@ -1,59 +1,75 @@
const timingOffset = performance.timing.navigationStart,
timingEnd = performance.timing.loadEventEnd,
totalTime = timingEnd - timingOffset;
function getLeft(stat) {
return ((performance.timing[stat] - timingOffset) / totalTime) * 100.0;
}
function getCSSWidth(stat, endStat) {
let width =
((performance.timing[endStat] - performance.timing[stat]) / totalTime) *
100.0;
// Calculate relative percent (same as sql panel logic)
width = (100.0 * width) / (100.0 - getLeft(stat));
return width < 1 ? "2px" : width + "%";
}
function addRow(tbody, stat, endStat) {
const row = document.createElement("tr");
if (endStat) {
// Render a start through end bar
row.innerHTML =
"<td>" +
stat.replace("Start", "") +
"</td>" +
'<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>' +
"<td>" +
(performance.timing[stat] - timingOffset) +
" (+" +
(performance.timing[endStat] - performance.timing[stat]) +
")</td>";
row.querySelector("rect").setAttribute(
"width",
getCSSWidth(stat, endStat)
);
} else {
// Render a point in time
row.innerHTML =
"<td>" +
stat +
"</td>" +
'<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>' +
"<td>" +
(performance.timing[stat] - timingOffset) +
"</td>";
row.querySelector("rect").setAttribute("width", 2);
import { $$ } from "./utils.js";

function insertBrowserTiming() {
console.log(["inserted"]);
const timingOffset = performance.timing.navigationStart,
timingEnd = performance.timing.loadEventEnd,
totalTime = timingEnd - timingOffset;
function getLeft(stat) {
return ((performance.timing[stat] - timingOffset) / totalTime) * 100.0;
}
function getCSSWidth(stat, endStat) {
let width =
((performance.timing[endStat] - performance.timing[stat]) /
totalTime) *
100.0;
// Calculate relative percent (same as sql panel logic)
width = (100.0 * width) / (100.0 - getLeft(stat));
return width < 1 ? "2px" : width + "%";
}
function addRow(tbody, stat, endStat) {
const row = document.createElement("tr");
if (endStat) {
// Render a start through end bar
row.innerHTML =
"<td>" +
stat.replace("Start", "") +
"</td>" +
'<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>' +
"<td>" +
(performance.timing[stat] - timingOffset) +
" (+" +
(performance.timing[endStat] - performance.timing[stat]) +
")</td>";
row.querySelector("rect").setAttribute(
"width",
getCSSWidth(stat, endStat)
);
} else {
// Render a point in time
row.innerHTML =
"<td>" +
stat +
"</td>" +
'<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>' +
"<td>" +
(performance.timing[stat] - timingOffset) +
"</td>";
row.querySelector("rect").setAttribute("width", 2);
}
row.querySelector("rect").setAttribute("x", getLeft(stat));
tbody.appendChild(row);
}

const browserTiming = document.getElementById("djDebugBrowserTiming");
// Determine if the browser timing section has already been rendered.
if (browserTiming.classList.contains("djdt-hidden")) {
const tbody = document.getElementById("djDebugBrowserTimingTableBody");
// This is a reasonably complete and ordered set of timing periods (2 params) and events (1 param)
addRow(tbody, "domainLookupStart", "domainLookupEnd");
addRow(tbody, "connectStart", "connectEnd");
addRow(tbody, "requestStart", "responseEnd"); // There is no requestEnd
addRow(tbody, "responseStart", "responseEnd");
addRow(tbody, "domLoading", "domComplete"); // Spans the events below
addRow(tbody, "domInteractive");
addRow(tbody, "domContentLoadedEventStart", "domContentLoadedEventEnd");
addRow(tbody, "loadEventStart", "loadEventEnd");
browserTiming.classList.remove("djdt-hidden");
}
row.querySelector("rect").setAttribute("x", getLeft(stat));
tbody.appendChild(row);
}

const tbody = document.getElementById("djDebugBrowserTimingTableBody");
// This is a reasonably complete and ordered set of timing periods (2 params) and events (1 param)
addRow(tbody, "domainLookupStart", "domainLookupEnd");
addRow(tbody, "connectStart", "connectEnd");
addRow(tbody, "requestStart", "responseEnd"); // There is no requestEnd
addRow(tbody, "responseStart", "responseEnd");
addRow(tbody, "domLoading", "domComplete"); // Spans the events below
addRow(tbody, "domInteractive");
addRow(tbody, "domContentLoadedEventStart", "domContentLoadedEventEnd");
addRow(tbody, "loadEventStart", "loadEventEnd");
document.getElementById("djDebugBrowserTiming").classList.remove("djdt-hidden");
const djDebug = document.getElementById("djDebug");
// Insert the browser timing now since it's possible for this
// script to miss the initial panel load event.
insertBrowserTiming();
$$.onPanelRender(djDebug, "TimerPanel", insertBrowserTiming);
16 changes: 14 additions & 2 deletions debug_toolbar/static/debug_toolbar/js/toolbar.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ const djdt = {
if (!this.className) {
return;
}
const current = document.getElementById(this.className);
const panelId = this.className;
const current = document.getElementById(panelId);
if ($$.visible(current)) {
djdt.hide_panels();
} else {
Expand All @@ -39,13 +40,24 @@ const djdt = {
window.location
);
url.searchParams.append("store_id", store_id);
url.searchParams.append("panel_id", this.className);
url.searchParams.append("panel_id", panelId);
ajax(url).then(function (data) {
inner.previousElementSibling.remove(); // Remove AJAX loader
inner.innerHTML = data.content;
$$.executeScripts(data.scripts);
$$.applyStyles(inner);
djDebug.dispatchEvent(
new CustomEvent("djdt.panel.render", {
detail: { panelId: panelId },
})
);
});
} else {
djDebug.dispatchEvent(
new CustomEvent("djdt.panel.render", {
detail: { panelId: panelId },
})
);
}
}
}
Expand Down
15 changes: 15 additions & 0 deletions debug_toolbar/static/debug_toolbar/js/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,21 @@ const $$ = {
}
});
},
onPanelRender(root, panelId, fn) {
/*
This is a helper function to attach a handler for a `djdt.panel.render`
event of a specific panel.

root: The container element that the listener should be attached to.
panelId: The Id of the panel.
fn: A function to execute when the event is triggered.
*/
root.addEventListener("djdt.panel.render", function (event) {
if (event.detail.panelId === panelId) {
fn.call(event);
}
});
},
show(element) {
element.classList.remove("djdt-hidden");
},
Expand Down
9 changes: 7 additions & 2 deletions docs/changes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,13 @@ Next version
* Added ``PRETTIFY_SQL`` configuration option to support controlling
SQL token grouping. By default it's set to True. When set to False,
a performance improvement can be seen by the SQL panel.
* Fixed issue with toolbar expecting URL paths to start with `/__debug__/`
while the documentation indicates it's not required.
* Added a JavaScript event when a panel loads of the format
``djdt.panel.[PanelId]`` where PanelId is the ``panel_id`` property
of the panel's Python class. Listening for this event corrects the bug
in the Timer Panel in which it didn't insert the browser timings
after switching requests in the History Panel.
* Fixed issue with the toolbar expecting URL paths to start with
``/__debug__/`` while the documentation indicates it's not required.

3.2 (2020-12-03)
----------------
Expand Down
33 changes: 31 additions & 2 deletions docs/panels.rst
Original file line number Diff line number Diff line change
Expand Up @@ -184,9 +184,9 @@ URL: https://github.com/danyi1212/django-windowsauth

Path: ``windows_auth.panels.LDAPPanel``

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

Line Profiler
Expand Down Expand Up @@ -402,3 +402,32 @@ common methods available.
.. js:function:: djdt.show_toolbar

Shows the toolbar.

Events
^^^^^^

.. js:attribute:: djdt.panel.render

This is an event raised when a panel is rendered. It has the property
``detail.panelId`` which identifies which panel has been loaded. This
event can be useful when creating custom scripts to process the HTML
further.

An example of this for the ``CustomPanel`` would be:

.. code-block:: javascript

import { $$ } from "./utils.js";
function addCustomMetrics() {
// Logic to process/add custom metrics here.

// Be sure to cover the case of this function being called twice
// due to file being loaded asynchronously.
}
const djDebug = document.getElementById("djDebug");
$$.onPanelRender(djDebug, "CustomPanel", addCustomMetrics);
// Since a panel's scripts are loaded asynchronously, it's possible that
// the above statement would occur after the djdt.panel.render event has
// been raised. To account for that, the rendering function should be
// called here as well.
addCustomMetrics();