Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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: 1 addition & 1 deletion e2e/questdb
Submodule questdb updated 164 files
13 changes: 12 additions & 1 deletion src/components/McpSetupCommand/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { CopyButton } from "../CopyButton"
import { CopyCommand } from "../icons/copy-command"
import { EXPECTED_BRIDGE_VERSION } from "../../utils/mcp/protocolVersion"
import { color } from "../../utils"
import { trackEvent } from "../../modules/ConsoleEventTracker"
import { ConsoleEvent } from "../../modules/ConsoleEventTracker/events"

export const SETUP_COMMAND = `npx @questdb/mcp-bridge@${EXPECTED_BRIDGE_VERSION} setup`

Expand Down Expand Up @@ -46,7 +48,13 @@ const CopyCommandButton = styled(CopyButton)`
}
`

export const McpSetupCommand = ({ iconSize = 16 }: { iconSize?: number }) => (
export const McpSetupCommand = ({
iconSize = 16,
source,
}: {
iconSize?: number
source: "promo" | "onboarding"
}) => (
<>
<Code>
npx <Package>@questdb/mcp-bridge@{EXPECTED_BRIDGE_VERSION}</Package> setup
Expand All @@ -57,6 +65,9 @@ export const McpSetupCommand = ({ iconSize = 16 }: { iconSize?: number }) => (
size="sm"
text={SETUP_COMMAND}
icon={<CopyCommand size={iconSize} />}
onCopy={() =>
void trackEvent(ConsoleEvent.MCP_SETUP_COMMAND_COPY, { source })
}
/>
</>
)
2 changes: 1 addition & 1 deletion src/components/NotebookOnboardingModal/Step2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ export const Step2 = ({
seamlessly
</Instruction>
<CommandBox>
<McpSetupCommand />
<McpSetupCommand source="onboarding" />
</CommandBox>
</CommandGroup>

Expand Down
34 changes: 30 additions & 4 deletions src/components/NotebookOnboardingModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { useLocalStorage } from "../../providers/LocalStorageProvider"
import { useEditor } from "../../providers/EditorProvider"
import { createDefaultNotebookViewState } from "../../store/notebook"
import { shouldShowNotebookModal } from "../../utils/notebookOnboarding"
import { trackEvent } from "../../modules/ConsoleEventTracker"
import { ConsoleEvent } from "../../modules/ConsoleEventTracker/events"
import { PRIMARY_BUTTON_HOOK } from "./shared"
import { Step1 } from "./Step1"
import { Step2 } from "./Step2"
Expand Down Expand Up @@ -134,6 +136,7 @@ export const NotebookOnboardingModal = () => {

useEffect(() => {
if (open) {
void trackEvent(ConsoleEvent.NOTEBOOK_ONBOARDING_OPEN)
updateNotebookOnboarding({ showNotebookPromo: false })
}
}, [open, updateNotebookOnboarding])
Expand All @@ -148,15 +151,27 @@ export const NotebookOnboardingModal = () => {

const createNotebook = () => {
updateNotebookOnboarding({ showMcpPromo: false })
void addBuffer({ notebookViewState: createDefaultNotebookViewState() })
void addBuffer({
notebookViewState: createDefaultNotebookViewState(),
}).then((buffer) => {
if (buffer) {
void trackEvent(ConsoleEvent.NOTEBOOK_CREATE, {
source: "onboarding_modal",
step,
})
}
})
setOpen(false)
}

return (
<RadixDialog.Root
open={open}
onOpenChange={(isOpen) => {
if (!isOpen) setOpen(false)
if (!isOpen) {
void trackEvent(ConsoleEvent.NOTEBOOK_ONBOARDING_CLOSE, { step })
setOpen(false)
}
}}
>
<RadixDialog.Portal>
Expand All @@ -172,13 +187,24 @@ export const NotebookOnboardingModal = () => {
<HiddenTitle>
{step === 0 ? "Introducing Notebooks" : "QuestDB MCP Connection"}
</HiddenTitle>
<CloseButton title="Close" onClick={() => setOpen(false)}>
<CloseButton
title="Close"
onClick={() => {
void trackEvent(ConsoleEvent.NOTEBOOK_ONBOARDING_CLOSE, { step })
setOpen(false)
}}
>
<XIcon size={16} color="#858585" weight="bold" />
</CloseButton>
{step === 0 ? (
<Step1
onCreateNotebook={createNotebook}
onNext={() => setStep(1)}
onNext={() => {
void trackEvent(ConsoleEvent.NOTEBOOK_ONBOARDING_STEP, {
step: 1,
})
setStep(1)
}}
/>
) : (
<Step2 onCreateNotebook={createNotebook} />
Expand Down
88 changes: 88 additions & 0 deletions src/modules/ConsoleEventTracker/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ export enum ConsoleEvent {
SIDEBAR_NAVIGATE = "sidebar.navigate",

TAB_ADD = "tab.add",
TAB_ARCHIVE = "tab.archive",
TAB_IMPORT = "tab.import",
TAB_EXPORT = "tab.export",
TAB_RENAME = "tab.rename",
Expand All @@ -101,4 +102,91 @@ export enum ConsoleEvent {
HELP_FEEDBACK_SUBMIT = "help.feedback_submit",

NEWS_OPEN = "news.open",

NOTEBOOK_CREATE = "notebook.create",
NOTEBOOK_DUPLICATE = "notebook.duplicate",
NOTEBOOK_BUILD_WITH_AI = "notebook.build_with_ai",
NOTEBOOK_LOAD_RETRY = "notebook.load_retry",
NOTEBOOK_LAYOUT_MODE_CHANGE = "notebook.layout_mode_change",
NOTEBOOK_CELL_ADD = "notebook.cell_add",
NOTEBOOK_CELL_LIMIT_REACHED = "notebook.cell_limit_reached",
NOTEBOOK_CELL_DELETE = "notebook.cell_delete",
NOTEBOOK_CELL_DUPLICATE = "notebook.cell_duplicate",
NOTEBOOK_CELL_MOVE = "notebook.cell_move",
NOTEBOOK_CELL_RENAME = "notebook.cell_rename",
NOTEBOOK_CELL_RUN = "notebook.cell_run",
NOTEBOOK_CELL_DRAW = "notebook.cell_draw",
NOTEBOOK_CELL_RUN_CANCEL = "notebook.cell_run_cancel",
NOTEBOOK_CELL_AUTOREFRESH_CHANGE = "notebook.cell_autorefresh_change",
NOTEBOOK_CELL_RESIZE = "notebook.cell_resize",
NOTEBOOK_CELL_EXPAND_WIDTH = "notebook.cell_expand_width",
NOTEBOOK_CELL_SIZE_RESET = "notebook.cell_size_reset",
NOTEBOOK_CELL_VIEW_CHANGE = "notebook.cell_view_change",
NOTEBOOK_CELL_VIEW_MAXIMIZE = "notebook.cell_view_maximize",
NOTEBOOK_CELL_MAXIMIZE = "notebook.cell_maximize",
NOTEBOOK_DRAW_TOGGLE = "notebook.draw_toggle",
NOTEBOOK_DRAW_REFUSED = "notebook.draw_refused",
NOTEBOOK_CHART_SETTINGS_OPEN = "notebook.chart_settings_open",
NOTEBOOK_CHART_SETTINGS_SAVE = "notebook.chart_settings_save",
NOTEBOOK_CHART_SETTINGS_CANCEL = "notebook.chart_settings_cancel",
NOTEBOOK_CHART_SAVE_BLOCKED = "notebook.chart_save_blocked",
NOTEBOOK_CHART_TYPE_CHANGE = "notebook.chart_type_change",
NOTEBOOK_CHART_RESET_AUTO = "notebook.chart_reset_auto",
NOTEBOOK_MARKDOWN_APPLY = "notebook.markdown_apply",
NOTEBOOK_VARIABLES_OPEN = "notebook.variables_open",
NOTEBOOK_VARIABLES_APPLY = "notebook.variables_apply",
NOTEBOOK_VARIABLES_IMPORT = "notebook.variables_import",
NOTEBOOK_RESULT_TAB_SWITCH = "notebook.result_tab_switch",
NOTEBOOK_ONBOARDING_OPEN = "notebook.onboarding_open",
NOTEBOOK_ONBOARDING_STEP = "notebook.onboarding_step",
NOTEBOOK_ONBOARDING_CLOSE = "notebook.onboarding_close",

MCP_ADD_CELL = "mcp.add_cell",
MCP_UPDATE_CELL = "mcp.update_cell",
MCP_DELETE_CELL = "mcp.delete_cell",
MCP_RUN_CELL = "mcp.run_cell",
MCP_RUN_QUERY = "mcp.run_query",
MCP_VALIDATE_QUERY = "mcp.validate_query",
MCP_CREATE_NOTEBOOK = "mcp.create_notebook",
MCP_ACTIVATE_NOTEBOOK = "mcp.activate_notebook",
MCP_DUPLICATE_NOTEBOOK = "mcp.duplicate_notebook",
MCP_DELETE_NOTEBOOK = "mcp.delete_notebook",
MCP_LIST_CELLS = "mcp.list_cells",
MCP_GET_CELL = "mcp.get_cell",
MCP_GET_NOTEBOOK_STATE = "mcp.get_notebook_state",
MCP_MOVE_CELL_UP = "mcp.move_cell_up",
MCP_MOVE_CELL_DOWN = "mcp.move_cell_down",
MCP_DUPLICATE_CELL = "mcp.duplicate_cell",
MCP_SET_LAYOUT_MODE = "mcp.set_layout_mode",
MCP_SET_CELL_LAYOUT = "mcp.set_cell_layout",
MCP_SET_CELL_MODE = "mcp.set_cell_mode",
MCP_SET_CELL_CHART_CONFIG = "mcp.set_cell_chart_config",
MCP_SET_CELL_AUTOREFRESH = "mcp.set_cell_autorefresh",
MCP_SET_CELL_NAME = "mcp.set_cell_name",
MCP_SET_CELL_VIEW_MAXIMIZED = "mcp.set_cell_view_maximized",
MCP_SET_CELL_MAXIMIZED = "mcp.set_cell_maximized",
MCP_APPLY_NOTEBOOK_STATE = "mcp.apply_notebook_state",
MCP_GET_TABLES = "mcp.get_tables",
MCP_GET_TABLE_SCHEMA = "mcp.get_table_schema",
MCP_GET_TABLE_DETAILS = "mcp.get_table_details",
MCP_GET_QUESTDB_TOC = "mcp.get_questdb_toc",
MCP_GET_QUESTDB_DOCUMENTATION = "mcp.get_questdb_documentation",
MCP_GET_WORKSPACE_STATE = "mcp.get_workspace_state",
MCP_GET_RECENT_USER_ACTIONS = "mcp.get_recent_user_actions",
MCP_UNKNOWN_TOOL = "mcp.unknown_tool",

MCP_PAIRING_PROMPTED = "mcp.pairing_prompted",
MCP_PAIRING_ACCEPTED = "mcp.pairing_accepted",
MCP_PAIRING_DECLINED = "mcp.pairing_declined",
MCP_MANUAL_PAIR_SUBMIT = "mcp.manual_pair_submit",
MCP_CONNECTED = "mcp.connected",
MCP_CONNECT_FAILED = "mcp.connect_failed",
MCP_DISCONNECTED = "mcp.disconnected",
MCP_DISCONNECT_CLICK = "mcp.disconnect_click",
MCP_PERMISSIONS_CHANGE = "mcp.permissions_change",

MCP_SETUP_COMMAND_COPY = "mcp.setup_command_copy",
MCP_AGENT_CHANGES_VIEW = "mcp.agent_changes_view",
MCP_AGENT_CHANGES_DISMISS = "mcp.agent_changes_dismiss",
MCP_STATUS_POPOVER_OPEN = "mcp.status_popover_open",
}
34 changes: 33 additions & 1 deletion src/modules/ConsoleEventTracker/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,12 +94,44 @@ describe("trackEvent", () => {
import.meta.env.MODE = originalMode
})

it("is a no-op before start", async () => {
it("queues events before start without writing to IDB", async () => {
await trackEvent("query.exec")
const entries = await db.events.toArray()
expect(entries).toHaveLength(0)
})

it("flushes queued pre-start events on start", async () => {
const originalMode = import.meta.env.MODE
import.meta.env.MODE = "production"

await trackEvent("notebook.onboarding_open")
await trackEvent("mcp.pairing_prompted", { a: 1 })
await start(mockConfig)

const entries = await db.events.toArray()
expect(entries.map((e) => e.name)).toEqual([
"notebook.onboarding_open",
"mcp.pairing_prompted",
])
expect(entries[1].props).toBe('{"a":1}')

import.meta.env.MODE = originalMode
})

it("discards queued events on stop", async () => {
const originalMode = import.meta.env.MODE
import.meta.env.MODE = "production"

await trackEvent("query.exec")
stop()
await start(mockConfig)

const entries = await db.events.toArray()
expect(entries).toHaveLength(0)

import.meta.env.MODE = originalMode
})

it("silently drops on IDB write error", async () => {
const originalMode = import.meta.env.MODE
import.meta.env.MODE = "production"
Expand Down
14 changes: 13 additions & 1 deletion src/modules/ConsoleEventTracker/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,21 @@ import { startPipeline, stopPipeline } from "./sendPipeline"
import type { TelemetryConfigShape } from "../../store/Telemetry/types"

const MAX_EVENTS = 10_000
const MAX_PENDING_EVENTS = 100

let started = false
let pending: Array<{ name: string; props?: string }> = []

export const trackEvent = async (
name: string,
payload?: Record<string, unknown>,
): Promise<void> => {
if (!started) return
try {
const props = payload ? JSON.stringify(payload) : undefined
if (!started) {
if (pending.length < MAX_PENDING_EVENTS) pending.push({ name, props })
return
}
await telemetryDb.putEvent(name, props)
} catch {
console.error("Could not track event in IndexedDB:", name)
Expand All @@ -30,10 +35,17 @@ export const start = async (config: TelemetryConfigShape): Promise<void> => {

await telemetryDb.trimToMaxRows(MAX_EVENTS)

const queued = pending
pending = []
for (const event of queued) {
await telemetryDb.putEvent(event.name, event.props)
}

startPipeline(config)
}

export const stop = (): void => {
stopPipeline()
started = false
pending = []
}
Loading
Loading