mirror of
https://github.com/PostHog/posthog.git
synced 2024-11-21 13:39:22 +01:00
Added embeddable endpoint
This commit is contained in:
parent
ffb5133dbb
commit
58fa69d7f1
@ -19,6 +19,7 @@ import { Logo } from '~/toolbar/assets/Logo'
|
||||
import { DashboardPlacement } from '~/types'
|
||||
|
||||
import { exporterViewLogic } from './exporterViewLogic'
|
||||
import { EmbeddedReplayHelper } from './replay/EmbeddedReplayHelper'
|
||||
|
||||
export function Exporter(props: ExportedData): JSX.Element {
|
||||
// NOTE: Mounting the logic is important as it is used by sub-logics
|
||||
@ -83,13 +84,16 @@ export function Exporter(props: ExportedData): JSX.Element {
|
||||
placement={type === ExportType.Image ? DashboardPlacement.Export : DashboardPlacement.Public}
|
||||
/>
|
||||
) : recording ? (
|
||||
<SessionRecordingPlayer
|
||||
playerKey="exporter"
|
||||
sessionRecordingId={recording.id}
|
||||
mode={SessionRecordingPlayerMode.Sharing}
|
||||
autoPlay={false}
|
||||
noInspector={!showInspector}
|
||||
/>
|
||||
<>
|
||||
<EmbeddedReplayHelper />
|
||||
<SessionRecordingPlayer
|
||||
playerKey="exporter"
|
||||
sessionRecordingId={recording.id}
|
||||
mode={SessionRecordingPlayerMode.Sharing}
|
||||
autoPlay={false}
|
||||
noInspector={!showInspector}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<h1 className="text-center p-4">Something went wrong...</h1>
|
||||
)}
|
||||
|
9
frontend/src/exporter/replay/EmbeddedReplayHelper.tsx
Normal file
9
frontend/src/exporter/replay/EmbeddedReplayHelper.tsx
Normal 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 <></>
|
||||
}
|
67
frontend/src/exporter/replay/embeddedReplayLogic.ts
Normal file
67
frontend/src/exporter/replay/embeddedReplayLogic.ts
Normal 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
|
||||
}
|
||||
})
|
||||
}
|
||||
}),
|
||||
])
|
@ -58,7 +58,7 @@ export const parseExportedSessionRecording = (fileData: string): ExportedSession
|
||||
* in practice, it will only wait for 1-2 retries
|
||||
* 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
|
||||
let retries = 0
|
||||
let dataLogic = null
|
||||
|
@ -235,6 +235,16 @@ class SharingViewerPageViewSet(mixins.RetrieveModelMixin, viewsets.GenericViewSe
|
||||
|
||||
@xframe_options_exempt
|
||||
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()
|
||||
|
||||
if not resource:
|
||||
|
Loading…
Reference in New Issue
Block a user