chore: cleanup properties tab (#34570)
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 31 KiB |
@@ -83,7 +83,7 @@ export type FingerprintRecordPart = FingerprintManual | FingerprintFrame | Finge
|
||||
|
||||
export interface ExceptionAttributes {
|
||||
ingestionErrors?: string[]
|
||||
runtime: ErrorTrackingRuntime
|
||||
runtime?: ErrorTrackingRuntime
|
||||
type?: string
|
||||
value?: string
|
||||
synthetic?: boolean
|
||||
@@ -96,7 +96,7 @@ export interface ExceptionAttributes {
|
||||
sentryUrl?: string
|
||||
level?: string
|
||||
url?: string
|
||||
handled: boolean
|
||||
handled?: boolean
|
||||
}
|
||||
|
||||
export type SymbolSetStatus = 'valid' | 'invalid'
|
||||
|
||||
@@ -17,7 +17,6 @@ import { GenericSelect } from './components/GenericSelect'
|
||||
import { IssueStatus, StatusIndicator } from './components/Indicator'
|
||||
import { issueActionsLogic } from './components/IssueActions/issueActionsLogic'
|
||||
import { errorTrackingIssueSceneLogic } from './errorTrackingIssueSceneLogic'
|
||||
import { useErrorTagRenderer } from './hooks/use-error-tag-renderer'
|
||||
import { Metadata } from './issue/Metadata'
|
||||
import { ISSUE_STATUS_OPTIONS } from './utils'
|
||||
import { sidePanelLogic } from '~/layout/navigation-3000/sidepanel/sidePanelLogic'
|
||||
@@ -47,7 +46,7 @@ export function ErrorTrackingIssueScene(): JSX.Element {
|
||||
useValues(errorTrackingIssueSceneLogic)
|
||||
const { loadIssue } = useActions(errorTrackingIssueSceneLogic)
|
||||
const { updateIssueAssignee, updateIssueStatus } = useActions(issueActionsLogic)
|
||||
const tagRenderer = useErrorTagRenderer()
|
||||
|
||||
const hasDiscussions = useFeatureFlag('DISCUSSIONS')
|
||||
const { openSidePanel } = useActions(sidePanelLogic)
|
||||
|
||||
@@ -105,7 +104,6 @@ export function ErrorTrackingIssueScene(): JSX.Element {
|
||||
issueLoading={issueLoading}
|
||||
event={selectedEvent ?? undefined}
|
||||
eventLoading={firstSeenEventLoading}
|
||||
label={tagRenderer(selectedEvent)}
|
||||
/>
|
||||
<ErrorFilters.Root>
|
||||
<ErrorFilters.DateRange />
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { LemonCard } from '@posthog/lemon-ui'
|
||||
import { Meta } from '@storybook/react'
|
||||
import { getAdditionalProperties, getExceptionAttributes } from 'lib/components/Errors/utils'
|
||||
|
||||
import { TEST_EVENTS, TestEventName } from '../__mocks__/events'
|
||||
import { ContextDisplay, ContextDisplayProps } from './ContextDisplay'
|
||||
import { getAdditionalProperties, getExceptionAttributes } from 'lib/components/Errors/utils'
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'ErrorTracking/ContextDisplay',
|
||||
@@ -11,15 +11,6 @@ const meta: Meta = {
|
||||
layout: 'centered',
|
||||
viewMode: 'story',
|
||||
},
|
||||
decorators: [
|
||||
(Story: React.FC): JSX.Element => {
|
||||
return (
|
||||
<LemonCard hoverEffect={false} className="p-2 px-3 w-[900px]">
|
||||
<Story />
|
||||
</LemonCard>
|
||||
)
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export default meta
|
||||
@@ -27,7 +18,7 @@ export default meta
|
||||
///////////////////// Context Display
|
||||
|
||||
export function ContextDisplayEmpty(): JSX.Element {
|
||||
return <ContextDisplay loading={false} />
|
||||
return <ContextDisplay loading={false} exceptionAttributes={{}} additionalProperties={{}} />
|
||||
}
|
||||
|
||||
export function ContextDisplayWithStacktrace(): JSX.Element {
|
||||
@@ -36,16 +27,12 @@ export function ContextDisplayWithStacktrace(): JSX.Element {
|
||||
|
||||
//////////////////// Utils
|
||||
|
||||
function getProps(event_name: TestEventName, overrideProps: Record<string, unknown> = {}): ContextDisplayProps {
|
||||
const properties = event_name ? TEST_EVENTS[event_name].properties : null
|
||||
const attributes = properties ? getExceptionAttributes(properties) : null
|
||||
const additionalProperties = properties ? getAdditionalProperties(properties, true) : {}
|
||||
return {
|
||||
loading: false,
|
||||
attributes,
|
||||
additionalProperties,
|
||||
...overrideProps,
|
||||
} as ContextDisplayProps
|
||||
function getProps(event_name: TestEventName): ContextDisplayProps {
|
||||
const properties = event_name ? TEST_EVENTS[event_name].properties : {}
|
||||
const exceptionAttributes = properties ? getExceptionAttributes(properties) : null
|
||||
const additionalProperties = getAdditionalProperties(properties, true)
|
||||
|
||||
return { loading: false, exceptionAttributes, additionalProperties }
|
||||
}
|
||||
|
||||
function ContextWrapperAllEvents({ children }: { children: (props: ContextDisplayProps) => JSX.Element }): JSX.Element {
|
||||
@@ -55,9 +42,9 @@ function ContextWrapperAllEvents({ children }: { children: (props: ContextDispla
|
||||
{eventNames.map((name: TestEventName) => {
|
||||
const props = getProps(name)
|
||||
return (
|
||||
<div className="px-3 py-2" key={name}>
|
||||
<LemonCard hoverEffect={false} className="p-0 w-[900px]">
|
||||
{children(props)}
|
||||
</div>
|
||||
</LemonCard>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
@@ -1,120 +1,99 @@
|
||||
import { IconCopy } from '@posthog/icons'
|
||||
import { LemonButton, Spinner } from '@posthog/lemon-ui'
|
||||
import { LemonButton, LemonTable, Spinner } from '@posthog/lemon-ui'
|
||||
import { ExceptionAttributes } from 'lib/components/Errors/types'
|
||||
import { concatValues } from 'lib/components/Errors/utils'
|
||||
import useIsHovering from 'lib/hooks/useIsHovering'
|
||||
import { identifierToHuman, isObject } from 'lib/utils'
|
||||
import { identifierToHuman } from 'lib/utils'
|
||||
import { copyToClipboard } from 'lib/utils/copyToClipboard'
|
||||
import { cn } from 'lib/utils/css-classes'
|
||||
import { useRef } from 'react'
|
||||
import { match } from 'ts-pattern'
|
||||
|
||||
import { cancelEvent } from '../utils'
|
||||
|
||||
export interface ContextDisplayProps {
|
||||
className?: string
|
||||
attributes?: ExceptionAttributes
|
||||
additionalProperties?: Record<string, unknown>
|
||||
export type ContextDisplayProps = {
|
||||
loading: boolean
|
||||
exceptionAttributes: ExceptionAttributes | null
|
||||
additionalProperties: Record<string, unknown>
|
||||
}
|
||||
|
||||
export function ContextDisplay({
|
||||
className,
|
||||
attributes,
|
||||
additionalProperties = {},
|
||||
loading,
|
||||
exceptionAttributes,
|
||||
additionalProperties,
|
||||
}: ContextDisplayProps): JSX.Element {
|
||||
const additionalEntries = Object.entries(additionalProperties).map(
|
||||
([key, value]) => [identifierToHuman(key, 'title'), value] as [string, unknown]
|
||||
)
|
||||
const exceptionEntries: [string, unknown][] = exceptionAttributes
|
||||
? [
|
||||
['Level', exceptionAttributes.level],
|
||||
['Synthetic', exceptionAttributes.synthetic],
|
||||
['Library', concatValues(exceptionAttributes, 'lib', 'libVersion')],
|
||||
['Handled', exceptionAttributes.handled],
|
||||
['Browser', concatValues(exceptionAttributes, 'browser', 'browserVersion')],
|
||||
['OS', concatValues(exceptionAttributes, 'os', 'osVersion')],
|
||||
['URL', exceptionAttributes.url],
|
||||
]
|
||||
: []
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<>
|
||||
{match(loading)
|
||||
.with(true, () => (
|
||||
<div className="flex justify-center w-full h-32 items-center">
|
||||
<Spinner />
|
||||
</div>
|
||||
))
|
||||
.with(false, () => {
|
||||
const additionalEntries = Object.entries(additionalProperties).map(
|
||||
([key, value]) => [identifierToHuman(key, 'title'), value] as [string, unknown]
|
||||
)
|
||||
const exceptionEntries =
|
||||
attributes &&
|
||||
([
|
||||
['Level', attributes.level],
|
||||
['Synthetic', attributes.synthetic],
|
||||
['Library', concatValues(attributes, 'lib', 'libVersion')],
|
||||
['Handled', attributes.handled],
|
||||
['Browser', concatValues(attributes, 'browser', 'browserVersion')],
|
||||
['OS', concatValues(attributes, 'os', 'osVersion')],
|
||||
['URL', attributes.url],
|
||||
] as [string, unknown][])
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<ContextTable entries={exceptionEntries || []} />
|
||||
{additionalEntries.length > 0 && <ContextTable entries={additionalEntries} />}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
.with(false, () => <ContextTable entries={[...exceptionEntries, ...additionalEntries]} />)
|
||||
.exhaustive()}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
type ContextRowProps = {
|
||||
label: string
|
||||
value: string
|
||||
}
|
||||
type ContextTableProps = { entries: [string, unknown][] }
|
||||
|
||||
function ContextTable({ entries }: { entries: [string, unknown][] }): JSX.Element {
|
||||
function ContextTable({ entries }: ContextTableProps): JSX.Element {
|
||||
return (
|
||||
<table
|
||||
className="border-spacing-0 border-separate rounded w-full border overflow-hidden cursor-default"
|
||||
onClick={cancelEvent}
|
||||
>
|
||||
<tbody className="w-full">
|
||||
{entries
|
||||
.filter(([, value]) => value !== undefined)
|
||||
.map(([key, value]) => (
|
||||
<ContextRow
|
||||
key={key}
|
||||
label={key}
|
||||
value={isObject(value) ? JSON.stringify(value) : String(value)}
|
||||
/>
|
||||
))}
|
||||
{entries.length == 0 && <tr className="w-full text-center">No data available</tr>}
|
||||
</tbody>
|
||||
</table>
|
||||
)
|
||||
}
|
||||
|
||||
function ContextRow({ label, value }: ContextRowProps): JSX.Element {
|
||||
const valueRef = useRef<HTMLTableCellElement>(null)
|
||||
const isHovering = useIsHovering(valueRef)
|
||||
|
||||
return (
|
||||
<tr className="even:bg-fill-tertiary w-full group">
|
||||
<th className="border-r-1 font-semibold text-xs p-1 w-1/3 text-left">{label}</th>
|
||||
<td ref={valueRef} className="w-full truncate p-1 text-xs max-w-0 relative" title={value}>
|
||||
{value}
|
||||
<div
|
||||
className={cn(
|
||||
'absolute right-0 top-[50%] translate-y-[-50%] group-even:bg-fill-tertiary group-odd:bg-fill-primary drop-shadow-sm',
|
||||
isHovering ? 'opacity-100' : 'opacity-0'
|
||||
)}
|
||||
>
|
||||
<LemonButton
|
||||
size="xsmall"
|
||||
className={cn('p-0 rounded-none')}
|
||||
tooltip="Copy"
|
||||
onClick={() => {
|
||||
copyToClipboard(value).catch((error) => {
|
||||
console.error('Failed to copy to clipboard:', error)
|
||||
})
|
||||
}}
|
||||
>
|
||||
<IconCopy />
|
||||
</LemonButton>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<LemonTable
|
||||
embedded
|
||||
size="small"
|
||||
dataSource={entries
|
||||
.filter(([, value]) => value !== undefined)
|
||||
.map(([key, value]) => ({
|
||||
key,
|
||||
value: String(value),
|
||||
}))}
|
||||
showHeader={false}
|
||||
columns={[
|
||||
{
|
||||
title: 'Key',
|
||||
key: 'key',
|
||||
dataIndex: 'key',
|
||||
width: 0,
|
||||
className: 'font-medium bg-inherit',
|
||||
render: (dataValue, record) => (
|
||||
<div className="flex gap-x-2 justify-between items-center">
|
||||
<div>{dataValue}</div>
|
||||
<LemonButton
|
||||
size="xsmall"
|
||||
tooltip="Copy value"
|
||||
className="invisible group-hover:visible"
|
||||
onClick={() =>
|
||||
copyToClipboard(record.value).catch((error) => {
|
||||
console.error('Failed to copy to clipboard:', error)
|
||||
})
|
||||
}
|
||||
>
|
||||
<IconCopy />
|
||||
</LemonButton>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Value',
|
||||
key: 'value',
|
||||
dataIndex: 'value',
|
||||
className: 'whitespace-nowrap',
|
||||
},
|
||||
]}
|
||||
rowClassName="even:bg-fill-tertiary odd:bg-surface-primary group"
|
||||
firstColumnSticky
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { IconLogomark } from '@posthog/icons'
|
||||
import { LemonCard } from '@posthog/lemon-ui'
|
||||
import { BindLogic, useActions } from 'kea'
|
||||
import { BindLogic, useActions, useValues } from 'kea'
|
||||
import { errorPropertiesLogic, ErrorPropertiesLogicProps } from 'lib/components/Errors/errorPropertiesLogic'
|
||||
import { ErrorEventType } from 'lib/components/Errors/types'
|
||||
import { TZLabel } from 'lib/components/TZLabel'
|
||||
@@ -11,8 +11,11 @@ import { ErrorTrackingRelationalIssue } from '~/queries/schema/schema-general'
|
||||
|
||||
import { exceptionCardLogic } from './exceptionCardLogic'
|
||||
import { PropertiesTab } from './Tabs/PropertiesTab'
|
||||
import { RawTab } from './Tabs/RawTab'
|
||||
import { StacktraceTab } from './Tabs/StacktraceTab'
|
||||
import ViewRecordingTrigger from 'lib/components/ViewRecordingButton/ViewRecordingTrigger'
|
||||
import { ButtonPrimitive } from 'lib/ui/Button/ButtonPrimitives'
|
||||
import { match, P } from 'ts-pattern'
|
||||
import { IconPlayCircle } from 'lib/lemon-ui/icons'
|
||||
|
||||
interface ExceptionCardContentProps {
|
||||
issue?: ErrorTrackingRelationalIssue
|
||||
@@ -26,7 +29,7 @@ export interface ExceptionCardProps extends Omit<ExceptionCardContentProps, 'tim
|
||||
eventLoading: boolean
|
||||
}
|
||||
|
||||
export function ExceptionCard({ issue, issueLoading, label, event, eventLoading }: ExceptionCardProps): JSX.Element {
|
||||
export function ExceptionCard({ issue, issueLoading, event, eventLoading }: ExceptionCardProps): JSX.Element {
|
||||
const { setLoading } = useActions(exceptionCardLogic)
|
||||
|
||||
useEffect(() => {
|
||||
@@ -43,27 +46,24 @@ export function ExceptionCard({ issue, issueLoading, label, event, eventLoading
|
||||
} as ErrorPropertiesLogicProps
|
||||
}
|
||||
>
|
||||
<ExceptionCardContent
|
||||
issue={issue}
|
||||
label={label}
|
||||
timestamp={event?.timestamp}
|
||||
issueLoading={issueLoading}
|
||||
/>
|
||||
<ExceptionCardContent issue={issue} timestamp={event?.timestamp} issueLoading={issueLoading} />
|
||||
</BindLogic>
|
||||
)
|
||||
}
|
||||
|
||||
function ExceptionCardContent({ issue, issueLoading, timestamp, label }: ExceptionCardContentProps): JSX.Element {
|
||||
function ExceptionCardContent({ issue, issueLoading, timestamp }: ExceptionCardContentProps): JSX.Element {
|
||||
const { sessionId, mightHaveRecording } = useValues(errorPropertiesLogic)
|
||||
|
||||
return (
|
||||
<LemonCard hoverEffect={false} className="group p-0 relative overflow-hidden">
|
||||
<LemonCard hoverEffect={false} className="p-0 relative overflow-hidden">
|
||||
<TabsPrimitive defaultValue="stacktrace">
|
||||
<div className="flex justify-between h-[2rem] items-center w-full px-2 border-b">
|
||||
<TabsPrimitiveList className="flex justify-between w-full h-full items-center">
|
||||
<div className="w-full h-full">
|
||||
<TabsPrimitiveTrigger value="raw" className="flex items-center gap-1 text-lg h-full">
|
||||
<div className="flex items-center gap-1 text-lg h-full">
|
||||
<IconLogomark />
|
||||
<span className="text-sm">Exception</span>
|
||||
</TabsPrimitiveTrigger>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2 w-full justify-center h-full">
|
||||
<TabsPrimitiveTrigger className="px-2" value="stacktrace">
|
||||
@@ -73,15 +73,32 @@ function ExceptionCardContent({ issue, issueLoading, timestamp, label }: Excepti
|
||||
Properties
|
||||
</TabsPrimitiveTrigger>
|
||||
</div>
|
||||
<div className="w-full flex gap-2 justify-end items-center">
|
||||
<div className="w-full flex gap-1 justify-end items-center">
|
||||
{timestamp && <TZLabel className="text-muted text-xs" time={timestamp} />}
|
||||
{label}
|
||||
<ViewRecordingTrigger sessionId={sessionId} inModal={true} timestamp={timestamp}>
|
||||
{(onClick, _, disabledReason, maybeSpinner) => {
|
||||
return (
|
||||
<ButtonPrimitive
|
||||
disabled={disabledReason != null || !mightHaveRecording}
|
||||
onClick={onClick}
|
||||
className="px-2 h-[1.4rem] whitespace-nowrap"
|
||||
tooltip={match([disabledReason != null, mightHaveRecording])
|
||||
.with([true, P.any], () => 'No recording available')
|
||||
.with([false, false], () => 'Recording not ready')
|
||||
.otherwise(() => 'View Recording')}
|
||||
>
|
||||
<IconPlayCircle />
|
||||
Recording
|
||||
{maybeSpinner}
|
||||
</ButtonPrimitive>
|
||||
)
|
||||
}}
|
||||
</ViewRecordingTrigger>
|
||||
</div>
|
||||
</TabsPrimitiveList>
|
||||
</div>
|
||||
<StacktraceTab value="stacktrace" issue={issue} issueLoading={issueLoading} timestamp={timestamp} />
|
||||
<PropertiesTab value="properties" />
|
||||
<RawTab value="raw" />
|
||||
</TabsPrimitive>
|
||||
</LemonCard>
|
||||
)
|
||||
|
||||
@@ -1,23 +1,76 @@
|
||||
import { useValues } from 'kea'
|
||||
import { errorPropertiesLogic } from 'lib/components/Errors/errorPropertiesLogic'
|
||||
import { JSONViewer } from 'lib/components/JSONViewer'
|
||||
import { TabsPrimitiveContent, TabsPrimitiveContentProps } from 'lib/ui/TabsPrimitive/TabsPrimitive'
|
||||
import { useActions, useValues } from 'kea'
|
||||
|
||||
import { ContextDisplay } from '../../ContextDisplay'
|
||||
import { LemonButton } from '@posthog/lemon-ui'
|
||||
import { exceptionCardLogic } from '../exceptionCardLogic'
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItemIndicator,
|
||||
DropdownMenuTrigger,
|
||||
} from 'lib/ui/DropdownMenu/DropdownMenu'
|
||||
import { ButtonPrimitive } from 'lib/ui/Button/ButtonPrimitives'
|
||||
import { IconChevronDown } from '@posthog/icons'
|
||||
import { ContextDisplay } from '../../ContextDisplay'
|
||||
|
||||
export interface PropertiesTabProps extends TabsPrimitiveContentProps {}
|
||||
|
||||
export function PropertiesTab({ ...props }: PropertiesTabProps): JSX.Element {
|
||||
const { loading } = useValues(exceptionCardLogic)
|
||||
const { exceptionAttributes, additionalProperties } = useValues(errorPropertiesLogic)
|
||||
const { properties, exceptionAttributes, additionalProperties } = useValues(errorPropertiesLogic)
|
||||
const { loading, showJSONProperties, showAdditionalProperties } = useValues(exceptionCardLogic)
|
||||
|
||||
return (
|
||||
<TabsPrimitiveContent {...props}>
|
||||
<ContextDisplay
|
||||
className="w-full p-2"
|
||||
attributes={exceptionAttributes ?? undefined}
|
||||
additionalProperties={additionalProperties}
|
||||
loading={loading}
|
||||
/>
|
||||
<div className="flex justify-end items-center border-b-1 bg-surface-secondary">
|
||||
<ShowDropDownMenu />
|
||||
</div>
|
||||
<div>
|
||||
{showJSONProperties ? (
|
||||
<JSONViewer src={properties} name="event" collapsed={1} collapseStringsAfterLength={80} sortKeys />
|
||||
) : (
|
||||
<ContextDisplay
|
||||
loading={loading}
|
||||
exceptionAttributes={exceptionAttributes}
|
||||
additionalProperties={showAdditionalProperties ? additionalProperties : {}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</TabsPrimitiveContent>
|
||||
)
|
||||
}
|
||||
|
||||
function ShowDropDownMenu(): JSX.Element {
|
||||
const { showJSONProperties, showAdditionalProperties } = useValues(exceptionCardLogic)
|
||||
const { setShowJSONProperties, setShowAdditionalProperties } = useActions(exceptionCardLogic)
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<LemonButton size="small" sideIcon={<IconChevronDown />}>
|
||||
Show
|
||||
</LemonButton>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuCheckboxItem
|
||||
checked={showAdditionalProperties}
|
||||
onCheckedChange={setShowAdditionalProperties}
|
||||
asChild
|
||||
>
|
||||
<ButtonPrimitive menuItem size="sm">
|
||||
<DropdownMenuItemIndicator intent="checkbox" />
|
||||
Additional properties
|
||||
</ButtonPrimitive>
|
||||
</DropdownMenuCheckboxItem>
|
||||
<DropdownMenuCheckboxItem checked={showJSONProperties} onCheckedChange={setShowJSONProperties} asChild>
|
||||
<ButtonPrimitive menuItem size="sm">
|
||||
<DropdownMenuItemIndicator intent="checkbox" />
|
||||
As JSON
|
||||
</ButtonPrimitive>
|
||||
</DropdownMenuCheckboxItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
import { useValues } from 'kea'
|
||||
import { errorPropertiesLogic } from 'lib/components/Errors/errorPropertiesLogic'
|
||||
import { JSONViewer } from 'lib/components/JSONViewer'
|
||||
import { TabsPrimitiveContent, TabsPrimitiveContentProps } from 'lib/ui/TabsPrimitive/TabsPrimitive'
|
||||
|
||||
export interface RawTabProps extends TabsPrimitiveContentProps {}
|
||||
|
||||
export function RawTab(props: RawTabProps): JSX.Element {
|
||||
const { properties } = useValues(errorPropertiesLogic)
|
||||
return (
|
||||
<TabsPrimitiveContent className="p-2" {...props}>
|
||||
<JSONViewer src={properties} name="event" collapsed={1} collapseStringsAfterLength={80} sortKeys />
|
||||
</TabsPrimitiveContent>
|
||||
)
|
||||
}
|
||||
@@ -3,8 +3,6 @@ import { useActions, useValues } from 'kea'
|
||||
import { errorPropertiesLogic } from 'lib/components/Errors/errorPropertiesLogic'
|
||||
import { ExceptionHeaderProps } from 'lib/components/Errors/StackTraces'
|
||||
import { ErrorTrackingException } from 'lib/components/Errors/types'
|
||||
import ViewRecordingTrigger from 'lib/components/ViewRecordingButton/ViewRecordingTrigger'
|
||||
import { IconPlayCircle } from 'lib/lemon-ui/icons'
|
||||
import { ButtonGroupPrimitive, ButtonPrimitive } from 'lib/ui/Button/ButtonPrimitives'
|
||||
import {
|
||||
DropdownMenu,
|
||||
@@ -24,7 +22,6 @@ import { FixModal } from '../FixModal'
|
||||
import { StacktraceBaseDisplayProps, StacktraceEmptyDisplay } from '../Stacktrace/StacktraceBase'
|
||||
import { StacktraceGenericDisplay } from '../Stacktrace/StacktraceGenericDisplay'
|
||||
import { StacktraceTextDisplay } from '../Stacktrace/StacktraceTextDisplay'
|
||||
import { match, P } from 'ts-pattern'
|
||||
|
||||
export interface StacktraceTabProps extends Omit<TabsPrimitiveContentProps, 'children'> {
|
||||
issue?: ErrorTrackingRelationalIssue
|
||||
@@ -40,7 +37,7 @@ export function StacktraceTab({
|
||||
...props
|
||||
}: StacktraceTabProps): JSX.Element {
|
||||
const { loading } = useValues(exceptionCardLogic)
|
||||
const { exceptionAttributes, exceptionList, sessionId, mightHaveRecording } = useValues(errorPropertiesLogic)
|
||||
const { exceptionAttributes, exceptionList } = useValues(errorPropertiesLogic)
|
||||
const showFixButton = hasResolvedStackFrames(exceptionList)
|
||||
const [showFixModal, setShowFixModal] = useState(false)
|
||||
return (
|
||||
@@ -50,25 +47,6 @@ export function StacktraceTab({
|
||||
<ExceptionAttributesPreview attributes={exceptionAttributes} loading={loading} />
|
||||
</div>
|
||||
<ButtonGroupPrimitive size="sm">
|
||||
<ViewRecordingTrigger sessionId={sessionId} inModal={true} timestamp={timestamp}>
|
||||
{(onClick, _, disabledReason, maybeSpinner) => {
|
||||
return (
|
||||
<ButtonPrimitive
|
||||
disabled={disabledReason != null || !mightHaveRecording}
|
||||
onClick={onClick}
|
||||
className="px-2 h-[1.4rem]"
|
||||
tooltip={match([disabledReason != null, mightHaveRecording])
|
||||
.with([true, P.any], () => 'No recording available')
|
||||
.with([false, false], () => 'Recording not ready')
|
||||
.otherwise(() => 'View Recording')}
|
||||
>
|
||||
<IconPlayCircle />
|
||||
View Recording
|
||||
{maybeSpinner}
|
||||
</ButtonPrimitive>
|
||||
)
|
||||
}}
|
||||
</ViewRecordingTrigger>
|
||||
{showFixButton && (
|
||||
<ButtonPrimitive
|
||||
onClick={() => setShowFixModal(true)}
|
||||
@@ -76,7 +54,7 @@ export function StacktraceTab({
|
||||
tooltip="Generate AI prompt to fix this error"
|
||||
>
|
||||
<IconMagicWand />
|
||||
Fix
|
||||
Fix with AI
|
||||
</ButtonPrimitive>
|
||||
)}
|
||||
<ShowDropDownMenu>
|
||||
@@ -123,6 +101,7 @@ function StacktraceIssueDisplay({
|
||||
function ShowDropDownMenu({ children }: { children: React.ReactNode }): JSX.Element {
|
||||
const { showAllFrames, showAsText } = useValues(exceptionCardLogic)
|
||||
const { setShowAllFrames, setShowAsText } = useActions(exceptionCardLogic)
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>{children}</DropdownMenuTrigger>
|
||||
|
||||
@@ -6,16 +6,30 @@ export const exceptionCardLogic = kea<exceptionCardLogicType>([
|
||||
path(() => ['scenes', 'error-tracking', 'exceptionCardLogic']),
|
||||
|
||||
actions({
|
||||
setShowJSONProperties: (showJSON: boolean) => ({ showJSON }),
|
||||
setShowAdditionalProperties: (showProperties: boolean) => ({ showProperties }),
|
||||
setShowAsText: (showAsText: boolean) => ({ showAsText }),
|
||||
setShowAllFrames: (showAllFrames: boolean) => ({ showAllFrames }),
|
||||
setLoading: (loading: boolean) => ({ loading }),
|
||||
}),
|
||||
|
||||
reducers({
|
||||
showJSONProperties: [
|
||||
false,
|
||||
{
|
||||
setShowJSONProperties: (_, { showJSON }) => showJSON,
|
||||
},
|
||||
],
|
||||
showAdditionalProperties: [
|
||||
false,
|
||||
{
|
||||
setShowAdditionalProperties: (_, { showProperties }) => showProperties,
|
||||
},
|
||||
],
|
||||
showAsText: [
|
||||
false,
|
||||
{
|
||||
setShowAsText: (_, { showAsText }: { showAsText: boolean }) => showAsText,
|
||||
setShowAsText: (_, { showAsText }) => showAsText,
|
||||
},
|
||||
],
|
||||
showAllFrames: [
|
||||
|
||||