diff --git a/frontend/src/scenes/error-tracking/ErrorTrackingGroupScene.tsx b/frontend/src/scenes/error-tracking/ErrorTrackingGroupScene.tsx
index d7509e87aa99e..668565b97495c 100644
--- a/frontend/src/scenes/error-tracking/ErrorTrackingGroupScene.tsx
+++ b/frontend/src/scenes/error-tracking/ErrorTrackingGroupScene.tsx
@@ -1,18 +1,13 @@
import './ErrorTracking.scss'
-import { PersonDisplay, TZLabel } from '@posthog/apps-common'
-import { Spinner } from '@posthog/lemon-ui'
-import clsx from 'clsx'
-import { useValues } from 'kea'
-import { EmptyMessage } from 'lib/components/EmptyMessage/EmptyMessage'
-import { ErrorDisplay } from 'lib/components/Errors/ErrorDisplay'
-import { NotFound } from 'lib/components/NotFound'
-import { Playlist } from 'lib/components/Playlist/Playlist'
+import { LemonTabs } from '@posthog/lemon-ui'
+import { useActions, useValues } from 'kea'
import { SceneExport } from 'scenes/sceneTypes'
-import { PropertyIcons } from 'scenes/session-recordings/playlist/SessionRecordingPreview'
import { ErrorTrackingFilters } from './ErrorTrackingFilters'
-import { errorTrackingGroupSceneLogic, ExceptionEventType } from './errorTrackingGroupSceneLogic'
+import { ErrorGroupTab, errorTrackingGroupSceneLogic } from './errorTrackingGroupSceneLogic'
+import { BreakdownsTab } from './groups/BreakdownsTab'
+import { OverviewTab } from './groups/OverviewTab'
export const scene: SceneExport = {
component: ErrorTrackingGroupScene,
@@ -23,75 +18,28 @@ export const scene: SceneExport = {
}
export function ErrorTrackingGroupScene(): JSX.Element {
- const { events, eventsLoading } = useValues(errorTrackingGroupSceneLogic)
+ const { errorGroupTab } = useValues(errorTrackingGroupSceneLogic)
+ const { setErrorGroupTab } = useActions(errorTrackingGroupSceneLogic)
- return eventsLoading ? (
-
- ) : events && events.length > 0 ? (
+ return (
-
-
}
- content={({ activeItem: event }) =>
- event ? (
-
-
-
- ) : (
-
- )
- }
- />
-
-
-
- ) : (
-
- )
-}
-
-const ListItemException = ({ item: event, isActive }: { item: ExceptionEventType; isActive: boolean }): JSX.Element => {
- const properties = ['$browser', '$device_type', '$os']
- .flatMap((property) => {
- let value = event.properties[property]
- const label = value
- if (property === '$device_type') {
- value = event.properties['$device_type'] || event.properties['$initial_device_type']
- }
-
- return { property, value, label }
- })
- .filter((property) => !!property.value)
-
- return (
-
-
- {event.properties.$current_url && (
-
{event.properties.$current_url}
- )}
-
,
+ },
+ {
+ key: ErrorGroupTab.Breakdowns,
+ label: 'Breakdowns',
+ content:
,
+ },
+ ]}
/>
)
diff --git a/frontend/src/scenes/error-tracking/errorTrackingGroupSceneLogic.ts b/frontend/src/scenes/error-tracking/errorTrackingGroupSceneLogic.ts
index 1a7685e2ed1b9..d3f3f8f346af8 100644
--- a/frontend/src/scenes/error-tracking/errorTrackingGroupSceneLogic.ts
+++ b/frontend/src/scenes/error-tracking/errorTrackingGroupSceneLogic.ts
@@ -1,4 +1,4 @@
-import { afterMount, connect, kea, path, props, selectors } from 'kea'
+import { actions, afterMount, connect, kea, path, props, reducers, selectors } from 'kea'
import { loaders } from 'kea-loaders'
import api from 'lib/api'
import { Scene } from 'scenes/sceneTypes'
@@ -16,6 +16,11 @@ export interface ErrorTrackingGroupSceneLogicProps {
export type ExceptionEventType = Pick
+export enum ErrorGroupTab {
+ Overview = 'overview',
+ Breakdowns = 'breakdowns',
+}
+
export const errorTrackingGroupSceneLogic = kea([
path((key) => ['scenes', 'error-tracking', 'errorTrackingGroupSceneLogic', key]),
props({} as ErrorTrackingGroupSceneLogicProps),
@@ -24,6 +29,19 @@ export const errorTrackingGroupSceneLogic = kea ({ tab }),
+ }),
+
+ reducers(() => ({
+ errorGroupTab: [
+ ErrorGroupTab.Overview as ErrorGroupTab,
+ {
+ setErrorGroupTab: (_, { tab }) => tab,
+ },
+ ],
+ })),
+
loaders(({ props, values }) => ({
events: [
[] as ExceptionEventType[],
diff --git a/frontend/src/scenes/error-tracking/groups/BreakdownsTab.tsx b/frontend/src/scenes/error-tracking/groups/BreakdownsTab.tsx
new file mode 100644
index 0000000000000..c9696694b1c2a
--- /dev/null
+++ b/frontend/src/scenes/error-tracking/groups/BreakdownsTab.tsx
@@ -0,0 +1,82 @@
+import { LemonSegmentedButton, LemonSegmentedButtonOption } from '@posthog/lemon-ui'
+import clsx from 'clsx'
+import { useValues } from 'kea'
+import { useResizeBreakpoints } from 'lib/hooks/useResizeObserver'
+import { useState } from 'react'
+
+import { Query } from '~/queries/Query/Query'
+
+import { errorTrackingLogic } from '../errorTrackingLogic'
+import { errorTrackingGroupBreakdownQuery } from '../queries'
+
+const gridColumnsMap = {
+ small: 'grid-cols-1',
+ medium: 'grid-cols-2',
+ large: 'grid-cols-3',
+}
+
+type BreakdownGroup = { title: string; options: LemonSegmentedButtonOption[] }
+
+export const BreakdownsTab = (): JSX.Element => {
+ const breakdownGroups: BreakdownGroup[] = [
+ {
+ title: 'Device',
+ options: [
+ { value: '$browser', label: 'Browser' },
+ { value: '$device_type', label: 'Device type' },
+ { value: '$os', label: 'Operating system' },
+ ],
+ },
+ {
+ title: 'User',
+ options: [
+ { value: '$user_id', label: 'User ID' },
+ { value: '$ip', label: 'IP address' },
+ ],
+ },
+ { title: 'URL', options: [{ value: '$pathname', label: 'Path' }] },
+ ]
+
+ const { ref, size } = useResizeBreakpoints({
+ 0: 'small',
+ 750: 'medium',
+ 1200: 'large',
+ })
+
+ return (
+
+ {breakdownGroups.map((group, index) => (
+
+ ))}
+
+ )
+}
+
+const BreakdownGroup = ({ group }: { group: BreakdownGroup }): JSX.Element => {
+ const { dateRange, filterTestAccounts, filterGroup } = useValues(errorTrackingLogic)
+ const [selectedProperty, setSelectedProperty] = useState(group.options[0].value)
+
+ return (
+
+
+
{group.title}
+ {group.options.length > 1 && (
+
+ )}
+
+
+
+ )
+}
diff --git a/frontend/src/scenes/error-tracking/groups/OverviewTab.tsx b/frontend/src/scenes/error-tracking/groups/OverviewTab.tsx
new file mode 100644
index 0000000000000..bcd1e32f31c36
--- /dev/null
+++ b/frontend/src/scenes/error-tracking/groups/OverviewTab.tsx
@@ -0,0 +1,79 @@
+import { PersonDisplay, TZLabel } from '@posthog/apps-common'
+import { Spinner } from '@posthog/lemon-ui'
+import clsx from 'clsx'
+import { useValues } from 'kea'
+import { EmptyMessage } from 'lib/components/EmptyMessage/EmptyMessage'
+import { ErrorDisplay } from 'lib/components/Errors/ErrorDisplay'
+import { Playlist } from 'lib/components/Playlist/Playlist'
+import { PropertyIcons } from 'scenes/session-recordings/playlist/SessionRecordingPreview'
+
+import { errorTrackingGroupSceneLogic, ExceptionEventType } from '../errorTrackingGroupSceneLogic'
+
+export const OverviewTab = (): JSX.Element => {
+ const { events, eventsLoading } = useValues(errorTrackingGroupSceneLogic)
+
+ return eventsLoading ? (
+
+ ) : (
+
+
}
+ content={({ activeItem: event }) =>
+ event ? (
+
+
+
+ ) : (
+
+ )
+ }
+ />
+
+
+ )
+}
+
+const ListItemException = ({ item: event, isActive }: { item: ExceptionEventType; isActive: boolean }): JSX.Element => {
+ const properties = ['$browser', '$device_type', '$os']
+ .flatMap((property) => {
+ let value = event.properties[property]
+ const label = value
+ if (property === '$device_type') {
+ value = event.properties['$device_type'] || event.properties['$initial_device_type']
+ }
+
+ return { property, value, label }
+ })
+ .filter((property) => !!property.value)
+
+ return (
+
+
+ {event.properties.$current_url && (
+
{event.properties.$current_url}
+ )}
+
+
+ )
+}
diff --git a/frontend/src/scenes/error-tracking/queries.ts b/frontend/src/scenes/error-tracking/queries.ts
index 3502dabe8f901..e4c993917ce12 100644
--- a/frontend/src/scenes/error-tracking/queries.ts
+++ b/frontend/src/scenes/error-tracking/queries.ts
@@ -1,7 +1,7 @@
import { UniversalFiltersGroup } from 'lib/components/UniversalFilters/UniversalFilters'
-import { DataTableNode, DateRange, ErrorTrackingOrder, EventsQuery, NodeKind } from '~/queries/schema'
-import { AnyPropertyFilter } from '~/types'
+import { DataTableNode, DateRange, ErrorTrackingOrder, EventsQuery, InsightVizNode, NodeKind } from '~/queries/schema'
+import { AnyPropertyFilter, BaseMathType, ChartDisplayType } from '~/types'
export const errorTrackingQuery = ({
order,
@@ -60,6 +60,45 @@ export const errorTrackingGroupQuery = ({
}
}
+export const errorTrackingGroupBreakdownQuery = ({
+ breakdownProperty,
+ dateRange,
+ filterTestAccounts,
+ filterGroup,
+}: {
+ breakdownProperty: string
+ dateRange: DateRange
+ filterTestAccounts: boolean
+ filterGroup: UniversalFiltersGroup
+}): InsightVizNode => {
+ return {
+ kind: NodeKind.InsightVizNode,
+ source: {
+ kind: NodeKind.TrendsQuery,
+ trendsFilter: {
+ display: ChartDisplayType.ActionsBarValue,
+ },
+ breakdownFilter: {
+ breakdown_type: 'event',
+ breakdown: breakdownProperty,
+ breakdown_limit: 10,
+ },
+ series: [
+ {
+ kind: NodeKind.EventsNode,
+ event: '$exception',
+ math: BaseMathType.TotalCount,
+ name: 'This is the series name',
+ custom_name: 'Boomer',
+ },
+ ],
+ dateRange: dateRange,
+ properties: filterGroup.values as AnyPropertyFilter[],
+ filterTestAccounts,
+ },
+ }
+}
+
const defaultProperties = ({
dateRange,
filterTestAccounts,