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
29 changes: 29 additions & 0 deletions docs/published/handbook/engineering/data-warehouse.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,35 @@ The SQL editor keeps unrun edits in browser storage, scoped to the user, project
An **Edited** label marks changes to a saved view or insight. **Discard changes** restores the saved copy already loaded in memory, then refreshes it from the server. The refresh preserves edits made after discarding.
Insights can be saved or updated before running the SQL. Updating a view still requires a successful run of the current SQL so its result types match the saved query. **Continue in a notebook** is in the update button's dropdown for saved views and insights.

## Calculated measures in BI mode

With `SQL_EDITOR_BI_MODE` enabled, select a table in the SQL editor's BI mode and choose **Add calculated measure** in the data pane.
Enter a name and an aggregate SQL formula, such as `sum(revenue) / nullIf(count(DISTINCT user_id), 0)` for average revenue per user.
Use field names from the selected table. Filters apply before the formula runs for each group on the worksheet.

The measure appears on Rows. Its menu lets you edit the name and formula, sort by the measure, or remove it.
Cancel discards the draft. Names and formulas persist with the worksheet's BI configuration; measures are not shared across worksheets.
Calculated measures cannot become dimensions or row filters.
If a measure name conflicts with a field or another result column, the generated query adds a numeric suffix to its column name. The worksheet keeps the name you entered on the measure pill and sort menu.

## Filters in BI mode

Drop a field onto the compact Filters shelf beside or below the data pane. Quick filters on the right
show the current selection; click a value to edit it, or use the checkbox beside its name to toggle it.
Wider worksheets show quick filters in two columns. In tight scenes, click a filter pill on the left
to edit its values and settings. String fields start with
**Is any of**: select several values, or type a value and press Enter. **Is none of** excludes the
selected values. An empty selection leaves all values included. Suggestions load when the picker
opens, respect the other applied filters, and show up to 100 distinct values; additional values can
always be entered manually.

Use **Between** for numeric or date fields. Both bounds are inclusive, and either can be left empty
for an open-ended range. Date-time fields include the time. Uncheck **Apply filter** in the editor to temporarily
ignore a filter without losing its settings. Click the field pill to edit the field expression, date
part, or custom SQL condition, or to remove the filter. Filter changes respect the worksheet's
auto-update setting and are preserved with its saved configuration.
Numeric filters preserve the precision of entered values. Invalid numbers show an error and prevent the worksheet from running until corrected or disabled.

## Apple Ads in Marketing analytics

Marketing analytics support is controlled by the boolean organization flag `marketing-analytics-apple-ads` and is off by default.
Expand Down
16 changes: 14 additions & 2 deletions frontend/snapshots.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9498,10 +9498,22 @@ snapshots:
hash: v1.k794b7964.22840c30d80b9e1a3bd41d5a31af2ab81a54d9f90fb82d540fb9494b44cda050.oC_UUgUXZVqKMZ6LuInaQADAzqeWAvw2dZ8T5eENhIQ
scenes-app-data-warehouse-settings-schemas--multi-schema--light:
hash: v1.k794b7964.e5d678194a8e191fdf4f8a92cd7328f1e3cb162a81d3c4d05ef608f3c61537da.ZshlecoDn8CpsaVHaONi7UiXq8r-Xi4zJSWKeg7nAP0
scenes-app-data-warehouse-sql-editor--bi-calculated-measure-editor--dark:
hash: v1.k794b7964.25fd8a22fa490bf3e3e600a984b131b28202b8172e46e1a35e090694dfe63e2f.UBgi2bw3KLPn9F1RDJIVKYgfFlzVDD2yZj4iUPufRVA
scenes-app-data-warehouse-sql-editor--bi-calculated-measure-editor--light:
hash: v1.k794b7964.66016e766d5d3b78aae28be29d15e9bbf7ebb60b48de6d488eb7b10ea4676afd.wpa1Nkk9kYDByTPyA-MjGrtArnLDfzP4S6zkO34CMfo
scenes-app-data-warehouse-sql-editor--bi-mode-worksheet--dark:
hash: v1.k794b7964.c658d01e1ed459e380f64ae18cc893a3bdd4d14d60924691224143b4133d304c.bJ8fJvADbAl-CJHuO6-1i9Ng1abedjo0nuR3uHQ42KM
hash: v1.k794b7964.ec7208e3d9f889ee6eccaec6109093fd6ea85b10ad19f1aa4789308197f2608f.IaUpl-X0QQu76brKDTNqjxOXEgO4qro9a3NSFX-n0uw
scenes-app-data-warehouse-sql-editor--bi-mode-worksheet--light:
hash: v1.k794b7964.e6e8b6fc3608d3bd734534aae37eb9a8c3d56f5979791ce904a8cf0e18f11f88.S4-L9C5xAT431duxCiXdzDM4sJw1eJ4URMS5uZnhUMI
hash: v1.k794b7964.df08de4b285910a523a4074657beabe516e0132ecd5fe87f988dd9490188f43b.G0eYvWelXNWqVgrwyW0GlLMoUDDhkChYKpjXKwm9IWs
scenes-app-data-warehouse-sql-editor--bi-quick-filters--dark:
hash: v1.k794b7964.d18442be26b311eab84c68699e59cb29c8e2c6e70a0442ea7e998b5ceae7d634.K8j9lWp-nB9fGXpZV3l__sgKeGmnwrIKSH892cSTAZk
scenes-app-data-warehouse-sql-editor--bi-quick-filters--light:
hash: v1.k794b7964.7d055020d8b9cdc001efc8b5ca146f2d22e7c916af54b7361f2d8f61d59b940f.11bvglysenXe98m_innE4VvTdCUKMhRuSS62QGmFscM
scenes-app-data-warehouse-sql-editor--bi-quick-filters-narrow--dark:
hash: v1.k794b7964.3a3d179dc8ec4fed037e1e0737ddad5dea4f828b262e35d07ae5c0dcfec1447d.dqy-xY-ApF2QSGtDMf_HIMrtyrPbFAnhhWHB8cmi4M8
scenes-app-data-warehouse-sql-editor--bi-quick-filters-narrow--light:
hash: v1.k794b7964.46ca9e07669ba124d6d051b6b0cf1dbbf30b441533a9eaa064c3492577893e93.KhY5WMXImePUoV5bH-nrNztp1axD08hcOl4kfH-uaYQ
scenes-app-data-warehouse-sql-editor--edited-insight--dark:
hash: v1.k794b7964.94eba9d351ec4c6b649fc4b09f5d4a69fed2995448c0cc1dac5d63d3df0ad20c.U6hnpHxuk9RKFoPU8bc536a9dTuzluwHE0pF4J0nPYc
scenes-app-data-warehouse-sql-editor--edited-insight--light:
Expand Down
121 changes: 121 additions & 0 deletions frontend/src/scenes/data-warehouse/editor/SQLEditorScene.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,127 @@ export const BIModeWorksheet: Story = {
},
}

export const BICalculatedMeasureEditor: Story = {
...BIModeWorksheet,
parameters: {
...BIModeWorksheet.parameters,
testOptions: {
waitForSelector: '[data-attr="bi-calculated-measure-modal"] .monaco-editor',
viewport: { width: 1050, height: 900 },
},
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement)
const addMeasure = await canvas.findByText('Add calculated measure', {}, { timeout: 15000 })
await waitFor(() => expect(addMeasure.closest('button')).toBeEnabled())
await userEvent.click(addMeasure)
const modal = within(canvasElement.ownerDocument.body)
await userEvent.type(await modal.findByLabelText('Name'), 'ARPU')
},
}

const BI_QUICK_FILTERS_CONFIG: BIConfig = {
...BI_WORKSHEET_CONFIG,
chartType: ChartDisplayType.ActionsTable,
columns: [],
values: [
{
field: biEventsField('revenue', 'float'),
aggregation: 'custom',
label: 'ARPU',
customExpression: 'sum(revenue) / nullIf(count(DISTINCT user_id), 0)',
},
],
filters: [
{ field: biEventsField('event', 'string'), operator: 'in', value: '', values: ['purchase', 'renewal'] },
{
field: biEventsField('timestamp', 'datetime'),
operator: 'between',
value: '2026-06-01 00:00:00',
valueTo: '2026-06-07 23:59:59',
},
],
}

export const BIQuickFilters: Story = {
...BIModeWorksheet,
parameters: {
...BIModeWorksheet.parameters,
pageUrl: `${urls.sqlEditor()}#${new URLSearchParams({ q: buildBIQuery(BI_QUICK_FILTERS_CONFIG)?.query ?? '', mode: 'bi', bi: JSON.stringify(BI_QUICK_FILTERS_CONFIG) })}`,
msw: {
mocks: {
...BIModeWorksheet.parameters?.msw.mocks,
post: {
...BIModeWorksheet.parameters?.msw.mocks.post,
'/api/environments/:team_id/query/HogQLQuery/': async ({ request }: { request: Request }) => {
const { query } = await request.json()
return [
200,
query.query.startsWith('SELECT DISTINCT')
? {
columns: ['value'],
types: ['String'],
results: [['purchase'], ['renewal'], ['refund'], ['trial_started']],
hasMore: false,
}
: {
columns: ['toStartOfDay(timestamp)', 'ARPU'],
types: ['DateTime', 'Float64'],
hasMore: false,
results: [24, 28, 26, 31, 35, 33, 38].map((value, index) => [
`2026-06-0${index + 1} 00:00:00`,
value,
]),
},
]
},
},
},
},
},
}

export const BIQuickFiltersNarrow: Story = {
...BIQuickFilters,
parameters: {
...BIQuickFilters.parameters,
pageUrl: `${urls.sqlEditor()}#${new URLSearchParams({
mode: 'bi',
bi: JSON.stringify({
...BI_QUICK_FILTERS_CONFIG,
filters: [
...BI_QUICK_FILTERS_CONFIG.filters,
{
field: biEventsField('properties.region', 'string'),
operator: 'in',
value: '',
values: ['North', 'West'],
},
{
field: biEventsField('properties.device', 'string'),
operator: 'in',
value: '',
values: ['Desktop'],
},
{
field: biEventsField('properties.channel', 'string'),
operator: 'not_in',
value: '',
values: ['Internal'],
},
{ field: biEventsField('revenue', 'float'), operator: 'between', value: '0', valueTo: '1000' },
{ field: biEventsField('duration_ms', 'integer'), operator: 'less_than', value: '5000' },
{ field: biEventsField('distinct_id', 'string'), operator: 'is_set', value: '' },
],
} satisfies BIConfig),
})}`,
testOptions: {
waitForSelector: '[data-attr="bi-editor-filters-pill"]',
viewport: { width: 1050, height: 900 },
},
},
}

export const LazySchema: Story = {
parameters: {
pageUrl: urls.sqlEditor({ query: 'SELECT * FROM events LIMIT 100' }),
Expand Down
24 changes: 19 additions & 5 deletions frontend/src/scenes/data-warehouse/editor/bi/BIEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import type { ReactNode } from 'react'
import { Resizer } from 'lib/components/Resizer/Resizer'
import { IconTableChart } from 'lib/lemon-ui/icons'

import { BICalculatedMeasureModal } from 'products/data_warehouse/frontend/bi/BICalculatedMeasureModal'
import { BIFilterControl } from 'products/data_warehouse/frontend/bi/BIFilterControl'

import { editorSizingLogic } from '../editorSizingLogic'
import { biEditorLogic } from './biEditorLogic'
import { BI_SHELF_PILL_DRAG_MIME_TYPE, parseBIShelfPillDragData } from './biEditorTypes'
Expand Down Expand Up @@ -44,17 +47,18 @@ export function BIEditor({ tabId, children }: { tabId: string; children: ReactNo
onDragEnd={() => setActiveDropShelf(null)}
>
<BIToolbar />
<BICalculatedMeasureModal />
<div className="flex min-h-0 flex-1">
<div
className="relative flex w-[var(--bi-side-pane-width)] shrink-0 flex-col border-r @4xl/bi-editor:w-[calc(var(--bi-side-pane-width)+12rem)] @4xl/bi-editor:flex-row"
className="relative flex w-[var(--bi-side-pane-width)] shrink-0 flex-col border-r @5xl/bi-editor:w-[calc(var(--bi-side-pane-width)+10rem)] @5xl/bi-editor:flex-row"
// eslint-disable-next-line react/forbid-dom-props
style={{ '--bi-side-pane-width': `${biSidePaneWidth}px` } as React.CSSProperties}
>
{/* The resizer measures the data pane alone, because the cards column has a fixed width */}
<div ref={biEditorResizerProps.containerRef} className="flex min-h-0 min-w-0 flex-1 flex-col">
<BIDataPane />
</div>
<div className="flex max-h-[50%] shrink-0 flex-col overflow-y-auto border-t @4xl/bi-editor:max-h-none @4xl/bi-editor:w-48 @4xl/bi-editor:border-l @4xl/bi-editor:border-t-0">
<div className="flex max-h-[40%] shrink-0 flex-col overflow-y-auto border-t @5xl/bi-editor:max-h-none @5xl/bi-editor:w-40 @5xl/bi-editor:border-l @5xl/bi-editor:border-t-0">
<BIFiltersCard />
<BIMarksCard />
</div>
Expand Down Expand Up @@ -88,9 +92,19 @@ export function BIEditor({ tabId, children }: { tabId: string; children: ReactNo
</BIShelfStrip>
<div className="flex min-h-0 flex-1 flex-col">{children}</div>
</div>
{showMeOpen ? (
<div className="hidden shrink-0 border-l bg-surface-primary @3xl/bi-editor:flex">
<BIShowMe docked />
{showMeOpen || config.filters.length > 0 ? (
<div className="hidden w-44 shrink-0 flex-col overflow-y-auto border-l bg-surface-primary @3xl/bi-editor:flex @6xl/bi-editor:w-80">
{config.filters.length > 0 && (
<section className="border-b p-2" aria-label="Quick filters">
<h3 className="mb-2 text-xs font-semibold">Filters</h3>
<div className="grid grid-cols-1 items-start gap-x-2 gap-y-2 @6xl/bi-editor:grid-cols-2">
{config.filters.map((filter, index) => (
<BIFilterControl key={filter.field.id} index={index} />
))}
</div>
</section>
)}
{showMeOpen && <BIShowMe docked />}
</div>
) : null}
</div>
Expand Down
Loading
Loading