diff --git a/packages/browser-tests/cypress.config.js b/packages/browser-tests/cypress.config.js index ff336654d..af92810a5 100644 --- a/packages/browser-tests/cypress.config.js +++ b/packages/browser-tests/cypress.config.js @@ -26,6 +26,13 @@ module.exports = defineConfig({ return launchOptions; }); + + on("task", { + log(message) { + console.log(message); + return null; + }, + }); }, }, }); diff --git a/packages/browser-tests/cypress/commands.js b/packages/browser-tests/cypress/commands.js index b5091713a..96dec7d64 100644 --- a/packages/browser-tests/cypress/commands.js +++ b/packages/browser-tests/cypress/commands.js @@ -9,7 +9,9 @@ addMatchImageSnapshotCommand({ blackout: [".notifications", 'button[class*="BuildVersion"'], }); -const ctrlOrCmd = Cypress.platform === "darwin" ? "{cmd}" : "{ctrl}"; +const { ctrlOrCmd, escapeRegExp } = require("./utils"); + +const baseUrl = "http://localhost:9999"; before(() => { Cypress.on("uncaught:exception", (err) => { @@ -33,10 +35,32 @@ beforeEach(() => { req.reply("{}"); } ); + + cy.intercept( + { + method: "POST", + url: "/**", + hostname: "fara.questdb.io", + }, + (req) => { + req.reply("{}"); + } + ).as("addTelemetry"); + + cy.intercept( + { + method: "POST", + url: "/**", + hostname: "alurin.questdb.io", + }, + (req) => { + req.reply("{}"); + } + ).as("addTelemetry"); cy.intercept( { method: "GET", - url: "/news", + url: "/api/news*", hostname: "cloud.questdb.com", }, (req) => { @@ -67,7 +91,8 @@ Cypress.Commands.add("typeQuery", (query) => Cypress.Commands.add("runLine", () => { cy.intercept("/exec*").as("exec"); - return cy.typeQuery(`${ctrlOrCmd}{enter}`).wait("@exec"); + cy.typeQuery(`${ctrlOrCmd}{enter}`); + cy.wait("@exec"); }); Cypress.Commands.add("clickRun", () => { @@ -146,3 +171,18 @@ Cypress.Commands.add("getCollapsedNotifications", () => Cypress.Commands.add("getExpandedNotifications", () => cy.get('[data-hook="notifications-expanded"]') ); + +Cypress.Commands.add("interceptQuery", (query, alias, response) => { + cy.intercept( + { + method: "GET", + url: new RegExp( + `^${escapeRegExp(baseUrl)}\/exec.*query=${encodeURIComponent( + escapeRegExp(query) + )}`, + "gmi" + ), + }, + response + ).as(alias); +}); diff --git a/packages/browser-tests/cypress/integration/console/editor.spec.js b/packages/browser-tests/cypress/integration/console/editor.spec.js index d303e2e7c..db1733744 100644 --- a/packages/browser-tests/cypress/integration/console/editor.spec.js +++ b/packages/browser-tests/cypress/integration/console/editor.spec.js @@ -158,7 +158,8 @@ describe("&query URL param", () => { it("should not append query if it already exists in editor", () => { const query = "select x\nfrom long_sequence(1);\n\n-- a\n-- b\n-- c"; - cy.typeQuery(query).clickRun(); + cy.typeQuery(query); + cy.clickRun(); cy.visit(`${baseUrl}?query=${encodeURIComponent(query)}&executeQuery=true`); cy.getEditorContent().should("be.visible"); cy.getEditorContent().should("have.value", query); @@ -166,7 +167,8 @@ describe("&query URL param", () => { it("should append query and scroll to it", () => { cy.typeQuery("select x from long_sequence(1);"); - cy.typeQuery("\n".repeat(20)).clickRun(); // take space so that query is not visible later, save by running + cy.typeQuery("\n".repeat(20)); + cy.clickRun(); // take space so that query is not visible later, save by running const appendedQuery = "-- hello world"; cy.visit(`${baseUrl}?query=${encodeURIComponent(appendedQuery)}`); @@ -179,10 +181,8 @@ describe("&query URL param", () => { describe("autocomplete", () => { before(() => { + cy.visit(baseUrl); cy.getEditorContent().should("be.visible"); - ["my_secrets", "my_secrets2", "my_publics"].forEach((table) => { - cy.typeQuery(`drop table if exists "${table}"`).runLine().clearEditor(); - }); [ 'create table "my_publics" ("public" string);', // We're creating another table with the same column name. @@ -202,10 +202,8 @@ describe("autocomplete", () => { }); it("should work when provided table name doesn't exist", () => { - cy.typeQuery("select * from teletubies") - .getAutocomplete() - .should("not.be.visible") - .clearEditor(); + cy.typeQuery("select * from teletubies"); + cy.getAutocomplete().should("not.be.visible").clearEditor(); cy.matchImageSnapshot(); }); @@ -261,14 +259,16 @@ describe("errors", () => { it("should mark '(200000)' as error", () => { const query = `create table test (\ncol symbol index CAPACITY (200000)`; - cy.typeQuery(query).runLine(); + cy.typeQuery(query); + cy.runLine(); cy.matchErrorMarkerPosition({ left: 237, width: 67 }); cy.matchImageSnapshot(); }); it("should mark date position as error", () => { const query = `select * from long_sequence(1) where cast(x as timestamp) = '2012-04-12T12:00:00A'`; - cy.typeQuery(query).runLine(); + cy.typeQuery(query); + cy.runLine(); cy.matchErrorMarkerPosition({ left: 506, width: 42 }); cy.getCollapsedNotifications().should("contain", "Invalid date"); @@ -290,7 +290,8 @@ describe("running query with F9", () => { cy.F9(); cy.getGridRow(0).should("contain", "1"); cy.clearEditor(); - cy.typeQuery(`select * from long_sequence(2);{leftArrow}`).F9(); + cy.typeQuery(`select * from long_sequence(2);{leftArrow}`); + cy.F9(); cy.getGridRow(1).should("contain", "2"); }); diff --git a/packages/browser-tests/cypress/integration/console/grid.spec.js b/packages/browser-tests/cypress/integration/console/grid.spec.js index 64d973aa1..837c478c6 100644 --- a/packages/browser-tests/cypress/integration/console/grid.spec.js +++ b/packages/browser-tests/cypress/integration/console/grid.spec.js @@ -1,23 +1,23 @@ /// -describe("questdb grid", () => { - before(() => { - cy.visit("http://localhost:9999"); - }); +const baseUrl = "http://localhost:9999"; +describe("questdb grid", () => { beforeEach(() => { + cy.visit(baseUrl); + cy.getEditorContent().should("be.visible"); cy.clearEditor(); }); it("when results empty", () => { - cy.typeQuery("select x from long_sequence(0)") - .runLine() - .getGridRows() - .should("have.length", 0); + cy.typeQuery("select x from long_sequence(0)"); + cy.runLine(); + cy.getGridRows().should("have.length", 0); }); it("when results have vertical scroll", () => { - cy.typeQuery(`select x from long_sequence(100)`).runLine(); + cy.typeQuery(`select x from long_sequence(100)`); + cy.runLine(); cy.wait(100); cy.getGridRows() @@ -35,7 +35,8 @@ describe("questdb grid", () => { const rows = 1000; const rowsPerPage = 128; const rowHeight = 30; - cy.typeQuery(`select x from long_sequence(${rows})`).runLine(); + cy.typeQuery(`select x from long_sequence(${rows})`); + cy.runLine(); for (let i = 0; i < rows; i += rowsPerPage) { cy.getGridViewport().scrollTo(0, i * rowHeight); @@ -48,8 +49,10 @@ describe("questdb grid", () => { cy.getGridViewport().scrollTo("bottom"); }); - it.only("copy cell into the clipboard", () => { - cy.typeQuery("select x from long_sequence(10)").runLine(); - cy.getGridCol(0).type("{ctrl}c").should("have.class", "qg-c-active-pulse"); + it("copy cell into the clipboard", () => { + cy.typeQuery("select x from long_sequence(10)"); + cy.runLine(); + cy.getGridCol(0).type("{ctrl}c"); + cy.getGridCol(0).should("have.class", "qg-c-active-pulse"); }); }); diff --git a/packages/browser-tests/cypress/integration/console/telemetry.spec.js b/packages/browser-tests/cypress/integration/console/telemetry.spec.js new file mode 100644 index 000000000..e30d7aaf9 --- /dev/null +++ b/packages/browser-tests/cypress/integration/console/telemetry.spec.js @@ -0,0 +1,75 @@ +/// + +const baseUrl = "http://localhost:9999"; + +const toggleTelemetry = (enabled) => { + // expected dataset format of the first row: + // [id, enabled, version, os, package] + cy.interceptQuery("telemetry_config LIMIT -1", "telemetryConfig", (req) => { + return req.continue((res) => { + // enable telemetry to kick start the process on the client side + res.body.dataset[0][1] = enabled; + return res; + }); + }); +}; + +describe("telemetry config", () => { + beforeEach(() => { + toggleTelemetry(true); + cy.visit(baseUrl); + }); + + it("should get telemetry config", () => { + cy.wait("@telemetryConfig").then(({ response }) => { + const columnNames = response.body.columns.map((c) => c.name); + expect(response.statusCode).to.equal(200); + ["id", "enabled", "version", "os", "package"].forEach((name) => { + expect(columnNames).to.include(name); + }); + expect(response.body.dataset[0][0]).to.be.string; + expect(response.body.dataset[0][1]).to.satisfy( + (v) => typeof v === "boolean" + ); + expect(response.body.dataset[0][2]).to.be.string; + expect(response.body.dataset[0][3]).to.be.string; + expect(typeof response.body.dataset[0][4]).to.satisfy( + (v) => v === null || typeof v === "string" + ); + }); + }); +}); + +describe("telemetry disabled", () => { + beforeEach(() => { + toggleTelemetry(false); + cy.visit(baseUrl); + }); + + it("should not start telemetry when disabled", () => { + cy.wait("@telemetryConfig").then(({ response }) => { + cy.intercept("@addTelemetry").then((interception) => { + expect(interception).to.be.null; + }); + }); + }); +}); + +describe("telemetry enabled", () => { + beforeEach(() => { + toggleTelemetry(true); + cy.visit(baseUrl); + }); + + it("should start telemetry when enabled", () => { + cy.wait("@telemetryConfig").then(({ response }) => { + cy.wait("@addTelemetry").then(({ request }) => { + const payload = JSON.parse(request.body); + expect(payload.id).to.equal(response.body.dataset[0][0]); + expect(payload.version).to.equal(response.body.dataset[0][2]); + expect(payload.os).to.equal(response.body.dataset[0][3]); + expect(payload.package).to.equal(response.body.dataset[0][4]); + }); + }); + }); +}); diff --git a/packages/browser-tests/cypress/utils.js b/packages/browser-tests/cypress/utils.js new file mode 100644 index 000000000..58e1f145f --- /dev/null +++ b/packages/browser-tests/cypress/utils.js @@ -0,0 +1,5 @@ +exports.ctrlOrCmd = Cypress.platform === "darwin" ? "{cmd}" : "{ctrl}"; + +exports.escapeRegExp = (string) => { + return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); +}; diff --git a/packages/browser-tests/questdb b/packages/browser-tests/questdb index 03345738a..05c8dbc31 160000 --- a/packages/browser-tests/questdb +++ b/packages/browser-tests/questdb @@ -1 +1 @@ -Subproject commit 03345738a1bbcc0689ea2c56742a03abc4220182 +Subproject commit 05c8dbc31a45318e72dda1b15f9fceb8f85f82ea diff --git a/packages/web-console/serve-dist.js b/packages/web-console/serve-dist.js index 31f148352..87f5e00f0 100644 --- a/packages/web-console/serve-dist.js +++ b/packages/web-console/serve-dist.js @@ -7,7 +7,10 @@ const server = http.createServer((req, res) => { const { method } = req const urlData = url.parse(req.url) - if (urlData.pathname.startsWith("/exec")) { + if ( + urlData.pathname.startsWith("/exec") || + urlData.pathname.startsWith("/settings") + ) { // proxy /exec requests to localhost:9000 const options = { hostname: "localhost", diff --git a/packages/web-console/src/scenes/Editor/Monaco/index.tsx b/packages/web-console/src/scenes/Editor/Monaco/index.tsx index 13e454c6d..99765ccd2 100644 --- a/packages/web-console/src/scenes/Editor/Monaco/index.tsx +++ b/packages/web-console/src/scenes/Editor/Monaco/index.tsx @@ -298,7 +298,10 @@ const MonacoEditor = () => { renderLineMarkings(monacoRef.current, editorRef?.current) } - if (result.type === QuestDB.Type.DDL || result.type === QuestDB.Type.DML) { + if ( + result.type === QuestDB.Type.DDL || + result.type === QuestDB.Type.DML + ) { dispatch( actions.query.addNotification({ content: ( diff --git a/packages/web-console/src/scenes/Editor/Monaco/utils.ts b/packages/web-console/src/scenes/Editor/Monaco/utils.ts index ea2c7546b..7b0c03e71 100644 --- a/packages/web-console/src/scenes/Editor/Monaco/utils.ts +++ b/packages/web-console/src/scenes/Editor/Monaco/utils.ts @@ -443,6 +443,7 @@ export const appendQuery = ( if (model) { const position = editor.getPosition() + const lineCount = model.getLineCount() if (position) { const newQueryLines = query.split("\n") @@ -490,7 +491,7 @@ export const appendQuery = ( editor.focus() if (options.appendAt === "end") { - editor.revealLine(model.getLineCount()) + editor.revealLine(lineCount) } } }