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 { 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,13 +84,16 @@ 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 ? (
|
||||||
<SessionRecordingPlayer
|
<>
|
||||||
playerKey="exporter"
|
<EmbeddedReplayHelper />
|
||||||
sessionRecordingId={recording.id}
|
<SessionRecordingPlayer
|
||||||
mode={SessionRecordingPlayerMode.Sharing}
|
playerKey="exporter"
|
||||||
autoPlay={false}
|
sessionRecordingId={recording.id}
|
||||||
noInspector={!showInspector}
|
mode={SessionRecordingPlayerMode.Sharing}
|
||||||
/>
|
autoPlay={false}
|
||||||
|
noInspector={!showInspector}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
) : (
|
) : (
|
||||||
<h1 className="text-center p-4">Something went wrong...</h1>
|
<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
|
* 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
|
||||||
|
@ -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:
|
||||||
|
Loading…
Reference in New Issue
Block a user