Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .capy-cache/docker-compose.capy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
services:
clickhouse:
mem_limit: !reset null
cpus: !reset null
proxy:
extra_hosts: !override
- 'plugins:host-gateway'
- 'capture:host-gateway'
- 'capture-ai:host-gateway'
- 'capture-logs:host-gateway'
- 'replay-capture:host-gateway'
- 'feature-flags:host-gateway'
- 'hypercache-server:host-gateway'
web:
image: caddy:latest
Comment thread
capy-ai[bot] marked this conversation as resolved.
Outdated
entrypoint: socat
Comment thread
capy-ai[bot] marked this conversation as resolved.
Outdated
command: ['TCP-LISTEN:8000,fork,reuseaddr', 'TCP:host.docker.internal:8011']
extra_hosts:
- 'host.docker.internal:host-gateway'
36 changes: 36 additions & 0 deletions frontend/src/generated/core/api.schemas.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

37 changes: 37 additions & 0 deletions frontend/src/generated/core/api.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

108 changes: 104 additions & 4 deletions frontend/src/scenes/data-management/definition/DefinitionView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,15 @@ import ViewRecordingsPlaylistButton from 'lib/components/ViewRecordingButton/Vie
import { FEATURE_FLAGS } from 'lib/constants'
import { LemonButton } from 'lib/lemon-ui/LemonButton'
import { LemonDialog } from 'lib/lemon-ui/LemonDialog'
import { LemonTable, LemonTableColumns } from 'lib/lemon-ui/LemonTable'
import { LemonTableLink } from 'lib/lemon-ui/LemonTable/LemonTableLink'
import { SpinnerOverlay } from 'lib/lemon-ui/Spinner/Spinner'
import { getPrimaryPropertyForEvent } from 'lib/utils/primaryEventProperty'
import { DefinitionLogicProps, definitionLogic } from 'scenes/data-management/definition/definitionLogic'
import {
DefinitionLogicProps,
PROPERTY_USAGE_EVENTS_PER_PAGE,
definitionLogic,
} from 'scenes/data-management/definition/definitionLogic'
import { EventDefinitionExperiments } from 'scenes/data-management/events/EventDefinitionExperiments'
import { EventDefinitionInsights } from 'scenes/data-management/events/EventDefinitionInsights'
import { EventDefinitionProperties } from 'scenes/data-management/events/EventDefinitionProperties'
Expand All @@ -28,6 +34,7 @@ import { LinkedHogFunctions } from 'scenes/hog-functions/list/LinkedHogFunctions
import { SceneExport } from 'scenes/sceneTypes'
import { urls } from 'scenes/urls'

import type { PropertyDefinitionEventUsageApi } from '~/generated/core/api.schemas'
import { SceneContent } from '~/layout/scenes/components/SceneContent'
import { SceneDivider } from '~/layout/scenes/components/SceneDivider'
import { SceneSection } from '~/layout/scenes/components/SceneSection'
Expand Down Expand Up @@ -122,11 +129,92 @@ function PrimaryPropertyDetail({ definition }: { definition: EventDefinition }):
)
}

function PropertyDefinitionUsageEvents({
events,
eventCount,
page,
loading,
onPageChange,
}: {
events: PropertyDefinitionEventUsageApi[]
eventCount: number
page: number
loading: boolean
onPageChange: (page: number) => void
}): JSX.Element {
const columns: LemonTableColumns<PropertyDefinitionEventUsageApi> = [
{
title: 'Event',
key: 'name',
render: function renderName(_, event) {
const title = getFilterLabel(event.name, TaxonomicFilterGroupType.Events) || event.name

return event.id ? (
<LemonTableLink to={urls.eventDefinition(event.id)} title={title} description={event.name} />
) : (
<span>{title}</span>
)
},
},
{
title: 'Last seen',
key: 'last_seen_at',
render: function renderLastSeen(_, event) {
return event.last_seen_at ? (
<div className="whitespace-nowrap">
<TZLabel time={event.last_seen_at} />
</div>
) : (
<span className="text-secondary">—</span>
)
},
},
]

return (
<SceneSection
title="Events using this property"
description="Events where PostHog has seen this event property."
>
<LemonTable
id="property-definition-events-table"
data-attr="property-definition-events-table"
columns={columns}
dataSource={events}
loading={loading}
pagination={{
controlled: true,
currentPage: page,
entryCount: eventCount,
pageSize: PROPERTY_USAGE_EVENTS_PER_PAGE,
onForward:
page * PROPERTY_USAGE_EVENTS_PER_PAGE < eventCount ? () => onPageChange(page + 1) : undefined,
onBackward: page > 1 ? () => onPageChange(page - 1) : undefined,
}}
rowKey="name"
nouns={['event', 'events']}
emptyState="No events use this property yet"
/>
</SceneSection>
)
}

export function DefinitionView(props: DefinitionLogicProps): JSX.Element {
const logic = definitionLogic(props)
const { definition, definitionLoading, definitionMissing, singular, isEvent, isProperty, metrics, metricsLoading } =
useValues(logic)
const { deleteDefinition } = useActions(logic)
const {
definition,
definitionLoading,
definitionMissing,
singular,
isEvent,
isProperty,
metrics,
metricsLoading,
propertyUsageEvents,
propertyUsageEventsLoading,
propertyUsageEventsPage,
} = useValues(logic)
const { deleteDefinition, setPropertyUsageEventsPage } = useActions(logic)

const memoizedQuery = useMemo(() => {
const columnsToUse =
Expand Down Expand Up @@ -434,6 +522,18 @@ export function DefinitionView(props: DefinitionLogicProps): JSX.Element {
</SceneSection>
</>
)}

{isProperty && definition.id !== 'new' && (
<>
<PropertyDefinitionUsageEvents
events={propertyUsageEvents.results}
eventCount={propertyUsageEvents.count}
page={propertyUsageEventsPage}
loading={propertyUsageEventsLoading}
onPageChange={setPropertyUsageEventsPage}
/>
</>
)}
</SceneContent>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,15 @@ describe('definitionLogic', () => {
useMocks({
get: {
'/api/projects/:team/event_definitions/:id': mockEventDefinitions[0],
'/api/projects/:team/event_definitions/:id/metrics/': {},
'/api/projects/:team/object_media_previews': { results: [] },
'/api/projects/:team/property_definitions/:id': mockEventPropertyDefinition,
'/api/projects/:team/property_definitions/:id/events/': {
count: 1,
results: [
{ id: mockEventDefinitions[0].id, name: mockEventDefinitions[0].name, last_seen_at: null },
],
},
},
})
initKeaTests()
Expand Down Expand Up @@ -49,9 +57,24 @@ describe('definitionLogic', () => {
router.actions.push(urls.propertyDefinition('1'))
logic = definitionLogic({ id: '1' })
logic.mount()
await expectLogic(logic).toDispatchActions(['loadDefinition', 'loadDefinitionSuccess']).toMatchValues({
definition: mockEventPropertyDefinition,
})
await expectLogic(logic)
.toDispatchActions([
'loadDefinition',
'loadDefinitionSuccess',
'setPropertyUsageEventsPage',
'loadPropertyUsageEvents',
'loadPropertyUsageEventsSuccess',
])
.toMatchValues({
definition: mockEventPropertyDefinition,
propertyUsageEvents: {
count: 1,
results: [
{ id: mockEventDefinitions[0].id, name: mockEventDefinitions[0].name, last_seen_at: null },
],
},
propertyUsageEventsPage: 1,
})
})

it('load new definition on mount', async () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { actions, afterMount, kea, key, listeners, path, props, reducers, selectors } from 'kea'
import { actions, afterMount, connect, kea, key, listeners, path, props, reducers, selectors } from 'kea'
import { loaders } from 'kea-loaders'
import { router } from 'kea-router'

import api from 'lib/api'
import { TaxonomicFilterGroupType } from 'lib/components/TaxonomicFilter/types'
import { lemonToast } from 'lib/lemon-ui/LemonToast/LemonToast'
import { Scene } from 'scenes/sceneTypes'
import { teamLogic } from 'scenes/teamLogic'
import { urls } from 'scenes/urls'

import { propertyDefinitionsEventsRetrieve } from '~/generated/core/api'
import type { PropertyDefinitionEventUsageResponseApi } from '~/generated/core/api.schemas'
import { updatePropertyDefinitions } from '~/models/propertyDefinitionsModel'
import { getFilterLabel } from '~/taxonomy/helpers'
import { Breadcrumb, Definition, EventDefinitionMetrics, ObjectMediaPreview, PropertyDefinition } from '~/types'
Expand All @@ -17,6 +20,8 @@ import { eventDefinitionsTableLogic } from '../events/eventDefinitionsTableLogic
import { propertyDefinitionsTableLogic } from '../properties/propertyDefinitionsTableLogic'
import type { definitionLogicType } from './definitionLogicType'

export const PROPERTY_USAGE_EVENTS_PER_PAGE = 100

export const createNewDefinition = (isEvent: boolean): Definition => ({
id: 'new',
name: `New ${isEvent ? 'Event' : 'Event property'}`,
Expand All @@ -36,10 +41,15 @@ export const definitionLogic = kea<definitionLogicType>([
path(['scenes', 'data-management', 'definition', 'definitionViewLogic']),
props({} as DefinitionLogicProps),
key((props) => props.id || 'new'),
connect(() => ({
values: [teamLogic, ['currentProjectId']],
})),
actions({
setDefinition: (definition: Partial<Definition>, options: SetDefinitionProps = {}) => ({ definition, options }),
loadDefinition: (id: Definition['id']) => ({ id }),
loadMetrics: (id: Definition['id']) => ({ id }),
loadPropertyUsageEvents: (id: Definition['id'], page = 1) => ({ id, page }),
setPropertyUsageEventsPage: (page: number) => ({ page }),
setDefinitionMissing: true,
loadPreviews: true,
createMediaPreview: (uploadedMediaId: string, metadata?: Record<string, any>) => ({
Expand All @@ -55,6 +65,12 @@ export const definitionLogic = kea<definitionLogicType>([
setDefinitionMissing: () => true,
},
],
propertyUsageEventsPage: [
1,
{
setPropertyUsageEventsPage: (_, { page }) => page,
},
],
})),
loaders(({ values, actions }) => ({
definition: [
Expand Down Expand Up @@ -116,6 +132,21 @@ export const definitionLogic = kea<definitionLogicType>([
},
},
],
propertyUsageEvents: [
{ count: 0, results: [] } as PropertyDefinitionEventUsageResponseApi,
{
loadPropertyUsageEvents: async ({ id, page }) => {
if (values.isEvent || !id || id === 'new') {
return { count: 0, results: [] }
}

return await propertyDefinitionsEventsRetrieve(String(values.currentProjectId), String(id), {
limit: PROPERTY_USAGE_EVENTS_PER_PAGE,
offset: (page - 1) * PROPERTY_USAGE_EVENTS_PER_PAGE,
})
},
},
],
previews: [
[] as ObjectMediaPreview[],
{
Expand Down Expand Up @@ -186,6 +217,13 @@ export const definitionLogic = kea<definitionLogicType>([
loadDefinitionSuccess: () => {
if (values.isEvent && values.definition.id && values.definition.id !== 'new') {
actions.loadPreviews()
} else if (values.isProperty && values.definition.id && values.definition.id !== 'new') {
actions.setPropertyUsageEventsPage(1)
}
},
setPropertyUsageEventsPage: ({ page }) => {
if (values.isProperty && values.definition.id && values.definition.id !== 'new') {
actions.loadPropertyUsageEvents(values.definition.id, page)
}
},
createMediaPreviewSuccess: () => {
Expand Down
Loading