Skip to content

Commit

Permalink
Merge branch 'master' into fix/groups-access
Browse files Browse the repository at this point in the history
  • Loading branch information
benjackwhite committed Nov 23, 2023
2 parents 63dbd99 + 6e51b92 commit 16d23ad
Show file tree
Hide file tree
Showing 59 changed files with 541 additions and 820 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/storybook-chromatic.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 30
container:
image: mcr.microsoft.com/playwright:v1.29.2 # Same as @storybook/test-runner@0.13's dependency
image: mcr.microsoft.com/playwright:v1.32.2 # Same as @storybook/test-runner@0.15.2's dependency
strategy:
fail-fast: false
matrix:
Expand Down
9 changes: 4 additions & 5 deletions .storybook/test-runner.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { toMatchImageSnapshot } from 'jest-image-snapshot'
import { getStoryContext, TestRunnerConfig, TestContext } from '@storybook/test-runner'
import type { Locator, Page, LocatorScreenshotOptions } from 'playwright-core'
import { getStoryContext, TestRunnerConfig, TestContext, waitForPageReady } from '@storybook/test-runner'
import type { Locator, Page, LocatorScreenshotOptions } from '@playwright/test'
import type { Mocks } from '~/mocks/utils'
import { StoryContext } from '@storybook/types'

Expand Down Expand Up @@ -110,8 +110,7 @@ async function expectStoryToMatchSnapshot(
check = expectStoryToMatchComponentSnapshot
}

// Wait for story to load
await page.waitForSelector('.sb-show-preparing-story', { state: 'detached' })
await waitForPageReady(page)
await page.evaluate(() => {
// Stop all animations for consistent snapshots
document.body.classList.add('storybook-test-runner')
Expand All @@ -123,7 +122,7 @@ async function expectStoryToMatchSnapshot(
await page.waitForSelector(waitForSelector)
}

await page.waitForTimeout(400) // Wait for animations to finish
await page.waitForTimeout(400) // Wait for effects to finish

// Wait for all images to load
await page.waitForFunction(() =>
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile.playwright
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# We do this to ensure our reference images for visual regression tests are the same during development and in CI.
#

FROM mcr.microsoft.com/playwright:v1.29.2
FROM mcr.microsoft.com/playwright:v1.32.2

WORKDIR /work

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified frontend/__snapshots__/lemon-ui-lemon-select--long-options.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified frontend/__snapshots__/lemon-ui-utilities--overview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified frontend/__snapshots__/scenes-app-dashboards--edit.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified frontend/__snapshots__/scenes-app-dashboards--show.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified frontend/__snapshots__/scenes-other-toolbar--heatmap-dark.png
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { IconExternal } from '@posthog/icons'
import { LemonButton, LemonSkeleton } from '@posthog/lemon-ui'
import { LemonButton, LemonSelect, LemonSkeleton } from '@posthog/lemon-ui'
import clsx from 'clsx'
import { useActions, useValues } from 'kea'
import { useEffect, useRef, useState } from 'react'
Expand All @@ -8,6 +8,11 @@ import { themeLogic } from '../../themeLogic'
import { SidePanelPaneHeader } from '../components/SidePanelPane'
import { POSTHOG_WEBSITE_ORIGIN, sidePanelDocsLogic } from './sidePanelDocsLogic'

type Menu = {
name: string
url?: string
}

function SidePanelDocsSkeleton(): JSX.Element {
return (
<div className="absolute inset-0 p-4 space-y-2">
Expand All @@ -23,12 +28,50 @@ function SidePanelDocsSkeleton(): JSX.Element {
)
}

function Menu({
menu,
activeMenuName,
onChange,
}: {
menu: Menu[]
activeMenuName: string | null
onChange: (newValue: string | null) => void
}): JSX.Element {
return (
<div className="mr-auto">
<LemonSelect
placeholder="Navigate"
dropdownMatchSelectWidth={false}
onChange={onChange}
size="small"
value={activeMenuName}
options={menu.map(({ name }) => ({ label: name, value: name }))}
/>
</div>
)
}

export const SidePanelDocs = (): JSX.Element => {
const { iframeSrc, currentUrl } = useValues(sidePanelDocsLogic)
const { updatePath, unmountIframe, closeSidePanel, handleExternalUrl } = useActions(sidePanelDocsLogic)
const ref = useRef<HTMLIFrameElement>(null)
const [ready, setReady] = useState(false)
const { isDarkModeOn } = useValues(themeLogic)
const [menu, setMenu] = useState<Menu[] | null>(null)
const [activeMenuName, setActiveMenuName] = useState<string | null>(null)

const handleMenuChange = (newValue: string | null): void => {
const url = menu?.find(({ name }: Menu) => name === newValue)?.url
if (url) {
ref.current?.contentWindow?.postMessage(
{
type: 'navigate',
url,
},
'*'
)
}
}

useEffect(() => {
ref.current?.contentWindow?.postMessage(
Expand Down Expand Up @@ -57,6 +100,15 @@ export const SidePanelDocs = (): JSX.Element => {
handleExternalUrl(event.data.url)
return
}
if (event.data.type === 'docs-menu') {
setMenu(event.data.menu)
return
}

if (event.data.type === 'docs-active-menu') {
setActiveMenuName(event.data.activeMenuName)
return
}

console.warn('Unhandled iframe message from Docs:', event.data)
}
Expand All @@ -79,6 +131,7 @@ export const SidePanelDocs = (): JSX.Element => {
return (
<>
<SidePanelPaneHeader>
{menu && <Menu menu={menu} activeMenuName={activeMenuName} onChange={handleMenuChange} />}
<LemonButton
size="small"
sideIcon={<IconExternal />}
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/lib/taxonomy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,11 @@ export const KEY_MAPPING: KeyMappingInterface = {
label: 'GeoIP Disabled',
description: `Whether to skip GeoIP processing for the event.`,
},
$el_text: {
label: 'Element Text',
description: `The text of the element that was clicked. Only sent with Autocapture events.`,
examples: ['Click here!'],
},
// NOTE: This is a hack. $session_duration is a session property, not an event property
// but we don't do a good job of tracking property types, so making it a session property
// would require a large refactor, and this works (because all properties are treated as
Expand Down
12 changes: 10 additions & 2 deletions frontend/src/lib/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -391,8 +391,16 @@ export function idToKey(array: Record<string, any>[], keyField: string = 'id'):
return object
}

export function delay(ms: number): Promise<number> {
return new Promise((resolve) => window.setTimeout(resolve, ms))
export function delay(ms: number, signal?: AbortSignal): Promise<void> {
return new Promise((resolve, reject) => {
const timeoutId = setTimeout(resolve, ms)
if (signal) {
signal.addEventListener('abort', () => {
clearTimeout(timeoutId)
reject(new DOMException('Aborted', 'AbortError'))
})
}
})
}

export function clearDOMTextSelection(): void {
Expand Down
10 changes: 2 additions & 8 deletions frontend/src/queries/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import {
isTimeToSeeDataSessionsQuery,
} from './utils'

const QUERY_ASYNC_MAX_INTERVAL_SECONDS = 10
const QUERY_ASYNC_MAX_INTERVAL_SECONDS = 5
const QUERY_ASYNC_TOTAL_POLL_SECONDS = 300

//get export context for a given query
Expand Down Expand Up @@ -115,15 +115,9 @@ async function executeQuery<N extends DataNode = DataNode>(
let currentDelay = 300 // start low, because all queries will take at minimum this

while (performance.now() - pollStart < QUERY_ASYNC_TOTAL_POLL_SECONDS * 1000) {
await delay(currentDelay)
await delay(currentDelay, methodOptions?.signal)
currentDelay = Math.min(currentDelay * 2, QUERY_ASYNC_MAX_INTERVAL_SECONDS * 1000)

if (methodOptions?.signal?.aborted) {
const customAbortError = new Error('Query aborted')
customAbortError.name = 'AbortError'
throw customAbortError
}

const statusResponse = await api.queryStatus.get(response.id)

if (statusResponse.complete || statusResponse.error) {
Expand Down
10 changes: 6 additions & 4 deletions frontend/src/scenes/funnels/FunnelBarChart/FunnelBarChart.scss
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
table {
--bar-width: 0.5rem; // This should be overriden from React
--bar-row-height: 18rem;
--bar-padding-top: 1rem;
--bar-padding-bottom: 1.5rem;

width: 100%;
height: 100%;
Expand All @@ -20,8 +22,8 @@
border-bottom: 1px solid var(--border);

> td {
padding: 1.5rem 0;
padding-top: 1rem;
padding-top: var(--bar-padding-top);
padding-bottom: var(--bar-padding-bottom);
}
}

Expand All @@ -40,7 +42,7 @@
}

.StepBarLabels {
height: calc(var(--bar-row-height) - 3rem);
height: calc(var(--bar-row-height) - var(--bar-padding-top) - var(--bar-padding-bottom));
display: flex;
flex-direction: column-reverse;
align-items: flex-end;
Expand Down Expand Up @@ -68,7 +70,7 @@
align-items: flex-end;
gap: 0.125rem;
border-bottom: 1px solid var(--border);
height: calc(var(--bar-row-height) - 3rem);
height: calc(var(--bar-row-height) - var(--bar-padding-top) - var(--bar-padding-bottom));
padding: 0 1rem;

&:not(.StepBars--first) {
Expand Down
115 changes: 62 additions & 53 deletions frontend/src/scenes/funnels/FunnelBarChart/FunnelBarChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import clsx from 'clsx'
import { useValues } from 'kea'
import { useResizeObserver } from 'lib/hooks/useResizeObserver'
import { useScrollable } from 'lib/hooks/useScrollable'
import { useMemo } from 'react'
import { useLayoutEffect, useState } from 'react'
import { insightLogic } from 'scenes/insights/insightLogic'

import { ChartParams } from '~/types'
Expand All @@ -29,7 +29,8 @@ export function FunnelBarChart({ showPersonsModal: showPersonsModalProp = true }
const vizRef = useFunnelTooltip(showPersonsModal)

const [scrollRef, [isScrollableLeft, isScrollableRight]] = useScrollable()
const { height } = useResizeObserver({ ref: vizRef })
const { height: availableHeight } = useResizeObserver({ ref: vizRef })
const [scrollbarHeightPx, setScrollbarHeightPx] = useState(0)

const seriesCount = visibleStepsWithConversionMetrics[0]?.nested_breakdown?.length ?? 0
const barWidthPx =
Expand All @@ -55,57 +56,24 @@ export function FunnelBarChart({ showPersonsModal: showPersonsModalProp = true }
? 96
: 192

const table = useMemo(() => {
/** Average conversion time is only shown if it's known for at least one step. */
// != is intentional to catch undefined too
const showTime = visibleStepsWithConversionMetrics.some((step) => step.average_conversion_time != null)
const barRowHeight = `calc(${height}px - 17px - 3rem - (1.75rem * ${showTime ? 3 : 2}) - 1px)`
useLayoutEffect(() => {
if (scrollRef.current) {
setScrollbarHeightPx(scrollRef.current.offsetHeight - scrollRef.current.clientHeight)
}
}, [availableHeight])

return (
<table
/* eslint-disable-next-line react/forbid-dom-props */
style={
{
'--bar-width': `${barWidthPx}px`,
'--bar-row-height': barRowHeight,
} as FunnelBarChartCSSProperties
}
>
<colgroup>
{visibleStepsWithConversionMetrics.map((_, i) => (
<col key={i} width={0} />
))}
<col width="100%" />
{/* The last column is meant to fill up leftover space. */}
</colgroup>
<tbody>
<tr>
<td>
<StepBarLabels />
</td>
{visibleStepsWithConversionMetrics.map((step, stepIndex) => (
<td key={stepIndex}>
<StepBars step={step} stepIndex={stepIndex} showPersonsModal={showPersonsModal} />
</td>
))}
</tr>
<tr>
<td />
{visibleStepsWithConversionMetrics.map((step, stepIndex) => (
<td key={stepIndex}>
<StepLegend
step={step}
stepIndex={stepIndex}
showTime={showTime}
showPersonsModal={showPersonsModal}
/>
</td>
))}
</tr>
</tbody>
</table>
)
}, [visibleStepsWithConversionMetrics, height])
/** Average conversion time is only shown if it's known for at least one step. */
// != is intentional to catch undefined too
const showTime = visibleStepsWithConversionMetrics.some((step) => step.average_conversion_time != null)

const stepLegendRows = showTime ? 4 : 3

// rows * (row height + gap between rows) - no gap for first row + padding top and bottom
const stepLegendHeightRem = stepLegendRows * (1.5 + 0.25) - 0.25 + 2 * 0.75
const borderHeightPx = 1

// available height - border - legend - (maybe) scrollbar
const barRowHeight = `calc(${availableHeight}px - ${borderHeightPx}px - ${stepLegendHeightRem}rem - ${scrollbarHeightPx}px)`

return (
<div
Expand All @@ -118,7 +86,48 @@ export function FunnelBarChart({ showPersonsModal: showPersonsModalProp = true }
data-attr="funnel-bar-graph"
>
<div className="scrollable__inner" ref={scrollRef}>
{table}
<table
/* eslint-disable-next-line react/forbid-dom-props */
style={
{
'--bar-width': `${barWidthPx}px`,
'--bar-row-height': barRowHeight,
} as FunnelBarChartCSSProperties
}
>
<colgroup>
{visibleStepsWithConversionMetrics.map((_, i) => (
<col key={i} width={0} />
))}
<col width="100%" />
{/* The last column is meant to fill up leftover space. */}
</colgroup>
<tbody>
<tr>
<td>
<StepBarLabels />
</td>
{visibleStepsWithConversionMetrics.map((step, stepIndex) => (
<td key={stepIndex}>
<StepBars step={step} stepIndex={stepIndex} showPersonsModal={showPersonsModal} />
</td>
))}
</tr>
<tr>
<td />
{visibleStepsWithConversionMetrics.map((step, stepIndex) => (
<td key={stepIndex}>
<StepLegend
step={step}
stepIndex={stepIndex}
showTime={showTime}
showPersonsModal={showPersonsModal}
/>
</td>
))}
</tr>
</tbody>
</table>
</div>
</div>
)
Expand Down
1 change: 1 addition & 0 deletions frontend/src/scenes/funnels/FunnelBarGraph/Bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ interface BarProps {
wrapperWidth: number
}
type LabelPosition = 'inside' | 'outside'

export function Bar({
percentage: conversionPercentage,
name,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export function FunnelBarGraph({
) : null}
</header>
<div className="funnel-inner-viz">
<div className={clsx('funnel-bar-wrapper', { breakdown: isBreakdown })}>
<div className={clsx('funnel-bar-wrapper', { breakdown: isBreakdown })} aria-busy={!width}>
{!width ? null : isBreakdown ? (
<>
{step?.nested_breakdown?.map((breakdown, index) => {
Expand Down
Loading

0 comments on commit 16d23ad

Please sign in to comment.