Skip to content

Commit

Permalink
feat(product-assistant): actor's property taxonomy query (#25539)
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
skoob13 and github-actions[bot] authored Oct 14, 2024
1 parent ef1a63f commit 07218a5
Show file tree
Hide file tree
Showing 13 changed files with 651 additions and 52 deletions.
142 changes: 141 additions & 1 deletion frontend/src/queries/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,80 @@
"required": ["id", "kind"],
"type": "object"
},
"ActorsPropertyTaxonomyQuery": {
"additionalProperties": false,
"properties": {
"group_type_index": {
"type": "integer"
},
"kind": {
"const": "ActorsPropertyTaxonomyQuery",
"type": "string"
},
"modifiers": {
"$ref": "#/definitions/HogQLQueryModifiers",
"description": "Modifiers used when performing the query"
},
"property": {
"type": "string"
},
"response": {
"$ref": "#/definitions/ActorsPropertyTaxonomyQueryResponse"
}
},
"required": ["kind", "property"],
"type": "object"
},
"ActorsPropertyTaxonomyQueryResponse": {
"additionalProperties": false,
"description": "All analytics query responses must inherit from this.",
"properties": {
"error": {
"description": "Query error. Returned only if 'explain' or `modifiers.debug` is true. Throws an error otherwise.",
"type": "string"
},
"hogql": {
"description": "Generated HogQL query.",
"type": "string"
},
"modifiers": {
"$ref": "#/definitions/HogQLQueryModifiers",
"description": "Modifiers used when performing the query"
},
"query_status": {
"$ref": "#/definitions/QueryStatus",
"description": "Query status indicates whether next to the provided data, a query is still running."
},
"results": {
"$ref": "#/definitions/ActorsPropertyTaxonomyResponse"
},
"timings": {
"description": "Measured timings for different parts of the query generation process",
"items": {
"$ref": "#/definitions/QueryTiming"
},
"type": "array"
}
},
"required": ["results"],
"type": "object"
},
"ActorsPropertyTaxonomyResponse": {
"additionalProperties": false,
"properties": {
"sample_count": {
"type": "integer"
},
"sample_values": {
"items": {
"type": "string"
},
"type": "array"
}
},
"required": ["sample_values", "sample_count"],
"type": "object"
},
"ActorsQuery": {
"additionalProperties": false,
"properties": {
Expand Down Expand Up @@ -766,6 +840,71 @@
"required": ["cache_key"],
"type": "object"
},
"CachedActorsPropertyTaxonomyQueryResponse": {
"additionalProperties": false,
"properties": {
"cache_key": {
"type": "string"
},
"cache_target_age": {
"format": "date-time",
"type": "string"
},
"calculation_trigger": {
"description": "What triggered the calculation of the query, leave empty if user/immediate",
"type": "string"
},
"error": {
"description": "Query error. Returned only if 'explain' or `modifiers.debug` is true. Throws an error otherwise.",
"type": "string"
},
"hogql": {
"description": "Generated HogQL query.",
"type": "string"
},
"is_cached": {
"type": "boolean"
},
"last_refresh": {
"format": "date-time",
"type": "string"
},
"modifiers": {
"$ref": "#/definitions/HogQLQueryModifiers",
"description": "Modifiers used when performing the query"
},
"next_allowed_client_refresh": {
"format": "date-time",
"type": "string"
},
"query_status": {
"$ref": "#/definitions/QueryStatus",
"description": "Query status indicates whether next to the provided data, a query is still running."
},
"results": {
"$ref": "#/definitions/ActorsPropertyTaxonomyResponse"
},
"timezone": {
"type": "string"
},
"timings": {
"description": "Measured timings for different parts of the query generation process",
"items": {
"$ref": "#/definitions/QueryTiming"
},
"type": "array"
}
},
"required": [
"cache_key",
"is_cached",
"last_refresh",
"next_allowed_client_refresh",
"results",
"timezone"
],
"type": "object"
},
"CachedActorsQueryResponse": {
"additionalProperties": false,
"properties": {
Expand Down Expand Up @@ -7267,7 +7406,8 @@
"DatabaseSchemaQuery",
"SuggestedQuestionsQuery",
"TeamTaxonomyQuery",
"EventTaxonomyQuery"
"EventTaxonomyQuery",
"ActorsPropertyTaxonomyQuery"
],
"type": "string"
},
Expand Down
16 changes: 16 additions & 0 deletions frontend/src/queries/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ export enum NodeKind {
SuggestedQuestionsQuery = 'SuggestedQuestionsQuery',
TeamTaxonomyQuery = 'TeamTaxonomyQuery',
EventTaxonomyQuery = 'EventTaxonomyQuery',
ActorsPropertyTaxonomyQuery = 'ActorsPropertyTaxonomyQuery',
}

export type AnyDataNode =
Expand Down Expand Up @@ -2038,3 +2039,18 @@ export interface EventTaxonomyQuery extends DataNode<EventTaxonomyQueryResponse>
export type EventTaxonomyQueryResponse = AnalyticsQueryResponseBase<EventTaxonomyResponse>

export type CachedEventTaxonomyQueryResponse = CachedQueryResponse<EventTaxonomyQueryResponse>

export interface ActorsPropertyTaxonomyResponse {
sample_values: string[]
sample_count: integer
}

export interface ActorsPropertyTaxonomyQuery extends DataNode<ActorsPropertyTaxonomyQueryResponse> {
kind: NodeKind.ActorsPropertyTaxonomyQuery
property: string
group_type_index?: integer
}

export type ActorsPropertyTaxonomyQueryResponse = AnalyticsQueryResponseBase<ActorsPropertyTaxonomyResponse>

export type CachedActorsPropertyTaxonomyQueryResponse = CachedQueryResponse<ActorsPropertyTaxonomyQueryResponse>
6 changes: 6 additions & 0 deletions frontend/src/scenes/saved-insights/SavedInsights.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,12 @@ export const QUERY_TYPES_METADATA: Record<NodeKind, InsightTypeMetadata> = {
icon: IconHogQL,
inMenu: false,
},
[NodeKind.ActorsPropertyTaxonomyQuery]: {
name: 'Actor Property Taxonomy',
description: 'View the taxonomy of the actor’s property.',
icon: IconHogQL,
inMenu: false,
},
}

export const INSIGHT_TYPE_OPTIONS: LemonSelectOptions<string> = [
Expand Down
103 changes: 103 additions & 0 deletions posthog/hogql_queries/ai/actors_property_taxonomy_query_runner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
from typing import Optional

from posthog.hogql import ast
from posthog.hogql.printer import to_printed_hogql
from posthog.hogql.query import execute_hogql_query
from posthog.hogql_queries.ai.utils import TaxonomyCacheMixin
from posthog.hogql_queries.query_runner import QueryRunner
from posthog.schema import (
ActorsPropertyTaxonomyQuery,
ActorsPropertyTaxonomyQueryResponse,
CachedActorsPropertyTaxonomyQueryResponse,
)


class ActorsPropertyTaxonomyQueryRunner(TaxonomyCacheMixin, QueryRunner):
query: ActorsPropertyTaxonomyQuery
response: ActorsPropertyTaxonomyQueryResponse
cached_response: CachedActorsPropertyTaxonomyQueryResponse

def calculate(self):
query = self.to_query()
hogql = to_printed_hogql(query, self.team)

response = execute_hogql_query(
query_type="ActorsPropertyTaxonomyQuery",
query=query,
team=self.team,
timings=self.timings,
modifiers=self.modifiers,
limit_context=self.limit_context,
)

results = (
{
"sample_values": response.results[0][0],
"sample_count": response.results[0][1],
}
if response.results
else {
"sample_values": [],
"sample_count": 0,
}
)

return ActorsPropertyTaxonomyQueryResponse(
results=results,
timings=response.timings,
hogql=hogql,
modifiers=self.modifiers,
)

def to_query(self) -> ast.SelectQuery | ast.SelectUnionQuery:
query = ast.SelectQuery(
select=[
ast.Call(name="groupArray", args=[ast.Field(chain=["prop"])], params=[ast.Constant(value=5)]),
ast.Call(name="count", args=[]),
],
select_from=ast.JoinExpr(table=self._get_subquery()),
)

return query

@property
def _actor_type(self) -> str:
if self.query.group_type_index is not None:
return "group"
return "person"

@property
def _origin(self) -> str:
if self._actor_type == "person":
return "persons"
return "groups"

def _subquery_filter(self) -> Optional[ast.Expr]:
field_filter = ast.Call(
name="isNotNull",
args=[ast.Field(chain=["prop"])],
)

if self._actor_type == "group":
return ast.And(
exprs=[
field_filter,
ast.CompareOperation(
left=ast.Field(chain=["index"]),
op=ast.CompareOperationOp.Eq,
right=ast.Constant(value=self.query.group_type_index),
),
]
)

return field_filter

def _get_subquery(self) -> ast.SelectQuery:
query = ast.SelectQuery(
select=[ast.Alias(expr=ast.Field(chain=["properties", self.query.property]), alias="prop")],
distinct=True,
select_from=ast.JoinExpr(table=ast.Field(chain=[self._origin])),
where=self._subquery_filter(),
order_by=[ast.OrderExpr(expr=ast.Field(chain=["created_at"]), order="DESC")],
)
return query
16 changes: 3 additions & 13 deletions posthog/hogql_queries/ai/event_taxonomy_query_runner.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
from datetime import datetime
from typing import Optional, cast
from typing import cast

from posthog.caching.utils import ThresholdMode, is_stale
from posthog.hogql import ast
from posthog.hogql.parser import parse_expr, parse_select
from posthog.hogql.printer import to_printed_hogql
from posthog.hogql.query import execute_hogql_query
from posthog.hogql_queries.ai.utils import TaxonomyCacheMixin
from posthog.hogql_queries.query_runner import QueryRunner
from posthog.schema import (
CachedEventTaxonomyQueryResponse,
Expand All @@ -15,7 +14,7 @@
)


class EventTaxonomyQueryRunner(QueryRunner):
class EventTaxonomyQueryRunner(TaxonomyCacheMixin, QueryRunner):
query: EventTaxonomyQuery
response: EventTaxonomyQueryResponse
cached_response: CachedEventTaxonomyQueryResponse
Expand Down Expand Up @@ -69,15 +68,6 @@ def to_query(self) -> ast.SelectQuery | ast.SelectUnionQuery:

return query

def _is_stale(self, last_refresh: Optional[datetime], lazy: bool = False) -> bool:
"""
Despite the lazy mode, it caches for an hour by default. We don't want frequent updates here.
"""
return is_stale(self.team, date_to=None, interval=None, last_refresh=last_refresh, mode=ThresholdMode.AI)

def cache_target_age(self, last_refresh: Optional[datetime], lazy: bool = False) -> Optional[datetime]:
return None

def _get_omit_filter(self):
"""
Ignore properties that are not useful for AI.
Expand Down
16 changes: 2 additions & 14 deletions posthog/hogql_queries/ai/team_taxonomy_query_runner.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
from datetime import datetime
from typing import Optional

from posthog.caching.utils import ThresholdMode, is_stale
from posthog.hogql import ast
from posthog.hogql.parser import parse_select
from posthog.hogql.printer import to_printed_hogql
from posthog.hogql.query import execute_hogql_query
from posthog.hogql_queries.ai.utils import TaxonomyCacheMixin
from posthog.hogql_queries.query_runner import QueryRunner
from posthog.schema import (
CachedTeamTaxonomyQueryResponse,
Expand All @@ -15,7 +12,7 @@
)


class TeamTaxonomyQueryRunner(QueryRunner):
class TeamTaxonomyQueryRunner(TaxonomyCacheMixin, QueryRunner):
"""
Calculates the top events for a team sorted by count. The EventDefinition model doesn't store the count of events,
so this query mitigates that.
Expand Down Expand Up @@ -63,12 +60,3 @@ def to_query(self) -> ast.SelectQuery | ast.SelectUnionQuery:
)

return query

def _is_stale(self, last_refresh: Optional[datetime], lazy: bool = False) -> bool:
"""
Despite the lazy mode, it caches for an hour by default. We don't want frequent updates here.
"""
return is_stale(self.team, date_to=None, interval=None, last_refresh=last_refresh, mode=ThresholdMode.AI)

def cache_target_age(self, last_refresh: Optional[datetime], lazy: bool = False) -> Optional[datetime]:
return None
Loading

0 comments on commit 07218a5

Please sign in to comment.