Skip to content

Commit

Permalink
feat(hogql): Add actor's event count to actor modal (#19926)
Browse files Browse the repository at this point in the history
  • Loading branch information
webjunkie authored Jan 25, 2024
1 parent 949916a commit a9ea061
Show file tree
Hide file tree
Showing 6 changed files with 76 additions and 43 deletions.
6 changes: 5 additions & 1 deletion frontend/src/scenes/paths/PathNodeCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,15 @@ export function PathNodeCard({ insightProps, node }: PathNodeCardProps): JSX.Ele
return null
}

// Attention: targetLinks are the incoming links, sourceLinks are the outgoing links
const isPathStart = node.targetLinks.length === 0
const isPathEnd = node.sourceLinks.length === 0
const continuingCount = node.sourceLinks.reduce((prev, curr) => prev + curr.value, 0)
const dropOffCount = node.value - continuingCount
const averageConversionTime = !isPathStart ? node.targetLinks[0].average_conversion_time / 1000 : null
const averageConversionTime = !isPathStart
? node.targetLinks.reduce((prev, curr) => prev + curr.average_conversion_time / 1000, 0) /
node.targetLinks.length
: null

return (
<Tooltip title={pageUrl(node)} placement="right">
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/scenes/paths/pathsDataLogic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,9 @@ export const pathsDataLogic = kea<pathsDataLogicType>([
label: path_dropoff_key || path_start_key || path_end_key || 'Pageview',
isDropOff: Boolean(path_dropoff_key),
}),
additionalFields: {
value_at_data_point: 'event_count',
},
})
} else if (personsUrl) {
openPersonsModal({
Expand Down
13 changes: 6 additions & 7 deletions frontend/src/scenes/trends/persons-modal/PersonsModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import { sessionPlayerModalLogic } from 'scenes/session-recordings/player/modal/
import { teamLogic } from 'scenes/teamLogic'

import { Noun } from '~/models/groupsModel'
import { InsightActorsQuery } from '~/queries/schema'
import {
ActorType,
ExporterFormat,
Expand All @@ -35,13 +34,11 @@ import {
SessionRecordingType,
} from '~/types'

import { personsModalLogic } from './personsModalLogic'
import { PersonModalLogicProps, personsModalLogic } from './personsModalLogic'
import { SaveCohortModal } from './SaveCohortModal'

export interface PersonsModalProps extends Pick<LemonModalProps, 'inline'> {
export interface PersonsModalProps extends PersonModalLogicProps, Pick<LemonModalProps, 'inline'> {
onAfterClose?: () => void
query?: InsightActorsQuery | null
url?: string | null
urlsIndex?: number
urls?: {
label: string | JSX.Element
Expand All @@ -58,13 +55,15 @@ export function PersonsModal({
title,
onAfterClose,
inline,
additionalFields,
}: PersonsModalProps): JSX.Element {
const [selectedUrlIndex, setSelectedUrlIndex] = useState(urlsIndex || 0)
const originalUrl = (urls || [])[selectedUrlIndex]?.value || _url || ''

const logic = personsModalLogic({
url: originalUrl,
query: _query,
additionalFields,
})

const {
Expand All @@ -80,7 +79,7 @@ export function PersonsModal({
missingActorsCount,
propertiesTimelineFilterFromUrl,
exploreUrl,
ActorsQuery,
actorsQuery,
} = useValues(logic)
const { updateActorsQuery, setSearchTerm, saveAsCohort, setIsCohortModalOpen, closeModal, loadNextActors } =
useActions(logic)
Expand Down Expand Up @@ -221,7 +220,7 @@ export function PersonsModal({
void triggerExport({
export_format: ExporterFormat.CSV,
export_context: query
? { source: ActorsQuery as Record<string, any> }
? { source: actorsQuery as Record<string, any> }
: { path: originalUrl },
})
}}
Expand Down
77 changes: 51 additions & 26 deletions frontend/src/scenes/trends/persons-modal/personsModalLogic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
ActorType,
BreakdownType,
ChartDisplayType,
CommonActorType,
IntervalType,
PersonActorType,
PropertiesTimelineFilterType,
Expand All @@ -35,6 +36,7 @@ const RESULTS_PER_PAGE = 100
export interface PersonModalLogicProps {
query?: InsightActorsQuery | null
url?: string | null
additionalFields?: Partial<Record<keyof CommonActorType, string>>
}

export interface ListActorsResponse {
Expand All @@ -61,16 +63,19 @@ export const personsModalLogic = kea<personsModalLogicType>([
query,
clear,
offset,
additionalFields,
}: {
url?: string | null
query?: InsightActorsQuery | null
clear?: boolean
offset?: number
additionalFields?: PersonModalLogicProps['additionalFields']
}) => ({
url,
query,
clear,
offset,
additionalFields,
}),
loadNextActors: true,
updateActorsQuery: (query: Partial<InsightActorsQuery>) => ({ query }),
Expand All @@ -85,7 +90,7 @@ export const personsModalLogic = kea<personsModalLogicType>([
actorsResponse: [
null as ListActorsResponse | null,
{
loadActors: async ({ url, query, clear, offset }, breakpoint) => {
loadActors: async ({ url, query, clear, offset, additionalFields }, breakpoint) => {
if (url) {
url += '&include_recordings=true'

Expand All @@ -102,28 +107,41 @@ export const personsModalLogic = kea<personsModalLogicType>([
return res
} else if (query) {
const response = await performQuery({
...values.ActorsQuery,
...values.actorsQuery,
limit: RESULTS_PER_PAGE + 1,
offset: offset || 0,
} as ActorsQuery)
breakpoint()

const assembledSelectFields = values.selectFields
const additionalFieldIndices = Object.values(additionalFields || {}).map((field) =>
assembledSelectFields.indexOf(field)
)
const newResponse: ListActorsResponse = {
results: [
{
count: response.results.length,
people: response.results.slice(0, RESULTS_PER_PAGE).map(
(result): PersonActorType => ({
type: 'person',
id: result[0].id,
uuid: result[0].id,
distinct_ids: result[0].distinct_ids,
is_identified: result[0].is_identified,
properties: result[0].properties,
created_at: result[0].created_at,
matched_recordings: [],
value_at_data_point: null,
})
),
people: response.results
.slice(0, RESULTS_PER_PAGE)
.map((result): PersonActorType => {
const person: PersonActorType = {
type: 'person',
id: result[0].id,
uuid: result[0].id,
distinct_ids: result[0].distinct_ids,
is_identified: result[0].is_identified,
properties: result[0].properties,
created_at: result[0].created_at,
matched_recordings: [],
value_at_data_point: null,
}

Object.keys(additionalFields || {}).forEach((field, index) => {
person[field] = result[additionalFieldIndices[index]]
})

return person
}),
},
],
}
Expand Down Expand Up @@ -213,8 +231,8 @@ export const personsModalLogic = kea<personsModalLogicType>([
is_static: true,
name: cohortName,
}
if (values.ActorsQuery) {
const cohort = await api.create('api/cohort', { ...cohortParams, query: values.ActorsQuery })
if (values.actorsQuery) {
const cohort = await api.create('api/cohort', { ...cohortParams, query: values.actorsQuery })
cohortsModel.actions.cohortCreated(cohort)
lemonToast.success('Cohort saved', {
toastId: `cohort-saved-${cohort.id}`,
Expand Down Expand Up @@ -299,28 +317,35 @@ export const personsModalLogic = kea<personsModalLogicType>([
return cleanFilters(filter)
},
],
ActorsQuery: [
(s) => [s.query, s.searchTerm],
(query, searchTerm): ActorsQuery | null => {
selectFields: [
() => [(_, p) => p.additionalFields],
(additionalFields: PersonModalLogicProps['additionalFields']): string[] => {
const extra = Object.values(additionalFields || {})
return ['person', 'created_at', ...extra]
},
],
actorsQuery: [
(s) => [(_, p) => p.query, s.searchTerm, s.selectFields],
(query, searchTerm, selectFields): ActorsQuery | null => {
if (!query) {
return null
}
return {
kind: NodeKind.ActorsQuery,
source: query,
select: ['person', 'created_at'],
select: selectFields,
orderBy: ['created_at DESC'],
search: searchTerm,
}
},
],
exploreUrl: [
(s) => [s.ActorsQuery],
(ActorsQuery): string | null => {
if (!ActorsQuery) {
(s) => [s.actorsQuery],
(actorsQuery): string | null => {
if (!actorsQuery) {
return null
}
const { select: _select, ...source } = ActorsQuery
const { select: _select, ...source } = actorsQuery
const query: DataTableNode = {
kind: NodeKind.DataTableNode,
source,
Expand All @@ -332,7 +357,7 @@ export const personsModalLogic = kea<personsModalLogicType>([
}),

afterMount(({ actions, props }) => {
actions.loadActors({ query: props.query, url: props.url })
actions.loadActors({ query: props.query, url: props.url, additionalFields: props.additionalFields })

actions.reportPersonsModalViewed({
url: props.url,
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -891,7 +891,7 @@ export interface MatchedRecording {
events: MatchedRecordingEvent[]
}

interface CommonActorType {
export interface CommonActorType {
id: string | number
properties: Record<string, any>
/** @format date-time */
Expand Down
18 changes: 10 additions & 8 deletions posthog/hogql_queries/insights/paths_query_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -504,23 +504,23 @@ def to_actors_query(self) -> ast.SelectQuery | ast.SelectUnionQuery:
if self.query.pathsFilter.pathDropoffKey:
conditions.append(
parse_expr(
"path_dropoff_key = {path_dropoff_key} AND path_dropoff_key = path_key",
{"path_dropoff_key": ast.Constant(value=self.query.pathsFilter.pathDropoffKey)},
"path_dropoff_key = {key} AND path_dropoff_key = path_key",
{"key": ast.Constant(value=self.query.pathsFilter.pathDropoffKey)},
)
)
else:
if self.query.pathsFilter.pathStartKey:
conditions.append(
parse_expr(
"last_path_key = {path_start_key}",
{"path_start_key": ast.Constant(value=self.query.pathsFilter.pathStartKey)},
"last_path_key = {key}",
{"key": ast.Constant(value=self.query.pathsFilter.pathStartKey)},
)
)
if self.query.pathsFilter.pathEndKey:
conditions.append(
parse_expr(
"path_key = {path_end_key}",
{"path_end_key": ast.Constant(value=self.query.pathsFilter.pathEndKey)},
"path_key = {key}",
{"key": ast.Constant(value=self.query.pathsFilter.pathEndKey)},
)
)
else:
Expand All @@ -531,10 +531,12 @@ def to_actors_query(self) -> ast.SelectQuery | ast.SelectUnionQuery:

actors_query = parse_select(
"""
SELECT DISTINCT
person_id as actor_id
SELECT
person_id as actor_id,
COUNT(*) as event_count
FROM {paths_per_person_query}
WHERE {conditions}
GROUP BY person_id
ORDER BY actor_id
""",
placeholders={
Expand Down

0 comments on commit a9ea061

Please sign in to comment.