diff --git a/frontend/src/scenes/data-management/events/EventDefinitionsTable.tsx b/frontend/src/scenes/data-management/events/EventDefinitionsTable.tsx
index e205b74a707..50ba8cb315a 100644
--- a/frontend/src/scenes/data-management/events/EventDefinitionsTable.tsx
+++ b/frontend/src/scenes/data-management/events/EventDefinitionsTable.tsx
@@ -64,7 +64,7 @@ export function EventDefinitionsTable(): JSX.Element {
render: function Render(_, definition: EventDefinition) {
return
},
- sorter: (a, b) => a.name?.localeCompare(b.name ?? '') ?? 0,
+ sorter: true,
},
...(hasDashboardCollaboration
? [
@@ -90,7 +90,7 @@ export function EventDefinitionsTable(): JSX.Element {
—
)
},
- sorter: (a, b) => (a?.volume_30_day ?? 0) - (b?.volume_30_day ?? 0),
+ sorter: true,
} as LemonTableColumn,
{
title: ,
@@ -103,7 +103,7 @@ export function EventDefinitionsTable(): JSX.Element {
—
)
},
- sorter: (a, b) => (a?.query_usage_30_day ?? 0) - (b?.query_usage_30_day ?? 0),
+ sorter: true,
} as LemonTableColumn,
]
: []),
@@ -198,6 +198,13 @@ export function EventDefinitionsTable(): JSX.Element {
}
: undefined,
}}
+ onSort={(newSorting) =>
+ setFilters({
+ ordering: newSorting
+ ? `${newSorting.order === -1 ? '-' : ''}${newSorting.columnKey}`
+ : undefined,
+ })
+ }
expandable={{
expandedRowRender: function RenderPropertiesTable(definition) {
return
@@ -206,6 +213,7 @@ export function EventDefinitionsTable(): JSX.Element {
noIndent: true,
}}
dataSource={eventDefinitions.results}
+ useURLForSorting={false}
emptyState="No event definitions"
nouns={['event', 'events']}
/>
diff --git a/frontend/src/scenes/data-management/events/eventDefinitionsTableLogic.ts b/frontend/src/scenes/data-management/events/eventDefinitionsTableLogic.ts
index c4713e8e624..886d64bdf28 100644
--- a/frontend/src/scenes/data-management/events/eventDefinitionsTableLogic.ts
+++ b/frontend/src/scenes/data-management/events/eventDefinitionsTableLogic.ts
@@ -25,6 +25,7 @@ export interface Filters {
event: string
properties: AnyPropertyFilter[]
event_type: EventDefinitionType
+ ordering?: string
}
function cleanFilters(filter: Partial): Filters {
@@ -32,6 +33,7 @@ function cleanFilters(filter: Partial): Filters {
event: '',
properties: [],
event_type: EventDefinitionType.Event,
+ ordering: '-volume_30_day',
...filter,
}
}
@@ -299,7 +301,7 @@ export const eventDefinitionsTableLogic = kea([
actions.loadEventDefinitions(
normalizeEventDefinitionEndpointUrl({
url: values.eventDefinitions.current,
- searchParams: { search: values.filters.event },
+ searchParams: { search: values.filters.event, ordering: values.filters.ordering },
full: true,
eventTypeFilter: values.filters.event_type,
})
diff --git a/package.json b/package.json
index 2e7fde68795..5acfb29dbd6 100644
--- a/package.json
+++ b/package.json
@@ -250,7 +250,10 @@
"lint-staged": {
"*.{js,jsx,mjs,ts,tsx,json,yaml,yml,css,scss}": "prettier --write",
"((frontend|cypress)/**).{js,jsx,mjs,ts,tsx}": "eslint -c .eslintrc.js --fix",
- "(plugin-server/**).{js,jsx,mjs,ts,tsx}": "eslint -c plugin-server/.eslintrc.js --fix",
+ "(plugin-server/**).{js,jsx,mjs,ts,tsx}": [
+ "eslint -c plugin-server/.eslintrc.js --fix",
+ "prettier --write"
+ ],
"*.{py,pyi}": [
"black",
"flake8",
diff --git a/posthog/api/event_definition.py b/posthog/api/event_definition.py
index 5914d7114e3..1442b0ec19b 100644
--- a/posthog/api/event_definition.py
+++ b/posthog/api/event_definition.py
@@ -65,7 +65,9 @@ class EventDefinitionViewSet(
permission_classes = [permissions.IsAuthenticated, OrganizationMemberPermissions, TeamMemberAccessPermission]
lookup_field = "id"
filter_backends = [TermSearchFilterBackend]
+
search_fields = ["name"]
+ ordering_fields = ["volume_30_day", "query_usage_30_day", "name"]
def get_queryset(self):
# `type` = 'all' | 'event' | 'action_event'
@@ -76,12 +78,24 @@ class EventDefinitionViewSet(
search_query, search_kwargs = term_search_filter_sql(self.search_fields, search)
params = {"team_id": self.team_id, "is_posthog_event": "$%", **search_kwargs}
+ if self.request.GET.get("ordering") and self.request.GET["ordering"].replace("-", "") in self.ordering_fields:
+ order = self.request.GET["ordering"].replace("-", "")
+ order_direction = "DESC" if "-" in self.request.GET["ordering"] else "ASC"
+ else:
+ order = "volume_30_day"
+ order_direction = "DESC"
if EE_AVAILABLE and self.request.user.organization.is_feature_available(AvailableFeature.INGESTION_TAXONOMY): # type: ignore
from ee.models.event_definition import EnterpriseEventDefinition
# Prevent fetching deprecated `tags` field. Tags are separately fetched in TaggedItemSerializerMixin
- sql = create_event_definitions_sql(event_type, is_enterprise=True, conditions=search_query)
+ sql = create_event_definitions_sql(
+ event_type,
+ is_enterprise=True,
+ conditions=search_query,
+ order_UNSAFE=order,
+ order_direction=order_direction,
+ )
ee_event_definitions = EnterpriseEventDefinition.objects.raw(sql, params=params)
ee_event_definitions_list = ee_event_definitions.prefetch_related(
@@ -90,7 +104,13 @@ class EventDefinitionViewSet(
return ee_event_definitions_list
- sql = create_event_definitions_sql(event_type, is_enterprise=False, conditions=search_query)
+ sql = create_event_definitions_sql(
+ event_type,
+ is_enterprise=False,
+ conditions=search_query,
+ order_UNSAFE=order,
+ order_direction=order_direction,
+ )
event_definitions_list = EventDefinition.objects.raw(sql, params=params)
return event_definitions_list
diff --git a/posthog/api/test/test_event_definition.py b/posthog/api/test/test_event_definition.py
index e06f32be44f..8905573fafb 100644
--- a/posthog/api/test/test_event_definition.py
+++ b/posthog/api/test/test_event_definition.py
@@ -73,6 +73,12 @@ class TestEventDefinitionAPI(APIBaseTest):
(dateutil.parser.isoparse(response_item["created_at"]) - timezone.now()).total_seconds(), 0
)
+ # Test ordering
+ response = self.client.get("/api/projects/@current/event_definitions/?ordering=volume_30_day")
+
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(response.json()["results"][0]["volume_30_day"], 1)
+
def test_pagination_of_event_definitions(self):
EventDefinition.objects.bulk_create(
[EventDefinition(team=self.demo_team, name=f"z_event_{i}") for i in range(1, 301)]
@@ -82,8 +88,8 @@ class TestEventDefinitionAPI(APIBaseTest):
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.json()["count"], 306)
self.assertEqual(len(response.json()["results"]), 100) # Default page size
- self.assertEqual(response.json()["results"][0]["name"], "$pageview") # Order by name (ascending)
- self.assertEqual(response.json()["results"][1]["name"], "entered_free_trial") # Order by name (ascending)
+ self.assertEqual(response.json()["results"][0]["name"], "$pageview") # Order by volume (desc)
+ self.assertEqual(response.json()["results"][1]["name"], "watched_movie") # Order by volume (desc)
event_checkpoints = [
184,
diff --git a/posthog/api/utils.py b/posthog/api/utils.py
index 469cf8d548b..b54540b7525 100644
--- a/posthog/api/utils.py
+++ b/posthog/api/utils.py
@@ -41,12 +41,22 @@ def get_target_entity(filter: Union[Filter, StickinessFilter]) -> Entity:
return possible_entity
possible_entity = retrieve_entity_from(
- filter.target_entity_id, filter.target_entity_type, entity_math, filter.events, filter.actions
+ filter.target_entity_id,
+ filter.target_entity_type,
+ entity_math,
+ filter.events,
+ filter.actions,
)
if possible_entity:
return possible_entity
elif filter.target_entity_type:
- return Entity({"id": filter.target_entity_id, "type": filter.target_entity_type, "math": entity_math})
+ return Entity(
+ {
+ "id": filter.target_entity_id,
+ "type": filter.target_entity_type,
+ "math": entity_math,
+ }
+ )
else:
raise ValidationError("An entity must be provided for target entity to be determined")
@@ -62,7 +72,11 @@ def entity_from_order(order: Optional[str], entities: List[Entity]) -> Optional[
def retrieve_entity_from(
- entity_id: str, entity_type: Optional[str], entity_math: MathType, events: List[Entity], actions: List[Entity]
+ entity_id: str,
+ entity_type: Optional[str],
+ entity_math: MathType,
+ events: List[Entity],
+ actions: List[Entity],
) -> Optional[Entity]:
"""
Retrieves the entity from the events and actions.
@@ -158,7 +172,11 @@ def get_data(request):
None,
cors_response(
request,
- generate_exception_response("capture", f"Malformed request data: {error}", code="invalid_payload"),
+ generate_exception_response(
+ "capture",
+ f"Malformed request data: {error}",
+ code="invalid_payload",
+ ),
),
)
@@ -222,7 +240,10 @@ def get_event_ingestion_context(
error_response = cors_response(
request,
generate_exception_response(
- "capture", "Invalid Project ID.", code="invalid_project", attr="project_id"
+ "capture",
+ "Invalid Project ID.",
+ code="invalid_project",
+ attr="project_id",
),
)
return None, db_error, error_response
@@ -272,7 +293,9 @@ def get_event_ingestion_context(
return ingestion_context, db_error, error_response
-def get_event_ingestion_context_for_token(token: str) -> Optional[EventIngestionContext]:
+def get_event_ingestion_context_for_token(
+ token: str,
+) -> Optional[EventIngestionContext]:
"""
Based on a token associated with a Team, retrieve the context that is
required to ingest events.
@@ -333,7 +356,11 @@ def safe_clickhouse_string(s: str) -> str:
def create_event_definitions_sql(
- event_type: EventDefinitionType, is_enterprise: bool = False, conditions: str = ""
+ event_type: EventDefinitionType,
+ is_enterprise: bool = False,
+ conditions: str = "",
+ order_UNSAFE: str = "",
+ order_direction: str = "DESC",
) -> str:
# Prevent fetching deprecated `tags` field. Tags are separately fetched in TaggedItemSerializerMixin
if is_enterprise:
@@ -364,11 +391,10 @@ def create_event_definitions_sql(
# Only return event definitions
raw_event_definition_fields = ",".join(event_definition_fields)
- ordering = (
- "ORDER BY last_seen_at DESC NULLS LAST, query_usage_30_day DESC NULLS LAST, name ASC"
- if is_enterprise
- else "ORDER BY name ASC"
+ provided_ordering = (
+ f"{order_UNSAFE} {order_direction} {'NULLS FIRST' if order_direction == 'ASC' else 'NULLS LAST'}"
)
+ ordering = f"ORDER BY {provided_ordering}, name ASC"
if event_type == EventDefinitionType.EVENT_CUSTOM:
shared_conditions += " AND posthog_eventdefinition.name NOT LIKE %(is_posthog_event)s"