From 0b15493ccdbd4c02d7486542955f1f1409f65b91 Mon Sep 17 00:00:00 2001 From: Chris Clark Date: Sun, 20 Oct 2024 15:48:23 -0700 Subject: [PATCH 1/5] fixes 685 --- explorer/src/scss/assistant.scss | 2 +- explorer/templates/explorer/assistant.html | 36 +++++++++++----------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/explorer/src/scss/assistant.scss b/explorer/src/scss/assistant.scss index 4f9fa7ea..465cd071 100644 --- a/explorer/src/scss/assistant.scss +++ b/explorer/src/scss/assistant.scss @@ -22,7 +22,7 @@ .assistant-icons { width: 1rem; position: absolute; - right: .75rem; + right: 1rem; } #table-list { diff --git a/explorer/templates/explorer/assistant.html b/explorer/templates/explorer/assistant.html index 4e5bf18a..ba0ec4e6 100644 --- a/explorer/templates/explorer/assistant.html +++ b/explorer/templates/explorer/assistant.html @@ -30,24 +30,24 @@
- -
-
- -
-
- -
-
- -
-
- -
-
- +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
From 84999e03bd87727159651ad9d523c13d20f8e36c Mon Sep 17 00:00:00 2001 From: Chris Clark Date: Sun, 20 Oct 2024 16:19:26 -0700 Subject: [PATCH 2/5] adding download to the preview pane, cleaning up buttons --- explorer/src/js/explorer.js | 74 +++++++++++++------ explorer/templates/explorer/play.html | 6 +- explorer/templates/explorer/preview_pane.html | 1 + explorer/templates/explorer/query.html | 30 +++++--- 4 files changed, 72 insertions(+), 39 deletions(-) diff --git a/explorer/src/js/explorer.js b/explorer/src/js/explorer.js index 823d25fd..8f3ba216 100644 --- a/explorer/src/js/explorer.js +++ b/explorer/src/js/explorer.js @@ -56,6 +56,30 @@ function selectConnection() { } } +function downloadCSVFromTable() { + var table = document.getElementById("preview"); + var rows = table.querySelectorAll("tr"); + var csv = []; + + rows.forEach(function (row) { + var cols = row.querySelectorAll("td, th"); + var rowData = []; + cols.forEach(function (col) { + rowData.push(col.innerText); + }); + csv.push(rowData.join(",")); + }); + + var csvFile = new Blob([csv.join("\n")], { type: "text/csv" }); + var downloadLink = document.createElement("a"); + downloadLink.href = URL.createObjectURL(csvFile); + downloadLink.download = "preview.csv"; + + document.body.appendChild(downloadLink); + downloadLink.click(); + document.body.removeChild(downloadLink); +} + export class ExplorerEditor { constructor(queryId) { @@ -100,7 +124,7 @@ export class ExplorerEditor { this.bind(); if (cookie.get("schema_sidebar_open") === 'true') { - this.showSchema(true); + this.toggleSchema(true, true); } } @@ -189,28 +213,29 @@ export class ExplorerEditor { form.submit(); } - showSchema(noAutofocus) { - if (noAutofocus === true) { - $("#schema_frame").addClass("no-autofocus"); - } - $("#query_area").removeClass("col").addClass("col-9"); - var schema$ = $("#schema"); - schema$.addClass("col-md-3"); - schema$.show(); - $("#show_schema_button").hide(); - $("#hide_schema_button").show(); - cookie.set("schema_sidebar_open", 'true'); - return false; - } + toggleSchema(noAutofocus, doShow) { + var schema = document.getElementById("schema"); + var queryArea = document.getElementById("query_area"); + var toggleBtn = document.getElementById("toggle_schema_button"); - hideSchema() { - $("#query_area").removeClass("col-9").addClass("col"); - var schema$ = $("#schema"); - schema$.removeClass("col-3"); - schema$.hide(); - $("#hide_schema_button").hide(); - $("#show_schema_button").show(); - cookie.set("schema_sidebar_open", 'false'); + if (doShow || schema.style.display === "none" || schema.style.display === "") { // show + if (noAutofocus === true) { + schema.classList.add("no-autofocus"); + } + queryArea.classList.remove("col"); + queryArea.classList.add("col-9"); + schema.classList.add("col-md-3"); + schema.style.display = "block"; + toggleBtn.innerHTML = "Hide Schema"; + cookie.set("schema_sidebar_open", 'true'); + } else { // hide + queryArea.classList.remove("col-9"); + queryArea.classList.add("col"); + schema.classList.remove("col-md-3"); + schema.style.display = "none"; + toggleBtn.innerHTML = "Show Schema"; + cookie.set("schema_sidebar_open", 'false'); + } return false; } @@ -237,9 +262,9 @@ export class ExplorerEditor { element.addEventListener('click', toggleFavorite); }); - document.getElementById('show_schema_button')?.addEventListener('click', this.showSchema.bind(this)); - document.getElementById('hide_schema_button')?.addEventListener('click', this.hideSchema.bind(this)); + document.getElementById('toggle_schema_button')?.addEventListener('click', this.toggleSchema.bind(this)); + document.getElementById('preview-download')?.addEventListener('click', downloadCSVFromTable) $("#format_button").click(function(e) { e.preventDefault(); @@ -262,6 +287,7 @@ export class ExplorerEditor { }.bind(this)); $("#save_only_button").click(function() { + console.log("here"); var params = this.getParams(this); if(params) { this.$form.attr('action', '../' + this.queryId + '/?show=0¶ms=' + this.serializeParams(params)); diff --git a/explorer/templates/explorer/play.html b/explorer/templates/explorer/play.html index ded60a33..ba247616 100644 --- a/explorer/templates/explorer/play.html +++ b/explorer/templates/explorer/play.html @@ -59,13 +59,9 @@

{% translate "Playground" %}

class="btn btn-outline-primary">{% translate 'Save As New' %} {% export_buttons query %} - -
diff --git a/explorer/templates/explorer/preview_pane.html b/explorer/templates/explorer/preview_pane.html index d63ebe52..6fd4985c 100644 --- a/explorer/templates/explorer/preview_pane.html +++ b/explorer/templates/explorer/preview_pane.html @@ -48,6 +48,7 @@ title="Fullscreen results"> + diff --git a/explorer/templates/explorer/query.html b/explorer/templates/explorer/query.html index e07127d9..01063024 100644 --- a/explorer/templates/explorer/query.html +++ b/explorer/templates/explorer/query.html @@ -111,19 +111,29 @@

{% endif %}
{% if can_change %} - - +
+ + + +
{% export_buttons query %} - - {% else %} {% export_buttons query %} From 6211aa822285e5511b5700804cc3d1e49e8c2669 Mon Sep 17 00:00:00 2001 From: Chris Clark Date: Sun, 20 Oct 2024 16:21:45 -0700 Subject: [PATCH 3/5] removing truncation - this is basically documentation --- explorer/templates/assistant/table_description_list.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/explorer/templates/assistant/table_description_list.html b/explorer/templates/assistant/table_description_list.html index 56410c1e..8ba9ccdf 100644 --- a/explorer/templates/assistant/table_description_list.html +++ b/explorer/templates/assistant/table_description_list.html @@ -21,7 +21,7 @@

Table Annotations

{{ table_description.database_connection }} {{ table_description.table_name }} - {{ table_description.description|truncatewords:20 }} + {{ table_description.description }} From 9e2dc388c2a2cb8fab8e735ef565607c8880e200 Mon Sep 17 00:00:00 2001 From: Chris Clark Date: Thu, 24 Sep 2026 14:12:29 -0400 Subject: [PATCH 4/5] Fix preview CSV download escaping and row selection; remove stray console.log - Quote values containing commas, quotes or newlines, matching csv.writer. - Export only the header row and data rows: skip the hidden row-number column, the stats row and the stats tables nested in the headers, and the "Empty Resultset" placeholder. - Prepend a UTF-8 BOM like the server-side export, and revoke the object URL. Co-Authored-By: Claude --- explorer/src/js/explorer.js | 35 ++++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/explorer/src/js/explorer.js b/explorer/src/js/explorer.js index 8f3ba216..6e295f38 100644 --- a/explorer/src/js/explorer.js +++ b/explorer/src/js/explorer.js @@ -56,28 +56,38 @@ function selectConnection() { } } +// Quote like Python's csv.writer (used by the server-side CSV export): only when needed. +function csvEscape(value) { + if (/[",\r\n]/.test(value)) { + return '"' + value.replace(/"/g, '""') + '"'; + } + return value; +} + function downloadCSVFromTable() { var table = document.getElementById("preview"); - var rows = table.querySelectorAll("tr"); - var csv = []; - - rows.forEach(function (row) { - var cols = row.querySelectorAll("td, th"); - var rowData = []; - cols.forEach(function (col) { - rowData.push(col.innerText); - }); - csv.push(rowData.join(",")); + // Skip the hidden row-number column, the optional stats row, and the stats tables nested in the headers. + var headers = table.querySelectorAll("thead > tr:first-child > th:not(.counter)"); + var rows = [Array.from(headers)].concat( + Array.from(table.querySelectorAll("tbody > tr.data-row")).map(function (row) { + return Array.from(row.querySelectorAll(":scope > td:not(.counter)")); + }) + ); + var csv = rows.map(function (cells) { + return cells.map(function (cell) { return csvEscape(cell.innerText); }).join(","); }); - var csvFile = new Blob([csv.join("\n")], { type: "text/csv" }); + // BOM so Excel detects UTF-8, matching the server-side export. + var csvFile = new Blob(["\ufeff" + csv.join("\r\n")], { type: "text/csv;charset=utf-8" }); + var url = URL.createObjectURL(csvFile); var downloadLink = document.createElement("a"); - downloadLink.href = URL.createObjectURL(csvFile); + downloadLink.href = url; downloadLink.download = "preview.csv"; document.body.appendChild(downloadLink); downloadLink.click(); document.body.removeChild(downloadLink); + URL.revokeObjectURL(url); } export class ExplorerEditor { @@ -287,7 +297,6 @@ export class ExplorerEditor { }.bind(this)); $("#save_only_button").click(function() { - console.log("here"); var params = this.getParams(this); if(params) { this.$form.attr('action', '../' + this.queryId + '/?show=0¶ms=' + this.serializeParams(params)); From 1607480cbaa0108681cb25bff8edcfe6b67ef5e9 Mon Sep 17 00:00:00 2001 From: Chris Clark Date: Thu, 24 Sep 2026 14:12:29 -0400 Subject: [PATCH 5/5] 5.3.1: version bump and history Co-Authored-By: Claude --- HISTORY.rst | 25 +++++++++++++++++++++++++ explorer/__init__.py | 2 +- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/HISTORY.rst b/HISTORY.rst index 7fc8d7c5..10babbd8 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -5,6 +5,24 @@ Change Log This document records all notable changes to `SQL Explorer `_. This project adheres to `Semantic Versioning `_. +`5.3.1`_ (2026-09-24) +=========================== +* `#710`_: **Security fix.** The AI Assistant endpoints (``/assistant/`` and ``/assistant/history/``) did not check + permissions, and table names passed to the assistant were not validated before being used to sample rows. The + endpoints now require ``EXPLORER_PERMISSION_CHANGE``, and only tables present in the connection's schema (respecting + ``EXPLORER_SCHEMA_INCLUDE_TABLE_PREFIXES`` / ``EXPLORER_SCHEMA_EXCLUDE_TABLE_PREFIXES``) are sampled. All users of + 4.1 and later are encouraged to upgrade. + +* `#687`_: UI improvements: + + - A download button in the preview pane exports the displayed results as CSV (`#683`_). + - Fixed the assistant controls floating incorrectly while a response is being generated (`#685`_). + - Show/Hide Schema is now a single toggle button. + - Table descriptions are no longer truncated in the table description list. + +* `#703`_: The email CSV endpoint no longer requires the ``X-Requested-With`` header, and returns a 400 if no email + address is provided. + `5.3.0`_ (2024-09-24) =========================== * `#664`_: Improvements to the AI SQL Assistant: @@ -589,6 +607,8 @@ Initial Release .. _5.1.1: https://github.com/explorerhq/sql-explorer/compare/5.1.0...5.1.1 .. _5.2.0: https://github.com/explorerhq/sql-explorer/compare/5.1.1...5.2.0 .. _5.3b1: https://github.com/explorerhq/sql-explorer/compare/5.2.0...5.3b1 +.. _5.3.0: https://github.com/explorerhq/sql-explorer/compare/5.3b1...5.3.0 +.. _5.3.1: https://github.com/explorerhq/sql-explorer/compare/5.3.0...5.3.1 .. _#254: https://github.com/explorerhq/sql-explorer/pull/254 @@ -681,6 +701,9 @@ Initial Release .. _#662: https://github.com/explorerhq/sql-explorer/pull/662 .. _#660: https://github.com/explorerhq/sql-explorer/pull/660 .. _#664: https://github.com/explorerhq/sql-explorer/pull/664 +.. _#687: https://github.com/explorerhq/sql-explorer/pull/687 +.. _#703: https://github.com/explorerhq/sql-explorer/pull/703 +.. _#710: https://github.com/explorerhq/sql-explorer/pull/710 .. _#269: https://github.com/explorerhq/sql-explorer/issues/269 .. _#288: https://github.com/explorerhq/sql-explorer/issues/288 @@ -698,6 +721,8 @@ Initial Release .. _#490: https://github.com/explorerhq/sql-explorer/issues/490 .. _#492: https://github.com/explorerhq/sql-explorer/issues/492 .. _#592: https://github.com/explorerhq/sql-explorer/issues/592 +.. _#683: https://github.com/explorerhq/sql-explorer/issues/683 +.. _#685: https://github.com/explorerhq/sql-explorer/issues/685 .. _#609: https://github.com/explorerhq/sql-explorer/issues/609 .. _#610: https://github.com/explorerhq/sql-explorer/issues/610 .. _#612: https://github.com/explorerhq/sql-explorer/issues/612 diff --git a/explorer/__init__.py b/explorer/__init__.py index e1c96d05..bbf2de6a 100644 --- a/explorer/__init__.py +++ b/explorer/__init__.py @@ -1,7 +1,7 @@ __version_info__ = { "major": 5, "minor": 3, - "patch": 0, + "patch": 1, "releaselevel": "final", "serial": 0 }