Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
b45446d
feat: notebooks with coding agents integration
emrberk May 12, 2026
5e1647d
Merge remote-tracking branch 'origin/main' into feat/notebooks
emrberk May 12, 2026
f6c3f84
running query gives content
emrberk May 12, 2026
3bf0409
ignore gitleaks false-positive
emrberk May 12, 2026
5be4160
and another one
emrberk May 12, 2026
1c02588
proper row count cap in notebook grid, update caps for run_query, cha…
emrberk May 13, 2026
8372468
update bundle limit
emrberk May 13, 2026
57322f7
connect_web_console -> get_pairing_credentials
emrberk May 13, 2026
89549fc
top/bottom height model, ephemeral spotlight resize, drop problematic…
emrberk May 14, 2026
f109ea9
default ws prefix
emrberk May 15, 2026
4976160
reusable variables through global DECLARE block on DQL
emrberk May 20, 2026
ebe53bc
update parser
emrberk May 20, 2026
d0e2b62
convert permissions to dropdown
emrberk May 20, 2026
2b82990
context menu positioning handling on cells
emrberk May 20, 2026
1e016ac
permissions popover close issue & cell editor autocomplete
emrberk May 25, 2026
b244d26
per-query run results as status
emrberk May 25, 2026
d013944
drag/drop in non-maximized chart, echart sampling perf improvements
emrberk May 25, 2026
d6e4c48
combine multiple charts in a cell
emrberk Jun 3, 2026
a69c415
default draw mode is split-view
emrberk Jun 3, 2026
cfae4a9
address reviews
emrberk Jun 5, 2026
7b9b57b
Merge remote-tracking branch 'origin/main' into feat/notebooks
emrberk Jun 5, 2026
6afadd2
handle empty candles and scatters, fix validation key issue
emrberk Jun 5, 2026
885ca8b
check abort signal after verification, harden apply_notebook_state va…
emrberk Jun 5, 2026
4178104
duplicate and delete notebooks
emrberk Jun 5, 2026
5231bee
improve staleness gates
emrberk Jun 8, 2026
a0ff12a
reviews - editor improvements
emrberk Jun 8, 2026
a1050ce
improve freshness gate and error results
emrberk Jun 9, 2026
6fa56e6
last result persistence, move cell up/down with arrows
emrberk Jun 9, 2026
e6c429f
review fixes: snapshot keying, draw debounce, import validation, read…
emrberk Jun 9, 2026
8b4efed
skip auto-run of previously-run DDL/DML cells in apply_notebook_state…
emrberk Jun 9, 2026
057d700
fix immediate persist clobbering newer pending debounced cells (grid …
emrberk Jun 9, 2026
9b2d5ab
dangling maximized cells, clashing keyboard shortcuts
emrberk Jun 10, 2026
539779e
last tab/cell handling
emrberk Jun 10, 2026
81502c4
prevent apply_notebook_state from re-running an already-run write cell
emrberk Jun 10, 2026
491cdba
carry run history through cell duplication; honest read caps + preser…
emrberk Jun 10, 2026
28c49b2
never auto-run DDL/DML from agent flows; run_cell is the only write path
emrberk Jun 11, 2026
c8605fc
result persistence performance improvements
emrberk Jun 11, 2026
29dda30
notify user on result storage failure
emrberk Jun 15, 2026
cb49062
resizer and styling updates, export notebook
emrberk Jun 16, 2026
5c702aa
markdown cells, ui updates, ci client fix
emrberk Jun 17, 2026
dea194d
fix test failures
emrberk Jun 17, 2026
0169c76
search integration, stale chart result fix
emrberk Jun 17, 2026
d7fc620
l3 reviews
emrberk Jun 17, 2026
8c9cd41
reviews
emrberk Jun 18, 2026
e6e6a79
Merge remote-tracking branch 'origin/main' into feat/notebooks
emrberk Jun 18, 2026
2e7f97c
reviews
emrberk Jun 18, 2026
dff9951
fix: avoid import abort on tab id collision and stale chart subtype r…
emrberk Jun 18, 2026
363a353
prioritize last point on charts, separate conversation messages from …
emrberk Jun 19, 2026
56ebf77
flaky test fix
emrberk Jun 19, 2026
013d3cd
submodule
emrberk Jun 19, 2026
c083215
present available notebooks when editor open, retry reconnection with…
emrberk Jun 19, 2026
f395554
Merge remote-tracking branch 'origin/main' into feat/notebooks
emrberk Jun 19, 2026
686209c
use the new ResultGrid in notebooks
emrberk Jun 19, 2026
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
2 changes: 2 additions & 0 deletions .gitleaksignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
8f797d824d664ccecaf1ce356509db5a3e3b739d:e2e/tests/enterprise/oidc.spec.js:generic-api-key:174
8f797d824d664ccecaf1ce356509db5a3e3b739d:e2e/tests/enterprise/oidc.spec.js:generic-api-key:175
8f797d824d664ccecaf1ce356509db5a3e3b739d:e2e/tests/enterprise/oidc.spec.js:generic-api-key:176
b45446d25724585fee8379601c351e0bb1960373:e2e/tests/console/mcpBridgePermissions.spec.js:generic-api-key:9
b45446d25724585fee8379601c351e0bb1960373:src/utils/mcp/consumePendingPair.test.ts:generic-api-key:27
7 changes: 6 additions & 1 deletion e2e/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -722,8 +722,13 @@ Cypress.Commands.add("getSearchResultGroups", () => {
return cy.getByDataHook("search-result-buffer-group")
})

Cypress.Commands.add("createTabWithContent", (content, title) => {
Cypress.Commands.add("addEditorTab", () => {
cy.getByDataHook("new-tab-button").click()
cy.getByDataHook("new-tab-editor").click()
})

Cypress.Commands.add("createTabWithContent", (content, title) => {
cy.addEditorTab()
cy.get(".chrome-tab-was-just-added").should("be.visible")
cy.get(".chrome-tab-was-just-added").should("not.exist")
cy.typeQueryDirectly(content)
Expand Down
23 changes: 13 additions & 10 deletions e2e/tests/console/aiAssistant.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -396,8 +396,9 @@ describe("ai assistant", () => {
// Then
cy.getByDataHook("ai-settings-modal-step-two").should("be.visible")

// When
cy.getByDataHook("ai-settings-schema-access").click()
// When - drop permissions to None so schema tools are excluded.
cy.getByDataHook("permissions-trigger").click()
cy.getByDataHook("permission-level-none").click()
cy.getByDataHook("multi-step-modal-next-button").click()

// Then - AI chat should be available
Expand All @@ -420,9 +421,10 @@ describe("ai assistant", () => {
})
})

// When - Open settings modal and enable schema access
// When - Open settings modal and re-enable schema access
cy.getByDataHook("ai-assistant-settings-button").click()
cy.getByDataHook("ai-settings-schema-access").click()
cy.getByDataHook("permissions-trigger").click()
cy.getByDataHook("permission-level-schema").click()
cy.getByDataHook("ai-settings-save").click()
cy.get(".toast-success-container").should("be.visible").click()

Expand Down Expand Up @@ -1234,7 +1236,7 @@ describe("ai assistant", () => {

// When - Close chat and create a new tab
cy.getByDataHook("chat-window-close").click()
cy.get(".new-tab-button").click()
cy.addEditorTab()

// Then - New tab should be created (2 tabs total now)
cy.getEditorTabs().should("have.length", 2)
Expand Down Expand Up @@ -3026,7 +3028,7 @@ Syntax: \`avg(column)\`

// Close the tab that the suggestion was applied to (archive the buffer)
// First, create another tab so we can close the current one
cy.get(".new-tab-button").click()
cy.addEditorTab()
cy.getEditorTabs().should("have.length", 2)

// Close the first tab (the one with the accepted query)
Expand Down Expand Up @@ -3336,7 +3338,8 @@ describe("custom providers", () => {
cy.getByDataHook("custom-provider-remove-model").click()
cy.getByDataHook("custom-provider-model-chip").should("not.exist")

cy.getByDataHook("custom-provider-schema-access").check()
cy.getByDataHook("permissions-trigger").click()
cy.getByDataHook("permission-level-schema").click()
cy.getByDataHook("multi-step-modal-next-button").click()

cy.contains("AI Assistant activated successfully").should("be.visible")
Expand Down Expand Up @@ -3911,7 +3914,7 @@ describe("custom providers", () => {
"have.value",
"200000",
)
cy.getByDataHook("custom-provider-schema-access").should("be.checked")
cy.getByDataHook("permissions-trigger").should("contain", "Schema access")
cy.getByDataHook("custom-provider-add-model-button").should("be.disabled")

cy.getByDataHook("custom-provider-manual-model-input").type(
Expand Down Expand Up @@ -4266,8 +4269,8 @@ describe("custom providers", () => {
cy.get("[data-model='llama3']").should("exist")
cy.get("[data-model='mistral']").should("exist")

// Schema access toggle is not disabled
cy.getByDataHook("ai-settings-schema-access").should("not.be.disabled")
// Permissions select is not disabled
cy.getByDataHook("permissions-trigger").should("not.be.disabled")

// Manage models button visible
cy.getByDataHook("ai-settings-manage-models").should("be.visible")
Expand Down
243 changes: 243 additions & 0 deletions e2e/tests/console/aiAssistantPermissions.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
/// <reference types="cypress" />

const {
PROVIDERS,
getOpenAIConfiguredSettings,
getOpenAIPermissionedSettings,
createToolCallFlow,
} = require("../../utils/aiAssistant")

const installValidateIntercept = () => {
cy.intercept("GET", "**/api/v1/sql/validate*", (req) => {
const url = new URL(req.url)
const sql = (url.searchParams.get("query") || "").trim().toUpperCase()
if (sql.startsWith("SELECT") || sql.startsWith("SHOW")) {
req.reply({
statusCode: 200,
body: {
query: sql,
columns: [{ name: "c1", type: "LONG" }],
timestamp: -1,
},
})
return
}
if (
sql.startsWith("INSERT") ||
sql.startsWith("UPDATE") ||
sql.startsWith("DELETE")
) {
req.reply({ statusCode: 200, body: { queryType: "INSERT" } })
return
}
req.reply({ statusCode: 200, body: { queryType: "CREATE TABLE" } })
}).as("validate")
}

const installExecDqlIntercept = () => {
cy.intercept("GET", "**/exec*", (req) => {
req.reply({
statusCode: 200,
body: {
query: "SELECT 1",
columns: [{ name: "c1", type: "LONG" }],
dataset: [[1]],
count: 1,
timestamp: -1,
},
})
}).as("exec")
}

// get_questdb_toc fetches the docs TOC over the network; stub it so the free
// tool resolves deterministically instead of reaching questdb.com.
const installDocsTocIntercept = () => {
cy.intercept("GET", "**/web-console/toc-list.json", {
statusCode: 200,
body: { functions: ["count()"], operators: ["="], sql: ["SELECT"] },
}).as("docsToc")
}

// Every backend a granted tool can touch, stubbed in one call so a single
// conversation can drive the full surface matrix without hitting the live DB.
const installToolStubs = () => {
installValidateIntercept()
installExecDqlIntercept()
installDocsTocIntercept()
}

describe("ai assistant permissions", () => {
beforeEach(() => {
// Fail loudly on any unmocked provider request — each test scripts its own intercept.
cy.intercept("POST", PROVIDERS.openai.endpoint, (req) => {
throw new Error(
`Unhandled OpenAI request detected! Request body: ${JSON.stringify(
req.body,
).slice(0, 200)}...`,
)
}).as("unhandledOpenAI")
})

describe("PermissionsSection in settings modals", () => {
beforeEach(() => {
cy.loadConsoleWithAuth(false, getOpenAIConfiguredSettings())
})

it("renders permission level select with cascading levels in SettingsModal", () => {
cy.getByDataHook("ai-assistant-settings-button")
.should("be.visible")
.click()

cy.getByDataHook("permissions").should("be.visible")
cy.getByDataHook("permissions-trigger").should("contain", "Schema access")

// Raise to Write: trigger label updates and all levels listed in menu.
cy.getByDataHook("permissions-trigger").click()
cy.getByDataHook("permission-level-write").click()
cy.getByDataHook("permissions-trigger").should("contain", "Write")

// Drop to None: trigger label updates back.
cy.getByDataHook("permissions-trigger").click()
cy.getByDataHook("permission-level-none").click()
cy.getByDataHook("permissions-trigger").should("contain", "None")
})
})

describe("tool permission gate — one pass per level", () => {
const GRANTED = { excludes: ["PERMISSION_DENIED"] }
const GRANTED_DQL = {
includes: ['"type":"dql"'],
excludes: ["PERMISSION_DENIED"],
}
const DENIED_SCHEMA = {
includes: ["PERMISSION_DENIED", "grantSchemaAccess"],
}
const DENIED_READ = { includes: ["PERMISSION_DENIED", "'read' permission"] }
const DENIED_WRITE = {
includes: ["PERMISSION_DENIED", "'write' permission"],
}

const hasRead = (level) => level === "read" || level === "write"

// Each surface is one tool call; `expect(level)` is the assertion for that
// surface's tool result at the configured level. free tools never gate;
// schema tools need grantSchemaAccess; run_query DQL needs read, DDL/DML
// needs write.
const SURFACES = [
{
toolCall: { name: "validate_query", args: { query: "SELECT 1" } },
expect: () => GRANTED,
},
{
toolCall: { name: "suggest_query", args: { query: "SELECT 1" } },
expect: () => GRANTED,
},
{
toolCall: { name: "get_questdb_toc", args: {} },
expect: () => GRANTED,
},
{
toolCall: { name: "get_tables", args: {} },
expect: (level) => (level === "none" ? DENIED_SCHEMA : GRANTED),
},
{
toolCall: {
name: "get_table_schema",
args: { table_name: "btc_trades" },
},
expect: (level) => (level === "none" ? DENIED_SCHEMA : GRANTED),
},
{
toolCall: {
name: "get_table_details",
args: { table_name: "btc_trades" },
},
expect: (level) => (level === "none" ? DENIED_SCHEMA : GRANTED),
},
{
toolCall: {
name: "run_query",
args: { sql: "SELECT count(*) FROM btc_trades" },
},
expect: (level) => (hasRead(level) ? GRANTED_DQL : DENIED_READ),
},
{
toolCall: { name: "run_query", args: { sql: "DROP TABLE btc_trades" } },
expect: (level) => (level === "write" ? GRANTED : DENIED_WRITE),
},
]

const buildSteps = (level) => {
const steps = SURFACES.map((surface, index) => ({
toolCall: surface.toolCall,
...(index > 0
? { expectToolResult: SURFACES[index - 1].expect(level) }
: {}),
}))
steps.push({
finalResponse: {
explanation: "Permission matrix probe complete.",
sql: null,
},
expectToolResult: SURFACES[SURFACES.length - 1].expect(level),
})
return steps
}

const runMatrix = (level) => {
const flow = createToolCallFlow({
provider: "openai",
streaming: true,
question: `Probe tool permissions for the ${level} level.`,
steps: buildSteps(level),
})

flow.intercept()
cy.getByDataHook("ai-chat-button").click()
cy.getByDataHook("chat-input-textarea")
.should("be.visible")
.type(flow.question)
cy.getByDataHook("chat-send-button").click()
flow.waitForCompletion()

cy.getByDataHook("chat-message-assistant")
.should("be.visible")
.should("contain", "matrix probe complete")
}

const LEVELS = {
none: { read: false, write: false, grantSchemaAccess: false },
schema: { read: false, write: false, grantSchemaAccess: true },
read: { read: true, write: false, grantSchemaAccess: true },
write: { read: true, write: true, grantSchemaAccess: true },
}

const loadAtLevel = (level) => {
cy.loadConsoleWithAuth(
false,
getOpenAIPermissionedSettings(LEVELS[level]),
)
installToolStubs()
}

it("none: free tools pass, schema and SQL tools are denied", () => {
loadAtLevel("none")
runMatrix("none")
})

it("schema access: schema tools pass, SQL tools still denied", () => {
loadAtLevel("schema")
runMatrix("schema")
})

it("read access: DQL runs, write SQL still denied", () => {
loadAtLevel("read")
runMatrix("read")
})

it("write access: every tool surface is granted", () => {
loadAtLevel("write")
runMatrix("write")
})
})
})
Loading
Loading