From f5a15b5240d74f63787b92654968b52847edd27f Mon Sep 17 00:00:00 2001 From: Ophir Lojkine Date: Tue, 29 Sep 2026 23:18:16 +0000 Subject: [PATCH 1/6] Make chart tooltip values clickable --- CHANGELOG.md | 2 +- .../sqlpage/migrations/01_documentation.sql | 2 +- frontend/src/apexcharts.ts | 30 ++++++++++++------ tests/end-to-end/fixtures/chart/test.ts | 31 ++++++++++++++++++- 4 files changed, 52 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d713ec79..10ad050a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## v0.47.0 (unreleased) - **Mac users:** the downloadable `sqlpage-macos.tgz` now runs natively on Apple silicon (M-series Macs) and no longer runs on Intel Macs. Homebrew remains the recommended and easiest installation method. On an Intel Mac, [install Homebrew](https://brew.sh/) if needed, then run `brew install sqlpage` (or `brew update` followed by `brew upgrade sqlpage` if you already installed it with Homebrew). Open Terminal in your existing website folder and run `sqlpage` instead of `./sqlpage.bin`; keep your SQL files, database, and `sqlpage` configuration folder in place. Intel installations may build from source and take longer; see the [macOS installation guide](https://sql-page.com/your-first-sql-website/?os=macos#download) for setup and older macOS requirements. -- Chart data points can now include a `link`. Clicking a point opens that URL; when the tooltip has a text x value as its title, that title links to it too. The tooltip remains open while the pointer moves onto the link. ApexCharts was updated to [v7.6.0](https://github.com/apexcharts/apexcharts.js/releases/tag/v7.6.0) to support this. +- Chart data points can now include a `link`. Clicking a point or its tooltip value opens that URL; a text x value in the tooltip title links to it too. The tooltip remains open while the pointer moves onto the link. ApexCharts was updated to [v7.6.0](https://github.com/apexcharts/apexcharts.js/releases/tag/v7.6.0) to support this. - Updated sqlx-oldapi to v0.6.57 to fix SQL Server fallback expressions such as `ISNULL($missing, 'default')` truncating defaults or failing for date values when the bound variable is `NULL`. - Fixed MSSQL `JSON_OBJECT('key': value)` expressions being rejected by SQLPage's parser, including when used in `SET` statements or nested in `sqlpage.*` function calls. - OIDC now checks both normalized request paths and their resolved SQL files against protected prefixes, closing authentication bypasses through path and clean-URL aliases. Nonce verification also rejects provider-returned Argon2 parameters outside SQLPage's fixed low-cost profile before hashing. diff --git a/examples/official-site/sqlpage/migrations/01_documentation.sql b/examples/official-site/sqlpage/migrations/01_documentation.sql index ccb180e4..632c1d2d 100644 --- a/examples/official-site/sqlpage/migrations/01_documentation.sql +++ b/examples/official-site/sqlpage/migrations/01_documentation.sql @@ -685,7 +685,7 @@ INSERT INTO parameter(component, name, description, type, top_level, optional) S ('x', 'The value of the point on the horizontal axis. Numeric values use continuous, proportionate positioning; text values are evenly spaced categories. Set the top-level time property for dates and timestamps.', 'REAL', FALSE, FALSE), ('y', 'The value of the point on the vertical axis', 'REAL', FALSE, FALSE), ('z', 'A third value carried by the point. Used as the bubble radius in a bubble chart, and shown in the tooltip under the name given by the top-level "ztitle".', 'REAL', FALSE, TRUE), - ('link', 'A URL to open when the data point is clicked. When the tooltip has a text x value as its title, that title also links to the point.', 'URL', FALSE, TRUE), + ('link', 'A URL to open when the data point is clicked. The tooltip value also links to the point, as does a text x value when shown as the tooltip title.', 'URL', FALSE, TRUE), ('label', 'An alias for parameter "x". On a row that draws a reference line, the text to display next to the line.', 'TEXT', FALSE, TRUE), ('value', 'An alias for parameter "y"', 'REAL', FALSE, TRUE), ('series', 'If multiple series are represented and share the same y-axis, this parameter can be used to distinguish between them.', 'TEXT', FALSE, TRUE), diff --git a/frontend/src/apexcharts.ts b/frontend/src/apexcharts.ts index eb676375..21af2f1f 100644 --- a/frontend/src/apexcharts.ts +++ b/frontend/src/apexcharts.ts @@ -25,7 +25,7 @@ type TooltipArgs = { w: any; }; -function formatTooltipX( +function linkTooltipValue( value: string | number | null, link: string | undefined, ) { @@ -332,21 +332,29 @@ const sqlpage_chart = (() => { formatter: has_point_links && text_x_values ? (value, args) => - formatTooltipX(value, args?.w && pointLink(args, points)) + linkTooltipValue(value, args?.w && pointLink(args, points)) : undefined, }, y: { - formatter: (value) => { + formatter: (value, args) => { if (value == null) return ""; + let formatted: string; if (is_timeseries && chart_type === "rangeBar") { const d = new Date(value); - if (d.getHours() === 0 && d.getMinutes() === 0) - return d.toLocaleDateString(); - return d.toLocaleString(); + formatted = + d.getHours() === 0 && d.getMinutes() === 0 + ? d.toLocaleDateString() + : d.toLocaleString(); + } else { + formatted = value.toLocaleString(undefined, { + maximumFractionDigits: 2, + }); } - return value.toLocaleString(undefined, { - maximumFractionDigits: 2, - }); + const w = args?.w || args; + return linkTooltipValue( + formatted, + w?.config && pointLink({ ...args, w }, points), + ); }, }, }, @@ -404,9 +412,11 @@ const sqlpage_chart = (() => { const formatter = axis === "y" && w.config.tooltip.y.formatter; const format = (v) => formatter ? formatter(v, { seriesIndex, dataPointIndex, w }) : v; - valueSpan.innerText = Array.isArray(value) + const formatted = Array.isArray(value) ? value.map(format).join(" - ") : format(value); + if (axis === "y" && point.link) valueSpan.innerHTML = formatted; + else valueSpan.innerText = formatted; axisValue.appendChild(valueSpan); tooltip.appendChild(axisValue); } diff --git a/tests/end-to-end/fixtures/chart/test.ts b/tests/end-to-end/fixtures/chart/test.ts index 11bff5fc..1beecb12 100644 --- a/tests/end-to-end/fixtures/chart/test.ts +++ b/tests/end-to-end/fixtures/chart/test.ts @@ -172,7 +172,10 @@ test("linked text x labels keep the native bar tooltip", async ({ page }) => { await expect( tooltip.locator(".apexcharts-tooltip-series-group.apexcharts-active"), ).toHaveCount(1); - await tooltip.locator("a").click(); + await expect( + tooltip.locator(".apexcharts-tooltip-text-y-value a"), + ).toHaveAttribute("href", "/linked.sql"); + await tooltip.locator(".apexcharts-tooltip-title a").click(); await expect(page).toHaveURL(/\/linked\.sql$/); }); @@ -188,6 +191,32 @@ test("linked date x values retain ApexCharts' native date formatting", async ({ await expect(title).toBeVisible(); await expect(title).not.toContainText(/\d{13}/); await expect(title.locator("a")).toHaveCount(0); + await expect( + page.locator("#test-chart .apexcharts-tooltip-text-y-value a"), + ).toHaveAttribute("href", "/linked.sql"); +}); + +test("pie values link to their respective points", async ({ page }) => { + await renderChart(page, "link-pie"); + const slices = page.locator("#test-chart .apexcharts-pie-area"); + const link = page.locator( + "#test-chart .apexcharts-tooltip-series-group.apexcharts-active .apexcharts-tooltip-text-y-value a", + ); + await slices.nth(0).hover(); + await expect(link).toHaveAttribute("href", "/linked.sql"); + await slices.nth(1).hover(); + await expect(link).toHaveAttribute("href", "/linked-too.sql"); +}); + +test("the custom scatter tooltip renders a linked value", async ({ page }) => { + await renderChart(page, "link-scatter"); + await page + .locator("#test-chart .apexcharts-marker") + .first() + .hover({ force: true }); + await expect( + page.locator("#test-chart .apexcharts-tooltip-text-y-value a"), + ).toHaveAttribute("href", "/linked.sql"); }); test("positions complete numeric bar series on an explicit numeric axis (#733)", async ({ From 45ce82eba836d0ed770257145a856ba1082bf042 Mon Sep 17 00:00:00 2001 From: lovasoa Date: Sun, 4 Oct 2026 21:53:04 +0200 Subject: [PATCH 2/6] fix type error --- frontend/src/apexcharts.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/src/apexcharts.ts b/frontend/src/apexcharts.ts index 2369418d..652ba15a 100644 --- a/frontend/src/apexcharts.ts +++ b/frontend/src/apexcharts.ts @@ -374,7 +374,9 @@ const sqlpage_chart = (() => { const w = args?.w || args; return linkTooltipValue( formatted, - w?.config && pointLink({ ...args, w }, points), + w?.config && + args?.seriesIndex && + pointLink({ ...args, w }, points), ); }, }, From 556fb84f7344fe7891c122cab1c160838612bbce Mon Sep 17 00:00:00 2001 From: lovasoa Date: Sun, 4 Oct 2026 22:35:02 +0200 Subject: [PATCH 3/6] show all tests by default in the playwright gui --- tests/end-to-end/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/end-to-end/package.json b/tests/end-to-end/package.json index 5a72f306..186782a4 100644 --- a/tests/end-to-end/package.json +++ b/tests/end-to-end/package.json @@ -6,7 +6,7 @@ "main": "index.js", "scripts": { "test": "playwright test --reporter=line", - "gui": "playwright test --ui" + "gui": "playwright test --ui -- --projects='*'" }, "keywords": [], "author": "", From 64d7cb1cfbebb7258ffe57d3b5c1b0cbe93b3631 Mon Sep 17 00:00:00 2001 From: lovasoa Date: Sun, 4 Oct 2026 23:06:24 +0200 Subject: [PATCH 4/6] fix failing test --- tests/end-to-end/fixtures/chart/link-time.sql | 5 +++-- tests/end-to-end/fixtures/chart/test.ts | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/end-to-end/fixtures/chart/link-time.sql b/tests/end-to-end/fixtures/chart/link-time.sql index 1c21fea7..88b529d6 100644 --- a/tests/end-to-end/fixtures/chart/link-time.sql +++ b/tests/end-to-end/fixtures/chart/link-time.sql @@ -1,4 +1,5 @@ SELECT 'chart' AS component, 'test-chart' AS id, 'Chart test fixture' AS title, 'line' AS type, TRUE AS time, 8 AS marker; -SELECT 'Points' AS series, '2024-03-01' AS x, 10 AS y, '/linked.sql' AS link; -SELECT 'Points' AS series, '2024-03-02' AS x, 20 AS y, '/linked-too.sql' AS link; +SELECT 'Points' AS series, '2024-03-01' AS x, 10 AS y, '/linked-before.sql' AS link; +SELECT 'Points' AS series, '2024-03-02' AS x, 15 AS y, '/linked.sql' AS link; +SELECT 'Points' AS series, '2024-03-03' AS x, 20 AS y, '/linked-after.sql' AS link; diff --git a/tests/end-to-end/fixtures/chart/test.ts b/tests/end-to-end/fixtures/chart/test.ts index cad76c37..2979ab7d 100644 --- a/tests/end-to-end/fixtures/chart/test.ts +++ b/tests/end-to-end/fixtures/chart/test.ts @@ -192,8 +192,8 @@ test("linked date x values retain ApexCharts' native date formatting", async ({ await renderChart(page, "link-time"); await page .locator("#test-chart .apexcharts-marker") - .first() - .hover({ force: true }); + .nth(1) // the center marker: '2024-03-02' AS x, 15 AS y + .hover({ force: true }); // apexcharts displays other elements on top of the marker, we just want to hover at its position const title = page.locator("#test-chart .apexcharts-tooltip-title"); await expect(title).toBeVisible(); await expect(title).not.toContainText(/\d{13}/); From 28b911d1ed0839fe099942178a5fa5a5ac51d316 Mon Sep 17 00:00:00 2001 From: lovasoa Date: Sun, 4 Oct 2026 23:22:32 +0200 Subject: [PATCH 5/6] series index zero is valid --- frontend/src/apexcharts.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/apexcharts.ts b/frontend/src/apexcharts.ts index 652ba15a..3098f755 100644 --- a/frontend/src/apexcharts.ts +++ b/frontend/src/apexcharts.ts @@ -375,7 +375,7 @@ const sqlpage_chart = (() => { return linkTooltipValue( formatted, w?.config && - args?.seriesIndex && + args?.seriesIndex !== undefined && pointLink({ ...args, w }, points), ); }, From 8a973e4d5926392283ffef68bd64724a61eb7603 Mon Sep 17 00:00:00 2001 From: lovasoa Date: Sun, 4 Oct 2026 23:23:14 +0200 Subject: [PATCH 6/6] revert gui cli args --- tests/end-to-end/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/end-to-end/package.json b/tests/end-to-end/package.json index 186782a4..5a72f306 100644 --- a/tests/end-to-end/package.json +++ b/tests/end-to-end/package.json @@ -6,7 +6,7 @@ "main": "index.js", "scripts": { "test": "playwright test --reporter=line", - "gui": "playwright test --ui -- --projects='*'" + "gui": "playwright test --ui" }, "keywords": [], "author": "",