Skip to content

Commit

Permalink
feat(insight-compare): compare with hogql results (#18564)
Browse files Browse the repository at this point in the history
  • Loading branch information
mariusandra authored Nov 13, 2023
1 parent a0241ee commit f7438a6
Show file tree
Hide file tree
Showing 6 changed files with 174 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.
1 change: 1 addition & 0 deletions frontend/src/lib/constants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ export const FEATURE_FLAGS = {
APPS_AND_EXPORTS_UI: 'apps-and-exports-ui', // owner: @benjackwhite
SESSION_REPLAY_CORS_PROXY: 'session-replay-cors-proxy', // owner: #team-monitoring
HOGQL_INSIGHTS: 'hogql-insights', // owner: @mariusandra
HOGQL_INSIGHT_LIVE_COMPARE: 'hogql-insight-live-compare', // owner: @mariusandra
WEBHOOKS_DENYLIST: 'webhooks-denylist', // owner: #team-pipeline
SURVEYS_MULTIPLE_QUESTIONS: 'surveys-multiple-questions', // owner: @liyiy
SURVEYS_RESULTS_VISUALIZATIONS: 'surveys-results-visualizations', // owner: @jurajmajerik
Expand Down
24 changes: 24 additions & 0 deletions frontend/src/lib/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1827,3 +1827,27 @@ export function shouldCancelQuery(error: any): boolean {
// the query will continue running in ClickHouse
return error.name === 'AbortError' || error.message?.name === 'AbortError' || error.status === 504
}

export function flattenObject(ob: Record<string, any>): Record<string, any> {
const toReturn = {}

for (const i in ob) {
if (!ob.hasOwnProperty(i)) {
continue
}

if (typeof ob[i] == 'object') {
const flatObject = flattenObject(ob[i])
for (const x in flatObject) {
if (!flatObject.hasOwnProperty(x)) {
continue
}

toReturn[i + '.' + x] = flatObject[x]
}
} else {
toReturn[i] = ob[i]
}
}
return toReturn
}
109 changes: 93 additions & 16 deletions frontend/src/queries/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
isInsightVizNode,
isQueryWithHogQLSupport,
isPersonsQuery,
isLifecycleQuery,
} from './utils'
import api, { ApiMethodOptions } from 'lib/api'
import { getCurrentTeamId } from 'lib/utils/logics'
Expand All @@ -25,7 +26,7 @@ import {
isStickinessFilter,
isTrendsFilter,
} from 'scenes/insights/sharedUtils'
import { toParams } from 'lib/utils'
import { flattenObject, toParams } from 'lib/utils'
import { queryNodeToFilter } from './nodes/InsightQuery/utils/queryNodeToFilter'
import { now } from 'lib/dayjs'
import { currentSessionId } from 'lib/internalMetrics'
Expand Down Expand Up @@ -108,25 +109,34 @@ export async function query<N extends DataNode = DataNode>(
const hogQLInsightsFlagEnabled = Boolean(
featureFlagLogic.findMounted()?.values.featureFlags?.[FEATURE_FLAGS.HOGQL_INSIGHTS]
)
const hogQLInsightsLiveCompareEnabled = Boolean(
featureFlagLogic.findMounted()?.values.featureFlags?.[FEATURE_FLAGS.HOGQL_INSIGHT_LIVE_COMPARE]
)

async function fetchLegacyInsights(): Promise<Record<string, any>> {
if (!isInsightQueryNode(queryNode)) {
throw new Error('fetchLegacyInsights called with non-insight query. Should be unreachable.')
}
const filters = queryNodeToFilter(queryNode)
const params = {
...filters,
...(refresh ? { refresh: true } : {}),
client_query_id: queryId,
session_id: currentSessionId(),
}
const [resp] = await legacyInsightQuery({
filters: params,
currentTeamId: getCurrentTeamId(),
methodOptions,
refresh,
})
response = await resp.json()
return response
}

try {
if (isPersonsNode(queryNode)) {
response = await api.get(getPersonsEndpoint(queryNode), methodOptions)
} else if (isInsightQueryNode(queryNode) && !(hogQLInsightsFlagEnabled && isQueryWithHogQLSupport(queryNode))) {
const filters = queryNodeToFilter(queryNode)
const params = {
...filters,
...(refresh ? { refresh: true } : {}),
client_query_id: queryId,
session_id: currentSessionId(),
}
const [resp] = await legacyInsightQuery({
filters: params,
currentTeamId: getCurrentTeamId(),
methodOptions,
refresh,
})
response = await resp.json()
} else if (isTimeToSeeDataQuery(queryNode)) {
response = await api.query(
{
Expand All @@ -138,6 +148,73 @@ export async function query<N extends DataNode = DataNode>(
},
methodOptions
)
} else if (isInsightQueryNode(queryNode)) {
if (hogQLInsightsFlagEnabled && isQueryWithHogQLSupport(queryNode)) {
if (hogQLInsightsLiveCompareEnabled) {
let legacyResponse
;[response, legacyResponse] = await Promise.all([
api.query(queryNode, methodOptions, queryId, refresh),
fetchLegacyInsights(),
])

const res1 = response?.result || response?.results
const res2 = legacyResponse?.result || legacyResponse?.results

if (isLifecycleQuery(queryNode)) {
// Results don't come back in a predetermined order for the legacy lifecycle insight
const order = { new: 1, returning: 2, resurrecting: 3, dormant: 4 }
res1.sort((a: any, b: any) => order[a.status] - order[b.status])
res2.sort((a: any, b: any) => order[a.status] - order[b.status])
}

const results = flattenObject(res1)
const legacyResults = flattenObject(res2)
const sortedKeys = Array.from(new Set([...Object.keys(results), ...Object.keys(legacyResults)]))
.filter((key) => !key.includes('.persons_urls.'))
.sort()
const tableData = [['', 'key', 'HOGQL', 'LEGACY']]
let matchCount = 0
let mismatchCount = 0
for (const key of sortedKeys) {
if (results[key] === legacyResults[key]) {
matchCount++
} else {
mismatchCount++
}
tableData.push([
results[key] === legacyResults[key] ? '✅' : '🚨',
key,
results[key],
legacyResults[key],
])
}
const symbols = mismatchCount === 0 ? '🍀🍀🍀' : '🏎️🏎️🏎'
// eslint-disable-next-line no-console
console.log(`${symbols} Insight Race ${symbols}`, {
query: queryNode,
duration: performance.now() - startTime,
hogqlResults: results,
legacyResults: legacyResults,
equal: mismatchCount === 0,
response,
legacyResponse,
})
// eslint-disable-next-line no-console
console.groupCollapsed(
`Results: ${mismatchCount === 0 ? '✅✅✅' : '✅'} ${matchCount}${
mismatchCount > 0 ? ` 🚨🚨🚨${mismatchCount}` : ''
}`
)
// eslint-disable-next-line no-console
console.table(tableData)
// eslint-disable-next-line no-console
console.groupEnd()
} else {
response = await api.query(queryNode, methodOptions, queryId, refresh)
}
} else {
response = await fetchLegacyInsights()
}
} else {
response = await api.query(queryNode, methodOptions, queryId, refresh)
if (isHogQLQuery(queryNode) && response && typeof response === 'object') {
Expand Down
25 changes: 24 additions & 1 deletion posthog/hogql_queries/insights/lifecycle_query_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,10 +147,33 @@ def calculate(self):
for item in val[0]
]

label = "{} - {}".format("", val[2]) # entity.name
# legacy response compatibility object
action_object = {}
label = "{} - {}".format("", val[2])
if isinstance(self.query.series[0], ActionsNode):
action = Action.objects.get(pk=int(self.query.series[0].id), team=self.team)
label = "{} - {}".format(action.name, val[2])
action_object = {
"id": str(action.pk),
"name": action.name,
"type": "actions",
"order": 0,
"math": "total",
}
elif isinstance(self.query.series[0], EventsNode):
label = "{} - {}".format(self.query.series[0].event, val[2])
action_object = {
"id": self.query.series[0].event,
"name": self.query.series[0].event,
"type": "events",
"order": 0,
"math": "total",
}

additional_values = {"label": label, "status": val[2]}
res.append(
{
"action": action_object,
"data": [float(c) for c in counts],
"count": float(sum(counts)),
"labels": labels,
Expand Down
36 changes: 32 additions & 4 deletions posthog/hogql_queries/insights/test/test_lifecycle_query_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ def test_lifecycle_query_whole_range(self):
"2020-01-18",
"2020-01-19",
],
"label": " - new",
"label": "$pageview - new",
"labels": [
"9-Jan-2020",
"10-Jan-2020",
Expand All @@ -161,6 +161,13 @@ def test_lifecycle_query_whole_range(self):
"19-Jan-2020",
],
"status": "new",
"action": {
"id": "$pageview",
"math": "total",
"name": "$pageview",
"order": 0,
"type": "events",
},
},
{
"count": 2.0,
Expand Down Expand Up @@ -190,7 +197,7 @@ def test_lifecycle_query_whole_range(self):
"2020-01-18",
"2020-01-19",
],
"label": " - returning",
"label": "$pageview - returning",
"labels": [
"9-Jan-2020",
"10-Jan-2020",
Expand All @@ -205,6 +212,13 @@ def test_lifecycle_query_whole_range(self):
"19-Jan-2020",
],
"status": "returning",
"action": {
"id": "$pageview",
"math": "total",
"name": "$pageview",
"order": 0,
"type": "events",
},
},
{
"count": 4.0,
Expand Down Expand Up @@ -234,7 +248,7 @@ def test_lifecycle_query_whole_range(self):
"2020-01-18",
"2020-01-19",
],
"label": " - resurrecting",
"label": "$pageview - resurrecting",
"labels": [
"9-Jan-2020",
"10-Jan-2020",
Expand All @@ -249,6 +263,13 @@ def test_lifecycle_query_whole_range(self):
"19-Jan-2020",
],
"status": "resurrecting",
"action": {
"id": "$pageview",
"math": "total",
"name": "$pageview",
"order": 0,
"type": "events",
},
},
{
"count": -7.0,
Expand Down Expand Up @@ -278,7 +299,7 @@ def test_lifecycle_query_whole_range(self):
"2020-01-18",
"2020-01-19",
],
"label": " - dormant",
"label": "$pageview - dormant",
"labels": [
"9-Jan-2020",
"10-Jan-2020",
Expand All @@ -293,6 +314,13 @@ def test_lifecycle_query_whole_range(self):
"19-Jan-2020",
],
"status": "dormant",
"action": {
"id": "$pageview",
"math": "total",
"name": "$pageview",
"order": 0,
"type": "events",
},
},
],
response.results,
Expand Down

0 comments on commit f7438a6

Please sign in to comment.