Skip to content

Commit

Permalink
Merge branch 'chore/optimize-new-flags-service' of github.com:PostHog…
Browse files Browse the repository at this point in the history
…/posthog into chore/optimize-new-flags-service
  • Loading branch information
dmarticus committed Nov 27, 2024
2 parents e68cf70 + 52a6761 commit 1106824
Show file tree
Hide file tree
Showing 28 changed files with 384 additions and 78 deletions.
2 changes: 1 addition & 1 deletion docker/clickhouse/users.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<max_memory_usage>10000000000</max_memory_usage>

<!-- Disable experimental analyzer -->
<!-- <allow_experimental_analyzer>0</allow_experimental_analyzer> -->
<allow_experimental_analyzer>0</allow_experimental_analyzer>

<!-- How to choose between replicas during distributed query processing.
random - choose random replica from set of replicas with minimum number of errors
Expand Down
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.
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.
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.
7 changes: 7 additions & 0 deletions frontend/src/lib/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,14 @@ export const cohortOperatorMap: Record<string, string> = {
not_in: 'user not in',
}

export const stickinessOperatorMap: Record<string, string> = {
exact: 'Exactly',
gte: 'At least',
lte: 'At most (but at least once)',
}

export const allOperatorsMapping: Record<string, string> = {
...stickinessOperatorMap,
...dateTimeOperatorMap,
...stringOperatorMap,
...numericOperatorMap,
Expand Down
27 changes: 27 additions & 0 deletions frontend/src/queries/nodes/InsightViz/EditorFilters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { insightLogic } from 'scenes/insights/insightLogic'
import { insightVizDataLogic } from 'scenes/insights/insightVizDataLogic'
import { userLogic } from 'scenes/userLogic'

import { StickinessCriteria } from '~/queries/nodes/InsightViz/StickinessCriteria'
import { InsightQueryNode } from '~/queries/schema'
import {
AvailableFeature,
Expand Down Expand Up @@ -56,6 +57,7 @@ export function EditorFilters({ query, showing, embedded }: EditorFiltersProps):
isRetention,
isPaths,
isLifecycle,
isStickiness,
isTrendsLike,
display,
breakdownFilter,
Expand Down Expand Up @@ -163,6 +165,31 @@ export function EditorFilters({ query, showing, embedded }: EditorFiltersProps):
component: LifecycleToggles as (props: EditorFilterProps) => JSX.Element | null,
}
: null,
isStickiness
? {
key: 'stickinessCriteria',
label: () => (
<div className="flex">
<span>Stickiness Criteria</span>
<Tooltip
closeDelayMs={200}
title={
<div className="space-y-2">
<div>
The stickiness criteria defines how many times a user must perform an
event inside of a given interval in order to be considered "sticky."
</div>
</div>
}
>
<IconInfo className="text-xl text-muted-alt shrink-0 ml-1" />
</Tooltip>
</div>
),
position: 'right',
component: StickinessCriteria as (props: EditorFilterProps) => JSX.Element | null,
}
: null,
{
key: 'properties',
label: 'Filters',
Expand Down
49 changes: 49 additions & 0 deletions frontend/src/queries/nodes/InsightViz/StickinessCriteria.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { LemonInput } from '@posthog/lemon-ui'
import { useActions, useValues } from 'kea'
import { OperatorSelect } from 'lib/components/PropertyFilters/components/OperatorValueSelect'
import { insightVizDataLogic } from 'scenes/insights/insightVizDataLogic'

import { StickinessOperator } from '~/queries/schema'
import { EditorFilterProps, PropertyOperator } from '~/types'

export function StickinessCriteria({ insightProps }: EditorFilterProps): JSX.Element {
const { stickinessFilter } = useValues(insightVizDataLogic(insightProps))
const { updateInsightFilter } = useActions(insightVizDataLogic(insightProps))

const stickinessCriteria = stickinessFilter?.stickinessCriteria
const currentOperator = stickinessCriteria?.operator ?? PropertyOperator.GreaterThanOrEqual
const currentValue = stickinessCriteria?.value ?? 1

const operators: StickinessOperator[] = [
PropertyOperator.LessThanOrEqual,
PropertyOperator.GreaterThanOrEqual,
PropertyOperator.Exact,
]

return (
<div className="flex items-center gap-2">
<OperatorSelect
className="flex-1"
operator={currentOperator}
operators={operators}
onChange={(newOperator: PropertyOperator) => {
updateInsightFilter({
stickinessCriteria: { operator: newOperator as StickinessOperator, value: currentValue },
})
}}
/>
<LemonInput
type="number"
className="ml-2 w-20"
defaultValue={currentValue}
min={1}
onChange={(newValue: number | undefined) => {
if (newValue !== undefined) {
updateInsightFilter({ stickinessCriteria: { operator: currentOperator, value: newValue } })
}
}}
/>
time(s) per interval
</div>
)
}
17 changes: 17 additions & 0 deletions frontend/src/queries/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -12226,6 +12226,19 @@
},
"showValuesOnSeries": {
"type": "boolean"
},
"stickinessCriteria": {
"additionalProperties": false,
"properties": {
"operator": {
"$ref": "#/definitions/StickinessOperator"
},
"value": {
"type": "integer"
}
},
"required": ["operator", "value"],
"type": "object"
}
},
"type": "object"
Expand Down Expand Up @@ -12265,6 +12278,10 @@
},
"type": "object"
},
"StickinessOperator": {
"enum": ["gte", "lte", "exact"],
"type": "string"
},
"StickinessQuery": {
"additionalProperties": false,
"properties": {
Expand Down
9 changes: 9 additions & 0 deletions frontend/src/queries/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1500,11 +1500,20 @@ export interface PathsQuery extends InsightsQueryBase<PathsQueryResponse> {
/** `StickinessFilterType` minus everything inherited from `FilterType` and persons modal related params */
export type StickinessFilterLegacy = Omit<StickinessFilterType, keyof FilterType | 'stickiness_days' | 'shown_as'>

export type StickinessOperator =
| PropertyOperator.GreaterThanOrEqual
| PropertyOperator.LessThanOrEqual
| PropertyOperator.Exact

export type StickinessFilter = {
display?: StickinessFilterLegacy['display']
showLegend?: StickinessFilterLegacy['show_legend']
showValuesOnSeries?: StickinessFilterLegacy['show_values_on_series']
hiddenLegendIndexes?: integer[]
stickinessCriteria?: {
operator: StickinessOperator
value: integer
}
}

export const STICKINESS_FILTER_PROPERTIES = new Set<keyof StickinessFilter>([
Expand Down
92 changes: 56 additions & 36 deletions posthog/hogql_queries/insights/stickiness_query_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from posthog.hogql_queries.utils.query_previous_period_date_range import QueryPreviousPeriodDateRange
from posthog.models import Team
from posthog.models.action.action import Action
from posthog.models.cohort.util import get_count_operator, get_count_operator_ast
from posthog.models.filters.mixins.utils import cached_property
from posthog.schema import (
ActionsNode,
Expand Down Expand Up @@ -87,46 +88,63 @@ def _aggregation_expressions(self, series: EventsNode | ActionsNode | DataWareho

return ast.Field(chain=["e", "person_id"])

def _having_clause(self) -> ast.Expr:
if not (self.query.stickinessFilter and self.query.stickinessFilter.stickinessCriteria):
return parse_expr("count() > 0")
operator = self.query.stickinessFilter.stickinessCriteria.operator
value = ast.Constant(value=self.query.stickinessFilter.stickinessCriteria.value)
return parse_expr(f"""count() {get_count_operator(operator)} {{value}}""", {"value": value})

def _events_query(self, series_with_extra: SeriesWithExtras) -> ast.SelectQuery:
num_intervals_column_expr = ast.Alias(
alias="num_intervals",
expr=ast.Call(
distinct=True,
name="count",
args=[self.query_date_range.date_to_start_of_interval_hogql(ast.Field(chain=["e", "timestamp"]))],
),
inner_query = parse_select(
"""
SELECT
{aggregation} as aggregation_target,
{start_of_interval} as start_of_interval,
FROM events e
SAMPLE {sample}
WHERE {where_clause}
GROUP BY aggregation_target, start_of_interval
HAVING {having_clause}
""",
{
"aggregation": self._aggregation_expressions(series_with_extra.series),
"start_of_interval": self.query_date_range.date_to_start_of_interval_hogql(
ast.Field(chain=["e", "timestamp"])
),
"sample": self._sample_value(),
"where_clause": self.where_clause(series_with_extra),
"having_clause": self._having_clause(),
},
)

aggregation = ast.Alias(
alias="aggregation_target", expr=self._aggregation_expressions(series_with_extra.series)
middle_query = parse_select(
"""
SELECT
aggregation_target,
count() as num_intervals
FROM
{inner_query}
GROUP BY
aggregation_target
""",
{"inner_query": inner_query},
)

select_query = parse_select(
outer_query = parse_select(
"""
SELECT
count(DISTINCT aggregation_target),
num_intervals
FROM (
SELECT {aggregation}, {num_intervals_column_expr}
FROM events e
SAMPLE {sample}
WHERE {where_clause}
GROUP BY aggregation_target
)
WHERE num_intervals <= {num_intervals}
GROUP BY num_intervals
ORDER BY num_intervals
SELECT
count(DISTINCT aggregation_target) as num_actors,
num_intervals
FROM
{middle_query}
GROUP BY num_intervals
ORDER BY num_intervals
""",
placeholders={
"where_clause": self.where_clause(series_with_extra),
"num_intervals": ast.Constant(value=self.intervals_num()),
"sample": self._sample_value(),
"num_intervals_column_expr": num_intervals_column_expr,
"aggregation": aggregation,
},
{"middle_query": middle_query},
)

return cast(ast.SelectQuery, select_query)
return cast(ast.SelectQuery, outer_query)

def to_query(self) -> ast.SelectSetQuery:
return ast.SelectSetQuery.create_from_queries(self.to_queries(), "UNION ALL")
Expand All @@ -145,12 +163,12 @@ def to_queries(self) -> list[ast.SelectQuery]:
select_query = parse_select(
"""
SELECT
groupArray(aggregation_target) as counts,
groupArray(num_actors) as counts,
groupArray(num_intervals) as intervals
FROM (
SELECT sum(aggregation_target) as aggregation_target, num_intervals
SELECT sum(num_actors) as num_actors, num_intervals
FROM (
SELECT 0 as aggregation_target, (number + 1) as num_intervals
SELECT 0 as num_actors, (number + 1) as num_intervals
FROM numbers(dateDiff({interval}, {date_from_start_of_interval}, {date_to_start_of_interval} + {interval_addition}))
UNION ALL
{events_query}
Expand All @@ -170,7 +188,9 @@ def to_queries(self) -> list[ast.SelectQuery]:

return queries

def to_actors_query(self, interval_num: Optional[int] = None) -> ast.SelectQuery | ast.SelectSetQuery:
def to_actors_query(
self, interval_num: Optional[int] = None, operator: Optional[str] = None
) -> ast.SelectQuery | ast.SelectSetQuery:
queries: list[ast.SelectQuery] = []

for series in self.series:
Expand All @@ -188,7 +208,7 @@ def to_actors_query(self, interval_num: Optional[int] = None) -> ast.SelectQuery
if interval_num is not None:
events_query.where = ast.CompareOperation(
left=ast.Field(chain=["num_intervals"]),
op=ast.CompareOperationOp.Eq,
op=ast.CompareOperationOp.Eq if operator is None else get_count_operator_ast(operator),
right=ast.Constant(value=interval_num),
)

Expand Down
Loading

0 comments on commit 1106824

Please sign in to comment.