0
0
mirror of https://github.com/PostHog/posthog.git synced 2024-11-24 18:07:17 +01:00

Added embeddable endpoint

This commit is contained in:
Ben White 2024-11-20 18:42:19 -08:00
parent ffb5133dbb
commit 58fa69d7f1
5 changed files with 98 additions and 8 deletions

View File

@ -19,6 +19,7 @@ import { Logo } from '~/toolbar/assets/Logo'
import { DashboardPlacement } from '~/types' import { DashboardPlacement } from '~/types'
import { exporterViewLogic } from './exporterViewLogic' import { exporterViewLogic } from './exporterViewLogic'
import { EmbeddedReplayHelper } from './replay/EmbeddedReplayHelper'
export function Exporter(props: ExportedData): JSX.Element { export function Exporter(props: ExportedData): JSX.Element {
// NOTE: Mounting the logic is important as it is used by sub-logics // NOTE: Mounting the logic is important as it is used by sub-logics
@ -83,6 +84,8 @@ export function Exporter(props: ExportedData): JSX.Element {
placement={type === ExportType.Image ? DashboardPlacement.Export : DashboardPlacement.Public} placement={type === ExportType.Image ? DashboardPlacement.Export : DashboardPlacement.Public}
/> />
) : recording ? ( ) : recording ? (
<>
<EmbeddedReplayHelper />
<SessionRecordingPlayer <SessionRecordingPlayer
playerKey="exporter" playerKey="exporter"
sessionRecordingId={recording.id} sessionRecordingId={recording.id}
@ -90,6 +93,7 @@ export function Exporter(props: ExportedData): JSX.Element {
autoPlay={false} autoPlay={false}
noInspector={!showInspector} noInspector={!showInspector}
/> />
</>
) : ( ) : (
<h1 className="text-center p-4">Something went wrong...</h1> <h1 className="text-center p-4">Something went wrong...</h1>
)} )}

View File

@ -0,0 +1,9 @@
import { useMountedLogic } from 'kea'
import { embeddedReplayLogic } from './embeddedReplayLogic'
export function EmbeddedReplayHelper(): JSX.Element {
// NOTE: This is a helper component to avoid circular imports from the logic
useMountedLogic(embeddedReplayLogic)
return <></>
}

View File

@ -0,0 +1,67 @@
import { actions, afterMount, connect, kea, listeners, path, selectors } from 'kea'
import { dayjs } from 'lib/dayjs'
import { waitForDataLogic } from 'scenes/session-recordings/file-playback/sessionRecordingFilePlaybackSceneLogic'
import { deduplicateSnapshots, parseEncodedSnapshots } from 'scenes/session-recordings/player/sessionRecordingDataLogic'
import { exporterViewLogic } from '../exporterViewLogic'
import { ExportedData } from '../types'
import type { embeddedReplayLogicType } from './embeddedReplayLogicType'
export const embeddedReplayLogic = kea<embeddedReplayLogicType>([
path(() => ['scenes', 'exporter', 'embeddedReplayLogic']),
connect({
values: [exporterViewLogic, ['exportedData']],
}),
actions({
loadReplayFromData: (data: any[]) => ({ data }),
}),
selectors(() => ({
isEmbeddedRecording: [
(s) => [s.exportedData],
(exportedData: ExportedData): boolean => !!(exportedData.recording && exportedData.recording.id === ''),
],
})),
listeners(() => ({
loadReplayFromData: async ({ data }) => {
const dataLogic = await waitForDataLogic('exporter')
if (!dataLogic || !data) {
return
}
// Add a window ID to the snapshots so that we can identify them
data.forEach((snapshot: any) => {
snapshot.window_id = 'window-1'
})
const snapshots = deduplicateSnapshots(await parseEncodedSnapshots(data, 'embedded'))
// Simulate a loaded source and sources so that nothing extra gets loaded
dataLogic.actions.loadSnapshotsForSourceSuccess({
snapshots: snapshots,
untransformed_snapshots: snapshots,
source: { source: 'file' },
})
dataLogic.actions.loadSnapshotSourcesSuccess([{ source: 'file' }])
dataLogic.actions.loadRecordingMetaSuccess({
id: 'embedded',
viewed: false,
recording_duration: snapshots[snapshots.length - 1].timestamp - snapshots[0].timestamp,
person: undefined,
start_time: dayjs(snapshots[0].timestamp).toISOString(),
end_time: dayjs(snapshots[snapshots.length - 1].timestamp).toISOString(),
snapshot_source: 'unknown', // TODO: we should be able to detect this from the file
})
},
})),
afterMount(({ values, actions }) => {
if (values.isEmbeddedRecording) {
window.addEventListener('message', (event) => {
if (event.data.type === 'session-replay-data') {
actions.loadReplayFromData(event.data.snapshots)
return
}
})
}
}),
])

View File

@ -58,7 +58,7 @@ export const parseExportedSessionRecording = (fileData: string): ExportedSession
* in practice, it will only wait for 1-2 retries * in practice, it will only wait for 1-2 retries
* but a timeout is provided to avoid waiting forever when something breaks * but a timeout is provided to avoid waiting forever when something breaks
*/ */
const waitForDataLogic = async (playerKey: string): Promise<BuiltLogic<sessionRecordingDataLogicType>> => { export const waitForDataLogic = async (playerKey: string): Promise<BuiltLogic<sessionRecordingDataLogicType>> => {
const maxRetries = 20 // 2 seconds / 100 ms per retry const maxRetries = 20 // 2 seconds / 100 ms per retry
let retries = 0 let retries = 0
let dataLogic = null let dataLogic = null

View File

@ -235,6 +235,16 @@ class SharingViewerPageViewSet(mixins.RetrieveModelMixin, viewsets.GenericViewSe
@xframe_options_exempt @xframe_options_exempt
def retrieve(self, request: Request, *args: Any, **kwargs: Any) -> Any: def retrieve(self, request: Request, *args: Any, **kwargs: Any) -> Any:
if self.kwargs.get("access_token") == "embedded-player":
# NOTE: This is a shortcut for the embedded player rendered on posthog.com
return render_template(
"exporter.html",
request=request,
context={
"exported_data": {"recording": {"id": ""}},
},
)
resource = self.get_object() resource = self.get_object()
if not resource: if not resource: