Skip to content

Commit

Permalink
feat(errors): Allow searching for errors (#25175)
Browse files Browse the repository at this point in the history
Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
  • Loading branch information
neilkakkar and github-actions[bot] authored Oct 1, 2024
1 parent 2b00c2b commit ea6cb43
Show file tree
Hide file tree
Showing 16 changed files with 200 additions and 21 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions frontend/src/queries/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -4200,6 +4200,9 @@
"response": {
"$ref": "#/definitions/ErrorTrackingQueryResponse"
},
"searchQuery": {
"type": "string"
},
"select": {
"items": {
"$ref": "#/definitions/HogQLExpression"
Expand Down
1 change: 1 addition & 0 deletions frontend/src/queries/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1539,6 +1539,7 @@ export interface ErrorTrackingQuery extends DataNode<ErrorTrackingQueryResponse>
assignee?: integer | null
filterGroup?: PropertyGroupFilter
filterTestAccounts?: boolean
searchQuery?: string
limit?: integer
}

Expand Down
25 changes: 14 additions & 11 deletions frontend/src/scenes/error-tracking/ErrorTrackingFilters.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { LemonSelect } from '@posthog/lemon-ui'
import { LemonInput, LemonSelect } from '@posthog/lemon-ui'
import { useActions, useValues } from 'kea'
import { DateFilter } from 'lib/components/DateFilter/DateFilter'
import { MemberSelect } from 'lib/components/MemberSelect'
Expand All @@ -13,19 +13,22 @@ import { errorTrackingLogic } from './errorTrackingLogic'
import { errorTrackingSceneLogic } from './errorTrackingSceneLogic'

export const FilterGroup = (): JSX.Element => {
const { filterGroup, filterTestAccounts } = useValues(errorTrackingLogic)
const { setFilterGroup, setFilterTestAccounts } = useActions(errorTrackingLogic)
const { filterGroup, filterTestAccounts, searchQuery } = useValues(errorTrackingLogic)
const { setFilterGroup, setFilterTestAccounts, setSearchQuery } = useActions(errorTrackingLogic)

return (
<div className="flex flex-1 items-center justify-between space-x-2">
<UniversalFilters
rootKey="error-tracking"
group={filterGroup}
taxonomicGroupTypes={[TaxonomicFilterGroupType.PersonProperties, TaxonomicFilterGroupType.Cohorts]}
onChange={setFilterGroup}
>
<RecordingsUniversalFilterGroup />
</UniversalFilters>
<div className="flex items-center gap-2">
<LemonInput type="search" placeholder="Search..." value={searchQuery} onChange={setSearchQuery} />
<UniversalFilters
rootKey="error-tracking"
group={filterGroup}
taxonomicGroupTypes={[TaxonomicFilterGroupType.PersonProperties, TaxonomicFilterGroupType.Cohorts]}
onChange={setFilterGroup}
>
<RecordingsUniversalFilterGroup />
</UniversalFilters>
</div>
<div>
<TestAccountFilter
size="small"
Expand Down
7 changes: 7 additions & 0 deletions frontend/src/scenes/error-tracking/errorTrackingLogic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export const errorTrackingLogic = kea<errorTrackingLogicType>([
setAssignee: (assignee: number | null) => ({ assignee }),
setFilterGroup: (filterGroup: UniversalFiltersGroup) => ({ filterGroup }),
setFilterTestAccounts: (filterTestAccounts: boolean) => ({ filterTestAccounts }),
setSearchQuery: (searchQuery: string) => ({ searchQuery }),
setSparklineSelectedPeriod: (period: string | null) => ({ period }),
_setSparklineOptions: (options: SparklineOption[]) => ({ options }),
}),
Expand Down Expand Up @@ -71,6 +72,12 @@ export const errorTrackingLogic = kea<errorTrackingLogicType>([
setFilterTestAccounts: (_, { filterTestAccounts }) => filterTestAccounts,
},
],
searchQuery: [
'' as string,
{
setSearchQuery: (_, { searchQuery }) => searchQuery,
},
],
sparklineSelectedPeriod: [
lastDay.value as string | null,
{ persist: true },
Expand Down
23 changes: 20 additions & 3 deletions frontend/src/scenes/error-tracking/errorTrackingSceneLogic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const errorTrackingSceneLogic = kea<errorTrackingSceneLogicType>([
connect({
values: [
errorTrackingLogic,
['dateRange', 'assignee', 'filterTestAccounts', 'filterGroup', 'sparklineSelectedPeriod'],
['dateRange', 'assignee', 'filterTestAccounts', 'filterGroup', 'sparklineSelectedPeriod', 'searchQuery'],
],
}),

Expand All @@ -39,15 +39,32 @@ export const errorTrackingSceneLogic = kea<errorTrackingSceneLogicType>([

selectors({
query: [
(s) => [s.order, s.dateRange, s.assignee, s.filterTestAccounts, s.filterGroup, s.sparklineSelectedPeriod],
(order, dateRange, assignee, filterTestAccounts, filterGroup, sparklineSelectedPeriod): DataTableNode =>
(s) => [
s.order,
s.dateRange,
s.assignee,
s.filterTestAccounts,
s.filterGroup,
s.sparklineSelectedPeriod,
s.searchQuery,
],
(
order,
dateRange,
assignee,
filterTestAccounts,
filterGroup,
sparklineSelectedPeriod,
searchQuery
): DataTableNode =>
errorTrackingQuery({
order,
dateRange,
assignee,
filterTestAccounts,
filterGroup,
sparklineSelectedPeriod,
searchQuery,
}),
],
}),
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/scenes/error-tracking/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,11 @@ export const errorTrackingQuery = ({
assignee,
filterTestAccounts,
filterGroup,
searchQuery,
sparklineSelectedPeriod,
columns,
limit = 50,
}: Pick<ErrorTrackingQuery, 'order' | 'dateRange' | 'assignee' | 'filterTestAccounts' | 'limit'> & {
}: Pick<ErrorTrackingQuery, 'order' | 'dateRange' | 'assignee' | 'filterTestAccounts' | 'limit' | 'searchQuery'> & {
filterGroup: UniversalFiltersGroup
sparklineSelectedPeriod: string | null
columns?: ('error' | 'volume' | 'occurrences' | 'sessions' | 'users' | 'assignee')[]
Expand Down Expand Up @@ -77,6 +78,7 @@ export const errorTrackingQuery = ({
assignee: assignee,
filterGroup: filterGroup as PropertyGroupFilter,
filterTestAccounts: filterTestAccounts,
searchQuery: searchQuery,
limit: limit,
},
showActions: false,
Expand Down
32 changes: 31 additions & 1 deletion posthog/hogql_queries/error_tracking_query_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ def to_query(self) -> ast.SelectQuery:

def select(self):
exprs: list[ast.Expr] = [
ast.Alias(alias="occurrences", expr=ast.Call(name="count", args=[])),
ast.Alias(
alias="occurrences", expr=ast.Call(name="count", distinct=True, args=[ast.Field(chain=["uuid"])])
),
ast.Alias(
alias="sessions", expr=ast.Call(name="count", distinct=True, args=[ast.Field(chain=["$session_id"])])
),
Expand Down Expand Up @@ -126,6 +128,34 @@ def where(self):
),
)

if self.query.searchQuery:
# TODO: Refine this so it only searches the frames inside $exception_list
# TODO: Split out spaces and search for each word separately
# TODO: Add support for searching for specific properties
# TODO: Add fuzzy search support
props_to_search = ["$exception_list", "$exception_stack_trace_raw", "$exception_type", "$exception_message"]
or_exprs: list[ast.Expr] = []
for prop in props_to_search:
or_exprs.append(
ast.CompareOperation(
op=ast.CompareOperationOp.Gt,
left=ast.Call(
name="position",
args=[
ast.Call(name="lower", args=[ast.Field(chain=["properties", prop])]),
ast.Call(name="lower", args=[ast.Constant(value=self.query.searchQuery)]),
],
),
right=ast.Constant(value=0),
)
)

exprs.append(
ast.Or(
exprs=or_exprs,
)
)

return ast.And(exprs=exprs)

def group_by(self):
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# serializer version: 1
# name: TestErrorTrackingQueryRunner.test_assignee_groups
'''
SELECT count() AS occurrences,
SELECT count(DISTINCT events.uuid) AS occurrences,
count(DISTINCT events.`$session_id`) AS sessions,
count(DISTINCT events.distinct_id) AS users,
max(toTimeZone(events.timestamp, 'UTC')) AS last_seen,
Expand All @@ -24,7 +24,7 @@
# ---
# name: TestErrorTrackingQueryRunner.test_column_names
'''
SELECT count() AS occurrences,
SELECT count(DISTINCT events.uuid) AS occurrences,
count(DISTINCT events.`$session_id`) AS sessions,
count(DISTINCT events.distinct_id) AS users,
max(toTimeZone(events.timestamp, 'UTC')) AS last_seen,
Expand Down Expand Up @@ -64,7 +64,7 @@
# ---
# name: TestErrorTrackingQueryRunner.test_column_names.1
'''
SELECT count() AS occurrences,
SELECT count(DISTINCT events.uuid) AS occurrences,
count(DISTINCT events.`$session_id`) AS sessions,
count(DISTINCT events.distinct_id) AS users,
max(toTimeZone(events.timestamp, 'UTC')) AS last_seen,
Expand Down Expand Up @@ -142,7 +142,7 @@
# ---
# name: TestErrorTrackingQueryRunner.test_fingerprints
'''
SELECT count() AS occurrences,
SELECT count(DISTINCT events.uuid) AS occurrences,
count(DISTINCT events.`$session_id`) AS sessions,
count(DISTINCT events.distinct_id) AS users,
max(toTimeZone(events.timestamp, 'UTC')) AS last_seen,
Expand All @@ -163,7 +163,7 @@
# ---
# name: TestErrorTrackingQueryRunner.test_hogql_filters
'''
SELECT count() AS occurrences,
SELECT count(DISTINCT events.uuid) AS occurrences,
count(DISTINCT events.`$session_id`) AS sessions,
count(DISTINCT events.distinct_id) AS users,
max(toTimeZone(events.timestamp, 'UTC')) AS last_seen,
Expand Down Expand Up @@ -241,3 +241,43 @@
max_bytes_before_external_group_by=0
'''
# ---
# name: TestErrorTrackingQueryRunner.test_search_query
'''
SELECT count(DISTINCT events.uuid) AS occurrences,
count(DISTINCT events.`$session_id`) AS sessions,
count(DISTINCT events.distinct_id) AS users,
max(toTimeZone(events.timestamp, 'UTC')) AS last_seen,
min(toTimeZone(events.timestamp, 'UTC')) AS first_seen,
any(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$exception_message'), ''), 'null'), '^"|"$', '')) AS description,
any(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$exception_type'), ''), 'null'), '^"|"$', '')) AS exception_type,
JSONExtract(ifNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$exception_fingerprint'), ''), 'null'), '^"|"$', ''), '[]'), 'Array(String)') AS fingerprint
FROM events
LEFT OUTER JOIN
(SELECT argMax(person_distinct_id_overrides.person_id, person_distinct_id_overrides.version) AS person_id,
person_distinct_id_overrides.distinct_id AS distinct_id
FROM person_distinct_id_overrides
WHERE equals(person_distinct_id_overrides.team_id, 2)
GROUP BY person_distinct_id_overrides.distinct_id
HAVING ifNull(equals(argMax(person_distinct_id_overrides.is_deleted, person_distinct_id_overrides.version), 0), 0) SETTINGS optimize_aggregation_in_order=1) AS events__override ON equals(events.distinct_id, events__override.distinct_id)
LEFT JOIN
(SELECT person.id AS id,
replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(person.properties, 'email'), ''), 'null'), '^"|"$', '') AS properties___email
FROM person
WHERE and(equals(person.team_id, 2), ifNull(in(tuple(person.id, person.version),
(SELECT person.id AS id, max(person.version) AS version
FROM person
WHERE equals(person.team_id, 2)
GROUP BY person.id
HAVING and(ifNull(equals(argMax(person.is_deleted, person.version), 0), 0), ifNull(less(argMax(toTimeZone(person.created_at, 'UTC'), person.version), plus(now64(6, 'UTC'), toIntervalDay(1))), 0)))), 0)) SETTINGS optimize_aggregation_in_order=1) AS events__person ON equals(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id), events__person.id)
WHERE and(equals(events.team_id, 2), equals(events.event, '$exception'), and(less(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2022-01-11 00:00:00.000000', 6, 'UTC')), greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), toDateTime64('2022-01-10 00:00:00.000000', 6, 'UTC')), ifNull(notILike(events__person.properties___email, '%@posthog.com%'), 1)), or(ifNull(greater(position(lower(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$exception_list'), ''), 'null'), '^"|"$', '')), lower('databasenot')), 0), 0), ifNull(greater(position(lower(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$exception_stack_trace_raw'), ''), 'null'), '^"|"$', '')), lower('databasenot')), 0), 0), ifNull(greater(position(lower(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$exception_type'), ''), 'null'), '^"|"$', '')), lower('databasenot')), 0), 0), ifNull(greater(position(lower(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$exception_message'), ''), 'null'), '^"|"$', '')), lower('databasenot')), 0), 0)))
GROUP BY fingerprint
LIMIT 101
OFFSET 0 SETTINGS readonly=2,
max_execution_time=60,
allow_experimental_object_type=1,
format_csv_allow_double_quotes=0,
max_ast_elements=4000000,
max_expanded_ast_elements=4000000,
max_bytes_before_external_group_by=0
'''
# ---
75 changes: 75 additions & 0 deletions posthog/hogql_queries/test/test_error_tracking_query_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,81 @@ def test_column_names(self):
],
)

@snapshot_clickhouse_queries
def test_search_query(self):
with freeze_time("2022-01-10 12:11:00"):
_create_event(
distinct_id=self.distinct_id_one,
event="$exception",
team=self.team,
properties={
"$exception_fingerprint": ["DatabaseNotFoundX"],
"$exception_type": "DatabaseNotFoundX",
"$exception_message": "this is the same error message",
},
)
_create_event(
distinct_id=self.distinct_id_one,
event="$exception",
team=self.team,
properties={
"$exception_fingerprint": ["DatabaseNotFoundY"],
"$exception_type": "DatabaseNotFoundY",
"$exception_message": "this is the same error message",
},
)
_create_event(
distinct_id=self.distinct_id_two,
event="$exception",
team=self.team,
properties={
"$exception_fingerprint": ["xyz"],
"$exception_type": "xyz",
"$exception_message": "this is the same error message",
},
)
flush_persons_and_events()

runner = ErrorTrackingQueryRunner(
team=self.team,
query=ErrorTrackingQuery(
kind="ErrorTrackingQuery",
fingerprint=None,
dateRange=DateRange(date_from="2022-01-10", date_to="2022-01-11"),
filterTestAccounts=True,
searchQuery="databasenot",
),
)

results = sorted(self._calculate(runner)["results"], key=lambda x: x["fingerprint"])

self.assertEqual(len(results), 2)
self.assertEqual(results[0]["fingerprint"], ["DatabaseNotFoundX"])
self.assertEqual(results[0]["occurrences"], 1)
self.assertEqual(results[0]["sessions"], 1)
self.assertEqual(results[0]["users"], 1)

self.assertEqual(results[1]["fingerprint"], ["DatabaseNotFoundY"])
self.assertEqual(results[1]["occurrences"], 1)
self.assertEqual(results[1]["sessions"], 1)
self.assertEqual(results[1]["users"], 1)

def test_empty_search_query(self):
runner = ErrorTrackingQueryRunner(
team=self.team,
query=ErrorTrackingQuery(
kind="ErrorTrackingQuery",
fingerprint=None,
dateRange=DateRange(),
filterTestAccounts=False,
searchQuery="probs not found",
),
)

results = self._calculate(runner)["results"]

self.assertEqual(len(results), 0)

@snapshot_clickhouse_queries
def test_fingerprints(self):
runner = ErrorTrackingQueryRunner(
Expand Down
1 change: 1 addition & 0 deletions posthog/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -5062,6 +5062,7 @@ class ErrorTrackingQuery(BaseModel):
)
order: Optional[Order] = None
response: Optional[ErrorTrackingQueryResponse] = None
searchQuery: Optional[str] = None
select: Optional[list[str]] = None


Expand Down

0 comments on commit ea6cb43

Please sign in to comment.