+
+ {events.length > 1 && (
+
}
onClick={() => setActiveEventId(activeEventId - 1)}
disabledReason={activeEventId <= 0 && 'No earlier examples'}
/>
}
onClick={() => setActiveEventId(activeEventId + 1)}
- disabledReason={activeEventId >= eventProperties.length - 1 && 'No newer examples'}
+ disabledReason={activeEventId >= events.length - 1 && 'No newer examples'}
/>
+
+ {activeEventId + 1} of {events.length}
+
)}
-
+
+
)
}
diff --git a/frontend/src/scenes/error-tracking/ErrorTrackingScene.tsx b/frontend/src/scenes/error-tracking/ErrorTrackingScene.tsx
index 80fdfbe760a8d..e19be29f0d213 100644
--- a/frontend/src/scenes/error-tracking/ErrorTrackingScene.tsx
+++ b/frontend/src/scenes/error-tracking/ErrorTrackingScene.tsx
@@ -1,20 +1,16 @@
-import { LemonSelect } from '@posthog/lemon-ui'
-import { useActions, useValues } from 'kea'
-import { DateFilter } from 'lib/components/DateFilter/DateFilter'
-import { TaxonomicFilterGroupType } from 'lib/components/TaxonomicFilter/types'
-import UniversalFilters from 'lib/components/UniversalFilters/UniversalFilters'
-import { universalFiltersLogic } from 'lib/components/UniversalFilters/universalFiltersLogic'
+import { useValues } from 'kea'
import { LemonTableLink } from 'lib/lemon-ui/LemonTable/LemonTableLink'
-import { TestAccountFilter } from 'scenes/insights/filters/TestAccountFilter'
+import { useMemo } from 'react'
import { SceneExport } from 'scenes/sceneTypes'
import { urls } from 'scenes/urls'
import { Query } from '~/queries/Query/Query'
-import { NodeKind } from '~/queries/schema'
import { QueryContext, QueryContextColumnComponent } from '~/queries/types'
-import { AnyPropertyFilter } from '~/types'
+import { ErrorTrackingFilters } from './ErrorTrackingFilters'
+import { errorTrackingLogic } from './errorTrackingLogic'
import { errorTrackingSceneLogic } from './errorTrackingSceneLogic'
+import { errorTrackingQuery } from './queries'
export const scene: SceneExport = {
component: ErrorTrackingScene,
@@ -22,7 +18,13 @@ export const scene: SceneExport = {
}
export function ErrorTrackingScene(): JSX.Element {
- const { dateRange, order, filterTestAccounts, filterGroup } = useValues(errorTrackingSceneLogic)
+ const { order } = useValues(errorTrackingSceneLogic)
+ const { dateRange, filterTestAccounts, filterGroup } = useValues(errorTrackingLogic)
+
+ const query = useMemo(
+ () => errorTrackingQuery({ order, dateRange, filterTestAccounts, filterGroup }),
+ [order, dateRange, filterTestAccounts, filterGroup]
+ )
const context: QueryContext = {
columns: {
@@ -36,39 +38,8 @@ export function ErrorTrackingScene(): JSX.Element {
return (
-
-
+
+
)
}
@@ -85,96 +56,3 @@ const CustomGroupTitleColumn: QueryContextColumnComponent = (props) => {
/>
)
}
-
-const Filters = (): JSX.Element => {
- const { dateRange, order, filterGroup, filterTestAccounts } = useValues(errorTrackingSceneLogic)
- const { setDateRange, setOrder, setFilterGroup, setFilterTestAccounts } = useActions(errorTrackingSceneLogic)
-
- return (
-
{
- setFilterGroup(filterGroup)
- }}
- >
-
-
-
- {
- setDateRange({ date_from: changedDateFrom, date_to: changedDateTo })
- }}
- size="small"
- />
-
-
-
- {
- setFilterTestAccounts(filter_test_accounts || false)
- }}
- size="small"
- />
-
-
-
-
-
-
-
-
- )
-}
-
-const RecordingsUniversalFilterGroup = (): JSX.Element => {
- const { filterGroup } = useValues(universalFiltersLogic)
- const { replaceGroupValue, removeGroupValue } = useActions(universalFiltersLogic)
-
- const values = filterGroup.values as AnyPropertyFilter[]
-
- return (
- <>
- {values.map((filter, index) => (
-
removeGroupValue(index)}
- onChange={(value) => replaceGroupValue(index, value)}
- />
- ))}
- >
- )
-}
diff --git a/frontend/src/scenes/error-tracking/errorTrackingGroupSceneLogic.ts b/frontend/src/scenes/error-tracking/errorTrackingGroupSceneLogic.ts
index c1ba3d9d88ff7..95490466861ea 100644
--- a/frontend/src/scenes/error-tracking/errorTrackingGroupSceneLogic.ts
+++ b/frontend/src/scenes/error-tracking/errorTrackingGroupSceneLogic.ts
@@ -1,53 +1,48 @@
-import { afterMount, kea, path, props, selectors } from 'kea'
+import { afterMount, connect, kea, path, props, selectors } from 'kea'
import { loaders } from 'kea-loaders'
import api from 'lib/api'
-import { uuid } from 'lib/utils'
import { Scene } from 'scenes/sceneTypes'
import { urls } from 'scenes/urls'
-import { HogQLQuery, NodeKind } from '~/queries/schema'
-import { hogql } from '~/queries/utils'
-import { Breadcrumb, ErrorTrackingGroup, EventType } from '~/types'
+import { Breadcrumb, EventType } from '~/types'
import type { errorTrackingGroupSceneLogicType } from './errorTrackingGroupSceneLogicType'
+import { errorTrackingLogic } from './errorTrackingLogic'
+import { errorTrackingGroupQuery } from './queries'
export interface ErrorTrackingGroupSceneLogicProps {
- id: ErrorTrackingGroup['id']
+ id: string
}
+export type ExceptionEventType = Pick
+
export const errorTrackingGroupSceneLogic = kea([
path((key) => ['scenes', 'error-tracking', 'errorTrackingGroupSceneLogic', key]),
props({} as ErrorTrackingGroupSceneLogicProps),
- loaders(({ props }) => ({
- group: [
- null as ErrorTrackingGroup | null,
- {
- loadGroup: async () => {
- // TODO: properly flesh out this page
- return {
- id: uuid(),
- title: 'Placeholder title',
- description: 'This is an error message',
- occurrences: 0,
- uniqueSessions: 0,
- uniqueUsers: 0,
- }
- },
- },
- ],
- eventProperties: [
- [] as EventType['properties'][],
+ connect({
+ values: [errorTrackingLogic, ['dateRange', 'filterTestAccounts', 'filterGroup']],
+ }),
+
+ loaders(({ props, values }) => ({
+ events: [
+ [] as ExceptionEventType[],
{
- loadGroupEvents: async () => {
- const query: HogQLQuery = {
- kind: NodeKind.HogQLQuery,
- query: hogql`SELECT properties
- FROM events e
- WHERE event = '$exception' AND properties.$exception_type = ${props.id}`,
- }
- const res = await api.query(query)
- return res.results.map((r) => JSON.parse(r[0]))
+ loadEvents: async () => {
+ const response = await api.query(
+ errorTrackingGroupQuery({
+ group: props.id,
+ dateRange: values.dateRange,
+ filterTestAccounts: values.filterTestAccounts,
+ filterGroup: values.filterGroup,
+ })
+ )
+
+ return response.results.map((r) => ({
+ properties: JSON.parse(r[0]),
+ timestamp: r[1],
+ person: r[2],
+ }))
},
},
],
@@ -55,8 +50,8 @@ export const errorTrackingGroupSceneLogic = kea [s.group],
- (group): Breadcrumb[] => {
+ (_, p) => [p.id],
+ (id): Breadcrumb[] => {
return [
{
key: Scene.ErrorTracking,
@@ -64,8 +59,8 @@ export const errorTrackingGroupSceneLogic = kea {
- actions.loadGroup()
- actions.loadGroupEvents()
+ actions.loadEvents()
}),
])
diff --git a/frontend/src/scenes/error-tracking/errorTrackingLogic.ts b/frontend/src/scenes/error-tracking/errorTrackingLogic.ts
new file mode 100644
index 0000000000000..6d0a7149cec16
--- /dev/null
+++ b/frontend/src/scenes/error-tracking/errorTrackingLogic.ts
@@ -0,0 +1,48 @@
+import { actions, kea, path, reducers } from 'kea'
+import { UniversalFiltersGroup } from 'lib/components/UniversalFilters/UniversalFilters'
+
+import { DateRange, ErrorTrackingOrder } from '~/queries/schema'
+import { FilterLogicalOperator } from '~/types'
+
+import type { errorTrackingLogicType } from './errorTrackingLogicType'
+
+export const errorTrackingLogic = kea([
+ path(['scenes', 'error-tracking', 'errorTrackingLogic']),
+
+ actions({
+ setDateRange: (dateRange: DateRange) => ({ dateRange }),
+ setOrder: (order: ErrorTrackingOrder) => ({ order }),
+ setFilterGroup: (filterGroup: UniversalFiltersGroup) => ({ filterGroup }),
+ setFilterTestAccounts: (filterTestAccounts: boolean) => ({ filterTestAccounts }),
+ }),
+ reducers({
+ dateRange: [
+ { date_from: '-7d', date_to: null } as DateRange,
+ { persist: true },
+ {
+ setDateRange: (_, { dateRange }) => dateRange,
+ },
+ ],
+ order: [
+ 'last_seen' as ErrorTrackingOrder,
+ { persist: true },
+ {
+ setOrder: (_, { order }) => order,
+ },
+ ],
+ filterGroup: [
+ { type: FilterLogicalOperator.And, values: [] } as UniversalFiltersGroup,
+ { persist: true },
+ {
+ setFilterGroup: (_, { filterGroup }) => filterGroup,
+ },
+ ],
+ filterTestAccounts: [
+ false as boolean,
+ { persist: true },
+ {
+ setFilterTestAccounts: (_, { filterTestAccounts }) => filterTestAccounts,
+ },
+ ],
+ }),
+])
diff --git a/frontend/src/scenes/error-tracking/errorTrackingSceneLogic.ts b/frontend/src/scenes/error-tracking/errorTrackingSceneLogic.ts
index 9e1eefa9d0390..85e867002fd9f 100644
--- a/frontend/src/scenes/error-tracking/errorTrackingSceneLogic.ts
+++ b/frontend/src/scenes/error-tracking/errorTrackingSceneLogic.ts
@@ -1,8 +1,6 @@
import { actions, kea, path, reducers } from 'kea'
-import { UniversalFiltersGroup } from 'lib/components/UniversalFilters/UniversalFilters'
-import { DateRange, ErrorTrackingOrder } from '~/queries/schema'
-import { FilterLogicalOperator } from '~/types'
+import { ErrorTrackingOrder } from '~/queries/schema'
import type { errorTrackingSceneLogicType } from './errorTrackingSceneLogicType'
@@ -10,35 +8,15 @@ export const errorTrackingSceneLogic = kea([
path(['scenes', 'error-tracking', 'errorTrackingSceneLogic']),
actions({
- setDateRange: (dateRange: DateRange) => ({ dateRange }),
setOrder: (order: ErrorTrackingOrder) => ({ order }),
- setFilterGroup: (filterGroup: UniversalFiltersGroup) => ({ filterGroup }),
- setFilterTestAccounts: (filterTestAccounts: boolean) => ({ filterTestAccounts }),
}),
reducers({
- dateRange: [
- { date_from: '-7d', date_to: null } as DateRange,
- {
- setDateRange: (_, { dateRange }) => dateRange,
- },
- ],
order: [
'last_seen' as ErrorTrackingOrder,
+ { persist: true },
{
setOrder: (_, { order }) => order,
},
],
- filterGroup: [
- { type: FilterLogicalOperator.And, values: [] } as UniversalFiltersGroup,
- {
- setFilterGroup: (_, { filterGroup }) => filterGroup,
- },
- ],
- filterTestAccounts: [
- false as boolean,
- {
- setFilterTestAccounts: (_, { filterTestAccounts }) => filterTestAccounts,
- },
- ],
}),
])
diff --git a/frontend/src/scenes/error-tracking/queries.ts b/frontend/src/scenes/error-tracking/queries.ts
new file mode 100644
index 0000000000000..5729c81a991d3
--- /dev/null
+++ b/frontend/src/scenes/error-tracking/queries.ts
@@ -0,0 +1,81 @@
+import { UniversalFiltersGroup } from 'lib/components/UniversalFilters/UniversalFilters'
+
+import { DataTableNode, DateRange, ErrorTrackingOrder, EventsQuery, NodeKind } from '~/queries/schema'
+import { AnyPropertyFilter } from '~/types'
+
+export const errorTrackingQuery = ({
+ order,
+ dateRange,
+ filterTestAccounts,
+ filterGroup,
+}: {
+ order: ErrorTrackingOrder
+ dateRange: DateRange
+ filterTestAccounts: boolean
+ filterGroup: UniversalFiltersGroup
+}): DataTableNode => {
+ return {
+ kind: NodeKind.DataTableNode,
+ source: {
+ kind: NodeKind.EventsQuery,
+ select: [
+ 'any(properties) -- Error',
+ 'properties.$exception_type',
+ 'count() as unique_occurrences -- Occurrences',
+ 'count(distinct $session_id) as unique_sessions -- Sessions',
+ 'count(distinct distinct_id) as unique_users -- Users',
+ 'max(timestamp) as last_seen',
+ 'min(timestamp) as first_seen',
+ ],
+ orderBy: [order],
+ ...defaultProperties({ dateRange, filterTestAccounts, filterGroup }),
+ },
+ hiddenColumns: [
+ 'properties.$exception_type',
+ 'first_value(properties)',
+ 'max(timestamp) as last_seen',
+ 'min(timestamp) as first_seen',
+ ],
+ showActions: false,
+ showTimings: false,
+ }
+}
+
+export const errorTrackingGroupQuery = ({
+ group,
+ dateRange,
+ filterTestAccounts,
+ filterGroup,
+}: {
+ group: string
+ dateRange: DateRange
+ filterTestAccounts: boolean
+ filterGroup: UniversalFiltersGroup
+}): EventsQuery => {
+ return {
+ kind: NodeKind.EventsQuery,
+ select: ['properties', 'timestamp', 'person'],
+ where: [`properties.$exception_type = '${group}'`],
+ ...defaultProperties({ dateRange, filterTestAccounts, filterGroup }),
+ }
+}
+
+const defaultProperties = ({
+ dateRange,
+ filterTestAccounts,
+ filterGroup,
+}: {
+ dateRange: DateRange
+ filterTestAccounts: boolean
+ filterGroup: UniversalFiltersGroup
+}): Pick => {
+ const properties = filterGroup.values as AnyPropertyFilter[]
+
+ return {
+ event: '$exception',
+ after: dateRange.date_from || undefined,
+ before: dateRange.date_to || undefined,
+ filterTestAccounts,
+ properties,
+ }
+}
diff --git a/frontend/src/types.ts b/frontend/src/types.ts
index be05ba1d1fb0a..5624a8841ccbc 100644
--- a/frontend/src/types.ts
+++ b/frontend/src/types.ts
@@ -1007,15 +1007,6 @@ export type ErrorCluster = {
}
export type ErrorClusterResponse = ErrorCluster[] | null
-export type ErrorTrackingGroup = {
- id: string
- title: string
- description: string
- occurrences: number
- uniqueSessions: number
- uniqueUsers: number
-}
-
export type EntityType = 'actions' | 'events' | 'data_warehouse' | 'new_entity'
export interface Entity {