Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: filter by assigned exceptions #24683

Merged
merged 1 commit into from
Aug 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions frontend/src/queries/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -3588,6 +3588,16 @@
"ErrorTrackingQuery": {
"additionalProperties": false,
"properties": {
"assignee": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
]
},
"dateRange": {
"$ref": "#/definitions/DateRange"
},
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 @@ -1362,6 +1362,7 @@ export interface ErrorTrackingQuery extends DataNode<ErrorTrackingQueryResponse>
eventColumns?: string[]
order?: 'last_seen' | 'first_seen' | 'occurrences' | 'users' | 'sessions'
dateRange: DateRange
assignee?: integer | null
filterGroup?: PropertyGroupFilter
filterTestAccounts?: boolean
limit?: integer
Expand Down
98 changes: 55 additions & 43 deletions frontend/src/scenes/error-tracking/ErrorTrackingFilters.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { LemonSelect } from '@posthog/lemon-ui'
import { useActions, useValues } from 'kea'
import { DateFilter } from 'lib/components/DateFilter/DateFilter'
import { MemberSelect } from 'lib/components/MemberSelect'
import { TaxonomicFilterGroupType } from 'lib/components/TaxonomicFilter/types'
import UniversalFilters from 'lib/components/UniversalFilters/UniversalFilters'
import { universalFiltersLogic } from 'lib/components/UniversalFilters/universalFiltersLogic'
Expand Down Expand Up @@ -71,57 +72,68 @@ const RecordingsUniversalFilterGroup = (): JSX.Element => {
}

export const Options = ({ showOrder = true }: { showOrder?: boolean }): JSX.Element => {
const { dateRange } = useValues(errorTrackingLogic)
const { setDateRange } = useActions(errorTrackingLogic)
const { dateRange, assignee } = useValues(errorTrackingLogic)
const { setDateRange, setAssignee } = useActions(errorTrackingLogic)
const { order } = useValues(errorTrackingSceneLogic)
const { setOrder } = useActions(errorTrackingSceneLogic)

return (
<div className="flex gap-4 py-2">
<div className="flex items-center gap-1">
<span>Date range:</span>
<DateFilter
dateFrom={dateRange.date_from}
dateTo={dateRange.date_to}
onChange={(changedDateFrom, changedDateTo) => {
setDateRange({ date_from: changedDateFrom, date_to: changedDateTo })
}}
size="small"
/>
</div>
{showOrder && (
<div className="flex justify-between">
<div className="flex gap-4 py-2">
<div className="flex items-center gap-1">
<span>Sort by:</span>
<LemonSelect
onSelect={setOrder}
onChange={setOrder}
value={order}
options={[
{
value: 'last_seen',
label: 'Last seen',
},
{
value: 'first_seen',
label: 'First seen',
},
{
value: 'occurrences',
label: 'Occurrences',
},
{
value: 'users',
label: 'Users',
},
{
value: 'sessions',
label: 'Sessions',
},
]}
<span>Date range:</span>
<DateFilter
dateFrom={dateRange.date_from}
dateTo={dateRange.date_to}
onChange={(changedDateFrom, changedDateTo) => {
setDateRange({ date_from: changedDateFrom, date_to: changedDateTo })
}}
size="small"
/>
</div>
)}
{showOrder && (
<div className="flex items-center gap-1">
<span>Sort by:</span>
<LemonSelect
onSelect={setOrder}
onChange={setOrder}
value={order}
options={[
{
value: 'last_seen',
label: 'Last seen',
},
{
value: 'first_seen',
label: 'First seen',
},
{
value: 'occurrences',
label: 'Occurrences',
},
{
value: 'users',
label: 'Users',
},
{
value: 'sessions',
label: 'Sessions',
},
]}
size="small"
/>
</div>
)}
</div>
<div className="flex items-center gap-1">
<span>Assigned to:</span>
<MemberSelect
value={assignee}
onChange={(user) => {
setAssignee(user?.id || null)
}}
/>
</div>
</div>
)
}
Expand Down
8 changes: 8 additions & 0 deletions frontend/src/scenes/error-tracking/errorTrackingLogic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export const errorTrackingLogic = kea<errorTrackingLogicType>([

actions({
setDateRange: (dateRange: DateRange) => ({ dateRange }),
setAssignee: (assignee: number | null) => ({ assignee }),
setFilterGroup: (filterGroup: UniversalFiltersGroup) => ({ filterGroup }),
setFilterTestAccounts: (filterTestAccounts: boolean) => ({ filterTestAccounts }),
setSparklineSelectedPeriod: (period: string | null) => ({ period }),
Expand All @@ -49,6 +50,13 @@ export const errorTrackingLogic = kea<errorTrackingLogicType>([
setDateRange: (_, { dateRange }) => dateRange,
},
],
assignee: [
null as number | null,
{ persist: true },
{
setAssignee: (_, { assignee }) => assignee,
},
],
filterGroup: [
DEFAULT_FILTER_GROUP as UniversalFiltersGroup,
{ persist: true },
Expand Down
10 changes: 7 additions & 3 deletions frontend/src/scenes/error-tracking/errorTrackingSceneLogic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ export const errorTrackingSceneLogic = kea<errorTrackingSceneLogicType>([
path(['scenes', 'error-tracking', 'errorTrackingSceneLogic']),

connect({
values: [errorTrackingLogic, ['dateRange', 'filterTestAccounts', 'filterGroup', 'sparklineSelectedPeriod']],
values: [
errorTrackingLogic,
['dateRange', 'assignee', 'filterTestAccounts', 'filterGroup', 'sparklineSelectedPeriod'],
],
}),

actions({
Expand All @@ -36,11 +39,12 @@ export const errorTrackingSceneLogic = kea<errorTrackingSceneLogicType>([

selectors({
query: [
(s) => [s.order, s.dateRange, s.filterTestAccounts, s.filterGroup, s.sparklineSelectedPeriod],
(order, dateRange, filterTestAccounts, filterGroup, sparklineSelectedPeriod): DataTableNode =>
(s) => [s.order, s.dateRange, s.assignee, s.filterTestAccounts, s.filterGroup, s.sparklineSelectedPeriod],
(order, dateRange, assignee, filterTestAccounts, filterGroup, sparklineSelectedPeriod): DataTableNode =>
errorTrackingQuery({
order,
dateRange,
assignee,
filterTestAccounts,
filterGroup,
sparklineSelectedPeriod,
Expand Down
8 changes: 3 additions & 5 deletions frontend/src/scenes/error-tracking/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,19 +35,16 @@ const toStartOfIntervalFn = {
export const errorTrackingQuery = ({
order,
dateRange,
assignee,
filterTestAccounts,
filterGroup,
sparklineSelectedPeriod,
columns,
limit = 50,
}: {
order: ErrorTrackingQuery['order']
dateRange: DateRange
filterTestAccounts: boolean
}: Pick<ErrorTrackingQuery, 'order' | 'dateRange' | 'assignee' | 'filterTestAccounts' | 'limit'> & {
filterGroup: UniversalFiltersGroup
sparklineSelectedPeriod: string | null
columns?: ('error' | 'volume' | 'occurrences' | 'sessions' | 'users' | 'assignee')[]
limit?: number
}): DataTableNode => {
const select: string[] = []
if (!columns) {
Expand All @@ -69,6 +66,7 @@ export const errorTrackingQuery = ({
select: select,
order: order,
dateRange: dateRange,
assignee: assignee,
filterGroup: filterGroup as PropertyGroupFilter,
filterTestAccounts: filterTestAccounts,
limit: limit,
Expand Down
23 changes: 16 additions & 7 deletions posthog/hogql_queries/error_tracking_query_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ def fingerprint_grouping_expr(self):
ast.Call(
name="has",
args=[
self.group_fingerprints(group),
self.group_fingerprints([group]),
self.extracted_fingerprint_property(),
],
),
Expand Down Expand Up @@ -131,13 +131,19 @@ def where(self):
ast.Placeholder(chain=["filters"]),
]

groups = []

if self.query.fingerprint:
group = self.group_or_default(self.query.fingerprint)
groups.append(self.group_or_default(self.query.fingerprint))
elif self.query.assignee:
groups.extend(self.error_tracking_groups.values())

if groups:
exprs.append(
ast.Call(
name="has",
args=[
self.group_fingerprints(group),
self.group_fingerprints(groups),
self.extracted_fingerprint_property(),
],
),
Expand Down Expand Up @@ -255,10 +261,12 @@ def group_or_default(self, fingerprint):
},
)

def group_fingerprints(self, group):
exprs: list[ast.Expr] = [ast.Constant(value=group["fingerprint"])]
for fp in group["merged_fingerprints"]:
exprs.append(ast.Constant(value=fp))
def group_fingerprints(self, groups):
exprs: list[ast.Expr] = []
for group in groups:
exprs.append(ast.Constant(value=group["fingerprint"]))
for fp in group["merged_fingerprints"]:
exprs.append(ast.Constant(value=fp))
return ast.Array(exprs=exprs)

def extracted_fingerprint_property(self):
Expand All @@ -284,5 +292,6 @@ def error_tracking_groups(self):
if self.query.fingerprint
else queryset.filter(status__in=[ErrorTrackingGroup.Status.ACTIVE])
)
queryset = queryset.filter(assignee=self.query.assignee) if self.query.assignee else queryset
groups = queryset.values("fingerprint", "merged_fingerprints", "status", "assignee")
return {str(item["fingerprint"]): item for item in groups}
Original file line number Diff line number Diff line change
@@ -1,4 +1,27 @@
# serializer version: 1
# name: TestErrorTrackingQueryRunner.test_assignee_groups
'''
SELECT count() 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,
multiIf(has([['SyntaxError']], JSONExtract(ifNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$exception_fingerprint'), ''), 'null'), '^"|"$', ''), '[]'), 'Array(String)')), ['SyntaxError'], has([['custom_fingerprint']], JSONExtract(ifNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$exception_fingerprint'), ''), 'null'), '^"|"$', ''), '[]'), 'Array(String)')), ['custom_fingerprint'], JSONExtract(ifNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$exception_fingerprint'), ''), 'null'), '^"|"$', ''), '[]'), 'Array(String)')) AS fingerprint
FROM events
WHERE and(equals(events.team_id, 2), equals(events.event, '$exception'), 1, has([['SyntaxError'], ['custom_fingerprint']], JSONExtract(ifNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$exception_fingerprint'), ''), 'null'), '^"|"$', ''), '[]'), 'Array(String)')))
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
'''
# ---
# name: TestErrorTrackingQueryRunner.test_column_names
'''
SELECT count() AS occurrences,
Expand Down
30 changes: 30 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 @@ -324,3 +324,33 @@ def test_merges_and_defaults_groups(self):
},
],
)

@snapshot_clickhouse_queries
def test_assignee_groups(self):
ErrorTrackingGroup.objects.create(
team=self.team,
fingerprint=["SyntaxError"],
assignee=self.user,
)
ErrorTrackingGroup.objects.create(
team=self.team,
fingerprint=["custom_fingerprint"],
assignee=self.user,
)
ErrorTrackingGroup.objects.create(
team=self.team,
fingerprint=["TypeError"],
)

runner = ErrorTrackingQueryRunner(
team=self.team,
query=ErrorTrackingQuery(
kind="ErrorTrackingQuery",
dateRange=DateRange(),
assignee=self.user.pk,
),
)

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

self.assertEqual(sorted([x["fingerprint"] for x in results]), [["SyntaxError"], ["custom_fingerprint"]])
1 change: 1 addition & 0 deletions posthog/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -4578,6 +4578,7 @@ class ErrorTrackingQuery(BaseModel):
model_config = ConfigDict(
extra="forbid",
)
assignee: Optional[int] = None
dateRange: DateRange
eventColumns: Optional[list[str]] = None
filterGroup: Optional[PropertyGroupFilter] = None
Expand Down
Loading