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 755 files
51 changes: 49 additions & 2 deletions src/components/DropdownMenu/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from "react"
import * as RadixDropdownMenu from "@radix-ui/react-dropdown-menu"
import { CaretRightIcon } from "@phosphor-icons/react"
import { CaretRightIcon, CheckIcon } from "@phosphor-icons/react"
import styled from "styled-components"
import {
menuContainerStyles,
Expand All @@ -22,14 +22,61 @@ const StyledItem = styled(RadixDropdownMenu.Item)`
${menuItemStyles}
`

const RadioItem = styled(RadixDropdownMenu.RadioItem)`
const StyledRadioItem = styled(RadixDropdownMenu.RadioItem)`
${menuItemStyles}

&[data-state="checked"] {
background: ${({ theme }) => theme.color.background};
}
`

// The checked background alone is the same token as the highlighted one, so
// selection and keyboard focus are otherwise indistinguishable. The slot is
// always rendered to keep every label on one left edge.
const RadioItemIndicator = styled.span`
display: inline-flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
width: 1.4rem;
color: ${({ theme }) => theme.color.pinkPrimary};

span {
display: inline-flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
svg {
width: 1.4rem;
height: 1.4rem;
}
`

type RadioItemProps = React.ComponentPropsWithoutRef<
typeof RadixDropdownMenu.RadioItem
> & {
// Shares the checkmark's slot: a row showing one is never the checked row.
indicator?: React.ReactNode
}

const RadioItem = React.forwardRef<
React.ElementRef<typeof RadixDropdownMenu.RadioItem>,
RadioItemProps
>(({ indicator, children, ...props }, ref) => (
<StyledRadioItem ref={ref} {...props}>
<RadioItemIndicator>
<RadixDropdownMenu.ItemIndicator>
<CheckIcon weight="bold" />
</RadixDropdownMenu.ItemIndicator>
{indicator}
</RadioItemIndicator>
{children}
</StyledRadioItem>
))

RadioItem.displayName = "DropdownMenuRadioItem"

type ItemProps = React.ComponentPropsWithoutRef<
typeof RadixDropdownMenu.Item
> & {
Expand Down
3 changes: 3 additions & 0 deletions src/components/NotebookOnboardingModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,9 @@ export const NotebookOnboardingModal = () => {
source: "onboarding_modal",
step,
})
void trackEvent(ConsoleEvent.NOTEBOOK_TAB_OPEN, {
source: "onboarding_modal",
})
}
})
setOpen(false)
Expand Down
12 changes: 12 additions & 0 deletions src/components/ResultGrid/useGridKeyboardNav.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,18 @@ export const useGridKeyboardNav = (
[],
)

// A refresh can shrink the dataset under the focus; clamp it back into
// bounds so navigation never anchors past the last row or column.
useEffect(() => {
setFocusedCell((cell) => {
if (!cell) return cell
if (rowCount === 0 || colCount === 0) return null
const row = Math.min(cell.row, rowCount - 1)
const col = Math.min(cell.col, colCount - 1)
return row === cell.row && col === cell.col ? cell : { row, col }
})
}, [rowCount, colCount])

const onKeyDown = useCallback(
(e: React.KeyboardEvent) => {
if (!focusedCell) return
Expand Down
69 changes: 57 additions & 12 deletions src/consts/shared-definitions.json

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions src/modules/ConsoleEventTracker/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ export enum ConsoleEvent {
NEWS_OPEN = "news.open",

NOTEBOOK_CREATE = "notebook.create",
NOTEBOOK_TAB_OPEN = "notebook.tab_open",
NOTEBOOK_DUPLICATE = "notebook.duplicate",
NOTEBOOK_BUILD_WITH_AI = "notebook.build_with_ai",
NOTEBOOK_LOAD_RETRY = "notebook.load_retry",
Expand All @@ -118,6 +119,9 @@ export enum ConsoleEvent {
NOTEBOOK_CELL_DRAW = "notebook.cell_draw",
NOTEBOOK_CELL_RUN_CANCEL = "notebook.cell_run_cancel",
NOTEBOOK_CELL_AUTOREFRESH_CHANGE = "notebook.cell_autorefresh_change",
NOTEBOOK_AUTOREFRESH_DEFAULT_CHANGE = "notebook.autorefresh_default_change",
NOTEBOOK_AUTOREFRESH_RESET_OVERRIDES = "notebook.autorefresh_reset_overrides",
NOTEBOOK_REFRESH_ALL = "notebook.refresh_all",
NOTEBOOK_CELL_RESIZE = "notebook.cell_resize",
NOTEBOOK_CELL_EXPAND_WIDTH = "notebook.cell_expand_width",
NOTEBOOK_CELL_SIZE_RESET = "notebook.cell_size_reset",
Expand Down Expand Up @@ -162,6 +166,7 @@ export enum ConsoleEvent {
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_NOTEBOOK_AUTOREFRESH = "mcp.set_notebook_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",
Expand Down
2 changes: 2 additions & 0 deletions src/providers/AIConversationProvider/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { PartitionBy } from "../../utils/questdb"
import type { QueryKey } from "../../scenes/Editor/Monaco/utils"
import type { Message } from "../../utils/ai/types"
import type { RanStatus } from "../../utils/ai/runStatus"
import type { AutoRefresh } from "../../store/notebook"
import type { TokenUsage } from "../../utils/ai/aiAssistant"
import type { OperationHistory } from "../AIStatusProvider"

Expand Down Expand Up @@ -68,6 +69,7 @@ export type UserActionDigest = {
edited: Set<string>
ran: Map<string, RanStatus>
layoutModeTo?: "list" | "grid"
autoRefreshDefaultTo?: AutoRefresh
notebookStatusChange?: "archived" | "deleted"
}

Expand Down
20 changes: 20 additions & 0 deletions src/providers/AIConversationProvider/userActionDigest.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,15 @@ describe("createEmptyDigest / isEmptyDigest", () => {
])
expect(isEmptyDigest(d)).toBe(true)
})

it("is not empty after an Off autorefresh default change", () => {
// Given only a default change to Off — false must count as a value
const d = apply([
{ kind: "user_changed_autorefresh_default", bufferId: 1, value: false },
])
// Then the digest reaches the agent instead of being dropped as empty
expect(isEmptyDigest(d)).toBe(false)
})
})

describe("user_added_cell", () => {
Expand Down Expand Up @@ -141,6 +150,17 @@ describe("user_changed_layout_mode", () => {
})
})

describe("user_changed_autorefresh_default", () => {
it("stores the final value, including Off (false)", () => {
const d = apply([
{ kind: "user_changed_autorefresh_default", bufferId: 1, value: "30s" },
{ kind: "user_changed_autorefresh_default", bufferId: 1, value: false },
])
// false means "Off" — a valid final value the digest must keep
expect(d.autoRefreshDefaultTo).toBe(false)
})
})

describe("notebook lifecycle events", () => {
it("user_archived_notebook flips the flag to archived", () => {
const d = apply([{ kind: "user_archived_notebook", bufferId: 1 }])
Expand Down
4 changes: 4 additions & 0 deletions src/providers/AIConversationProvider/userActionDigest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ export const applyUserActionToDigest = (
case "user_changed_layout_mode":
digest.layoutModeTo = evt.mode
return digest
case "user_changed_autorefresh_default":
digest.autoRefreshDefaultTo = evt.value
return digest
case "user_archived_notebook":
digest.notebookStatusChange = "archived"
return digest
Expand All @@ -57,4 +60,5 @@ export const isEmptyDigest = (d: UserActionDigest): boolean =>
d.edited.size === 0 &&
d.ran.size === 0 &&
d.layoutModeTo === undefined &&
d.autoRefreshDefaultTo === undefined &&
d.notebookStatusChange === undefined
2 changes: 2 additions & 0 deletions src/providers/AIStatusProvider/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ export enum AIOperationStatus {
// Layout = positional/structural; Chart = visualization settings.
ConfiguringLayout = "Configuring layout",
ConfiguringChart = "Configuring chart",
// Auto-refresh applies to grids as well as charts — neutral wording.
ConfiguringAutoRefresh = "Configuring auto-refresh",
InspectingNotebook = "Inspecting notebook",
}

Expand Down
26 changes: 26 additions & 0 deletions src/scenes/Editor/Monaco/importTabs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -832,6 +832,32 @@ describe("sanitizeBuffer", () => {
expect(state?.settings?.variables).toEqual([{ name: "v", value: "1" }])
})

it("round-trips settings.autoRefreshDefault and drops an invalid token", () => {
const input = {
label: "Notebook",
value: "",
position: 0,
notebookViewState: {
cells: [{ id: "c1", value: "SELECT 1" }],
settings: { autoRefreshDefault: "30s" },
},
}
// A valid token survives import…
expect(
sanitizeBuffer(input).notebookViewState?.settings?.autoRefreshDefault,
).toBe("30s")
// …Off (false) is a valid value, not an absent one…
input.notebookViewState.settings = { autoRefreshDefault: false as never }
expect(
sanitizeBuffer(input).notebookViewState?.settings?.autoRefreshDefault,
).toBe(false)
// …and an unknown token is dropped.
input.notebookViewState.settings = { autoRefreshDefault: "2s" }
expect(
sanitizeBuffer(input).notebookViewState?.settings?.autoRefreshDefault,
).toBeUndefined()
})

it("keeps a fixed-interval autoRefresh token and bottomResized, drops a malformed interval", () => {
const input = {
label: "Notebook",
Expand Down
3 changes: 3 additions & 0 deletions src/scenes/Editor/Monaco/importTabs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,9 @@ const sanitizeNotebookSettings = (
return typeof o.name === "string" && typeof o.value === "string"
})
}
if (isAutoRefresh(item.autoRefreshDefault))
settings.autoRefreshDefault = item.autoRefreshDefault
if (item.autoRefreshMigrated === true) settings.autoRefreshMigrated = true
return settings
}

Expand Down
3 changes: 3 additions & 0 deletions src/scenes/Editor/Monaco/tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -716,6 +716,9 @@ export const Tabs = () => {
void trackEvent(ConsoleEvent.NOTEBOOK_CREATE, {
source: "tab_menu",
})
void trackEvent(ConsoleEvent.NOTEBOOK_TAB_OPEN, {
source: "tab_menu",
})
}
})
}}
Expand Down
10 changes: 5 additions & 5 deletions src/scenes/Editor/Notebook/DrawCanvas/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ import { toast } from "../../../../components/Toast"
import { CircleNotchSpinner } from "../../Monaco/icons"
import { eventBus } from "../../../../modules/EventBus"
import { EventType } from "../../../../modules/EventBus/types"
import { useChartFetchState } from "../chartRefresh/ChartRefreshContext"
import { useCellFetchState } from "../cellRefresh/CellRefreshContext"
import {
deriveChartLoading,
pendingChartFetchState,
} from "../chartRefresh/chartRefreshEngine"
pendingCellFetchState,
} from "../cellRefresh/cellRefreshEngine"
import { useCellResultStatus } from "../resultHydration/CellResultHydrationContext"
import {
getChartZoom,
Expand Down Expand Up @@ -85,10 +85,10 @@ export const DrawCanvas: React.FC<Props> = ({
const configAtSettingsOpenRef = useRef<ChartConfig | undefined>(undefined)
const chartRendererRef = useRef<ChartRendererHandle | null>(null)

const fetchState = useChartFetchState(cell.id)
const fetchState = useCellFetchState(cell.id)
const resultStatus = useCellResultStatus(cell.id)
const state = useMemo(
() => fetchState ?? pendingChartFetchState(cell.value),
() => fetchState ?? pendingCellFetchState(cell.value),
[fetchState, cell.value],
)
const { queries, queriesKey, settledKey, classifyBlock } = state
Expand Down
Loading
Loading