Skip to content

Commit

Permalink
Web Dashboard: restricted query rendering, full query text download o…
Browse files Browse the repository at this point in the history
…ption
  • Loading branch information
iagaponenko committed Sep 13, 2024
1 parent 3876a9f commit 4e3fe06
Show file tree
Hide file tree
Showing 11 changed files with 117 additions and 26 deletions.
2 changes: 1 addition & 1 deletion src/www/qserv/css/QservCzarMySQLQueries.css
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ table#fwk-czar-mysql-queries > thead > tr > th.sticky {
}
table#fwk-czar-mysql-queries tbody th,
table#fwk-czar-mysql-queries tbody td {
vertical-align:middle;
vertical-align:top;
}
table#fwk-czar-mysql-queries pre {
padding: 0;
Expand Down
2 changes: 1 addition & 1 deletion src/www/qserv/css/QservWorkerMySQLQueries.css
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ table#fwk-worker-mysql-queries > thead > tr > th.sticky {
}
table#fwk-worker-mysql-queries tbody th,
table#fwk-worker-mysql-queries tbody td {
vertical-align:middle;
vertical-align:top;
}
table#fwk-worker-mysql-queries pre {
padding: 0;
Expand Down
2 changes: 1 addition & 1 deletion src/www/qserv/css/QservWorkerQueries.css
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ table#fwk-qserv-queries caption {
}
table#fwk-qserv-queries tbody th,
table#fwk-qserv-queries tbody td {
vertical-align:middle;
vertical-align:top;
}
table#fwk-qserv-queries pre {
padding: 0;
Expand Down
4 changes: 4 additions & 0 deletions src/www/qserv/css/StatusQueryInspector.css
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@
height:20px;
margin-top: 7px;
}
#fwk-status-query-info tbody > tr > td > a.download-query {
height:20px;
margin-top: 7px;
}
#fwk-status-query-info tbody > tr > td > pre.query_toggler {
color:#4d4dff;
}
Expand Down
14 changes: 13 additions & 1 deletion src/www/qserv/js/Common.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,18 @@ function(sqlFormatter,
static RestAPIVersion = 35;
static query2text(query, expanded) {
if (expanded) {
return sqlFormatter.format(query, Common._sqlFormatterConfig);
if (query.length > Common._max_expanded_length) {
return sqlFormatter.format(
"************* ATTENTION **************;" +
"*Query has been truncated at " + Common._max_expanded_length + " bytes since it is too long*;" +
"*Click the download button to see the full text of the query*;" +
"********************************;" +
";" +
query.substring(0, Common._max_expanded_length) + "...",
Common._sqlFormatterConfig);
} else {
return sqlFormatter.format(query, Common._sqlFormatterConfig);
}
} else if (query.length > Common._max_compact_length) {
return query.substring(0, Common._max_compact_length) + "...";
} else {
Expand All @@ -18,6 +29,7 @@ function(sqlFormatter,
}
static _sqlFormatterConfig = {"language":"mysql", "uppercase:":true, "indent":" "};
static _max_compact_length = 120;
static _max_expanded_length = 4096;
static _ivals = [
{value: 2, name: '2 sec'},
{value: 5, name: '5 sec'},
Expand Down
10 changes: 10 additions & 0 deletions src/www/qserv/js/QservCzarMySQLQueries.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ function(CSSLoader,
// queries between updates.
this._id2query = {}; // Store query text for each identifier. The dictionary gets
// updated at each refresh of the page.
this._id2url = {}; // Store URL to the query blob for each identifier
}
fwk_app_on_show() {
console.log('show: ' + this.fwk_app_name);
Expand Down Expand Up @@ -70,6 +71,7 @@ function(CSSLoader,
<th class="sticky" style="text-align:right;">Time</th>
<th class="sticky" style="text-align:right;">State</th>
<th class="sticky" style="text-align:center;"><i class="bi bi-clipboard-fill"></i></th>
<th class="sticky" style="text-align:center;"><i class="bi bi-download"></i></th>
<th class="sticky">Query</th>
</tr>
</thead>
Expand Down Expand Up @@ -132,20 +134,25 @@ function(CSSLoader,
}
_display(queries) {
const queryCopyTitle = "Click to copy the query text to the clipboard.";
const queryDownloadTitle = "Click to download the query text to your computer.";
const COL_Id = 0, COL_Command = 4, COL_Time = 5, COL_State = 6, COL_Info = 7;
let tbody = this._table().children('tbody');
if (_.isEmpty(queries.columns)) {
tbody.html('');
return;
}
this._id2query = {};
for (let id in this._id2url) {
URL.revokeObjectURL(this._id2url[id]);
}
let html = '';
for (let i in queries.rows) {
let row = queries.rows[i];
if (row[COL_Command] !== 'Query') continue;
let queryId = row[COL_Id];
let query = row[COL_Info];
this._id2query[queryId] = query;
this._id2url[queryId] = URL.createObjectURL(new Blob([query], {type: "text/plain"}));
const expanded = (queryId in this._queryId2Expanded) && this._queryId2Expanded[queryId];
const queryToggleTitle = "Click to toggle query formatting.";
const queryStyle = "color:#4d4dff;";
Expand All @@ -157,6 +164,9 @@ function(CSSLoader,
<td style="text-align:center; padding-top:0; padding-bottom:0">
<button class="btn btn-outline-dark btn-sm copy-query" style="height:20px; margin:0px;" title="${queryCopyTitle}"></button>
</td>
<td style="text-align:center; padding-top:0; padding-bottom:0">
<a class="btn btn-outline-dark btn-sm" style="height:20px; margin:0px;" title="${queryDownloadTitle}" href="${this._id2url[queryId]}" download></a>
</td>
<td class="query_toggler" title="${queryToggleTitle}"><pre class="query" style="${queryStyle}">` + this._query2text(queryId, expanded) + `<pre></td>
</tr>`;
}
Expand Down
49 changes: 30 additions & 19 deletions src/www/qserv/js/QservWorkerMySQLQueries.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@ function(CSSLoader,

constructor(name) {
super(name);
this._mySqlThreadId2Expanded = {}; // Store 'true' to allow persistent state for the expanded
this._mysqlThreadId2Expanded = {}; // Store 'true' to allow persistent state for the expanded
// queries between updates.
this._mySqlThreaId2query = {}; // Store query text for each identifier. The dictionary gets
this._mysqlThreadId2query = {}; // Store query text for each identifier. The dictionary gets
// updated at each refresh of the page.
this._mysqlThreadId2url = {}; // Store URL to the query blob for each identifier
}
fwk_app_on_show() {
console.log('show: ' + this.fwk_app_name);
Expand Down Expand Up @@ -101,6 +102,7 @@ function(CSSLoader,
<th>&nbsp;</th>
<th>&nbsp;</th>
<th>&nbsp;</th>
<th>&nbsp;</th>
</tr>
<tr>
<th class="sticky" style="text-align:right;">QID</th>
Expand All @@ -115,6 +117,7 @@ function(CSSLoader,
<th class="sticky" style="text-align:right;">Command</th>
<th class="sticky" style="text-align:right;">State</th>
<th class="sticky" style="text-align:center;"><i class="bi bi-clipboard-fill"></i></th>
<th class="sticky" style="text-align:center;"><i class="bi bi-download"></i></th>
<th class="sticky">Query</th>
</tr>
</thead>
Expand Down Expand Up @@ -217,14 +220,18 @@ function(CSSLoader,
_display(status) {
const queryInspectTitle = "Click to see detailed info (progress, messages, etc.) on the query.";
const queryCopyTitle = "Click to copy the query text to the clipboard.";
const queryDownloadTitle = "Click to download the query text to your computer.";
const COL_Id = 0, COL_Command = 4, COL_Time = 5, COL_State = 6, COL_Info = 7;
const desiredQueryCommand = this._query_command();
let tbody = this._table().children('tbody');
if (_.isEmpty(status.queries.columns)) {
tbody.html('');
return;
}
this._mySqlThreaId2query = {};
this._mysqlThreadId2query = {};
for (let id in this._mysqlThreadId2url) {
URL.revokeObjectURL(this._mysqlThreadId2url[id]);
}
let numQueriesTotal = 0;
let numQueriesDisplayed = 0;
let html = '';
Expand All @@ -234,10 +241,11 @@ function(CSSLoader,
let row = status.queries.rows[i];
const thisQueryCommand = row[COL_Command];
if ((desiredQueryCommand !== '') && (thisQueryCommand !== desiredQueryCommand)) continue;
let mySqlThreadId = row[COL_Id];
let mysqlThreadId = row[COL_Id];
let query = row[COL_Info];
this._mySqlThreaId2query[mySqlThreadId] = query;
const expanded = (mySqlThreadId in this._mySqlThreadId2Expanded) && this._mySqlThreadId2Expanded[mySqlThreadId];
this._mysqlThreadId2query[mysqlThreadId] = query;
this._mysqlThreadId2url[mysqlThreadId] = URL.createObjectURL(new Blob([query], {type: "text/plain"}));
const expanded = (mysqlThreadId in this._mysqlThreadId2Expanded) && this._mysqlThreadId2Expanded[mysqlThreadId];
const queryToggleTitle = "Click to toggle query formatting.";
const queryStyle = "color:#4d4dff;";
// Task context (if any)
Expand All @@ -247,8 +255,8 @@ function(CSSLoader,
let subChunkId = '';
let templateId = '';
let state = '';
if (_.has(status.mysql_thread_to_task, mySqlThreadId)) {
let task = status.mysql_thread_to_task[mySqlThreadId];
if (_.has(status.mysql_thread_to_task, mysqlThreadId)) {
let task = status.mysql_thread_to_task[mysqlThreadId];
queryId = task['query_id'];
jobId = task['job_id'];
chunkId = task['chunk_id'];
Expand All @@ -258,7 +266,7 @@ function(CSSLoader,
}
const rowClass = QservWorkerMySQLQueries._state2css(state);
html += `
<tr mysql_thread_id="${mySqlThreadId}" query_id="${queryId}">
<tr mysql_thread_id="${mysqlThreadId}" query_id="${queryId}">
<th style="text-align:right;"><pre>${queryId}</pre></th>`;
if (queryId === '') {
html += `
Expand All @@ -275,7 +283,7 @@ function(CSSLoader,
<td style="text-align:right;" class="${rowClass}"><pre>${subChunkId}</pre></td>
<td style="text-align:right;" class="${rowClass}"><pre>${templateId}</pre></td>
<td style="text-align:right;" class="${rowClass}"><pre>${state}</pre></td>
<th style="text-align:right;"><pre>${mySqlThreadId}</pre></th>
<th style="text-align:right;"><pre>${mysqlThreadId}</pre></th>
<td style="text-align:right;"><pre>${row[COL_Time]}</pre></td>
<td style="text-align:right;"><pre>${row[COL_Command]}</pre></td>
<td style="text-align:right;"><pre>${row[COL_State]}</pre></td>`;
Expand All @@ -289,7 +297,10 @@ function(CSSLoader,
<td style="text-align:center; padding-top:0; padding-bottom:0">
<button class="btn btn-outline-dark btn-sm copy-query" style="height:20px; margin:0px;" title="${queryCopyTitle}"></button>
</td>
<td class="query_toggler" title="${queryToggleTitle}"><pre class="query" style="${queryStyle}">` + this._query2text(mySqlThreadId, expanded) + `<pre></td>`;
<td style="text-align:center; padding-top:0; padding-bottom:0">
<a class="btn btn-outline-dark btn-sm" style="height:20px; margin:0px;" title="${queryDownloadTitle}" href="${this._mysqlThreadId2url[mysqlThreadId]}" download></a>
</td>
<td class="query_toggler" title="${queryToggleTitle}"><pre class="query" style="${queryStyle}">` + this._query2text(mysqlThreadId, expanded) + `<pre></td>`;
}
html += `
</tr>`;
Expand All @@ -305,8 +316,8 @@ function(CSSLoader,
};
let copyQueryToClipboard = function(e) {
let button = $(e.currentTarget);
let mySqlThreadId = button.parent().parent().attr("mysql_thread_id");
let query = that._mySqlThreaId2query[mySqlThreadId];
let mysqlThreadId = button.parent().parent().attr("mysql_thread_id");
let query = that._mysqlThreadId2query[mysqlThreadId];
navigator.clipboard.writeText(query,
() => {},
() => { alert("Failed to write the query to the clipboard. Please copy the text manually: " + query); }
Expand All @@ -315,18 +326,18 @@ function(CSSLoader,
let toggleQueryDisplay = function(e) {
let td = $(e.currentTarget);
let pre = td.find("pre.query");
const mySqlThreadId = td.parent().attr("mysql_thread_id");
const expanded = !((mySqlThreadId in that._mySqlThreadId2Expanded) && that._mySqlThreadId2Expanded[mySqlThreadId]);
pre.text(that._query2text(mySqlThreadId, expanded));
that._mySqlThreadId2Expanded[mySqlThreadId] = expanded;
const mysqlThreadId = td.parent().attr("mysql_thread_id");
const expanded = !((mysqlThreadId in that._mysqlThreadId2Expanded) && that._mysqlThreadId2Expanded[mysqlThreadId]);
pre.text(that._query2text(mysqlThreadId, expanded));
that._mysqlThreadId2Expanded[mysqlThreadId] = expanded;
};
tbody.find("button.inspect-query").click(displayQuery);
tbody.find("button.copy-query").click(copyQueryToClipboard);
tbody.find("td.query_toggler").click(toggleQueryDisplay);
this._set_num_queries(numQueriesTotal, numQueriesDisplayed);
}
_query2text(mySqlThreadId, expanded) {
return Common.query2text(this._mySqlThreaId2query[mySqlThreadId], expanded);
_query2text(mysqlThreadId, expanded) {
return Common.query2text(this._mysqlThreadId2query[mysqlThreadId], expanded);
}
static _state2css(state) {
switch (state) {
Expand Down
14 changes: 12 additions & 2 deletions src/www/qserv/js/QservWorkerQueries.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ function(CSSLoader,
// queries between updates.
this._id2query = {}; // Store query text for each identifier. The dictionary gets
// updated at each refresh of the page.
this._id2url = {}; // Store URL to the query blob for each identifier
}
fwk_app_on_show() {
console.log('show: ' + this.fwk_app_name);
Expand Down Expand Up @@ -71,6 +72,7 @@ function(CSSLoader,
<th class="sticky">#tasks</th>
<th class="sticky">qid</th>
<th class="sticky" style="text-align:center;"><i class="bi bi-clipboard-fill"></i></th>
<th class="sticky" style="text-align:center;"><i class="bi bi-download"></i></th>
<th class="sticky" style="text-align:center;"><i class="bi bi-info-circle-fill"></i></th>
<th class="sticky">query</th>
</tr>
Expand Down Expand Up @@ -142,11 +144,15 @@ function(CSSLoader,
*/
_display(data) {
const queryCopyTitle = "Click to copy the query text to the clipboard.";
const queryDownloadTitle = "Click to download the query text to your computer.";
const queryInspectTitle = "Click to see detailed info (progress, messages, etc.) on the query.";
const queryToggleTitle = "Click to toggle query formatting.";
const queryStyle = "color:#4d4dff;";
let html = '';
this._id2query = {};
for (let id in this._id2url) {
URL.revokeObjectURL(this._id2url[id]);
}
for (let worker in data) {
if (!data[worker].success) {
html += `
Expand All @@ -173,15 +179,19 @@ function(CSSLoader,
const queryId = scheduler.query_id_to_count[j][0];
const numTasks = scheduler.query_id_to_count[j][1];
this._id2query[queryId] = queries[queryId].query;
this._id2url[queryId] = URL.createObjectURL(new Blob([queries[queryId].query], {type: "text/plain"}));
const expanded = (queryId in this._queryId2Expanded) && this._queryId2Expanded[queryId];
htmlSchedulerQueries += `
<tr id="${queryId}">
<td><pre>${numTasks}</pre></td>
<td><pre>${queryId}</pre></td>
<th><pre>${queryId}</pre></th>
<td style="text-align:center; padding-top:0; padding-bottom:0">
<button class="btn btn-outline-dark btn-sm copy-query" style="height:20px; margin:0px;" title="${queryCopyTitle}"></button>
</td>
<td style="text-align:right; padding-top:0; padding-bottom:0">
<td style="text-align:center; padding-top:0; padding-bottom:0">
<a class="btn btn-outline-dark btn-sm" style="height:20px; margin:0px;" title="${queryDownloadTitle}" href="${this._id2url[queryId]}" download></a>
</td>
<td style="text-align:center; padding-top:0; padding-bottom:0">
<button class="btn btn-outline-info btn-sm inspect-query" style="height:20px; margin:0px;" title="${queryInspectTitle}"></button>
</td>
<td class="query_toggler" title="${queryToggleTitle}"><pre class="query" style="${queryStyle}">` + this._query2text(queryId, expanded) + `<pre></td>
Expand Down
10 changes: 10 additions & 0 deletions src/www/qserv/js/StatusActiveQueries.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ function(CSSLoader,
this._queryId2Expanded = {}; // Store 'true' to allow persistent state for the expanded
// queries between updates.
this._id2query = {}; // Store query text for each identifier
this._id2url = {}; // Store URL to the query blob for each identifier
}

/**
Expand Down Expand Up @@ -108,6 +109,7 @@ function(CSSLoader,
<th class="sticky" style="text-align:right;">Czar</th>
<th class="sticky" style="text-align:right;">QID</th>
<th class="sticky" style="text-align:center;"><i class="bi bi-clipboard-fill"></i></th>
<th class="sticky" style="text-align:center;"><i class="bi bi-download"></i></th>
<th class="sticky" class="sticky" style="text-align:center;"><i class="bi bi-info-circle-fill"></i></th>
<th class="sticky" class="sticky" style="text-align:center;"><i class="bi bi-bar-chart-steps"></i></th>
<th class="sticky">Query</th>
Expand Down Expand Up @@ -184,15 +186,20 @@ function(CSSLoader,
}
_display(data) {
this._id2query = {};
for (let id in this._id2url) {
URL.revokeObjectURL(this._id2url[id]);
}
const queryToggleTitle = "Click to toggle query formatting.";
const queryCopyTitle = "Click to copy the query text to the clipboard.";
const queryDownloadTitle = "Click to download the query text to your computer.";
const queryInspectTitle = "Click to see detailed info (progress, messages, etc.) on the query.";
const queryProgressTitle = "Click to see query progression plot.";
const queryStyle = "color:#4d4dff;";
let html = '';
for (let i in data.queries) {
let query = data.queries[i];
this._id2query[query.queryId] = query.query;
this._id2url[query.queryId] = URL.createObjectURL(new Blob([query.query], {type: "text/plain"}));
const progress = Math.floor(100. * query.completedChunks / query.totalChunks);
const scheduler = _.isUndefined(query.scheduler) ? 'Loading...' : query.scheduler.substring('Sched'.length);
const scheduler_color = _.has(this._scheduler2color, scheduler) ?
Expand Down Expand Up @@ -231,6 +238,9 @@ function(CSSLoader,
<td style="text-align:center; padding-top:0; padding-bottom:0">
<button class="btn btn-outline-dark btn-sm copy-query" style="height:20px; margin:0px;" title="${queryCopyTitle}"></button>
</td>
<td style="text-align:center; padding-top:0; padding-bottom:0">
<a class="btn btn-outline-dark btn-sm" style="height:20px; margin:0px;" title="${queryDownloadTitle}" href="${this._id2url[query.queryId]}" download></a>
</td>
<td style="text-align:center; padding-top:0; padding-bottom:0">
<button class="btn btn-outline-info btn-sm inspect-query" style="height:20px; margin:0px;" title="${queryInspectTitle}"></button>
</td>
Expand Down
Loading

0 comments on commit 4e3fe06

Please sign in to comment.