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 } diff --git a/explorer/src/js/explorer.js b/explorer/src/js/explorer.js index 823d25fd..6e295f38 100644 --- a/explorer/src/js/explorer.js +++ b/explorer/src/js/explorer.js @@ -56,6 +56,40 @@ 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"); + // 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(","); + }); + + // 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; + downloadLink.download = "preview.csv"; + + document.body.appendChild(downloadLink); + downloadLink.click(); + document.body.removeChild(downloadLink); + URL.revokeObjectURL(url); +} + export class ExplorerEditor { constructor(queryId) { @@ -100,7 +134,7 @@ export class ExplorerEditor { this.bind(); if (cookie.get("schema_sidebar_open") === 'true') { - this.showSchema(true); + this.toggleSchema(true, true); } } @@ -189,28 +223,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 +272,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(); 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/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 }} 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 @@
- -
-
- -
-
- -
-
- -
-
- -
-
- +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
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 %}