diff --git a/frontend/src/layout/navigation-3000/sidepanel/components/SidePanelPaneHeader.tsx b/frontend/src/layout/navigation-3000/sidepanel/components/SidePanelPaneHeader.tsx index 1346c218f55..03f6a6e8239 100644 --- a/frontend/src/layout/navigation-3000/sidepanel/components/SidePanelPaneHeader.tsx +++ b/frontend/src/layout/navigation-3000/sidepanel/components/SidePanelPaneHeader.tsx @@ -1,5 +1,5 @@ import { IconX } from '@posthog/icons' -import { LemonButton, Tooltip } from '@posthog/lemon-ui' +import { LemonButton } from '@posthog/lemon-ui' import clsx from 'clsx' import { useActions, useValues } from 'kea' @@ -31,9 +31,13 @@ export function SidePanelPaneHeader({ children, title }: SidePanelPaneHeaderProp ) : null} {children} - - } onClick={() => closeSidePanel()} /> - + } + onClick={() => closeSidePanel()} + tooltip={modalMode ? 'Close' : 'Close this side panel'} + tooltipPlacement={modalMode ? 'top' : 'bottom-end'} + /> ) } diff --git a/frontend/src/layout/navigation/TopBar/AccountPopover.tsx b/frontend/src/layout/navigation/TopBar/AccountPopover.tsx index 0eeada32885..b7cdf594047 100644 --- a/frontend/src/layout/navigation/TopBar/AccountPopover.tsx +++ b/frontend/src/layout/navigation/TopBar/AccountPopover.tsx @@ -17,7 +17,6 @@ import clsx from 'clsx' import { useActions, useValues } from 'kea' import { LemonButton } from 'lib/lemon-ui/LemonButton' import { ProfilePicture } from 'lib/lemon-ui/ProfilePicture' -import { Tooltip } from 'lib/lemon-ui/Tooltip' import { UploadedLogo } from 'lib/lemon-ui/UploadedLogo' import { eventUsageLogic } from 'lib/utils/eventUsageLogic' import { inviteLogic } from 'scenes/settings/organization/inviteLogic' @@ -85,27 +84,27 @@ function CurrentOrganization({ organization }: { organization: OrganizationBasic const { closeAccountPopover } = useActions(navigationLogic) return ( - - - } - sideIcon={} - fullWidth - to={urls.settings('organization')} - onClick={closeAccountPopover} - > -
- {organization.name} - -
-
-
+ + } + sideIcon={} + fullWidth + to={urls.settings('organization')} + onClick={closeAccountPopover} + tooltip="Organization settings" + tooltipPlacement="left" + > +
+ {organization.name} + +
+
) } diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index 51edbf741f5..a5c1b48eedf 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -5,7 +5,7 @@ import { ActivityLogItem } from 'lib/components/ActivityLog/humanizeActivity' import { apiStatusLogic } from 'lib/logic/apiStatusLogic' import { objectClean, toParams } from 'lib/utils' import posthog from 'posthog-js' -import { ErrorTrackingSymbolSet } from 'scenes/error-tracking/errorTrackingConfigurationSceneLogic' +import { ErrorTrackingSymbolSet } from 'scenes/error-tracking/errorTrackingSymbolSetLogic' import { stringifiedFingerprint } from 'scenes/error-tracking/utils' import { RecordingComment } from 'scenes/session-recordings/player/inspector/playerInspectorLogic' import { SavedSessionRecordingPlaylistsResult } from 'scenes/session-recordings/saved-playlists/savedSessionRecordingPlaylistsLogic' @@ -726,10 +726,6 @@ class ApiRequest { return this.errorTrackingSymbolSets().addPathComponent('missing') } - public errorTrackingStackFrames(ids: string[]): ApiRequest { - return this.errorTracking().addPathComponent('stack_frames').withQueryString({ ids }) - } - // # Warehouse public dataWarehouseTables(teamId?: TeamType['id']): ApiRequest { return this.projectsDetail(teamId).addPathComponent('warehouse_tables') @@ -1872,10 +1868,6 @@ const api = { async missingSymbolSets(): Promise { return await new ApiRequest().errorTrackingMissingSymbolSets().get() }, - - async fetchStackFrames(ids: string[]): Promise<{ content: string }> { - return await new ApiRequest().errorTrackingStackFrames(ids).get() - }, }, recordings: { diff --git a/frontend/src/lib/components/Errors/ErrorDisplay.tsx b/frontend/src/lib/components/Errors/ErrorDisplay.tsx index 604b80fcf68..fcbc48f0bb4 100644 --- a/frontend/src/lib/components/Errors/ErrorDisplay.tsx +++ b/frontend/src/lib/components/Errors/ErrorDisplay.tsx @@ -1,13 +1,15 @@ import './ErrorDisplay.scss' -import { IconFlag } from '@posthog/icons' -import { LemonCollapse } from '@posthog/lemon-ui' +import { IconFlag, IconUpload } from '@posthog/icons' +import { LemonButton, LemonCollapse } from '@posthog/lemon-ui' +import { useActions } from 'kea' import { TitledSnack } from 'lib/components/TitledSnack' import { LemonDivider } from 'lib/lemon-ui/LemonDivider' import { LemonSwitch } from 'lib/lemon-ui/LemonSwitch' import { LemonTag } from 'lib/lemon-ui/LemonTag/LemonTag' import { Link } from 'lib/lemon-ui/Link' import { useState } from 'react' +import { errorTrackingSymbolSetLogic } from 'scenes/error-tracking/errorTrackingSymbolSetLogic' import { EventType } from '~/types' @@ -29,29 +31,53 @@ interface Exception { value: string } -function StackTrace({ frames, showAllFrames }: { frames: StackFrame[]; showAllFrames: boolean }): JSX.Element | null { +function StackTrace({ + frames, + showAllFrames, + resolved, +}: { + frames: StackFrame[] + showAllFrames: boolean + resolved: boolean +}): JSX.Element | null { + const { setUploadSymbolSetReference } = useActions(errorTrackingSymbolSetLogic) const displayFrames = showAllFrames ? frames : frames.filter((f) => f.in_app) const panels = displayFrames.map(({ filename, lineno, colno, function: functionName }, index) => { return { key: index, header: ( -
- {filename} - {functionName ? ( -
- in - {functionName} -
- ) : null} - {lineno && colno ? ( -
- at line +
+
+ {filename} + {functionName ? ( +
+ in + {functionName} +
+ ) : null} + {lineno && colno ? ( +
+ at line + + {lineno}:{colno} + +
+ ) : null} +
+ {!resolved && ( +
- {lineno}:{colno} + Unresolved + } + size="xsmall" + onClick={() => setUploadSymbolSetReference(filename)} + tooltip="Upload source map" + />
- ) : null} + )}
), content: null, @@ -78,7 +104,7 @@ function ChainedStackTraces({ exceptionList }: { exceptionList: Exception[] }): />
{exceptionList.map(({ stacktrace, value }, index) => { - const { frames } = stacktrace || {} + const { frames, type } = stacktrace || {} if (!showAllFrames && !frames?.some((frame) => frame.in_app)) { // if we're not showing all frames and there are no in_app frames, skip this exception return null @@ -87,7 +113,11 @@ function ChainedStackTraces({ exceptionList }: { exceptionList: Exception[] }): return (

{value}

- +
) })} diff --git a/frontend/src/lib/components/Errors/stackFrameLogic.tsx b/frontend/src/lib/components/Errors/stackFrameLogic.tsx index 3852055d12b..d8b0874258a 100644 --- a/frontend/src/lib/components/Errors/stackFrameLogic.tsx +++ b/frontend/src/lib/components/Errors/stackFrameLogic.tsx @@ -1,9 +1,3 @@ -import { kea, path } from 'kea' -import { loaders } from 'kea-loaders' -import api from 'lib/api' - -import type { stackFrameLogicType } from './stackFrameLogicType' - export interface StackFrame { filename: string lineno: number @@ -11,20 +5,3 @@ export interface StackFrame { function: string in_app?: boolean } - -export const stackFrameLogic = kea([ - path(['components', 'Errors', 'stackFrameLogic']), - loaders(({ values }) => ({ - stackFrames: [ - {} as Record, - { - loadFrames: async ({ frameIds }: { frameIds: string[] }) => { - const loadedFrameIds = Object.keys(values.stackFrames) - const ids = frameIds.filter((id) => loadedFrameIds.includes(id)) - await api.errorTracking.fetchStackFrames(ids) - return {} - }, - }, - ], - })), -]) diff --git a/frontend/src/scenes/error-tracking/ErrorTrackingConfigurationScene.tsx b/frontend/src/scenes/error-tracking/ErrorTrackingConfigurationScene.tsx index a9ff35ce7bd..57909bb3272 100644 --- a/frontend/src/scenes/error-tracking/ErrorTrackingConfigurationScene.tsx +++ b/frontend/src/scenes/error-tracking/ErrorTrackingConfigurationScene.tsx @@ -1,25 +1,30 @@ import { IconUpload } from '@posthog/icons' -import { LemonButton, LemonFileInput, LemonModal, LemonTable } from '@posthog/lemon-ui' +import { LemonButton, LemonTable } from '@posthog/lemon-ui' import { useActions, useValues } from 'kea' -import { Form } from 'kea-forms' -import { IconUploadFile } from 'lib/lemon-ui/icons' -import { LemonField } from 'lib/lemon-ui/LemonField' import { SceneExport } from 'scenes/sceneTypes' -import { errorTrackingConfigurationSceneLogic } from './errorTrackingConfigurationSceneLogic' +import { errorTrackingSymbolSetLogic } from './errorTrackingSymbolSetLogic' +import { SymbolSetUploadModal } from './SymbolSetUploadModal' export const scene: SceneExport = { component: ErrorTrackingConfigurationScene, - logic: errorTrackingConfigurationSceneLogic, + logic: errorTrackingSymbolSetLogic, } export function ErrorTrackingConfigurationScene(): JSX.Element { - const { missingSymbolSets, missingSymbolSetsLoading } = useValues(errorTrackingConfigurationSceneLogic) - const { setUploadSymbolSetReference } = useActions(errorTrackingConfigurationSceneLogic) + const { setUploadSymbolSetReference } = useActions(errorTrackingSymbolSetLogic) + const { missingSymbolSets, missingSymbolSetsLoading } = useValues(errorTrackingSymbolSetLogic) return (
+

Missing symbol sets

+

+ Source maps are required to demangle any minified code in your exception stack traces. PostHog + automatically retrieves source maps where possible. Cases where it was not possible are listed below. + Source maps can be uploaded retroactively but changes will only apply to all future exceptions ingested. +

- setUploadSymbolSetReference(null)} /> +
) } - -const SymbolSetUploadModal = ({ onClose }: { onClose: () => void }): JSX.Element => { - const { uploadSymbolSetReference, isUploadSymbolSetSubmitting, uploadSymbolSet } = useValues( - errorTrackingConfigurationSceneLogic - ) - - return ( - -
- -

Upload source map

-
- - - - - Add source map - -
- Drag and drop your local source map here or click to open the file browser. -
-
- } - /> - - - - - Cancel - - - Upload - - - - - ) -} diff --git a/frontend/src/scenes/error-tracking/ErrorTrackingGroupScene.tsx b/frontend/src/scenes/error-tracking/ErrorTrackingGroupScene.tsx index ae12520249a..3270a14d564 100644 --- a/frontend/src/scenes/error-tracking/ErrorTrackingGroupScene.tsx +++ b/frontend/src/scenes/error-tracking/ErrorTrackingGroupScene.tsx @@ -13,6 +13,7 @@ import { AssigneeSelect } from './AssigneeSelect' import ErrorTrackingFilters from './ErrorTrackingFilters' import { errorTrackingGroupSceneLogic } from './errorTrackingGroupSceneLogic' import { OverviewTab } from './groups/OverviewTab' +import { SymbolSetUploadModal } from './SymbolSetUploadModal' export const scene: SceneExport = { component: ErrorTrackingGroupScene, @@ -82,6 +83,7 @@ export function ErrorTrackingGroupScene(): JSX.Element { + ) } diff --git a/frontend/src/scenes/error-tracking/SymbolSetUploadModal.tsx b/frontend/src/scenes/error-tracking/SymbolSetUploadModal.tsx new file mode 100644 index 00000000000..e0ce858f7c1 --- /dev/null +++ b/frontend/src/scenes/error-tracking/SymbolSetUploadModal.tsx @@ -0,0 +1,59 @@ +import { LemonButton, LemonFileInput, LemonModal } from '@posthog/lemon-ui' +import { useActions, useValues } from 'kea' +import { Form } from 'kea-forms' +import { IconUploadFile } from 'lib/lemon-ui/icons' +import { LemonField } from 'lib/lemon-ui/LemonField' + +import { errorTrackingSymbolSetLogic } from './errorTrackingSymbolSetLogic' + +export const SymbolSetUploadModal = (): JSX.Element => { + const { setUploadSymbolSetReference } = useActions(errorTrackingSymbolSetLogic) + const { uploadSymbolSetReference, isUploadSymbolSetSubmitting, uploadSymbolSet } = + useValues(errorTrackingSymbolSetLogic) + + const onClose = (): void => { + setUploadSymbolSetReference(null) + } + + return ( + +
+ +

Upload source map

+
+ + + + + Add source map + +
+ Drag and drop your local source map here or click to open the file browser. +
+ + } + /> +
+
+ + + Cancel + + + Upload + + +
+
+ ) +} diff --git a/frontend/src/scenes/error-tracking/errorTrackingConfigurationSceneLogic.tsx b/frontend/src/scenes/error-tracking/errorTrackingSymbolSetLogic.tsx similarity index 88% rename from frontend/src/scenes/error-tracking/errorTrackingConfigurationSceneLogic.tsx rename to frontend/src/scenes/error-tracking/errorTrackingSymbolSetLogic.tsx index 25c6003b5d9..3a507800ac0 100644 --- a/frontend/src/scenes/error-tracking/errorTrackingConfigurationSceneLogic.tsx +++ b/frontend/src/scenes/error-tracking/errorTrackingSymbolSetLogic.tsx @@ -8,7 +8,7 @@ import { urls } from 'scenes/urls' import { Breadcrumb } from '~/types' -import type { errorTrackingConfigurationSceneLogicType } from './errorTrackingConfigurationSceneLogicType' +import type { errorTrackingSymbolSetLogicType } from './errorTrackingSymbolSetLogicType' export enum ErrorGroupTab { Overview = 'overview', @@ -19,8 +19,8 @@ export interface ErrorTrackingSymbolSet { ref: string } -export const errorTrackingConfigurationSceneLogic = kea([ - path((key) => ['scenes', 'error-tracking', 'errorTrackingConfigurationSceneLogic', key]), +export const errorTrackingSymbolSetLogic = kea([ + path(['scenes', 'error-tracking', 'errorTrackingSymbolSetLogic']), actions({ setUploadSymbolSetReference: (ref: string | null) => ({ ref }), diff --git a/posthog/api/error_tracking.py b/posthog/api/error_tracking.py index 70321e4ea22..beaca924ed4 100644 --- a/posthog/api/error_tracking.py +++ b/posthog/api/error_tracking.py @@ -63,6 +63,7 @@ class ErrorTrackingSymbolSetViewSet(TeamAndOrgViewSetMixin, mixins.UpdateModelMi storage_ptr = upload_symbol_set(request.FILES["source_map"], self.team_id) symbol_set.storage_ptr = storage_ptr symbol_set.save() + # TODO: cascade delete the associated frame resolutions return Response({"ok": True}, status=status.HTTP_204_NO_CONTENT) @extend_schema(exclude=True)