diff --git a/.github/workflows/storybook-chromatic.yml b/.github/workflows/storybook-chromatic.yml index 9ae51e2933067..cdf2ba0504a51 100644 --- a/.github/workflows/storybook-chromatic.yml +++ b/.github/workflows/storybook-chromatic.yml @@ -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: diff --git a/.storybook/test-runner.ts b/.storybook/test-runner.ts index 0ec91135cda24..908be67cb6cab 100644 --- a/.storybook/test-runner.ts +++ b/.storybook/test-runner.ts @@ -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' @@ -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') @@ -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(() => diff --git a/Dockerfile.playwright b/Dockerfile.playwright index 7591a53141b18..e911be228f55c 100644 --- a/Dockerfile.playwright +++ b/Dockerfile.playwright @@ -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 diff --git a/frontend/__snapshots__/components-cards-insight-card--insight-card.png b/frontend/__snapshots__/components-cards-insight-card--insight-card.png index febbc6faf33b6..0a38b26952afc 100644 Binary files a/frontend/__snapshots__/components-cards-insight-card--insight-card.png and b/frontend/__snapshots__/components-cards-insight-card--insight-card.png differ diff --git a/frontend/__snapshots__/exporter-exporter--funnel-left-to-right-breakdown-insight.png b/frontend/__snapshots__/exporter-exporter--funnel-left-to-right-breakdown-insight.png index e7268e9e53d57..310cd26b65415 100644 Binary files a/frontend/__snapshots__/exporter-exporter--funnel-left-to-right-breakdown-insight.png and b/frontend/__snapshots__/exporter-exporter--funnel-left-to-right-breakdown-insight.png differ diff --git a/frontend/__snapshots__/exporter-exporter--funnel-left-to-right-insight.png b/frontend/__snapshots__/exporter-exporter--funnel-left-to-right-insight.png index dd65298c2ff8f..d4c096567c39b 100644 Binary files a/frontend/__snapshots__/exporter-exporter--funnel-left-to-right-insight.png and b/frontend/__snapshots__/exporter-exporter--funnel-left-to-right-insight.png differ diff --git a/frontend/__snapshots__/filters-cohort-filters-fields-number--basic.png b/frontend/__snapshots__/filters-cohort-filters-fields-number--basic.png index f8cb1193d87e2..d9d0a7dd0ada5 100644 Binary files a/frontend/__snapshots__/filters-cohort-filters-fields-number--basic.png and b/frontend/__snapshots__/filters-cohort-filters-fields-number--basic.png differ diff --git a/frontend/__snapshots__/filters-propertyfilters--comparing-property-filters.png b/frontend/__snapshots__/filters-propertyfilters--comparing-property-filters.png index 4bf204ad1e606..d90e64ea54ca3 100644 Binary files a/frontend/__snapshots__/filters-propertyfilters--comparing-property-filters.png and b/frontend/__snapshots__/filters-propertyfilters--comparing-property-filters.png differ diff --git a/frontend/__snapshots__/filters-propertyfilters--with-no-close-button.png b/frontend/__snapshots__/filters-propertyfilters--with-no-close-button.png index d6139bbdf9627..511e5bcc051b4 100644 Binary files a/frontend/__snapshots__/filters-propertyfilters--with-no-close-button.png and b/frontend/__snapshots__/filters-propertyfilters--with-no-close-button.png differ diff --git a/frontend/__snapshots__/lemon-ui-lemon-calendar-lemon-calendar--custom-styles.png b/frontend/__snapshots__/lemon-ui-lemon-calendar-lemon-calendar--custom-styles.png index bc7634e3b5f1e..92882cab397a8 100644 Binary files a/frontend/__snapshots__/lemon-ui-lemon-calendar-lemon-calendar--custom-styles.png and b/frontend/__snapshots__/lemon-ui-lemon-calendar-lemon-calendar--custom-styles.png differ diff --git a/frontend/__snapshots__/lemon-ui-lemon-calendar-lemon-calendar--default.png b/frontend/__snapshots__/lemon-ui-lemon-calendar-lemon-calendar--default.png index 3ac0dedd31030..9e1bea26011c1 100644 Binary files a/frontend/__snapshots__/lemon-ui-lemon-calendar-lemon-calendar--default.png and b/frontend/__snapshots__/lemon-ui-lemon-calendar-lemon-calendar--default.png differ diff --git a/frontend/__snapshots__/lemon-ui-lemon-calendar-lemon-calendar--friday-first.png b/frontend/__snapshots__/lemon-ui-lemon-calendar-lemon-calendar--friday-first.png index 8cc57d7815d4b..751a6708b994f 100644 Binary files a/frontend/__snapshots__/lemon-ui-lemon-calendar-lemon-calendar--friday-first.png and b/frontend/__snapshots__/lemon-ui-lemon-calendar-lemon-calendar--friday-first.png differ diff --git a/frontend/__snapshots__/lemon-ui-lemon-calendar-lemon-calendar--monday-first.png b/frontend/__snapshots__/lemon-ui-lemon-calendar-lemon-calendar--monday-first.png index 0e49faf8d2454..c2af14074c91a 100644 Binary files a/frontend/__snapshots__/lemon-ui-lemon-calendar-lemon-calendar--monday-first.png and b/frontend/__snapshots__/lemon-ui-lemon-calendar-lemon-calendar--monday-first.png differ diff --git a/frontend/__snapshots__/lemon-ui-lemon-calendar-lemon-calendar--multiple-months.png b/frontend/__snapshots__/lemon-ui-lemon-calendar-lemon-calendar--multiple-months.png index f17a82b5c17f5..a79d0f0a89777 100644 Binary files a/frontend/__snapshots__/lemon-ui-lemon-calendar-lemon-calendar--multiple-months.png and b/frontend/__snapshots__/lemon-ui-lemon-calendar-lemon-calendar--multiple-months.png differ diff --git a/frontend/__snapshots__/lemon-ui-lemon-calendar-lemon-calendar--saturday-first.png b/frontend/__snapshots__/lemon-ui-lemon-calendar-lemon-calendar--saturday-first.png index d22c3d4b650b3..5a0879568f37e 100644 Binary files a/frontend/__snapshots__/lemon-ui-lemon-calendar-lemon-calendar--saturday-first.png and b/frontend/__snapshots__/lemon-ui-lemon-calendar-lemon-calendar--saturday-first.png differ diff --git a/frontend/__snapshots__/lemon-ui-lemon-calendar-lemon-calendar--sunday-first.png b/frontend/__snapshots__/lemon-ui-lemon-calendar-lemon-calendar--sunday-first.png index 3ac0dedd31030..9e1bea26011c1 100644 Binary files a/frontend/__snapshots__/lemon-ui-lemon-calendar-lemon-calendar--sunday-first.png and b/frontend/__snapshots__/lemon-ui-lemon-calendar-lemon-calendar--sunday-first.png differ diff --git a/frontend/__snapshots__/lemon-ui-lemon-calendar-lemon-calendar--thursday-first.png b/frontend/__snapshots__/lemon-ui-lemon-calendar-lemon-calendar--thursday-first.png index 2daa09266b125..854876aec0706 100644 Binary files a/frontend/__snapshots__/lemon-ui-lemon-calendar-lemon-calendar--thursday-first.png and b/frontend/__snapshots__/lemon-ui-lemon-calendar-lemon-calendar--thursday-first.png differ diff --git a/frontend/__snapshots__/lemon-ui-lemon-calendar-lemon-calendar--tuesday-first.png b/frontend/__snapshots__/lemon-ui-lemon-calendar-lemon-calendar--tuesday-first.png index 5035ace9f02d5..34d43dec75bca 100644 Binary files a/frontend/__snapshots__/lemon-ui-lemon-calendar-lemon-calendar--tuesday-first.png and b/frontend/__snapshots__/lemon-ui-lemon-calendar-lemon-calendar--tuesday-first.png differ diff --git a/frontend/__snapshots__/lemon-ui-lemon-calendar-lemon-calendar--wednesday-first.png b/frontend/__snapshots__/lemon-ui-lemon-calendar-lemon-calendar--wednesday-first.png index 2677654470c84..d1ca9e9a54ff5 100644 Binary files a/frontend/__snapshots__/lemon-ui-lemon-calendar-lemon-calendar--wednesday-first.png and b/frontend/__snapshots__/lemon-ui-lemon-calendar-lemon-calendar--wednesday-first.png differ diff --git a/frontend/__snapshots__/lemon-ui-lemon-calendar-lemon-calendar-range--lemon-calendar-range.png b/frontend/__snapshots__/lemon-ui-lemon-calendar-lemon-calendar-range--lemon-calendar-range.png index 4c4220ef11640..b5e2113ee71af 100644 Binary files a/frontend/__snapshots__/lemon-ui-lemon-calendar-lemon-calendar-range--lemon-calendar-range.png and b/frontend/__snapshots__/lemon-ui-lemon-calendar-lemon-calendar-range--lemon-calendar-range.png differ diff --git a/frontend/__snapshots__/lemon-ui-lemon-calendar-lemon-calendar-range-inline--lemon-calendar-range-inline.png b/frontend/__snapshots__/lemon-ui-lemon-calendar-lemon-calendar-range-inline--lemon-calendar-range-inline.png index 4d72144b7b205..6b9f4b17649a5 100644 Binary files a/frontend/__snapshots__/lemon-ui-lemon-calendar-lemon-calendar-range-inline--lemon-calendar-range-inline.png and b/frontend/__snapshots__/lemon-ui-lemon-calendar-lemon-calendar-range-inline--lemon-calendar-range-inline.png differ diff --git a/frontend/__snapshots__/lemon-ui-lemon-calendar-lemon-calendar-select--lemon-calendar-select.png b/frontend/__snapshots__/lemon-ui-lemon-calendar-lemon-calendar-select--lemon-calendar-select.png index 1c7c6fa99c2ed..8ea8124973d82 100644 Binary files a/frontend/__snapshots__/lemon-ui-lemon-calendar-lemon-calendar-select--lemon-calendar-select.png and b/frontend/__snapshots__/lemon-ui-lemon-calendar-lemon-calendar-select--lemon-calendar-select.png differ diff --git a/frontend/__snapshots__/lemon-ui-lemon-select--long-options.png b/frontend/__snapshots__/lemon-ui-lemon-select--long-options.png index f9d8d709b0563..01c803c849fb2 100644 Binary files a/frontend/__snapshots__/lemon-ui-lemon-select--long-options.png and b/frontend/__snapshots__/lemon-ui-lemon-select--long-options.png differ diff --git a/frontend/__snapshots__/lemon-ui-utilities--overview.png b/frontend/__snapshots__/lemon-ui-utilities--overview.png index 646796a14a8f2..57866050bab70 100644 Binary files a/frontend/__snapshots__/lemon-ui-utilities--overview.png and b/frontend/__snapshots__/lemon-ui-utilities--overview.png differ diff --git a/frontend/__snapshots__/scenes-app-dashboards--edit.png b/frontend/__snapshots__/scenes-app-dashboards--edit.png index b7fe0d4edb8c0..89cb4fa56f224 100644 Binary files a/frontend/__snapshots__/scenes-app-dashboards--edit.png and b/frontend/__snapshots__/scenes-app-dashboards--edit.png differ diff --git a/frontend/__snapshots__/scenes-app-dashboards--show.png b/frontend/__snapshots__/scenes-app-dashboards--show.png index 432321bdd4a81..11b82f1a6edce 100644 Binary files a/frontend/__snapshots__/scenes-app-dashboards--show.png and b/frontend/__snapshots__/scenes-app-dashboards--show.png differ diff --git a/frontend/__snapshots__/scenes-app-experiments--complete-funnel-experiment.png b/frontend/__snapshots__/scenes-app-experiments--complete-funnel-experiment.png index 5f7c428ad21dd..3831be858dd60 100644 Binary files a/frontend/__snapshots__/scenes-app-experiments--complete-funnel-experiment.png and b/frontend/__snapshots__/scenes-app-experiments--complete-funnel-experiment.png differ diff --git a/frontend/__snapshots__/scenes-app-insights--funnel-historical-trends-edit--webkit.png b/frontend/__snapshots__/scenes-app-insights--funnel-historical-trends-edit--webkit.png index 613e89cdbc0d8..5ee7a8708613f 100644 Binary files a/frontend/__snapshots__/scenes-app-insights--funnel-historical-trends-edit--webkit.png and b/frontend/__snapshots__/scenes-app-insights--funnel-historical-trends-edit--webkit.png differ diff --git a/frontend/__snapshots__/scenes-app-insights--funnel-left-to-right--webkit.png b/frontend/__snapshots__/scenes-app-insights--funnel-left-to-right--webkit.png index f62fd32d947ea..336a737d983af 100644 Binary files a/frontend/__snapshots__/scenes-app-insights--funnel-left-to-right--webkit.png and b/frontend/__snapshots__/scenes-app-insights--funnel-left-to-right--webkit.png differ diff --git a/frontend/__snapshots__/scenes-app-insights--funnel-left-to-right-breakdown--webkit.png b/frontend/__snapshots__/scenes-app-insights--funnel-left-to-right-breakdown--webkit.png index 9dd59f171f782..b22467c95b46c 100644 Binary files a/frontend/__snapshots__/scenes-app-insights--funnel-left-to-right-breakdown--webkit.png and b/frontend/__snapshots__/scenes-app-insights--funnel-left-to-right-breakdown--webkit.png differ diff --git a/frontend/__snapshots__/scenes-app-insights--funnel-left-to-right-breakdown-edit--webkit.png b/frontend/__snapshots__/scenes-app-insights--funnel-left-to-right-breakdown-edit--webkit.png index a290aabaf2c89..8bcadaeddb713 100644 Binary files a/frontend/__snapshots__/scenes-app-insights--funnel-left-to-right-breakdown-edit--webkit.png and b/frontend/__snapshots__/scenes-app-insights--funnel-left-to-right-breakdown-edit--webkit.png differ diff --git a/frontend/__snapshots__/scenes-app-insights--funnel-left-to-right-breakdown-edit.png b/frontend/__snapshots__/scenes-app-insights--funnel-left-to-right-breakdown-edit.png index 61758c0e5908a..48c95635cae74 100644 Binary files a/frontend/__snapshots__/scenes-app-insights--funnel-left-to-right-breakdown-edit.png and b/frontend/__snapshots__/scenes-app-insights--funnel-left-to-right-breakdown-edit.png differ diff --git a/frontend/__snapshots__/scenes-app-insights--funnel-left-to-right-breakdown.png b/frontend/__snapshots__/scenes-app-insights--funnel-left-to-right-breakdown.png index f0f18dad2910e..f7f0a786c5d30 100644 Binary files a/frontend/__snapshots__/scenes-app-insights--funnel-left-to-right-breakdown.png and b/frontend/__snapshots__/scenes-app-insights--funnel-left-to-right-breakdown.png differ diff --git a/frontend/__snapshots__/scenes-app-insights--funnel-left-to-right-edit--webkit.png b/frontend/__snapshots__/scenes-app-insights--funnel-left-to-right-edit--webkit.png index 0e7909e11f451..9a4387639a76b 100644 Binary files a/frontend/__snapshots__/scenes-app-insights--funnel-left-to-right-edit--webkit.png and b/frontend/__snapshots__/scenes-app-insights--funnel-left-to-right-edit--webkit.png differ diff --git a/frontend/__snapshots__/scenes-app-insights--funnel-left-to-right-edit.png b/frontend/__snapshots__/scenes-app-insights--funnel-left-to-right-edit.png index 9e9adda1263cd..4cf0295e84e27 100644 Binary files a/frontend/__snapshots__/scenes-app-insights--funnel-left-to-right-edit.png and b/frontend/__snapshots__/scenes-app-insights--funnel-left-to-right-edit.png differ diff --git a/frontend/__snapshots__/scenes-app-insights--funnel-left-to-right.png b/frontend/__snapshots__/scenes-app-insights--funnel-left-to-right.png index 74c7c60a50e11..456b5fd067465 100644 Binary files a/frontend/__snapshots__/scenes-app-insights--funnel-left-to-right.png and b/frontend/__snapshots__/scenes-app-insights--funnel-left-to-right.png differ diff --git a/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom.png b/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom.png index 8760764ed3a04..1a4722fdb1a6f 100644 Binary files a/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom.png and b/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom.png differ diff --git a/frontend/__snapshots__/scenes-app-project-homepage--project-homepage.png b/frontend/__snapshots__/scenes-app-project-homepage--project-homepage.png index 774aa3c97a3e1..e68bb4406ab67 100644 Binary files a/frontend/__snapshots__/scenes-app-project-homepage--project-homepage.png and b/frontend/__snapshots__/scenes-app-project-homepage--project-homepage.png differ diff --git a/frontend/__snapshots__/scenes-other-toolbar--heatmap-dark.png b/frontend/__snapshots__/scenes-other-toolbar--heatmap-dark.png index d00fa2fb6139e..fcee0abbf1035 100644 Binary files a/frontend/__snapshots__/scenes-other-toolbar--heatmap-dark.png and b/frontend/__snapshots__/scenes-other-toolbar--heatmap-dark.png differ diff --git a/frontend/src/layout/navigation-3000/sidepanel/panels/SidePanelDocs.tsx b/frontend/src/layout/navigation-3000/sidepanel/panels/SidePanelDocs.tsx index e080f49c67f26..e193083feef00 100644 --- a/frontend/src/layout/navigation-3000/sidepanel/panels/SidePanelDocs.tsx +++ b/frontend/src/layout/navigation-3000/sidepanel/panels/SidePanelDocs.tsx @@ -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' @@ -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 (
@@ -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 ( +
+ ({ label: name, value: name }))} + /> +
+ ) +} + export const SidePanelDocs = (): JSX.Element => { const { iframeSrc, currentUrl } = useValues(sidePanelDocsLogic) const { updatePath, unmountIframe, closeSidePanel, handleExternalUrl } = useActions(sidePanelDocsLogic) const ref = useRef(null) const [ready, setReady] = useState(false) const { isDarkModeOn } = useValues(themeLogic) + const [menu, setMenu] = useState(null) + const [activeMenuName, setActiveMenuName] = useState(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( @@ -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) } @@ -79,6 +131,7 @@ export const SidePanelDocs = (): JSX.Element => { return ( <> + {menu && } } diff --git a/frontend/src/lib/taxonomy.tsx b/frontend/src/lib/taxonomy.tsx index 10be42e8fd679..c37cf5ecceb6f 100644 --- a/frontend/src/lib/taxonomy.tsx +++ b/frontend/src/lib/taxonomy.tsx @@ -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 diff --git a/frontend/src/lib/utils.tsx b/frontend/src/lib/utils.tsx index b37a126d607e8..302f81c2d0657 100644 --- a/frontend/src/lib/utils.tsx +++ b/frontend/src/lib/utils.tsx @@ -391,8 +391,16 @@ export function idToKey(array: Record[], keyField: string = 'id'): return object } -export function delay(ms: number): Promise { - return new Promise((resolve) => window.setTimeout(resolve, ms)) +export function delay(ms: number, signal?: AbortSignal): Promise { + 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 { diff --git a/frontend/src/queries/query.ts b/frontend/src/queries/query.ts index 29d08863a62c0..ded22d89a9979 100644 --- a/frontend/src/queries/query.ts +++ b/frontend/src/queries/query.ts @@ -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 @@ -115,15 +115,9 @@ async function executeQuery( 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) { diff --git a/frontend/src/scenes/funnels/FunnelBarChart/FunnelBarChart.scss b/frontend/src/scenes/funnels/FunnelBarChart/FunnelBarChart.scss index b4a3090c12a25..6b1aea45485cc 100644 --- a/frontend/src/scenes/funnels/FunnelBarChart/FunnelBarChart.scss +++ b/frontend/src/scenes/funnels/FunnelBarChart/FunnelBarChart.scss @@ -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%; @@ -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); } } @@ -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; @@ -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) { diff --git a/frontend/src/scenes/funnels/FunnelBarChart/FunnelBarChart.tsx b/frontend/src/scenes/funnels/FunnelBarChart/FunnelBarChart.tsx index db04937511c85..bd18eb7fdcbad 100644 --- a/frontend/src/scenes/funnels/FunnelBarChart/FunnelBarChart.tsx +++ b/frontend/src/scenes/funnels/FunnelBarChart/FunnelBarChart.tsx @@ -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' @@ -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 = @@ -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 ( - - - {visibleStepsWithConversionMetrics.map((_, i) => ( - - ))} - - {/* The last column is meant to fill up leftover space. */} - - - - - {visibleStepsWithConversionMetrics.map((step, stepIndex) => ( - - ))} - - - - ))} - - -
- - - -
- {visibleStepsWithConversionMetrics.map((step, stepIndex) => ( - - -
- ) - }, [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 (
- {table} + + + {visibleStepsWithConversionMetrics.map((_, i) => ( + + ))} + + {/* The last column is meant to fill up leftover space. */} + + + + + {visibleStepsWithConversionMetrics.map((step, stepIndex) => ( + + ))} + + + + ))} + + +
+ + + +
+ {visibleStepsWithConversionMetrics.map((step, stepIndex) => ( + + +
) diff --git a/frontend/src/scenes/funnels/FunnelBarGraph/Bar.tsx b/frontend/src/scenes/funnels/FunnelBarGraph/Bar.tsx index 5b77c77556392..2569743a57205 100644 --- a/frontend/src/scenes/funnels/FunnelBarGraph/Bar.tsx +++ b/frontend/src/scenes/funnels/FunnelBarGraph/Bar.tsx @@ -25,6 +25,7 @@ interface BarProps { wrapperWidth: number } type LabelPosition = 'inside' | 'outside' + export function Bar({ percentage: conversionPercentage, name, diff --git a/frontend/src/scenes/funnels/FunnelBarGraph/FunnelBarGraph.tsx b/frontend/src/scenes/funnels/FunnelBarGraph/FunnelBarGraph.tsx index 10b44978f1cf8..77f589785120a 100644 --- a/frontend/src/scenes/funnels/FunnelBarGraph/FunnelBarGraph.tsx +++ b/frontend/src/scenes/funnels/FunnelBarGraph/FunnelBarGraph.tsx @@ -98,7 +98,7 @@ export function FunnelBarGraph({ ) : null}
-
+
{!width ? null : isBreakdown ? ( <> {step?.nested_breakdown?.map((breakdown, index) => { diff --git a/frontend/src/scenes/web-analytics/webAnalyticsLogic.ts b/frontend/src/scenes/web-analytics/webAnalyticsLogic.ts index 845629e3d7037..ac41d8398f921 100644 --- a/frontend/src/scenes/web-analytics/webAnalyticsLogic.ts +++ b/frontend/src/scenes/web-analytics/webAnalyticsLogic.ts @@ -90,6 +90,11 @@ export interface WebAnalyticsStatusCheck { shouldWarnAboutNoPageleaves: boolean } +export const GEOIP_PLUGIN_URLS = [ + 'https://github.com/PostHog/posthog-plugin-geoip', + 'https://www.npmjs.com/package/@posthog/geoip-plugin', +] + export const initialWebAnalyticsFilter = [] as WebAnalyticsPropertyFilters export const webAnalyticsLogic = kea([ @@ -718,9 +723,7 @@ export const webAnalyticsLogic = kea([ const geoIpPlugin = pluginsResponse.status === 'fulfilled' && - pluginsResponse.value.find( - (plugin) => plugin.url === 'https://www.npmjs.com/package/@posthog/geoip-plugin' - ) + pluginsResponse.value.find((plugin) => GEOIP_PLUGIN_URLS.includes(plugin.url)) const geoIpPluginId = geoIpPlugin ? geoIpPlugin.id : undefined const geoIpPluginConfig = diff --git a/package.json b/package.json index fbc8db885bca9..01da1beb31545 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "engines": { "node": ">=18 <19" }, - "packageManager": "pnpm@8.6.0", + "packageManager": "pnpm@8.10.5", "scripts": { "copy-scripts": "mkdir -p frontend/dist/ && ./bin/copy-posthog-js", "test": "pnpm test:unit && pnpm test:visual-regression", @@ -182,7 +182,7 @@ "@babel/preset-typescript": "^7.22.5", "@babel/runtime": "^7.22.10", "@cypress/webpack-preprocessor": "^5.17.1", - "@playwright/test": "1.29.2", + "@playwright/test": "1.32.2", "@sentry/types": "7.22.0", "@storybook/addon-a11y": "^7.5.1", "@storybook/addon-actions": "^7.5.1", @@ -271,7 +271,6 @@ "msw": "^0.49.0", "path-browserify": "^1.0.1", "pixelmatch": "^5.3.0", - "playwright-core": "1.29.2", "pngjs": "^6.0.0", "postcss": "^8.4.31", "postcss-loader": "^4.3.0", @@ -296,6 +295,11 @@ "optionalDependencies": { "fsevents": "^2.3.2" }, + "pnpm": { + "overrides": { + "playwright": "1.32.2" + } + }, "lint-staged": { "*.{json,yaml,yml}": "prettier --write", "*.{css,scss}": [ diff --git a/playwright.config.ts b/playwright.config.ts index 27462f7c034e3..a31aa1543f192 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -1,5 +1,4 @@ -import type { PlaywrightTestConfig } from '@playwright/test' -import { devices } from '@playwright/test' +import { defineConfig, devices } from '@playwright/test' /** * Read environment variables from file. @@ -10,7 +9,7 @@ import { devices } from '@playwright/test' /** * See https://playwright.dev/docs/test-configuration. */ -const config: PlaywrightTestConfig = { +export default defineConfig({ testDir: './playwright', /* Maximum time one test can run for. */ timeout: 30 * 1000, @@ -102,6 +101,4 @@ const config: PlaywrightTestConfig = { // command: 'npm run start', // port: 3000, // }, -} - -export default config +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8e86f320900ea..d94b6538da7a5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,9 +1,12 @@ -lockfileVersion: '6.1' +lockfileVersion: '6.0' settings: autoInstallPeers: true excludeLinksFromLockfile: false +overrides: + playwright: 1.32.2 + dependencies: '@ant-design/icons': specifier: ^4.7.0 @@ -355,8 +358,8 @@ devDependencies: specifier: ^5.17.1 version: 5.17.1(@babel/core@7.22.10)(@babel/preset-env@7.22.10)(babel-loader@8.3.0)(webpack@5.88.2) '@playwright/test': - specifier: 1.29.2 - version: 1.29.2 + specifier: 1.32.2 + version: 1.32.2 '@sentry/types': specifier: 7.22.0 version: 7.22.0 @@ -612,9 +615,6 @@ devDependencies: pixelmatch: specifier: ^5.3.0 version: 5.3.0 - playwright-core: - specifier: 1.29.2 - version: 1.29.2 pngjs: specifier: ^6.0.0 version: 6.0.0 @@ -795,7 +795,7 @@ packages: dependencies: '@babel/types': 7.23.4 '@jridgewell/gen-mapping': 0.3.2 - '@jridgewell/trace-mapping': 0.3.20 + '@jridgewell/trace-mapping': 0.3.17 jsesc: 2.5.2 /@babel/helper-annotate-as-pure@7.22.5: @@ -808,7 +808,7 @@ packages: resolution: {integrity: sha512-Av0qubwDQxC56DoUReVDeLfMEjYYSN1nZrTUrWkXd7hpU73ymRANkbuDm3yni9npkn+RXy9nNbEJZEzXr7xrfQ==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.23.4 + '@babel/types': 7.23.0 dev: true /@babel/helper-compilation-targets@7.22.10: @@ -873,19 +873,19 @@ packages: engines: {node: '>=6.9.0'} dependencies: '@babel/template': 7.22.15 - '@babel/types': 7.23.4 + '@babel/types': 7.23.0 /@babel/helper-hoist-variables@7.22.5: resolution: {integrity: sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.23.4 + '@babel/types': 7.23.0 /@babel/helper-member-expression-to-functions@7.22.5: resolution: {integrity: sha512-aBiH1NKMG0H2cGZqspNvsaBe6wNGjbJjuLy29aU+eDZjSbbN53BaxlpB02xm9v34pLTZ1nIQPFYn2qMZoa5BQQ==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.23.4 + '@babel/types': 7.23.0 /@babel/helper-module-imports@7.22.5: resolution: {integrity: sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg==} @@ -910,7 +910,7 @@ packages: resolution: {integrity: sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.23.4 + '@babel/types': 7.23.0 /@babel/helper-plugin-utils@7.22.5: resolution: {integrity: sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==} @@ -943,19 +943,19 @@ packages: resolution: {integrity: sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.23.4 + '@babel/types': 7.23.0 /@babel/helper-skip-transparent-expression-wrappers@7.22.5: resolution: {integrity: sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.23.4 + '@babel/types': 7.23.0 /@babel/helper-split-export-declaration@7.22.6: resolution: {integrity: sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.23.4 + '@babel/types': 7.23.0 /@babel/helper-string-parser@7.22.5: resolution: {integrity: sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==} @@ -2677,11 +2677,11 @@ packages: resolution: {integrity: sha512-IRE6GD47KwcqA09RIWrabKdHPiKDGgtAL31xDxbi/RjQMsr+lY+ppxmHwY0dUEV3qvvxZzoe5Hl0RXZJOjQNUg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jest/types': 29.6.3 + '@jest/types': 29.3.1 '@types/node': 18.18.4 chalk: 4.1.2 - jest-message-util: 29.7.0 - jest-util: 29.7.0 + jest-message-util: 29.3.1 + jest-util: 29.3.1 slash: 3.0.0 dev: true @@ -2735,7 +2735,6 @@ packages: slash: 3.0.0 strip-ansi: 6.0.1 transitivePeerDependencies: - - babel-plugin-macros - supports-color - ts-node dev: true @@ -2824,6 +2823,16 @@ packages: jest-get-type: 29.6.3 dev: true + /@jest/expect@29.3.1: + resolution: {integrity: sha512-QivM7GlSHSsIAWzgfyP8dgeExPRZ9BIe2LsdPyEhCGkZkoyA+kGsoIzbKAfZCvvRzfZioKwPtCZIt5SaoxYCvg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + expect: 29.3.1 + jest-snapshot: 29.3.1 + transitivePeerDependencies: + - supports-color + dev: true + /@jest/expect@29.7.0: resolution: {integrity: sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -2862,10 +2871,10 @@ packages: resolution: {integrity: sha512-cTicd134vOcwO59OPaB6AmdHQMCtWOe+/DitpTZVxWgMJ+YvXL1HNAmPyiGbSHmF/mXVBkvlm8YYtQhyHPnV6Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jest/environment': 29.7.0 - '@jest/expect': 29.7.0 - '@jest/types': 29.6.3 - jest-mock: 29.7.0 + '@jest/environment': 29.3.1 + '@jest/expect': 29.3.1 + '@jest/types': 29.3.1 + jest-mock: 29.3.1 transitivePeerDependencies: - supports-color dev: true @@ -2893,10 +2902,10 @@ packages: dependencies: '@bcoe/v8-coverage': 0.2.3 '@jest/console': 29.3.1 - '@jest/test-result': 29.7.0 + '@jest/test-result': 29.3.1 '@jest/transform': 29.3.1 - '@jest/types': 29.6.3 - '@jridgewell/trace-mapping': 0.3.20 + '@jest/types': 29.3.1 + '@jridgewell/trace-mapping': 0.3.17 '@types/node': 18.18.4 chalk: 4.1.2 collect-v8-coverage: 1.0.1 @@ -2908,8 +2917,8 @@ packages: istanbul-lib-report: 3.0.0 istanbul-lib-source-maps: 4.0.1 istanbul-reports: 3.1.5 - jest-message-util: 29.7.0 - jest-util: 29.7.0 + jest-message-util: 29.3.1 + jest-util: 29.3.1 jest-worker: 29.3.1 slash: 3.0.0 string-length: 4.0.2 @@ -2974,7 +2983,7 @@ packages: resolution: {integrity: sha512-1NX9/7zzI0nqa6+kgpSdKPK+WU1p+SJk3TloWZf5MzPbxri9UEeXX5bWZAPCzbQcyuAzubcdUHA7hcNznmRqWQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jridgewell/trace-mapping': 0.3.20 + '@jridgewell/trace-mapping': 0.3.17 callsites: 3.1.0 graceful-fs: 4.2.11 dev: true @@ -2993,7 +3002,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/console': 29.3.1 - '@jest/types': 29.6.3 + '@jest/types': 29.3.1 '@types/istanbul-lib-coverage': 2.0.4 collect-v8-coverage: 1.0.1 dev: true @@ -3012,9 +3021,9 @@ packages: resolution: {integrity: sha512-IqYvLbieTv20ArgKoAMyhLHNrVHJfzO6ARZAbQRlY4UGWfdDnLlZEF0BvKOMd77uIiIjSZRwq3Jb3Fa3I8+2UA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jest/test-result': 29.7.0 + '@jest/test-result': 29.3.1 graceful-fs: 4.2.11 - jest-haste-map: 29.7.0 + jest-haste-map: 29.3.1 slash: 3.0.0 dev: true @@ -3033,8 +3042,8 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@babel/core': 7.22.10 - '@jest/types': 29.6.3 - '@jridgewell/trace-mapping': 0.3.20 + '@jest/types': 29.3.1 + '@jridgewell/trace-mapping': 0.3.17 babel-plugin-istanbul: 6.1.1 chalk: 4.1.2 convert-source-map: 2.0.0 @@ -3122,7 +3131,7 @@ packages: dependencies: '@jridgewell/set-array': 1.1.2 '@jridgewell/sourcemap-codec': 1.4.14 - '@jridgewell/trace-mapping': 0.3.20 + '@jridgewell/trace-mapping': 0.3.17 /@jridgewell/resolve-uri@3.1.0: resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==} @@ -3136,7 +3145,7 @@ packages: resolution: {integrity: sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==} dependencies: '@jridgewell/gen-mapping': 0.3.2 - '@jridgewell/trace-mapping': 0.3.20 + '@jridgewell/trace-mapping': 0.3.17 dev: true /@jridgewell/sourcemap-codec@1.4.14: @@ -3153,6 +3162,7 @@ packages: dependencies: '@jridgewell/resolve-uri': 3.1.0 '@jridgewell/sourcemap-codec': 1.4.14 + dev: true /@jridgewell/trace-mapping@0.3.9: resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} @@ -3367,13 +3377,15 @@ packages: tslib: 2.6.2 dev: true - /@playwright/test@1.29.2: - resolution: {integrity: sha512-+3/GPwOgcoF0xLz/opTnahel1/y42PdcgZ4hs+BZGIUjtmEFSXGg+nFoaH3NSmuc7a6GSFwXDJ5L7VXpqzigNg==} + /@playwright/test@1.32.2: + resolution: {integrity: sha512-nhaTSDpEdTTttdkDE8Z6K3icuG1DVRxrl98Qq0Lfc63SS9a2sjc9+x8ezysh7MzCKz6Y+nArml3/mmt+gqRmQQ==} engines: {node: '>=14'} hasBin: true dependencies: '@types/node': 18.18.4 - playwright-core: 1.29.2 + playwright-core: 1.32.2 + optionalDependencies: + fsevents: 2.3.2 dev: true /@pmmmwh/react-refresh-webpack-plugin@0.5.11(react-refresh@0.11.0)(webpack@5.88.2): @@ -5312,7 +5324,7 @@ packages: jest-serializer-html: 7.1.0 jest-watch-typeahead: 2.2.2(jest@29.7.0) node-fetch: 2.6.7 - playwright: 1.29.2 + playwright: 1.32.2 read-pkg-up: 7.0.1 tempy: 1.0.1 ts-dedent: 2.2.0 @@ -5926,7 +5938,7 @@ packages: /@types/babel__generator@7.6.6: resolution: {integrity: sha512-66BXMKb/sUWbMdBNdMvajU7i/44RkrA3z/Yt1c7R5xejt8qh84iU54yUWCtm0QwGJlDcf/gg4zd/x4mpLAlb/w==} dependencies: - '@babel/types': 7.23.4 + '@babel/types': 7.23.0 dev: true /@types/babel__generator@7.6.7: @@ -5938,8 +5950,8 @@ packages: /@types/babel__template@7.4.3: resolution: {integrity: sha512-ciwyCLeuRfxboZ4isgdNZi/tkt06m8Tw6uGbBSBgWrnnZGNXiEyM27xc/PjXGQLqlZ6ylbgHMnm7ccF9tCkOeQ==} dependencies: - '@babel/parser': 7.23.4 - '@babel/types': 7.23.4 + '@babel/parser': 7.23.0 + '@babel/types': 7.23.0 dev: true /@types/babel__template@7.4.4: @@ -5952,7 +5964,7 @@ packages: /@types/babel__traverse@7.20.3: resolution: {integrity: sha512-Lsh766rGEFbaxMIDH7Qa+Yha8cMVI3qAK6CHt3OR0YfxOIn5Z54iHiyDRycHrBqeIiqGa20Kpsv1cavfBKkRSw==} dependencies: - '@babel/types': 7.23.4 + '@babel/types': 7.23.0 dev: true /@types/babel__traverse@7.20.4: @@ -7649,8 +7661,8 @@ packages: '@babel/core': ^7.8.0 dependencies: '@babel/core': 7.22.10 - '@jest/transform': 29.7.0 - '@types/babel__core': 7.20.4 + '@jest/transform': 29.3.1 + '@types/babel__core': 7.20.3 babel-plugin-istanbul: 6.1.1 babel-preset-jest: 29.2.0(@babel/core@7.22.10) chalk: 4.1.2 @@ -7735,8 +7747,8 @@ packages: dependencies: '@babel/template': 7.22.15 '@babel/types': 7.23.4 - '@types/babel__core': 7.20.4 - '@types/babel__traverse': 7.20.4 + '@types/babel__core': 7.20.3 + '@types/babel__traverse': 7.20.3 dev: true /babel-plugin-jest-hoist@29.6.3: @@ -11236,6 +11248,14 @@ packages: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} dev: true + /fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + requiresBuild: true + dev: true + optional: true + /fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -12622,6 +12642,33 @@ packages: p-limit: 3.1.0 dev: true + /jest-circus@29.3.1: + resolution: {integrity: sha512-wpr26sEvwb3qQQbdlmei+gzp6yoSSoSL6GsLPxnuayZSMrSd5Ka7IjAvatpIernBvT2+Ic6RLTg+jSebScmasg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/environment': 29.3.1 + '@jest/expect': 29.3.1 + '@jest/test-result': 29.3.1 + '@jest/types': 29.3.1 + '@types/node': 18.18.4 + chalk: 4.1.2 + co: 4.6.0 + dedent: 0.7.0 + is-generator-fn: 2.1.0 + jest-each: 29.3.1 + jest-matcher-utils: 29.3.1 + jest-message-util: 29.3.1 + jest-runtime: 29.3.1 + jest-snapshot: 29.3.1 + jest-util: 29.3.1 + p-limit: 3.1.0 + pretty-format: 29.3.1 + slash: 3.0.0 + stack-utils: 2.0.5 + transitivePeerDependencies: + - supports-color + dev: true + /jest-circus@29.7.0: resolution: {integrity: sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -12675,7 +12722,6 @@ packages: yargs: 17.6.2 transitivePeerDependencies: - '@types/node' - - babel-plugin-macros - supports-color - ts-node dev: true @@ -12722,7 +12768,7 @@ packages: dependencies: '@babel/core': 7.22.10 '@jest/test-sequencer': 29.3.1 - '@jest/types': 29.6.3 + '@jest/types': 29.3.1 '@types/node': 18.11.9 babel-jest: 29.3.1(@babel/core@7.22.10) chalk: 4.1.2 @@ -12730,22 +12776,21 @@ packages: deepmerge: 4.2.2 glob: 7.2.3 graceful-fs: 4.2.11 - jest-circus: 29.7.0 - jest-environment-node: 29.7.0 + jest-circus: 29.3.1 + jest-environment-node: 29.3.1 jest-get-type: 29.2.0 jest-regex-util: 29.2.0 jest-resolve: 29.3.1 - jest-runner: 29.7.0 - jest-util: 29.7.0 + jest-runner: 29.3.1 + jest-util: 29.3.1 jest-validate: 29.3.1 micromatch: 4.0.5 parse-json: 5.2.0 - pretty-format: 29.7.0 + pretty-format: 29.3.1 slash: 3.0.0 strip-json-comments: 3.1.1 ts-node: 10.9.1(@swc/core@1.3.93)(@types/node@18.11.9)(typescript@4.9.5) transitivePeerDependencies: - - babel-plugin-macros - supports-color dev: true @@ -12763,7 +12808,7 @@ packages: dependencies: '@babel/core': 7.22.10 '@jest/test-sequencer': 29.3.1 - '@jest/types': 29.6.3 + '@jest/types': 29.3.1 '@types/node': 18.18.4 babel-jest: 29.3.1(@babel/core@7.22.10) chalk: 4.1.2 @@ -12771,22 +12816,21 @@ packages: deepmerge: 4.2.2 glob: 7.2.3 graceful-fs: 4.2.11 - jest-circus: 29.7.0 - jest-environment-node: 29.7.0 + jest-circus: 29.3.1 + jest-environment-node: 29.3.1 jest-get-type: 29.2.0 jest-regex-util: 29.2.0 jest-resolve: 29.3.1 - jest-runner: 29.7.0 - jest-util: 29.7.0 + jest-runner: 29.3.1 + jest-util: 29.3.1 jest-validate: 29.3.1 micromatch: 4.0.5 parse-json: 5.2.0 - pretty-format: 29.7.0 + pretty-format: 29.3.1 slash: 3.0.0 strip-json-comments: 3.1.1 ts-node: 10.9.1(@swc/core@1.3.93)(@types/node@18.11.9)(typescript@4.9.5) transitivePeerDependencies: - - babel-plugin-macros - supports-color dev: true @@ -12879,7 +12923,7 @@ packages: chalk: 4.1.2 diff-sequences: 29.3.1 jest-get-type: 29.2.0 - pretty-format: 29.7.0 + pretty-format: 29.3.1 dev: true /jest-diff@29.7.0: @@ -12906,6 +12950,17 @@ packages: detect-newline: 3.1.0 dev: true + /jest-each@29.3.1: + resolution: {integrity: sha512-qrZH7PmFB9rEzCSl00BWjZYuS1BSOH8lLuC0azQE9lQrAx3PWGKHTDudQiOSwIy5dGAJh7KA0ScYlCP7JxvFYA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/types': 29.3.1 + chalk: 4.1.2 + jest-get-type: 29.2.0 + jest-util: 29.3.1 + pretty-format: 29.3.1 + dev: true + /jest-each@29.7.0: resolution: {integrity: sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -12940,6 +12995,18 @@ packages: - utf-8-validate dev: true + /jest-environment-node@29.3.1: + resolution: {integrity: sha512-xm2THL18Xf5sIHoU7OThBPtuH6Lerd+Y1NLYiZJlkE3hbE+7N7r8uvHIl/FkZ5ymKXJe/11SQuf3fv4v6rUMag==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/environment': 29.3.1 + '@jest/fake-timers': 29.3.1 + '@jest/types': 29.3.1 + '@types/node': 18.18.4 + jest-mock: 29.3.1 + jest-util: 29.3.1 + dev: true + /jest-environment-node@29.7.0: resolution: {integrity: sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -12966,14 +13033,14 @@ packages: resolution: {integrity: sha512-/FFtvoG1xjbbPXQLFef+WSU4yrc0fc0Dds6aRPBojUid7qlPqZvxdUBA03HW0fnVHXVCnCdkuoghYItKNzc/0A==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jest/types': 29.6.3 + '@jest/types': 29.3.1 '@types/graceful-fs': 4.1.5 '@types/node': 18.18.4 anymatch: 3.1.2 fb-watchman: 2.0.2 graceful-fs: 4.2.11 jest-regex-util: 29.2.0 - jest-util: 29.7.0 + jest-util: 29.3.1 jest-worker: 29.3.1 micromatch: 4.0.5 walker: 1.0.8 @@ -13032,8 +13099,8 @@ packages: resolution: {integrity: sha512-3DA/VVXj4zFOPagGkuqHnSQf1GZBmmlagpguxEERO6Pla2g84Q1MaVIB3YMxgUaFIaYag8ZnTyQgiZ35YEqAQA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - jest-get-type: 29.6.3 - pretty-format: 29.7.0 + jest-get-type: 29.2.0 + pretty-format: 29.3.1 dev: true /jest-leak-detector@29.7.0: @@ -13069,7 +13136,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@babel/code-frame': 7.22.13 - '@jest/types': 29.6.3 + '@jest/types': 29.3.1 '@types/stack-utils': 2.0.1 chalk: 4.1.2 graceful-fs: 4.2.11 @@ -13127,7 +13194,7 @@ packages: jest-process-manager: 0.3.1 jest-runner: 29.7.0 nyc: 15.1.0 - playwright-core: 1.29.2 + playwright-core: 1.32.2 rimraf: 3.0.2 uuid: 8.3.2 transitivePeerDependencies: @@ -13192,7 +13259,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: jest-regex-util: 29.2.0 - jest-snapshot: 29.7.0 + jest-snapshot: 29.3.1 transitivePeerDependencies: - supports-color dev: true @@ -13215,7 +13282,7 @@ packages: graceful-fs: 4.2.11 jest-haste-map: 29.3.1 jest-pnp-resolver: 1.2.2(jest-resolve@29.3.1) - jest-util: 29.7.0 + jest-util: 29.3.1 jest-validate: 29.3.1 resolve: 1.22.1 resolve.exports: 1.1.0 @@ -13242,22 +13309,22 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/console': 29.3.1 - '@jest/environment': 29.7.0 - '@jest/test-result': 29.7.0 + '@jest/environment': 29.3.1 + '@jest/test-result': 29.3.1 '@jest/transform': 29.3.1 - '@jest/types': 29.6.3 + '@jest/types': 29.3.1 '@types/node': 18.18.4 chalk: 4.1.2 emittery: 0.13.1 graceful-fs: 4.2.11 jest-docblock: 29.2.0 - jest-environment-node: 29.7.0 + jest-environment-node: 29.3.1 jest-haste-map: 29.3.1 jest-leak-detector: 29.3.1 - jest-message-util: 29.7.0 + jest-message-util: 29.3.1 jest-resolve: 29.3.1 - jest-runtime: 29.7.0 - jest-util: 29.7.0 + jest-runtime: 29.3.1 + jest-util: 29.3.1 jest-watcher: 29.3.1 jest-worker: 29.3.1 p-limit: 3.1.0 @@ -13299,13 +13366,13 @@ packages: resolution: {integrity: sha512-jLzkIxIqXwBEOZx7wx9OO9sxoZmgT2NhmQKzHQm1xwR1kNW/dn0OjxR424VwHHf1SPN6Qwlb5pp1oGCeFTQ62A==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jest/environment': 29.7.0 - '@jest/fake-timers': 29.7.0 + '@jest/environment': 29.3.1 + '@jest/fake-timers': 29.3.1 '@jest/globals': 29.3.1 '@jest/source-map': 29.2.0 - '@jest/test-result': 29.7.0 + '@jest/test-result': 29.3.1 '@jest/transform': 29.3.1 - '@jest/types': 29.6.3 + '@jest/types': 29.3.1 '@types/node': 18.18.4 chalk: 4.1.2 cjs-module-lexer: 1.2.2 @@ -13313,12 +13380,12 @@ packages: glob: 7.2.3 graceful-fs: 4.2.11 jest-haste-map: 29.3.1 - jest-message-util: 29.7.0 - jest-mock: 29.7.0 + jest-message-util: 29.3.1 + jest-mock: 29.3.1 jest-regex-util: 29.2.0 jest-resolve: 29.3.1 - jest-snapshot: 29.7.0 - jest-util: 29.7.0 + jest-snapshot: 29.3.1 + jest-util: 29.3.1 slash: 3.0.0 strip-bom: 4.0.0 transitivePeerDependencies: @@ -13373,7 +13440,7 @@ packages: '@babel/types': 7.23.4 '@jest/expect-utils': 29.3.1 '@jest/transform': 29.3.1 - '@jest/types': 29.6.3 + '@jest/types': 29.3.1 '@types/babel__traverse': 7.20.3 '@types/prettier': 2.7.1 babel-preset-current-node-syntax: 1.0.1(@babel/core@7.22.10) @@ -13383,11 +13450,11 @@ packages: jest-diff: 29.3.1 jest-get-type: 29.2.0 jest-haste-map: 29.3.1 - jest-matcher-utils: 29.7.0 - jest-message-util: 29.7.0 - jest-util: 29.7.0 + jest-matcher-utils: 29.3.1 + jest-message-util: 29.3.1 + jest-util: 29.3.1 natural-compare: 1.4.0 - pretty-format: 29.7.0 + pretty-format: 29.3.1 semver: 7.5.4 transitivePeerDependencies: - supports-color @@ -13449,12 +13516,12 @@ packages: resolution: {integrity: sha512-N9Lr3oYR2Mpzuelp1F8negJR3YE+L1ebk1rYA5qYo9TTY3f9OWdptLoNSPP9itOCBIRBqjt/S5XHlzYglLN67g==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jest/types': 29.6.3 + '@jest/types': 29.3.1 camelcase: 6.3.0 chalk: 4.1.2 jest-get-type: 29.2.0 leven: 3.1.0 - pretty-format: 29.7.0 + pretty-format: 29.3.1 dev: true /jest-validate@29.7.0: @@ -13478,8 +13545,8 @@ packages: ansi-escapes: 6.0.0 chalk: 5.2.0 jest: 29.7.0(@types/node@18.11.9)(ts-node@10.9.1) - jest-regex-util: 29.6.3 - jest-watcher: 29.7.0 + jest-regex-util: 29.2.0 + jest-watcher: 29.3.1 slash: 5.0.0 string-length: 5.0.1 strip-ansi: 7.0.1 @@ -13489,13 +13556,13 @@ packages: resolution: {integrity: sha512-RspXG2BQFDsZSRKGCT/NiNa8RkQ1iKAjrO0//soTMWx/QUt+OcxMqMSBxz23PYGqUuWm2+m2mNNsmj0eIoOaFg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jest/test-result': 29.7.0 - '@jest/types': 29.6.3 + '@jest/test-result': 29.3.1 + '@jest/types': 29.3.1 '@types/node': 18.18.4 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 - jest-util: 29.7.0 + jest-util: 29.3.1 string-length: 4.0.2 dev: true @@ -13527,7 +13594,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@types/node': 18.18.4 - jest-util: 29.7.0 + jest-util: 29.3.1 merge-stream: 2.0.0 supports-color: 8.1.1 dev: true @@ -13558,7 +13625,6 @@ packages: jest-cli: 29.3.1(@types/node@18.11.9)(ts-node@10.9.1) transitivePeerDependencies: - '@types/node' - - babel-plugin-macros - supports-color - ts-node dev: true @@ -15347,19 +15413,19 @@ packages: find-up: 6.3.0 dev: true - /playwright-core@1.29.2: - resolution: {integrity: sha512-94QXm4PMgFoHAhlCuoWyaBYKb92yOcGVHdQLoxQ7Wjlc7Flg4aC/jbFW7xMR52OfXMVkWicue4WXE7QEegbIRA==} + /playwright-core@1.32.2: + resolution: {integrity: sha512-zD7aonO+07kOTthsrCR3YCVnDcqSHIJpdFUtZEMOb6//1Rc7/6mZDRdw+nlzcQiQltOOsiqI3rrSyn/SlyjnJQ==} engines: {node: '>=14'} hasBin: true dev: true - /playwright@1.29.2: - resolution: {integrity: sha512-hKBYJUtdmYzcjdhYDkP9WGtORwwZBBKAW8+Lz7sr0ZMxtJr04ASXVzH5eBWtDkdb0c3LLFsehfPBTRfvlfKJOA==} + /playwright@1.32.2: + resolution: {integrity: sha512-jHVnXJke0PXpuPszKtk9y1zZSlzO5+2a+aockT/AND0oeXx46FiJEFrafthurglLygVZA+1gEbtUM1C7qtTV+Q==} engines: {node: '>=14'} hasBin: true requiresBuild: true dependencies: - playwright-core: 1.29.2 + playwright-core: 1.32.2 dev: true /please-upgrade-node@3.2.0: @@ -19329,7 +19395,7 @@ packages: resolution: {integrity: sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w==} engines: {node: '>=10.12.0'} dependencies: - '@jridgewell/trace-mapping': 0.3.20 + '@jridgewell/trace-mapping': 0.3.17 '@types/istanbul-lib-coverage': 2.0.4 convert-source-map: 1.9.0 dev: true @@ -19941,4 +20007,4 @@ packages: /zxcvbn@4.4.2: resolution: {integrity: sha512-Bq0B+ixT/DMyG8kgX2xWcI5jUvCwqrMxSFam7m0lAf78nf04hv6lNCsyLYdyYTrCVMqNDY/206K7eExYCeSyUQ==} - dev: false \ No newline at end of file + dev: false diff --git a/posthog/admin.py b/posthog/admin.py index 2c0d26a1b01fb..7161cbaf27081 100644 --- a/posthog/admin.py +++ b/posthog/admin.py @@ -33,8 +33,6 @@ ) from posthog.warehouse.models import DataWarehouseTable -admin.site.register(DataWarehouseTable) - class DashboardTileInline(admin.TabularInline): extra = 0 @@ -81,6 +79,39 @@ def organization_link(self, dashboard: Dashboard): ) +@admin.register(DataWarehouseTable) +class DataWarehouseTableAdmin(admin.ModelAdmin): + list_display = ( + "id", + "name", + "format", + "url_pattern", + "team_link", + "organization_link", + "created_at", + "created_by", + ) + list_display_links = ("id", "name") + list_select_related = ("team", "team__organization") + search_fields = ("id", "name", "team__name", "team__organization__name") + autocomplete_fields = ("team", "created_by") + ordering = ("-created_at",) + + def team_link(self, dashboard: Dashboard): + return format_html( + '{}', + dashboard.team.pk, + dashboard.team.name, + ) + + def organization_link(self, dashboard: Dashboard): + return format_html( + '{}', + dashboard.team.organization.pk, + dashboard.team.organization.name, + ) + + @admin.register(Text) class TextAdmin(admin.ModelAdmin): autocomplete_fields = ("created_by", "last_modified_by", "team") diff --git a/posthog/api/cohort.py b/posthog/api/cohort.py index 90324a48bfda0..c023d7ffe7ab2 100644 --- a/posthog/api/cohort.py +++ b/posthog/api/cohort.py @@ -2,6 +2,7 @@ import json from django.db import DatabaseError +from sentry_sdk import start_span import structlog from posthog.models.feature_flag.flag_matching import ( @@ -10,7 +11,8 @@ get_feature_flag_hash_key_overrides, ) from posthog.models.person.person import PersonDistinctId -from posthog.models.property.property import Property +from posthog.models.property.property import Property, PropertyGroup +from posthog.queries.base import property_group_to_Q from posthog.queries.insight import insight_sync_execute import posthoganalytics from posthog.metrics import LABEL_TEAM_ID @@ -47,6 +49,7 @@ INSIGHT_TRENDS, LIMIT, OFFSET, + PropertyOperatorType, ) from posthog.event_usage import report_user_action from posthog.hogql.context import HogQLContext @@ -584,11 +587,20 @@ def get_cohort_actors_for_feature_flag(cohort_id: int, flag: str, team_id: int, for property in property_list: default_person_properties.update(get_default_person_property(property, cohorts_cache)) + flag_property_conditions = [Filter(data=condition).property_groups for condition in feature_flag.conditions] + flag_property_group = PropertyGroup(type=PropertyOperatorType.OR, values=flag_property_conditions) + try: # QuerySet.Iterator() doesn't work with pgbouncer, it will load everything into memory and then stream # which doesn't work for us, so need a manual chunking here. # Because of this pgbouncer transaction pooling mode, we can't use server-side cursors. - queryset = Person.objects.filter(team_id=team_id).order_by("id") + # We pre-filter all persons to be ones that will match the feature flag, so that we don't have to + # iterate through all persons + queryset = ( + Person.objects.filter(team_id=team_id) + .filter(property_group_to_Q(flag_property_group, cohorts_cache=cohorts_cache)) + .order_by("id") + ) # get batchsize number of people at a time start = 0 batch_of_persons = queryset[start : start + batchsize] @@ -614,48 +626,49 @@ def get_cohort_actors_for_feature_flag(cohort_id: int, flag: str, team_id: int, if len(all_persons) == 0: break - for person in all_persons: - # ignore almost-deleted persons / persons with no distinct ids - if len(person.distinct_ids) == 0: - continue - - distinct_id = person.distinct_ids[0] - person_overrides = {} - if feature_flag.ensure_experience_continuity: - # :TRICKY: This is inefficient because it tries to get the hashkey overrides one by one. - # But reusing functions is better for maintainability. Revisit optimising if this becomes a bottleneck. - person_overrides = get_feature_flag_hash_key_overrides( - team_id, [distinct_id], person_id_to_distinct_id_mapping={person.id: distinct_id} - ) + with start_span(op="batch_flag_matching_with_overrides"): + for person in all_persons: + # ignore almost-deleted persons / persons with no distinct ids + if len(person.distinct_ids) == 0: + continue + + distinct_id = person.distinct_ids[0] + person_overrides = {} + if feature_flag.ensure_experience_continuity: + # :TRICKY: This is inefficient because it tries to get the hashkey overrides one by one. + # But reusing functions is better for maintainability. Revisit optimising if this becomes a bottleneck. + person_overrides = get_feature_flag_hash_key_overrides( + team_id, [distinct_id], person_id_to_distinct_id_mapping={person.id: distinct_id} + ) - try: - match = FeatureFlagMatcher( - [feature_flag], - distinct_id, - groups={}, - cache=matcher_cache, - hash_key_overrides=person_overrides, - property_value_overrides={**default_person_properties, **person.properties}, - group_property_value_overrides={}, - cohorts_cache=cohorts_cache, - ).get_match(feature_flag) - if match.match: - uuids_to_add_to_cohort.append(str(person.uuid)) - except (DatabaseError, ValueError, ValidationError): - logger.exception( - "Error evaluating feature flag for person", person_uuid=str(person.uuid), team_id=team_id - ) - except Exception as err: - # matching errors are not fatal, so we just log them and move on. - # Capturing in sentry for now just in case there are some unexpected errors - # we did not account for. - capture_exception(err) - - if len(uuids_to_add_to_cohort) >= batchsize - 1: - cohort.insert_users_list_by_uuid( - uuids_to_add_to_cohort, insert_in_clickhouse=True, batchsize=batchsize - ) - uuids_to_add_to_cohort = [] + try: + match = FeatureFlagMatcher( + [feature_flag], + distinct_id, + groups={}, + cache=matcher_cache, + hash_key_overrides=person_overrides, + property_value_overrides={**default_person_properties, **person.properties}, + group_property_value_overrides={}, + cohorts_cache=cohorts_cache, + ).get_match(feature_flag) + if match.match: + uuids_to_add_to_cohort.append(str(person.uuid)) + except (DatabaseError, ValueError, ValidationError): + logger.exception( + "Error evaluating feature flag for person", person_uuid=str(person.uuid), team_id=team_id + ) + except Exception as err: + # matching errors are not fatal, so we just log them and move on. + # Capturing in sentry for now just in case there are some unexpected errors + # we did not account for. + capture_exception(err) + + if len(uuids_to_add_to_cohort) >= batchsize: + cohort.insert_users_list_by_uuid( + uuids_to_add_to_cohort, insert_in_clickhouse=True, batchsize=batchsize + ) + uuids_to_add_to_cohort = [] start += batchsize batch_of_persons = queryset[start : start + batchsize] diff --git a/posthog/api/feature_flag.py b/posthog/api/feature_flag.py index 92add84a0bcab..5022fd21676ea 100644 --- a/posthog/api/feature_flag.py +++ b/posthog/api/feature_flag.py @@ -636,6 +636,7 @@ def create_static_cohort_for_flag(self, request: request.Request, **kwargs): "is_static": True, "key": feature_flag_key, "name": f'Users with feature flag {feature_flag_key} enabled at {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}', + "is_calculating": True, }, context={ "request": request, diff --git a/posthog/api/test/__snapshots__/test_feature_flag.ambr b/posthog/api/test/__snapshots__/test_feature_flag.ambr index ffe583b425eac..e58824ff62f94 100644 --- a/posthog/api/test/__snapshots__/test_feature_flag.ambr +++ b/posthog/api/test/__snapshots__/test_feature_flag.ambr @@ -380,7 +380,7 @@ LIMIT 21 ' --- -# name: TestCohortGenerationForFeatureFlag.test_creating_static_cohort_iterator.10 +# name: TestCohortGenerationForFeatureFlag.test_creating_static_cohort_iterator.2 ' SELECT "posthog_person"."id", "posthog_person"."created_at", @@ -393,77 +393,15 @@ "posthog_person"."uuid", "posthog_person"."version" FROM "posthog_person" - WHERE "posthog_person"."team_id" = 2 + WHERE ("posthog_person"."team_id" = 2 + AND ("posthog_person"."properties" -> 'key') = '"value"' + AND "posthog_person"."properties" ? 'key' + AND NOT (("posthog_person"."properties" -> 'key') = 'null')) ORDER BY "posthog_person"."id" ASC LIMIT 2 - OFFSET 4 - ' ---- -# name: TestCohortGenerationForFeatureFlag.test_creating_static_cohort_iterator.11 - ' - SELECT "posthog_featureflag"."id", - "posthog_featureflag"."key", - "posthog_featureflag"."name", - "posthog_featureflag"."filters", - "posthog_featureflag"."rollout_percentage", - "posthog_featureflag"."team_id", - "posthog_featureflag"."created_by_id", - "posthog_featureflag"."created_at", - "posthog_featureflag"."deleted", - "posthog_featureflag"."active", - "posthog_featureflag"."rollback_conditions", - "posthog_featureflag"."performed_rollback", - "posthog_featureflag"."ensure_experience_continuity", - "posthog_featureflag"."usage_dashboard_id", - "posthog_featureflag"."has_enriched_analytics" - FROM "posthog_featureflag" - WHERE ("posthog_featureflag"."key" = 'some-feature2' - AND "posthog_featureflag"."team_id" = 2) - LIMIT 21 - ' ---- -# name: TestCohortGenerationForFeatureFlag.test_creating_static_cohort_iterator.12 - ' - SELECT "posthog_cohort"."id", - "posthog_cohort"."name", - "posthog_cohort"."description", - "posthog_cohort"."team_id", - "posthog_cohort"."deleted", - "posthog_cohort"."filters", - "posthog_cohort"."version", - "posthog_cohort"."pending_version", - "posthog_cohort"."count", - "posthog_cohort"."created_by_id", - "posthog_cohort"."created_at", - "posthog_cohort"."is_calculating", - "posthog_cohort"."last_calculation", - "posthog_cohort"."errors_calculating", - "posthog_cohort"."is_static", - "posthog_cohort"."groups" - FROM "posthog_cohort" - WHERE "posthog_cohort"."id" = 2 - LIMIT 21 - ' ---- -# name: TestCohortGenerationForFeatureFlag.test_creating_static_cohort_iterator.13 - ' - SELECT "posthog_person"."id", - "posthog_person"."created_at", - "posthog_person"."properties_last_updated_at", - "posthog_person"."properties_last_operation", - "posthog_person"."team_id", - "posthog_person"."properties", - "posthog_person"."is_user_id", - "posthog_person"."is_identified", - "posthog_person"."uuid", - "posthog_person"."version" - FROM "posthog_person" - WHERE "posthog_person"."team_id" = 2 - ORDER BY "posthog_person"."id" ASC - LIMIT 10 ' --- -# name: TestCohortGenerationForFeatureFlag.test_creating_static_cohort_iterator.14 +# name: TestCohortGenerationForFeatureFlag.test_creating_static_cohort_iterator.3 ' SELECT "posthog_persondistinctid"."id", "posthog_persondistinctid"."team_id", @@ -475,7 +413,7 @@ (SELECT U0."id" FROM "posthog_persondistinctid" U0 WHERE U0."person_id" = "posthog_persondistinctid"."person_id" - LIMIT 1) + LIMIT 3) AND "posthog_persondistinctid"."person_id" IN (1, 2, 3, @@ -483,26 +421,7 @@ 5 /* ... */)) ' --- -# name: TestCohortGenerationForFeatureFlag.test_creating_static_cohort_iterator.15 - ' - SELECT "posthog_person"."id", - "posthog_person"."created_at", - "posthog_person"."properties_last_updated_at", - "posthog_person"."properties_last_operation", - "posthog_person"."team_id", - "posthog_person"."properties", - "posthog_person"."is_user_id", - "posthog_person"."is_identified", - "posthog_person"."uuid", - "posthog_person"."version" - FROM "posthog_person" - WHERE "posthog_person"."team_id" = 2 - ORDER BY "posthog_person"."id" ASC - LIMIT 10 - OFFSET 10 - ' ---- -# name: TestCohortGenerationForFeatureFlag.test_creating_static_cohort_iterator.16 +# name: TestCohortGenerationForFeatureFlag.test_creating_static_cohort_iterator.4 ' SELECT "posthog_person"."uuid" FROM "posthog_person" @@ -517,7 +436,7 @@ LIMIT 1))) ' --- -# name: TestCohortGenerationForFeatureFlag.test_creating_static_cohort_iterator.17 +# name: TestCohortGenerationForFeatureFlag.test_creating_static_cohort_iterator.5 ' SELECT "posthog_team"."id", "posthog_team"."uuid", @@ -568,13 +487,14 @@ "posthog_team"."event_properties", "posthog_team"."event_properties_with_usage", "posthog_team"."event_properties_numerical", - "posthog_team"."external_data_workspace_id" + "posthog_team"."external_data_workspace_id", + "posthog_team"."external_data_workspace_last_synced_at" FROM "posthog_team" WHERE "posthog_team"."id" = 2 LIMIT 21 ' --- -# name: TestCohortGenerationForFeatureFlag.test_creating_static_cohort_iterator.2 +# name: TestCohortGenerationForFeatureFlag.test_creating_static_cohort_iterator.6 ' SELECT "posthog_person"."id", "posthog_person"."created_at", @@ -587,12 +507,16 @@ "posthog_person"."uuid", "posthog_person"."version" FROM "posthog_person" - WHERE "posthog_person"."team_id" = 2 + WHERE ("posthog_person"."team_id" = 2 + AND ("posthog_person"."properties" -> 'key') = '"value"' + AND "posthog_person"."properties" ? 'key' + AND NOT (("posthog_person"."properties" -> 'key') = 'null')) ORDER BY "posthog_person"."id" ASC LIMIT 2 + OFFSET 2 ' --- -# name: TestCohortGenerationForFeatureFlag.test_creating_static_cohort_iterator.3 +# name: TestCohortGenerationForFeatureFlag.test_creating_static_cohort_iterator.7 ' SELECT "posthog_persondistinctid"."id", "posthog_persondistinctid"."team_id", @@ -612,95 +536,7 @@ 5 /* ... */)) ' --- -# name: TestCohortGenerationForFeatureFlag.test_creating_static_cohort_iterator.4 - ' - SELECT "posthog_person"."uuid" - FROM "posthog_person" - WHERE ("posthog_person"."team_id" = 2 - AND "posthog_person"."uuid" IN ('00000000-0000-0000-0000-000000000000'::uuid, - '00000000-0000-0000-0000-000000000001'::uuid /* ... */) - AND NOT (EXISTS - (SELECT (1) AS "a" - FROM "posthog_cohortpeople" U1 - WHERE (U1."cohort_id" = 2 - AND U1."person_id" = "posthog_person"."id") - LIMIT 1))) - ' ---- -# name: TestCohortGenerationForFeatureFlag.test_creating_static_cohort_iterator.5 - ' - SELECT "posthog_team"."id", - "posthog_team"."uuid", - "posthog_team"."organization_id", - "posthog_team"."api_token", - "posthog_team"."app_urls", - "posthog_team"."name", - "posthog_team"."slack_incoming_webhook", - "posthog_team"."created_at", - "posthog_team"."updated_at", - "posthog_team"."anonymize_ips", - "posthog_team"."completed_snippet_onboarding", - "posthog_team"."has_completed_onboarding_for", - "posthog_team"."ingested_event", - "posthog_team"."autocapture_opt_out", - "posthog_team"."autocapture_exceptions_opt_in", - "posthog_team"."autocapture_exceptions_errors_to_ignore", - "posthog_team"."session_recording_opt_in", - "posthog_team"."session_recording_sample_rate", - "posthog_team"."session_recording_minimum_duration_milliseconds", - "posthog_team"."session_recording_linked_flag", - "posthog_team"."session_recording_network_payload_capture_config", - "posthog_team"."capture_console_log_opt_in", - "posthog_team"."capture_performance_opt_in", - "posthog_team"."surveys_opt_in", - "posthog_team"."session_recording_version", - "posthog_team"."signup_token", - "posthog_team"."is_demo", - "posthog_team"."access_control", - "posthog_team"."week_start_day", - "posthog_team"."inject_web_apps", - "posthog_team"."test_account_filters", - "posthog_team"."test_account_filters_default_checked", - "posthog_team"."path_cleaning_filters", - "posthog_team"."timezone", - "posthog_team"."data_attributes", - "posthog_team"."person_display_name_properties", - "posthog_team"."live_events_columns", - "posthog_team"."recording_domains", - "posthog_team"."primary_dashboard_id", - "posthog_team"."extra_settings", - "posthog_team"."correlation_config", - "posthog_team"."session_recording_retention_period_days", - "posthog_team"."plugins_opt_in", - "posthog_team"."opt_out_capture", - "posthog_team"."event_names", - "posthog_team"."event_names_with_usage", - "posthog_team"."event_properties", - "posthog_team"."event_properties_with_usage", - "posthog_team"."event_properties_numerical", - "posthog_team"."external_data_workspace_id", - "posthog_team"."external_data_workspace_last_synced_at" - FROM "posthog_team" - WHERE "posthog_team"."id" = 2 - LIMIT 21 - ' ---- -# name: TestCohortGenerationForFeatureFlag.test_creating_static_cohort_iterator.6 - ' - SELECT "posthog_person"."uuid" - FROM "posthog_person" - WHERE ("posthog_person"."team_id" = 2 - AND "posthog_person"."uuid" IN ('00000000-0000-0000-0000-000000000000'::uuid, - '00000000-0000-0000-0000-000000000001'::uuid /* ... */) - AND NOT (EXISTS - (SELECT (1) AS "a" - FROM "posthog_cohortpeople" U1 - WHERE (U1."cohort_id" = 2 - AND U1."person_id" = "posthog_person"."id") - LIMIT 1))) - ' ---- -# name: TestCohortGenerationForFeatureFlag.test_creating_static_cohort_iterator.7 +# name: TestCohortGenerationForFeatureFlag.test_creating_static_cohort_iterator.8 ' SELECT "posthog_person"."id", "posthog_person"."created_at", @@ -713,30 +549,13 @@ "posthog_person"."uuid", "posthog_person"."version" FROM "posthog_person" - WHERE "posthog_person"."team_id" = 2 + WHERE ("posthog_person"."team_id" = 2 + AND ("posthog_person"."properties" -> 'key') = '"value"' + AND "posthog_person"."properties" ? 'key' + AND NOT (("posthog_person"."properties" -> 'key') = 'null')) ORDER BY "posthog_person"."id" ASC LIMIT 2 - OFFSET 2 - ' ---- -# name: TestCohortGenerationForFeatureFlag.test_creating_static_cohort_iterator.8 - ' - SELECT "posthog_persondistinctid"."id", - "posthog_persondistinctid"."team_id", - "posthog_persondistinctid"."person_id", - "posthog_persondistinctid"."distinct_id", - "posthog_persondistinctid"."version" - FROM "posthog_persondistinctid" - WHERE ("posthog_persondistinctid"."id" IN - (SELECT U0."id" - FROM "posthog_persondistinctid" U0 - WHERE U0."person_id" = "posthog_persondistinctid"."person_id" - LIMIT 3) - AND "posthog_persondistinctid"."person_id" IN (1, - 2, - 3, - 4, - 5 /* ... */)) + OFFSET 4 ' --- # name: TestCohortGenerationForFeatureFlag.test_creating_static_cohort_iterator.9 @@ -909,7 +728,28 @@ "posthog_person"."uuid", "posthog_person"."version" FROM "posthog_person" - WHERE "posthog_person"."team_id" = 2 + WHERE ("posthog_person"."team_id" = 2 + AND ((("posthog_person"."properties" -> 'group') = '"none"' + AND "posthog_person"."properties" ? 'group' + AND NOT (("posthog_person"."properties" -> 'group') = 'null')) + OR (("posthog_person"."properties" -> 'group2') IN ('1', + '2', + '3') + AND "posthog_person"."properties" ? 'group2' + AND NOT (("posthog_person"."properties" -> 'group2') = 'null')) + OR EXISTS + (SELECT (1) AS "a" + FROM "posthog_cohortpeople" U0 + WHERE (U0."cohort_id" = 2 + AND U0."cohort_id" = 2 + AND U0."person_id" = "posthog_person"."id") + LIMIT 1) + OR (("posthog_person"."properties" -> 'does-not-exist') = '"none"' + AND "posthog_person"."properties" ? 'does-not-exist' + AND NOT (("posthog_person"."properties" -> 'does-not-exist') = 'null')) + OR (("posthog_person"."properties" -> 'key') = '"value"' + AND "posthog_person"."properties" ? 'key' + AND NOT (("posthog_person"."properties" -> 'key') = 'null')))) ORDER BY "posthog_person"."id" ASC LIMIT 1000 ' @@ -1023,7 +863,28 @@ "posthog_person"."uuid", "posthog_person"."version" FROM "posthog_person" - WHERE "posthog_person"."team_id" = 2 + WHERE ("posthog_person"."team_id" = 2 + AND ((("posthog_person"."properties" -> 'group') = '"none"' + AND "posthog_person"."properties" ? 'group' + AND NOT (("posthog_person"."properties" -> 'group') = 'null')) + OR (("posthog_person"."properties" -> 'group2') IN ('1', + '2', + '3') + AND "posthog_person"."properties" ? 'group2' + AND NOT (("posthog_person"."properties" -> 'group2') = 'null')) + OR EXISTS + (SELECT (1) AS "a" + FROM "posthog_cohortpeople" U0 + WHERE (U0."cohort_id" = 2 + AND U0."cohort_id" = 2 + AND U0."person_id" = "posthog_person"."id") + LIMIT 1) + OR (("posthog_person"."properties" -> 'does-not-exist') = '"none"' + AND "posthog_person"."properties" ? 'does-not-exist' + AND NOT (("posthog_person"."properties" -> 'does-not-exist') = 'null')) + OR (("posthog_person"."properties" -> 'key') = '"value"' + AND "posthog_person"."properties" ? 'key' + AND NOT (("posthog_person"."properties" -> 'key') = 'null')))) ORDER BY "posthog_person"."id" ASC LIMIT 1000 OFFSET 1000 @@ -1096,16 +957,6 @@ ' --- # name: TestCohortGenerationForFeatureFlag.test_creating_static_cohort_with_default_person_properties_adjustment.11 - ' - SELECT ("posthog_person"."properties" -> 'key') IS NOT NULL AS "flag_X_condition_0" - FROM "posthog_person" - INNER JOIN "posthog_persondistinctid" ON ("posthog_person"."id" = "posthog_persondistinctid"."person_id") - WHERE ("posthog_persondistinctid"."distinct_id" = 'person3' - AND "posthog_persondistinctid"."team_id" = 2 - AND "posthog_person"."team_id" = 2) - ' ---- -# name: TestCohortGenerationForFeatureFlag.test_creating_static_cohort_with_default_person_properties_adjustment.12 ' SELECT "posthog_person"."id", "posthog_person"."created_at", @@ -1118,13 +969,14 @@ "posthog_person"."uuid", "posthog_person"."version" FROM "posthog_person" - WHERE "posthog_person"."team_id" = 2 + WHERE ("posthog_person"."team_id" = 2 + AND ("posthog_person"."properties" -> 'key') IS NOT NULL) ORDER BY "posthog_person"."id" ASC LIMIT 1000 OFFSET 1000 ' --- -# name: TestCohortGenerationForFeatureFlag.test_creating_static_cohort_with_default_person_properties_adjustment.13 +# name: TestCohortGenerationForFeatureFlag.test_creating_static_cohort_with_default_person_properties_adjustment.12 ' SELECT "posthog_person"."uuid" FROM "posthog_person" @@ -1139,7 +991,7 @@ LIMIT 1))) ' --- -# name: TestCohortGenerationForFeatureFlag.test_creating_static_cohort_with_default_person_properties_adjustment.14 +# name: TestCohortGenerationForFeatureFlag.test_creating_static_cohort_with_default_person_properties_adjustment.13 ' SELECT "posthog_team"."id", "posthog_team"."uuid", @@ -1210,7 +1062,10 @@ "posthog_person"."uuid", "posthog_person"."version" FROM "posthog_person" - WHERE "posthog_person"."team_id" = 2 + WHERE ("posthog_person"."team_id" = 2 + AND UPPER(("posthog_person"."properties" ->> 'key')::text) LIKE UPPER('%value%') + AND "posthog_person"."properties" ? 'key' + AND NOT (("posthog_person"."properties" -> 'key') = 'null')) ORDER BY "posthog_person"."id" ASC LIMIT 1000 ' @@ -1248,7 +1103,10 @@ "posthog_person"."uuid", "posthog_person"."version" FROM "posthog_person" - WHERE "posthog_person"."team_id" = 2 + WHERE ("posthog_person"."team_id" = 2 + AND UPPER(("posthog_person"."properties" ->> 'key')::text) LIKE UPPER('%value%') + AND "posthog_person"."properties" ? 'key' + AND NOT (("posthog_person"."properties" -> 'key') = 'null')) ORDER BY "posthog_person"."id" ASC LIMIT 1000 OFFSET 1000 @@ -1386,34 +1244,12 @@ "posthog_person"."uuid", "posthog_person"."version" FROM "posthog_person" - WHERE "posthog_person"."team_id" = 2 + WHERE ("posthog_person"."team_id" = 2 + AND ("posthog_person"."properties" -> 'key') IS NOT NULL) ORDER BY "posthog_person"."id" ASC LIMIT 1000 ' --- -# name: TestCohortGenerationForFeatureFlag.test_creating_static_cohort_with_deleted_flag - ' - SELECT "posthog_featureflag"."id", - "posthog_featureflag"."key", - "posthog_featureflag"."name", - "posthog_featureflag"."filters", - "posthog_featureflag"."rollout_percentage", - "posthog_featureflag"."team_id", - "posthog_featureflag"."created_by_id", - "posthog_featureflag"."created_at", - "posthog_featureflag"."deleted", - "posthog_featureflag"."active", - "posthog_featureflag"."rollback_conditions", - "posthog_featureflag"."performed_rollback", - "posthog_featureflag"."ensure_experience_continuity", - "posthog_featureflag"."usage_dashboard_id", - "posthog_featureflag"."has_enriched_analytics" - FROM "posthog_featureflag" - WHERE ("posthog_featureflag"."key" = 'some-feature' - AND "posthog_featureflag"."team_id" = 2) - LIMIT 21 - ' ---- # name: TestCohortGenerationForFeatureFlag.test_creating_static_cohort_with_experience_continuity_flag ' SELECT "posthog_featureflag"."id", @@ -1460,83 +1296,7 @@ LIMIT 21 ' --- -# name: TestCohortGenerationForFeatureFlag.test_creating_static_cohort_with_experience_continuity_flag.10 - ' - SELECT "posthog_team"."id", - "posthog_team"."uuid", - "posthog_team"."organization_id", - "posthog_team"."api_token", - "posthog_team"."app_urls", - "posthog_team"."name", - "posthog_team"."slack_incoming_webhook", - "posthog_team"."created_at", - "posthog_team"."updated_at", - "posthog_team"."anonymize_ips", - "posthog_team"."completed_snippet_onboarding", - "posthog_team"."has_completed_onboarding_for", - "posthog_team"."ingested_event", - "posthog_team"."autocapture_opt_out", - "posthog_team"."autocapture_exceptions_opt_in", - "posthog_team"."autocapture_exceptions_errors_to_ignore", - "posthog_team"."session_recording_opt_in", - "posthog_team"."session_recording_sample_rate", - "posthog_team"."session_recording_minimum_duration_milliseconds", - "posthog_team"."session_recording_linked_flag", - "posthog_team"."session_recording_network_payload_capture_config", - "posthog_team"."capture_console_log_opt_in", - "posthog_team"."capture_performance_opt_in", - "posthog_team"."surveys_opt_in", - "posthog_team"."session_recording_version", - "posthog_team"."signup_token", - "posthog_team"."is_demo", - "posthog_team"."access_control", - "posthog_team"."week_start_day", - "posthog_team"."inject_web_apps", - "posthog_team"."test_account_filters", - "posthog_team"."test_account_filters_default_checked", - "posthog_team"."path_cleaning_filters", - "posthog_team"."timezone", - "posthog_team"."data_attributes", - "posthog_team"."person_display_name_properties", - "posthog_team"."live_events_columns", - "posthog_team"."recording_domains", - "posthog_team"."primary_dashboard_id", - "posthog_team"."extra_settings", - "posthog_team"."correlation_config", - "posthog_team"."session_recording_retention_period_days", - "posthog_team"."plugins_opt_in", - "posthog_team"."opt_out_capture", - "posthog_team"."event_names", - "posthog_team"."event_names_with_usage", - "posthog_team"."event_properties", - "posthog_team"."event_properties_with_usage", - "posthog_team"."event_properties_numerical", - "posthog_team"."external_data_workspace_id" - FROM "posthog_team" - WHERE "posthog_team"."id" = 2 - LIMIT 21 - ' ---- -# name: TestCohortGenerationForFeatureFlag.test_creating_static_cohort_with_experience_continuity_flag.11 - ' - SELECT "posthog_person"."id", - "posthog_person"."created_at", - "posthog_person"."properties_last_updated_at", - "posthog_person"."properties_last_operation", - "posthog_person"."team_id", - "posthog_person"."properties", - "posthog_person"."is_user_id", - "posthog_person"."is_identified", - "posthog_person"."uuid", - "posthog_person"."version" - FROM "posthog_person" - WHERE "posthog_person"."team_id" = 2 - ORDER BY "posthog_person"."id" ASC - LIMIT 21 - OFFSET 5000 - ' ---- -# name: TestCohortGenerationForFeatureFlag.test_creating_static_cohort_with_experience_continuity_flag.12 +# name: TestCohortGenerationForFeatureFlag.test_creating_static_cohort_with_experience_continuity_flag.2 ' SELECT "posthog_person"."id", "posthog_person"."created_at", @@ -1549,98 +1309,10 @@ "posthog_person"."uuid", "posthog_person"."version" FROM "posthog_person" - WHERE "posthog_person"."team_id" = 2 - ORDER BY "posthog_person"."id" ASC - LIMIT 5000 - OFFSET 5000 - ' ---- -# name: TestCohortGenerationForFeatureFlag.test_creating_static_cohort_with_experience_continuity_flag.13 - ' - SELECT "posthog_person"."uuid" - FROM "posthog_person" WHERE ("posthog_person"."team_id" = 2 - AND "posthog_person"."uuid" IN ('00000000-0000-0000-0000-000000000000'::uuid, - '00000000-0000-0000-0000-000000000001'::uuid /* ... */) - AND NOT (EXISTS - (SELECT (1) AS "a" - FROM "posthog_cohortpeople" U1 - WHERE (U1."cohort_id" = 2 - AND U1."person_id" = "posthog_person"."id") - LIMIT 1))) - ' ---- -# name: TestCohortGenerationForFeatureFlag.test_creating_static_cohort_with_experience_continuity_flag.14 - ' - SELECT "posthog_team"."id", - "posthog_team"."uuid", - "posthog_team"."organization_id", - "posthog_team"."api_token", - "posthog_team"."app_urls", - "posthog_team"."name", - "posthog_team"."slack_incoming_webhook", - "posthog_team"."created_at", - "posthog_team"."updated_at", - "posthog_team"."anonymize_ips", - "posthog_team"."completed_snippet_onboarding", - "posthog_team"."has_completed_onboarding_for", - "posthog_team"."ingested_event", - "posthog_team"."autocapture_opt_out", - "posthog_team"."autocapture_exceptions_opt_in", - "posthog_team"."autocapture_exceptions_errors_to_ignore", - "posthog_team"."session_recording_opt_in", - "posthog_team"."session_recording_sample_rate", - "posthog_team"."session_recording_minimum_duration_milliseconds", - "posthog_team"."session_recording_linked_flag", - "posthog_team"."session_recording_network_payload_capture_config", - "posthog_team"."capture_console_log_opt_in", - "posthog_team"."capture_performance_opt_in", - "posthog_team"."surveys_opt_in", - "posthog_team"."session_recording_version", - "posthog_team"."signup_token", - "posthog_team"."is_demo", - "posthog_team"."access_control", - "posthog_team"."week_start_day", - "posthog_team"."inject_web_apps", - "posthog_team"."test_account_filters", - "posthog_team"."test_account_filters_default_checked", - "posthog_team"."path_cleaning_filters", - "posthog_team"."timezone", - "posthog_team"."data_attributes", - "posthog_team"."person_display_name_properties", - "posthog_team"."live_events_columns", - "posthog_team"."recording_domains", - "posthog_team"."primary_dashboard_id", - "posthog_team"."extra_settings", - "posthog_team"."correlation_config", - "posthog_team"."session_recording_retention_period_days", - "posthog_team"."plugins_opt_in", - "posthog_team"."opt_out_capture", - "posthog_team"."event_names", - "posthog_team"."event_names_with_usage", - "posthog_team"."event_properties", - "posthog_team"."event_properties_with_usage", - "posthog_team"."event_properties_numerical", - "posthog_team"."external_data_workspace_id" - FROM "posthog_team" - WHERE "posthog_team"."id" = 2 - LIMIT 21 - ' ---- -# name: TestCohortGenerationForFeatureFlag.test_creating_static_cohort_with_experience_continuity_flag.2 - ' - SELECT "posthog_person"."id", - "posthog_person"."created_at", - "posthog_person"."properties_last_updated_at", - "posthog_person"."properties_last_operation", - "posthog_person"."team_id", - "posthog_person"."properties", - "posthog_person"."is_user_id", - "posthog_person"."is_identified", - "posthog_person"."uuid", - "posthog_person"."version" - FROM "posthog_person" - WHERE "posthog_person"."team_id" = 2 + AND ("posthog_person"."properties" -> 'key') = '"value"' + AND "posthog_person"."properties" ? 'key' + AND NOT (("posthog_person"."properties" -> 'key') = 'null')) ORDER BY "posthog_person"."id" ASC LIMIT 1000 ' @@ -1720,7 +1392,10 @@ "posthog_person"."uuid", "posthog_person"."version" FROM "posthog_person" - WHERE "posthog_person"."team_id" = 2 + WHERE ("posthog_person"."team_id" = 2 + AND ("posthog_person"."properties" -> 'key') = '"value"' + AND "posthog_person"."properties" ? 'key' + AND NOT (("posthog_person"."properties" -> 'key') = 'null')) ORDER BY "posthog_person"."id" ASC LIMIT 1000 OFFSET 1000 @@ -1799,161 +1474,6 @@ LIMIT 21 ' --- -# name: TestCohortGenerationForFeatureFlag.test_creating_static_cohort_with_group_flag - ' - SELECT "posthog_featureflag"."id", - "posthog_featureflag"."key", - "posthog_featureflag"."name", - "posthog_featureflag"."filters", - "posthog_featureflag"."rollout_percentage", - "posthog_featureflag"."team_id", - "posthog_featureflag"."created_by_id", - "posthog_featureflag"."created_at", - "posthog_featureflag"."deleted", - "posthog_featureflag"."active", - "posthog_featureflag"."rollback_conditions", - "posthog_featureflag"."performed_rollback", - "posthog_featureflag"."ensure_experience_continuity", - "posthog_featureflag"."usage_dashboard_id", - "posthog_featureflag"."has_enriched_analytics" - FROM "posthog_featureflag" - WHERE ("posthog_featureflag"."key" = 'some-feature3' - AND "posthog_featureflag"."team_id" = 2) - LIMIT 21 - ' ---- -# name: TestCohortGenerationForFeatureFlag.test_creating_static_cohort_with_group_flag.1 - ' - SELECT "posthog_cohort"."id", - "posthog_cohort"."name", - "posthog_cohort"."description", - "posthog_cohort"."team_id", - "posthog_cohort"."deleted", - "posthog_cohort"."filters", - "posthog_cohort"."version", - "posthog_cohort"."pending_version", - "posthog_cohort"."count", - "posthog_cohort"."created_by_id", - "posthog_cohort"."created_at", - "posthog_cohort"."is_calculating", - "posthog_cohort"."last_calculation", - "posthog_cohort"."errors_calculating", - "posthog_cohort"."is_static", - "posthog_cohort"."groups" - FROM "posthog_cohort" - WHERE "posthog_cohort"."id" = 2 - LIMIT 21 - ' ---- -# name: TestCohortGenerationForFeatureFlag.test_creating_static_cohort_with_group_flag.2 - ' - DECLARE "_django_curs_X" NO SCROLL - CURSOR WITHOUT HOLD - FOR - SELECT "posthog_person"."id", - "posthog_person"."created_at", - "posthog_person"."properties_last_updated_at", - "posthog_person"."properties_last_operation", - "posthog_person"."team_id", - "posthog_person"."properties", - "posthog_person"."is_user_id", - "posthog_person"."is_identified", - "posthog_person"."uuid", - "posthog_person"."version" - FROM "posthog_person" - WHERE "posthog_person"."team_id" = 2 - ' ---- -# name: TestCohortGenerationForFeatureFlag.test_creating_static_cohort_with_group_flag.3 - ' - SELECT "posthog_persondistinctid"."distinct_id" - FROM "posthog_persondistinctid" - WHERE ("posthog_persondistinctid"."person_id" = 2 - AND "posthog_persondistinctid"."team_id" = 2) - LIMIT 1 - ' ---- -# name: TestCohortGenerationForFeatureFlag.test_creating_static_cohort_with_group_flag.4 - ' - SELECT "posthog_grouptypemapping"."id", - "posthog_grouptypemapping"."team_id", - "posthog_grouptypemapping"."group_type", - "posthog_grouptypemapping"."group_type_index", - "posthog_grouptypemapping"."name_singular", - "posthog_grouptypemapping"."name_plural" - FROM "posthog_grouptypemapping" - WHERE "posthog_grouptypemapping"."team_id" = 2 - ' ---- -# name: TestCohortGenerationForFeatureFlag.test_creating_static_cohort_with_inactive_flag - ' - SELECT "posthog_featureflag"."id", - "posthog_featureflag"."key", - "posthog_featureflag"."name", - "posthog_featureflag"."filters", - "posthog_featureflag"."rollout_percentage", - "posthog_featureflag"."team_id", - "posthog_featureflag"."created_by_id", - "posthog_featureflag"."created_at", - "posthog_featureflag"."deleted", - "posthog_featureflag"."active", - "posthog_featureflag"."rollback_conditions", - "posthog_featureflag"."performed_rollback", - "posthog_featureflag"."ensure_experience_continuity", - "posthog_featureflag"."usage_dashboard_id", - "posthog_featureflag"."has_enriched_analytics" - FROM "posthog_featureflag" - WHERE ("posthog_featureflag"."key" = 'some-feature2' - AND "posthog_featureflag"."team_id" = 2) - LIMIT 21 - ' ---- -# name: TestCohortGenerationForFeatureFlag.test_creating_static_cohort_with_invalid_flags - ' - SELECT "posthog_featureflag"."id", - "posthog_featureflag"."key", - "posthog_featureflag"."name", - "posthog_featureflag"."filters", - "posthog_featureflag"."rollout_percentage", - "posthog_featureflag"."team_id", - "posthog_featureflag"."created_by_id", - "posthog_featureflag"."created_at", - "posthog_featureflag"."deleted", - "posthog_featureflag"."active", - "posthog_featureflag"."rollback_conditions", - "posthog_featureflag"."performed_rollback", - "posthog_featureflag"."ensure_experience_continuity", - "posthog_featureflag"."usage_dashboard_id", - "posthog_featureflag"."has_enriched_analytics" - FROM "posthog_featureflag" - WHERE ("posthog_featureflag"."key" = 'some-feature' - AND "posthog_featureflag"."team_id" = 2) - LIMIT 21 - ' ---- -# name: TestCohortGenerationForFeatureFlag.test_creating_static_cohort_with_non_existing_flag - ' - SELECT "posthog_featureflag"."id", - "posthog_featureflag"."key", - "posthog_featureflag"."name", - "posthog_featureflag"."filters", - "posthog_featureflag"."rollout_percentage", - "posthog_featureflag"."team_id", - "posthog_featureflag"."created_by_id", - "posthog_featureflag"."created_at", - "posthog_featureflag"."deleted", - "posthog_featureflag"."active", - "posthog_featureflag"."rollback_conditions", - "posthog_featureflag"."performed_rollback", - "posthog_featureflag"."ensure_experience_continuity", - "posthog_featureflag"."usage_dashboard_id", - "posthog_featureflag"."has_enriched_analytics" - FROM "posthog_featureflag" - WHERE ("posthog_featureflag"."key" = 'some-feature2' - AND "posthog_featureflag"."team_id" = 2) - LIMIT 21 - ' ---- # name: TestFeatureFlag.test_creating_static_cohort ' SELECT "posthog_user"."id", @@ -2067,10 +1587,13 @@ "posthog_person"."uuid", "posthog_person"."version" FROM "posthog_person" - WHERE "posthog_person"."team_id" = 2 + WHERE ("posthog_person"."team_id" = 2 + AND ("posthog_person"."properties" -> 'key') = '"value"' + AND "posthog_person"."properties" ? 'key' + AND NOT (("posthog_person"."properties" -> 'key') = 'null')) ORDER BY "posthog_person"."id" ASC - LIMIT 1000 - OFFSET 1000 /*controller='project_feature_flags-create-static-cohort-for-flag',route='api/projects/%28%3FP%3Cparent_lookup_team_id%3E%5B%5E/.%5D%2B%29/feature_flags/%28%3FP%3Cpk%3E%5B%5E/.%5D%2B%29/create_static_cohort_for_flag/%3F%24'*/ + LIMIT 10000 + OFFSET 10000 /*controller='project_feature_flags-create-static-cohort-for-flag',route='api/projects/%28%3FP%3Cparent_lookup_team_id%3E%5B%5E/.%5D%2B%29/feature_flags/%28%3FP%3Cpk%3E%5B%5E/.%5D%2B%29/create_static_cohort_for_flag/%3F%24'*/ ' --- # name: TestFeatureFlag.test_creating_static_cohort.12 @@ -2519,9 +2042,12 @@ "posthog_person"."uuid", "posthog_person"."version" FROM "posthog_person" - WHERE "posthog_person"."team_id" = 2 + WHERE ("posthog_person"."team_id" = 2 + AND ("posthog_person"."properties" -> 'key') = '"value"' + AND "posthog_person"."properties" ? 'key' + AND NOT (("posthog_person"."properties" -> 'key') = 'null')) ORDER BY "posthog_person"."id" ASC - LIMIT 1000 /*controller='project_feature_flags-create-static-cohort-for-flag',route='api/projects/%28%3FP%3Cparent_lookup_team_id%3E%5B%5E/.%5D%2B%29/feature_flags/%28%3FP%3Cpk%3E%5B%5E/.%5D%2B%29/create_static_cohort_for_flag/%3F%24'*/ + LIMIT 10000 /*controller='project_feature_flags-create-static-cohort-for-flag',route='api/projects/%28%3FP%3Cparent_lookup_team_id%3E%5B%5E/.%5D%2B%29/feature_flags/%28%3FP%3Cpk%3E%5B%5E/.%5D%2B%29/create_static_cohort_for_flag/%3F%24'*/ ' --- # name: TestResiliency.test_feature_flags_v3_with_experience_continuity_working_slow_db diff --git a/posthog/api/test/test_feature_flag.py b/posthog/api/test/test_feature_flag.py index 31f46aabff9a0..9e33e55ca1a51 100644 --- a/posthog/api/test/test_feature_flag.py +++ b/posthog/api/test/test_feature_flag.py @@ -3883,7 +3883,7 @@ def test_creating_static_cohort_iterator(self): ) # Extra queries because each batch adds its own queries - with snapshot_postgres_queries_context(self), self.assertNumQueries(17): + with snapshot_postgres_queries_context(self), self.assertNumQueries(14): get_cohort_actors_for_feature_flag(cohort.pk, "some-feature2", self.team.pk, batchsize=2) cohort.refresh_from_db() @@ -3974,8 +3974,8 @@ def test_creating_static_cohort_with_default_person_properties_adjustment(self): name="some cohort2", ) - with snapshot_postgres_queries_context(self), self.assertNumQueries(13): - # need to evaluate flags for person3 using db, because is_set operator can't have defaults added. + with snapshot_postgres_queries_context(self), self.assertNumQueries(9): + # person3 doesn't match filter conditions so is pre-filtered out get_cohort_actors_for_feature_flag(cohort2.pk, "some-feature-new", self.team.pk) cohort2.refresh_from_db() diff --git a/posthog/settings/sentry.py b/posthog/settings/sentry.py index d38d73300f792..208c862c7fa7e 100644 --- a/posthog/settings/sentry.py +++ b/posthog/settings/sentry.py @@ -114,6 +114,14 @@ def traces_sampler(sampling_context: dict) -> float: else: # Default sample rate for Celery tasks return 0.001 # 0.1% + elif op == "queue.task.celery": + task = sampling_context.get("celery_job", {}).get("task") + if task == "posthog.tasks.calculate_cohort.insert_cohort_from_feature_flag": + # sample all cohort calculations via feature flag + return 1 + # Default sample rate + return 0.01 + else: # Default sample rate for everything else return 0.01 # 1% diff --git a/posthog/tasks/calculate_cohort.py b/posthog/tasks/calculate_cohort.py index 066469636dc37..b4ff3a9aff390 100644 --- a/posthog/tasks/calculate_cohort.py +++ b/posthog/tasks/calculate_cohort.py @@ -77,4 +77,4 @@ def insert_cohort_from_insight_filter(cohort_id: int, filter_data: Dict[str, Any def insert_cohort_from_feature_flag(cohort_id: int, flag_key: str, team_id: int) -> None: from posthog.api.cohort import get_cohort_actors_for_feature_flag - get_cohort_actors_for_feature_flag(cohort_id, flag_key, team_id) + get_cohort_actors_for_feature_flag(cohort_id, flag_key, team_id, batchsize=10_000) diff --git a/posthog/warehouse/models/table.py b/posthog/warehouse/models/table.py index 2acf4fb1faf9b..93e6a20e890f2 100644 --- a/posthog/warehouse/models/table.py +++ b/posthog/warehouse/models/table.py @@ -43,6 +43,7 @@ "Array": StringArrayDatabaseField, "Map": StringJSONDatabaseField, "Bool": BooleanDatabaseField, + "Decimal": IntegerDatabaseField, } ExtractErrors = {