Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
274fa83
fix(metrics): remove explain metric button from viewer clause row
frankh Oct 1, 2026
29596c0
refactor(metrics): remove the fundamentals tab, explain API, and flag
frankh Oct 1, 2026
71fc81e
chore: update OpenAPI generated types
tests-posthog[bot] Oct 1, 2026
29505b1
fix(aio): bound openai-compatible provider requests
Radu-Raicea Oct 1, 2026
bcfd812
fix(aio): close bounded streams outside active event loops
Radu-Raicea Oct 1, 2026
7451568
Merge remote-tracking branch 'origin/master' into feat/aio-openai-com…
Radu-Raicea Oct 1, 2026
1b75a63
Merge branch 'master' into posthog/remove-metric-explain-button
frankh Oct 1, 2026
3e331e3
refactor(oauth): mint direct access tokens through one core helper
webjunkie Oct 1, 2026
83ef097
feat(tasks): show artifact comment threads inline
puemos Oct 2, 2026
2ac0bb6
chore(aio): merge latest master for provider review fixes
Radu-Raicea Oct 2, 2026
a6a1bf9
fix(aio): address bounded provider review feedback
Radu-Raicea Oct 2, 2026
be7db4e
chore(autoresearch): deploy autoresearch code to the self-driving worker
posthog[bot] Oct 2, 2026
c89116c
chore(aio): merge master into custom provider limits
Radu-Raicea Oct 2, 2026
948d374
fix(aio): retry custom provider rate limits in evaluations
Radu-Raicea Oct 2, 2026
6e3b0b2
fix(autoresearch): keep both ends of a clipped emit error description
posthog[bot] Oct 2, 2026
26a035a
feat(ai-research): send a us.posthog.com referer from the image fetch…
robbie-c Oct 2, 2026
8911ed0
perf(metrics): read the hourly projection in the overview services query
frankh Oct 2, 2026
de574c6
test(metrics): force the projection instead of reading the query log
frankh Oct 2, 2026
a4f679b
fix(aio): skip rejected tagger responses
Radu-Raicea Oct 2, 2026
b78cf57
chore(aio): merge master into provider request bounds
Radu-Raicea Oct 2, 2026
05e6399
chore(aio): pin tagger workflow test clock
Radu-Raicea Oct 2, 2026
82a3890
fix(meta-ads): keep entity syncs retryable when meta refuses the smal…
kyleswank Oct 2, 2026
fcf0b83
chore(data-warehouse): remove data quality tab from data ops
sakce Oct 2, 2026
bb253de
fix(tasks): open a new session page from the new chat button
k11kirky Oct 2, 2026
9fca0c9
fix(tasks): rename new chat to new session and share the space composer
k11kirky Oct 2, 2026
90a85b6
chore(tasks): refresh new session logic typegen after rebase
k11kirky Oct 2, 2026
bd9a99b
fix(tasks): place the new session composer 34% down the pane like des…
k11kirky Oct 2, 2026
02d00c0
fix(tasks): default new sessions to the right space and drop the defa…
k11kirky Oct 2, 2026
c8d2377
chore(tasks): refresh typegen and app url manifest for new session ro…
k11kirky Oct 2, 2026
311d369
test(mcp): update the generate-app-url snapshot for the new session r…
k11kirky Oct 2, 2026
e93aed4
feat(autoresearch): have the training agent build a report notebook
andrewm4894 Oct 2, 2026
08b6aff
chore(aio): merge latest master
Radu-Raicea Oct 2, 2026
ba71a9e
chore(aio): sync master for final validation
Radu-Raicea Oct 2, 2026
28d01ec
fix(aio): release stream slots before blocked reads finish
Radu-Raicea Oct 2, 2026
2f99c86
chore(data-warehouse): point data quality e2e at the models overview
sakce Oct 2, 2026
a177dfa
feat(tasks): show cited posthog objects as their real pages in artifacts
posthog[bot] Oct 2, 2026
782185a
fix(tasks): sandbox the object page frame and keep its fixes local
posthog[bot] Oct 2, 2026
2d77dfb
fix(tasks): reset the object page loader when the frame url changes
posthog[bot] Oct 2, 2026
0fa23a3
feat(data-warehouse): implement the aws inspector import source
Gilbert09 Oct 2, 2026
0d84912
Merging e93aed49af118b347fe8eb6b6b2e6616683f2354 into trunk-temp/pr-1…
trunk-io[bot] Oct 2, 2026
94a4f82
Merging de574c6c923776b50e0f3b57c874fe94eaa81c00 into trunk-temp/pr-1…
trunk-io[bot] Oct 2, 2026
d77f93d
Merging 1b75a63b38b928c41dac69f3d3555eb1517bd9a7 into trunk-temp/pr-1…
trunk-io[bot] Oct 2, 2026
83fa7f4
Merging 83ef09782feafeff1353255b9d4c496a72458c70 into trunk-temp/pr-1…
trunk-io[bot] Oct 2, 2026
c6440ea
Merging 83fa7f476ea2baaa497a2f1584ca220cfdd6f0ab into trunk-temp/pr-1…
trunk-io[bot] Oct 2, 2026
6bd60a1
Merging 82a38903a723ce57c12ad90e35b0bb13318cd788 into trunk-temp/pr-1…
trunk-io[bot] Oct 2, 2026
ef48045
Merging 28d01ecf7cd406001af92ba7d8bf132c81edf07d into trunk-temp/pr-1…
trunk-io[bot] Oct 2, 2026
d287bb8
Merging 311d3694a23fc3383d4b47828c6f0fc085987792 into trunk-temp/pr-1…
trunk-io[bot] Oct 2, 2026
ab76094
Merging 2f99c866cdcbd3cbf1c289416f3c935162b9647d into trunk-temp/pr-1…
trunk-io[bot] Oct 2, 2026
d5b2e5d
Merging ab760943434afd75c1d0ef982f392341ebb489b3 into trunk-temp/pr-1…
trunk-io[bot] Oct 2, 2026
fdce82c
Merging 2d77dfb80f92fa13866750fc15e044aa22d6151b into trunk-temp/pr-1…
trunk-io[bot] Oct 2, 2026
ca921c0
Merging 3e331e33697a554823f012896e76b40dca5bb55e into trunk-temp/pr-1…
trunk-io[bot] Oct 2, 2026
b9e4fb7
Merging 0fa23a3175decc031deb8e5c2faf0259e5b2a8e9 into trunk-temp/pr-1…
trunk-io[bot] Oct 2, 2026
a942587
Merging 6e3b0b2a076c7e96e90f99598ae77db6bc722fca into trunk-temp/pr-1…
trunk-io[bot] Oct 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/container-images-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,7 @@ jobs:
selfDriving:
- 'products/signals/backend/**'
- 'products/signals/skills/**'
- 'products/autoresearch/backend/**'
- 'posthog/settings/temporal.py'
- 'posthog/temporal/common/**'
- 'posthog/management/commands/start_temporal_worker.py'
Expand Down
18 changes: 18 additions & 0 deletions docs/internal/ai-observability-judge-inputs.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,24 @@ They sample the combined input, tool definitions, and output only when that text

Implementation: [trace judge](../../posthog/temporal/ai_observability/run_trace_evaluation.py), [session judge](../../posthog/temporal/ai_observability/run_session_evaluation.py), and [generation judge](../../posthog/temporal/ai_observability/evaluation_llm_judge.py).

## OpenAI-compatible judges

Custom OpenAI-compatible connections use the shared DNS-pinned HTTPX transport with response bounds enabled.
Every completion request has a 60-second total HTTP deadline, including connection setup, response headers, and the body.
Key validation and model listing use a 10-second total deadline.
Responses, including errors and streamed completions, are limited to 1 MiB.
The endpoint must return uncompressed responses; compressed responses are rejected before decoding.
Expired requests, rejected responses, and streams closed by the caller close their underlying connection.

The OpenAI SDK does not retry custom-provider requests. Online evaluations and taggers use their existing Temporal retry policies for transient failures, and worker cancellation propagates to Temporal.
Rate-limit responses retry without disabling the evaluation or marking its connection invalid, honoring `Retry-After` up to one minute. Quota and authentication errors keep their existing terminal behavior.
Models without native structured-output support retain the JSON fallback, which can make one additional bounded request.
Oversized or compressed completion responses skip the evaluation as a rejected request without disabling the connection.
The evaluation records the response limit and how to configure the endpoint.
These connection and response limits also apply when using the same provider in the playground.
Disconnecting from the playground releases the server's stream slot without waiting for an in-flight provider read.
The worker closes the connection when that read finishes or reaches the provider's deadline.

## System One judges

System One-compatible models are available under the existing LLM judge option.
Expand Down
42 changes: 35 additions & 7 deletions ee/hogai/utils/asgi.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from collections.abc import AsyncIterator, Callable, Iterable, Iterator
import threading
from collections.abc import AsyncIterator, Iterable, Iterator
from typing import TypeVar

from asgiref.sync import sync_to_async
Expand All @@ -9,18 +10,45 @@
class SyncIterableToAsync(AsyncIterator[T]):
def __init__(self, iterable: Iterable[T]) -> None:
self._iterable: Iterable[T] = iterable
# async versions of the `next` and `iter` functions
self.next_async: Callable = sync_to_async(self.next, thread_sensitive=False)
self.iter_async: Callable = sync_to_async(iter, thread_sensitive=False)
self.sync_iterator: Iterator[T] | None = None
self._lock = threading.Lock()
self._close_requested = threading.Event()
self._closed = False

def __aiter__(self) -> AsyncIterator[T]:
return self

async def __anext__(self) -> T:
if self.sync_iterator is None:
self.sync_iterator = await self.iter_async(self._iterable)
return await self.next_async(self.sync_iterator)
return await sync_to_async(self._next, thread_sensitive=False)()

def _next(self) -> T:
try:
with self._lock:
if self._close_requested.is_set():
raise StopAsyncIteration
if self.sync_iterator is None:
self.sync_iterator = iter(self._iterable)
return self.next(self.sync_iterator)
finally:
if self._close_requested.is_set():
self._close()

def _close(self) -> None:
with self._lock:
if self._closed:
return
self._closed = True
iterator = self.sync_iterator if self.sync_iterator is not None else self._iterable
close = getattr(iterator, "close", None)
if close is not None:
close()

async def aclose(self) -> None:
self._close_requested.set()
# Cancellation cannot interrupt a sync read, so its worker closes the iterator when the read finishes.
if self._lock.acquire(blocking=False):
self._lock.release()
await sync_to_async(self._close, thread_sensitive=False)()

@staticmethod
def next(it: Iterator[T]) -> T:
Expand Down
61 changes: 61 additions & 0 deletions ee/hogai/utils/test/test_asgi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import asyncio
import threading
from collections.abc import Iterator

import pytest

from parameterized import parameterized

from ee.hogai.utils.asgi import SyncIterableToAsync


class TestSyncIterableToAsync:
@parameterized.expand([("idle", False), ("reading", True)])
async def test_closes_cancelled_stream_in_worker(self, _name: str, reading: bool) -> None:
loop = asyncio.get_running_loop()
read_started = asyncio.Event()
close_started = asyncio.Event()
closed = asyncio.Event()
release_read = threading.Event()
release_close = threading.Event()
closed_on: list[int] = []

def generate() -> Iterator[int]:
try:
yield 1
loop.call_soon_threadsafe(read_started.set)
assert release_read.wait(5)
yield 2
finally:
loop.call_soon_threadsafe(close_started.set)
assert release_close.wait(5)
closed_on.append(threading.get_ident())
loop.call_soon_threadsafe(closed.set)

stream = SyncIterableToAsync(generate())
assert await anext(stream) == 1
if reading:
pending = asyncio.create_task(anext(stream))
await asyncio.wait_for(read_started.wait(), 5)
pending.cancel()
with pytest.raises(asyncio.CancelledError):
await pending

cleanup = asyncio.create_task(stream.aclose())
try:
if reading:
await asyncio.wait_for(cleanup, 5)
assert not close_started.is_set()
release_read.set()
await asyncio.wait_for(close_started.wait(), 5)
finally:
release_read.set()
release_close.set()
await asyncio.wait_for(cleanup, 5)
await asyncio.wait_for(closed.wait(), 5)

assert len(closed_on) == 1
assert closed_on[0] != threading.get_ident()
await stream.aclose()
with pytest.raises(StopAsyncIteration):
await anext(stream)
12 changes: 11 additions & 1 deletion frontend/src/layout/navigation-3000/Navigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
import { sidePanelStateLogic } from './sidepanel/sidePanelStateLogic'
import { themeLogic } from './themeLogic'

export function Navigation({

Check warning on line 32 in frontend/src/layout/navigation-3000/Navigation.tsx

View workflow job for this annotation

GitHub Actions / Frontend formatting

lint:complexity

`Navigation` has cyclomatic complexity 26 (warn >10)
children,
sceneConfig,
}: {
Expand Down Expand Up @@ -134,7 +134,17 @@
}
>
{showMinimalNavigation && <MinimalNavigation />}
<main className={mode === 'zen' ? 'p-4' : undefined}>{children}</main>
<main
className={
mode === 'zen'
? 'p-4'
: mode === 'embedded'
? '@container/main-content min-h-screen p-4'
: undefined
}
>
{children}
</main>
</div>
)
}
Expand Down
7 changes: 6 additions & 1 deletion frontend/src/layout/navigation-3000/navigationLogic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import posthog from 'posthog-js'

import { FEATURE_FLAGS } from 'lib/constants'
import { featureFlagLogic } from 'lib/logic/featureFlagLogic'
import { isEmbeddedPageFrame } from 'lib/utils/embeddedPageFrame'
import { onboardingVariantChrome, resolveOnboardingFlowVariant } from 'scenes/onboarding/onboardingVariants'
import { organizationLogic } from 'scenes/organizationLogic'
import { sceneLogic } from 'scenes/sceneLogic'
Expand All @@ -13,7 +14,7 @@ import { Scene } from 'scenes/sceneTypes'
import type { SceneConfig } from '../../scenes/sceneTypes'
import { navigationLogic } from '../navigation/navigationLogic'

export type Navigation3000Mode = 'none' | 'minimal' | 'zen' | 'full'
export type Navigation3000Mode = 'none' | 'minimal' | 'zen' | 'embedded' | 'full'

export type ZenModeTrigger = 'shortcut' | 'account_menu' | 'help_menu' | 'exit_button' | 'url'

Expand Down Expand Up @@ -120,6 +121,10 @@ export const navigation3000Logic = kea<navigation3000LogicType>([
activeSceneId: string | null,
featureFlags: import('lib/logic/featureFlagLogic').FeatureFlagsSet
): Navigation3000Mode => {
// An embedded page frame sits inside another app page, so it shows the scene alone.
if (isEmbeddedPageFrame()) {
return 'embedded'
}
if (zenMode) {
return 'zen'
}
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/layout/today/TodaySpaceActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { urls } from 'scenes/urls'
import { ChannelDTOApi } from 'products/tasks/frontend/generated/api.schemas'

import { TodayMenuParts } from './todayMenuParts'
import { spaceNewSessionUrl, todaySpacesLogic } from './todaySpacesLogic'
import { todaySpacesLogic } from './todaySpacesLogic'

interface TodaySpaceActionsProps {
parts: TodayMenuParts
Expand All @@ -42,7 +42,7 @@ export function TodaySpaceActions({

return (
<>
<Item to={spaceNewSessionUrl(space.id)} dataAttr={attr('new-session')}>
<Item to={urls.taskSpaceNewSession(space.id)} dataAttr={attr('new-session')}>
<IconPlus />
New session
</Item>
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/layout/today/TodaySpacesSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@

const SKELETON_ROW_WIDTHS = ['w-3/5', 'w-4/5', 'w-2/5'] as const

export function TodaySpacesSidebar(): JSX.Element {

Check warning on line 34 in frontend/src/layout/today/TodaySpacesSidebar.tsx

View workflow job for this annotation

GitHub Actions / Frontend formatting

lint:complexity

`TodaySpacesSidebar` has cyclomatic complexity 17 (warn >10)
const {
visibleSpaces,
spacesLoading,
Expand Down Expand Up @@ -163,11 +163,11 @@
variant="primary"
size="lg"
className="w-full"
render={<LinkPrimitive to={urls.ai()} />}
render={<LinkPrimitive to={urls.taskNewSession()} />}
data-attr="today-spaces-new-chat"
>
<IconPlus />
New chat
New session
</Button>
<div className="mt-6 flex min-h-0 flex-1 flex-col overflow-hidden px-1" ref={layout.measureRefs.area}>
{hasPinned && (
Expand Down Expand Up @@ -220,7 +220,7 @@
loadError('Recent sessions didn’t load.', loadRecentTasks, 'today-recent-retry')
) : recentState === 'empty' ? (
<Text size="xs" variant="muted" className="px-2 py-1">
Sessions and chats you open show up here. Start one with New chat.
Sessions and chats you open show up here. Start one with New session.
</Text>
) : recentState === 'no-matches' ? (
notice(
Expand Down
31 changes: 24 additions & 7 deletions frontend/src/layout/today/todaySpacesLogic.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { MakeLogicType, actions, afterMount, connect, kea, listeners, path, reducers, selectors } from 'kea'
import { loaders } from 'kea-loaders'
import { combineUrl, router } from 'kea-router'
import { router } from 'kea-router'
import type { LocationChangedPayload } from 'kea-router/lib/types'

import { toast } from '@posthog/quill'

import { removeProjectIdIfPresent } from 'lib/utils/kea-router'
import { writeToClipboard } from 'lib/utils/writeToClipboard'
import { maxGlobalLogic } from 'scenes/max/maxGlobalLogic'
import { teamLogic } from 'scenes/teamLogic'
Expand Down Expand Up @@ -79,11 +80,10 @@ const SPACE_PRESENCE_POLL_INTERVAL_MS = 90_000

export type TodayWorkSectionId = 'pinned' | 'recent' | 'spaces'

/** The space page reads this search param once and focuses its new-session composer. */
export const SPACE_COMPOSE_PARAM = 'compose'

export function spaceNewSessionUrl(spaceId: string): string {
return combineUrl(urls.taskSpace(spaceId), { [SPACE_COMPOSE_PARAM]: 1 }).url
/** The space a path is in, like PostHog Desktop's scoped space. `/spaces/new` is in no space. */
export function spaceIdForPath(pathname: string): string | null {
const match = removeProjectIdIfPresent(pathname).match(/^\/spaces\/([^/]+)/)
return match && match[1] !== 'new' ? match[1] : null
}

/** The personal space first, then the team's general space, then starred spaces, then the rest by name. */
Expand Down Expand Up @@ -133,6 +133,7 @@ export interface todaySpacesLogicValues {
user: UserType | null // userLogic
allRecentItems: TodayWorkItem[]
collapsedSections: TodayWorkSectionId[]
lastSpaceId: string | null
pendingSpaceIds: string[]
pinnedItems: TodayWorkItem[]
pinnedTasks: TaskListItemApi[]
Expand Down Expand Up @@ -325,6 +326,9 @@ export interface todaySpacesLogicActions {
setSectionHeights: (heights: Partial<Record<TodayWorkSectionId, number>>) => {
heights: Partial<Record<TodayWorkSectionId, number>>
}
spaceVisited: (spaceId: string) => {
spaceId: string
}
starFailed: (spaceId: string) => {
spaceId: string
}
Expand Down Expand Up @@ -408,6 +412,7 @@ export const todaySpacesLogic = kea<todaySpacesLogicType>([
toggleSection: (sectionId: TodayWorkSectionId) => ({ sectionId }),
setSectionHeights: (heights: Partial<Record<TodayWorkSectionId, number>>) => ({ heights }),
resetSectionPair: (upper: TodayWorkSectionId, lower: TodayWorkSectionId) => ({ upper, lower }),
spaceVisited: (spaceId: string) => ({ spaceId }),
setRecentQuery: (query: string) => ({ query }),
setRecentSearchOpen: (open: boolean) => ({ open }),
setRecentFilters: (filters: TodayRecentFilters) => ({ filters }),
Expand Down Expand Up @@ -529,6 +534,8 @@ export const todaySpacesLogic = kea<todaySpacesLogicType>([
},
},
],
// A generic New session files here, like PostHog Desktop's scoped space. A stale id falls back to personal.
lastSpaceId: [null as string | null, { persist: true }, { spaceVisited: (_, { spaceId }) => spaceId }],
recentQuery: ['', { setRecentQuery: (_, { query }) => query, clearRecentSearchAndFilters: () => '' }],
recentSearchOpen: [
false,
Expand Down Expand Up @@ -709,7 +716,13 @@ export const todaySpacesLogic = kea<todaySpacesLogicType>([
}
}
return {
locationChanged: markOpenSessionRead,
locationChanged: ({ pathname }) => {
const spaceId = spaceIdForPath(pathname)
if (spaceId) {
actions.spaceVisited(spaceId)
}
markOpenSessionRead()
},
loadTaskActivitySuccess: markOpenSessionRead,
loadRecentTasks: () => actions.loadTaskActivity(),
markSessionRead: async ({ marker, activityIds }) => {
Expand Down Expand Up @@ -758,6 +771,10 @@ export const todaySpacesLogic = kea<todaySpacesLogicType>([
},
})),
afterMount(({ actions, cache }) => {
const spaceId = spaceIdForPath(router.values.location.pathname)
if (spaceId) {
actions.spaceVisited(spaceId)
}
actions.loadSpaces()
actions.loadPinnedTasks()
actions.loadRecentTasks()
Expand Down
1 change: 0 additions & 1 deletion frontend/src/lib/constants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -423,7 +423,6 @@ export const FEATURE_FLAGS = {
METRICS: 'metrics', // owner: #team-apm (@jonmcwest, @frankh)
METRICS_DASHBOARD_PANELS: 'metrics-dashboard-panels', // owner: #team-apm — gates the stat/gauge/bargauge/table panel picker entries
METRICS_ERROR_OVERLAYS: 'metrics-error-overlays', // owner: #team-apm — gates the error-spike overlay PoC on metrics charts
METRICS_FUNDAMENTALS: 'metrics-fundamentals', // owner: #team-apm (@jonmcwest, @frankh), gates the Fundamentals tab and the explain API behind it, which check the metrics viewer's own reductions
ML_INFERENCE_DECISIONS: 'ml-inference-decisions', // owner: #team-ai-research, gates the decisions playground; the API checks the same flag server side
NEW_TAB_PROJECT_EXPLORER: 'new-tab-project-explorer', // owner: #team-platform-ux
NEW_TEAM_CORE_EVENTS: 'new-team-core-events', // owner: @jabahamondes #team-web-analytics
Expand Down
19 changes: 19 additions & 0 deletions frontend/src/lib/utils/embeddedPageFrame.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/** The name of a frame that shows an app page inside another app page, such as a cited object in a task. */
export const EMBEDDED_PAGE_FRAME_NAME = 'posthog-embedded-page'

/**
* True when this document is an app page inside an embedded page frame, so it renders without the app's
* navigation. The frame name stays when the page navigates inside the frame. The origin check makes sure
* only the app itself can turn the mode on.
*/
export function isEmbeddedPageFrame(): boolean {
if (typeof window === 'undefined' || window.name !== EMBEDDED_PAGE_FRAME_NAME || window.parent === window) {
return false
}
try {
return window.parent.location.origin === window.location.origin
} catch {
// A cross-origin parent throws on any read of its location.
return false
}
}
11 changes: 10 additions & 1 deletion frontend/src/loadPostHogJS.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import posthog, { BeforeSendFn, BrowserMetricsConfig, PostHogConfig, SessionReco
import { FEATURE_FLAGS } from 'lib/constants'
import { isOAuthMode } from 'lib/oauth/oauthClient'
import { inStorybook, inStorybookTestRunner } from 'lib/utils/dom'
import { isEmbeddedPageFrame } from 'lib/utils/embeddedPageFrame'
import { getAppContext } from 'lib/utils/getAppContext'

import { startDetachedElementTracking } from './detachedElementTracker'
Expand Down Expand Up @@ -54,6 +55,10 @@ export function withLastSeenFeatureFlags(
return { ...bootstrap, featureFlags: { ...lastSeen.featureFlags, ...bootstrap.featureFlags } }
}

// pinned: analytics property name. Insights filter the framed pages by it.
const stampEmbeddedPageFrame: BeforeSendFn = (event) =>
event && { ...event, properties: { ...event.properties, embedded_page_frame: true } }

function readLastSeenFeatureFlags(): LastSeenFeatureFlags | null {
try {
const stored = window.localStorage.getItem(LAST_SEEN_FEATURE_FLAGS_KEY)
Expand Down Expand Up @@ -119,7 +124,11 @@ export function loadPostHogJS(options: LoadPostHogJSOptions = {}): void {
__capturePostHogExceptions: true,
},
metrics: { network: true, serviceName: 'posthog-app', ...options.metrics },
before_send: options.beforeSend,
// A page in a frame counts its own pageviews, so its events say so and analysis can filter them.
// `register` would persist the property in storage the main window shares, so it is stamped per event.
before_send: isEmbeddedPageFrame()
? [stampEmbeddedPageFrame, ...(options.beforeSend ? [options.beforeSend].flat() : [])]
: options.beforeSend,
loaded: (loadedInstance) => {
if (loadedInstance.sessionRecording) {
loadedInstance.sessionRecording._forceAllowLocalhostNetworkCapture = true
Expand Down
1 change: 1 addition & 0 deletions frontend/src/productScenes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ export const productScenes: Record<string, () => Promise<any>> = {
Subscription: () => import('../../products/subscriptions/frontend/scenes/SubscriptionScene'),
SlackTaskContext: () => import('../../products/tasks/frontend/SlackTaskContextScene'),
TaskSpaces: () => import('../../products/tasks/frontend/spaces/SpacesScene'),
TaskNewSession: () => import('../../products/tasks/frontend/spaces/NewSessionScene'),
TaskSpace: () => import('../../products/tasks/frontend/spaces/SpaceScene'),
Tracing: () => import('../../products/tracing/frontend/TracingScene'),
TracingOperation: () => import('../../products/tracing/frontend/TracingOperationScene'),
Expand Down
Loading
Loading